Refusals and logging¶
The handler refuses some requests itself, before or instead of calling the CA. This page lists those answers, the log records the handler writes and how a CA learns about refusals.
HTTP refusals¶
These requests get an HTTP error and no CertRep. The body is a short reason and never contains the cause.
| Request | Status | Body |
|---|---|---|
No operation parameter or an unknown operation |
400 | unknown SCEP operation |
| GetCACaps, GetCACert, GetCACertChain or GetNextCACert with a method other than GET | 405 with Allow: GET |
method not allowed |
| PKIOperation with a method other than GET or POST | 405 with Allow: GET, POST |
method not allowed |
GetCACert or GetCACertChain for a name the CA returns ErrNotFound for |
404 | unknown CA |
GetNextCACert while the CA returns ErrNotFound |
404 | no next CA certificate |
| GetCACertChain or GetNextCACert when the CA lacks the interface | 501 | GetCACertChain is not supported or GetNextCACert is not supported |
PKIOperation POST with an empty body. GET without a valid base64 message |
400 | malformed PKIOperation request |
PKIOperation POST over MaxMessageBytes |
413 | malformed PKIOperation request |
PKIOperation GET with a message over MaxMessageBytes |
414 | malformed PKIOperation request |
| A message that does not parse or whose signature does not verify | 400 | malformed or unverifiable pkiMessage |
| A CertRep sent as a request | 400 | a CertRep is not a request |
A pkiEnvelope for which Responder returns ErrNotFound for every recipient |
400 | the pkiEnvelope is not addressed to this CA |
*Busy from the CA |
503 with Retry-After | busy, retry later |
| Any other error from the CA or an answer that cannot be built | 500 | internal error |
FAILURE answers from the handler¶
These requests are answered with a FAILURE CertRep without reaching Enroll, Poll, GetCert or
GetCRL:
| Cause | failInfo | Failure.Err |
|---|---|---|
| The signer certificate has no RSA key, so the answer could not be encrypted for it | badAlg | scepserver.ErrSignerNotRSA |
The pkiEnvelope uses a cipher Options.Decrypt does not accept or an unsupported algorithm |
badAlg | The go-pkcs7 error, such as pkcs7.ErrContentEncryptionNotAllowed |
| The pkiEnvelope does not decrypt with the responder key | badMessageCheck | Wraps pkcs7.ErrDecryptionFailed |
| The decrypted content is not a valid CSR, CSR signature or query | badRequest | The parse error |
GetCert or GetCRL to a CA without CertificateGetter or CRLGetter |
badRequest | GetCert is not supported or GetCRL is not supported |
pkcs7.ErrDecryptionFailed does not say whether the key or the padding was wrong, so a sender
cannot use the answer to probe the key.
Recording refusals¶
A CA that implements scepserver.RefusalRecorder is called before each FAILURE answer in the
table above. Use it for audit records or to count devices that send what the CA cannot accept:
// RecordRefusal writes an audit record for a request the handler refused on its own.
func (ca *myCA) RecordRefusal(ctx context.Context, req *scepserver.Request, failure *scepserver.Failure) {
ca.audit.InfoContext(ctx, "scep request refused by the handler",
"message_type", req.Message.MessageType.String(),
"transaction_id", string(req.Message.TransactionID),
"fail_info", failure.Info.String(),
"signer_not_rsa", errors.Is(failure.Err, scepserver.ErrSignerNotRSA),
"cause", failure.Err,
"client", req.HTTPRequest.RemoteAddr)
}
The pkiEnvelope of the request may not be decrypted yet. Some refusals also concern messages
without a CSR, such as GetCert. req.Message.CSRReqMessage, req.Message.PollRequest and
req.Message.CertificateID can therefore be nil. req.Message.CSR is promoted from the embedded
*CSRReqMessage, so reading it while that pointer is nil panics. Check the pointer first. The handler does not call RecordRefusal for the outcomes the CA returns itself or for
the HTTP refusals.
Log records¶
Options.Logger receives the records below. The default discards them.
| Message | Level | Attributes | Written for |
|---|---|---|---|
scep request answered |
Info | message_type, transaction_id and status. For FAILURE also fail_info and cause when there is one |
Every CertRep, including FAILURE answers |
scep request deferred |
Info | message_type, transaction_id, retry_after |
Every *Busy |
scep request refused |
Warn | operation, status, reason, cause when there is one |
Every HTTP 404, 400, 413, 414 and 501 refusal except an unknown operation and a wrong method |
scep request failed |
Error | operation, error |
Every HTTP 500 |
With slog.NewTextHandler, a refused request looks like this:
time=2026-11-03T15:06:54.157+01:00 level=INFO msg="scep request answered" message_type="PKCSReq (19)" transaction_id=B485DB38004DF987172810E833FB1BB48D72867B status="FAILURE (2)" fail_info="badRequest (2)" cause="wrong challenge password"
The cause is Failure.Err of the CA or of the handler. Keep secrets such as the challenge
password out of it. The handler does not log the challenge password or the CSR.