-
Notifications
You must be signed in to change notification settings - Fork 51
/
jwt.go
140 lines (126 loc) · 4.61 KB
/
jwt.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
package pkitokens
import (
"context"
"crypto"
"fmt"
"io/ioutil"
"net/url"
jwt "github.com/dgrijalva/jwt-go"
"go.aporeto.io/trireme-lib/controller/pkg/usertokens/common"
)
// PKIJWTVerifier is a generic JWT PKI verifier. It assumes that the tokens have
// been signed by a private key, and it validates them with the provide public key.
// This is a simple and stateless verifier that doesn't depend on central server
// for validating the tokens. The public key is provided out-of-band.
type PKIJWTVerifier struct {
JWTCertPEM []byte
keys []crypto.PublicKey
RedirectURL string
}
// NewVerifierFromFile assumes that the input is provided as file path.
func NewVerifierFromFile(jwtcertPath string, redirectURI string, redirectOnFail, redirectOnNoToken bool) (*PKIJWTVerifier, error) {
jwtCertPEM, err := ioutil.ReadFile(jwtcertPath)
if err != nil {
return nil, fmt.Errorf("failed to read JWT signing certificates or public keys from file: %s", err)
}
return NewVerifierFromPEM(jwtCertPEM, redirectURI, redirectOnFail, redirectOnNoToken)
}
// NewVerifierFromPEM assumes that the input is a PEM byte array.
func NewVerifierFromPEM(jwtCertPEM []byte, redirectURI string, redirectOnFail, redirectOnNoToken bool) (*PKIJWTVerifier, error) {
keys, err := parsePublicKeysFromPEM(jwtCertPEM)
// pay attention to the return format of parsePublicKeysFromPEM
// when checking for an error here
if keys == nil && err != nil {
return nil, fmt.Errorf("failed to read JWT signing certificates or public keys from PEM: %s", err)
}
return &PKIJWTVerifier{
JWTCertPEM: jwtCertPEM,
keys: keys,
RedirectURL: redirectURI,
}, nil
}
// NewVerifier creates a new verifier from the provided configuration.
func NewVerifier(v *PKIJWTVerifier) (*PKIJWTVerifier, error) {
if len(v.JWTCertPEM) == 0 {
return v, nil
}
keys, err := parsePublicKeysFromPEM(v.JWTCertPEM)
// pay attention to the return format of parsePublicKeysFromPEM
// when checking for an error here
if keys == nil && err != nil {
return nil, fmt.Errorf("failed to parse JWT signing certificates or public keys from PEM: %s", err)
}
v.keys = keys
return v, nil
}
// Validate parses a generic JWT token and flattens the claims in a normalized form. It
// assumes that any of the JWT signing certs or public keys will validate the token.
func (j *PKIJWTVerifier) Validate(ctx context.Context, tokenString string) ([]string, bool, string, error) {
if len(tokenString) == 0 {
return []string{}, false, tokenString, fmt.Errorf("Empty token")
}
if len(j.keys) == 0 {
return []string{}, false, tokenString, fmt.Errorf("No public keys loaded into verifier")
}
// iterate over all public keys that we have and try to validate the token
// the first one to succeed will be used
var errs []error
for _, key := range j.keys {
claims := &jwt.MapClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
switch token.Method.(type) {
case *jwt.SigningMethodECDSA:
if isECDSAPublicKey(key) {
return key, nil
}
case *jwt.SigningMethodRSA:
if isRSAPublicKey(key) {
return key, nil
}
default:
return nil, fmt.Errorf("unsupported signing method '%T'", token.Method)
}
return nil, fmt.Errorf("signing method '%T' and public key type '%T' mismatch", token.Method, key)
})
// cover all error cases after parsing/verifying
if err != nil {
errs = append(errs, err)
continue
}
if token == nil {
errs = append(errs, fmt.Errorf("no token was parsed"))
continue
}
if !token.Valid {
errs = append(errs, fmt.Errorf("token failed to verify against public key"))
continue
}
// return successful on match/verification with the first key
attributes := []string{}
for k, v := range *claims {
attributes = append(attributes, common.FlattenClaim(k, v)...)
}
return attributes, false, tokenString, nil
}
// generate a detailed error
var detailedError string
for i, err := range errs {
detailedError += err.Error()
if i+1 < len(errs) {
detailedError += "; "
}
}
return []string{}, false, tokenString, fmt.Errorf("Invalid token - errors: [%s]", detailedError)
}
// VerifierType returns the type of the verifier.
func (j *PKIJWTVerifier) VerifierType() common.JWTType {
return common.PKI
}
// Callback is called by an IDP. Not implemented here. No central authorizer for the tokens.
func (j *PKIJWTVerifier) Callback(ctx context.Context, u *url.URL) (string, string, int, error) {
return "", "", 0, nil
}
// IssueRedirect issues a redirect. Not implemented. There is no need for a redirect.
func (j *PKIJWTVerifier) IssueRedirect(originURL string) string {
return ""
}