CVE-2016-9123: Integer Overflow or Wraparound (CWE-190)
value: go-jose before 1.0.5 suffers from a CBC-HMAC integer overflow on 32-bit architectures. An integer overflow could lead to authentication bypass for CBC-HMAC encrypted ciphertexts on 32-bit architectures.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.5
Published: 2017-03-28T02:59Z
Repository: https://github.com/square/go-jose
Language: Go
Files changed: cipher/cbc_hmac.go, cipher/cbc_hmac_test.go, cipher/concat_kdf.go, cipher/ecdh_es.go
Commit date: 2016-09-02 21:47:34 -0700
Commit message: Use uint64 for all size calculations, size checks

Diff: diff --git a/cipher/cbc_hmac.go b/cipher/cbc_hmac.go
index a5c3583..126b85c 100644
--- a/cipher/cbc_hmac.go
+++ b/cipher/cbc_hmac.go
@@ -80,20 +80,20 @@ func (ctx *cbcAEAD) Overhead() int {
 }
 
 // Seal encrypts and authenticates the plaintext.
 func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
 	// Output buffer -- must take care not to mangle plaintext input.
-	ciphertext := make([]byte, len(plaintext)+ctx.Overhead())[:len(plaintext)]
+	ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
 	copy(ciphertext, plaintext)
 	ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
 
 	cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
 
 	cbc.CryptBlocks(ciphertext, ciphertext)
 	authtag := ctx.computeAuthTag(data, nonce, ciphertext)
 
-	ret, out := resize(dst, len(dst)+len(ciphertext)+len(authtag))
+	ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
 	copy(out, ciphertext)
 	copy(out[len(ciphertext):], authtag)
 
 	return ret
 }
@@ -126,24 +126,24 @@ func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
 	plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
 	if err != nil {
 		return nil, err
 	}
 
-	ret, out := resize(dst, len(dst)+len(plaintext))
+	ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
 	copy(out, plaintext)
 
 	return ret, nil
 }
 
 // Compute an authentication tag
 func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
-	buffer := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
+	buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
 	n := 0
 	n += copy(buffer, aad)
 	n += copy(buffer[n:], nonce)
 	n += copy(buffer[n:], ciphertext)
-	binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad)*8))
+	binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
 
 	// According to documentation, Write() on hash.Hash never fails.
 	hmac := hmac.New(ctx.hash, ctx.integrityKey)
 	_, _ = hmac.Write(buffer)
 
@@ -151,12 +151,12 @@ func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
 }
 
 // resize ensures the the given slice has a capacity of at least n bytes.
 // If the capacity of the slice is less than n, a new slice is allocated
 // and the existing data will be copied.
