Add support for revocation using JWK

This commit is contained in:
Herman Slatman 2021-07-03 01:56:14 +02:00
parent 84e7d468f2
commit 0e56932e76
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
3 changed files with 59 additions and 8 deletions

View file

@ -94,11 +94,17 @@ func (h *Handler) Route(r api.Router) {
r.MethodFunc("GET", getPath(DirectoryLinkType, "{provisionerID}"), h.baseURLFromRequest(h.lookupProvisioner(h.GetDirectory)))
r.MethodFunc("HEAD", getPath(DirectoryLinkType, "{provisionerID}"), h.baseURLFromRequest(h.lookupProvisioner(h.GetDirectory)))
validatingMiddleware := func(next nextHTTP) nextHTTP {
return h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.addDirLink(h.verifyContentType(h.parseJWS(next))))))
}
extractPayloadByJWK := func(next nextHTTP) nextHTTP {
return h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.addDirLink(h.verifyContentType(h.parseJWS(h.validateJWS(h.extractJWK(h.verifyAndExtractJWSPayload(next)))))))))
return validatingMiddleware(h.extractJWK(h.verifyAndExtractJWSPayload(next)))
}
extractPayloadByKid := func(next nextHTTP) nextHTTP {
return h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.addDirLink(h.verifyContentType(h.parseJWS(h.validateJWS(h.lookupJWK(h.verifyAndExtractJWSPayload(next)))))))))
return validatingMiddleware(h.lookupJWK(h.verifyAndExtractJWSPayload(next)))
}
extractPayloadByKidOrJWK := func(next nextHTTP) nextHTTP {
return validatingMiddleware(h.extractOrLookupJWK(h.verifyAndExtractJWSPayload(next)))
}
r.MethodFunc("POST", getPath(NewAccountLinkType, "{provisionerID}"), extractPayloadByJWK(h.NewAccount))
@ -111,7 +117,7 @@ func (h *Handler) Route(r api.Router) {
r.MethodFunc("POST", getPath(AuthzLinkType, "{provisionerID}", "{authzID}"), extractPayloadByKid(h.isPostAsGet(h.GetAuthorization)))
r.MethodFunc("POST", getPath(ChallengeLinkType, "{provisionerID}", "{authzID}", "{chID}"), extractPayloadByKid(h.GetChallenge))
r.MethodFunc("POST", getPath(CertificateLinkType, "{provisionerID}", "{certID}"), extractPayloadByKid(h.isPostAsGet(h.GetCertificate)))
r.MethodFunc("POST", getPath(RevokeCertLinkType, "{provisionerID}"), extractPayloadByKid(h.RevokeCert)) // TODO: check kid vs. jws; revoke can do both
r.MethodFunc("POST", getPath(RevokeCertLinkType, "{provisionerID}"), extractPayloadByKidOrJWK(h.RevokeCert))
}
// GetNonce just sets the right header since a Nonce is added to each response

View file

@ -352,6 +352,37 @@ func (h *Handler) lookupJWK(next nextHTTP) nextHTTP {
}
}
// extractOrLookupJWK forwards handling to either extractJWK or
// lookupJWK based on the presence of a JWK or a KID, respectively.
func (h *Handler) extractOrLookupJWK(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
jws, err := jwsFromContext(ctx)
if err != nil {
api.WriteError(w, err)
return
}
// at this point the JWS has already been verified (if correctly configured in middleware),
// and it can be used to check if a jwk exists.
if canExtractJWKFrom(jws) {
h.extractJWK(next)(w, r)
return
}
// default to looking up the JWK based on KID
h.lookupJWK(next)(w, r)
}
}
// canExtractJWKFrom checks if the JWS has a JWK that can be extracted
func canExtractJWKFrom(jws *jose.JSONWebSignature) bool {
if len(jws.Signatures) == 0 {
return false
}
return jws.Signatures[0].Protected.JSONWebKey != nil
}
// verifyAndExtractJWSPayload extracts the JWK from the JWS and saves it in the context.
// Make sure to parse and validate the JWS before running this middleware.
func (h *Handler) verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {

View file

@ -9,6 +9,7 @@ import (
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
"go.step.sm/crypto/jose"
"golang.org/x/crypto/ocsp"
)
@ -19,16 +20,21 @@ type revokePayload struct {
func (h *Handler) RevokeCert(w http.ResponseWriter, r *http.Request) {
// TODO: support the non-kid case, i.e. JWK with the public key of the cert
// base the account + certificate JWK instead of the kid (which is now the case)
ctx := r.Context()
_, err := accountFromContext(ctx)
jws, err := jwsFromContext(ctx)
if err != nil {
api.WriteError(w, err)
return
}
if shouldCheckAccount(jws) {
_, err := accountFromContext(ctx)
if err != nil {
api.WriteError(w, err)
return
}
}
// TODO: do checks on account, i.e. is it still valid? is it allowed to do revocations? Revocations on the to be revoked cert?
_, err = provisionerFromContext(ctx)
@ -65,7 +71,7 @@ func (h *Handler) RevokeCert(w http.ResponseWriter, r *http.Request) {
}
certID := certToBeRevoked.SerialNumber.String()
// TODO: retrieving the certificate to verify the account does not seem to work? Results in certificate not found error.
// TODO: retrieving the certificate to verify the account does not seem to work, so far? Results in certificate not found error.
// When Revoke is called, the certificate IS in fact found? The (h *Handler) GetCertificate function is fairly similar, too.
// existingCert, err := h.db.GetCertificate(ctx, certID)
// if err != nil {
@ -101,6 +107,7 @@ func (h *Handler) RevokeCert(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Add("Link", link(h.linker.GetLink(ctx, DirectoryLinkType), "index"))
w.Write(nil)
}
@ -130,3 +137,10 @@ func reason(reasonCode int) string {
return "unspecified reason"
}
}
// shouldUseAccount indicates whether an account should be
// retrieved from the context, so that it can be used for
// additional checks
func shouldCheckAccount(jws *jose.JSONWebSignature) bool {
return !canExtractJWKFrom(jws)
}