You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/acme/api/revoke.go

147 lines
4.0 KiB
Go

package api
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"net/http"
"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"
)
type revokePayload struct {
Certificate string `json:"certificate"`
ReasonCode int `json:"reason"`
}
func (h *Handler) RevokeCert(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
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)
if err != nil {
api.WriteError(w, err)
return
}
// 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 {
api.WriteError(w, err)
return
}
var payload revokePayload
err = json.Unmarshal(p.value, &payload)
if err != nil {
api.WriteError(w, err) // TODO: fix error type
return
}
certBytes, err := base64.RawURLEncoding.DecodeString(payload.Certificate)
if err != nil {
api.WriteError(w, err) // TODO: fix error type
return
}
certToBeRevoked, err := x509.ParseCertificate(certBytes)
if err != nil {
api.WriteError(w, err) // TODO: fix error type
return
}
certID := certToBeRevoked.SerialNumber.String()
// 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 {
// api.WriteError(w, acme.WrapErrorISE(err, "error retrieving certificate"))
// return
// }
// if existingCert.AccountID != acc.ID {
// api.WriteError(w, acme.NewError(acme.ErrorUnauthorizedType,
// "account '%s' does not own certificate '%s'", acc.ID, certID))
// return // TODO: this check should only be performed in case acc exists (i.e. KID revoke)
// }
// TODO: validate the certToBeRevoked against what we know about it?
if payload.ReasonCode < ocsp.Unspecified || payload.ReasonCode > ocsp.AACompromise {
api.WriteError(w, acme.NewError(acme.ErrorBadRevocationReasonType, "reasonCode out of bounds"))
return
}
// TODO: check reason code; should be allowed (based on what? and determined by Provisioner?); otherwise send error
options := &authority.RevokeOptions{
Serial: certID,
Reason: reason(payload.ReasonCode),
ReasonCode: payload.ReasonCode,
ACME: true,
Crt: certToBeRevoked,
}
err = h.ca.Revoke(ctx, options)
if err != nil {
api.WriteError(w, err) // TODO: send the right error; 400; alreadyRevoked (or something else went wrong, of course)
return
}
w.Header().Add("Link", link(h.linker.GetLink(ctx, DirectoryLinkType), "index"))
w.Write(nil)
}
func reason(reasonCode int) string {
switch reasonCode {
case ocsp.Unspecified:
return "unspecified reason"
case ocsp.KeyCompromise:
return "key compromised"
case ocsp.CACompromise:
return "ca compromised"
case ocsp.AffiliationChanged:
return "affiliation changed"
case ocsp.Superseded:
return "superseded"
case ocsp.CessationOfOperation:
return "cessation of operation"
case ocsp.CertificateHold:
return "certificate hold"
case ocsp.RemoveFromCRL:
return "remove from crl"
case ocsp.PrivilegeWithdrawn:
return "privilege withdrawn"
case ocsp.AACompromise:
return "aa compromised"
default:
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)
}