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