1 // Copyright 2023 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; 18 19 import com.google.crypto.tink.AccessesPartialKey; 20 import com.google.errorprone.annotations.CanIgnoreReturnValue; 21 import com.google.errorprone.annotations.Immutable; 22 import com.google.errorprone.annotations.RestrictedApi; 23 import java.security.GeneralSecurityException; 24 import java.util.Objects; 25 import javax.annotation.Nullable; 26 27 /** 28 * Describes the parameters of an {@link LegacyKmsEnvelopeAeadKey}. 29 * 30 * <p>Usage of this key type is not recommended. Instead, we recommend to implement the idea of this 31 * class manually: 32 * 33 * <ol> 34 * <li>Create an remote {@link com.google.crypto.tink.Aead} object for your KMS with an 35 * appropriate Tink extension (typically using a subclass of {@link 36 * com.google.crypto.tink.KmsClient}). 37 * <li>Create an envelope AEAD with {@link com.google.crypto.tink.aead.KmsEnvelopeAead#create}. 38 * </ol> 39 * 40 * <H1>Known Issues</h1> 41 * 42 * <H2>Global registration</h2> 43 * 44 * If a user uses a {@code LegacyKmsEnvelopeAeadKey}, when the corresponding {@code Aead} is 45 * created, Tink looks up the {@code KmsClient} in a global registry. This registry needs to store 46 * all the credentials and all the information. This is inappropriate in many situations. 47 * 48 * <h2>Ciphertext format</h2> 49 * 50 * The ciphertext format does not encode the key type of the key used. This can lead to unexpected 51 * results if a user changes the {@code dekParametersForNewKeys} or the {@code dekParsingStrategy} 52 * for the same remote key. In more details, the ciphertext contains a Tink key proto of newly 53 * generated key, but not the type URL. This means that if a user reuses the same remote Key with a 54 * different key type, it will be parsed with the wrong one. 55 * 56 * <p>Also, Tink does note compare the parameters of the parsed key with the parameters specified in 57 * {@code dekParametersForNewKeys}. For example, if the {@code dekParametersForNewKeys} is specified 58 * as AES_128_GCM in one binary, and AES_256_GCM in another binary, communication between the 59 * binaries succeeds in both directions. 60 * 61 * <h2>Ciphertext malleability</h2> 62 * 63 * <p>Some KMS have malleable ciphertexts. This means that the Aeads corresponding to these keys may 64 * be malleable. See https://developers.google.com/tink/issues/envelope-aead-malleability 65 */ 66 public final class LegacyKmsEnvelopeAeadParameters extends AeadParameters { 67 68 /** 69 * Describes how the prefix is computed. There are two main possibilities: NO_PREFIX (empty 70 * prefix) and TINK (prefix the ciphertext with 0x01 followed by a 4-byte key id in big endian. 71 */ 72 @Immutable 73 public static final class Variant { 74 public static final Variant TINK = new Variant("TINK"); 75 public static final Variant NO_PREFIX = new Variant("NO_PREFIX"); 76 77 private final String name; 78 Variant(String name)79 private Variant(String name) { 80 this.name = name; 81 } 82 83 @Override toString()84 public String toString() { 85 return name; 86 } 87 } 88 89 /** 90 * Specifies how the DEK in received ciphertexts are parsed. 91 * 92 * <p>See section "Ciphertext format" above for a discussion of this. 93 */ 94 @Immutable 95 public static final class DekParsingStrategy { 96 /** When parsing, assume that the ciphertext was encrypted with AES GCM. */ 97 public static final DekParsingStrategy ASSUME_AES_GCM = 98 new DekParsingStrategy("ASSUME_AES_GCM"); 99 100 /** When parsing, assume that the ciphertext was encrypted with XChaCha20-Poly1305. */ 101 public static final DekParsingStrategy ASSUME_XCHACHA20POLY1305 = 102 new DekParsingStrategy("ASSUME_XCHACHA20POLY1305"); 103 104 /** When parsing, assume that the ciphertext was encrypted with ChaCha20-Poly1305. */ 105 public static final DekParsingStrategy ASSUME_CHACHA20POLY1305 = 106 new DekParsingStrategy("ASSUME_CHACHA20POLY1305"); 107 108 /** When parsing, assume that the ciphertext was encrypted with AES CTR HMAC. */ 109 public static final DekParsingStrategy ASSUME_AES_CTR_HMAC = 110 new DekParsingStrategy("ASSUME_AES_CTR_HMAC"); 111 112 /** When parsing, assume that the ciphertext was encrypted with AES EAX. */ 113 public static final DekParsingStrategy ASSUME_AES_EAX = 114 new DekParsingStrategy("ASSUME_AES_EAX"); 115 116 /** When parsing, assume that the ciphertext was encrypted with AES GCM SIV. */ 117 public static final DekParsingStrategy ASSUME_AES_GCM_SIV = 118 new DekParsingStrategy("ASSUME_AES_GCM_SIV"); 119 120 private final String name; 121 DekParsingStrategy(String name)122 private DekParsingStrategy(String name) { 123 this.name = name; 124 } 125 126 @Override toString()127 public String toString() { 128 return name; 129 } 130 } 131 132 private final Variant variant; 133 private final String kekUri; 134 private final DekParsingStrategy dekParsingStrategy; 135 private final AeadParameters dekParametersForNewKeys; 136 LegacyKmsEnvelopeAeadParameters( Variant variant, String kekUri, DekParsingStrategy dekParsingStrategy, AeadParameters dekParametersForNewKeys)137 private LegacyKmsEnvelopeAeadParameters( 138 Variant variant, 139 String kekUri, 140 DekParsingStrategy dekParsingStrategy, 141 AeadParameters dekParametersForNewKeys) { 142 this.variant = variant; 143 this.kekUri = kekUri; 144 this.dekParsingStrategy = dekParsingStrategy; 145 this.dekParametersForNewKeys = dekParametersForNewKeys; 146 } 147 148 /** Builder for {@link LegacyKmsEnvelopeAeadParameters}. */ 149 public static class Builder { 150 @Nullable private Variant variant; 151 @Nullable private String kekUri; 152 @Nullable private DekParsingStrategy dekParsingStrategy; 153 @Nullable private AeadParameters dekParametersForNewKeys; 154 Builder()155 private Builder() {} 156 157 @CanIgnoreReturnValue setVariant(Variant variant)158 public Builder setVariant(Variant variant) { 159 this.variant = variant; 160 return this; 161 } 162 163 /** 164 * Sets the URI of the KMS to be used. 165 * 166 * <p>The KMS will be used to encrypt the DEK key as an AEAD. 167 */ 168 @CanIgnoreReturnValue setKekUri(String kekUri)169 public Builder setKekUri(String kekUri) { 170 this.kekUri = kekUri; 171 return this; 172 } 173 174 @CanIgnoreReturnValue setDekParsingStrategy(DekParsingStrategy dekParsingStrategy)175 public Builder setDekParsingStrategy(DekParsingStrategy dekParsingStrategy) { 176 this.dekParsingStrategy = dekParsingStrategy; 177 return this; 178 } 179 180 @CanIgnoreReturnValue setDekParametersForNewKeys(AeadParameters aeadParameters)181 public Builder setDekParametersForNewKeys(AeadParameters aeadParameters) { 182 this.dekParametersForNewKeys = aeadParameters; 183 return this; 184 } 185 parsingStrategyAllowed( DekParsingStrategy parsingStrategy, AeadParameters aeadParameters)186 private static boolean parsingStrategyAllowed( 187 DekParsingStrategy parsingStrategy, AeadParameters aeadParameters) { 188 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_AES_GCM) 189 && (aeadParameters instanceof AesGcmParameters)) { 190 return true; 191 } 192 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_CHACHA20POLY1305) 193 && (aeadParameters instanceof ChaCha20Poly1305Parameters)) { 194 return true; 195 } 196 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_XCHACHA20POLY1305) 197 && (aeadParameters instanceof XChaCha20Poly1305Parameters)) { 198 return true; 199 } 200 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_AES_CTR_HMAC) 201 && (aeadParameters instanceof AesCtrHmacAeadParameters)) { 202 return true; 203 } 204 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_AES_EAX) 205 && (aeadParameters instanceof AesEaxParameters)) { 206 return true; 207 } 208 if (parsingStrategy.equals(DekParsingStrategy.ASSUME_AES_GCM_SIV) 209 && (aeadParameters instanceof AesGcmSivParameters)) { 210 return true; 211 } 212 return false; 213 } 214 215 /** Builds the LegacyKmsEnvelopeAeadParameters. */ build()216 public LegacyKmsEnvelopeAeadParameters build() throws GeneralSecurityException { 217 if (variant == null) { 218 // Use NO_PREFIX as default prefix. 219 variant = Variant.NO_PREFIX; 220 } 221 if (kekUri == null) { 222 throw new GeneralSecurityException("kekUri must be set"); 223 } 224 if (dekParsingStrategy == null) { 225 throw new GeneralSecurityException("dekParsingStrategy must be set"); 226 } 227 if (dekParametersForNewKeys == null) { 228 throw new GeneralSecurityException("dekParametersForNewKeys must be set"); 229 } 230 if (dekParametersForNewKeys.hasIdRequirement()) { 231 throw new GeneralSecurityException("dekParametersForNewKeys must not have ID Requirements"); 232 } 233 if (!parsingStrategyAllowed(dekParsingStrategy, dekParametersForNewKeys)) { 234 throw new GeneralSecurityException( 235 "Cannot use parsing strategy " 236 + dekParsingStrategy.toString() 237 + " when new keys are picked according to " 238 + dekParametersForNewKeys 239 + "."); 240 } 241 242 return new LegacyKmsEnvelopeAeadParameters( 243 variant, kekUri, dekParsingStrategy, dekParametersForNewKeys); 244 } 245 } 246 247 @RestrictedApi( 248 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 249 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 250 allowedOnPath = ".*Test\\.java", 251 allowlistAnnotations = {AccessesPartialKey.class}) builder()252 public static Builder builder() { 253 return new Builder(); 254 } 255 256 /** Returns the URI with the key of the remote AEAD used. */ 257 @RestrictedApi( 258 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 259 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 260 allowedOnPath = ".*Test\\.java", 261 allowlistAnnotations = {AccessesPartialKey.class}) getKekUri()262 public String getKekUri() { 263 return kekUri; 264 } 265 getVariant()266 public Variant getVariant() { 267 return variant; 268 } 269 270 @Override hasIdRequirement()271 public boolean hasIdRequirement() { 272 return variant != Variant.NO_PREFIX; 273 } 274 275 /** 276 * Returns the type URL which is used when parsing encrypted keys. 277 * 278 * <p>See "Known Issues" section above. 279 */ getDekParsingStrategy()280 public DekParsingStrategy getDekParsingStrategy() { 281 return dekParsingStrategy; 282 } 283 284 /** Parameters used when creating new keys. */ getDekParametersForNewKeys()285 public AeadParameters getDekParametersForNewKeys() { 286 return dekParametersForNewKeys; 287 } 288 289 @Override equals(Object o)290 public boolean equals(Object o) { 291 if (!(o instanceof LegacyKmsEnvelopeAeadParameters)) { 292 return false; 293 } 294 LegacyKmsEnvelopeAeadParameters that = (LegacyKmsEnvelopeAeadParameters) o; 295 return that.dekParsingStrategy.equals(dekParsingStrategy) 296 && that.dekParametersForNewKeys.equals(dekParametersForNewKeys) 297 && that.kekUri.equals(kekUri) 298 && that.variant.equals(variant); 299 } 300 301 @Override hashCode()302 public int hashCode() { 303 return Objects.hash( 304 LegacyKmsEnvelopeAeadParameters.class, 305 kekUri, 306 dekParsingStrategy, 307 dekParametersForNewKeys, 308 variant); 309 } 310 311 @Override toString()312 public String toString() { 313 return "LegacyKmsEnvelopeAead Parameters (kekUri: " 314 + kekUri 315 + ", " 316 + "dekParsingStrategy: " 317 + dekParsingStrategy 318 + ", " 319 + "dekParametersForNewKeys: " 320 + dekParametersForNewKeys 321 + ", " 322 + "variant: " 323 + variant 324 + ")"; 325 } 326 } 327