mirror of
				https://github.com/StackExchange/dnscontrol.git
				synced 2024-05-11 05:55:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			361 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			361 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*-
 | |
|  * 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/aes"
 | |
| 	"crypto/cipher"
 | |
| 	"crypto/hmac"
 | |
| 	"crypto/rand"
 | |
| 	"crypto/sha256"
 | |
| 	"crypto/sha512"
 | |
| 	"crypto/subtle"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"hash"
 | |
| 	"io"
 | |
| 
 | |
| 	"gopkg.in/square/go-jose.v2/cipher"
 | |
| )
 | |
| 
 | |
| // Random reader (stubbed out in tests)
 | |
| var randReader = rand.Reader
 | |
| 
 | |
| // Dummy key cipher for shared symmetric key mode
 | |
| type symmetricKeyCipher struct {
 | |
| 	key []byte // Pre-shared content-encryption key
 | |
| }
 | |
| 
 | |
| // 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: 16,
 | |
| 		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
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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:
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| }
 |