From d53bcaf830980ea18e958adf027fa4ec544f7e91 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 2 Jul 2021 22:51:15 +0200 Subject: [PATCH] Add base logic for ACME revoke-cert --- acme/api/handler.go | 1 + acme/api/revoke.go | 79 +++++++++++++++++++++++++++++++++++++++++++++ acme/common.go | 2 ++ 3 files changed, 82 insertions(+) create mode 100644 acme/api/revoke.go diff --git a/acme/api/handler.go b/acme/api/handler.go index 2a6d3a02..06bd4bb4 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -111,6 +111,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 } // GetNonce just sets the right header since a Nonce is added to each response diff --git a/acme/api/revoke.go b/acme/api/revoke.go new file mode 100644 index 00000000..ab537007 --- /dev/null +++ b/acme/api/revoke.go @@ -0,0 +1,79 @@ +package api + +import ( + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" +) + +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) + // TODO: handle errors; sent the right ACME response back + + ctx := r.Context() + _, 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) + if err != nil { + fmt.Println(err) + } + + // TODO: let provisioner authorize the revocation? Necessary per provisioner? Or can it be done by the CA, like the Revoke itself. + + p, err := payloadFromContext(ctx) + if err != nil { + fmt.Println(err) + } + + type revokedCert struct { + Certificate string `json:"certificate"` + Reason int `json:"reason"` // TODO: is optional; handle accordingly + } + + var rc revokedCert + err = json.Unmarshal(p.value, &rc) + if err != nil { + fmt.Println("error:", err) + } + + c, err := base64.RawURLEncoding.DecodeString(rc.Certificate) + if err != nil { + fmt.Println("error:", err) + } + + certToBeRevoked, err := x509.ParseCertificate(c) + if err != nil { + fmt.Println("error: failed to parse certificate: " + err.Error()) + } + + // TODO: check reason code; should be allowed; otherwise send error + + options := &authority.RevokeOptions{ + Serial: certToBeRevoked.SerialNumber.String(), + Reason: "test", // TODO: map it to the reason based on code? + ReasonCode: rc.Reason, + PassiveOnly: false, + MTLS: true, // TODO: should be false, I guess, but results in error: authority.Revoke; error parsing token: square/go-jose: compact JWS format must have three parts (OTT) + Crt: certToBeRevoked, + OTT: "", + } + err = h.ca.Revoke(ctx, options) + if err != nil { + fmt.Println("error: ", err.Error()) // TODO: send the right error; 400; alreadyRevoked (or something else went wrong, of course) + } + + w.Write(nil) +} diff --git a/acme/common.go b/acme/common.go index 26552c61..77a219a0 100644 --- a/acme/common.go +++ b/acme/common.go @@ -5,12 +5,14 @@ import ( "crypto/x509" "time" + "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" ) // CertificateAuthority is the interface implemented by a CA authority. type CertificateAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Revoke(context.Context, *authority.RevokeOptions) error LoadProvisionerByID(string) (provisioner.Interface, error) }