randombit / botan

Cryptography Toolkit
https://botan.randombit.net
BSD 2-Clause "Simplified" License
2.59k stars 567 forks source link

Creating X509 self signed CA cert via PKCS11 module #4269

Open jacobschloss opened 3 months ago

jacobschloss commented 3 months ago

I'm trying to make a self signed CA cert for a private key held on a ATECC608C-TNGTLS chip via PKCS11. ATECC608* chips only support secp256r1 ECC keys.

This chip has a factory burned master key I want to make a self/internally signed CA cert for, and 3 internal key slots for short term rotating keys. I want to make X509 certs for the rotating keys, a CA key for the master key, and sign the rotating keys's certificates with the master key. The on-chip master key also has x509 certs traceable to a Microchip Inc. root key.

Loading a key pair (eg a Botan::PKCS11::PKCS11_ECDSA_PrivateKey) and on-chip raw signing seems to be working using Microchip's cryptoauthlib's PKCS11 interface.

I am wondering if my issue is somewhat similar to https://github.com/randombit/botan/issues/3891, where the SoftHSM does not support multipart signing.

The PKCS11 provider lib only seems to support signatures in "Raw" mode without offloaded SHA-256 checksum calculation. The ATECC608C-TNGTLS supports SHA-256, but only as a separate operation and not part of the sign command which only supports signing an externally created 32b digest.

Eg, Botan::PK_Signer signer(priv_key_handle, m_rng, "Raw", Botan::Signature_Format::Standard, "pkcs11"); signing a 32byte pre-calculated digest message works, but Botan::PK_Signer signer(priv_key_handle, m_rng, "SHA-256", Botan::Signature_Format::Standard, "pkcs11"); returns a PKCS11 error.

Trying to call Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_cert_opt, *pkcs11_priv_key, "SHA-256", rng); returns the same error as a Botan::PK_Signer in SHA-256 mode. Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_cert_opt, *pkcs11_priv_key, "Raw", rng); returns a Botan error "No OID associated with name 'ECDSA/Raw'", which seems reasonable I suppose.

I can attach my full code if it helps, need to extract it to a new mini demo if needed.

I am using Botan 3.5.0, built locally from github sources.

Is there a way to get the X509 signing machinery to do the SHA-256 hash in software host side before asking the PKCS11 module to do the signature?

jacobschloss commented 3 months ago

I also was looking at a workflow using

Botan::X509::create_cert_req

and then

Botan::X509_Object::make_signed

with an external PK_Signer, but ran into similar issues with a PKCS11 error. I was planning on using Botan::X509::create_cert_req for the rotating keys which I'd then sign with the master / X509_CA key.

jacobschloss commented 3 months ago

The supported mechanism types are

0x00000250 0x00000251 0x00000252 0x00001040 0x00001041 0x00001081 0x00001082 0x00001085 0x00001086 0x00001087 0x00001088 0x0000108A 0x0000108B 0x00001104 0x00001105

eg CKM_ECDSA but not CKM_ECDSA_SHA256

randombit commented 3 months ago

Quite an interesting situation.

I think this can be made to work with the code as it stands, but it's a bit hairy

We should be able to do better here. A couple of options that come to mind off the top of my head

This might well be useful even when the device does support the hash since you don't have to send the entire message you're signing over potentially slow device links.

Alternately we could support a fallback option

This would be nicer in some ways; things would just work in the obvious way. But it eliminates being able to opt into prehashing as a performance optimization if the device does support signing with a hash, and it's also kind of magic/potentially surprising, which I like to avoid where possible.

jacobschloss commented 3 months ago
  • Define a new Private_Key type which holds internally your PKCS11_ECDSA_PrivateKey
  • This private key implements create_signature_op and returns from there a PK_Ops::Signature. Basically this outer/wrapper Signature impl would create a PK_Ops::Signature of the inner key (using "Raw"), prehash its input on the CPU, and then forward the final hash to the PKCS11 implementation.

Ok thanks - I made a custom child of Botan::PK_Ops::Signature and Botan::Private_Key, wrapped my PKCS11 object, and passed these to Botan::X509::create_self_signed_cert and was able to get it to output a cert I could pass to the Botan::X509_CA constructor.

This probably works ok for the time being. I /might/ just use the native SDK for the ATECC608 chip in a child class like this instead of going through PKCS11, it makes debugging easier although it is perhaps less generic.

I probably have some bug somewhere still, the generated certificate signature is failing verification (X509_Certificate::check_signature returns false).

I will do some more checking to make sure I am actually getting the correct data in and out of the chip / MCP's SDK to see if that is the cause of the signature verification failure.

  • If a PKCS11 signing/verification operation sees this, it knows to use a Raw mechanism with the PKCS11 library and prehashes on the CPU.

This might be nice, on pretty much any platform I'm likely to use it will be faster to hash on the host than transfer the whole message over the I2C bus.

Alternately we could support a fallback option

  • Given a request to sign using "SHA-256", try precisely that.
  • If the mechanism fails, retry with "Raw" and prehashing.

This would be nicer in some ways; things would just work in the obvious way. But it eliminates being able to opt into prehashing as a performance optimization if the device does support signing with a hash, and it's also kind of magic/potentially surprising, which I like to avoid where possible.

Yeah, I'd lean towards manual mode selection personally. I also like to avoid potential unexpected magic. Also the bus is slow enough that the latency of an extra failed sign attempt might be noticeable, or at least undesired. (At 100kbit/s, we only get to send 32 Bytes about 400 times a second, and the bus is shared with other things).

jacobschloss commented 3 months ago

Got a good verification for the signature now - The inner Botan::PK_Signer needed to use Botan::Signature_Format::DerSequence format, I had Standard.

reneme commented 3 months ago

Define some naming convention for prehashing. Say "SHA-256/Raw" or whatever.

As a side-note: We're currently working on signing support for TPM 2.0 (#3877). For that, such an agreed-upon naming convention would be beneficial as well. I.e. to switch between on-chip and software-based hashing and/or padding.

reneme commented 3 months ago

We should be able to do better here. A couple of options that come to mind off the top of my head

Apart from (or additionally to) a raw-signing naming convention, we should also be able to use PKCS11::Slot::get_mechanism_list() to figure out what the specific token is capable of and automagically select raw or on-chip hashing. That feels much better than the suggested try-and-fallback approach.