forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jws.go
209 lines (182 loc) · 6.83 KB
/
jws.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
// Package jws implements the digital Signature on JSON based data
// structures as described in https://tools.ietf.org/html/rfc7515
//
// If you do not care about the details, the only things that you
// would need to use are the following functions:
//
// jws.SignWithOption(Payload, algorithm, key)
// jws.Verify(encodedjws, algorithm, key)
//
// To sign, simply use `jws.SignWithOption`. `Payload` is a []byte buffer that
// contains whatever data you want to sign. `alg` is one of the
// jwa.SignatureAlgorithm constants from package jwa. For RSA and
// ECDSA family of algorithms, you will need to prepare a private key.
// For HMAC family, you just need a []byte value. The `jws.SignWithOption`
// function will return the encoded JWS message on success.
//
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
// and verify the result using `algorithm` and `key`. Upon successful
// verification, the original Payload is returned, so you can work on it.
package jws
import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwa"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jwk"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/sign"
"github.com/open-policy-agent/opa/topdown/internal/jwx/jws/verify"
"strings"
"github.com/pkg/errors"
)
// SignLiteral generates a Signature for the given Payload and Headers, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
func SignLiteral(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, hdrBuf []byte) ([]byte, error) {
encodedHdr := base64.RawURLEncoding.EncodeToString(hdrBuf)
encodedPayload := base64.RawURLEncoding.EncodeToString(payload)
signingInput := strings.Join(
[]string{
encodedHdr,
encodedPayload,
}, ".",
)
signer, err := sign.New(alg)
if err != nil {
return nil, errors.Wrap(err, `failed to create signer`)
}
signature, err := signer.Sign([]byte(signingInput), key)
if err != nil {
return nil, errors.Wrap(err, `failed to sign Payload`)
}
encodedSignature := base64.RawURLEncoding.EncodeToString(signature)
compactSerialization := strings.Join(
[]string{
signingInput,
encodedSignature,
}, ".",
)
return []byte(compactSerialization), nil
}
// SignWithOption generates a Signature for the given Payload, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
// If you would like to pass custom Headers, use the WithHeaders option.
func SignWithOption(payload []byte, alg jwa.SignatureAlgorithm, key interface{}) ([]byte, error) {
var headers Headers = &StandardHeaders{}
err := headers.Set(AlgorithmKey, alg)
if err != nil {
return nil, errors.Wrap(err, "Failed to set alg value")
}
hdrBuf, err := json.Marshal(headers)
if err != nil {
return nil, errors.Wrap(err, `failed to marshal Headers`)
}
return SignLiteral(payload, alg, key, hdrBuf)
}
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
// If the verification is successful, `err` is nil, and the content of the
// Payload that was signed is returned. If you need more fine-grained
// control of the verification process, manually call `Parse`, generate a
// verifier, and call `Verify` on the parsed JWS message object.
func Verify(buf []byte, alg jwa.SignatureAlgorithm, key interface{}) (ret []byte, err error) {
verifier, err := verify.New(alg)
if err != nil {
return nil, errors.Wrap(err, "failed to create verifier")
}
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New(`attempt to verify empty buffer`)
}
parts, err := SplitCompact(string(buf[:]))
if err != nil {
return nil, errors.Wrap(err, `failed extract from compact serialization format`)
}
signingInput := strings.Join(
[]string{
parts[0],
parts[1],
}, ".",
)
decodedSignature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, errors.Wrap(err, "Failed to decode signature")
}
if err := verifier.Verify([]byte(signingInput), decodedSignature, key); err != nil {
return nil, errors.Wrap(err, "Failed to verify message")
}
if decodedPayload, err := base64.RawURLEncoding.DecodeString(parts[1]); err == nil {
return decodedPayload, nil
}
return nil, errors.Wrap(err, "Failed to decode Payload")
}
// VerifyWithJWK verifies the JWS message using the specified JWK
func VerifyWithJWK(buf []byte, key jwk.Key) (payload []byte, err error) {
keyVal, err := key.Materialize()
if err != nil {
return nil, errors.Wrap(err, "Failed to materialize key")
}
return Verify(buf, key.GetAlgorithm(), keyVal)
}
// VerifyWithJWKSet verifies the JWS message using JWK key set.
// By default it will only pick up keys that have the "use" key
// set to either "sig" or "enc", but you can override it by
// providing a keyaccept function.
func VerifyWithJWKSet(buf []byte, keyset *jwk.Set) (payload []byte, err error) {
for _, key := range keyset.Keys {
payload, err := VerifyWithJWK(buf, key)
if err == nil {
return payload, nil
}
}
return nil, errors.New("failed to verify with any of the keys")
}
// ParseByte parses a JWS value serialized via compact serialization and provided as []byte.
func ParseByte(jwsCompact []byte) (m *Message, err error) {
return parseCompact(string(jwsCompact[:]))
}
// ParseString parses a JWS value serialized via compact serialization and provided as string.
func ParseString(s string) (*Message, error) {
return parseCompact(s)
}
// SplitCompact splits a JWT and returns its three parts
// separately: Protected Headers, Payload and Signature.
func SplitCompact(jwsCompact string) ([]string, error) {
parts := strings.Split(jwsCompact, ".")
if len(parts) < 3 {
return nil, errors.New("Failed to split compact serialization")
}
return parts, nil
}
// parseCompact parses a JWS value serialized via compact serialization.
func parseCompact(str string) (m *Message, err error) {
var decodedHeader, decodedPayload, decodedSignature []byte
parts, err := SplitCompact(str)
if err != nil {
return nil, errors.Wrap(err, `invalid compact serialization format`)
}
if decodedHeader, err = base64.RawURLEncoding.DecodeString(parts[0]); err != nil {
return nil, errors.Wrap(err, `failed to decode Headers`)
}
var hdr StandardHeaders
if err := json.Unmarshal(decodedHeader, &hdr); err != nil {
return nil, errors.Wrap(err, `failed to parse JOSE Headers`)
}
if decodedPayload, err = base64.RawURLEncoding.DecodeString(parts[1]); err != nil {
return nil, errors.Wrap(err, `failed to decode Payload`)
}
if len(parts) > 2 {
if decodedSignature, err = base64.RawURLEncoding.DecodeString(parts[2]); err != nil {
return nil, errors.Wrap(err, `failed to decode Signature`)
}
}
var msg Message
msg.Payload = decodedPayload
msg.Signatures = append(msg.Signatures, &Signature{
Protected: &hdr,
Signature: decodedSignature,
})
return &msg, nil
}