• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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