Repository navigation
V2 spec follow-ups: SSL.com DCV, reissue endpoint, revoke fallback, token retry (0094, 0095) - #40
Open
spbsoluble wants to merge 11 commits into
Open
spbsoluble wants to merge 11 commits into
spbsoluble wants to merge 11 commits into
Conversation
…endpoints (0094 item 1)
Each pending V2 domain is looked up with GET /domains?search=&exactMatch=true. For
validatedBy=SSLCOM the plugin reads SSL.com's record from GET /domains/{id}/dcv, publishes
its dns-txt recordName/recordValue (not DcvTxtRecordTemplate), verifies with
POST /domains/{id}/dcv/verify {optionId}, and cleans up. dns-cname/http-url can't be staged
through IAnyCAPlugin 3.3's TXT-only IDomainValidator, so those domains stay pending with a
warning. 502s, unknown shapes and 422 PENDING/UNDER_REVIEW leave the order pending for sync;
nothing is cancelled. A failed lookup or emSign row keeps the order-level flow. UCC orders
route each domain by its own row.
Live (sandbox): the /domains row id is domainId; the account has no SSLCOM domains, so the
SSL.com path is unit-tested only.
…tion; match CERTInext's newer reason-rejection wording (0094 item 2) The 2.0.0 spec dropped privilege-withdrawn from the SSL/TLS revoke enum, and live (2026-11-13) now rejects it with 422 "Revocation reason privilege-withdrawn is not allowed." - a different wording from the "Invalid Revoke Reason ID" the fallback matched, so a reason-9 revoke failed with no retry. The first attempt still sends privilege-withdrawn; a rejection in either wording now retries once with cessation-of-operation. Opt-in live tests: privilege-withdrawn acceptance on an own order, and a per-reason acceptance probe.
…094 item 3) The spec's SSL/TLS enum now has 9 values including aa-compromise and no privilege-withdrawn; the bullet said 8 and that aa-compromise was Private PKI/Document Signer only.
…revettingToken (0094 item 4) SslOrganizationRef names it prevettingToken; the DTO sent preVettingToken. Nothing sets the token today, so this is wire-neutral; a serialization test pins the spelling.
…e; V2ReissueRevokePrevious (0094 item 5) A V2 SSL/TLS Reissue, or a RenewOrReissue whose prior certificate is unexpired and outside RenewalWindowDays (V1's reissue boundary), resolves the prior order from PriorCertSN and calls /reissue in rekey mode with the new CSR and UCC additionalDomains. The derived order id (e.g. 1984857369R001) becomes the CARequestID and the normal post-create flow (track, agreement, DCV, pickup) runs on it. update-sans is never used: Command gives no signal that only SANs changed. New connector setting V2ReissueRevokePrevious (Boolean, default false; constants, config, annotation, ValidateCAConnectionInfo, manifest): sends revokePrevious=true with revokeReason=superseded, audit-logged before the call. No prior order, or a 404/422, falls back to a new order and the result message says so. A 5xx, lost response or 2xx without an order id throws with the do-not-resubmit wording and places nothing. Private PKI/Document Signer keep their behaviour. Order ids were already handled as opaque strings everywhere (audited); a WireMock test round-trips a derived id through enroll, GetSingleRecord and Sync. Opt-in live test (not yet run): CERTINEXT_V2_LIFECYCLE_REISSUE_ENDPOINT=1 + CERTINEXT_V2_REISSUE_SOURCE_ORDER_ID.
… transport detail (0095) A token request that gets no HTTP response is retried twice (1 s, 3 s); it is idempotent and creates nothing. HTTP answers (400/401/403 included) are final, and order creates are still never retried. The failure log and exception now carry TransportDetail (RestSharp ResponseStatus plus the exception chain's types/messages, passed through RedactCredentials with the configured secret values replaced and capped) instead of only HttpStatus=0. LogV2ApiFailure/ThrowOnV2Failure, the inline-CSR create and the reissue do the same for any V2 call with no response; ThrowOnV2Failure keeps 'HTTP 0.' first so IsTransportLevelCsrFailure still reads status 0. Tests drive a real dropped connection through a loopback TCP proxy (WireMock's faults still send a 200 status line): enroll, sync and connector validation each succeed with two token attempts; a permanent drop tries three times with no secret in the message; 400/401/403 are tried once.
… acceptance; tidy reissue result message Sync_UnresolvedReportStatuses_Probe (read-only) lists every /reports/orders status pair, the rows the mapping can't place (UnresolvedStatusFallbacks) and a Track Order sample of rows whose two fields disagree. Live: the only unmapped pair is 'On Hold | Setup Pending' (6 orders, Track 404 in every family), so no mapping change; 'Order Accepted | Rejected due to Order Cancellation' tracks as pending-dcv, so it is not remapped either (Finding 33 update). The reissue live test now routes logs before building the plugin, and the reissue note is joined to the result message with a sentence break.
…ad of counting them as errors Every 'On Hold | Setup Pending' row on the sandbox falls back to Track Order, which finds the order in no family. Each one logged an error with a stack trace and counted toward the 25% error-rate abort. Such rows are now skipped for the pass with one Information line and counted as UntrackableListedOrders in the summary; a later pass tracks them again. Nothing is emitted for them (no evidence the order failed). The reissue success log now carries the HTTP status. Adds a read-only live full-sync summary probe.
…summary CERTInext documents none of the report's orderStatus/certificateStatus display strings (Finding 35), so the only way to learn new ones is to observe them. The summary line now carries UnmappedReportStatuses=[pair (count); ...] at Information instead of leaving each to a per-row Debug line.
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.
Stacked on #39. Implements
issues/0094-v2-spec-followups-plan.mditems 1-5 andissues/0095(V2 token transport detail and retry). It also covers theUnresolvedStatusFallbacks=5investigation. Item 6 (OV organization representative) is deferred.Items
1. DCV for SSL.com-validated domains (
5e84e8c)GET /domains?search=<fqdn>&exactMatch=true.validatedBy: SSLCOM). The plugin reads SSL.com's record fromGET /domains/{id}/dcvand publishes that exactrecordName/recordValue.DcvTxtRecordTemplateis not used on this path. It then callsPOST /domains/{id}/dcv/verify {optionId}and cleans up.dns-txtoptions are used. The IAnyCAPlugin 3.3IDomainValidator.StageValidation(key, value)has no record-type argument, so a CNAME can't be staged. A domain offered onlydns-cnameorhttp-urlstays pending with a warning.PENDING/UNDER_REVIEW/FAILED/EXPIRED, or no DNS provider.2. Revoke reason
privilege-withdrawn(5a30826)privilege-withdrawn, with new wording: 422"Revocation reason privilege-withdrawn is not allowed.", with no EMS code."Invalid Revoke Reason ID", so a reason-9 revoke failed with no retry.privilege-withdrawn. A rejection in either wording now retries once ascessation-of-operation.3. Docs: revoke reasons (
56633e2). The spec note now gives the 2.0.0 SSL/TLS enum: 9 values, noprivilege-withdrawn, andaa-compromiseincluded.4.
prevettingToken(f7766e9). The JSON name now matches the spec. Nothing sets the token today, so this is wire-neutral; a serialization test pins the spelling.5. Reissue via
POST /ssl-certificates/{orderId}/reissue(8486405)Reissue, or aRenewOrReissuewhose prior certificate is unexpired and expires afterRenewalWindowDays. That's V1's reissue boundary.PriorCertSN, then/reissueis called withmode: rekey, the new CSR, and UCCadditionalDomains. It always usesrekey: Command gives no signal that only the SANs changed.3527789913R001) becomes theCARequestID, and the normal post-create flow runs on it.V2ReissueRevokePrevious(Boolean, defaultfalse). When on, the call sendsrevokePrevious: true, revokeReason: superseded, and the revoke intent is audit-logged first. It's wired through the constants, config, annotation,ValidateCAConnectionInfoand the manifest.PriorCertSN, no matching gateway order, or a 404/422 from/reissue.orderId. These fail with "do not resubmit".0095. Token transport failure (
07d119a)TransportDetail=: RestSharp'sResponseStatusplus the exception chain. That text passes throughRedactCredentials, the configured secret values are replaced outright, and the length is capped. A short form goes into the exception message.ThrowOnV2FailurekeepsHTTP 0.first, becauseIsTransportLevelCsrFailureparses the firstHTTP n.in the message.Sync: untrackable report rows (
ae97214,a9d4ef6)/reports/orderslists but Track Order finds in no family is now skipped with one Information line and counted asUntrackableListedOrders. EveryOn Hold | Setup Pendingrow behaves this way. These rows used to log an error with a stack trace and count toward the sync's 25% error-rate abort.orderStatus | certificateStatuspair, with counts (UnmappedReportStatuses=[...]). CERTInext documents none of this text (Finding 35), so a new value now shows up in ordinary logs.Also included:
1782375adds the live probes and a result-message tidy;af91cf7documents the probe flags.Testing
dotnet build -c Release --no-incrementalgives 0 warnings with DcvSupporttrueandfalse.CERTInext.Testspasses 1296/1296 (baseline 1236, +60).rekeybody,revokePreviouson/off, UCCadditionalDomains, 404/422 fallback, no fallback on 5xx or an unreadable 201, theRenewOrReissuewindow, Private PKI unchanged, and a derived id round-tripped through enroll,GetSingleRecordand Sync.UNDER_REVIEWand 502; GetDcv 502;VALIDATED; emSign and failed-lookup paths; UCC with mixed validators.Live results (sandbox, each run once)
SSL.com DCV: not verifiable on sandbox. The lookup works (HTTP 200), and the row id field is
domainId. The test domain has novalidatedBy(emSign), andGET /domains?validatedBy=SSLCOMreturns 0 rows.Revoke, per reason (sent to our own order; CERTInext checks the reason before the order state):
unspecified,ca-compromise,certificate-hold,aa-compromise,remove-from-crlprivilege-withdrawnaffiliation-changed,cessation-of-operation,key-compromise,supersededReissue,
V2ReissueRevokePrevious=false(two approved runs):3527789913R001; the HTTP code wasn't captured on that run. 4669599214 gave HTTP 201,4669599214R001,status: issued.Approved by System).ssl-certificates, and/reports/orderslists them.Full plugin sync (read-only, group 3162337784):
Errors=0,UntrackableListedOrders=6, all of themOn Hold | Setup Pending. 272 issued orders came back with bodies, and 541 pending.kfclab
gateway-certinext(net8, DcvSupport=false, this branch's build; the DLL hash was verified in the pod):AnyCA_DV SSL: order6881488167was created (201) and went toissued, but the certificate isn't generated (422 EMS-1165). Command's workflow timed out at 60 s during the ~55 s pickup (an existing Known Limitation). Gateway row 1999 ispending.race-10, 90-dayRenewalWindowDays):RenewOrReissuecorrectly took the renewal path, placing new order2195712881.DV SSLtemplate'sRenewalWindowDayslowered to 30 in the gateway config (restored to the previous config afterwards), a Command renew ofrace-6went Command → plugin →POST /reissue→ 201 (Reissue=True) and created5234549875R001(gateway row 2002). The reissue call held for 25.8 s, and with the ~55 s pickup on top, Command's 60 s workflow timed out (see open question 5).3527789913R001, which the gateway's own sync imported.race-8,TemplateId: null, imported by sync) fails inside Command with HTTP 500, aNullReferenceExceptioninEnrollmentSecurityService.IsUserAllowedEnrollmentPattern, before any gateway call.UnresolvedStatusFallbacks=5/reports/ordersrows found that the onlyorderStatus | certificateStatuspair the mapping can't place isOn Hold | Setup Pending. Six orders have it account-wide (842):3549334354,3643952534,4958277715,1257971154and6534374548from 2026-10-16, plus2973146273from 2026-06-11, which the gateway's group scope presumably excludes.UnresolvedStatusFallbacksandErrors.Order Accepted | Rejected due to Order Cancellationtracks aspending-dcv, not cancelled, so it was not remapped.Order Fulfilled | Pending for Approver(1 row) tracks asissued, but the plugin maps it to pending; recorded, not changed.Orders left at CERTInext (sandbox stall; can't be revoked or cancelled yet)
1288714496,3527789913R001,4669599214R001,5234549875R001,6881488167,2195712881. All four areissuedwith no certificate generated. Revoke answers "Certificate Request still being processed", and an issued order can't be cancelled. RunCERTINEXT_V2_CLEANUP_ORDERS=...once the sandbox generates certificates again.Deferred
Open questions
validatedBypresent onGET /domainslist rows (not justGET /dcv)? The spec doesn't type the list row, and the sandbox has none to check.RenewOrReissueinside the renewal window also use/reissue, rather than a new order? It would keep the subscription and avoid a new billable order, but V1's boundary was kept.GET /domainson every DCV pass acceptable? It's bounded by the per-pass DCV caps. Caching per pass is an option.On Hold | Setup Pendingbe shown as pending or failed, and which field is authoritative when the report and Track disagree? (Finding 33.)