Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

P256 keys using Secure Enclave and Android StrongBox #245

Conversation

berendsliedrecht
Copy link
Contributor

@berendsliedrecht berendsliedrecht commented May 2, 2024

This PR contains the functionality to add support for hardware-backed P256 keys on iOS and Android Using iOS Secure Enclave and Android StrongBox. This is done by using the secure-env library. iOS is accessed via Security.framework and Android via JNI. Using these features you must build the library with mobile_secure_element or askar-crypto directly with p256_hardware.

During the implementation I ran into some issues due to askar-crypto being no_std. because of this I made the rng: impl KeyMaterial optional as well as the id: &str. My initial intention was to create a structure or enum for KeyGenOps, but due to not always having access to an allocator I could not leverage Box and dynamic dispatch. If there are any solutions for this, I would be happy to change the current implementation.

askar-crypto now also has a method to get the key by id. Which ties askar-crypto directly to storage, but I could not find any other way of doing this due to the nature of Secure Elements.

Over FFI, when supplying an id to askar_key_generate, the key will be created on hardware. This seems a bit too implicit for me. My next approach would be generate a random id, uuid or something and simply pass in a boolean for hardware_backed.

Implemented that we generate an id now for secure element keys and the user just has to provide hardware_backed argument over FFI as an i8.

Still in draft for now, as I have to do some testing.

Will also include an updated python wrapper (only software as key_backend allowed) and an updated node.js wrapper. React Native will be done in #247 as that will also include the React Native Example app and move to pnpm.

@berendsliedrecht berendsliedrecht marked this pull request as draft May 2, 2024 14:12
Copy link
Contributor

@TimoGlastra TimoGlastra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Will the is_hardware_backed also be exposed over FFI?

* id is an optional argument only required for hardware-bound keys
*/
ErrorCode askar_key_generate(FfiStr alg,
int8_t hardware_backed,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of marking something as hardware_backed, should this be a more generic parameters so it's a bit more ready for future changes? E.g. "key_backend" which can take a value of secure_env, or something like strongbox? I foresee us using the same capabilities for cloud HSM, with possibly multiple backends configured, and it might be nice to make it a backend rather than strictly hardware_backed?

Just a thought, this approach seems fine as well

@TimoGlastra
Copy link
Contributor

In credo we sign based on the public key, will that also be possible with the hardware keys? Or do we need to pass the key id specifically?

@berendsliedrecht
Copy link
Contributor Author

In credo we sign based on the public key, will that also be possible with the hardware keys? Or do we need to pass the key id specifically?

Do you have an example of this code? If I can check which path it takes I can see whether it'll work or not. I don't think we can go from a public key to storage and get the HSM key. Software keys are stored as a JWK, but for secure element keys I chose to store just the id. If this is a very strong requirement I could do something like:

{
  "id": "CUSTOM_USER_DEFINED_ID",
  "public_key_base58": "bla..."
}

instead of the id alone.

@TimoGlastra
Copy link
Contributor

Could we make the id the public key? Or is it generated by the secure enclave?

this is the code in Credo, we fetch the key based on the publicKeyBase58: https://github.com/openwallet-foundation/credo-ts/blob/8f49d84d52143bd9136cebd502bd9d10b5881463/packages/askar/src/wallet/AskarBaseWallet.ts#L214

If we can't do that, it will require A LOT of effort in Credo, as we would have to introduce a new concept of keyIds (which is a good thing probably), but it will be a big breaking chagne as we pass a Key instance around on MANY levels and layers

@genaris
Copy link
Contributor

genaris commented May 6, 2024

Could we make the id the public key? Or is it generated by the secure enclave?

this is the code in Credo, we fetch the key based on the publicKeyBase58: https://github.com/openwallet-foundation/credo-ts/blob/8f49d84d52143bd9136cebd502bd9d10b5881463/packages/askar/src/wallet/AskarBaseWallet.ts#L214

If we can't do that, it will require A LOT of effort in Credo, as we would have to introduce a new concept of keyIds (which is a good thing probably), but it will be a big breaking chagne as we pass a Key instance around on MANY levels and layers

In Credo we store the key using publicKeyBase58 as its name (id). Does this new approach prevent us from specifying the id we want when creating a key in hardware?

@berendsliedrecht
Copy link
Contributor Author

Could we make the id the public key? Or is it generated by the secure enclave?

Yes! The name is not equal to the underlying Secure Element id, they are separate identifiers.

If we can't do that, it will require A LOT of effort in Credo, as we would have to introduce a new concept of keyIds (which is a good thing probably), but it will be a big breaking chagne as we pass a Key instance around on MANY levels and layers.

We can defer it for now, and possibly introduce it later, but it is not required.

In Credo we store the key using publicKeyBase58 as its name (id). Does this new approach prevent us from specifying the id we want when creating a key in hardware?

No it does not. It will work exactly the same as before. askar_key_generate now takes a key_backend argument which can be Software (default) or Secure Element. And based on that backend we generate a uuid for the Secure Element and only use that internally. After creation you can fetch the public key and use that as the name of the row.

@TimoGlastra
Copy link
Contributor

Okay perfect, sounds good :)

