141 lines
5.2 KiB
Java
141 lines
5.2 KiB
Java
/*
|
|
* 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 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;
|
|
|
|
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.
|
|
*/
|
|
static byte[] computeHkdf(
|
|
String macAlgorithm, final byte[] ikm, final byte[] salt, 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);
|
|
}
|
|
}
|
|
|
|
}
|