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