Skip to content

Commit 8fb53fc

Browse files
authored
Crypto: add support for HPKE encryption and decryption. (#584)
This is based on the HPKE encryption routines in CredmanUtils. It's presently using Tink but in the future we may switch to the HPKE implementation in BC to avoid the extra dependency (or we may switch to using just Tink and not BC at all.) Also port our Credman integration to use these new routines. Test: New unit tests and all unit tests pass. Test: Manually tested Credman presentations from appholder and wallet to both appverifier and https://www.identitycredential.dev/ Signed-off-by: David Zeuthen <zeuthen@google.com>
1 parent 99bebc7 commit 8fb53fc

File tree

12 files changed

+488
-421
lines changed

12 files changed

+488
-421
lines changed

appholder/src/main/java/com/android/identity/wallet/GetCredentialActivity.kt

+12-11
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import androidx.biometric.BiometricManager
99
import androidx.biometric.BiometricPrompt
1010
import androidx.fragment.app.FragmentActivity
1111
import com.android.identity.android.mdoc.util.CredmanUtil
12-
import com.android.identity.android.mdoc.util.CredmanUtil.Companion.generateClientIdHash
13-
import com.android.identity.android.mdoc.util.CredmanUtil.Companion.generatePublicKeyHash
1412
import com.android.identity.android.securearea.AndroidKeystoreKeyUnlockData
1513
import com.android.identity.document.Credential
1614
import com.android.identity.document.DocumentRequest
1715
import com.android.identity.document.NameSpacedData
1816
import com.android.identity.crypto.Algorithm
17+
import com.android.identity.crypto.Crypto
18+
import com.android.identity.crypto.EcCurve
19+
import com.android.identity.crypto.EcPublicKeyDoubleCoordinate
1920
import com.android.identity.mdoc.mso.StaticAuthDataParser
2021
import com.android.identity.mdoc.response.DeviceResponseGenerator
2122
import com.android.identity.mdoc.response.DocumentGenerator
@@ -30,12 +31,10 @@ import com.android.identity.wallet.util.log
3031
import com.google.android.gms.identitycredentials.GetCredentialResponse
3132
import com.google.android.gms.identitycredentials.IntentHelper
3233
import com.google.android.gms.identitycredentials.IntentHelper.EXTRA_CREDENTIAL_ID
33-
import com.google.android.gms.identitycredentials.IntentHelper.extractCallingAppInfo
3434
import com.google.android.gms.identitycredentials.IntentHelper.extractGetCredentialRequest
3535
import com.google.android.gms.identitycredentials.IntentHelper.setGetCredentialException
3636
import com.google.android.gms.identitycredentials.IntentHelper.setGetCredentialResponse
3737
import org.json.JSONObject
38-
import java.security.PublicKey
3938
import java.util.StringTokenizer
4039

4140
class GetCredentialActivity : FragmentActivity() {
@@ -201,7 +200,8 @@ class GetCredentialActivity : FragmentActivity() {
201200

202201
// Covert nonce and publicKey
203202
val nonce = Base64.decode(nonceBase64, Base64.NO_WRAP or Base64.URL_SAFE)
204-
val readerPublicKey = CredmanUtil.publicKeyFromUncompressed(
203+
val readerPublicKey = EcPublicKeyDoubleCoordinate.fromUncompressedPointEncoding(
204+
EcCurve.P256,
205205
Base64.decode(readerPublicKeyBase64, Base64.NO_WRAP or Base64.URL_SAFE)
206206
)
207207

@@ -227,20 +227,21 @@ class GetCredentialActivity : FragmentActivity() {
227227
CredmanUtil.generateAndroidSessionTranscript(
228228
nonce,
229229
callingPackageName,
230-
generatePublicKeyHash(readerPublicKey)
230+
Crypto.digest(Algorithm.SHA256, readerPublicKey.asUncompressedPointEncoding)
231231
)
232232
} else {
233233
CredmanUtil.generateBrowserSessionTranscript(
234234
nonce,
235235
callingOrigin,
236-
generatePublicKeyHash(readerPublicKey)
236+
Crypto.digest(Algorithm.SHA256, readerPublicKey.asUncompressedPointEncoding)
237237
)
238238
}
239239
// Create ISO DeviceResponse
240240
createMDocDeviceResponse(credentialId, dataElements, encodedSessionTranscript) { deviceResponse ->
241241
// The Preview protocol HPKE encrypts the response.
242-
val credmanUtil = CredmanUtil(readerPublicKey, null)
243-
val (cipherText, encapsulatedPublicKey) = credmanUtil.encrypt(
242+
val (cipherText, encapsulatedPublicKey) = Crypto.hpkeEncrypt(
243+
Algorithm.HPKE_BASE_P256_SHA256_AES128GCM,
244+
readerPublicKey,
244245
deviceResponse,
245246
encodedSessionTranscript
246247
)
@@ -307,13 +308,13 @@ class GetCredentialActivity : FragmentActivity() {
307308
CredmanUtil.generateAndroidSessionTranscript(
308309
nonce,
309310
callingPackageName,
310-
generateClientIdHash(clientID)
311+
Crypto.digest(Algorithm.SHA256, clientID.toByteArray())
311312
)
312313
} else {
313314
CredmanUtil.generateBrowserSessionTranscript(
314315
nonce,
315316
callingOrigin,
316-
generateClientIdHash(clientID)
317+
Crypto.digest(Algorithm.SHA256, clientID.toByteArray())
317318
)
318319
}
319320
// Create ISO DeviceResponse

appverifier/src/main/java/com/android/mdl/appreader/fragment/RequestOptionsFragment.kt

+8-5
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,6 @@ class RequestOptionsFragment() : Fragment() {
154154

155155
// Generate the readerKey.
156156
val readerKey = Crypto.createEcPrivateKey(EcCurve.P256)
157-
val readerKeyPair = KeyPair(
158-
readerKey.publicKey.javaPublicKey,
159-
readerKey.javaPrivateKey
160-
)
161157

162158
// TODO: Right now we just request the first of potentially multiple documents, it
163159
// would be nice to request each document in sequence and then display all the
@@ -206,7 +202,14 @@ class RequestOptionsFragment() : Fragment() {
206202

207203
requireActivity().runOnUiThread {
208204
findNavController().navigate(RequestOptionsFragmentDirections
209-
.toShowDeviceResponse(bundle, readerKeyPair))
205+
.toShowDeviceResponse(
206+
bundle,
207+
KeyPair(
208+
readerKey.publicKey.javaPublicKey,
209+
readerKey.javaPrivateKey
210+
)
211+
)
212+
)
210213
}
211214
} catch (e: GetCredentialException) {
212215
Logger.e(TAG, "An error occurred", e)

appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDeviceResponseFragment.kt

+17-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ import androidx.navigation.fragment.findNavController
1919
import androidx.navigation.fragment.navArgs
2020
import com.android.identity.android.mdoc.util.CredmanUtil
2121
import com.android.identity.cbor.Cbor
22+
import com.android.identity.crypto.Algorithm
23+
import com.android.identity.crypto.Crypto
24+
import com.android.identity.crypto.EcCurve
25+
import com.android.identity.crypto.EcPublicKeyDoubleCoordinate
2226
import com.android.identity.crypto.javaPublicKey
2327
import com.android.identity.crypto.javaX509Certificates
28+
import com.android.identity.crypto.toEcPrivateKey
2429
import com.android.identity.mdoc.response.DeviceResponseParser
2530
import com.android.mdl.appreader.R
2631
import com.android.mdl.appreader.VerifierApp
@@ -76,22 +81,29 @@ class ShowDeviceResponseFragment : Fragment() {
7681
val responseJson = JSONObject(args.bundle.getString("responseJson")!!)
7782
val nonce = args.bundle.getByteArray("nonce")!!
7883
val requestIdentityKeyPair = args.requestIdentityKeyPair
84+
val requestIdentityKey = requestIdentityKeyPair.private.toEcPrivateKey(
85+
requestIdentityKeyPair.public,
86+
EcCurve.P256
87+
)
7988

8089
val encryptedCredentialDocumentBase64 = responseJson.getString("token")!!
8190
val encryptedCredentialDocument = Base64.decode(encryptedCredentialDocumentBase64, Base64.URL_SAFE or Base64.NO_WRAP )
8291

8392
val (cipherText, encapsulatedPublicKey) = CredmanUtil.parseCredentialDocument(encryptedCredentialDocument)
8493

94+
val uncompressed = (requestIdentityKey.publicKey as EcPublicKeyDoubleCoordinate).asUncompressedPointEncoding
8595
val encodedSessionTranscript = CredmanUtil.generateAndroidSessionTranscript(
8696
nonce,
8797
requireContext().packageName,
88-
CredmanUtil.generatePublicKeyHash(requestIdentityKeyPair.public)
98+
Crypto.digest(Algorithm.SHA256, uncompressed)
8999
)
90100

91-
val credmanUtil = CredmanUtil(requestIdentityKeyPair.public, requestIdentityKeyPair.private)
92-
val encodedDeviceResponse = credmanUtil.decrypt(cipherText,
93-
encapsulatedPublicKey as ECPublicKey,
94-
encodedSessionTranscript)
101+
val encodedDeviceResponse = Crypto.hpkeDecrypt(
102+
Algorithm.HPKE_BASE_P256_SHA256_AES128GCM,
103+
requestIdentityKey,
104+
cipherText,
105+
encodedSessionTranscript,
106+
encapsulatedPublicKey)
95107

96108
val parser = DeviceResponseParser(encodedDeviceResponse, encodedSessionTranscript)
97109
val deviceResponse = parser.parse()

gradle/libs.versions.toml

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
org-jetbrains-kotlin-jvm = "1.8.20"
4949
accompanist-permissions = "0.34.0"
5050
ktlint = "12.1.0"
51+
tink = "1.9.0"
5152

5253
[libraries]
5354
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
@@ -126,6 +127,7 @@
126127
play-services-tasks = { module = "com.google.android.gms:play-services-tasks", version = "18.0.2" }
127128

128129
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist-permissions"}
130+
tink = { group = "com.google.crypto.tink", name = "tink-android", version.ref = "tink"}
129131

130132
ktlint-gradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" }
131133

identity-android/build.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ dependencies {
4040
implementation libs.volley
4141
implementation libs.kotlinx.datetime
4242
implementation libs.kotlinx.io.bytestring
43-
implementation 'com.google.crypto.tink:tink-android:1.9.0'
4443

4544
testImplementation libs.androidx.test.espresso
4645
testImplementation libs.androidx.test.ext.junit

0 commit comments

Comments
 (0)