1 // Copyright 2022 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 package com.google.crypto.tink.integration.gcpkms; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 import static java.util.Arrays.asList; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.api.services.cloudkms.v1.CloudKMS; 24 import com.google.api.services.cloudkms.v1.model.DecryptRequest; 25 import com.google.api.services.cloudkms.v1.model.DecryptResponse; 26 import com.google.api.services.cloudkms.v1.model.EncryptRequest; 27 import com.google.api.services.cloudkms.v1.model.EncryptResponse; 28 import com.google.crypto.tink.aead.AeadConfig; 29 import java.io.IOException; 30 import org.junit.BeforeClass; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.junit.runners.JUnit4; 34 35 @RunWith(JUnit4.class) 36 public final class FakeCloudKmsTest { 37 38 private static final String KEY_ID = 39 "projects/tink-test/locations/global/keyRings/unit-test/cryptoKeys/aead-key"; 40 private static final String KEY_ID_2 = 41 "projects/tink-test/locations/global/keyRings/unit-test/cryptoKeys/aead-key-2"; 42 43 @BeforeClass setUpClass()44 public static void setUpClass() throws Exception { 45 AeadConfig.register(); 46 } 47 48 @Test testEncryptDecryptWithValidKeyId_success()49 public void testEncryptDecryptWithValidKeyId_success() throws Exception { 50 CloudKMS kms = new FakeCloudKms(asList(KEY_ID)); 51 52 byte[] plaintext = "plaintext".getBytes(UTF_8); 53 byte[] associatedData = "associatedData".getBytes(UTF_8); 54 55 EncryptRequest encRequest = 56 new EncryptRequest() 57 .encodePlaintext(plaintext) 58 .encodeAdditionalAuthenticatedData(associatedData); 59 60 EncryptResponse encResponse = 61 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest).execute(); 62 63 DecryptRequest decRequest = 64 new DecryptRequest() 65 .encodeCiphertext(encResponse.decodeCiphertext()) 66 .encodeAdditionalAuthenticatedData(associatedData); 67 68 DecryptResponse decResponse = 69 kms.projects().locations().keyRings().cryptoKeys().decrypt(KEY_ID, decRequest).execute(); 70 71 assertThat(decResponse.decodePlaintext()).isEqualTo(plaintext); 72 } 73 74 @Test encryptEmptyData_decryptReturnsNull()75 public void encryptEmptyData_decryptReturnsNull() throws Exception { 76 CloudKMS kms = new FakeCloudKms(asList(KEY_ID)); 77 78 byte[] plaintext = "".getBytes(UTF_8); 79 byte[] associatedData = "associatedData".getBytes(UTF_8); 80 81 EncryptRequest encRequest = 82 new EncryptRequest() 83 .encodePlaintext(plaintext) 84 .encodeAdditionalAuthenticatedData(associatedData); 85 86 EncryptResponse encResponse = 87 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest).execute(); 88 89 DecryptRequest decRequest = 90 new DecryptRequest() 91 .encodeCiphertext(encResponse.decodeCiphertext()) 92 .encodeAdditionalAuthenticatedData(associatedData); 93 94 DecryptResponse decResponse = 95 kms.projects().locations().keyRings().cryptoKeys().decrypt(KEY_ID, decRequest).execute(); 96 97 assertThat(decResponse.decodePlaintext()).isNull(); 98 } 99 100 @Test testEncryptWithUnknownKeyId_executeFails()101 public void testEncryptWithUnknownKeyId_executeFails() throws Exception { 102 CloudKMS kms = new FakeCloudKms(asList(KEY_ID)); 103 104 byte[] plaintext = "plaintext".getBytes(UTF_8); 105 byte[] associatedData = "associatedData".getBytes(UTF_8); 106 107 EncryptRequest encRequest = 108 new EncryptRequest() 109 .encodePlaintext(plaintext) 110 .encodeAdditionalAuthenticatedData(associatedData); 111 112 // The RPC to the KMS is done when execute is called. Therefore, calling encrypt for a 113 // valid but unknown key id does not (yet) fail. 114 CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Encrypt enc = 115 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID_2, encRequest); 116 assertThrows(IOException.class, enc::execute); 117 } 118 119 @Test testEncryptWithInvalidKeyId_encryptFails()120 public void testEncryptWithInvalidKeyId_encryptFails() throws Exception { 121 String invalidId = "invalid"; 122 123 CloudKMS kms = new FakeCloudKms(asList(invalidId)); 124 125 byte[] plaintext = "plaintext".getBytes(UTF_8); 126 byte[] associatedData = "associatedData".getBytes(UTF_8); 127 128 EncryptRequest encRequest = 129 new EncryptRequest() 130 .encodePlaintext(plaintext) 131 .encodeAdditionalAuthenticatedData(associatedData); 132 133 // Encrypts validates the format of the key id, and fails if it is invalid. 134 CloudKMS.Projects.Locations.KeyRings.CryptoKeys cryptoKeys = 135 kms.projects().locations().keyRings().cryptoKeys(); 136 assertThrows(IllegalArgumentException.class, () -> cryptoKeys.encrypt(invalidId, encRequest)); 137 } 138 139 @Test testDecryptWithInvalidKeyId_decryptFails()140 public void testDecryptWithInvalidKeyId_decryptFails() throws Exception { 141 String invalidId = "invalid"; 142 143 CloudKMS kms = new FakeCloudKms(asList(KEY_ID, invalidId)); 144 145 byte[] plaintext = "plaintext".getBytes(UTF_8); 146 byte[] associatedData = "associatedData".getBytes(UTF_8); 147 148 EncryptRequest encRequest = 149 new EncryptRequest() 150 .encodePlaintext(plaintext) 151 .encodeAdditionalAuthenticatedData(associatedData); 152 153 EncryptResponse encResponse = 154 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest).execute(); 155 156 DecryptRequest decRequest = 157 new DecryptRequest() 158 .encodeCiphertext(encResponse.decodeCiphertext()) 159 .encodeAdditionalAuthenticatedData(associatedData); 160 161 // Decrypt validates the format of the key id, and fails if it is invalid. 162 CloudKMS.Projects.Locations.KeyRings.CryptoKeys cryptoKeys = 163 kms.projects().locations().keyRings().cryptoKeys(); 164 assertThrows(IllegalArgumentException.class, () -> cryptoKeys.decrypt(invalidId, decRequest)); 165 } 166 167 168 @Test testDecryptWithWrongKeyId_decryptFails()169 public void testDecryptWithWrongKeyId_decryptFails() throws Exception { 170 CloudKMS kms = new FakeCloudKms(asList(KEY_ID, KEY_ID_2)); 171 172 byte[] plaintext = "plaintext".getBytes(UTF_8); 173 byte[] associatedData = "associatedData".getBytes(UTF_8); 174 175 EncryptRequest encRequest = 176 new EncryptRequest() 177 .encodePlaintext(plaintext) 178 .encodeAdditionalAuthenticatedData(associatedData); 179 180 EncryptResponse encResponse = 181 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest).execute(); 182 183 DecryptRequest decRequest = 184 new DecryptRequest() 185 .encodeCiphertext(encResponse.decodeCiphertext()) 186 .encodeAdditionalAuthenticatedData(associatedData); 187 188 // The RPC to the KMS is done when execute is called. Therefore, calling decrypt with a wrong 189 // key id does not (yet) fail. 190 CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Decrypt dec = 191 kms.projects() 192 .locations() 193 .keyRings() 194 .cryptoKeys() 195 .decrypt(KEY_ID_2, decRequest); 196 assertThrows(IOException.class, dec::execute); 197 } 198 199 @Test testDecryptExecuteWithInvalidCiphertext_executeFails()200 public void testDecryptExecuteWithInvalidCiphertext_executeFails() throws Exception { 201 CloudKMS kms = new FakeCloudKms(asList(KEY_ID)); 202 203 byte[] invalidCiphertext = "invalid".getBytes(UTF_8); 204 byte[] associatedData = "associatedData".getBytes(UTF_8); 205 206 DecryptRequest decRequestWithInvalidCiphertext = 207 new DecryptRequest() 208 .encodeCiphertext(invalidCiphertext) 209 .encodeAdditionalAuthenticatedData(associatedData); 210 211 CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Decrypt dec = 212 kms.projects() 213 .locations() 214 .keyRings() 215 .cryptoKeys() 216 .decrypt(KEY_ID, decRequestWithInvalidCiphertext); 217 assertThrows(IOException.class, dec::execute); 218 } 219 220 @Test testEncryptDecryptWithTwoValidKeyId_success()221 public void testEncryptDecryptWithTwoValidKeyId_success() throws Exception { 222 CloudKMS kms = new FakeCloudKms(asList(KEY_ID, KEY_ID_2)); 223 224 byte[] plaintext = "plaintext".getBytes(UTF_8); 225 byte[] plaintext2 = "plaintext2".getBytes(UTF_8); 226 byte[] associatedData = "associatedData".getBytes(UTF_8); 227 228 EncryptRequest encRequest = 229 new EncryptRequest() 230 .encodePlaintext(plaintext) 231 .encodeAdditionalAuthenticatedData(associatedData); 232 EncryptResponse encResponse = 233 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest).execute(); 234 235 EncryptRequest encRequest2 = 236 new EncryptRequest() 237 .encodePlaintext(plaintext2) 238 .encodeAdditionalAuthenticatedData(associatedData); 239 240 EncryptResponse encResponse2 = 241 kms.projects().locations().keyRings().cryptoKeys().encrypt(KEY_ID, encRequest2).execute(); 242 243 DecryptRequest decRequest = 244 new DecryptRequest() 245 .encodeCiphertext(encResponse.decodeCiphertext()) 246 .encodeAdditionalAuthenticatedData(associatedData); 247 248 DecryptResponse decResponse = 249 kms.projects().locations().keyRings().cryptoKeys().decrypt(KEY_ID, decRequest).execute(); 250 251 assertThat(decResponse.decodePlaintext()).isEqualTo(plaintext); 252 253 DecryptRequest decRequest2 = 254 new DecryptRequest() 255 .encodeCiphertext(encResponse2.decodeCiphertext()) 256 .encodeAdditionalAuthenticatedData(associatedData); 257 258 DecryptResponse decResponse2 = 259 kms.projects().locations().keyRings().cryptoKeys().decrypt(KEY_ID, decRequest2).execute(); 260 261 assertThat(decResponse2.decodePlaintext()).isEqualTo(plaintext2); 262 } 263 } 264