-
Notifications
You must be signed in to change notification settings - Fork 1
/
anvil.go
204 lines (162 loc) · 4.69 KB
/
anvil.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
package anvil
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/big"
"net/http"
jwt "github.com/dgrijalva/jwt-go"
)
// Claims is the payload we are looking to get on each request.
type Claims struct {
Jti string
Iss string
Sub string
Aud string
Exp int64
Iat int64
Scope string // This is where we find the authorization markers.
}
// key contains the JSON WEB KEY required to encrypt the JWT received on
// every request.
type key struct {
Type string `json:"kty"` // Key Type: RSA
Use string `json:"use"` // Verifiying signatures for `sig` or `enc`
Algorithm string `json:"alg"` // Algorithm to use: RS256
Modulus string `json:"n"` // The modulus section of the key
Exponent string `json:"e"` // The exponent of the key
}
// keys match the document returned by Anvil.io on the request.
// curl http://HOST/jwks
type keys struct {
Set []key `json:"keys"`
}
//==============================================================================
// Anvil provides support for validating Anvil.io based JWTs and extracting
// claims for authorization.
type Anvil struct {
PublicKey *rsa.PublicKey
}
// New create a new Anvil value for use with handling JWTs from Anvil.io.
func New(host string) (*Anvil, error) {
// Ask Anvil.io for the public keys.
r, err := http.Get(host + "/jwks")
if err != nil {
return nil, err
}
// Validate we successfully received the keys.
if r.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid Status: %d %s", r.StatusCode, r.Status)
}
defer r.Body.Close()
// Decode the document we received.
var ks keys
if err := json.NewDecoder(r.Body).Decode(&ks); err != nil {
return nil, err
}
// Find the `sig` key since this is what we need.
var jwk *key
for _, key := range ks.Set {
if key.Use == "sig" {
jwk = &key
break
}
}
// Did we find the `sig` key.
if jwk == nil {
return nil, errors.New("`Sig` key not found")
}
// Convert the `sig` key into an rsa public key.
pk, err := jwkToPK(*jwk)
if err != nil {
return nil, err
}
return &Anvil{PublicKey: pk}, nil
}
// ValidateFromRequest takes a request and extracts the JWT. Then it performs
// validation and returns the claims if everything is valid.
func (a *Anvil) ValidateFromRequest(r *http.Request) (Claims, error) {
// Function is required to return the public key that is needed to
// validate the JWT and extract the claims.
f := func(token *jwt.Token) (interface{}, error) {
return a.PublicKey, nil
}
// Parse the request, looking for the JWT and peforming transformations.
token, err := jwt.ParseFromRequest(r, f)
if err != nil {
return Claims{}, fmt.Errorf("Parse Error: %v", err)
}
// Was the token valid.
if !token.Valid {
return Claims{}, errors.New("Token is invalid")
}
// Trying to reduce the cost of unmarshaling the map into our struct so
// doing it manually. Calling Marshal/Unmarshal will cost more.
var claims Claims
if v, exists := token.Claims["jti"]; exists {
claims.Jti = v.(string)
}
if v, exists := token.Claims["iss"]; exists {
claims.Iss = v.(string)
}
if v, exists := token.Claims["sub"]; exists {
claims.Sub = v.(string)
}
if v, exists := token.Claims["aud"]; exists {
claims.Aud = v.(string)
}
if v, exists := token.Claims["exp"]; exists {
claims.Exp = int64(v.(float64))
}
if v, exists := token.Claims["iat"]; exists {
claims.Iat = int64(v.(float64))
}
if v, exists := token.Claims["scope"]; exists {
claims.Scope = v.(string)
}
return claims, nil
}
//==============================================================================
// jwkToPK converts the Anvil JWK into a RSA public key.
func jwkToPK(k key) (*rsa.PublicKey, error) {
// Convert the Modulus into a Big Int.
n, err := base64RawURLEncToBigInt(k.Modulus)
if err != nil {
return nil, err
}
// Convert the Exponent into a Big Int.
e, err := base64RawURLEncToBigInt(k.Exponent)
if err != nil {
return nil, err
}
// Create an rsa public key value based on the JWK received from Anvil.
key := rsa.PublicKey{
N: n,
E: int(e.Int64()),
}
return &key, nil
}
// base64StdEncToBigInt takes a base64 standard encoded string and converts
// it to a Big Int.
func base64StdEncToBigInt(str string) (*big.Int, error) {
decoded, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}
var bint big.Int
bint.SetBytes([]byte(decoded))
return &bint, nil
}
// base64RawURLEncToBigInt takes a base64 raw url encoded string and converts
// it to a Big Int.
func base64RawURLEncToBigInt(str string) (*big.Int, error) {
decoded, err := base64.RawURLEncoding.DecodeString(str)
if err != nil {
return nil, err
}
var bint big.Int
bint.SetBytes([]byte(decoded))
return &bint, nil
}