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; 18 19 import com.google.crypto.tink.internal.MutableSerializationRegistry; 20 import com.google.crypto.tink.internal.ProtoKeySerialization; 21 import com.google.crypto.tink.monitoring.MonitoringAnnotations; 22 import com.google.crypto.tink.proto.KeyStatusType; 23 import com.google.crypto.tink.proto.Keyset; 24 import com.google.crypto.tink.proto.OutputPrefixType; 25 import com.google.crypto.tink.subtle.Hex; 26 import com.google.errorprone.annotations.CanIgnoreReturnValue; 27 import java.security.GeneralSecurityException; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.util.concurrent.ConcurrentMap; 35 import javax.annotation.Nullable; 36 37 /** 38 * A container class for a set of primitives -- implementations of cryptographic primitives offered 39 * by Tink. 40 * 41 * <p>It provides also additional properties for the primitives it holds. In particular, one of the 42 * primitives in the set can be distinguished as "the primary" one. 43 * 44 * <p>PrimitiveSet is an auxiliary class used for supporting key rotation: primitives in a set 45 * correspond to keys in a keyset. Users will usually work with primitive instances, which 46 * essentially wrap primitive sets. For example an instance of an Aead-primitive for a given keyset 47 * holds a set of Aead-primitives corresponding to the keys in the keyset, and uses the set members 48 * to do the actual crypto operations: to encrypt data the primary Aead-primitive from the set is 49 * used, and upon decryption the ciphertext's prefix determines the id of the primitive from the 50 * set. 51 * 52 * <p>PrimitiveSet is a public class to allow its use in implementations of custom primitives. 53 * 54 * @since 1.0.0 55 */ 56 public final class PrimitiveSet<P> { 57 58 /** 59 * A single entry in the set. In addition to the actual primitive it holds also some extra 60 * information about the primitive. 61 */ 62 public static final class Entry<P> { 63 // If set, this is a primitive of a key. 64 @Nullable private final P fullPrimitive; 65 @Nullable private final P primitive; 66 // Identifies the primitive within the set. 67 // It is the ciphertext prefix of the corresponding key. 68 private final byte[] identifier; 69 // The status of the key represented by the primitive. 70 private final KeyStatusType status; 71 // The output prefix type of the key represented by the primitive. 72 private final OutputPrefixType outputPrefixType; 73 // The id of the key. 74 private final int keyId; 75 private final String keyType; 76 private final Key key; 77 Entry( @ullable P fullPrimitive, @Nullable P primitive, final byte[] identifier, KeyStatusType status, OutputPrefixType outputPrefixType, int keyId, String keyType, Key key)78 Entry( 79 @Nullable P fullPrimitive, 80 @Nullable P primitive, 81 final byte[] identifier, 82 KeyStatusType status, 83 OutputPrefixType outputPrefixType, 84 int keyId, 85 String keyType, 86 Key key) { 87 this.fullPrimitive = fullPrimitive; 88 this.primitive = primitive; 89 this.identifier = Arrays.copyOf(identifier, identifier.length); 90 this.status = status; 91 this.outputPrefixType = outputPrefixType; 92 this.keyId = keyId; 93 this.keyType = keyType; 94 this.key = key; 95 } 96 97 /** 98 * Returns the full primitive for this entry. 99 * 100 * <p>This is used in cases when the new Tink Key interface is used and the primitive is 101 * self-sufficient by itself, meaning that all the necessary information to process the 102 * primitive is contained in the primitive (most likely through the new Key interface), as 103 * opposed to the {@code primitive} field (see {@link #getPrimitive} for details). 104 */ 105 @Nullable getFullPrimitive()106 public P getFullPrimitive() { 107 return this.fullPrimitive; 108 } 109 110 /** 111 * Returns the primitive for this entry. 112 * 113 * <p>For primitives of type {@code Mac}, {@code Aead}, {@code PublicKeySign}, {@code 114 * PublicKeyVerify}, {@code DeterministicAead}, {@code HybridEncrypt}, and {@code HybridDecrypt} 115 * this is a primitive which <b>ignores</b> the output prefix and assumes "RAW". 116 */ 117 @Nullable getPrimitive()118 public P getPrimitive() { 119 return this.primitive; 120 } 121 getStatus()122 public KeyStatusType getStatus() { 123 return status; 124 } 125 getOutputPrefixType()126 public OutputPrefixType getOutputPrefixType() { 127 return outputPrefixType; 128 } 129 130 @Nullable getIdentifier()131 public final byte[] getIdentifier() { 132 if (identifier == null) { 133 return null; 134 } else { 135 return Arrays.copyOf(identifier, identifier.length); 136 } 137 } 138 getKeyId()139 public int getKeyId() { 140 return keyId; 141 } 142 getKeyType()143 public String getKeyType() { 144 return keyType; 145 } 146 getKey()147 public Key getKey() { 148 return key; 149 } 150 151 @Nullable getParameters()152 public Parameters getParameters() { 153 if (key == null) { 154 return null; 155 } 156 return key.getParameters(); 157 } 158 } 159 createEntry( @ullable P fullPrimitive, @Nullable P primitive, Keyset.Key key)160 private static <P> Entry<P> createEntry( 161 @Nullable P fullPrimitive, @Nullable P primitive, Keyset.Key key) 162 throws GeneralSecurityException { 163 @Nullable Integer idRequirement = key.getKeyId(); 164 if (key.getOutputPrefixType() == OutputPrefixType.RAW) { 165 idRequirement = null; 166 } 167 Key keyObject = 168 MutableSerializationRegistry.globalInstance() 169 .parseKeyWithLegacyFallback( 170 ProtoKeySerialization.create( 171 key.getKeyData().getTypeUrl(), 172 key.getKeyData().getValue(), 173 key.getKeyData().getKeyMaterialType(), 174 key.getOutputPrefixType(), 175 idRequirement), 176 InsecureSecretKeyAccess.get()); 177 return new Entry<P>( 178 fullPrimitive, 179 primitive, 180 CryptoFormat.getOutputPrefix(key), 181 key.getStatus(), 182 key.getOutputPrefixType(), 183 key.getKeyId(), 184 key.getKeyData().getTypeUrl(), 185 keyObject); 186 } 187 storeEntryInPrimitiveSet( Entry<P> entry, ConcurrentMap<Prefix, List<Entry<P>>> primitives, List<Entry<P>> primitivesInKeysetOrder)188 private static <P> void storeEntryInPrimitiveSet( 189 Entry<P> entry, 190 ConcurrentMap<Prefix, List<Entry<P>>> primitives, 191 List<Entry<P>> primitivesInKeysetOrder) { 192 List<Entry<P>> list = new ArrayList<>(); 193 list.add(entry); 194 // Cannot use byte[] as keys in hash map, convert to Prefix wrapper class. 195 Prefix identifier = new Prefix(entry.getIdentifier()); 196 List<Entry<P>> existing = primitives.put(identifier, Collections.unmodifiableList(list)); 197 if (existing != null) { 198 List<Entry<P>> newList = new ArrayList<>(); 199 newList.addAll(existing); 200 newList.add(entry); 201 primitives.put(identifier, Collections.unmodifiableList(newList)); 202 } 203 primitivesInKeysetOrder.add(entry); 204 } 205 206 /** Returns the entry with the primary primitive. */ 207 @Nullable getPrimary()208 public Entry<P> getPrimary() { 209 return primary; 210 } 211 hasAnnotations()212 public boolean hasAnnotations() { 213 return !annotations.toMap().isEmpty(); 214 } 215 getAnnotations()216 public MonitoringAnnotations getAnnotations() { 217 return annotations; 218 } 219 220 /** Returns all primitives using RAW prefix. */ getRawPrimitives()221 public List<Entry<P>> getRawPrimitives() { 222 return getPrimitive(CryptoFormat.RAW_PREFIX); 223 } 224 225 /** Returns the entries with primitive identifed by {@code identifier}. */ getPrimitive(final byte[] identifier)226 public List<Entry<P>> getPrimitive(final byte[] identifier) { 227 List<Entry<P>> found = primitives.get(new Prefix(identifier)); 228 return found != null ? found : Collections.<Entry<P>>emptyList(); 229 } 230 231 /** Returns all primitives. */ getAll()232 public Collection<List<Entry<P>>> getAll() { 233 return primitives.values(); 234 } 235 236 /** Returns all primitives in the original keyset key order. */ getAllInKeysetOrder()237 public List<Entry<P>> getAllInKeysetOrder() { 238 return Collections.unmodifiableList(primitivesInKeysetOrder); 239 } 240 241 /** 242 * The primitives are stored in a hash map of (ciphertext prefix, list of primitives sharing the 243 * prefix). This allows quickly retrieving the list of primitives sharing some particular prefix. 244 * Because all RAW keys are using an empty prefix, this also quickly allows retrieving them. 245 */ 246 private final ConcurrentMap<Prefix, List<Entry<P>>> primitives; 247 248 /** Stores entries in the original keyset key order. */ 249 private final List<Entry<P>> primitivesInKeysetOrder; 250 251 private Entry<P> primary; 252 private final Class<P> primitiveClass; 253 private final MonitoringAnnotations annotations; 254 private final boolean isMutable; 255 PrimitiveSet(Class<P> primitiveClass)256 private PrimitiveSet(Class<P> primitiveClass) { 257 this.primitives = new ConcurrentHashMap<>(); 258 this.primitivesInKeysetOrder = new ArrayList<>(); 259 this.primitiveClass = primitiveClass; 260 this.annotations = MonitoringAnnotations.EMPTY; 261 this.isMutable = true; 262 } 263 264 /** Creates an immutable PrimitiveSet. It is used by the Builder. */ PrimitiveSet( ConcurrentMap<Prefix, List<Entry<P>>> primitives, List<Entry<P>> primitivesInKeysetOrder, Entry<P> primary, MonitoringAnnotations annotations, Class<P> primitiveClass)265 private PrimitiveSet( 266 ConcurrentMap<Prefix, List<Entry<P>>> primitives, 267 List<Entry<P>> primitivesInKeysetOrder, 268 Entry<P> primary, 269 MonitoringAnnotations annotations, 270 Class<P> primitiveClass) { 271 this.primitives = primitives; 272 this.primitivesInKeysetOrder = primitivesInKeysetOrder; 273 this.primary = primary; 274 this.primitiveClass = primitiveClass; 275 this.annotations = annotations; 276 this.isMutable = false; 277 } 278 279 /** 280 * Creates a new mutable PrimitiveSet. 281 * 282 * @deprecated use {@link Builder} instead. 283 */ 284 @Deprecated newPrimitiveSet(Class<P> primitiveClass)285 public static <P> PrimitiveSet<P> newPrimitiveSet(Class<P> primitiveClass) { 286 return new PrimitiveSet<P>(primitiveClass); 287 } 288 289 /** 290 * Sets given Entry {@code primary} as the primary one. 291 * 292 * @throws IllegalStateException if object has been created by the {@link Builder}. 293 * @deprecated use {@link Builder.addPrimaryPrimitive} instead. 294 */ 295 @Deprecated setPrimary(final Entry<P> primary)296 public void setPrimary(final Entry<P> primary) { 297 if (!isMutable) { 298 throw new IllegalStateException("setPrimary cannot be called on an immutable primitive set"); 299 } 300 if (primary == null) { 301 throw new IllegalArgumentException("the primary entry must be non-null"); 302 } 303 if (primary.getStatus() != KeyStatusType.ENABLED) { 304 throw new IllegalArgumentException("the primary entry has to be ENABLED"); 305 } 306 List<Entry<P>> entries = getPrimitive(primary.getIdentifier()); 307 if (entries.isEmpty()) { 308 throw new IllegalArgumentException( 309 "the primary entry cannot be set to an entry which is not held by this primitive set"); 310 } 311 this.primary = primary; 312 } 313 314 /** 315 * Creates an entry in the primitive table. 316 * 317 * @return the added {@link Entry} 318 * @throws IllegalStateException if object has been created by the {@link Builder}. 319 * @deprecated use {@link Builder.addPrimitive} or {@link Builder.addPrimaryPrimitive} instead. 320 */ 321 @CanIgnoreReturnValue 322 @Deprecated addPrimitive(final P primitive, Keyset.Key key)323 public Entry<P> addPrimitive(final P primitive, Keyset.Key key) 324 throws GeneralSecurityException { 325 if (!isMutable) { 326 throw new IllegalStateException( 327 "addPrimitive cannot be called on an immutable primitive set"); 328 } 329 if (key.getStatus() != KeyStatusType.ENABLED) { 330 throw new GeneralSecurityException("only ENABLED key is allowed"); 331 } 332 Entry<P> entry = createEntry(null, primitive, key); 333 storeEntryInPrimitiveSet(entry, primitives, primitivesInKeysetOrder); 334 return entry; 335 } 336 getPrimitiveClass()337 public Class<P> getPrimitiveClass() { 338 return primitiveClass; 339 } 340 341 private static class Prefix implements Comparable<Prefix> { 342 private final byte[] prefix; 343 Prefix(byte[] prefix)344 private Prefix(byte[] prefix) { 345 this.prefix = Arrays.copyOf(prefix, prefix.length); 346 } 347 348 @Override hashCode()349 public int hashCode() { 350 return Arrays.hashCode(prefix); 351 } 352 353 @Override equals(Object o)354 public boolean equals(Object o) { 355 if (!(o instanceof Prefix)) { 356 return false; 357 } 358 Prefix other = (Prefix) o; 359 return Arrays.equals(prefix, other.prefix); 360 } 361 362 @Override compareTo(Prefix o)363 public int compareTo(Prefix o) { 364 if (prefix.length != o.prefix.length) { 365 return prefix.length - o.prefix.length; 366 } 367 for (int i = 0; i < prefix.length; i++) { 368 if (prefix[i] != o.prefix[i]) { 369 return prefix[i] - o.prefix[i]; 370 } 371 } 372 return 0; 373 } 374 375 @Override toString()376 public String toString() { 377 return Hex.encode(prefix); 378 } 379 } 380 381 /** Builds an immutable PrimitiveSet. This is the prefered way to construct a PrimitiveSet. */ 382 public static class Builder<P> { 383 private final Class<P> primitiveClass; 384 385 // primitives == null indicates that build has been called and the builder can't be used 386 // anymore. 387 private ConcurrentMap<Prefix, List<Entry<P>>> primitives = new ConcurrentHashMap<>(); 388 private final List<Entry<P>> primitivesInKeysetOrder = new ArrayList<>(); 389 private Entry<P> primary; 390 private MonitoringAnnotations annotations; 391 392 @CanIgnoreReturnValue addPrimitive( @ullable final P fullPrimitive, @Nullable final P primitive, Keyset.Key key, boolean asPrimary)393 private Builder<P> addPrimitive( 394 @Nullable final P fullPrimitive, 395 @Nullable final P primitive, 396 Keyset.Key key, 397 boolean asPrimary) 398 throws GeneralSecurityException { 399 if (primitives == null) { 400 throw new IllegalStateException("addPrimitive cannot be called after build"); 401 } 402 if (fullPrimitive == null && primitive == null) { 403 throw new GeneralSecurityException( 404 "at least one of the `fullPrimitive` or `primitive` must be set"); 405 } 406 if (key.getStatus() != KeyStatusType.ENABLED) { 407 throw new GeneralSecurityException("only ENABLED key is allowed"); 408 } 409 Entry<P> entry = createEntry(fullPrimitive, primitive, key); 410 storeEntryInPrimitiveSet(entry, primitives, primitivesInKeysetOrder); 411 if (asPrimary) { 412 if (this.primary != null) { 413 throw new IllegalStateException("you cannot set two primary primitives"); 414 } 415 this.primary = entry; 416 } 417 return this; 418 } 419 420 /* Adds a non-primary primitive.*/ 421 @CanIgnoreReturnValue addPrimitive(final P primitive, Keyset.Key key)422 public Builder<P> addPrimitive(final P primitive, Keyset.Key key) 423 throws GeneralSecurityException { 424 return addPrimitive(null, primitive, key, false); 425 } 426 427 /** 428 * Adds the primary primitive. This or addPrimaryFullPrimitiveAndOptionalPrimitive should be 429 * called exactly once per PrimitiveSet. 430 */ 431 @CanIgnoreReturnValue addPrimaryPrimitive(final P primitive, Keyset.Key key)432 public Builder<P> addPrimaryPrimitive(final P primitive, Keyset.Key key) 433 throws GeneralSecurityException { 434 return addPrimitive(null, primitive, key, true); 435 } 436 437 @CanIgnoreReturnValue addFullPrimitiveAndOptionalPrimitive( @ullable final P fullPrimitive, @Nullable final P primitive, Keyset.Key key)438 public Builder<P> addFullPrimitiveAndOptionalPrimitive( 439 @Nullable final P fullPrimitive, @Nullable final P primitive, Keyset.Key key) 440 throws GeneralSecurityException { 441 return addPrimitive(fullPrimitive, primitive, key, false); 442 } 443 444 /** 445 * Adds the primary primitive and full primitive. This or addPrimaryPrimitive should be called 446 * exactly once per PrimitiveSet. 447 */ 448 @CanIgnoreReturnValue addPrimaryFullPrimitiveAndOptionalPrimitive( @ullable final P fullPrimitive, @Nullable final P primitive, Keyset.Key key)449 public Builder<P> addPrimaryFullPrimitiveAndOptionalPrimitive( 450 @Nullable final P fullPrimitive, @Nullable final P primitive, Keyset.Key key) 451 throws GeneralSecurityException { 452 return addPrimitive(fullPrimitive, primitive, key, true); 453 } 454 455 @CanIgnoreReturnValue setAnnotations(MonitoringAnnotations annotations)456 public Builder<P> setAnnotations(MonitoringAnnotations annotations) { 457 if (primitives == null) { 458 throw new IllegalStateException("setAnnotations cannot be called after build"); 459 } 460 this.annotations = annotations; 461 return this; 462 } 463 build()464 public PrimitiveSet<P> build() throws GeneralSecurityException { 465 if (primitives == null) { 466 throw new IllegalStateException("build cannot be called twice"); 467 } 468 // Note that we currently don't enforce that primary must be set. 469 PrimitiveSet<P> output = 470 new PrimitiveSet<P>( 471 primitives, primitivesInKeysetOrder, primary, annotations, primitiveClass); 472 this.primitives = null; 473 return output; 474 } 475 Builder(Class<P> primitiveClass)476 private Builder(Class<P> primitiveClass) { 477 this.primitiveClass = primitiveClass; 478 this.annotations = MonitoringAnnotations.EMPTY; 479 } 480 } 481 newBuilder(Class<P> primitiveClass)482 public static <P> Builder<P> newBuilder(Class<P> primitiveClass) { 483 return new Builder<P>(primitiveClass); 484 } 485 } 486