| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.security.identity; |
| |
| import android.annotation.NonNull; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PublicKey; |
| import java.security.interfaces.ECPublicKey; |
| import java.security.spec.ECPoint; |
| import java.util.Collection; |
| |
| import javax.crypto.Mac; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| /** |
| * @hide |
| */ |
| public class Util { |
| private static final String TAG = "Util"; |
| |
| static int[] integerCollectionToArray(Collection<Integer> collection) { |
| int[] result = new int[collection.size()]; |
| int n = 0; |
| for (int item : collection) { |
| result[n++] = item; |
| } |
| return result; |
| } |
| |
| static byte[] stripLeadingZeroes(byte[] value) { |
| int n = 0; |
| while (n < value.length && value[n] == 0) { |
| n++; |
| } |
| int newLen = value.length - n; |
| byte[] ret = new byte[newLen]; |
| int m = 0; |
| while (n < value.length) { |
| ret[m++] = value[n++]; |
| } |
| return ret; |
| } |
| |
| static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) { |
| ECPoint w = ((ECPublicKey) publicKey).getW(); |
| // X and Y are always positive so for interop we remove any leading zeroes |
| // inserted by the BigInteger encoder. |
| byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); |
| byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); |
| try { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| baos.write(0x04); |
| baos.write(x); |
| baos.write(y); |
| return baos.toByteArray(); |
| } catch (IOException e) { |
| throw new RuntimeException("Unexpected IOException", e); |
| } |
| } |
| |
| /** |
| * Computes an HKDF. |
| * |
| * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google |
| * /crypto/tink/subtle/Hkdf.java |
| * which is also Copyright (c) Google and also licensed under the Apache 2 license. |
| * |
| * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or |
| * "HMACSHA256". |
| * @param ikm the input keying material. |
| * @param salt optional salt. A possibly non-secret random value. If no salt is |
| * provided (i.e. if |
| * salt has length 0) then an array of 0s of the same size as the hash |
| * digest is used as salt. |
| * @param info optional context and application specific information. |
| * @param size The length of the generated pseudorandom string in bytes. The maximal |
| * size is |
| * 255.DigestSize, where DigestSize is the size of the underlying HMAC. |
| * @return size pseudorandom bytes. |
| */ |
| @NonNull public static byte[] computeHkdf( |
| @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt, |
| @NonNull final byte[] info, int size) { |
| Mac mac = null; |
| try { |
| mac = Mac.getInstance(macAlgorithm); |
| } catch (NoSuchAlgorithmException e) { |
| throw new RuntimeException("No such algorithm: " + macAlgorithm, e); |
| } |
| if (size > 255 * mac.getMacLength()) { |
| throw new RuntimeException("size too large"); |
| } |
| try { |
| if (salt == null || salt.length == 0) { |
| // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided |
| // then HKDF uses a salt that is an array of zeros of the same length as the hash |
| // digest. |
| mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); |
| } else { |
| mac.init(new SecretKeySpec(salt, macAlgorithm)); |
| } |
| byte[] prk = mac.doFinal(ikm); |
| byte[] result = new byte[size]; |
| int ctr = 1; |
| int pos = 0; |
| mac.init(new SecretKeySpec(prk, macAlgorithm)); |
| byte[] digest = new byte[0]; |
| while (true) { |
| mac.update(digest); |
| mac.update(info); |
| mac.update((byte) ctr); |
| digest = mac.doFinal(); |
| if (pos + digest.length < size) { |
| System.arraycopy(digest, 0, result, pos, digest.length); |
| pos += digest.length; |
| ctr++; |
| } else { |
| System.arraycopy(digest, 0, result, pos, size - pos); |
| break; |
| } |
| } |
| return result; |
| } catch (InvalidKeyException e) { |
| throw new RuntimeException("Error MACing", e); |
| } |
| } |
| |
| private Util() {} |
| } |