Authentication¶
The server verifies the protection of every message before your code sees it. It has no credential store of its own. You supply two lookups:
WithSecretLookupfinds the shared secret of a MAC-protected message.WithCertificateLookupfinds the certificate of a signature-protected message.
Without a lookup, messages with that kind of protection are refused: MAC-protected ones with badMessageCheck and signed ones with signerNotTrusted.
Shared secrets¶
type SecretLookup interface {
LookupSecret(sender pkix.Name, senderKID []byte) ([]byte, error)
}
RFC 9810 section 5.1.1 covers two cases:
- Known sender. The sender field carries the name and
senderKIDshould be set. Either or both may locate the secret. - Unknown sender, usually a first enrollment. The sender is a NULL-DN and
senderKIDcarries the reference number of the secret (RFC 4210 section 5.1.3.1).
The reference and the secret work like a user name and a password. Two clients that share a reference cannot be told apart and one could obtain certificates in the other's name. Enforce unique references when you provision secrets:
func Register(senderKID, secret []byte) error {
if exists(senderKID) {
return errors.New("senderKID already registered")
}
store[string(senderKID)] = secret
return nil
}
The server keeps a MAC client's transactions and its share of WithMaxTransactionsPerCredential
apart from other clients by the shared secret itself, whichever field the lookup used to find it. The
sender name and senderKID a request carries do not select the quota, so one secret cannot get a
second quota by naming a different sender. Clients that share one secret share one quota. Clients
that send only a name, as RFC 4210 allows, keep separate transactions as long as their secrets differ.
The client chooses the MAC iteration count and the server runs every iteration before it can check
the MAC. WithMACIterationLimits bounds that work, 100 to 1000000 by default. For PBMAC1 the maximum
bounds the whole PBKDF2 derivation, the iterations multiplied by the number of PRF output blocks the
requested key length needs, so a long key from a short PRF cannot multiply the work. A request
outside the range is refused with badMessageCheck and the text "MAC protection parameters not
accepted" before any key derivation. The minimum is the 100 that RFC 4211 section 4.4 requires. Lower
it only for a peer that sends fewer: a small count makes an offline guess of a weak secret cheap.
Certificates¶
type CertificateLookup interface {
LookupCertificate(issuer pkix.Name, subject pkix.Name, senderKID []byte) (*x509.Certificate, error)
}
issuer is the recipient in the header, subject the sender and senderKID the key identifier.
Any of them may be empty. The names are lookup keys only: the server verifies the signature against
the certificate you return, so a client that lies about its name either finds nothing or fails the
signature check. Trust comes from your store and proof of the private key.
A senderKID that is present must name the key that signed the message (RFC 9483 section 3.5). The
server compares it with the subject key identifier of the certificate you return and refuses a
mismatch with badMessageCheck. This holds also when your lookup ignores senderKID, for example
because it takes the signer from extraCerts. A certificate without a subject key identifier is not
compared.
Return only certificates you issued or otherwise trust and do not return revoked ones.
The sender identity¶
A verified message reaches your code with a SenderIdentity:
| Field | Content |
|---|---|
Certificate |
The signer certificate of a signature-protected message |
Sender |
The sender name. For a signed message it equals the certificate subject, so you can authorize on it |
SenderKID |
The reference of a MAC-protected message |
MACVerified |
True for a MAC-protected message |
RA |
The registration authority that forwarded the request, see Registration authorities |
For a MAC-protected message the sender name is whatever resolved the secret. Authorize on
SenderKID or on the name the secret is registered to.
Algorithms¶
A request protected with an algorithm the server does not implement is refused with badAlg. This
includes SHA-1 signatures, which RFC 9481 section 7.1 deprecates. WithSHA1Signatures accepts them
from RFC 4210 era devices, in the protection and in the proof of possession.
Response protection¶
The server answers a MAC-protected request with a MAC from the same secret and algorithm suite, with
a fresh salt, as RFC 9483 section 3.2 requires. It repeats the client's senderKID. Responses to
signed requests and to messages whose protection failed are signed with the WithSigner key.
Signing every refused request costs one signature per unauthenticated message, which matters with a
slow signer such as SLH-DSA-SHA2-128s. WithUnprotectedErrorsBeforeAuthentication leaves errors
raised before authentication unprotected, as RFC 9483 section 3.6.4 allows when the recipient has no
credential to verify them with. A request forwarded by an authenticated registration authority is
still answered with a protected error. The client of this module reports an unprotected error as
UnverifiedStatusError. OpenSSL reports a missing protection instead of the status unless it runs
with -unprotected_errors.