| /* |
| * Copyright (C) 2022 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.keystore2; |
| |
| import android.annotation.NonNull; |
| import android.security.KeyStoreSecurityLevel; |
| import android.system.keystore2.KeyDescriptor; |
| import android.system.keystore2.KeyMetadata; |
| |
| import java.math.BigInteger; |
| import java.security.interfaces.EdECPublicKey; |
| import java.security.spec.EdECPoint; |
| import java.security.spec.NamedParameterSpec; |
| import java.util.Arrays; |
| import java.util.Objects; |
| |
| /** |
| * {@link EdECPublicKey} backed by keystore. |
| * |
| * @hide |
| */ |
| public class AndroidKeyStoreEdECPublicKey extends AndroidKeyStorePublicKey |
| implements EdECPublicKey { |
| /** |
| * DER sequence, as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-4 and |
| * https://datatracker.ietf.org/doc/html/rfc5280#section-4.1. |
| * SEQUENCE (2 elem) |
| * SEQUENCE (1 elem) |
| * OBJECT IDENTIFIER 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm) |
| * as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-3 |
| * BIT STRING (256 bit) as defined in |
| * https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2 |
| */ |
| private static final byte[] DER_KEY_PREFIX = new byte[] { |
| 0x30, |
| 0x2a, |
| 0x30, |
| 0x05, |
| 0x06, |
| 0x03, |
| 0x2b, |
| 0x65, |
| 0x70, |
| 0x03, |
| 0x21, |
| 0x00, |
| }; |
| private static final int ED25519_KEY_SIZE_BYTES = 32; |
| |
| private byte[] mEncodedKey; |
| private EdECPoint mPoint; |
| |
| public AndroidKeyStoreEdECPublicKey( |
| @NonNull KeyDescriptor descriptor, |
| @NonNull KeyMetadata metadata, |
| @NonNull String algorithm, |
| @NonNull KeyStoreSecurityLevel iSecurityLevel, |
| @NonNull byte[] encodedKey) { |
| super(descriptor, metadata, encodedKey, algorithm, iSecurityLevel); |
| mEncodedKey = encodedKey; |
| |
| int preambleLength = matchesPreamble(DER_KEY_PREFIX, encodedKey); |
| if (preambleLength == 0) { |
| throw new IllegalArgumentException("Key size is not correct size"); |
| } |
| |
| mPoint = pointFromKeyByteArray( |
| Arrays.copyOfRange(encodedKey, preambleLength, encodedKey.length)); |
| } |
| |
| @Override |
| AndroidKeyStorePrivateKey getPrivateKey() { |
| return new AndroidKeyStoreEdECPrivateKey( |
| getUserKeyDescriptor(), |
| getKeyIdDescriptor().nspace, |
| getAuthorizations(), |
| "EdDSA", |
| getSecurityLevel()); |
| } |
| |
| @Override |
| public NamedParameterSpec getParams() { |
| return NamedParameterSpec.ED25519; |
| } |
| |
| @Override |
| public EdECPoint getPoint() { |
| return mPoint; |
| } |
| |
| private static int matchesPreamble(byte[] preamble, byte[] encoded) { |
| if (encoded.length != (preamble.length + ED25519_KEY_SIZE_BYTES)) { |
| return 0; |
| } |
| if (Arrays.compare(preamble, Arrays.copyOf(encoded, preamble.length)) != 0) { |
| return 0; |
| } |
| return preamble.length; |
| } |
| |
| private static EdECPoint pointFromKeyByteArray(byte[] coordinates) { |
| Objects.requireNonNull(coordinates); |
| |
| // Oddity of the key is the most-significant bit of the last byte. |
| boolean isOdd = (0x80 & coordinates[coordinates.length - 1]) != 0; |
| // Zero out the oddity bit. |
| coordinates[coordinates.length - 1] &= (byte) 0x7f; |
| // Representation of Y is in little-endian, according to rfc8032 section-3.1. |
| reverse(coordinates); |
| // The integer representing Y starts from the first bit in the coordinates array. |
| BigInteger y = new BigInteger(1, coordinates); |
| return new EdECPoint(isOdd, y); |
| } |
| |
| private static void reverse(byte[] coordinateArray) { |
| int start = 0; |
| int end = coordinateArray.length - 1; |
| while (start < end) { |
| byte tmp = coordinateArray[start]; |
| coordinateArray[start] = coordinateArray[end]; |
| coordinateArray[end] = tmp; |
| start++; |
| end--; |
| } |
| } |
| |
| @Override |
| public byte[] getEncoded() { |
| return mEncodedKey.clone(); |
| } |
| } |