1 // Copyright 2017 Google Inc. 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 17 package com.google.crypto.tink.aead; // instead of subtle, because it depends on KeyTemplate. 18 19 import com.google.crypto.tink.Aead; 20 import com.google.crypto.tink.InsecureSecretKeyAccess; 21 import com.google.crypto.tink.Key; 22 import com.google.crypto.tink.Parameters; 23 import com.google.crypto.tink.TinkProtoParametersFormat; 24 import com.google.crypto.tink.internal.MutableKeyCreationRegistry; 25 import com.google.crypto.tink.internal.MutablePrimitiveRegistry; 26 import com.google.crypto.tink.internal.MutableSerializationRegistry; 27 import com.google.crypto.tink.internal.ProtoKeySerialization; 28 import com.google.crypto.tink.proto.KeyData.KeyMaterialType; 29 import com.google.crypto.tink.proto.KeyTemplate; 30 import com.google.crypto.tink.proto.OutputPrefixType; 31 import com.google.protobuf.ByteString; 32 import com.google.protobuf.ExtensionRegistryLite; 33 import com.google.protobuf.InvalidProtocolBufferException; 34 import java.nio.BufferUnderflowException; 35 import java.nio.ByteBuffer; 36 import java.security.GeneralSecurityException; 37 import java.util.Collections; 38 import java.util.HashSet; 39 import java.util.Set; 40 41 /** 42 * This primitive implements <a href="https://cloud.google.com/kms/docs/data-encryption-keys"> 43 * envelope encryption</a>. 44 * 45 * <p>In envelope encryption, a user generates a data encryption key (DEK) locally, encrypts data 46 * with the DEK, sends the DEK to a KMS to be encrypted (with a key managed by KMS), and then stores 47 * the encrypted DEK with the encrypted data. At a later point, a user can retrieve the encrypted 48 * data and the encyrpted DEK, use the KMS to decrypt the DEK, and use the decrypted DEK to decrypt 49 * the data. 50 * 51 * <p>The ciphertext structure is as follows: 52 * 53 * <ul> 54 * <li>Length of the encrypted DEK: 4 bytes. 55 * <li>Encrypted DEK: variable length that is equal to the value specified in the last 4 bytes. 56 * <li>AEAD payload: variable length. 57 * </ul> 58 */ 59 public final class KmsEnvelopeAead implements Aead { 60 private static final byte[] EMPTY_AAD = new byte[0]; 61 private final String typeUrlForParsing; 62 private final Parameters parametersForNewKeys; 63 64 private final Aead remote; 65 private static final int LENGTH_ENCRYPTED_DEK = 4; 66 // A DEK is always an AEAD keyset with one key. 67 private static final int MAX_LENGTH_ENCRYPTED_DEK = 4096; 68 listSupportedDekKeyTypes()69 private static Set<String> listSupportedDekKeyTypes() { 70 HashSet<String> dekKeyTypeUrls = new HashSet<>(); 71 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.AesGcmKey"); 72 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key"); 73 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key"); 74 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey"); 75 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.AesGcmSivKey"); 76 dekKeyTypeUrls.add("type.googleapis.com/google.crypto.tink.AesEaxKey"); 77 return Collections.unmodifiableSet(dekKeyTypeUrls); 78 } 79 80 private static final Set<String> supportedDekKeyTypes = listSupportedDekKeyTypes(); 81 isSupportedDekKeyType(String dekKeyTypeUrl)82 public static boolean isSupportedDekKeyType(String dekKeyTypeUrl) { 83 return supportedDekKeyTypes.contains(dekKeyTypeUrl); 84 } 85 getRawParameters(KeyTemplate dekTemplate)86 private Parameters getRawParameters(KeyTemplate dekTemplate) throws GeneralSecurityException { 87 KeyTemplate rawTemplate = 88 KeyTemplate.newBuilder(dekTemplate).setOutputPrefixType(OutputPrefixType.RAW).build(); 89 return TinkProtoParametersFormat.parse(rawTemplate.toByteArray()); 90 } 91 92 /** 93 * Creates a new KmsEnvelopeAead. 94 * 95 * <p>This function should be avoided. Instead, if you use this with one of the predefined key 96 * templates, call create with the corresponding parameters object. 97 * 98 * <p>For example, if you use: 99 * 100 * <p><code>Aead aead = new KmsEnvelopeAead(AeadKeyTemplates.AES128_GCM, remote)</code> you should 101 * replace this with: 102 * 103 * <p><code>Aead aead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_GCM, remote)</code> 104 * 105 * @deprecated Instead, call {@code KmsEnvelopeAead.create} as explained above. 106 */ 107 @Deprecated KmsEnvelopeAead(KeyTemplate dekTemplate, Aead remote)108 public KmsEnvelopeAead(KeyTemplate dekTemplate, Aead remote) 109 throws GeneralSecurityException { 110 if (!isSupportedDekKeyType(dekTemplate.getTypeUrl())) { 111 throw new IllegalArgumentException( 112 "Unsupported DEK key type: " 113 + dekTemplate.getTypeUrl() 114 + ". Only Tink AEAD key types are supported."); 115 } 116 this.typeUrlForParsing = dekTemplate.getTypeUrl(); 117 this.parametersForNewKeys = getRawParameters(dekTemplate); 118 this.remote = remote; 119 } 120 121 /** 122 * Creates a new instance of Tink's KMS Envelope AEAD. 123 * 124 * <p>{@code dekParameters} must be any of these Tink AEAD parameters (any other will be 125 * rejected): {@link AesGcmParameters}, {@link ChaCha20Poly1305Parameters}, {@link 126 * XChaCha20Poly1305Parameters}, {@link AesCtrHmacAeadParameters}, {@link AesGcmSivParameters}, or 127 * {@link AesEaxParameters}. 128 */ create(AeadParameters dekParameters, Aead remote)129 public static Aead create(AeadParameters dekParameters, Aead remote) 130 throws GeneralSecurityException { 131 // This serializes the parameters, changes output prefix to raw, and parses it again. 132 // It would be better to reject the parameters immediately if it was a non-raw object, but 133 // this might break someone, so we keep as is. 134 KeyTemplate dekTemplate; 135 try { 136 dekTemplate = 137 KeyTemplate.parseFrom( 138 TinkProtoParametersFormat.serialize(dekParameters), 139 ExtensionRegistryLite.getEmptyRegistry()); 140 } catch (InvalidProtocolBufferException e) { 141 throw new GeneralSecurityException(e); 142 } 143 return new KmsEnvelopeAead(dekTemplate, remote); 144 } 145 146 @Override encrypt(final byte[] plaintext, final byte[] associatedData)147 public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) 148 throws GeneralSecurityException { 149 Key key = 150 MutableKeyCreationRegistry.globalInstance() 151 .createKey(parametersForNewKeys, /* idRequirement= */ null); 152 153 ProtoKeySerialization serialization = 154 MutableSerializationRegistry.globalInstance() 155 .serializeKey(key, ProtoKeySerialization.class, InsecureSecretKeyAccess.get()); 156 byte[] dek = serialization.getValue().toByteArray(); 157 // Wrap it with remote. 158 byte[] encryptedDek = remote.encrypt(dek, EMPTY_AAD); 159 if (encryptedDek.length > MAX_LENGTH_ENCRYPTED_DEK) { 160 throw new GeneralSecurityException("length of encrypted DEK too large"); 161 } 162 // Use DEK to encrypt plaintext. 163 Aead aead = MutablePrimitiveRegistry.globalInstance().getPrimitive(key, Aead.class); 164 byte[] payload = aead.encrypt(plaintext, associatedData); 165 // Build ciphertext protobuf and return result. 166 return buildCiphertext(encryptedDek, payload); 167 } 168 169 @Override decrypt(final byte[] ciphertext, final byte[] associatedData)170 public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) 171 throws GeneralSecurityException { 172 try { 173 ByteBuffer buffer = ByteBuffer.wrap(ciphertext); 174 int encryptedDekSize = buffer.getInt(); 175 if (encryptedDekSize <= 0 176 || encryptedDekSize > MAX_LENGTH_ENCRYPTED_DEK 177 || encryptedDekSize > (ciphertext.length - LENGTH_ENCRYPTED_DEK)) { 178 throw new GeneralSecurityException("length of encrypted DEK too large"); 179 } 180 byte[] encryptedDek = new byte[encryptedDekSize]; 181 buffer.get(encryptedDek, 0, encryptedDekSize); 182 byte[] payload = new byte[buffer.remaining()]; 183 buffer.get(payload, 0, buffer.remaining()); 184 // Use remote to decrypt encryptedDek. 185 byte[] dek = remote.decrypt(encryptedDek, EMPTY_AAD); 186 // Use DEK to decrypt payload. 187 ProtoKeySerialization serialization = 188 ProtoKeySerialization.create( 189 typeUrlForParsing, 190 ByteString.copyFrom(dek), 191 KeyMaterialType.SYMMETRIC, 192 OutputPrefixType.RAW, 193 /* idRequirement= */ null); 194 Key key = 195 MutableSerializationRegistry.globalInstance() 196 .parseKey(serialization, InsecureSecretKeyAccess.get()); 197 198 Aead aead = MutablePrimitiveRegistry.globalInstance().getPrimitive(key, Aead.class); 199 return aead.decrypt(payload, associatedData); 200 } catch (IndexOutOfBoundsException 201 | BufferUnderflowException 202 | NegativeArraySizeException e) { 203 throw new GeneralSecurityException("invalid ciphertext", e); 204 } 205 } 206 buildCiphertext(final byte[] encryptedDek, final byte[] payload)207 private byte[] buildCiphertext(final byte[] encryptedDek, final byte[] payload) { 208 return ByteBuffer.allocate(LENGTH_ENCRYPTED_DEK + encryptedDek.length + payload.length) 209 .putInt(encryptedDek.length) 210 .put(encryptedDek) 211 .put(payload) 212 .array(); 213 } 214 } 215