171 lines
6.6 KiB
Java
171 lines
6.6 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 android.annotation.NonNull;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.PublicKey;
|
|
import java.security.interfaces.ECPublicKey;
|
|
import java.security.spec.ECPoint;
|
|
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public class Util {
|
|
private static final String TAG = "Util";
|
|
|
|
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();
|
|
BigInteger x = w.getAffineX();
|
|
BigInteger y = w.getAffineY();
|
|
if (x.compareTo(BigInteger.ZERO) < 0) {
|
|
throw new RuntimeException("X is negative");
|
|
}
|
|
if (y.compareTo(BigInteger.ZERO) < 0) {
|
|
throw new RuntimeException("Y is negative");
|
|
}
|
|
try {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
baos.write(0x04);
|
|
|
|
// Each coordinate may be encoded in 33*, 32, or fewer bytes.
|
|
//
|
|
// * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
|
|
// minimum number of bytes required to represent this BigInteger, including at
|
|
// least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
|
|
// the MSB is always 0x00. This is taken care of by calling calling
|
|
// stripLeadingZeroes().
|
|
//
|
|
// We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
|
|
// and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
|
|
// where X and Y are encoded in exactly 32 byte, big endian integer values each.
|
|
//
|
|
byte[] xBytes = stripLeadingZeroes(x.toByteArray());
|
|
if (xBytes.length > 32) {
|
|
throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
|
|
}
|
|
for (int n = 0; n < 32 - xBytes.length; n++) {
|
|
baos.write(0x00);
|
|
}
|
|
baos.write(xBytes);
|
|
|
|
byte[] yBytes = stripLeadingZeroes(y.toByteArray());
|
|
if (yBytes.length > 32) {
|
|
throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
|
|
}
|
|
for (int n = 0; n < 32 - yBytes.length; n++) {
|
|
baos.write(0x00);
|
|
}
|
|
baos.write(yBytes);
|
|
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() {}
|
|
}
|