-
-
Notifications
You must be signed in to change notification settings - Fork 155
/
jwe.go
377 lines (326 loc) · 10.7 KB
/
jwe.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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
//go:generate ./gen.sh
// Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516
package jwe
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"io"
"io/ioutil"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/keyconv"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/jwe/internal/keyenc"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
"github.com/lestrrat-go/jwx/x25519"
"github.com/pkg/errors"
)
var registry = json.NewRegistry()
// Encrypt takes the plaintext payload and encrypts it in JWE compact format.
// `key` should be a public key, and it may be a raw key (e.g. rsa.PublicKey) or a jwk.Key
//
// Encrypt currently does not support multi-recipient messages.
func Encrypt(payload []byte, keyalg jwa.KeyEncryptionAlgorithm, key interface{}, contentalg jwa.ContentEncryptionAlgorithm, compressalg jwa.CompressionAlgorithm, options ...EncryptOption) ([]byte, error) {
var protected Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeader{}:
protected = option.Value().(Headers)
}
}
if protected == nil {
protected = NewHeaders()
}
contentcrypt, err := content_crypt.NewGeneric(contentalg)
if err != nil {
return nil, errors.Wrap(err, `failed to create AES encrypter`)
}
var keyID string
if jwkKey, ok := key.(jwk.Key); ok {
keyID = jwkKey.KeyID()
var raw interface{}
if err := jwkKey.Raw(&raw); err != nil {
return nil, errors.Wrapf(err, `failed to retrieve raw key out of %T`, key)
}
key = raw
}
var enc keyenc.Encrypter
switch keyalg {
case jwa.RSA1_5:
var pubkey rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewRSAPKCSEncrypt(keyalg, &pubkey)
if err != nil {
return nil, errors.Wrap(err, "failed to create RSA PKCS encrypter")
}
case jwa.RSA_OAEP, jwa.RSA_OAEP_256:
var pubkey rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewRSAOAEPEncrypt(keyalg, &pubkey)
if err != nil {
return nil, errors.Wrap(err, "failed to create RSA OAEP encrypter")
}
case jwa.A128KW, jwa.A192KW, jwa.A256KW,
jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW,
jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
sharedkey, ok := key.([]byte)
if !ok {
return nil, errors.New("invalid key: []byte required")
}
switch keyalg {
case jwa.A128KW, jwa.A192KW, jwa.A256KW:
enc, err = keyenc.NewAES(keyalg, sharedkey)
case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
enc, err = keyenc.NewPBES2Encrypt(keyalg, sharedkey)
default:
enc, err = keyenc.NewAESGCMEncrypt(keyalg, sharedkey)
}
if err != nil {
return nil, errors.Wrap(err, "failed to create key wrap encrypter")
}
// NOTE: there was formerly a restriction, introduced
// in PR #26, which disallowed certain key/content
// algorithm combinations. This seemed bogus, and
// interop with the jose tool demonstrates it.
case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
var keysize int
switch keyalg {
case jwa.ECDH_ES:
// https://tools.ietf.org/html/rfc7518#page-15
// In Direct Key Agreement mode, the output of the Concat KDF MUST be a
// key of the same length as that used by the "enc" algorithm.
keysize = contentcrypt.KeySize()
case jwa.ECDH_ES_A128KW:
keysize = 16
case jwa.ECDH_ES_A192KW:
keysize = 24
case jwa.ECDH_ES_A256KW:
keysize = 32
}
switch key := key.(type) {
case x25519.PublicKey:
enc, err = keyenc.NewECDHESEncrypt(keyalg, contentalg, keysize, key)
default:
var pubkey ecdsa.PublicKey
if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewECDHESEncrypt(keyalg, contentalg, keysize, &pubkey)
}
if err != nil {
return nil, errors.Wrap(err, "failed to create ECDHS key wrap encrypter")
}
case jwa.DIRECT:
sharedkey, ok := key.([]byte)
if !ok {
return nil, errors.New("invalid key: []byte required")
}
enc, _ = keyenc.NewNoop(keyalg, sharedkey)
default:
return nil, errors.Errorf(`invalid key encryption algorithm (%s)`, keyalg)
}
if keyID != "" {
enc.SetKeyID(keyID)
}
keysize := contentcrypt.KeySize()
encctx := getEncryptCtx()
defer releaseEncryptCtx(encctx)
encctx.protected = protected
encctx.contentEncrypter = contentcrypt
encctx.generator = keygen.NewRandom(keysize)
encctx.keyEncrypters = []keyenc.Encrypter{enc}
encctx.compress = compressalg
msg, err := encctx.Encrypt(payload)
if err != nil {
return nil, errors.Wrap(err, "failed to encrypt payload")
}
return Compact(msg)
}
// DecryptCtx is used internally when jwe.Decrypt is called, and is
// passed for hooks that you may pass into it.
//
// Regular users should not have to touch this object, but if you need advanced handling
// of messages, you might have to use it. Only use it when you really
// understand how JWE processing works in this library.
type DecryptCtx interface {
Algorithm() jwa.KeyEncryptionAlgorithm
SetAlgorithm(jwa.KeyEncryptionAlgorithm)
Key() interface{}
SetKey(interface{})
Message() *Message
SetMessage(*Message)
}
type decryptCtx struct {
alg jwa.KeyEncryptionAlgorithm
key interface{}
msg *Message
}
func (ctx *decryptCtx) Algorithm() jwa.KeyEncryptionAlgorithm {
return ctx.alg
}
func (ctx *decryptCtx) SetAlgorithm(v jwa.KeyEncryptionAlgorithm) {
ctx.alg = v
}
func (ctx *decryptCtx) Key() interface{} {
return ctx.key
}
func (ctx *decryptCtx) SetKey(v interface{}) {
ctx.key = v
}
func (ctx *decryptCtx) Message() *Message {
return ctx.msg
}
func (ctx *decryptCtx) SetMessage(m *Message) {
ctx.msg = m
}
// Decrypt takes the key encryption algorithm and the corresponding
// key to decrypt the JWE message, and returns the decrypted payload.
// The JWE message can be either compact or full JSON format.
//
// `key` must be a private key. It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key
func Decrypt(buf []byte, alg jwa.KeyEncryptionAlgorithm, key interface{}, options ...DecryptOption) ([]byte, error) {
var ctx decryptCtx
ctx.key = key
ctx.alg = alg
var dst *Message
var postParse PostParser
//nolint:forcetypeassert
for _, option := range options {
switch option.Ident() {
case identMessage{}:
dst = option.Value().(*Message)
case identPostParser{}:
postParse = option.Value().(PostParser)
}
}
msg, err := parseJSONOrCompact(buf, true)
if err != nil {
return nil, errors.Wrap(err, "failed to parse buffer for Decrypt")
}
ctx.msg = msg
if postParse != nil {
if err := postParse.PostParse(&ctx); err != nil {
return nil, errors.Wrap(err, `failed to execute PostParser hook`)
}
}
payload, err := doDecryptCtx(&ctx)
if err != nil {
return nil, errors.Wrap(err, `failed to decrypt message`)
}
if dst != nil {
*dst = *msg
dst.rawProtectedHeaders = nil
dst.storeProtectedHeaders = false
}
return payload, nil
}
// Parse parses the JWE message into a Message object. The JWE message
// can be either compact or full JSON format.
func Parse(buf []byte) (*Message, error) {
return parseJSONOrCompact(buf, false)
}
func parseJSONOrCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New("empty buffer")
}
if buf[0] == '{' {
return parseJSON(buf, storeProtectedHeaders)
}
return parseCompact(buf, storeProtectedHeaders)
}
// ParseString is the same as Parse, but takes a string.
func ParseString(s string) (*Message, error) {
return Parse([]byte(s))
}
// ParseReader is the same as Parse, but takes an io.Reader.
func ParseReader(src io.Reader) (*Message, error) {
buf, err := ioutil.ReadAll(src)
if err != nil {
return nil, errors.Wrap(err, `failed to read from io.Reader`)
}
return Parse(buf)
}
func parseJSON(buf []byte, storeProtectedHeaders bool) (*Message, error) {
m := NewMessage()
m.storeProtectedHeaders = storeProtectedHeaders
if err := json.Unmarshal(buf, &m); err != nil {
return nil, errors.Wrap(err, "failed to parse JSON")
}
return m, nil
}
func parseCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
parts := bytes.Split(buf, []byte{'.'})
if len(parts) != 5 {
return nil, errors.Errorf(`compact JWE format must have five parts (%d)`, len(parts))
}
hdrbuf, err := base64.Decode(parts[0])
if err != nil {
return nil, errors.Wrap(err, `failed to parse first part of compact form`)
}
protected := NewHeaders()
if err := json.Unmarshal(hdrbuf, protected); err != nil {
return nil, errors.Wrap(err, "failed to parse header JSON")
}
ivbuf, err := base64.Decode(parts[2])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode iv")
}
ctbuf, err := base64.Decode(parts[3])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode content")
}
tagbuf, err := base64.Decode(parts[4])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode tag")
}
m := NewMessage()
if err := m.Set(CipherTextKey, ctbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, CipherTextKey)
}
if err := m.Set(InitializationVectorKey, ivbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, InitializationVectorKey)
}
if err := m.Set(ProtectedHeadersKey, protected); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, ProtectedHeadersKey)
}
if err := m.makeDummyRecipient(string(parts[1]), protected); err != nil {
return nil, errors.Wrap(err, `failed to setup recipient`)
}
if err := m.Set(TagKey, tagbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, TagKey)
}
if storeProtectedHeaders {
// This is later used for decryption.
m.rawProtectedHeaders = parts[0]
}
return m, nil
}
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In that case you would register a custom field as follows
//
// jwe.RegisterCustomField(`x-birthday`, timeT)
//
// Then `hdr.Get("x-birthday")` will still return an `interface{}`,
// but you can convert its type to `time.Time`
//
// bdayif, _ := hdr.Get(`x-birthday`)
// bday := bdayif.(time.Time)
func RegisterCustomField(name string, object interface{}) {
registry.Register(name, object)
}