| /*- |
| * 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 |
| } |