mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 20:02:25 -06:00 
			
		
		
		
	* start fixing up tests * fix up tests + automate with drone * fiddle with linting * messing about with drone.yml * some more fiddling * hmmm * add cache * add vendor directory * verbose * ci updates * update some little things * update sig
		
			
				
	
	
		
			482 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			482 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
 | 
						|
}
 |