1 /* Copyright 2018 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 * https://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 package com.google.security.cryptauth.lib.securemessage; 16 17 import com.google.security.annotations.SuppressInsecureCipherModeCheckerReviewed; 18 import java.io.UnsupportedEncodingException; 19 import java.security.InvalidAlgorithmParameterException; 20 import java.security.InvalidKeyException; 21 import java.security.Key; 22 import java.security.MessageDigest; 23 import java.security.NoSuchAlgorithmException; 24 import java.security.PrivateKey; 25 import java.security.PublicKey; 26 import java.security.SecureRandom; 27 import java.security.Signature; 28 import java.security.SignatureException; 29 import javax.annotation.Nullable; 30 import javax.crypto.BadPaddingException; 31 import javax.crypto.Cipher; 32 import javax.crypto.IllegalBlockSizeException; 33 import javax.crypto.Mac; 34 import javax.crypto.NoSuchPaddingException; 35 import javax.crypto.SecretKey; 36 import javax.crypto.spec.IvParameterSpec; 37 import javax.crypto.spec.SecretKeySpec; 38 39 /** 40 * Encapsulates the cryptographic operations used by the {@code SecureMessage*} classes. 41 */ 42 public class CryptoOps { 43 CryptoOps()44 private CryptoOps() {} // Do not instantiate 45 46 /** 47 * Enum of supported signature types, with additional mappings to indicate the name of the 48 * underlying JCA algorithm used to create the signature. 49 * @see <a href= 50 * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> 51 * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> 52 */ 53 public enum SigType { 54 HMAC_SHA256(SecureMessageProto.SigScheme.HMAC_SHA256, "HmacSHA256", false), 55 ECDSA_P256_SHA256(SecureMessageProto.SigScheme.ECDSA_P256_SHA256, "SHA256withECDSA", true), 56 RSA2048_SHA256(SecureMessageProto.SigScheme.RSA2048_SHA256, "SHA256withRSA", true); 57 getSigScheme()58 public SecureMessageProto.SigScheme getSigScheme() { 59 return sigScheme; 60 } 61 getJcaName()62 public String getJcaName() { 63 return jcaName; 64 } 65 isPublicKeyScheme()66 public boolean isPublicKeyScheme() { 67 return publicKeyScheme; 68 } 69 valueOf(SecureMessageProto.SigScheme sigScheme)70 public static SigType valueOf(SecureMessageProto.SigScheme sigScheme) { 71 for (SigType value : values()) { 72 if (value.sigScheme.equals(sigScheme)) { 73 return value; 74 } 75 } 76 throw new IllegalArgumentException("Unsupported SigType: " + sigScheme); 77 } 78 79 private final SecureMessageProto.SigScheme sigScheme; 80 private final String jcaName; 81 private final boolean publicKeyScheme; 82 SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme)83 SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme) { 84 this.sigScheme = sigType; 85 this.jcaName = jcaName; 86 this.publicKeyScheme = publicKeyScheme; 87 } 88 } 89 90 /** 91 * Enum of supported encryption types, with additional mappings to indicate the name of the 92 * underlying JCA algorithm used to perform the encryption. 93 * @see <a href= 94 * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html"> 95 * Java Cryptography Architecture, Standard Algorithm Name Documentation</a> 96 */ 97 public enum EncType { 98 NONE(SecureMessageProto.EncScheme.NONE, "InvalidDoNotUseForJCA"), 99 AES_256_CBC(SecureMessageProto.EncScheme.AES_256_CBC, "AES/CBC/PKCS5Padding"); 100 getEncScheme()101 public SecureMessageProto.EncScheme getEncScheme() { 102 return encScheme; 103 } 104 getJcaName()105 public String getJcaName() { 106 return jcaName; 107 } 108 valueOf(SecureMessageProto.EncScheme encScheme)109 public static EncType valueOf(SecureMessageProto.EncScheme encScheme) { 110 for (EncType value : values()) { 111 if (value.encScheme.equals(encScheme)) { 112 return value; 113 } 114 } 115 throw new IllegalArgumentException("Unsupported EncType: " + encScheme); 116 } 117 118 private final SecureMessageProto.EncScheme encScheme; 119 private final String jcaName; 120 EncType(SecureMessageProto.EncScheme encScheme, String jcaName)121 EncType(SecureMessageProto.EncScheme encScheme, String jcaName) { 122 this.encScheme = encScheme; 123 this.jcaName = jcaName; 124 } 125 } 126 127 /** 128 * Truncated hash output length, in bytes. 129 */ 130 static final int DIGEST_LENGTH = 20; 131 /** 132 * A salt value specific to this library, generated as SHA-256("SecureMessage") 133 */ 134 private static final byte[] SALT = sha256("SecureMessage"); 135 private static final byte[] CONSTANT_01 = { 0x01 }; // For convenience 136 137 /** 138 * Signs {@code data} using the algorithm specified by {@code sigType} with {@code signingKey}. 139 * 140 * @param rng is required for public key signature schemes 141 * @return raw signature 142 * @throws InvalidKeyException if {@code signingKey} is incompatible with {@code sigType} 143 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} 144 */ sign( SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data)145 static byte[] sign( 146 SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data) 147 throws InvalidKeyException, NoSuchAlgorithmException { 148 if ((signingKey == null) || (data == null)) { 149 throw new NullPointerException(); 150 } 151 if (sigType.isPublicKeyScheme()) { 152 if (rng == null) { 153 throw new NullPointerException(); 154 } 155 if (!(signingKey instanceof PrivateKey)) { 156 throw new InvalidKeyException("Expected a PrivateKey"); 157 } 158 Signature sigScheme = Signature.getInstance(sigType.getJcaName()); 159 sigScheme.initSign((PrivateKey) signingKey, rng); 160 try { 161 // We include a fixed magic value (salt) in the signature so that if the signing key is 162 // reused in another context we can't be confused -- provided that the other user of the 163 // signing key only signs statements that do not begin with this salt. 164 sigScheme.update(SALT); 165 sigScheme.update(data); 166 return sigScheme.sign(); 167 } catch (SignatureException e) { 168 throw new IllegalStateException(e); // Consistent with failures in Mac.doFinal 169 } 170 } else { 171 Mac macScheme = Mac.getInstance(sigType.getJcaName()); 172 // Note that an AES-256 SecretKey should work with most Mac schemes 173 SecretKey derivedKey = deriveAes256KeyFor(getSecretKey(signingKey), getPurpose(sigType)); 174 macScheme.init(derivedKey); 175 return macScheme.doFinal(data); 176 } 177 } 178 179 /** 180 * Verifies the {@code signature} on {@code data} using the algorithm specified by 181 * {@code sigType} with {@code verificationKey}. 182 * 183 * @return true iff the signature is verified 184 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType} 185 * @throws InvalidKeyException if {@code verificationKey} is incompatible with {@code sigType} 186 * @throws SignatureException 187 */ verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data)188 static boolean verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data) 189 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 190 if ((verificationKey == null) || (signature == null) || (data == null)) { 191 throw new NullPointerException(); 192 } 193 if (sigType.isPublicKeyScheme()) { 194 if (!(verificationKey instanceof PublicKey)) { 195 throw new InvalidKeyException("Expected a PublicKey"); 196 } 197 Signature sigScheme = Signature.getInstance(sigType.getJcaName()); 198 sigScheme.initVerify((PublicKey) verificationKey); 199 sigScheme.update(SALT); // See the comments in sign() for more on this 200 sigScheme.update(data); 201 return sigScheme.verify(signature); 202 } else { 203 Mac macScheme = Mac.getInstance(sigType.getJcaName()); 204 SecretKey derivedKey = 205 deriveAes256KeyFor(getSecretKey(verificationKey), getPurpose(sigType)); 206 macScheme.init(derivedKey); 207 return constantTimeArrayEquals(signature, macScheme.doFinal(data)); 208 } 209 } 210 211 /** 212 * Generate a random IV appropriate for use with the algorithm specified in {@code encType}. 213 * 214 * @return a freshly generated IV (a random byte sequence of appropriate length) 215 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 216 */ 217 @SuppressInsecureCipherModeCheckerReviewed 218 // See b/26525455 for security review. generateIv(EncType encType, SecureRandom rng)219 static byte[] generateIv(EncType encType, SecureRandom rng) throws NoSuchAlgorithmException { 220 if (rng == null) { 221 throw new NullPointerException(); 222 } 223 try { 224 Cipher encrypter = Cipher.getInstance(encType.getJcaName()); 225 byte[] iv = new byte[encrypter.getBlockSize()]; 226 rng.nextBytes(iv); 227 return iv; 228 } catch (NoSuchPaddingException e) { 229 throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException 230 } 231 } 232 233 /** 234 * Encrypts {@code plaintext} using the algorithm specified in {@code encType}, with the specified 235 * {@code iv} and {@code encryptionKey}. 236 * 237 * @param rng source of randomness to be used with the specified cipher, if necessary 238 * @return encrypted data 239 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 240 * @throws InvalidKeyException if {@code encryptionKey} is incompatible with {@code encType} 241 */ 242 @SuppressInsecureCipherModeCheckerReviewed 243 // See b/26525455 for security review. encrypt( Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext)244 static byte[] encrypt( 245 Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext) 246 throws NoSuchAlgorithmException, InvalidKeyException { 247 if ((encryptionKey == null) || (iv == null) || (plaintext == null)) { 248 throw new NullPointerException(); 249 } 250 if (encType == EncType.NONE) { 251 throw new NoSuchAlgorithmException("Cannot use NONE type here"); 252 } 253 try { 254 Cipher encrypter = Cipher.getInstance(encType.getJcaName()); 255 SecretKey derivedKey = 256 deriveAes256KeyFor(getSecretKey(encryptionKey), getPurpose(encType)); 257 encrypter.init(Cipher.ENCRYPT_MODE, derivedKey, new IvParameterSpec(iv), rng); 258 return encrypter.doFinal(plaintext); 259 } catch (InvalidAlgorithmParameterException e) { 260 throw new AssertionError(e); // Should never happen 261 } catch (IllegalBlockSizeException e) { 262 throw new AssertionError(e); // Should never happen 263 } catch (BadPaddingException e) { 264 throw new AssertionError(e); // Should never happen 265 } catch (NoSuchPaddingException e) { 266 throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException 267 } 268 } 269 270 /** 271 * Decrypts {@code ciphertext} using the algorithm specified in {@code encType}, with the 272 * specified {@code iv} and {@code decryptionKey}. 273 * 274 * @return the plaintext (decrypted) data 275 * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType} 276 * @throws InvalidKeyException if {@code decryptionKey} is incompatible with {@code encType} 277 * @throws InvalidAlgorithmParameterException if {@code encType} exceeds legal cryptographic 278 * strength limits in this jurisdiction 279 * @throws IllegalBlockSizeException if {@code ciphertext} contains an illegal block 280 * @throws BadPaddingException if {@code ciphertext} contains an illegal padding 281 */ 282 @SuppressInsecureCipherModeCheckerReviewed 283 // See b/26525455 for security review decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext)284 static byte[] decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext) 285 throws NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, 286 IllegalBlockSizeException, BadPaddingException { 287 if ((decryptionKey == null) || (iv == null) || (ciphertext == null)) { 288 throw new NullPointerException(); 289 } 290 if (encType == EncType.NONE) { 291 throw new NoSuchAlgorithmException("Cannot use NONE type here"); 292 } 293 try { 294 Cipher decrypter = Cipher.getInstance(encType.getJcaName()); 295 SecretKey derivedKey = 296 deriveAes256KeyFor(getSecretKey(decryptionKey), getPurpose(encType)); 297 decrypter.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(iv)); 298 return decrypter.doFinal(ciphertext); 299 } catch (NoSuchPaddingException e) { 300 throw new AssertionError(e); // Should never happen 301 } 302 } 303 304 /** 305 * Computes a collision-resistant hash of {@link #DIGEST_LENGTH} bytes 306 * (using a truncated SHA-256 output). 307 */ digest(byte[] data)308 static byte[] digest(byte[] data) throws NoSuchAlgorithmException { 309 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 310 byte[] truncatedHash = new byte[DIGEST_LENGTH]; 311 System.arraycopy(sha256.digest(data), 0, truncatedHash, 0, DIGEST_LENGTH); 312 return truncatedHash; 313 } 314 315 /** 316 * Returns {@code true} if the two arrays are equal to one another. 317 * When the two arrays differ in length, trivially returns {@code false}. 318 * When the two arrays are equal in length, does a constant-time comparison 319 * of the two, i.e. does not abort the comparison when the first differing 320 * element is found. 321 * 322 * <p>NOTE: This is a copy of {@code java/com/google/math/crypto/ConstantTime#arrayEquals}. 323 * 324 * @param a An array to compare 325 * @param b Another array to compare 326 * @return {@code true} if these arrays are both null or if they have equal 327 * length and equal bytes in all elements 328 */ constantTimeArrayEquals(@ullable byte[] a, @Nullable byte[] b)329 static boolean constantTimeArrayEquals(@Nullable byte[] a, @Nullable byte[] b) { 330 if (a == null || b == null) { 331 return (a == b); 332 } 333 if (a.length != b.length) { 334 return false; 335 } 336 byte result = 0; 337 for (int i = 0; i < b.length; i++) { 338 result = (byte) (result | a[i] ^ b[i]); 339 } 340 return (result == 0); 341 } 342 343 // @VisibleForTesting getPurpose(SigType sigType)344 static String getPurpose(SigType sigType) { 345 return "SIG:" + sigType.getSigScheme().getNumber(); 346 } 347 348 // @VisibleForTesting getPurpose(EncType encType)349 static String getPurpose(EncType encType) { 350 return "ENC:" + encType.getEncScheme().getNumber(); 351 } 352 getSecretKey(Key key)353 private static SecretKey getSecretKey(Key key) throws InvalidKeyException { 354 if (!(key instanceof SecretKey)) { 355 throw new InvalidKeyException("Expected a SecretKey"); 356 } 357 return (SecretKey) key; 358 } 359 360 /** 361 * @return the UTF-8 encoding of the given string 362 * @throws RuntimeException if the UTF-8 charset is not present. 363 */ utf8StringToBytes(String input)364 public static byte[] utf8StringToBytes(String input) { 365 try { 366 return input.getBytes("UTF-8"); 367 } catch (UnsupportedEncodingException e) { 368 throw new RuntimeException(e); // Shouldn't happen, UTF-8 is universal 369 } 370 } 371 372 /** 373 * @return SHA-256(UTF-8 encoded input) 374 */ sha256(String input)375 public static byte[] sha256(String input) { 376 MessageDigest sha256; 377 try { 378 sha256 = MessageDigest.getInstance("SHA-256"); 379 return sha256.digest(utf8StringToBytes(input)); 380 } catch (NoSuchAlgorithmException e) { 381 throw new RuntimeException("No security provider initialized yet?", e); 382 } 383 } 384 385 /** 386 * A key derivation function specific to this library, which accepts a {@code masterKey} and an 387 * arbitrary {@code purpose} describing the intended application of the derived sub-key, 388 * and produces a derived AES-256 key safe to use as if it were independent of any other 389 * derived key which used a different {@code purpose}. 390 * 391 * @param masterKey any key suitable for use with HmacSHA256 392 * @param purpose a UTF-8 encoded string describing the intended purpose of derived key 393 * @return a derived SecretKey suitable for use with AES-256 394 * @throws InvalidKeyException if the encoded form of {@code masterKey} cannot be accessed 395 */ deriveAes256KeyFor(SecretKey masterKey, String purpose)396 static SecretKey deriveAes256KeyFor(SecretKey masterKey, String purpose) 397 throws NoSuchAlgorithmException, InvalidKeyException { 398 return new SecretKeySpec(hkdf(masterKey, SALT, utf8StringToBytes(purpose)), "AES"); 399 } 400 401 /** 402 * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length. 403 * 404 * Please make sure to select a salt that is fixed and unique for your codebase, and use the 405 * {@code info} parameter to specify any additional bits that should influence the derived key. 406 * 407 * @param inputKeyMaterial master key from which to derive sub-keys 408 * @param salt a (public) randomly generated 256-bit input that can be re-used 409 * @param info arbitrary information that is bound to the derived key (i.e., used in its creation) 410 * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info) 411 * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed 412 */ hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info)413 public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info) 414 throws NoSuchAlgorithmException, InvalidKeyException { 415 if ((inputKeyMaterial == null) || (salt == null) || (info == null)) { 416 throw new NullPointerException(); 417 } 418 return hkdfSha256Expand(hkdfSha256Extract(inputKeyMaterial, salt), info); 419 } 420 421 /** 422 * @return the concatenation of {@code a} and {@code b}, treating {@code null} as the empty array. 423 */ concat(@ullable byte[] a, @Nullable byte[] b)424 static byte[] concat(@Nullable byte[] a, @Nullable byte[] b) { 425 if ((a == null) && (b == null)) { 426 return new byte[] { }; 427 } 428 if (a == null) { 429 return b; 430 } 431 if (b == null) { 432 return a; 433 } 434 byte[] result = new byte[a.length + b.length]; 435 System.arraycopy(a, 0, result, 0, a.length); 436 System.arraycopy(b, 0, result, a.length, b.length); 437 return result; 438 } 439 440 /** 441 * Since {@code Arrays.copyOfRange(...)} is not available on older Android platforms, 442 * a custom method for computing a subarray is provided here. 443 * 444 * @return the substring of {@code in} from {@code beginIndex} (inclusive) 445 * up to {@code endIndex} (exclusive) 446 */ subarray(byte[] in, int beginIndex, int endIndex)447 static byte[] subarray(byte[] in, int beginIndex, int endIndex) { 448 if (in == null) { 449 throw new NullPointerException(); 450 } 451 int length = endIndex - beginIndex; 452 if ((length < 0) 453 || (beginIndex < 0) 454 || (endIndex < 0) 455 || (beginIndex >= in.length) 456 || (endIndex > in.length)) { 457 throw new IndexOutOfBoundsException(); 458 } 459 byte[] result = new byte[length]; 460 if (length > 0) { 461 System.arraycopy(in, beginIndex, result, 0, length); 462 } 463 return result; 464 } 465 466 /** 467 * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is 468 * used to pre-process the inputKeyMaterial and mix it with the salt, producing output suitable 469 * for use with HKDF expansion function (which produces the actual derived key). 470 * 471 * @see #hkdfSha256Expand(byte[], byte[]) 472 * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC) 473 * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed 474 * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable 475 */ hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt)476 private static byte[] hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt) 477 throws NoSuchAlgorithmException, InvalidKeyException { 478 Mac macScheme = Mac.getInstance("HmacSHA256"); 479 try { 480 macScheme.init(new SecretKeySpec(salt, "AES")); 481 } catch (InvalidKeyException e) { 482 throw new AssertionError(e); // This should never happen 483 } 484 // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should be 485 // consistent across implementations. 486 byte[] encodedKeyMaterial = inputKeyMaterial.getEncoded(); 487 if (encodedKeyMaterial == null) { 488 throw new InvalidKeyException("Cannot get encoded form of SecretKey"); 489 } 490 return macScheme.doFinal(encodedKeyMaterial); 491 } 492 493 /** 494 * Special case of HKDF (RFC 5869) expansion function, using the SHA-256 hash function and 495 * allowing for a maximum output length of 256 bits. 496 * 497 * @param pseudoRandomKey should be generated by {@link #hkdfSha256Expand(byte[], byte[])} 498 * @param info arbitrary information the derived key should be bound to 499 * @return raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01) 500 * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable 501 */ hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info)502 private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info) 503 throws NoSuchAlgorithmException { 504 Mac macScheme = Mac.getInstance("HmacSHA256"); 505 try { 506 macScheme.init(new SecretKeySpec(pseudoRandomKey, "AES")); 507 } catch (InvalidKeyException e) { 508 throw new AssertionError(e); // This should never happen 509 } 510 // Arbitrary "info" to be included in the MAC. 511 macScheme.update(info); 512 // Note that RFC 5869 computes number of blocks N = ceil(hash length / output length), but 513 // here we only deal with a 256 bit hash up to a 256 bit output, yielding N=1. 514 return macScheme.doFinal(CONSTANT_01); 515 } 516 517 } 518