forked from smallstep/certificates
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.go
333 lines (294 loc) · 9.48 KB
/
api.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package api
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-chi/chi"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/cli/crypto/tlsutil"
)
// Authority is the interface implemented by a CA authority.
type Authority interface {
Authorize(ott string) ([]interface{}, error)
GetTLSOptions() *tlsutil.TLSOptions
Root(shasum string) (*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, signOpts authority.SignOptions, extraOpts ...interface{}) (*x509.Certificate, *x509.Certificate, error)
Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
GetProvisioners(cursor string, limit int) ([]*authority.Provisioner, string, error)
GetEncryptedKey(kid string) (string, error)
}
// Certificate wraps a *x509.Certificate and adds the json.Marshaler interface.
type Certificate struct {
*x509.Certificate
}
// NewCertificate is a helper method that returns a Certificate from a
// *x509.Certificate.
func NewCertificate(cr *x509.Certificate) Certificate {
return Certificate{
Certificate: cr,
}
}
// MarshalJSON implements the json.Marshaler interface. The certificate is
// quoted string using the PEM encoding.
func (c Certificate) MarshalJSON() ([]byte, error) {
if c.Certificate == nil {
return []byte("null"), nil
}
block := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: c.Raw,
})
return json.Marshal(string(block))
}
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate is
// expected to be a quoted string using the PEM encoding.
func (c *Certificate) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, "error decoding certificate")
}
block, _ := pem.Decode([]byte(s))
if block == nil {
return errors.New("error decoding certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return errors.Wrap(err, "error decoding certificate")
}
c.Certificate = cert
return nil
}
// CertificateRequest wraps a *x509.CertificateRequest and adds the
// json.Unmarshaler interface.
type CertificateRequest struct {
*x509.CertificateRequest
}
// NewCertificateRequest is a helper method that returns a CertificateRequest
// from a *x509.CertificateRequest.
func NewCertificateRequest(cr *x509.CertificateRequest) CertificateRequest {
return CertificateRequest{
CertificateRequest: cr,
}
}
// MarshalJSON implements the json.Marshaler interface. The certificate request
// is a quoted string using the PEM encoding.
func (c CertificateRequest) MarshalJSON() ([]byte, error) {
if c.CertificateRequest == nil {
return []byte("null"), nil
}
block := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: c.Raw,
})
return json.Marshal(string(block))
}
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate
// request is expected to be a quoted string using the PEM encoding.
func (c *CertificateRequest) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, "error decoding csr")
}
block, _ := pem.Decode([]byte(s))
if block == nil {
return errors.New("error decoding csr")
}
cr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return errors.Wrap(err, "error decoding csr")
}
c.CertificateRequest = cr
return nil
}
// Router defines a common router interface.
type Router interface {
// MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
MethodFunc(method, pattern string, h http.HandlerFunc)
}
// RouterHandler is the interface that a HTTP handler that manages multiple
// endpoints will implement.
type RouterHandler interface {
Route(r Router)
}
// HealthResponse is the response object that returns the health of the server.
type HealthResponse struct {
Status string `json:"status"`
}
// RootResponse is the response object that returns the PEM of a root certificate.
type RootResponse struct {
RootPEM Certificate `json:"ca"`
}
// SignRequest is the request body for a certificate signature request.
type SignRequest struct {
CsrPEM CertificateRequest `json:"csr"`
OTT string `json:"ott"`
NotAfter time.Time `json:"notAfter"`
NotBefore time.Time `json:"notBefore"`
}
// ProvisionersResponse is the response object that returns the list of
// provisioners.
type ProvisionersResponse struct {
Provisioners []*authority.Provisioner `json:"provisioners"`
NextCursor string `json:"nextCursor"`
}
// ProvisionerKeyResponse is the response object that returns the encryptoed key
// of a provisioner.
type ProvisionerKeyResponse struct {
Key string `json:"key"`
}
// Validate checks the fields of the SignRequest and returns nil if they are ok
// or an error if something is wrong.
func (s *SignRequest) Validate() error {
if s.CsrPEM.CertificateRequest == nil {
return BadRequest(errors.New("missing csr"))
}
if err := s.CsrPEM.CertificateRequest.CheckSignature(); err != nil {
return BadRequest(errors.Wrap(err, "invalid csr"))
}
if s.OTT == "" {
return BadRequest(errors.New("missing ott"))
}
return nil
}
// SignResponse is the response object of the certificate signature request.
type SignResponse struct {
ServerPEM Certificate `json:"crt"`
CaPEM Certificate `json:"ca"`
TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"`
TLS *tls.ConnectionState `json:"-"`
}
// caHandler is the type used to implement the different CA HTTP endpoints.
type caHandler struct {
Authority Authority
}
// New creates a new RouterHandler with the CA endpoints.
func New(authority Authority) RouterHandler {
return &caHandler{
Authority: authority,
}
}
func (h *caHandler) Route(r Router) {
r.MethodFunc("GET", "/health", h.Health)
r.MethodFunc("GET", "/root/{sha}", h.Root)
r.MethodFunc("POST", "/sign", h.Sign)
r.MethodFunc("POST", "/renew", h.Renew)
r.MethodFunc("GET", "/provisioners", h.Provisioners)
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)
// For compatibility with old code:
r.MethodFunc("POST", "/re-sign", h.Renew)
}
// Health is an HTTP handler that returns the status of the server.
func (h *caHandler) Health(w http.ResponseWriter, r *http.Request) {
JSON(w, HealthResponse{Status: "ok"})
}
// Root is an HTTP handler that using the SHA256 from the URL, returns the root
// certificate for the given SHA256.
func (h *caHandler) Root(w http.ResponseWriter, r *http.Request) {
sha := chi.URLParam(r, "sha")
sum := strings.ToLower(strings.Replace(sha, "-", "", -1))
// Load root certificate with the
cert, err := h.Authority.Root(sum)
if err != nil {
WriteError(w, NotFound(errors.Wrapf(err, "%s was not found", r.RequestURI)))
return
}
JSON(w, &RootResponse{RootPEM: Certificate{cert}})
}
// Sign is an HTTP handler that reads a certificate request and an
// one-time-token (ott) from the body and creates a new certificate with the
// information in the certificate request.
func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
var body SignRequest
if err := ReadJSON(r.Body, &body); err != nil {
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
return
}
if err := body.Validate(); err != nil {
WriteError(w, err)
return
}
signOpts := authority.SignOptions{
NotBefore: body.NotBefore,
NotAfter: body.NotAfter,
}
extraOpts, err := h.Authority.Authorize(body.OTT)
if err != nil {
WriteError(w, Unauthorized(err))
return
}
cert, root, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, signOpts, extraOpts...)
if err != nil {
WriteError(w, Forbidden(err))
return
}
w.WriteHeader(http.StatusCreated)
JSON(w, &SignResponse{
ServerPEM: Certificate{cert},
CaPEM: Certificate{root},
TLSOptions: h.Authority.GetTLSOptions(),
})
}
// Renew uses the information of certificate in the TLS connection to create a
// new one.
func (h *caHandler) Renew(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
WriteError(w, BadRequest(errors.New("missing peer certificate")))
return
}
cert, root, err := h.Authority.Renew(r.TLS.PeerCertificates[0])
if err != nil {
WriteError(w, Forbidden(err))
return
}
w.WriteHeader(http.StatusCreated)
JSON(w, &SignResponse{
ServerPEM: Certificate{cert},
CaPEM: Certificate{root},
TLSOptions: h.Authority.GetTLSOptions(),
})
}
// Provisioners returns the list of provisioners configured in the authority.
func (h *caHandler) Provisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := parseCursor(r)
if err != nil {
WriteError(w, BadRequest(err))
return
}
p, next, err := h.Authority.GetProvisioners(cursor, limit)
if err != nil {
WriteError(w, InternalServerError(err))
return
}
JSON(w, &ProvisionersResponse{
Provisioners: p,
NextCursor: next,
})
}
// ProvisionerKey returns the encrypted key of a provisioner by it's key id.
func (h *caHandler) ProvisionerKey(w http.ResponseWriter, r *http.Request) {
kid := chi.URLParam(r, "kid")
key, err := h.Authority.GetEncryptedKey(kid)
if err != nil {
WriteError(w, NotFound(err))
return
}
JSON(w, &ProvisionerKeyResponse{key})
}
func parseCursor(r *http.Request) (cursor string, limit int, err error) {
q := r.URL.Query()
cursor = q.Get("cursor")
if v := q.Get("limit"); len(v) > 0 {
limit, err = strconv.Atoi(v)
if err != nil {
return "", 0, errors.Wrapf(err, "error converting %s to integer", v)
}
}
return
}