github.com/trustbloc/kms-go@v1.1.2/crypto/tinkcrypto/primitive/aead/subtle/gojose_aes_cbc_hmac_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package subtle_test
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/aes"
    12  	"crypto/cipher"
    13  	"fmt"
    14  	"testing"
    15  
    16  	josecipher "github.com/go-jose/go-jose/v3/cipher"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/aead/subtle"
    20  )
    21  
    22  func TestNewAESCBCHMAC(t *testing.T) {
    23  	key := make([]byte, 64)
    24  
    25  	// Test various key sizes.
    26  	for i := 0; i < 64; i++ {
    27  		k := key[:i]
    28  		keySize := len(k)
    29  
    30  		c, err := subtle.NewAESCBCHMAC(k)
    31  
    32  		switch keySize {
    33  		case 32, 48, 64:
    34  			// Valid key sizes.
    35  			require.NoError(t, err, "want: valid cipher (key size=%d), got: error %v", len(k), err)
    36  
    37  			// Verify that the struct contents are correctly set.
    38  			require.Equal(t, len(k), len(c.Key), "want: key size=%d, got: key size=%d", keySize, len(c.Key))
    39  		default:
    40  			require.EqualError(t, err, fmt.Sprintf("aes_cbc_hmac: invalid AES CBC key size; want 32, 48 or 64, got %d", keySize))
    41  		}
    42  	}
    43  }
    44  
    45  func TestIETFTestVector(t *testing.T) {
    46  	// Source: https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5
    47  	plaintext := []byte{
    48  		0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
    49  		0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
    50  		0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
    51  		0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
    52  		0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
    53  		0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
    54  		0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
    55  		0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65,
    56  	}
    57  
    58  	aad := []byte{
    59  		0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
    60  		0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
    61  		0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73,
    62  	}
    63  
    64  	nonce := []byte{
    65  		0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04,
    66  	}
    67  
    68  	expectedCiphertext1 := []byte{
    69  		0xc8, 0x0e, 0xdf, 0xa3, 0x2d, 0xdf, 0x39, 0xd5, 0xef, 0x00, 0xc0, 0xb4, 0x68, 0x83, 0x42, 0x79,
    70  		0xa2, 0xe4, 0x6a, 0x1b, 0x80, 0x49, 0xf7, 0x92, 0xf7, 0x6b, 0xfe, 0x54, 0xb9, 0x03, 0xa9, 0xc9,
    71  		0xa9, 0x4a, 0xc9, 0xb4, 0x7a, 0xd2, 0x65, 0x5c, 0x5f, 0x10, 0xf9, 0xae, 0xf7, 0x14, 0x27, 0xe2,
    72  		0xfc, 0x6f, 0x9b, 0x3f, 0x39, 0x9a, 0x22, 0x14, 0x89, 0xf1, 0x63, 0x62, 0xc7, 0x03, 0x23, 0x36,
    73  		0x09, 0xd4, 0x5a, 0xc6, 0x98, 0x64, 0xe3, 0x32, 0x1c, 0xf8, 0x29, 0x35, 0xac, 0x40, 0x96, 0xc8,
    74  		0x6e, 0x13, 0x33, 0x14, 0xc5, 0x40, 0x19, 0xe8, 0xca, 0x79, 0x80, 0xdf, 0xa4, 0xb9, 0xcf, 0x1b,
    75  		0x38, 0x4c, 0x48, 0x6f, 0x3a, 0x54, 0xc5, 0x10, 0x78, 0x15, 0x8e, 0xe5, 0xd7, 0x9d, 0xe5, 0x9f,
    76  		0xbd, 0x34, 0xd8, 0x48, 0xb3, 0xd6, 0x95, 0x50, 0xa6, 0x76, 0x46, 0x34, 0x44, 0x27, 0xad, 0xe5,
    77  		0x4b, 0x88, 0x51, 0xff, 0xb5, 0x98, 0xf7, 0xf8, 0x00, 0x74, 0xb9, 0x47, 0x3c, 0x82, 0xe2, 0xdb,
    78  	}
    79  
    80  	expectedAuthtag1 := []byte{
    81  		0x65, 0x2c, 0x3f, 0xa3, 0x6b, 0x0a, 0x7c, 0x5b, 0x32, 0x19, 0xfa, 0xb3, 0xa3, 0x0b, 0xc1, 0xc4,
    82  	}
    83  
    84  	key1 := []byte{
    85  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    86  		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    87  	}
    88  
    89  	expectedCiphertext2 := []byte{
    90  		0xea, 0x65, 0xda, 0x6b, 0x59, 0xe6, 0x1e, 0xdb, 0x41, 0x9b, 0xe6, 0x2d, 0x19, 0x71, 0x2a, 0xe5,
    91  		0xd3, 0x03, 0xee, 0xb5, 0x00, 0x52, 0xd0, 0xdf, 0xd6, 0x69, 0x7f, 0x77, 0x22, 0x4c, 0x8e, 0xdb,
    92  		0x00, 0x0d, 0x27, 0x9b, 0xdc, 0x14, 0xc1, 0x07, 0x26, 0x54, 0xbd, 0x30, 0x94, 0x42, 0x30, 0xc6,
    93  		0x57, 0xbe, 0xd4, 0xca, 0x0c, 0x9f, 0x4a, 0x84, 0x66, 0xf2, 0x2b, 0x22, 0x6d, 0x17, 0x46, 0x21,
    94  		0x4b, 0xf8, 0xcf, 0xc2, 0x40, 0x0a, 0xdd, 0x9f, 0x51, 0x26, 0xe4, 0x79, 0x66, 0x3f, 0xc9, 0x0b,
    95  		0x3b, 0xed, 0x78, 0x7a, 0x2f, 0x0f, 0xfc, 0xbf, 0x39, 0x04, 0xbe, 0x2a, 0x64, 0x1d, 0x5c, 0x21,
    96  		0x05, 0xbf, 0xe5, 0x91, 0xba, 0xe2, 0x3b, 0x1d, 0x74, 0x49, 0xe5, 0x32, 0xee, 0xf6, 0x0a, 0x9a,
    97  		0xc8, 0xbb, 0x6c, 0x6b, 0x01, 0xd3, 0x5d, 0x49, 0x78, 0x7b, 0xcd, 0x57, 0xef, 0x48, 0x49, 0x27,
    98  		0xf2, 0x80, 0xad, 0xc9, 0x1a, 0xc0, 0xc4, 0xe7, 0x9c, 0x7b, 0x11, 0xef, 0xc6, 0x00, 0x54, 0xe3,
    99  	}
   100  
   101  	expectedAuthtag2 := []byte{
   102  		0x84, 0x90, 0xac, 0x0e, 0x58, 0x94, 0x9b, 0xfe, 0x51, 0x87, 0x5d, 0x73, 0x3f, 0x93, 0xac, 0x20,
   103  		0x75, 0x16, 0x80, 0x39, 0xcc, 0xc7, 0x33, 0xd7,
   104  	}
   105  
   106  	key2 := []byte{
   107  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
   108  		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
   109  		0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
   110  	}
   111  
   112  	// Key3 is not 32, 48 or 64 in size and therefore not supported by go-jose.
   113  	// key3 := []byte{
   114  	//	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
   115  	//	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
   116  	//	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
   117  	//	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37}
   118  
   119  	expectedCiphertext4 := []byte{
   120  		0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5, 0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10, 0xff, 0xbd,
   121  		0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26, 0x91, 0x2d, 0xa0, 0x37, 0xec, 0xbc, 0xc7, 0xbd,
   122  		0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b, 0xcc, 0xb5, 0x84, 0xad, 0x3e, 0x92, 0x79, 0xc2,
   123  		0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07, 0x75, 0x53, 0xdf, 0x82, 0x94, 0x10, 0x44, 0x6b,
   124  		0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6, 0x42, 0x7e, 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1,
   125  		0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b, 0xfe, 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3,
   126  		0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41, 0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e,
   127  		0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0, 0x53, 0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b,
   128  		0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7, 0xa4, 0x93, 0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6,
   129  	}
   130  
   131  	expectedAuthtag4 := []byte{
   132  		0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf,
   133  		0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5,
   134  	}
   135  
   136  	key4 := []byte{
   137  		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
   138  		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
   139  		0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
   140  		0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
   141  	}
   142  
   143  	tests := []struct {
   144  		name               string
   145  		plaintext          []byte
   146  		aad                []byte
   147  		expectedCiphertext []byte
   148  		expectedAuthtag    []byte
   149  		key                []byte
   150  		nonce              []byte
   151  	}{
   152  		{
   153  			name:               "AEAD_AES_128_CBC_HMAC_SHA256",
   154  			plaintext:          plaintext,
   155  			aad:                aad,
   156  			expectedCiphertext: expectedCiphertext1,
   157  			expectedAuthtag:    expectedAuthtag1,
   158  			key:                key1,
   159  			nonce:              nonce,
   160  		},
   161  		{
   162  			name:               "AEAD_AES_192_CBC_HMAC_SHA384",
   163  			plaintext:          plaintext,
   164  			aad:                aad,
   165  			expectedCiphertext: expectedCiphertext2,
   166  			expectedAuthtag:    expectedAuthtag2,
   167  			key:                key2,
   168  			nonce:              nonce,
   169  		},
   170  		// {
   171  		//	name:               "AEAD_AES_256_CBC_HMAC_SHA384",
   172  		//	plaintext:          plaintext,
   173  		//	aad:                aad,
   174  		//	expectedCiphertext: expectedCiphertext3,
   175  		//	expectedAuthtag:    expectedAuthtag3,
   176  		// Key3 is not supported by Go-Jose (key length=56 not supported). This is why this test is commented out.
   177  		//	key:                key3,
   178  		//	nonce:              nonce,
   179  		// },
   180  		{
   181  			name:               "AEAD_AES_256_CBC_HMAC_SHA512",
   182  			plaintext:          plaintext,
   183  			aad:                aad,
   184  			expectedCiphertext: expectedCiphertext4,
   185  			expectedAuthtag:    expectedAuthtag4,
   186  			key:                key4,
   187  			nonce:              nonce,
   188  		},
   189  	}
   190  
   191  	t.Parallel()
   192  
   193  	for _, test := range tests {
   194  		tc := test
   195  		t.Run(tc.name, func(t *testing.T) {
   196  			cbcHMAC, err := josecipher.NewCBCHMAC(tc.key, aes.NewCipher)
   197  			require.NoError(t, err)
   198  
   199  			enc := mockNONCEInCBCHMAC{
   200  				nonce:   nonce,
   201  				cbcHMAC: cbcHMAC,
   202  			}
   203  
   204  			out, err := enc.Encrypt(plaintext, aad)
   205  			require.NoError(t, err, "unable to encrypt")
   206  
   207  			tagSize := len(tc.expectedAuthtag)
   208  
   209  			ct := make([]byte, len(nonce)+len(tc.expectedCiphertext)+len(tc.expectedAuthtag))
   210  			copy(ct, nonce)
   211  			copy(ct[len(nonce):], tc.expectedCiphertext)
   212  			copy(ct[len(nonce)+len(tc.expectedCiphertext):], tc.expectedAuthtag)
   213  
   214  			out1, err := enc.Decrypt(ct, aad)
   215  			require.NoError(t, err, "unable to decrypt")
   216  
   217  			require.EqualValues(t, plaintext, out1)
   218  
   219  			if !bytes.Equal(out[len(nonce):len(out)-tagSize], tc.expectedCiphertext) {
   220  				t.Error("Ciphertext did not match, got", out[len(nonce):len(out)-tagSize], "wanted", tc.expectedCiphertext)
   221  			}
   222  
   223  			if !bytes.Equal(out[len(out)-tagSize:], tc.expectedAuthtag) {
   224  				t.Error("Auth tag did not match, got", out[len(out)-tagSize:], "wanted", tc.expectedAuthtag)
   225  			}
   226  		})
   227  	}
   228  }
   229  
   230  type mockNONCEInCBCHMAC struct {
   231  	subtle.AESCBCHMAC
   232  
   233  	cbcHMAC cipher.AEAD
   234  	nonce   []byte
   235  }
   236  
   237  // Encrypt using the mocked nonce instead of generating a random one.
   238  func (a *mockNONCEInCBCHMAC) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
   239  	AESCBCIVSize := 16
   240  
   241  	ciphertext := a.cbcHMAC.Seal(nil, a.nonce, plaintext, additionalData)
   242  
   243  	ciphertextAndIV := make([]byte, AESCBCIVSize+len(ciphertext))
   244  	if n := copy(ciphertextAndIV, a.nonce); n != AESCBCIVSize {
   245  		return nil, fmt.Errorf("aes_cbc_hmac: failed to copy IV (copied %d/%d bytes)", n, AESCBCIVSize)
   246  	}
   247  
   248  	copy(ciphertextAndIV[AESCBCIVSize:], ciphertext)
   249  
   250  	return ciphertextAndIV, nil
   251  }
   252  
   253  func (a *mockNONCEInCBCHMAC) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
   254  	ivSize := a.cbcHMAC.NonceSize()
   255  	if len(ciphertext) < ivSize {
   256  		return nil, fmt.Errorf("aes_cbc_hmac: ciphertext too short")
   257  	}
   258  
   259  	iv := ciphertext[:ivSize]
   260  
   261  	return a.cbcHMAC.Open(nil, iv, ciphertext[ivSize:], additionalData)
   262  }
   263  
   264  func TestAESCBCRoundtrip(t *testing.T) {
   265  	key128 := []byte{
   266  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   267  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   268  	}
   269  
   270  	key192 := []byte{
   271  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   272  		0, 1, 2, 3, 4, 5, 6, 7,
   273  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   274  		0, 1, 2, 3, 4, 5, 6, 7,
   275  	}
   276  
   277  	key256 := []byte{
   278  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   279  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   280  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   281  		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
   282  	}
   283  
   284  	RunRoundtrip(t, key128)
   285  	RunRoundtrip(t, key192)
   286  	RunRoundtrip(t, key256)
   287  }
   288  
   289  func RunRoundtrip(t *testing.T, key []byte) {
   290  	aead, err := subtle.NewAESCBCHMAC(key)
   291  	require.NoError(t, err)
   292  
   293  	// Test pre-existing data in dst buffer
   294  	plaintext := []byte{0, 0, 0, 0}
   295  	aad := []byte{4, 3, 2, 1}
   296  
   297  	result, err := aead.Encrypt(plaintext, aad)
   298  	require.NoError(t, err)
   299  
   300  	result, err = aead.Decrypt(result, aad)
   301  	require.NoError(t, err)
   302  	require.EqualValues(t, plaintext, result, "Plaintext does not match output")
   303  
   304  	t.Run("failure: bad cipher", func(t *testing.T) {
   305  		result, err = aead.Decrypt([]byte("bad cipher"), aad)
   306  		require.EqualError(t, err, "aes_cbc_hmac: ciphertext too short")
   307  	})
   308  
   309  	t.Run("failure: cipher not short but not large enough to contain an authentication tag", func(t *testing.T) {
   310  		result, err = aead.Decrypt([]byte("bad cipher with not too short length to cause decryption failure"), aad)
   311  		require.EqualError(t, err, "aes_cbc_hmac: failed to decrypt: go-jose/go-jose: invalid ciphertext "+
   312  			"(auth tag mismatch)")
   313  	})
   314  }