forked from smallstep/certificates
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauthorize.go
172 lines (153 loc) · 5.08 KB
/
authorize.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
package authority
import (
"crypto/x509"
"encoding/asn1"
"net/http"
"time"
"github.com/pkg/errors"
"gopkg.in/square/go-jose.v2/jwt"
)
type idUsed struct {
UsedAt int64 `json:"ua,omitempty"`
Subject string `json:"sub,omitempty"`
}
// matchesOne returns true if A and B share at least one element.
func matchesOne(as, bs []string) bool {
if len(bs) == 0 || len(as) == 0 {
return false
}
for _, b := range bs {
for _, a := range as {
if b == a {
return true
}
}
}
return false
}
// Authorize authorizes a signature request by validating and authenticating
// a OTT that must be sent w/ the request.
func (a *Authority) Authorize(ott string) ([]interface{}, error) {
var (
errContext = map[string]interface{}{"ott": ott}
claims = jwt.Claims{}
)
// Validate payload
token, err := jwt.ParseSigned(ott)
if err != nil {
return nil, &apiError{errors.Wrapf(err, "authorize: error parsing token"),
http.StatusUnauthorized, errContext}
}
// Get claims w/out verification. We need to look up the provisioner
// key in order to verify the claims and we need the issuer from the claims
// before we can look up the provisioner.
if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil, &apiError{err, http.StatusUnauthorized, errContext}
}
kid := token.Headers[0].KeyID // JWT will only have 1 header.
if len(kid) == 0 {
return nil, &apiError{errors.New("authorize: token KeyID cannot be empty"),
http.StatusUnauthorized, errContext}
}
pid := claims.Issuer + ":" + kid
val, ok := a.provisionerIDIndex.Load(pid)
if !ok {
return nil, &apiError{errors.Errorf("authorize: provisioner with id %s not found", pid),
http.StatusUnauthorized, errContext}
}
p, ok := val.(*Provisioner)
if !ok {
return nil, &apiError{errors.Errorf("authorize: invalid provisioner type"),
http.StatusInternalServerError, errContext}
}
if err = token.Claims(p.Key, &claims); err != nil {
return nil, &apiError{err, http.StatusUnauthorized, errContext}
}
// According to "rfc7519 JSON Web Token" acceptable skew should be no
// more than a few minutes.
if err = claims.ValidateWithLeeway(jwt.Expected{
Issuer: p.Name,
}, time.Minute); err != nil {
return nil, &apiError{errors.Wrapf(err, "authorize: invalid token"),
http.StatusUnauthorized, errContext}
}
// Do not accept tokens issued before the start of the ca.
// This check is meant as a stopgap solution to the current lack of a persistence layer.
if a.config.AuthorityConfig != nil && !a.config.AuthorityConfig.DisableIssuedAtCheck {
if claims.IssuedAt > 0 && claims.IssuedAt.Time().Before(a.startTime) {
return nil, &apiError{errors.New("token issued before the bootstrap of certificate authority"),
http.StatusUnauthorized, errContext}
}
}
if !matchesOne(claims.Audience, a.audiences) {
return nil, &apiError{errors.New("authorize: token audience invalid"), http.StatusUnauthorized,
errContext}
}
if claims.Subject == "" {
return nil, &apiError{errors.New("authorize: token subject cannot be empty"),
http.StatusUnauthorized, errContext}
}
signOps := []interface{}{
&commonNameClaim{claims.Subject},
&dnsNamesClaim{claims.Subject},
&ipAddressesClaim{claims.Subject},
p,
}
// Store the token to protect against reuse.
if _, ok := a.ottMap.LoadOrStore(claims.ID, &idUsed{
UsedAt: time.Now().Unix(),
Subject: claims.Subject,
}); ok {
return nil, &apiError{errors.Errorf("token already used"), http.StatusUnauthorized,
errContext}
}
return signOps, nil
}
// authorizeRenewal tries to locate the step provisioner extension, and checks
// if for the configured provisioner, the renewal is enabled or not. If the
// extra extension cannot be found, authorize the renewal by default.
//
// TODO(mariano): should we authorize by default?
func (a *Authority) authorizeRenewal(crt *x509.Certificate) error {
errContext := map[string]interface{}{"serialNumber": crt.SerialNumber.String()}
for _, e := range crt.Extensions {
if e.Id.Equal(stepOIDProvisioner) {
var provisioner stepProvisionerASN1
if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil {
return &apiError{
err: errors.Wrap(err, "error decoding step provisioner extension"),
code: http.StatusInternalServerError,
context: errContext,
}
}
// Look for the provisioner, if it cannot be found, renewal will not
// be authorized.
pid := string(provisioner.Name) + ":" + string(provisioner.CredentialID)
val, ok := a.provisionerIDIndex.Load(pid)
if !ok {
return &apiError{
err: errors.Errorf("not found: provisioner %s", pid),
code: http.StatusUnauthorized,
context: errContext,
}
}
p, ok := val.(*Provisioner)
if !ok {
return &apiError{
err: errors.Errorf("invalid type: provisioner %s, type %T", pid, val),
code: http.StatusInternalServerError,
context: errContext,
}
}
if p.Claims.IsDisableRenewal() {
return &apiError{
err: errors.Errorf("renew disabled: provisioner %s", pid),
code: http.StatusUnauthorized,
context: errContext,
}
}
return nil
}
}
return nil
}