mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 01:22:28 -05:00 
			
		
		
		
	
		
			
	
	
		
			483 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			483 lines
		
	
	
	
		
			11 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 ( | ||
|  | 	"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 | ||
|  | } |