blob: 789ff06f064b16cc4f652c109a61951a458d88d9 [file] [log] [blame]
/*
* 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() {}
}