-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
path_acme_revoke.go
182 lines (154 loc) · 6.75 KB
/
path_acme_revoke.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pki
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathAcmeRevoke(b *backend) []*framework.Path {
return buildAcmeFrameworkPaths(b, patternAcmeRevoke, "/revoke-cert")
}
func patternAcmeRevoke(b *backend, pattern string) *framework.Path {
fields := map[string]*framework.FieldSchema{}
addFieldsForACMEPath(fields, pattern)
addFieldsForACMERequest(fields)
return &framework.Path{
Pattern: pattern,
Fields: fields,
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.acmeParsedWrapper(b.acmeRevocationHandler),
ForwardPerformanceSecondary: false,
ForwardPerformanceStandby: true,
},
},
HelpSynopsis: pathAcmeHelpSync,
HelpDescription: pathAcmeHelpDesc,
}
}
func (b *backend) acmeRevocationHandler(acmeCtx *acmeContext, _ *logical.Request, _ *framework.FieldData, userCtx *jwsCtx, data map[string]interface{}) (*logical.Response, error) {
var cert *x509.Certificate
rawCertificate, present := data["certificate"]
if present {
certBase64, ok := rawCertificate.(string)
if !ok {
return nil, fmt.Errorf("invalid type (%T; expected string) for field 'certificate': %w", rawCertificate, ErrMalformed)
}
certBytes, err := base64.RawURLEncoding.DecodeString(certBase64)
if err != nil {
return nil, fmt.Errorf("failed to base64 decode certificate: %v: %w", err, ErrMalformed)
}
cert, err = x509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %v: %w", err, ErrMalformed)
}
} else {
return nil, fmt.Errorf("bad request was lacking required field 'certificate': %w", ErrMalformed)
}
rawReason, present := data["reason"]
if present {
reason, ok := rawReason.(float64)
if !ok {
return nil, fmt.Errorf("invalid type (%T; expected float64) for field 'reason': %w", rawReason, ErrMalformed)
}
if int(reason) != 0 {
return nil, fmt.Errorf("Vault does not support revocation reasons (got %v; expected omitted or 0/unspecified): %w", int(reason), ErrBadRevocationReason)
}
}
// If the certificate expired, there's no point in revoking it.
if cert.NotAfter.Before(time.Now()) {
return nil, fmt.Errorf("refusing to revoke expired certificate: %w", ErrMalformed)
}
// Fetch the CRL config as we need it to ultimately do the
// revocation. This should be cached and thus relatively fast.
config, err := b.crlBuilder.getConfigWithUpdate(acmeCtx.sc)
if err != nil {
return nil, fmt.Errorf("unable to revoke certificate: failed reading revocation config: %v: %w", err, ErrServerInternal)
}
// Load our certificate from storage to ensure it exists and matches
// what was given to us.
serial := serialFromCert(cert)
certEntry, err := fetchCertBySerial(acmeCtx.sc, "certs/", serial)
if err != nil {
return nil, fmt.Errorf("unable to revoke certificate: err reading global cert entry: %v: %w", err, ErrServerInternal)
}
if certEntry == nil {
return nil, fmt.Errorf("unable to revoke certificate: no global cert entry found: %w", ErrServerInternal)
}
// Validate that the provided certificate matches the stored
// certificate. This completes the chain of:
//
// provided_auth -> provided_cert == stored cert.
//
// Allowing revocation to be safe.
//
// We use the non-subtle unsafe bytes equality check here as we have
// already fetched this certificate from storage, thus already leaking
// timing information that this cert exists. The user could thus simply
// fetch the cert from Vault matching this serial number via the unauthed
// pki/certs/:serial API endpoint.
if !bytes.Equal(certEntry.Value, cert.Raw) {
return nil, fmt.Errorf("unable to revoke certificate: supplied certificate does not match CA's stored value: %w", ErrMalformed)
}
// Check if it was already revoked; in this case, we do not need to
// revoke it again and want to respond with an appropriate error message.
revEntry, err := fetchCertBySerial(acmeCtx.sc, "revoked/", serial)
if err != nil {
return nil, fmt.Errorf("unable to revoke certificate: err reading revocation entry: %v: %w", err, ErrServerInternal)
}
if revEntry != nil {
return nil, fmt.Errorf("unable to revoke certificate: %w", ErrAlreadyRevoked)
}
// Finally, do the relevant permissions/authorization check as
// appropriate based on the type of revocation happening.
if !userCtx.Existing {
return b.acmeRevocationByPoP(acmeCtx, userCtx, cert, config)
}
return b.acmeRevocationByAccount(acmeCtx, userCtx, cert, config)
}
func (b *backend) acmeRevocationByPoP(acmeCtx *acmeContext, userCtx *jwsCtx, cert *x509.Certificate, config *crlConfig) (*logical.Response, error) {
// Since this account does not exist, ensure we've gotten a private key
// matching the certificate's public key. This private key isn't
// explicitly provided, but instead provided by proxy (public key,
// signature over message). That signature is validated by an earlier
// wrapper (VerifyJWS called by ParseRequestParams). What still remains
// is validating that this implicit private key (with given public key
// and valid JWS signature) matches the certificate's public key.
givenPublic, ok := userCtx.Key.Key.(crypto.PublicKey)
if !ok {
return nil, fmt.Errorf("unable to revoke certificate: unable to parse message header's JWS key of type (%T): %w", userCtx.Key.Key, ErrMalformed)
}
// Ensure that our PoP's implicit private key matches this certificate's
// public key.
if err := validatePublicKeyMatchesCert(givenPublic, cert); err != nil {
return nil, fmt.Errorf("unable to revoke certificate: unable to verify proof of possession of private key provided by proxy: %v: %w", err, ErrMalformed)
}
// Now it is safe to revoke.
b.revokeStorageLock.Lock()
defer b.revokeStorageLock.Unlock()
return revokeCert(acmeCtx.sc, config, cert)
}
func (b *backend) acmeRevocationByAccount(acmeCtx *acmeContext, userCtx *jwsCtx, cert *x509.Certificate, config *crlConfig) (*logical.Response, error) {
// Fetch the account; disallow revocations from non-valid-status accounts.
_, err := requireValidAcmeAccount(acmeCtx, userCtx)
if err != nil {
return nil, fmt.Errorf("failed to lookup account: %w", err)
}
// We only support certificates issued by this user, we don't support
// cross-account revocations.
serial := serialFromCert(cert)
acmeEntry, err := b.acmeState.GetIssuedCert(acmeCtx, userCtx.Kid, serial)
if err != nil || acmeEntry == nil {
return nil, fmt.Errorf("unable to revoke certificate: %v: %w", err, ErrMalformed)
}
// Now it is safe to revoke.
b.revokeStorageLock.Lock()
defer b.revokeStorageLock.Unlock()
return revokeCert(acmeCtx.sc, config, cert)
}