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.math.BigInteger;
24 import java.security.InvalidKeyException;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.PublicKey;
27 import java.security.interfaces.ECPublicKey;
28 import java.security.spec.ECPoint;
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 
stripLeadingZeroes(byte[] value)39     static byte[] stripLeadingZeroes(byte[] value) {
40         int n = 0;
41         while (n < value.length && value[n] == 0) {
42             n++;
43         }
44         int newLen = value.length - n;
45         byte[] ret = new byte[newLen];
46         int m = 0;
47         while (n < value.length) {
48             ret[m++] = value[n++];
49         }
50         return ret;
51     }
52 
publicKeyEncodeUncompressedForm(PublicKey publicKey)53     static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) {
54         ECPoint w = ((ECPublicKey) publicKey).getW();
55         BigInteger x = w.getAffineX();
56         BigInteger y = w.getAffineY();
57         if (x.compareTo(BigInteger.ZERO) < 0) {
58             throw new RuntimeException("X is negative");
59         }
60         if (y.compareTo(BigInteger.ZERO) < 0) {
61             throw new RuntimeException("Y is negative");
62         }
63         try {
64             ByteArrayOutputStream baos = new ByteArrayOutputStream();
65             baos.write(0x04);
66 
67             // Each coordinate may be encoded in 33*, 32, or fewer bytes.
68             //
69             //  * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
70             //      minimum number of bytes required to represent this BigInteger, including at
71             //      least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
72             //      the MSB is always 0x00. This is taken care of by calling calling
73             //      stripLeadingZeroes().
74             //
75             // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
76             // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
77             // where X and Y are encoded in exactly 32 byte, big endian integer values each.
78             //
79             byte[] xBytes = stripLeadingZeroes(x.toByteArray());
80             if (xBytes.length > 32) {
81                 throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
82             }
83             for (int n = 0; n < 32 - xBytes.length; n++) {
84                 baos.write(0x00);
85             }
86             baos.write(xBytes);
87 
88             byte[] yBytes = stripLeadingZeroes(y.toByteArray());
89             if (yBytes.length > 32) {
90                 throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
91             }
92             for (int n = 0; n < 32 - yBytes.length; n++) {
93                 baos.write(0x00);
94             }
95             baos.write(yBytes);
96             return baos.toByteArray();
97         } catch (IOException e) {
98             throw new RuntimeException("Unexpected IOException", e);
99         }
100     }
101 
102     /**
103      * Computes an HKDF.
104      *
105      * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
106      * /crypto/tink/subtle/Hkdf.java
107      * which is also Copyright (c) Google and also licensed under the Apache 2 license.
108      *
109      * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
110      *                     "HMACSHA256".
111      * @param ikm          the input keying material.
112      * @param salt         optional salt. A possibly non-secret random value. If no salt is
113      *                     provided (i.e. if
114      *                     salt has length 0) then an array of 0s of the same size as the hash
115      *                     digest is used as salt.
116      * @param info         optional context and application specific information.
117      * @param size         The length of the generated pseudorandom string in bytes. The maximal
118      *                     size is
119      *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
120      * @return size pseudorandom bytes.
121      */
computeHkdf( @onNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt, @NonNull final byte[] info, int size)122     @NonNull public static byte[] computeHkdf(
123             @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
124             @NonNull final byte[] info, int size) {
125         Mac mac = null;
126         try {
127             mac = Mac.getInstance(macAlgorithm);
128         } catch (NoSuchAlgorithmException e) {
129             throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
130         }
131         if (size > 255 * mac.getMacLength()) {
132             throw new RuntimeException("size too large");
133         }
134         try {
135             if (salt == null || salt.length == 0) {
136                 // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
137                 // then HKDF uses a salt that is an array of zeros of the same length as the hash
138                 // digest.
139                 mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
140             } else {
141                 mac.init(new SecretKeySpec(salt, macAlgorithm));
142             }
143             byte[] prk = mac.doFinal(ikm);
144             byte[] result = new byte[size];
145             int ctr = 1;
146             int pos = 0;
147             mac.init(new SecretKeySpec(prk, macAlgorithm));
148             byte[] digest = new byte[0];
149             while (true) {
150                 mac.update(digest);
151                 mac.update(info);
152                 mac.update((byte) ctr);
153                 digest = mac.doFinal();
154                 if (pos + digest.length < size) {
155                     System.arraycopy(digest, 0, result, pos, digest.length);
156                     pos += digest.length;
157                     ctr++;
158                 } else {
159                     System.arraycopy(digest, 0, result, pos, size - pos);
160                     break;
161                 }
162             }
163             return result;
164         } catch (InvalidKeyException e) {
165             throw new RuntimeException("Error MACing", e);
166         }
167     }
168 
Util()169     private Util() {}
170 }
171