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