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 TestChaCha20Poly1305EncryptDecrypt(t *testing.T) { 33 for i, test := range chaCha20Poly1305Tests { 34 key, _ := hex.DecodeString(test.key) 35 pt, _ := hex.DecodeString(test.plaintext) 36 ad, _ := hex.DecodeString(test.associatedData) 37 nonce, _ := hex.DecodeString(test.nonce) 38 out, _ := hex.DecodeString(test.out) 39 40 ca, err := subtle.NewChaCha20Poly1305(key) 41 if err != nil { 42 t.Errorf("#%d, cannot create new instance of ChaCha20Poly1305: %s", i, err) 43 continue 44 } 45 46 _, err = ca.Encrypt(pt, ad) 47 if err != nil { 48 t.Errorf("#%d, unexpected encryption error: %s", i, err) 49 continue 50 } 51 52 var combinedCT []byte 53 combinedCT = append(combinedCT, nonce...) 54 combinedCT = append(combinedCT, out...) 55 if got, err := ca.Decrypt(combinedCT, ad); err != nil { 56 t.Errorf("#%d, unexpected decryption error: %s", i, err) 57 continue 58 } else if !bytes.Equal(pt, got) { 59 t.Errorf("#%d, plaintext's don't match: got %x vs %x", i, got, pt) 60 continue 61 } 62 } 63} 64 65func TestChaCha20Poly1305EmptyAssociatedData(t *testing.T) { 66 key := random.GetRandomBytes(chacha20poly1305.KeySize) 67 emptyAD := []byte{} 68 badAD := []byte{1, 2, 3} 69 70 ca, err := subtle.NewChaCha20Poly1305(key) 71 if err != nil { 72 t.Fatal(err) 73 } 74 75 for i := 0; i < 75; i++ { 76 pt := random.GetRandomBytes(uint32(i)) 77 // Encrypting with associatedData as a 0-length array 78 { 79 ct, err := ca.Encrypt(pt, emptyAD) 80 if err != nil { 81 t.Errorf("Encrypt(%x, %x) failed", pt, emptyAD) 82 continue 83 } 84 85 if got, err := ca.Decrypt(ct, emptyAD); err != nil || !bytes.Equal(pt, got) { 86 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x", emptyAD, got, pt) 87 } 88 if got, err := ca.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) { 89 t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x", got, pt) 90 } 91 if _, err := ca.Decrypt(ct, badAD); err == nil { 92 t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAD) 93 } 94 } 95 // Encrypting with associatedData equal to nil 96 { 97 ct, err := ca.Encrypt(pt, nil) 98 if err != nil { 99 t.Errorf("Encrypt(%x, nil) failed", pt) 100 } 101 102 if got, err := ca.Decrypt(ct, emptyAD); err != nil || !bytes.Equal(pt, got) { 103 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", emptyAD, got, pt, err) 104 } 105 if got, err := ca.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) { 106 t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x; error: %v", got, pt, err) 107 } 108 if _, err := ca.Decrypt(ct, badAD); err == nil { 109 t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAD) 110 } 111 } 112 } 113} 114 115func TestChaCha20Poly1305LongMessages(t *testing.T) { 116 dataSize := uint32(16) 117 // Encrypts and decrypts messages of size <= 8192. 118 for dataSize <= 1<<24 { 119 pt := random.GetRandomBytes(dataSize) 120 ad := random.GetRandomBytes(dataSize / 3) 121 key := random.GetRandomBytes(chacha20poly1305.KeySize) 122 123 ca, err := subtle.NewChaCha20Poly1305(key) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 ct, err := ca.Encrypt(pt, ad) 129 if err != nil { 130 t.Errorf("Encrypt(%x, %x) failed", pt, ad) 131 continue 132 } 133 134 if got, err := ca.Decrypt(ct, ad); err != nil || !bytes.Equal(pt, got) { 135 t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", ad, got, pt, err) 136 } 137 138 dataSize += 5 * dataSize / 11 139 } 140} 141 142func TestChaCha20Poly1305ModifyCiphertext(t *testing.T) { 143 for i, test := range chaCha20Poly1305Tests { 144 key, _ := hex.DecodeString(test.key) 145 pt, _ := hex.DecodeString(test.plaintext) 146 ad, _ := hex.DecodeString(test.associatedData) 147 148 ca, err := subtle.NewChaCha20Poly1305(key) 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 ct, err := ca.Encrypt(pt, ad) 154 if err != nil { 155 t.Errorf("#%d: Encrypt failed", i) 156 continue 157 } 158 159 if len(ad) > 0 { 160 alteredIndex := rand.Intn(len(ad)) 161 ad[alteredIndex] ^= 0x80 162 if _, err := ca.Decrypt(ct, ad); err == nil { 163 t.Errorf("#%d: Decrypt was successful after altering associated data", i) 164 continue 165 } 166 ad[alteredIndex] ^= 0x80 167 } 168 169 alterCtIdx := rand.Intn(len(ct)) 170 ct[alterCtIdx] ^= 0x80 171 if _, err := ca.Decrypt(ct, ad); err == nil { 172 t.Errorf("#%d: Decrypt was successful after altering ciphertext", i) 173 continue 174 } 175 ct[alterCtIdx] ^= 0x80 176 } 177} 178 179// This is a very simple test for the randomness of the nonce. 180// The test simply checks that the multiple ciphertexts of the same message are distinct. 181func TestChaCha20Poly1305RandomNonce(t *testing.T) { 182 key := random.GetRandomBytes(chacha20poly1305.KeySize) 183 ca, err := subtle.NewChaCha20Poly1305(key) 184 if err != nil { 185 t.Fatal(err) 186 } 187 188 cts := make(map[string]bool) 189 pt, ad := []byte{}, []byte{} 190 for i := 0; i < 1<<10; i++ { 191 ct, err := ca.Encrypt(pt, ad) 192 ctHex := hex.EncodeToString(ct) 193 if err != nil || cts[ctHex] { 194 t.Errorf("TestRandomNonce failed: %v", err) 195 } else { 196 cts[ctHex] = true 197 } 198 } 199} 200 201func TestChaCha20Poly1305WycheproofCases(t *testing.T) { 202 testutil.SkipTestIfTestSrcDirIsNotSet(t) 203 suite := new(AEADSuite) 204 if err := testutil.PopulateSuite(suite, "chacha20_poly1305_test.json"); err != nil { 205 t.Fatalf("failed populating suite: %s", err) 206 } 207 for _, group := range suite.TestGroups { 208 if group.KeySize/8 != chacha20poly1305.KeySize { 209 continue 210 } 211 if group.IvSize/8 != chacha20poly1305.NonceSize { 212 continue 213 } 214 215 for _, test := range group.Tests { 216 caseName := fmt.Sprintf("%s-%s:Case-%d", suite.Algorithm, group.Type, test.CaseID) 217 t.Run(caseName, func(t *testing.T) { runChaCha20Poly1305WycheproofCase(t, test) }) 218 } 219 } 220} 221 222func runChaCha20Poly1305WycheproofCase(t *testing.T, tc *AEADCase) { 223 var combinedCT []byte 224 combinedCT = append(combinedCT, tc.Iv...) 225 combinedCT = append(combinedCT, tc.Ct...) 226 combinedCT = append(combinedCT, tc.Tag...) 227 228 ca, err := subtle.NewChaCha20Poly1305(tc.Key) 229 if err != nil { 230 t.Fatalf("cannot create new instance of ChaCha20Poly1305: %s", err) 231 } 232 233 _, err = ca.Encrypt(tc.Msg, tc.Aad) 234 if err != nil { 235 t.Fatalf("unexpected encryption error: %s", err) 236 } 237 238 decrypted, err := ca.Decrypt(combinedCT, tc.Aad) 239 if err != nil { 240 if tc.Result == "valid" { 241 t.Errorf("unexpected error: %s", err) 242 } 243 } else { 244 if tc.Result == "invalid" { 245 t.Error("decrypted invalid") 246 } 247 if !bytes.Equal(decrypted, tc.Msg) { 248 t.Error("incorrect decryption") 249 } 250 } 251} 252