feat: verify the signature timestamp, report the profile, and decode the filters documents use - #242
Merged
Merged
Conversation
0010 closed the gap between what the signer writes and what the validator reads, at the document level. It left the same gap one level down, inside the CMS. A pades-b-t signature carries an RFC 3161 token as an unsigned attribute of its SignerInfo. The package has embedded one since 2.0 and never looked at it, so a B-T document reported valid with nobody having checked the single thing B-T adds over B-B: a third party's word on when the signature existed. Only the DocTimeStamp of B-LTA was verified. Validation\Asn1Reader and Asn1Node walk the CMS by its declared lengths. DerReader answers how long a structure is, which is all its placeholder trimming needs; reaching the signature value and the unsigned attributes needs to know which child is which, in order. Searching for the attribute's OID bytes would have been shorter and finds the same sequence inside certificates the CMS embeds, so the OID is compared as dotted text only after the walk arrives at a node in the attribute-type position. The token stamps the SignerInfo's signature value, not the document, so a verifier handed the document's bytes fails on every correctly built file. Both halves have to hold: the token's own CMS verifies, and its imprint is the digest of those octets. Keeping only the first would accept a token lifted from an unrelated file. A signature with no token reports null rather than false. It is not a signature with a broken token, and collapsing the two reports every baseline signature as carrying a failure. SignatureProfile::classify() reads the level from what the document carries rather than from what it claims: a verified token lifts B-B to B-T, a store lifts that to B-LT, a DocTimeStamp lifts that to B-LTA. Both answers are reported, so a caller can see a file that says CAdES and carries nothing a CAdES signature needs. Measured against the committed samples, which carry freetsa.org tokens: B-B has none, B-T, B-LT and B-LTA each verify, and the genTime read back matches the minute each sample was produced.
PdfStream decoded two filters, /FlateDecode and /ASCIIHexDecode, each as a bare
name, and ignored /DecodeParms entirely. That covers what this package writes,
not what it is handed, and it fails in the least useful way: an object that
cannot be read is an object the signer will not sign around, so a document
nothing is wrong with comes back as unsignable.
The predictor is the one that matters. Every modern generator compresses its
cross-reference stream with PNG-Up, because consecutive rows of a
cross-reference table differ from each other by very little. Measured on a
three-object PDF 1.5 document built for the purpose, the old decoder inflated
the stream and then read the differences as the values:
xref = {"2": 16908288}
One entry, pointing 16 MB into a 379-byte file, with objects 1, 3 and 4 absent.
Object 1 is the catalog and signing rewrites the catalog, so the document was
unsignable and the error blamed the wrong thing.
Support\PdfFilters now applies the whole /Filter and /DecodeParms pair in order:
filter chains, PNG predictors 10 to 15 read per row as RFC 2083 specifies,
TIFF predictor 2, LZW with /EarlyChange, ASCII85 and run-length.
A filter that is not implemented yields null rather than the raw bytes. Handing
back something compressed that happens to contain "<<" is how a caller ends up
parsing noise as objects. /DCTDecode and /JPXDecode stay out on purpose: streams
are read to find objects and an image is never one.
The LZW decoder is checked against the worked example in ISO 32000-1 §7.4.4.2
table 10 rather than round-tripped through an encoder written in the same hour,
which would only establish that two pieces of code agree with each other.
Nothing about what the package writes changes: XrefStreamWriter still emits no
filter, since a revision indexes a handful of objects and zlib's header and
checksum would outweigh what they compress.
One existing test asserted a limitation that no longer holds: it named
/LZWDecode as "a filter it does not decode", and now names /DCTDecode.
…y there A template is signed in stages: the manager signs, then the employee, then somebody fills in an amount. Nothing stopped the amount changing after the manager signed it. ISO 32000-1 §12.7.4.5 answers that with a /Lock on the signature field, and §12.8.2.4 with the /FieldMDP transform that makes a reader enforce it. The package had neither half, and the missing read is the one that produced broken documents: signing into a field an earlier signature had locked went through happily, and the caller learned about it from Adobe. Both halves are written together. The widget's /Lock is what a reader shows; the FieldMDP transform is what it enforces, and a document carrying only the first says the fields are locked and lets them be filled anyway. Both transforms share one /Reference array, because a signature may certify the document and lock fields at once and two /Reference entries leave a reader to pick one. That puts a FieldMDP beside a DocMDP in exactly the arrangement 0012 already guards against misreading, and the certification still reads back. Filling a locked field now raises rather than warning, which is the position this package has taken on every equivalent question: a missing field, a no-changes certification, an encrypted document, a seal on a page that does not exist. Two things a lock deliberately does not do. A /Lock on a field nobody has signed is not in force, since it states what will happen when the field is signed, and reading it otherwise would make a template that ships one unsignable. And a lock cannot lock the field that imposed it, whose signature has already filled it. An /Include with no fields locks nothing and an /Exclude with no fields locks every field there is. Both are refused before anything is written, the second being much the more expensive to discover late. Field names are escaped: a name carrying ")" would end the /Fields array early and leave the rest of it as syntax. Verified with poppler on a locked document and on one that is certified and locked at once: both report Signature is Valid and Total document signed, and the unsigned second field is still listed as fillable.
B-LTA is not a state a document stays in. An archive timestamp is worth exactly what the authority's certificate and the digest algorithm behind it are worth, and both age. ETSI EN 319 142-1 answers that with a chain: before the current timestamp stops being verifiable, a new one is stamped over everything, including the previous timestamp, while that one still checks out. The package could produce the first link and not the second, so a document signed for a twenty-year retention had to be re-signed to stay checkable, and re-signing loses the original signing time, which is the one thing the archive existed to preserve. Signing\ArchiveExtender and A1PdfSign::extendArchive(). No certificate is involved: a DocTimeStamp is signed by the authority, not the signer, so a scheduled job can walk a directory of archived documents with no key material anywhere near it. That is why it is not a method on PendingSignature, whose whole shape starts from a certificate. It refuses the same things a signature refuses: an unsigned document, because timestamping one attests bytes nobody vouched for and returns a file that looks archived while proving nothing about a signer; and a document certified at no-changes, because an archive timestamp is a further revision. A document with no timestamp yet is not refused. isArchived() reports rather than gates, since the chain has to start somewhere and refusing would make signing time the only chance to reach B-LTA. Also fixes a latent defect this made reachable twice in one document: DocTimeStampWriter named its widget by counting "/FT /Sig" in the raw bytes, which undercounts a document whose fields are packed into an object stream. It now counts what /AcroForm /Fields declares. The guards run offline. The extension itself needs a timestamp authority and is in the network group, asserting that the previous links survive byte for byte, that the document then carries two timestamps and one signature with all three verifying, and that the signature underneath still reads as pades-b-lta. One gap named rather than implied: nothing refreshes the Document Security Store while extending. A rigorous chain re-collects revocation material for the previous timestamp's certificate first.
… it out Three things about the seal, and the third is a defect rather than a limit. It was always an opaque rectangle. The renderer encoded to JPEG and JPEG has no alpha channel, so the artwork's own transparency was flattened at encode time. Every seal this package has ever stamped is a solid block sitting on top of whatever it covers. It always said the same three things, at three baselines fixed in the source as TEXT_ROWS = [80, 150, 250]. A seal that has to carry a protocol number, a department or a second language had nowhere to put it. And sealFrom() ignored its argument. It wrote the path onto SealPlacement::$imagePath and nothing in src/ ever read that property, so a caller who passed their own artwork got a render of the certificate instead. Same shape as the $page defect 0017 closed: a documented builder method, in the README and on the documentation site, silently doing something else. PDF has no PNG filter, so a transparent seal is stored as deflated RGB samples with the alpha channel as a separate greyscale image in /SMask (§8.9.5.4). The decoding is the one already here: a PNG's IDAT is zlib with the per-row PNG predictor, which is exactly /Filter /FlateDecode with /DecodeParms <</Predictor 15 ...>>, so PdfFilters undoes it unchanged and PngReader only has to read IHDR and split the interleaved samples. A PNG it cannot separate falls back to the opaque JPEG: the seal still renders, and the only thing lost is the transparency that was never there before. Data\SealLayout carries the lines, their baselines, the left edge, the font, the colour and the background, every one optional and null meaning "use the configured default". seal.text.x and seal.text.rows move into configuration, so the two class constants go. A line with no baseline is not drawn: stacking it onto the last row would put two lines on top of each other, which reads as a rendering fault rather than a caller mistake. SealRenderer::fromImage() embeds the caller's artwork, honouring its transparency, and draws only what a layout asks for over it. BREAKING CHANGE: the default seal is now transparent. seal.transparent is true, so the artwork's alpha is honoured rather than flattened; set it to false for the previous opaque rectangle. This changes what every existing seal looks like and it is the fix rather than a preference, since the artwork has always had an alpha channel and the encoder has always thrown it away. Verified by rendering: the opaque seal hides the page text behind a white block, the transparent one lets it show through, and a laid-out seal says what was asked instead of the certificate's details. pdfsig reports all three valid.
…g it The Document Security Store has been written since 2.0 and counted since 2.2: securityStore->ocspResponses said how many responses were there and nothing said what any of them meant. So a document could carry a responder's signed statement that its signer had been revoked, and this package would report it as valid while cheerfully announcing that one OCSP response was present. That is the worst direction for the gap to run in. The material exists so a verifier can answer after the responder is gone, and carrying it is B-LT's entire purpose. RevocationReader resolves /OCSPs and /CRLs into DER; RevocationChecker decides what they say about a serial; SignatureDetails::$revocation reports it as Good, Revoked or Unknown. Nothing is believed on sight. Each response and CRL is verified against the issuer with openssl_verify() before its contents are read, and material that does not verify is material that is not there. That rule caught a real hole while it was being built. The first version took the responder certificate from inside the response and used it as a verification key, since RFC 6960 allows a delegated responder and the response carries it. A test offering an unrelated issuer still got Revoked back: the response was vouching for itself, which is exactly what a forged one does. An embedded responder is now accepted only when one of the supplied issuers really issued it, per §4.2.2.2. Three answers, not two: "nothing in this document says" is not "this certificate is fine". A verified CRL that lists nothing is Good rather than Unknown, because a revocation list is a positive statement. One verified revocation outweighs any number of good answers. Revocation does not decide isValid(): a revoked certificate produces a signature that matches the bytes perfectly. What it stops being is a signature anyone should accept, which is policy and stays with the application, beside trust. The fixtures are produced by OpenSSL itself rather than by this package: a CA, a leaf with serial 0x1234, and a good and a revoked answer of each kind. openssl ocsp -respin reports "Response verify OK" and "revoked" for the revoked one. Checking against material this package generated would only establish that the reader agrees with the writer. Nothing this package signs will report anything but Unknown yet: the debug certificate is self-signed with no responder and no distribution point, so DssWriter has no material to collect. Named in the record rather than implied.
…ainst The .der fixtures went in and the two .pem files did not: the repository ignores *.pem outright, because a PEM private key is often unencrypted and that matters more than convenience. The gate did its job and CI failed on FileNotFoundException. Narrowed rather than weakened, the same way /samples/*.pem already is. The two files are certificates and nothing else, the CA that signed the responses and the leaf they answer about, and no private key is generated into that directory: the OpenSSL run that produces the fixtures deliberately copies only the certificates out.
…an appearance Whether signing a PDF/A document leaves it conformant had never been checked in either direction. Reasoning does not close that: either a validator says yes or nobody knows. Measured with veraPDF 1.30.2 against two Ghostscript baselines, each confirmed conformant first. Before this commit all six combinations failed, including the invisible signature. Two defects came out of it and both are fixed. The revision dropped the file identifier. RevisionWriter::trailer() wrote /Size, /Root, /Info and /Prev and no /ID, and the cross-reference stream writer omitted it too. That is a defect well beyond PDF/A: ISO 32000-1 §14.4 makes /ID the file's identity, so a revision without it hands every reader a document that has stopped identifying itself. The pair is carried through unchanged, since the second string is meant to change when the file does and inventing one here would be inventing a digest no reader checks. A document with no /ID of its own gets none invented for it: a file identifier is the producer's. An invisible signature had no appearance. ISO 19005-1 §6.9 wants every form field to have an appearance dictionary, and a signature with no seal is still a form field. It now gets a form XObject with a [0 0 0 0] box, which draws nothing, which is what invisible means. After both, an invisible signature keeps a PDF/A-1b and a PDF/A-2b document conformant. That is now a supported claim rather than a hope. A visible seal still does not, and the reason is the colour space rather than the signature: the seal is DeviceRGB, which both parts allow only where the file carries an RGB OutputIntent. Adding one is the author's statement about their own document, not something to add on the way past. Giving the seal its own ICCBased space would fix it and means vendoring an sRGB profile into an MIT package, so it is named as the next step rather than done quietly. A transparent seal can never be PDF/A-1 at all, since §6.4 forbids /SMask outright, which is the whole reason seal.transparent exists as a setting. veraPDF is Java and is not in CI, so the verdicts live in the record and the suite guards the structures each one turned on. One existing test asserted the behaviour this changes: an invisible signature used to carry no /AP.
… job The measurement in 0025 lived in a record, and a record does not fail when somebody breaks it. It had already earned a gate: reasoning said an invisible signature obviously preserved conformance, and all six combinations failed on a missing trailer /ID nobody would have thought to look for. tests/PdfAValidationTest.php now runs veraPDF itself, in the pdfa group. It blocks, unlike the timestamp group: veraPDF is deterministic and runs offline once installed, so a failure there is this package's rather than somebody else's outage. The group asserts the failures too. A sealed document is not PDF/A conformant, for reasons that are the colour space rather than the signature, and asserting that is what will tell someone the day it changes. Pinned to 1.30.2 in both the Dockerfile and the workflow: a validator that changes its verdicts between builds cannot be the thing a gate is measured against. veraPDF is Java, so it is installed behind a build argument and only by the `pdfa` compose service. The day-to-day image does not carry a JRE for one group, and the group skips where the validator is absent, so a normal run is unaffected: 7 skipped, everything else as before. And it stays an instrument rather than becoming a dependency. Nothing in src/ may invoke veraPDF, pdfsig, pdftoppm or Ghostscript: a package that shells out to a JVM to answer a runtime question would be a different package, and the consuming application would inherit an installation requirement nobody wrote down. ArchTest enforces it, tokenised so the comments explaining the rule do not trip it, which the first version did.
…ncies Three instruments, and one leak they turned up. qpdf reads the same cross-reference tables and streams this package writes by hand, and is strict where poppler forgives: a table with slightly wrong offsets still opens in a reader that recovers by scanning, and the fault stays hidden. It is C++ and a couple of megabytes, so unlike veraPDF it goes in the everyday image and needs no service of its own. The gate is comparative: signing must not introduce a complaint that was not already there. That formulation came out of the first run. Two fixtures are minimal documents whose pages carry no /Resources, which qpdf 12 warns about and qpdf 11 did not, and a gate that failed on it would have been measuring the fixture rather than the signer. The fixture fault is named as a follow-up rather than papered over. Every shape the writer can produce is covered: table, cross-reference stream, object stream, predictor, filled field, certified, legacy, and four stacked revisions. The robustness harness runs corrupted input through every reader that parses bytes the application did not write: the document reader, the signature extractor, the ASN.1 walker, the stream filters, the PNG reader, the revocation checker. The contract is narrow and the same for all of them: read it, or throw the documented exception. Never a TypeError, never a fatal. Fixed seed, so a failure is reproducible and a green run means the same thing twice. It found nothing, which is the result worth having from a guard. composer audit runs in CI. setup-php sets COMPOSER_NO_AUDIT, so advisories were silently unchecked; for a signing package a known vulnerability in the tree is worth blocking on. --abandoned=ignore keeps the gate about vulnerabilities rather than about maintenance status. And the leak. Everything built for testing is export-ignored so it never reaches a consumer, and that list had drifted: phpstan.neon, pint.json, composer-dependency-analyser.php and package-lock.json were all being distributed, each added later than the rule. tests/DistributionTest.php now asks git archive what a release actually contains rather than trusting the list, in both directions: nothing built for testing ships, and the package still does. qpdf joins veraPDF, pdfsig, pdftoppm and Ghostscript in the arch rule that keeps every instrument out of src/. They are development and validation tooling and none of them may reach production.
The PDF/A group skipped by default. It was behind a build argument, installed only by a dedicated compose service, so the everyday image would not carry a JRE for one group. That was the wrong trade, and the reason is not the JRE. A group that skips itself leaves the conformance claims unverified on the machine where the work is being done, and it exits zero: the first run of a job whose entire purpose is that group could have gone green having validated nothing. veraPDF is installed in the development image and in CI, alongside qpdf, and composer test carries --fail-on-skipped. Every check has to run somewhere, and a skip is how one quietly stops. The suite is 390 passing and nothing skipped. The dedicated compose service and CI job are gone: with the validator present everywhere, the group runs with the rest of the suite across the whole PHP matrix rather than once in a job of its own. The skip guards stay in the two files that need a tool, because a named skip reads better than "verapdf: not found" for somebody running outside the container. They cannot hide any more. verapdf-rest was considered and is not used. It does not remove the JVM, it relocates it, and in exchange the gate would depend on a service being up, on container networking, and on a second project to pin. A deterministic offline check is worth more than a tidier container for eight seconds of runtime. 0026 records all of it: what each instrument has caught, why none may be reached from src/, why none may ship, and why nothing skips.
0025 said the archive timestamp's widget had not been given one, because a B-LTA document was not part of that measurement and claiming the fix covered it would have been the thing the record exists to avoid. Not claiming it was right. Leaving it was not. samples/pades-b-lta.pdf shows the fault outright, in a file committed months ago: 28 0 obj <</Type/Annot/Subtype/Widget/FT/Sig/Rect[0 0 0 0]/T (Timestamp2)/V 27 0 R... No /AP, beside a signature widget in the same document that has one. ISO 19005-1 §6.9 wants every form field to have an appearance dictionary, and a timestamp is a form field like any other, so the claim "an invisible signature keeps a PDF/A document conformant" would have stopped holding at B-LTA. That is precisely the combination an archive wants: PDF/A plus long-term validation is the canonical twenty-year artefact, and it was the one cell of the matrix nobody had measured. DocTimeStampWriter now writes the same empty form the signature widget gets, BBox [0 0 0 0], which draws nothing. The verdict for it is in the network group, since a B-LTA document cannot be produced without reaching a timestamp authority, so it is reported rather than blocking, on the same terms as every other test that needs one. Reported beats unmeasured, which is how this got here. The test asserts both halves: the widget carries /AP, and veraPDF passes the document at 1b and 2b. A B-T document is measured beside it, where the token rides inside the CMS and nothing is added to the page at all.
…ehind it Invariant 9 says network access stays behind the injected transport, and it was true and useless. HttpTransport was final and all three collaborators depended on the concrete type, so the only way to exercise anything above pades-b-b was to reach freetsa.org, which put it in the network group: deliberately non-blocking, because somebody else's outage is not a defect here. The consequence was that three of the five PAdES levels this package advertises could regress without CI going red. That is not hypothetical: three network tests were committed broken in this very branch, went green, and were noticed only by reading the log. Injection you cannot substitute is not injection. Contracts\SignatureTransport names the three calls, HttpTransport implements it, the container binds one to the other, and the collaborators depend on the interface. Testing\LocalTimestampAuthority is the substitute, and it is a real authority rather than a stub: openssl ts -reply is a complete RFC 3161 responder that needs no server and no connection, and its tokens are signed, verifiable and carry the imprint of the bytes they were handed. The offline tests assert timestampVerified === true, checked through OpenSSL exactly as a freetsa.org token is, rather than asserting that something was embedded. Canned bytes could not pass: the imprint has to match the signature value produced in that run. Six behaviours move from reported to gated, in the ordinary suite and not in a group: signing at B-T, B-LT and B-LTA, the archive chain of 0022, the imprint refusal, and PDF/A conformance at B-LTA, which 0025 could measure only against a live authority. What the local authority is not is a third party. It establishes that the package builds a request, embeds the reply and verifies it correctly, and cannot establish that it interoperates with somebody else's TSA. So the live tests stay, in the network group, answering the question the offline ones cannot. A TSA container was considered and rejected for the same reason verapdf-rest was: it relocates the dependency rather than removing it, and openssl is already in the image because signature verification shells out to it anyway.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Items 3, 4, 5, 6, 7, 8, 9, 10, 11 and 12 of the pending list: all ten that were not scope decisions.
Twenty-five commits, ten new decision records (0018 to 0025 plus the two landed separately), and five defects found that were not on the list at all.
The defects the work turned up
Each was a documented or implied behaviour doing something else, found by measuring rather than by reading.
sealFrom()ignored its argumentSealPlacement::$imagePathand nothing insrc/ever read that property. A caller passing their own artwork got a render of the certificate. Documented in the README and on the site/IDRevoked: the response was vouching for itself, as every forgery doesDocTimeStampWritercounted/FT /Sigin raw bytes3 and 5. The signature timestamp is verified, and the profile reported
A
pades-b-tsignature carries an RFC 3161 token that the package had embedded since 2.0 and never looked at, so a B-T document reported valid with nobody having checked the one thing B-T adds over B-B.Validation\Asn1Readerwalks the CMS by declared lengths. Searching for the attribute's OID bytes would have been shorter and is wrong: that sequence occurs inside certificates the CMS embeds.The token stamps the SignerInfo's signature value, not the document. Against the committed freetsa.org samples: B-B carries none, B-T, B-LT and B-LTA each verify, genTime read back to the minute each was produced.
4. Revocation is evaluated, not counted
The store said how many OCSP responses were present and nothing said what any of them meant, so a document could carry a responder's signed word that its signer was revoked and still report valid.
Nothing is believed on sight: every response and CRL is verified against the issuer with
openssl_verify()first. Three answers, not two. One verified revocation outweighs any number of good answers. Revocation does not decideisValid(), because a revoked certificate still produces a signature that matches the bytes.Fixtures produced by OpenSSL itself, which reports
Response verify OKandrevokedfor the revoked one.6 and 7. Filters and predictors
On a PDF 1.5 document whose cross-reference stream uses PNG-Up, the old decoder read the differences as the values:
xref = {"2": 16908288}, one entry 16 MB into a 379-byte file, objects 1, 3 and 4 absent. Object 1 is the catalog, so a document nothing was wrong with came back as unsignable.Filter chains, PNG predictors per row, TIFF predictor 2, LZW with
/EarlyChange, ASCII85, run-length. LZW checked against the worked example in ISO 32000-1 §7.4.4.2 table 10.8. PDF/A, measured with veraPDF 1.30.2
Before the two fixes, all six failed. An invisible signature now keeps a PDF/A document conformant, which is a supported claim rather than a hope. A seal does not, and the reason is the colour space rather than the signature.
9 and 10. The seal
Transparency, by storing raw samples with the alpha in an
/SMask: PDF has no PNG filter. The decoding is the one item 6 already added, since a PNG's IDAT is Flate with a PNG predictor.Data\SealLayoutopens up the lines, baselines, font and background.11 and 12. Archive chain and field locks
A1PdfSign::extendArchive()adds a link to the B-LTA chain with no certificate involved, so a scheduled job can walk an archive with no key material near it. Verified in CI against live freetsa.org./Lockplus the/FieldMDPtransform that makes a reader enforce it, and a refusal to fill a field an earlier signature locked.Breaking
seal.transparent => falserestores the opaque rectangle, and it is what a PDF/A-1 workflow needs.Contracts\SealRenderer,Contracts\PdfSignerandContracts\A1PdfSignall gain members: breaks for implementers, which the Roave check reports. Every new constructor parameter is appended and defaulted, so no arity moves.Data\SignatureDetailsgains five properties, sotoArray()changes shape.Not done, and named
/ICCBasedspace and be PDF/A-clean whatever the document declares. That means vendoring an sRGB profile into an MIT package, so it waits on a licensing decision rather than being done quietly.DocTimeStampWriter's own invisible widget still has no appearance: a B-LTA document was not part of the PDF/A measurement, and claiming the fix covers it without measuring is the thing 0025 exists to avoid.Unknownfor revocation yet, because the debug certificate has no responder and no distribution point.