/*- * 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 ( "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/sha512" "crypto/subtle" "errors" "fmt" "hash" "io" "golang.org/x/crypto/pbkdf2" "gopkg.in/square/go-jose.v2/cipher" ) // Random reader (stubbed out in tests) var RandReader = rand.Reader const ( // RFC7518 recommends a minimum of 1,000 iterations: // https://tools.ietf.org/html/rfc7518#section-4.8.1.2 // NIST recommends a minimum of 10,000: // https://pages.nist.gov/800-63-3/sp800-63b.html // 1Password uses 100,000: // https://support.1password.com/pbkdf2/ defaultP2C = 100000 // Default salt size: 128 bits defaultP2SSize = 16 ) // Dummy key cipher for shared symmetric key mode type symmetricKeyCipher struct { key []byte // Pre-shared content-encryption key p2c int // PBES2 Count p2s []byte // PBES2 Salt Input } // Signer/verifier for MAC modes type symmetricMac struct { key []byte } // Input/output from an AEAD operation type aeadParts struct { iv, ciphertext, tag []byte } // A content cipher based on an AEAD construction type aeadContentCipher struct { keyBytes int authtagBytes int getAead func(key []byte) (cipher.AEAD, error) } // Random key generator type randomKeyGenerator struct { size int } // Static key generator type staticKeyGenerator struct { key []byte } // Create a new content cipher based on AES-GCM func newAESGCM(keySize int) contentCipher { return &aeadContentCipher{ keyBytes: keySize, authtagBytes: 16, getAead: func(key []byte) (cipher.AEAD, error) { aes, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewGCM(aes) }, } } // Create a new content cipher based on AES-CBC+HMAC func newAESCBC(keySize int) contentCipher { return &aeadContentCipher{ keyBytes: keySize * 2, authtagBytes: keySize, getAead: func(key []byte) (cipher.AEAD, error) { return josecipher.NewCBCHMAC(key, aes.NewCipher) }, } } // Get an AEAD cipher object for the given content encryption algorithm func getContentCipher(alg ContentEncryption) contentCipher { switch alg { case A128GCM: return newAESGCM(16) case A192GCM: return newAESGCM(24) case A256GCM: return newAESGCM(32) case A128CBC_HS256: return newAESCBC(16) case A192CBC_HS384: return newAESCBC(24) case A256CBC_HS512: return newAESCBC(32) default: return nil } } // getPbkdf2Params returns the key length and hash function used in // pbkdf2.Key. func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) { switch alg { case PBES2_HS256_A128KW: return 16, sha256.New case PBES2_HS384_A192KW: return 24, sha512.New384 case PBES2_HS512_A256KW: return 32, sha512.New default: panic("invalid algorithm") } } // getRandomSalt generates a new salt of the given size. func getRandomSalt(size int) ([]byte, error) { salt := make([]byte, size) _, err := io.ReadFull(RandReader, salt) if err != nil { return nil, err } return salt, nil } // newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap. func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) { switch keyAlg { case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW: case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: default: return recipientKeyInfo{}, ErrUnsupportedAlgorithm } return recipientKeyInfo{ keyAlg: keyAlg, keyEncrypter: &symmetricKeyCipher{ key: key, }, }, nil } // newSymmetricSigner creates a recipientSigInfo based on the given key. func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo, error) { // Verify that key management algorithm is supported by this encrypter switch sigAlg { case HS256, HS384, HS512: default: return recipientSigInfo{}, ErrUnsupportedAlgorithm } return recipientSigInfo{ sigAlg: sigAlg, signer: &symmetricMac{ key: key, }, }, nil } // Generate a random key for the given content cipher func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) { key := make([]byte, ctx.size) _, err := io.ReadFull(RandReader, key) if err != nil { return nil, rawHeader{}, err } return key, rawHeader{}, nil } // Key size for random generator func (ctx randomKeyGenerator) keySize() int { return ctx.size } // Generate a static key (for direct mode) func (ctx staticKeyGenerator) genKey() ([]byte, rawHeader, error) { cek := make([]byte, len(ctx.key)) copy(cek, ctx.key) return cek, rawHeader{}, nil } // Key size for static generator func (ctx staticKeyGenerator) keySize() int { return len(ctx.key) } // Get key size for this cipher func (ctx aeadContentCipher) keySize() int { return ctx.keyBytes } // Encrypt some data func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) { // Get a new AEAD instance aead, err := ctx.getAead(key) if err != nil { return nil, err } // Initialize a new nonce iv := make([]byte, aead.NonceSize()) _, err = io.ReadFull(RandReader, iv) if err != nil { return nil, err } ciphertextAndTag := aead.Seal(nil, iv, pt, aad) offset := len(ciphertextAndTag) - ctx.authtagBytes return &aeadParts{ iv: iv, ciphertext: ciphertextAndTag[:offset], tag: ciphertextAndTag[offset:], }, nil } // Decrypt some data func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte, error) { aead, err := ctx.getAead(key) if err != nil { return nil, err } if len(parts.iv) != aead.NonceSize() || len(parts.tag) < ctx.authtagBytes { return nil, ErrCryptoFailure } return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad) } // Encrypt the content encryption key. func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { switch alg { case DIRECT: return recipientInfo{ header: &rawHeader{}, }, nil case A128GCMKW, A192GCMKW, A256GCMKW: aead := newAESGCM(len(ctx.key)) parts, err := aead.encrypt(ctx.key, []byte{}, cek) if err != nil { return recipientInfo{}, err } header := &rawHeader{} header.set(headerIV, newBuffer(parts.iv)) header.set(headerTag, newBuffer(parts.tag)) return recipientInfo{ header: header, encryptedKey: parts.ciphertext, }, nil case A128KW, A192KW, A256KW: block, err := aes.NewCipher(ctx.key) if err != nil { return recipientInfo{}, err } jek, err := josecipher.KeyWrap(block, cek) if err != nil { return recipientInfo{}, err } return recipientInfo{ encryptedKey: jek, header: &rawHeader{}, }, nil case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: if len(ctx.p2s) == 0 { salt, err := getRandomSalt(defaultP2SSize) if err != nil { return recipientInfo{}, err } ctx.p2s = salt } if ctx.p2c <= 0 { ctx.p2c = defaultP2C } // salt is UTF8(Alg) || 0x00 || Salt Input salt := bytes.Join([][]byte{[]byte(alg), ctx.p2s}, []byte{0x00}) // derive key keyLen, h := getPbkdf2Params(alg) key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h) // use AES cipher with derived key block, err := aes.NewCipher(key) if err != nil { return recipientInfo{}, err } jek, err := josecipher.KeyWrap(block, cek) if err != nil { return recipientInfo{}, err } header := &rawHeader{} header.set(headerP2C, ctx.p2c) header.set(headerP2S, newBuffer(ctx.p2s)) return recipientInfo{ encryptedKey: jek, header: header, }, nil } return recipientInfo{}, ErrUnsupportedAlgorithm } // Decrypt the content encryption key. func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { switch headers.getAlgorithm() { case DIRECT: cek := make([]byte, len(ctx.key)) copy(cek, ctx.key) return cek, nil case A128GCMKW, A192GCMKW, A256GCMKW: aead := newAESGCM(len(ctx.key)) iv, err := headers.getIV() if err != nil { return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err) } tag, err := headers.getTag() if err != nil { return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err) } parts := &aeadParts{ iv: iv.bytes(), ciphertext: recipient.encryptedKey, tag: tag.bytes(), } cek, err := aead.decrypt(ctx.key, []byte{}, parts) if err != nil { return nil, err } return cek, nil case A128KW, A192KW, A256KW: block, err := aes.NewCipher(ctx.key) if err != nil { return nil, err } cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) if err != nil { return nil, err } return cek, nil case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: p2s, err := headers.getP2S() if err != nil { return nil, fmt.Errorf("square/go-jose: invalid P2S: %v", err) } if p2s == nil || len(p2s.data) == 0 { return nil, fmt.Errorf("square/go-jose: invalid P2S: must be present") } p2c, err := headers.getP2C() if err != nil { return nil, fmt.Errorf("square/go-jose: invalid P2C: %v", err) } if p2c <= 0 { return nil, fmt.Errorf("square/go-jose: invalid P2C: must be a positive integer") } // salt is UTF8(Alg) || 0x00 || Salt Input alg := headers.getAlgorithm() salt := bytes.Join([][]byte{[]byte(alg), p2s.bytes()}, []byte{0x00}) // derive key keyLen, h := getPbkdf2Params(alg) key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h) // use AES cipher with derived key block, err := aes.NewCipher(key) if err != nil { return nil, err } cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey) if err != nil { return nil, err } return cek, nil } return nil, ErrUnsupportedAlgorithm } // Sign the given payload func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { mac, err := ctx.hmac(payload, alg) if err != nil { return Signature{}, errors.New("square/go-jose: failed to compute hmac") } return Signature{ Signature: mac, protected: &rawHeader{}, }, nil } // Verify the given payload func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error { expected, err := ctx.hmac(payload, alg) if err != nil { return errors.New("square/go-jose: failed to compute hmac") } if len(mac) != len(expected) { return errors.New("square/go-jose: invalid hmac") } match := subtle.ConstantTimeCompare(mac, expected) if match != 1 { return errors.New("square/go-jose: invalid hmac") } return nil } // Compute the HMAC based on the given alg value func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) { var hash func() hash.Hash switch alg { case HS256: hash = sha256.New case HS384: hash = sha512.New384 case HS512: hash = sha512.New default: return nil, ErrUnsupportedAlgorithm } hmac := hmac.New(hash, ctx.key) // According to documentation, Write() on hash never fails _, _ = hmac.Write(payload) return hmac.Sum(nil), nil }