1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.credentials.e2ee 18 19 import androidx.annotation.IntDef 20 import androidx.annotation.RestrictTo 21 import androidx.annotation.WorkerThread 22 import com.google.crypto.tink.subtle.Ed25519Sign 23 import com.google.crypto.tink.subtle.Hkdf 24 25 /** 26 * A public-private key pair usable for signing, representing an end user identity in an end-to-end 27 * encrypted messaging system. 28 * 29 * @property public The public key, stored as a byte array. 30 * @property private The private key, stored as a byte array. 31 * @property type The type of signing key, e.g. Ed25519. 32 */ 33 class IdentityKey 34 private constructor(val public: ByteArray, val private: ByteArray, @IdentityKeyType val type: Int) { 35 @RestrictTo(RestrictTo.Scope.LIBRARY) 36 @Retention(AnnotationRetention.SOURCE) 37 @IntDef(IDENTITY_KEY_TYPE_RESERVED, IDENTITY_KEY_TYPE_ED25519) 38 annotation class IdentityKeyType 39 40 companion object { 41 /** 42 * The default signing key type, which should not be used. This is required to match 43 * https://www.iana.org/assignments/cose/cose.xhtml#algorithms 44 */ 45 const val IDENTITY_KEY_TYPE_RESERVED = 0 46 47 /** 48 * A signing key on Ed25519. The value matches 49 * https://www.iana.org/assignments/cose/cose.xhtml#algorithms 50 */ 51 const val IDENTITY_KEY_TYPE_ED25519 = 6 52 53 /** 54 * Creates a [IdentityKey], a public/private key pair usable for signing. It is intended for 55 * use with the WebAuthn PRF extension (https://w3c.github.io/webauthn/#prf-extension). The 56 * generated IdentityKey is deterministic given prf and salt, thus the prf value must be 57 * kept secret. Currently, only Ed25519 is supported as a key type. 58 * 59 * @param prf The PRF output of WebAuthn used in the key derivation. 60 * @param salt An optional salt used in the key derivation. 61 * @param keyType The type of IdentityKey to generate, e.g. Ed25519. 62 * @return a [IdentityKey], a public/private key pair usable for signing. 63 * @throws IllegalArgumentException if the key type is not supported. 64 */ 65 @JvmStatic 66 @WorkerThread createFromPrfnull67 fun createFromPrf( 68 prf: ByteArray, 69 salt: ByteArray?, 70 @IdentityKeyType keyType: Int 71 ): IdentityKey { 72 if (keyType != IDENTITY_KEY_TYPE_ED25519) { 73 throw IllegalArgumentException("Only Ed25519 is supported at this stage.") 74 } 75 76 val hkdf: ByteArray = 77 Hkdf.computeHkdf( 78 "HmacSHA256", 79 prf, 80 // According to RFC 5869, Section 2.2 the salt is optional. If no salt is 81 // provided, the HKDF uses a salt that is an array of zeros of the same length 82 // as the hash digest. 83 /* salt= */ salt ?: ByteArray(32), 84 /* info= */ ByteArray(0), 85 /* size= */ 32 86 ) 87 val keyPair: Ed25519Sign.KeyPair = Ed25519Sign.KeyPair.newKeyPairFromSeed(hkdf) 88 return IdentityKey(keyPair.publicKey, keyPair.privateKey, IDENTITY_KEY_TYPE_ED25519) 89 } 90 } 91 equalsnull92 override fun equals(other: Any?): Boolean { 93 if (other == null) return false 94 if (this === other) return true 95 if (other !is IdentityKey) return false 96 if ( 97 type != other.type || 98 !private.contentEquals(other.private) || 99 !public.contentEquals(other.public) 100 ) 101 return false 102 return true 103 } 104 hashCodenull105 override fun hashCode(): Int { 106 var result = public.contentHashCode() 107 result = 31 * result + private.contentHashCode() 108 result = 31 * result + type 109 return result 110 } 111 } 112