1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.identity; 18 19 import android.annotation.NonNull; 20 21 import java.io.ByteArrayOutputStream; 22 import java.io.IOException; 23 import java.security.InvalidKeyException; 24 import java.security.NoSuchAlgorithmException; 25 import java.security.PublicKey; 26 import java.security.interfaces.ECPublicKey; 27 import java.security.spec.ECPoint; 28 import java.util.Collection; 29 30 import javax.crypto.Mac; 31 import javax.crypto.spec.SecretKeySpec; 32 33 /** 34 * @hide 35 */ 36 public class Util { 37 private static final String TAG = "Util"; 38 integerCollectionToArray(Collection<Integer> collection)39 static int[] integerCollectionToArray(Collection<Integer> collection) { 40 int[] result = new int[collection.size()]; 41 int n = 0; 42 for (int item : collection) { 43 result[n++] = item; 44 } 45 return result; 46 } 47 stripLeadingZeroes(byte[] value)48 static byte[] stripLeadingZeroes(byte[] value) { 49 int n = 0; 50 while (n < value.length && value[n] == 0) { 51 n++; 52 } 53 int newLen = value.length - n; 54 byte[] ret = new byte[newLen]; 55 int m = 0; 56 while (n < value.length) { 57 ret[m++] = value[n++]; 58 } 59 return ret; 60 } 61 publicKeyEncodeUncompressedForm(PublicKey publicKey)62 static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) { 63 ECPoint w = ((ECPublicKey) publicKey).getW(); 64 // X and Y are always positive so for interop we remove any leading zeroes 65 // inserted by the BigInteger encoder. 66 byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); 67 byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); 68 try { 69 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 70 baos.write(0x04); 71 baos.write(x); 72 baos.write(y); 73 return baos.toByteArray(); 74 } catch (IOException e) { 75 throw new RuntimeException("Unexpected IOException", e); 76 } 77 } 78 79 /** 80 * Computes an HKDF. 81 * 82 * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google 83 * /crypto/tink/subtle/Hkdf.java 84 * which is also Copyright (c) Google and also licensed under the Apache 2 license. 85 * 86 * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or 87 * "HMACSHA256". 88 * @param ikm the input keying material. 89 * @param salt optional salt. A possibly non-secret random value. If no salt is 90 * provided (i.e. if 91 * salt has length 0) then an array of 0s of the same size as the hash 92 * digest is used as salt. 93 * @param info optional context and application specific information. 94 * @param size The length of the generated pseudorandom string in bytes. The maximal 95 * size is 96 * 255.DigestSize, where DigestSize is the size of the underlying HMAC. 97 * @return size pseudorandom bytes. 98 */ computeHkdf( @onNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt, @NonNull final byte[] info, int size)99 @NonNull public static byte[] computeHkdf( 100 @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt, 101 @NonNull final byte[] info, int size) { 102 Mac mac = null; 103 try { 104 mac = Mac.getInstance(macAlgorithm); 105 } catch (NoSuchAlgorithmException e) { 106 throw new RuntimeException("No such algorithm: " + macAlgorithm, e); 107 } 108 if (size > 255 * mac.getMacLength()) { 109 throw new RuntimeException("size too large"); 110 } 111 try { 112 if (salt == null || salt.length == 0) { 113 // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided 114 // then HKDF uses a salt that is an array of zeros of the same length as the hash 115 // digest. 116 mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); 117 } else { 118 mac.init(new SecretKeySpec(salt, macAlgorithm)); 119 } 120 byte[] prk = mac.doFinal(ikm); 121 byte[] result = new byte[size]; 122 int ctr = 1; 123 int pos = 0; 124 mac.init(new SecretKeySpec(prk, macAlgorithm)); 125 byte[] digest = new byte[0]; 126 while (true) { 127 mac.update(digest); 128 mac.update(info); 129 mac.update((byte) ctr); 130 digest = mac.doFinal(); 131 if (pos + digest.length < size) { 132 System.arraycopy(digest, 0, result, pos, digest.length); 133 pos += digest.length; 134 ctr++; 135 } else { 136 System.arraycopy(digest, 0, result, pos, size - pos); 137 break; 138 } 139 } 140 return result; 141 } catch (InvalidKeyException e) { 142 throw new RuntimeException("Error MACing", e); 143 } 144 } 145 Util()146 private Util() {} 147 } 148