1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15//////////////////////////////////////////////////////////////////////////////// 16 17package subtle_test 18 19import ( 20 "bytes" 21 "encoding/hex" 22 "fmt" 23 "math/rand" 24 "testing" 25 26 "golang.org/x/crypto/chacha20poly1305" 27 "github.com/google/tink/go/aead/subtle" 28 "github.com/google/tink/go/subtle/random" 29 "github.com/google/tink/go/testutil" 30) 31 32func TestXChaCha20Poly1305EncryptDecrypt(t *testing.T) { 33 for i, test := range xChaCha20Poly1305Tests { 34 key, _ := hex.DecodeString(test.key) 35 pt, _ := hex.DecodeString(test.plaintext) 36 aad, _ := hex.DecodeString(test.aad) 37 nonce, _ := hex.DecodeString(test.nonce) 38 out, _ := hex.DecodeString(test.out) 39 tag, _ := hex.DecodeString(test.tag) 40 41 x, err := subtle.NewXChaCha20Poly1305(key) 42 if err != nil { 43 t.Errorf("#%d, cannot create new instance of XChaCha20Poly1305: %s", i, err) 44 continue 45 } 46 47 _, err = x.Encrypt(pt, aad) 48 if err != nil { 49 t.Errorf("#%d, unexpected encryption error: %s", i, err) 50 continue 51 } 52 53 var combinedCt []byte 54 combinedCt = append(combinedCt, nonce...) 55 combinedCt = append(combinedCt, out...) 56 combinedCt = append(combinedCt, tag...) 57 if got, err := x.Decrypt(combinedCt, aad); err != nil { 58 t.Errorf("#%d, unexpected decryption error: %s", i, err) 59 continue 60 } else if !bytes.Equal(pt, got) { 61 t.Errorf("#%d, plaintext's don't match: got %x vs %x", i, got, pt) 62 continue 63 } 64 } 65} 66 67func TestXChaCha20Poly1305EmptyAssociatedData(t *testing.T) { 68 key := random.GetRandomBytes(chacha20poly1305.KeySize) 69 aad := []byte{} 70 badAad := []byte{1, 2, 3} 71 72 x, err := subtle.NewXChaCha20Poly1305(key) 73 if err != nil { 74 t.Fatal(err) 75 } 76 77 for i := 0; i < 75; i++ { 78 pt := random.GetRandomBytes(uint32(i)) 79 // Encrpting with aad as a 0-length array 80 { 81 ct, err := x.Encrypt(pt, aad) 82 if err != nil { 83 t.Errorf("Encrypt(%x, %x) failed", pt, aad) 84 continue 85 } 86 87 if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) { 88 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x", aad, got, pt) 89 } 90 if got, err := x.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) { 91 t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x", got, pt) 92 } 93 if _, err := x.Decrypt(ct, badAad); err == nil { 94 t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad) 95 } 96 } 97 // Encrpting with aad equal to null 98 { 99 ct, err := x.Encrypt(pt, nil) 100 if err != nil { 101 t.Errorf("Encrypt(%x, nil) failed", pt) 102 } 103 104 if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) { 105 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err) 106 } 107 if got, err := x.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) { 108 t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x; error: %v", got, pt, err) 109 } 110 if _, err := x.Decrypt(ct, badAad); err == nil { 111 t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad) 112 } 113 } 114 } 115} 116 117func TestXChaCha20Poly1305LongMessages(t *testing.T) { 118 dataSize := uint32(16) 119 // Encrypts and decrypts messages of size <= 8192. 120 for dataSize <= 1<<24 { 121 pt := random.GetRandomBytes(dataSize) 122 aad := random.GetRandomBytes(dataSize / 3) 123 key := random.GetRandomBytes(chacha20poly1305.KeySize) 124 125 x, err := subtle.NewXChaCha20Poly1305(key) 126 if err != nil { 127 t.Fatal(err) 128 } 129 130 ct, err := x.Encrypt(pt, aad) 131 if err != nil { 132 t.Errorf("Encrypt(%x, %x) failed", pt, aad) 133 continue 134 } 135 136 if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) { 137 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err) 138 } 139 140 dataSize += 5 * dataSize / 11 141 } 142} 143 144func TestXChaCha20Poly1305ModifyCiphertext(t *testing.T) { 145 for i, test := range xChaCha20Poly1305Tests { 146 key, _ := hex.DecodeString(test.key) 147 pt, _ := hex.DecodeString(test.plaintext) 148 aad, _ := hex.DecodeString(test.aad) 149 150 x, err := subtle.NewXChaCha20Poly1305(key) 151 if err != nil { 152 t.Fatal(err) 153 } 154 155 ct, err := x.Encrypt(pt, aad) 156 if err != nil { 157 t.Errorf("#%d: Encrypt failed", i) 158 continue 159 } 160 161 if len(aad) > 0 { 162 alterAadIdx := rand.Intn(len(aad)) 163 aad[alterAadIdx] ^= 0x80 164 if _, err := x.Decrypt(ct, aad); err == nil { 165 t.Errorf("#%d: Decrypt was successful after altering additional data", i) 166 continue 167 } 168 aad[alterAadIdx] ^= 0x80 169 } 170 171 alterCtIdx := rand.Intn(len(ct)) 172 ct[alterCtIdx] ^= 0x80 173 if _, err := x.Decrypt(ct, aad); err == nil { 174 t.Errorf("#%d: Decrypt was successful after altering ciphertext", i) 175 continue 176 } 177 ct[alterCtIdx] ^= 0x80 178 } 179} 180 181// This is a very simple test for the randomness of the nonce. 182// The test simply checks that the multiple ciphertexts of the same message are distinct. 183func TestXChaCha20Poly1305RandomNonce(t *testing.T) { 184 key := random.GetRandomBytes(chacha20poly1305.KeySize) 185 x, err := subtle.NewXChaCha20Poly1305(key) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 cts := make(map[string]bool) 191 pt, aad := []byte{}, []byte{} 192 for i := 0; i < 1<<10; i++ { 193 ct, err := x.Encrypt(pt, aad) 194 ctHex := hex.EncodeToString(ct) 195 if err != nil || cts[ctHex] { 196 t.Errorf("TestRandomNonce failed: %v", err) 197 } else { 198 cts[ctHex] = true 199 } 200 } 201} 202 203func TestXChaCha20Poly1305WycheproofCases(t *testing.T) { 204 testutil.SkipTestIfTestSrcDirIsNotSet(t) 205 suite := new(AEADSuite) 206 if err := testutil.PopulateSuite(suite, "xchacha20_poly1305_test.json"); err != nil { 207 t.Fatalf("failed populating suite: %s", err) 208 } 209 for _, group := range suite.TestGroups { 210 if group.KeySize/8 != chacha20poly1305.KeySize { 211 continue 212 } 213 if group.IvSize/8 != chacha20poly1305.NonceSizeX { 214 continue 215 } 216 for _, test := range group.Tests { 217 caseName := fmt.Sprintf("%s-%s:Case-%d", suite.Algorithm, group.Type, test.CaseID) 218 t.Run(caseName, func(t *testing.T) { runXChaCha20Poly1305WycheproofCase(t, test) }) 219 } 220 } 221} 222 223func runXChaCha20Poly1305WycheproofCase(t *testing.T, tc *AEADCase) { 224 var combinedCt []byte 225 combinedCt = append(combinedCt, tc.Iv...) 226 combinedCt = append(combinedCt, tc.Ct...) 227 combinedCt = append(combinedCt, tc.Tag...) 228 229 ca, err := subtle.NewXChaCha20Poly1305(tc.Key) 230 if err != nil { 231 t.Fatalf("cannot create new instance of ChaCha20Poly1305: %s", err) 232 } 233 234 _, err = ca.Encrypt(tc.Msg, tc.Aad) 235 if err != nil { 236 t.Fatalf("unexpected encryption error: %s", err) 237 } 238 239 decrypted, err := ca.Decrypt(combinedCt, tc.Aad) 240 if err != nil { 241 if tc.Result == "valid" { 242 t.Errorf("unexpected error: %s", err) 243 } 244 } else { 245 if tc.Result == "invalid" { 246 t.Error("decrypted invalid") 247 } 248 if !bytes.Equal(decrypted, tc.Msg) { 249 t.Error("incorrect decryption") 250 } 251 } 252} 253 254func TestPreallocatedCiphertextMemoryInXChaCha20Poly1305IsExact(t *testing.T) { 255 key := random.GetRandomBytes(chacha20poly1305.KeySize) 256 a, err := subtle.NewXChaCha20Poly1305(key) 257 if err != nil { 258 t.Fatalf("aead.NewAESGCMInsecureIV() err = %v, want nil", err) 259 } 260 plaintext := random.GetRandomBytes(13) 261 associatedData := random.GetRandomBytes(17) 262 263 ciphertext, err := a.Encrypt(plaintext, associatedData) 264 if err != nil { 265 t.Fatalf("a.Encrypt() err = %v, want nil", err) 266 } 267 // Encrypt() uses cipher.Overhead() to pre-allocate the memory needed store the ciphertext. 268 // For ChaCha20Poly1305, the size of the allocated memory should always be exact. If this check 269 // fails, the pre-allocated memory was too large or too small. If it was too small, the system had 270 // to re-allocate more memory, which is expensive and should be avoided. 271 if len(ciphertext) != cap(ciphertext) { 272 t.Errorf("want len(ciphertext) == cap(ciphertext), got %d != %d", len(ciphertext), cap(ciphertext)) 273 } 274} 275