123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- /*-
- * Copyright 2014 Square Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package jose
- import (
- "crypto/ecdsa"
- "crypto/rsa"
- "errors"
- "fmt"
- "reflect"
- "gopkg.in/square/go-jose.v2/json"
- )
- // Encrypter represents an encrypter which produces an encrypted JWE object.
- type Encrypter interface {
- Encrypt(plaintext []byte) (*JSONWebEncryption, error)
- EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error)
- Options() EncrypterOptions
- }
- // A generic content cipher
- type contentCipher interface {
- keySize() int
- encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
- decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
- }
- // A key generator (for generating/getting a CEK)
- type keyGenerator interface {
- keySize() int
- genKey() ([]byte, rawHeader, error)
- }
- // A generic key encrypter
- type keyEncrypter interface {
- encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
- }
- // A generic key decrypter
- type keyDecrypter interface {
- decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
- }
- // A generic encrypter based on the given key encrypter and content cipher.
- type genericEncrypter struct {
- contentAlg ContentEncryption
- compressionAlg CompressionAlgorithm
- cipher contentCipher
- recipients []recipientKeyInfo
- keyGenerator keyGenerator
- extraHeaders map[HeaderKey]interface{}
- }
- type recipientKeyInfo struct {
- keyID string
- keyAlg KeyAlgorithm
- keyEncrypter keyEncrypter
- }
- // EncrypterOptions represents options that can be set on new encrypters.
- type EncrypterOptions struct {
- Compression CompressionAlgorithm
- // Optional map of additional keys to be inserted into the protected header
- // of a JWS object. Some specifications which make use of JWS like to insert
- // additional values here. All values must be JSON-serializable.
- ExtraHeaders map[HeaderKey]interface{}
- }
- // WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
- // if necessary. It returns itself and so can be used in a fluent style.
- func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
- if eo.ExtraHeaders == nil {
- eo.ExtraHeaders = map[HeaderKey]interface{}{}
- }
- eo.ExtraHeaders[k] = v
- return eo
- }
- // WithContentType adds a content type ("cty") header and returns the updated
- // EncrypterOptions.
- func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions {
- return eo.WithHeader(HeaderContentType, contentType)
- }
- // WithType adds a type ("typ") header and returns the updated EncrypterOptions.
- func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
- return eo.WithHeader(HeaderType, typ)
- }
- // Recipient represents an algorithm/key to encrypt messages to.
- //
- // PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used
- // on the password-based encryption algorithms PBES2-HS256+A128KW,
- // PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe
- // default of 100000 will be used for the count and a 128-bit random salt will
- // be generated.
- type Recipient struct {
- Algorithm KeyAlgorithm
- Key interface{}
- KeyID string
- PBES2Count int
- PBES2Salt []byte
- }
- // NewEncrypter creates an appropriate encrypter based on the key type
- func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) {
- encrypter := &genericEncrypter{
- contentAlg: enc,
- recipients: []recipientKeyInfo{},
- cipher: getContentCipher(enc),
- }
- if opts != nil {
- encrypter.compressionAlg = opts.Compression
- encrypter.extraHeaders = opts.ExtraHeaders
- }
- if encrypter.cipher == nil {
- return nil, ErrUnsupportedAlgorithm
- }
- var keyID string
- var rawKey interface{}
- switch encryptionKey := rcpt.Key.(type) {
- case JSONWebKey:
- keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
- case *JSONWebKey:
- keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
- case OpaqueKeyEncrypter:
- keyID, rawKey = encryptionKey.KeyID(), encryptionKey
- default:
- rawKey = encryptionKey
- }
- switch rcpt.Algorithm {
- case DIRECT:
- // Direct encryption mode must be treated differently
- if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
- return nil, ErrUnsupportedKeyType
- }
- if encrypter.cipher.keySize() != len(rawKey.([]byte)) {
- return nil, ErrInvalidKeySize
- }
- encrypter.keyGenerator = staticKeyGenerator{
- key: rawKey.([]byte),
- }
- recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
- recipientInfo.keyID = keyID
- if rcpt.KeyID != "" {
- recipientInfo.keyID = rcpt.KeyID
- }
- encrypter.recipients = []recipientKeyInfo{recipientInfo}
- return encrypter, nil
- case ECDH_ES:
- // ECDH-ES (w/o key wrapping) is similar to DIRECT mode
- typeOf := reflect.TypeOf(rawKey)
- if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
- return nil, ErrUnsupportedKeyType
- }
- encrypter.keyGenerator = ecKeyGenerator{
- size: encrypter.cipher.keySize(),
- algID: string(enc),
- publicKey: rawKey.(*ecdsa.PublicKey),
- }
- recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
- recipientInfo.keyID = keyID
- if rcpt.KeyID != "" {
- recipientInfo.keyID = rcpt.KeyID
- }
- encrypter.recipients = []recipientKeyInfo{recipientInfo}
- return encrypter, nil
- default:
- // Can just add a standard recipient
- encrypter.keyGenerator = randomKeyGenerator{
- size: encrypter.cipher.keySize(),
- }
- err := encrypter.addRecipient(rcpt)
- return encrypter, err
- }
- }
- // NewMultiEncrypter creates a multi-encrypter based on the given parameters
- func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) {
- cipher := getContentCipher(enc)
- if cipher == nil {
- return nil, ErrUnsupportedAlgorithm
- }
- if rcpts == nil || len(rcpts) == 0 {
- return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
- }
- encrypter := &genericEncrypter{
- contentAlg: enc,
- recipients: []recipientKeyInfo{},
- cipher: cipher,
- keyGenerator: randomKeyGenerator{
- size: cipher.keySize(),
- },
- }
- if opts != nil {
- encrypter.compressionAlg = opts.Compression
- }
- for _, recipient := range rcpts {
- err := encrypter.addRecipient(recipient)
- if err != nil {
- return nil, err
- }
- }
- return encrypter, nil
- }
- func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
- var recipientInfo recipientKeyInfo
- switch recipient.Algorithm {
- case DIRECT, ECDH_ES:
- return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
- }
- recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
- if recipient.KeyID != "" {
- recipientInfo.keyID = recipient.KeyID
- }
- switch recipient.Algorithm {
- case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
- if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok {
- sr.p2c = recipient.PBES2Count
- sr.p2s = recipient.PBES2Salt
- }
- }
- if err == nil {
- ctx.recipients = append(ctx.recipients, recipientInfo)
- }
- return err
- }
- func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
- switch encryptionKey := encryptionKey.(type) {
- case *rsa.PublicKey:
- return newRSARecipient(alg, encryptionKey)
- case *ecdsa.PublicKey:
- return newECDHRecipient(alg, encryptionKey)
- case []byte:
- return newSymmetricRecipient(alg, encryptionKey)
- case string:
- return newSymmetricRecipient(alg, []byte(encryptionKey))
- case *JSONWebKey:
- recipient, err := makeJWERecipient(alg, encryptionKey.Key)
- recipient.keyID = encryptionKey.KeyID
- return recipient, err
- }
- if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok {
- return newOpaqueKeyEncrypter(alg, encrypter)
- }
- return recipientKeyInfo{}, ErrUnsupportedKeyType
- }
- // newDecrypter creates an appropriate decrypter based on the key type
- func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
- switch decryptionKey := decryptionKey.(type) {
- case *rsa.PrivateKey:
- return &rsaDecrypterSigner{
- privateKey: decryptionKey,
- }, nil
- case *ecdsa.PrivateKey:
- return &ecDecrypterSigner{
- privateKey: decryptionKey,
- }, nil
- case []byte:
- return &symmetricKeyCipher{
- key: decryptionKey,
- }, nil
- case string:
- return &symmetricKeyCipher{
- key: []byte(decryptionKey),
- }, nil
- case JSONWebKey:
- return newDecrypter(decryptionKey.Key)
- case *JSONWebKey:
- return newDecrypter(decryptionKey.Key)
- }
- if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok {
- return &opaqueKeyDecrypter{decrypter: okd}, nil
- }
- return nil, ErrUnsupportedKeyType
- }
- // Implementation of encrypt method producing a JWE object.
- func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) {
- return ctx.EncryptWithAuthData(plaintext, nil)
- }
- // Implementation of encrypt method producing a JWE object.
- func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) {
- obj := &JSONWebEncryption{}
- obj.aad = aad
- obj.protected = &rawHeader{}
- err := obj.protected.set(headerEncryption, ctx.contentAlg)
- if err != nil {
- return nil, err
- }
- obj.recipients = make([]recipientInfo, len(ctx.recipients))
- if len(ctx.recipients) == 0 {
- return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
- }
- cek, headers, err := ctx.keyGenerator.genKey()
- if err != nil {
- return nil, err
- }
- obj.protected.merge(&headers)
- for i, info := range ctx.recipients {
- recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
- if err != nil {
- return nil, err
- }
- err = recipient.header.set(headerAlgorithm, info.keyAlg)
- if err != nil {
- return nil, err
- }
- if info.keyID != "" {
- err = recipient.header.set(headerKeyID, info.keyID)
- if err != nil {
- return nil, err
- }
- }
- obj.recipients[i] = recipient
- }
- if len(ctx.recipients) == 1 {
- // Move per-recipient headers into main protected header if there's
- // only a single recipient.
- obj.protected.merge(obj.recipients[0].header)
- obj.recipients[0].header = nil
- }
- if ctx.compressionAlg != NONE {
- plaintext, err = compress(ctx.compressionAlg, plaintext)
- if err != nil {
- return nil, err
- }
- err = obj.protected.set(headerCompression, ctx.compressionAlg)
- if err != nil {
- return nil, err
- }
- }
- for k, v := range ctx.extraHeaders {
- b, err := json.Marshal(v)
- if err != nil {
- return nil, err
- }
- (*obj.protected)[k] = makeRawMessage(b)
- }
- authData := obj.computeAuthData()
- parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
- if err != nil {
- return nil, err
- }
- obj.iv = parts.iv
- obj.ciphertext = parts.ciphertext
- obj.tag = parts.tag
- return obj, nil
- }
- func (ctx *genericEncrypter) Options() EncrypterOptions {
- return EncrypterOptions{
- Compression: ctx.compressionAlg,
- ExtraHeaders: ctx.extraHeaders,
- }
- }
- // Decrypt and validate the object and return the plaintext. Note that this
- // function does not support multi-recipient, if you desire multi-recipient
- // decryption use DecryptMulti instead.
- func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
- headers := obj.mergedHeaders(nil)
- if len(obj.recipients) > 1 {
- return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
- }
- critical, err := headers.getCritical()
- if err != nil {
- return nil, fmt.Errorf("square/go-jose: invalid crit header")
- }
- if len(critical) > 0 {
- return nil, fmt.Errorf("square/go-jose: unsupported crit header")
- }
- decrypter, err := newDecrypter(decryptionKey)
- if err != nil {
- return nil, err
- }
- cipher := getContentCipher(headers.getEncryption())
- if cipher == nil {
- return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
- }
- generator := randomKeyGenerator{
- size: cipher.keySize(),
- }
- parts := &aeadParts{
- iv: obj.iv,
- ciphertext: obj.ciphertext,
- tag: obj.tag,
- }
- authData := obj.computeAuthData()
- var plaintext []byte
- recipient := obj.recipients[0]
- recipientHeaders := obj.mergedHeaders(&recipient)
- cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
- if err == nil {
- // Found a valid CEK -- let's try to decrypt.
- plaintext, err = cipher.decrypt(cek, authData, parts)
- }
- if plaintext == nil {
- return nil, ErrCryptoFailure
- }
- // The "zip" header parameter may only be present in the protected header.
- if comp := obj.protected.getCompression(); comp != "" {
- plaintext, err = decompress(comp, plaintext)
- }
- return plaintext, err
- }
- // DecryptMulti decrypts and validates the object and returns the plaintexts,
- // with support for multiple recipients. It returns the index of the recipient
- // for which the decryption was successful, the merged headers for that recipient,
- // and the plaintext.
- func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
- globalHeaders := obj.mergedHeaders(nil)
- critical, err := globalHeaders.getCritical()
- if err != nil {
- return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
- }
- if len(critical) > 0 {
- return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
- }
- decrypter, err := newDecrypter(decryptionKey)
- if err != nil {
- return -1, Header{}, nil, err
- }
- encryption := globalHeaders.getEncryption()
- cipher := getContentCipher(encryption)
- if cipher == nil {
- return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
- }
- generator := randomKeyGenerator{
- size: cipher.keySize(),
- }
- parts := &aeadParts{
- iv: obj.iv,
- ciphertext: obj.ciphertext,
- tag: obj.tag,
- }
- authData := obj.computeAuthData()
- index := -1
- var plaintext []byte
- var headers rawHeader
- for i, recipient := range obj.recipients {
- recipientHeaders := obj.mergedHeaders(&recipient)
- cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
- if err == nil {
- // Found a valid CEK -- let's try to decrypt.
- plaintext, err = cipher.decrypt(cek, authData, parts)
- if err == nil {
- index = i
- headers = recipientHeaders
- break
- }
- }
- }
- if plaintext == nil || err != nil {
- return -1, Header{}, nil, ErrCryptoFailure
- }
- // The "zip" header parameter may only be present in the protected header.
- if comp := obj.protected.getCompression(); comp != "" {
- plaintext, err = decompress(comp, plaintext)
- }
- sanitized, err := headers.sanitized()
- if err != nil {
- return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
- }
- return index, sanitized, plaintext, err
- }
|