Delayed issuance and confirmation¶
A CA does not have to decide at once. It can hold a request until an operator approves it or an HSM queue drains and it can learn whether the client accepted the certificate it issued.
Waiting for a decision¶
Implement server.PendingChecker and return a WaitingResponse from IssueCertificate:
func (c *myCA) IssueCertificate(ctx context.Context, reqType server.RequestType,
tmpl *x509.Certificate, sender *server.SenderIdentity) (*server.Response, error) {
jobID := c.queue.Add(tmpl, sender)
return &server.Response{Waiting: &server.WaitingResponse{
CheckAfter: 30 * time.Second,
Reason: "waiting for operator approval",
PollRef: jobID,
}}, nil
}
func (c *myCA) CheckPending(ctx context.Context, pollRef string,
sender *server.SenderIdentity) (*server.Response, error) {
switch job := c.queue.Get(pollRef); {
case job.Pending():
return &server.Response{Waiting: &server.WaitingResponse{CheckAfter: 30 * time.Second, PollRef: pollRef}}, nil
case job.Rejected():
return nil, &server.Error{Status: pkicmp.StatusRejection, FailureInfo: pkicmp.FailNotAuthorized}
default:
return &server.Response{Certificate: job.Certificate()}, nil
}
}
The flow follows RFC 9483 section 4.4:
- The server answers the request with status waiting.
Reasonbecomes its statusString. - The client polls with
pollReqafterCheckAfter. - The server calls
CheckPendingwith the storedPollRef. CheckPendingreturns the certificate, anotherWaitingResponseor a*server.Error.
A rejection reaches the client in the ip, cp or kup it would have got at once, with the
request's certReqId and ends the transaction. The final response grants implicit confirmation
only when the delayed request asked for it.
Polling rules¶
pollRepadvertisesCheckAfterin whole seconds, at least one. ApollReqthat arrives earlier gets anotherpollRepwith the time left and does not reach the CA.- A
pollReqnames the request by itscertReqIdor by -1 for the whole message. Ap10crmay also be polled with 0. Any other value is refused with badRequest. - A
pollReqmust repeat the senderNonce of the previous response in its recipNonce.WithOptionalPollReqRecipNonceaccepts one without it, which the Insta Certifier clients need.
Clients that stop polling¶
A request waits until CleanupExpired removes it, 24 hours after its last message by default. Set
the lifetime with WithWaitingLifetime. It must exceed the CheckAfter you return. A CA that
implements WaitingExpirer is told the PollRef of each request it loses this way, so it can close
its own record.
How long a request may wait for a decision is your policy: CheckPending rejects a request you no
longer want to decide.
The waiting state lives in the server. Replicas share it only through snapshots.
Confirmation¶
After an issued certificate, the client sends certConf to accept or reject it and the server
answers pkiConf. Implement server.CertificateConfirmer to hear the outcome:
func (c *myCA) ConfirmCertificate(ctx context.Context, cert *x509.Certificate,
status server.ConfirmStatus, issueRef any) error {
switch status {
case server.ConfirmAccepted, server.ConfirmImplicit:
return c.store.Publish(issueRef)
case server.ConfirmRejected, server.ConfirmExpired:
return c.store.Revoke(issueRef)
}
return nil
}
| Status | Meaning |
|---|---|
ConfirmAccepted |
The client accepted the certificate |
ConfirmRejected |
The client rejected it or sent a certConf without any CertStatus |
ConfirmImplicit |
Implicit confirmation was granted, so no certConf follows |
ConfirmExpired |
No certConf arrived before the confirmation wait ended |
issueRef is the Response.IssueRef you set in IssueCertificate, such as a database row ID.
WithConfirmWaitTime sets how long the server waits for certConf, 10 seconds by default. The
value is sent to the client in the response.
Rules for certConf¶
- Every CertStatus must carry the
certHashandcertReqIdof the issued certificate. All of them must accept it or all must reject it. The CA is notified once. - For a
p10crthecertReqIdmay be -1 or 0, because RFC 9810 and RFC 9483 disagree and deployed peers use both. certConfmust repeat the senderNonce of the certificate response in its recipNonce.WithOptionalCertConfRecipNonceaccepts one without it. A different value is always refused.certConfmust use the credentials of the request, because the transaction is found by them. A key update that keeps its key is confirmed with the old certificate.
Implicit confirmation¶
WithImplicitConfirm grants implicit confirmation in every successful certificate response to a
client that asks for it. The certificate then counts as confirmed and the certConf round trip is
skipped. A certificate encrypted for proof by decryption never gets implicit confirmation, because
its certConf is the proof. See Keys that cannot sign.
Context values¶
A custom Handler built with server.New sees polls and confirmations as calls whose context
carries these values. The CA callbacks get the same details as arguments.
| Function | Set while the server handles |
|---|---|
server.PollRefFromContext |
A poll for a delayed request |
server.IssueRefFromContext |
A certConf, with the IssueRef of the certificate |
server.IssuedCertFromContext |
A certConf, with the certificate being confirmed |
server.POPMethodFromContext |
A certificate request and its confirmation, with the proof of possession method |