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 com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 19 import com.google.api.client.http.HttpTransport; 20 import com.google.api.client.http.LowLevelHttpRequest; 21 import com.google.api.client.json.gson.GsonFactory; 22 import com.google.api.services.cloudkms.v1.CloudKMS; 23 import com.google.api.services.cloudkms.v1.model.DecryptRequest; 24 import com.google.api.services.cloudkms.v1.model.DecryptResponse; 25 import com.google.api.services.cloudkms.v1.model.EncryptRequest; 26 import com.google.api.services.cloudkms.v1.model.EncryptResponse; 27 import com.google.crypto.tink.Aead; 28 import com.google.crypto.tink.KeyTemplates; 29 import com.google.crypto.tink.KeysetHandle; 30 import java.io.IOException; 31 import java.security.GeneralSecurityException; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 36 /** 37 * A partial, fake implementation of {@link com.google.api.services.cloudkms.v1.CloudKMS}. 38 * 39 * <p>It creates a new AEAD for every valid key ID. CryptoKeys use them to encrypt and decrypt. 40 */ 41 final class FakeCloudKms extends CloudKMS { 42 private final Map<String, Aead> aeads = new HashMap<>(); 43 FakeCloudKms(List<String> validKeyIds)44 public FakeCloudKms(List<String> validKeyIds) 45 throws GeneralSecurityException { 46 super( 47 new HttpTransport() { 48 @Override 49 protected LowLevelHttpRequest buildRequest(String method, String url) throws IOException { 50 throw new IOException("Test should not have interacted with HttpTransport."); 51 } 52 }, 53 new GsonFactory(), 54 new GoogleCredential()); 55 for (String keyId : validKeyIds) { 56 Aead aead = KeysetHandle.generateNew(KeyTemplates.get("AES128_GCM")).getPrimitive(Aead.class); 57 aeads.put(keyId, aead); 58 } 59 } 60 61 private final Projects projects = new Projects(); 62 63 @Override projects()64 public Projects projects() { 65 return projects; 66 } 67 68 final class Projects extends CloudKMS.Projects { 69 70 private final Locations locations = new Locations(); 71 72 @Override locations()73 public Locations locations() { 74 return locations; 75 } 76 77 final class Locations extends CloudKMS.Projects.Locations { 78 79 private final KeyRings keyRings = new KeyRings(); 80 81 @Override keyRings()82 public KeyRings keyRings() { 83 return keyRings; 84 } 85 86 final class KeyRings extends CloudKMS.Projects.Locations.KeyRings { 87 88 private final CryptoKeys cryptoKeys = new CryptoKeys(); 89 90 @Override cryptoKeys()91 public CryptoKeys cryptoKeys() { 92 return cryptoKeys; 93 } 94 95 final class CryptoKeys extends CloudKMS.Projects.Locations.KeyRings.CryptoKeys { 96 @Override encrypt(String name, EncryptRequest request)97 public Encrypt encrypt(String name, EncryptRequest request) { 98 return new Encrypt(name, request); 99 } 100 101 @Override decrypt(String name, DecryptRequest request)102 public Decrypt decrypt(String name, DecryptRequest request) { 103 return new Decrypt(name, request); 104 } 105 106 final class Encrypt extends CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Encrypt { 107 String name; 108 EncryptRequest request; 109 Encrypt(String name, EncryptRequest request)110 Encrypt(String name, EncryptRequest request) { 111 super(name, request); 112 this.name = name; 113 this.request = request; 114 } 115 116 @Override execute()117 public EncryptResponse execute() throws IOException { 118 if (!aeads.containsKey(name)) { 119 throw new IOException( 120 "Unknown key ID : " + name + " is not in " + aeads.keySet()); 121 } 122 try { 123 Aead aead = aeads.get(name); 124 byte[] ciphertext = 125 aead.encrypt( 126 request.decodePlaintext(), request.decodeAdditionalAuthenticatedData()); 127 return new EncryptResponse().encodeCiphertext(ciphertext); 128 } catch (GeneralSecurityException e) { 129 throw new IOException(e.getMessage()); 130 } 131 } 132 } 133 134 final class Decrypt extends CloudKMS.Projects.Locations.KeyRings.CryptoKeys.Decrypt { 135 String name; 136 DecryptRequest request; 137 Decrypt(String name, DecryptRequest request)138 Decrypt(String name, DecryptRequest request) { 139 super(name, request); 140 this.name = name; 141 this.request = request; 142 } 143 144 @Override execute()145 public DecryptResponse execute() throws IOException { 146 if (!aeads.containsKey(name)) { 147 throw new IOException("Unknown key ID : " + name + " is not in " + aeads.keySet()); 148 } 149 try { 150 Aead aead = aeads.get(name); 151 byte[] plaintext = 152 aead.decrypt( 153 request.decodeCiphertext(), request.decodeAdditionalAuthenticatedData()); 154 if (plaintext.length == 0) { 155 // The real CloudKMS also returns null in this case. 156 return new DecryptResponse().encodePlaintext(null); 157 } else { 158 return new DecryptResponse().encodePlaintext(plaintext); 159 } 160 } catch (GeneralSecurityException e) { 161 throw new IOException(e.getMessage()); 162 } 163 } 164 } 165 } 166 } 167 } 168 } 169 } 170