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 android.util.Base64
20 import com.google.common.io.BaseEncoding
21 import com.google.common.truth.Truth.assertThat
22 import java.util.Random
23 import org.junit.Test
24 
25 class IdentityKeyTest {
26 
27     var sRandom: Random = Random()
28 
randBytesnull29     private fun randBytes(numBytes: Int): ByteArray {
30         val bytes = ByteArray(numBytes)
31         sRandom.nextBytes(bytes)
32         return bytes
33     }
34 
hexEncodenull35     private fun hexEncode(bytes: ByteArray): String {
36         return BaseEncoding.base16().lowerCase().encode(bytes)
37     }
38 
39     @Test
identityKeyWithFixedInputs_mustProduceExpectedOutputnull40     fun identityKeyWithFixedInputs_mustProduceExpectedOutput() {
41         val prf = ByteArray(32)
42         val salt = ByteArray(32)
43         // with an all-zero PRF and salt, this is the expected key
44         val expectedPrivKeyHex = "df7204546f1bee78b85324a7898ca119b387e01386d1aef037781d4a8a036aee"
45         val expectedPubKeyHex = "ba33d523fd7bf0d06ce9298c3440be1bea3748c6270ae3e07ae8ea19abb8ed23"
46 
47         val identityKey =
48             IdentityKey.createFromPrf(prf, salt, IdentityKey.IDENTITY_KEY_TYPE_ED25519)
49 
50         assertThat(identityKey.private).isNotNull()
51         assertThat(identityKey.public).isNotNull()
52         assertThat(hexEncode(identityKey.private)).isEqualTo(expectedPrivKeyHex)
53         assertThat(hexEncode(identityKey.public)).isEqualTo(expectedPubKeyHex)
54     }
55 
56     @Test
identityKeyWithoutSalt_mustBeIdenticalToEmptySaltnull57     fun identityKeyWithoutSalt_mustBeIdenticalToEmptySalt() {
58         for (i in 1..10) {
59             val prf = randBytes(32)
60             val identityKey =
61                 IdentityKey.createFromPrf(
62                     prf,
63                     /* salt= */ null,
64                     IdentityKey.IDENTITY_KEY_TYPE_ED25519
65                 )
66             val identityKey2 =
67                 IdentityKey.createFromPrf(prf, ByteArray(32), IdentityKey.IDENTITY_KEY_TYPE_ED25519)
68 
69             assertThat(identityKey).isEqualTo(identityKey2)
70         }
71     }
72 
73     @Test
identityKey_canBeGeneratedUsingWebAuthnPrfOutputnull74     fun identityKey_canBeGeneratedUsingWebAuthnPrfOutput() {
75         /*
76         Ideally, we would test the full webauthn interaction (set the PRF extension to true, call
77         navigator.credentials.create, read the PRF output). The problem is that this would tie
78         androidX to the implementation of a password manager.
79         Instead, we manually copy the prfOutput value from //com/google/android/gms/fido/authenticator/embedded/AuthenticationRequestHandlerTest.java,
80         like a test vector. Even if the two values get out of sync, what we care about is the Base64
81         format, as the PRF output is fully random-looking by definition.
82          */
83         val prfOutput =
84             Base64.decode(
85                 "f2HM0TolWHyYJ/+LQDW8N2vRdE0+risMV/tIKXQdj7tVKdGChdJuMyz1" +
86                     "/iX7x4y3GvHLlmja1A8qCsKsekW22Q==",
87                 Base64.DEFAULT
88             )
89         val salt = ByteArray(32)
90         val expectedPrivKeyHex = "bccdec572ae1be6b3c3f3473781965a1935d2614c928f5430b79188950658ad6"
91         val expectedPubKeyHex = "23fa91da0af9edefae9c53c584f933f3d02f934aebddb70511adac91f255afda"
92 
93         val identityKey =
94             IdentityKey.createFromPrf(prfOutput, salt, IdentityKey.IDENTITY_KEY_TYPE_ED25519)
95 
96         assertThat(prfOutput).isNotNull()
97         assertThat(identityKey.private).isNotNull()
98         assertThat(identityKey.public).isNotNull()
99         assertThat(hexEncode(identityKey.private)).isEqualTo(expectedPrivKeyHex)
100         assertThat(hexEncode(identityKey.public)).isEqualTo(expectedPubKeyHex)
101     }
102 }
103