The Identity Credential APIs provides an interface to a secure store for user identity documents. These APIs are deliberately fairly general and abstract. To the extent possible, specification of the message formats and semantics of communication with credential verification devices and Issuing Authorities (IAs) is out of scope for these APIs. The Identity Credential APIs rely on user authentication to protect data elements in credentials which is implemented through auth-tokens. This CL contains changes to CryptoObject to allow this. Bug: 111446262 Test: CtsIdentityTestCases Change-Id: I48f21a561b762d86c9ca8d229962782572412f47
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);
|
|
}
|
|
}
|
|
|
|
}
|