-func resize(in []byte, n int) (head, tail []byte) {
-	if cap(in) >= n {
+func resize(in []byte, n uint64) (head, tail []byte) {
+	if uint64(cap(in)) >= n {
 		head = in[:n]
 	} else {
 		head = make([]byte, n)
 		copy(head, in)
 	}
@@ -166,11 +166,11 @@ func resize(in []byte, n int) (head, tail []byte) {
 }
 
 // Apply padding
 func padBuffer(buffer []byte, blockSize int) []byte {
 	missing := blockSize - (len(buffer) % blockSize)
-	ret, out := resize(buffer, len(buffer)+missing)
+	ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
 	padding := bytes.Repeat([]byte{byte(missing)}, missing)
 	copy(out, padding)
 	return ret
 }
 
diff --git a/cipher/cbc_hmac_test.go b/cipher/cbc_hmac_test.go
index c230271..40bcb20 100644
--- a/cipher/cbc_hmac_test.go
+++ b/cipher/cbc_hmac_test.go
@@ -281,11 +281,11 @@ func TestTruncatedCiphertext(t *testing.T) {
 
 	ctx := aead.(*cbcAEAD)
 	ct := aead.Seal(nil, nonce, data, nil)
 
 	// Truncated ciphertext, but with correct auth tag
-	truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], len(ct)-2)
+	truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], uint64(len(ct))-2)
 	copy(tail, ctx.computeAuthTag(nil, nonce, truncated[:len(truncated)-ctx.authtagBytes]))
 
 	// Open should fail
 	_, err = aead.Open(nil, nonce, truncated, nil)
 	if err == nil {
@@ -311,12 +311,12 @@ func TestInvalidPaddingOpen(t *testing.T) {
 
 	aead, _ := NewCBCHMAC(key, aes.NewCipher)
 	ctx := aead.(*cbcAEAD)
 
 	// Mutated ciphertext, but with correct auth tag
-	size := len(buffer)
-	ciphertext, tail := resize(buffer, size+(len(key)/2))
+	size := uint64(len(buffer))
+	ciphertext, tail := resize(buffer, size+(uint64(len(key))/2))
 	copy(tail, ctx.computeAuthTag(nil, nonce, ciphertext[:size]))
 
 	// Open should fail (b/c of invalid padding, even though tag matches)
 	_, err := aead.Open(nil, nonce, ciphertext, nil)
 	if err == nil || !strings.Contains(err.Error(), "invalid padding") {
diff --git a/cipher/concat_kdf.go b/cipher/concat_kdf.go
index cbb5f7b..f62c3bd 100644
--- a/cipher/concat_kdf.go
+++ b/cipher/concat_kdf.go
@@ -30,11 +30,11 @@ type concatKDF struct {
 	hasher  hash.Hash
 }
 
 // NewConcatKDF builds a KDF reader based on the given inputs.
 func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
-	buffer := make([]byte, len(algID)+len(ptyUInfo)+len(ptyVInfo)+len(supPubInfo)+len(supPrivInfo))
+	buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
 	n := 0
 	n += copy(buffer, algID)
 	n += copy(buffer[n:], ptyUInfo)
 	n += copy(buffer[n:], ptyVInfo)
 	n += copy(buffer[n:], supPubInfo)
diff --git a/cipher/ecdh_es.go b/cipher/ecdh_es.go
index 05d48ae..f23d49e 100644
--- a/cipher/ecdh_es.go
+++ b/cipher/ecdh_es.go
@@ -22,12 +22,17 @@ import (
 	"encoding/binary"
 )
 
 // DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
 // It is an error to call this function with a private/public key that are not on the same
-// curve. Callers must ensure that the keys are valid before calling this function.
+// curve. Callers must ensure that the keys are valid before calling this function. Output
+// size may be at most 1<<16 bytes (64 KiB).
 func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
+	if size > 1<<16 {
+		panic("ECDH-ES output size too large, must be less than 1<<16")
+	}
+
 	// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
 	algID := lengthPrefixed([]byte(alg))
 	ptyUInfo := lengthPrefixed(apuData)
 	ptyVInfo := lengthPrefixed(apvData)
 

Vulnerable code: 	ciphertext := make([]byte, len(plaintext)+ctx.Overhead())[:len(plaintext)]
	ret, out := resize(dst, len(dst)+len(ciphertext)+len(authtag))
	ret, out := resize(dst, len(dst)+len(plaintext))
	buffer := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
	binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad)*8))
func resize(in []byte, n int) (head, tail []byte) {
	if cap(in) >= n {
	ret, out := resize(buffer, len(buffer)+missing)
	truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], len(ct)-2)
	size := len(buffer)
	ciphertext, tail := resize(buffer, size+(len(key)/2))
	buffer := make([]byte, len(algID)+len(ptyUInfo)+len(ptyVInfo)+len(supPubInfo)+len(supPrivInfo))
// curve. Callers must ensure that the keys are valid before calling this function.
Fixed code: 	ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
	ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
	ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
	buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
	binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
func resize(in []byte, n uint64) (head, tail []byte) {
	if uint64(cap(in)) >= n {
	ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
	truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], uint64(len(ct))-2)
	size := uint64(len(buffer))
	ciphertext, tail := resize(buffer, size+(uint64(len(key))/2))
	buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
// curve. Callers must ensure that the keys are valid before calling this function. Output
// size may be at most 1<<16 bytes (64 KiB).
	if size > 1<<16 {
		panic("ECDH-ES output size too large, must be less than 1<<16")
	}