Signing not possible with an AndroidKeyStore key that has .setUserAuthenticationRequired(true)
The AndroidKeyStore has some particularly funky stuff, and I’ve run into a brick wall trying to figuring out how to use it to sign a jws without doing some really quite nasty things.
In particular the Android HSM/TEE can (if you do setUserAuthenticationRequired(true)
when creating the key in the HSM) protect a key completely from the OS, to the point where the hardware insists that every signature operation requires the user to present their biometric. (This is a pretty great thing for security, as even if the device is thoroughly compromised, not only is the attacker unable to extract the private key material, they can’t even sign something with the key without the user that owns the relevant finger/other appendage being actively involved.)
The problem arises around this little bit of code:
public byte[] sign(Key key, byte[] securedInputBytes, ProviderContext providerContext) throws JoseException
{
Signature signature = getSignature(providerContext);
initForSign(signature, key, providerContext);
try
{
signature.update(securedInputBytes);
return signature.sign();
}
catch (SignatureException e)
{
throw new JoseException("Problem creating signature.", e);
}
}
(from BaseSignatureAlgorithm.java )
In particular after the .update()
on line 6, it’s necessary to call Android’s BiometricPrompt.authenticate() passing it signature
- it then completes asynchronously and only when it calls back to the onAuthenticationSucceeded
method (once the user has provided their finger/iris/face/whatever) can you go on to call signature.sign()
to retrieve the signature.
The asynchronous nature makes it particularly tricky to do - the only solutions I’ve come up with is:
- A fairly nasty subclassing of
JsonWebStructure
to essentially expose some of it’s privates - so you do the actual underlying signature operation outside of jose4j and inject it back in - Something fairly nasty with running
jws.getCompactSerialization();
on a background thread/executor and blocking that thread whilst the BiometricPrompt part happens. - Some kind of fairly invasive looking refactor to split sign() up into a ‘pre sign step’ and ‘signature available’ step.
I’m not sure if I’ve missed some easier solution though - can you see a better one?
Comments (17)
-
repo owner -
repo owner saw the similar ask over here https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/373/biometricprompt-support-in-android too
-
Hi Brian
Thanks! Yes, I can certainly share an example project, once I have one working (for reasons currently unclear to me my current test apps seem to end up generating an invalid signature, which I wasn’t worrying about too much because one of my colleagues said he’d got everything fully working in a different way in the production full app, but further investigation revealed that app was also generating invalid signatures…).
The good news is I’m fairly certain I got the place where the biometric authentication needs to happen slightly wrong and it actually can/should/must be before the
.update()
call, which means the solution is I think easier (perhaps an API to create a signature object, then adding a new variant ofsign()
that accepts theSignature
object once it’s been biometric authorized).I’ll get back to you with a test project once I’ve actually managed to produce a valid signed JWT
-
repo owner Thanks! I look forward to it (kinda). And that is likely good news regrading the timing of the call.
-
I don’t even necessarily need something that’s working to produce a valid signed JWT. Just having the scaffolding around the android stuff would be super helpful.
-
I figured out the stupid thing I did. Should have some working (but very hacky) code for you shortly
-
and here we go: https://gitlab.com/emobix/android-biometric-signed-jwt-test
There’s a README.md with brief instructions - if I missed a step out just shout. (I’m on the idpro slack.)
The actual code is in the main activity: https://gitlab.com/emobix/android-biometric-signed-jwt-test/-/blob/master/app/src/main/java/uk/co/emobix/testapp/MainActivity.java
No judging me for the way I got this to work without changing the library please
-
Thank you! I’ll dig into this and promise no judgment.
-
repo owner Kinda clever what you did to get it to work without changing the library
But ugh… the abstractions don’t line up quite right. I’m struggling to come up with an approach that’s not too ugly. I’m working on it though.
-
repo owner 958d339 introduces a new
prepareSigningPrimitive
method onJsonWebSignature
that gives access to the underlyingSignature
instance, which should much more easily allow for setting up theBiometricPrompt
with theSignature
. Using it might look something like this:jws.setPayload(...); jws.setKey(privateKey); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256); CryptoPrimitive cryptoPrimitive = jws.prepareSigningPrimitive(); Signature signature = cryptoPrimitive.getSignature(); biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(signature));
and then in the
onAuthenticationSucceeded(...)
callback you can finish the job by callinggetCompactSerialization()
on the JWS.
-
Nice! Thanks Brian. That’s a much neater change to the library than I was fearing, I struggled to see a route that didn’t involve a major refactor but it’s actually shaken out well.
Any chance of a release soon please?
-
repo owner Some of the “internal” changes hurt my aesthetic sensibilities quite a lot but the ‘external’ API change didn’t turn out too bad at all.
I’m working on similar updates for decryption on the JWE side with a708d41 but should be able to do a release pretty soon.
-
repo owner - changed status to resolved
-
repo owner - changed status to closed
released in v0.7.3
-
reporter Thanks Brian! I’ve tried it and it works nicely.
-
-
repo owner FWIW https://github.com/b---c/Jose4jBioPromptExample is a simple little Android project that shows these new methods in action for doing signing and decryption.
- Log in to comment
Hello there @josephheenan, this is a fun one for sure. Ideally we could support this in way that’s not too awkward to use but also doesn’t break the current APIs and won’t impact folks doing things the regular old way. I’m honestly not sure how to accomplish that though. And think I’d need to spend some time digging into it (fighting with it). Would you by chance have anything that could help bootstrap my efforts? My Android skills are pretty slim and basically amount to having Android Studio installed. Like perhaps a project you’ve been using to experiment that would show usage of the Android HSM/TEE and Bio Prompt stuff and be runnable in the emulator. That’d help me get to a place where I can play around with it and try and come up with something clean.