Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/A1PdfSignManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function encryptCertificate(
/**
* encryptCertificate() stores the PEM bundle, so this parses it directly.
* The v1 helper wrote it to a .pfx and fed it to `openssl pkcs12 -in`,
* which expects binary PKCS#12 and always failed — see
* which expects binary PKCS#12 and always failed. See
* docs/history/v2-modernization.md.
*/
public function decryptCertificate(
Expand Down Expand Up @@ -160,7 +160,7 @@ private function read(
/**
* Reads whichever encoding turned up.
*
* Signing keeps explicit siblingssignFromFile() and signFromPem() so
* Signing keeps explicit siblings, signFromFile() and signFromPem(), so
* the caller states what it holds. encryptCertificate() has no sibling and
* takes "a certificate" generically, so it detects instead. The two
* encodings are trivially distinguishable, text marker against binary, so
Expand Down
2 changes: 1 addition & 1 deletion src/Certificates/CertificateParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function parse(
// The key is passed as [bundle, password] rather than as a bare string:
// the string form cannot decrypt a passphrase-protected private key, so
// a PEM carrying one failed here with an exception naming the wrong
// cause. PKCS#12 never exposed it openssl_pkcs12_read() hands back an
// cause. PKCS#12 never exposed it: openssl_pkcs12_read() hands back an
// already-decrypted key. The array form is correct for both, so there is
// nothing to branch on. See docs/decisions/0007-pem-second-entry-one-pipeline.md.
if (! openssl_x509_check_private_key($x509, [$pem, $password])) {
Expand Down
4 changes: 2 additions & 2 deletions src/Certificates/CertificateVault.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Encrypts a parsed certificate for storage, and reads it back.
*
* Each vault carries its own key. seal() returns that key alongside the
* ciphertext, and open() needs it losing it means losing the certificate.
* ciphertext, and open() needs it: losing it means losing the certificate.
*/
final readonly class CertificateVault
{
Expand Down Expand Up @@ -62,7 +62,7 @@ public function seal(
/**
* Restores a sealed certificate.
*
* What seal() stored is the PEM bundle, so it is parsed directly no
* What seal() stored is the PEM bundle, so it is parsed directly: no
* PKCS#12 conversion, no temporary file and no shell-out. The v1 pair
* wrote the PEM to a .pfx and fed it back to `openssl pkcs12 -in`, which
* expects binary PKCS#12 and always failed. See docs/history/v2-modernization.md.
Expand Down
2 changes: 1 addition & 1 deletion src/Certificates/NativeCertificateReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function read(
/**
* Rebuilds the bundle as the combined PEM the signer expects.
*
* The order certificate, private key, then the CA chain matches what
* The order (certificate, private key, then the CA chain) matches what
* `openssl pkcs12 -nodes` writes, so output stays interchangeable with the
* legacy reader's.
*
Expand Down
12 changes: 6 additions & 6 deletions src/Certificates/PemCertificateReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
use SensitiveParameter;

/**
* Reads PEM the degenerate case of {@see CertificateReader}.
* Reads PEM, the degenerate case of {@see CertificateReader}.
*
* The other two readers exist to convert PKCS#12 into PEM before handing it to
* {@see CertificateParser}. PEM is already that destination format, so this
* reader has no conversion step: it checks the input is what it claims to be,
* and delegates. Everything downstream is unchanged, which is the whole point
* and delegates. Everything downstream is unchanged, which is the whole point:
* one pipeline, reached through a second entry (docs/decisions/0007-pem-second-entry-one-pipeline.md).
*
* It carries no legacy/native axis, so it is not built by {@see ReaderFactory};
Expand All @@ -36,7 +36,7 @@ public function __construct(private CertificateParser $parser) {}
/**
* Whether these bytes carry a PEM certificate.
*
* Callers that accept either encoding the pdf:sign command, the vault
* Callers that accept either encoding (the pdf:sign command, the vault)
* route on this, so "what counts as PEM" is decided in one place rather
* than re-implemented per entry point.
*/
Expand All @@ -49,7 +49,7 @@ public static function looksLikePem(string $contents): bool
* Reads a bundle holding both the certificate and its private key.
*
* The password defaults to empty because, unlike PKCS#12, a PEM private key
* is frequently unencrypted and OpenSSL ignores a passphrase given for a
* is frequently unencrypted, and OpenSSL ignores a passphrase given for a
* key that does not need one, so the default is safe either way.
*
* @param string $contents A PEM bundle: certificate and private key, in any order.
Expand All @@ -72,7 +72,7 @@ public function read(
/**
* Reads a certificate and a private key that arrived as separate files.
*
* The two are checked separately so the message names the file at fault
* The two are checked separately so the message names the file at fault:
* passing the same path twice is a real mistake, and it reads as "no
* private key" rather than as something about the certificate.
*
Expand Down Expand Up @@ -105,7 +105,7 @@ private function requireCertificate(string $contents, string $label): void
// on DER without saying why, and a .pfx handed to the PEM entry point
// would otherwise be reported as malformed rather than as misrouted.
throw new InvalidPemContentException(str_starts_with($contents, self::DER_PREFIX)
? "Expected PEM in {$label}, found binary DER or PKCS#12 bytes — read those through certificate() instead."
? "Expected PEM in {$label}, found binary DER or PKCS#12 bytes. Read those through certificate() instead."
: "No PEM certificate block found in {$label}.");
}

Expand Down
4 changes: 2 additions & 2 deletions src/Certificates/ReaderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
* Picks the certificate reader.
*
* Native is the default. The CLI is only reached when legacy mode is on,
* because that is the single capability ext-openssl cannot provide reading
* because that is the single capability ext-openssl cannot provide: reading
* RC2/40-bit bundles under OpenSSL 3.x. See docs/decisions/0001-openssl-native-with-cli-fallback.md.
*/
final readonly class ReaderFactory
{
/**
* The temp path comes from the A1PdfSign contract, but resolving it here
* would close a cycle the manager depends on this factory. The container
* would close a cycle: the manager depends on this factory. The container
* is held instead and the contract resolved only when the CLI reader is
* actually built, which is the rare path.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/ValidatePdfSignatureCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function handle(): int
$status = $signature->verified ? 'verified' : 'NOT verified';
$scope = $signature->coversWholeDocument ? 'covers the whole file' : 'covers its own revision';

$this->line(sprintf(' %d. %s %s, %s', $index + 1, $signer, $status, $scope), 'info');
$this->line(sprintf(' %d. %s: %s, %s', $index + 1, $signer, $status, $scope), 'info');
}

return $validated->isValid() ? self::SUCCESS : self::INVALID;
Expand Down
2 changes: 1 addition & 1 deletion src/Contracts/CertificateReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* Turns encoded certificate bytes into a parsed certificate.
*
* Implementations differ only in the encoding they ingest PKCS#12, whether
* Implementations differ only in the encoding they ingest: PKCS#12, whether
* read natively or through the CLI, and PEM, which needs no conversion at all.
* All of them converge on the same PEM bundle and the same
* {@see \LSNepomuceno\LaravelA1PdfSign\Certificates\CertificateParser}.
Expand Down
2 changes: 1 addition & 1 deletion src/Exceptions/InvalidPemContentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* The input is not the PEM it was supposed to be.
*
* This covers only what can be decided before parsing a missing block, or
* This covers only what can be decided before parsing: a missing block, or
* binary bytes handed to the PEM entry point. A certificate and key that are
* both present but do not belong together is a different failure, and keeps
* its own class: {@see InvalidX509PrivateKeyException}.
Expand Down
4 changes: 2 additions & 2 deletions src/Seal/InterventionSealRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
/**
* Renders the seal with Intervention Image.
*
* Everything the v1 code hard-coded driver, font file, size, colour and the
* background image now comes from configuration, and the result is returned
* Everything the v1 code hard-coded (driver, font file, size, colour and the
* background image) now comes from configuration, and the result is returned
* as bytes rather than written to a temporary file.
*/
final readonly class InterventionSealRenderer implements SealRenderer
Expand Down
2 changes: 1 addition & 1 deletion src/Signing/Cades/HttpTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* The HTTP the signature primitives deliberately do not do.
*
* tc-lib-pdf-sign keeps its codecs pure and takes transports as callables, so
* the host owns networking and therefore owns the SSRF surface. Every URL
* the host owns networking, and therefore owns the SSRF surface. Every URL
* reached here comes from configuration or from an extension inside the
* signer's own certificate, never from the document being signed.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Signing/Incremental/ByteRangeCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function apply(string $pdf, int $contentsHexLength): string
* The spacing is not fixed: this package writes "/Contents <" while
* tc-lib-pdf-sign writes "/Contents<". Matching a literal meant the
* document timestamp revision found the *signature's* placeholder instead
* of its own and overwrote it poppler reported the signer as the
* of its own and overwrote it: poppler reported the signer as the
* timestamp authority and the digest as mismatched.
*
* @throws InvalidPdfFileException
Expand All @@ -84,7 +84,7 @@ public function lastContentsOffset(string $pdf): int
* Reads back the /ByteRange of the revision just written.
*
* An already-signed document holds several; ours is always the last. Taking
* the first would overwrite a previous signature's /Contents the bug that
* the first would overwrite a previous signature's /Contents, the bug that
* PoC 0b surfaced, which is why this is a named method with its own test.
*
* @return array{0: int, 1: int, 2: int} Offset of '<', offset past '>', trailing length.
Expand Down
4 changes: 2 additions & 2 deletions src/Signing/Incremental/DocTimeStampWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
* Appends the archive timestamp that makes a document PAdES B-LTA.
*
* B-LT proves the certificate was good when it was used. B-LTA proves the
* whole filesignature and validation material together existed at a point
* whole file, signature and validation material together, existed at a point
* in time attested by an authority, which is what keeps it verifiable once the
* signing algorithms themselves age out.
*
* Unlike a signature timestamp, which covers only the signature bytes, this one
* covers the entire file through its own /ByteRange, and it is a bare RFC 3161
* token rather than a CAdES structure hence /SubFilter /ETSI.RFC3161.
* token rather than a CAdES structure, hence /SubFilter /ETSI.RFC3161.
*
* @internal
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Signing/Incremental/DssWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function append(string $pdf, Certificate $certificate): string
*
* A self-signed certificate has neither an OCSP responder nor a CRL
* distribution point, and an unreachable responder must not fail the
* signature in both cases the document simply stays at B-T.
* signature: in both cases the document simply stays at B-T.
*
* @return array{certs: list<string>, ocsp: list<string>, crls: list<string>}|null
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Signing/Incremental/SealAppearance.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* A visible signature is a widget whose /AP points at a form XObject, which in
* turn draws an image XObject (ISO 32000-1 §12.5.5 and §12.7.4.5). The JPEG is
* embedded through /DCTDecode, so the bytes Intervention produced are stored
* as they are no decode and re-encode.
* as they are: no decode and re-encode.
*
* @internal
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Signing/IncrementalSigner.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* This is the default path, and it is what makes multiple signatures possible:
* each one covers the file up to its own revision, so signing again does not
* invalidate what came before. It also stops the silent damage the v1 flow
* caused rebuilding a document through FPDI discarded annotations, form
* caused: rebuilding a document through FPDI discarded annotations, form
* fields and any signature already present. See docs/decisions/0006-incremental-revision.md.
*
* Proven by poc/incremental-signature: three signatures, all valid.
Expand Down
4 changes: 2 additions & 2 deletions src/Signing/PendingSignature.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function certificateFromUpload(
* as .pem, .crt, .cer, .key and .txt, so the format is decided by content
* (docs/decisions/0007-pem-second-entry-one-pipeline.md).
*
* @param string $password Empty when the private key is unencrypted legal and
* @param string $password Empty when the private key is unencrypted, legal and
* common for PEM, impossible for PKCS#12.
*
* @throws FileNotFoundException
Expand All @@ -118,7 +118,7 @@ public function certificatePem(
}

/**
* The same, from bytes the caller already holds an upload, a secret
* The same, from bytes the caller already holds: an upload, a secret
* manager, a database column.
*
* @throws InvalidPemContentException
Expand Down
2 changes: 1 addition & 1 deletion src/Support/ProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* docs/decisions/0001-openssl-native-with-cli-fallback.md.
*
* It runs through Laravel's process factory rather than Symfony's Process
* directly: the behaviour is identical the factory builds the same object
* directly: the behaviour is identical (the factory builds the same object)
* but a host application can fake it in its own tests, which is impossible
* when the class is instantiated inline.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Support/TemporaryFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* A file that deletes itself.
*
* The v1 code deleted its temporary files with a call placed after the work
* that might throw, so any failure leaked them including PEM files holding
* that might throw, so any failure leaked them, including PEM files holding
* a private key. Here deletion happens in a finally block, with the destructor
* as a backstop. See docs/history/v2-modernization.md.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Testing/DebugCertificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static function make(int $daysValid = 600): array
* fixtures rather than one (docs/decisions/0007-pem-second-entry-one-pipeline.md).
*
* @return array{0: string, 1: string, 2: string} Certificate PEM, private key PEM, and the
* key's password empty when it is unencrypted.
* key's password, empty when it is unencrypted.
*/
public static function makePem(bool $encryptKey = true, int $daysValid = 600): array
{
Expand Down
2 changes: 1 addition & 1 deletion src/Validation/DerReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* A signature's /Contents is a fixed-width placeholder padded with zeros, so
* the CMS has to be cut at its declared length. Trimming the padding with
* rtrim() instead would cut legitimate 0x00 bytes off the end of the DER a
* rtrim() instead would cut legitimate 0x00 bytes off the end of the DER, a
* bug PoC 0b hit and this exists to keep fixed.
*
* ISO/IEC 8825-1 §8.1.3: a first length byte below 0x80 is the length itself;
Expand Down
4 changes: 2 additions & 2 deletions src/Validation/PdfSignatureExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* Pulls every signature out of a document.
*
* The 1.x code read `$result[2][0]`the first match only so a document
* The 1.x code read `$result[2][0]`, the first match only, so a document
* with more than one signature reported on the first and ignored the rest.
* Now that the package emits multi-signature documents, that would mean it
* could not describe its own output.
Expand Down Expand Up @@ -51,7 +51,7 @@ public function extract(string $pdf): array
* Whether the entry is an archive timestamp rather than a signature.
*
* A /DocTimeStamp carries an RFC 3161 token, whose SignedData signs the
* TSTInfo holding the document's hash not the document itself. Verifying
* TSTInfo holding the document's hash, not the document itself. Verifying
* it the way a signature is verified always fails, so it has to be told
* apart before anything tries.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Validation/Pkcs7Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Reads the certificates embedded in a detached CMS.
*
* 1.x shelled out to `openssl pkcs7 -print_certs` and parsed the human-readable
* output with three chained preg_replace calls which broke outright when
* output with three chained preg_replace calls, which broke outright when
* OpenSSL 3.5 changed its field separator (§1.9, §1.14). Here the DER is
* scanned for certificate structures and each one is handed to
* openssl_x509_parse(), so the result is structured data rather than text.
Expand Down Expand Up @@ -55,7 +55,7 @@ public function parsedCertificates(string $der): array
*
* Certificates sit inside the SignedData's certificate set as DER
* SEQUENCEs. Rather than walking the whole CMS grammar, candidates are
* offered to openssl_x509_read() and kept when it accepts them the
* offered to openssl_x509_read() and kept when it accepts them: the
* parser itself decides what is a certificate.
*
* @return list<string>
Expand Down
4 changes: 2 additions & 2 deletions src/Validation/SignatureVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
* Decides whether a detached CMS matches the bytes it covers.
*
* This is the one part of validation that shells out, and deliberately so.
* PHP's openssl_pkcs7_verify() cannot take detached content it only writes
* the verified content out and reconstructing an S/MIME envelope around
* PHP's openssl_pkcs7_verify() cannot take detached content (it only writes
* the verified content out) and reconstructing an S/MIME envelope around
* binary PDF bytes fails on MIME canonicalisation. The alternative is walking
* the CMS grammar by hand to check the message-digest attribute and verify the
* signed attributes, which is exactly the kind of code whose bugs produce a
Expand Down
4 changes: 2 additions & 2 deletions tests/CertificatesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
| PEM bundles
|--------------------------------------------------------------------------
|
| The parser has always accepted PEM every reader converges on it. What it
| The parser has always accepted PEM: every reader converges on it. What it
| could not do is validate a passphrase-protected private key, because the
| bundle was handed to openssl_x509_check_private_key() as a bare string.
| PKCS#12 never reached that path: openssl_pkcs12_read() returns a key that is
Expand Down Expand Up @@ -211,7 +211,7 @@
})->throws(InvalidPemContentException::class, 'No PEM certificate block found in the bundle');

it('still reports a key that does not match its certificate as such', function () {
// The format is fine here, so this is not a PEM problem it keeps the
// The format is fine here, so this is not a PEM problem: it keeps the
// exception that already says exactly this, rather than a second one.
[$certificate] = DebugCertificate::makePem();
[, $otherKey, $otherPassword] = DebugCertificate::makePem();
Expand Down
2 changes: 1 addition & 1 deletion tests/CommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
});

it('signs with a PEM certificate through the pdf:sign command', function () {
// No flag says "this is PEM" the command reads the encoding from the
// No flag says "this is PEM": the command reads the encoding from the
// bytes, because PEM ships under half a dozen extensions.
[, , $bundlePath, $pass] = pemCertificate();

Expand Down
2 changes: 1 addition & 1 deletion tests/DssTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
->sign();

// A self-signed certificate has no OCSP responder and no CRL distribution
// point, so only the chain itself is embedded which is still worth
// point, so only the chain itself is embedded, which is still worth
// carrying, since a verifier then needs to fetch nothing.
expect($longTerm->contents)->toContain('/Type /DSS')
->toContain('/Certs')
Expand Down
2 changes: 1 addition & 1 deletion tests/PadesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
$hex = rtrim($matches[1] ?? '', '0');
$der = (string) hex2bin(strlen($hex) % 2 === 1 ? $hex . '0' : $hex);

// OID 1.2.840.113549.1.9.16.2.47 id-aa-signingCertificateV2.
// OID 1.2.840.113549.1.9.16.2.47, id-aa-signingCertificateV2.
$oid = hex2bin('2A864886F70D010910022F');

expect($der)->toContain((string) $oid);
Expand Down
Loading