@swcurran swcurran requested a review from andrewwhitehead May 6, 2024 14:04
@@ -14,9 +43,11 @@ pub struct KeyParams {

/// An optional external reference for the key
#[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
pub reference: Option<KeyReference>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected that the reference field would be something like "secure_element:{key_id}" and the data field would be empty in this case, but I suppose this works too and might be better for indexing keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too set on either approach. I can leave it as-is or change it to w/e is your preferred method.

fn generate_with_rng(alg: KeyAlg, rng: impl KeyMaterial) -> Result<Self, Error>;

/// Generate a new key with an id for the given key algorithm.
fn generate_with_id(alg: KeyAlg, id: &str) -> Result<Self, Error>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be restricted to hardware backed keys, and the naming doesn't really reflect that for me. Do we need to accept the ID as an argument or could it just be generated automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the id is generated in the FFI layer. I could also defer this to askar-crypto where I replace generate_any_with_id with generate_any_hardware and generate the id there. This would also remove the uuid dependency from aries-askar, but would introduce it into askar-crypto.

I will implement it like this, but also open to other suggestions.

src/ffi/key.rs Outdated
KeyBackend::Software => LocalKey::generate_with_rng(alg, ephemeral != 0),
KeyBackend::SecureElement => {
let id = Uuid::new_v4().to_string();
LocalKey::generate_with_id(alg, &id, ephemeral != 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it appropriate to support 'ephemeral' for these keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it appropriate to support 'ephemeral' for these keys?

The underlying library does not yet implement the deletion of keys, so ATM not really no. Once it is there, it would be fine imo to support it. Otherwise we would also have to create a new FFI function for it or make it an optional argument.

@andrewwhitehead
Copy link
Contributor

Looks great so far, maybe there would be a need to inspect what backends are supported by Askar for the current platform?

@berendsliedrecht
Copy link
Contributor Author

Looks great so far, maybe there would be a need to inspect what backends are supported by Askar for the current platform?

Yes that should be quite easy based on the feature flags. I will add it to askar-crypto and expose it over ffi as askar_get_supported_key_backends(out: *mut FfiStringListHandle) -> ErrorCode.

@berendsliedrecht berendsliedrecht force-pushed the mobile-hardware-es256 branch 10 times, most recently from bd315cc to 9e9ae56 Compare May 7, 2024 12:53
@berendsliedrecht berendsliedrecht marked this pull request as ready for review May 7, 2024 13:02
@berendsliedrecht
Copy link
Contributor Author

CI is finally finished so it is ready.

Signed-off-by: Berend Sliedrecht <sliedrecht@berend.io>
@berendsliedrecht berendsliedrecht force-pushed the mobile-hardware-es256 branch from 9e9ae56 to c4425f2 Compare May 8, 2024 09:26
Berend Sliedrecht added 4 commits May 8, 2024 11:28
Signed-off-by: Berend Sliedrecht <sliedrecht@berend.io>
Signed-off-by: Berend Sliedrecht <sliedrecht@berend.io>
Signed-off-by: Berend Sliedrecht <sliedrecht@berend.io>
Signed-off-by: Berend Sliedrecht <sliedrecht@berend.io>
Comment on lines +10 to +15
test('supported backends', () => {
const backends = ariesAskarNodeJS.keyGetSupportedBackends()

expect(backends.length).toStrictEqual(1)
expect(backends).toStrictEqual(expect.arrayContaining(['software']))
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should also support returning which backends are supported for which key, but I'm als fine to add that in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would be a good one. A bit more difficult to check dynamically, but it should be fine to track that statically.

Copy link
Contributor

@TimoGlastra TimoGlastra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Maybe @andrewwhitehead can also give a final ✅ if everythings looks ok?

@berendsliedrecht berendsliedrecht merged commit 81a925b into openwallet-foundation:main May 10, 2024
30 checks passed
@berendsliedrecht berendsliedrecht deleted the mobile-hardware-es256 branch May 10, 2024 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants