Open vsukhoml opened 8 months ago
I think the solution here depends on the exact use-case. Do you need: (a) some pathway where you can input the provided data to keygen, so you can get a key that is always the same for the same data and different for different data, or (b) specifically a pathway where you can do this by seeding a DRBG, because you need the output key to be the exact value you would get from seeding the DRBG and then running keygen and no other bitstring will do?
If (a), then I recommend using otcrypto_hw_backed
to generate a keymgr handle for the key based on your provided data. Then all the ECC keygen operations will give you a keypair that is derived from this diversification data (specifically, passing it through KMAC-KDF as the diversification input with the keymgr state as the key).
If (b), it's a far more controversial discussion, because exposing an interface like that would leave a major footgun in the API (the caller could improperly seed the DRBG) and also substantially weaken the SCA picture. Right now OTBN pulls randomness directly from its own CSRNG instance, and Ibex cannot interfere with that pathway. If we were to include a route from Ibex's CSRNG instance, the only one that can be manually/deterministically instantiated, then we'd have to pass the seed from CSRNG to Ibex and then from Ibex to OTBN, which involves a lot more secret data passing over the bus. And if it has to be a super specific bitstring that corresponds, for example, to the first 32 bytes that come from DRBG, then we would be forced to handle those bits unmasked at least briefly when they are produced (as opposed to pulling enough bytes for two shares and assuming that the secret value is a combination of the two that we never handle directly).
I think option (a) describes what I need better, but devil is in details. What is missing in documentation @ https://opentitan.org/book/doc/security/cryptolib/cryptolib_api.html is the example of use.
Assuming I configured otcrypto_blinded_key_t
and set config.hw_backed=true
and set the keyblob as 8 words long, where the first word is the version and the remaining 7 words are the salt and called otcrypto_ecdsa_keygen
. Is it correct? As documentation for otcrypto_ecdsa_keygen
says For hardware-backed keys, the keyblob length is 0 and the keyblob pointer may be NULL
. What is the output? What happens with content of keyblob?
Does it mean that otcrypto_ecdsa_keygen
will initialize other fields of the otcrypto_blinded_key_t *private_key
and make ready to use for sign?
What it is the difference with otcrypto_ecdh_keygen
which seems to be exactly the same as function call and difference hidden in probably OTBN part? Why not to have just a single version of keygen for compatible parts?
What is the purpose version
in the first word of keyblob and why only 7 words are used? Practically this means I'd need to truncate inputs to 7 words, say by truncating hash of provided data. Also, if I need to export such key I'd need to export these 8 words, words on my own and recreate key later, right? Then export can work in the same manner - whatever is in keyblob - export it, so I don't have to have another path. Sure, it may be not practical to protect "public" data, but this data may be not public as it can be a result of derivation/DRBG with other secrets mixed it.
You've mentioned KMAC-KDF is used for this salt - why not to apply it to arbitrary sized keyblob?
As for option (b) as a path I'm not seeking on how to inject randomness to CSRNG, but rather a pathe to load randomness from memory. It will become just an input argument.
Practically I think the major difference for me to adapt to is more paths to support in key gen. Say, in current implementation we follow same idea as TPM2 - construct DRBG in some way and then from that DRBG generate whatever is needed. We have a way to seed DRBG either with provided data (deterministic), hw-backed way where part comes from HW, part from inputs, and from other "primary" DRBG which is seeded from entropy source to get fully random keys. This primitive handles most kinds of keygen. On OT it is more complicated due to direct connection of CSRNG and certain design decisions which prohibits "software" DRBG on OTBN or having Ibex core to drive keygen on OTBN by generating candidates in the loop.
Another use case is constructing key from TPM2 KDFa for cases where it is mandated to be this specific way. This practically means a need to import key created externally even though KDFa itself can use hw-backed key, but this is outside of scope for this issue.
I think option (a) describes what I need better, but devil is in details. What is missing in documentation @ https://opentitan.org/book/doc/security/cryptolib/cryptolib_api.html is the example of use.
This is a good point, we should probably add some usage examples there for hardware-backed keys especially. For now, the test code might be a helpful reference:
https://github.com/lowRISC/opentitan/blob/20efb67f6bcea540fb6d82568d5f9f6cbaa36a2f/sw/device/tests/crypto/ecdsa_p256_functest.c#L62-L63
https://github.com/lowRISC/opentitan/blob/20efb67f6bcea540fb6d82568d5f9f6cbaa36a2f/sw/device/tests/crypto/ecdsa_p256_sideload_functest.c#L74
(First one is a random key, second is a hardware-backed key. Other files under sw/device/tests/crypto
cover other algorithms and parameters.)
Assuming I configured
otcrypto_blinded_key_t
and setconfig.hw_backed=true
and set the keyblob as 8 words long, where the first word is the version and the remaining 7 words are the salt and calledotcrypto_ecdsa_keygen
. Is it correct? As documentation forotcrypto_ecdsa_keygen
saysFor hardware-backed keys, the keyblob length is 0 and the keyblob pointer may be NULL
. What is the output? What happens with content of keyblob? Does it mean thatotcrypto_ecdsa_keygen
will initialize other fields of theotcrypto_blinded_key_t *private_key
and make ready to use for sign?
The "keyblob length is 0" comment is wrong, that was the old format for hardware-backed keys and got changed a while ago but the comment was left in by mistake. I'll fix that! The "8 words long" version is correct.
What it is the difference with
otcrypto_ecdh_keygen
which seems to be exactly the same as function call and difference hidden in probably OTBN part? Why not to have just a single version of keygen for compatible parts?
Do you mean what is the difference between ECDSA and ECDH keygen? Nothing algorithmically, but the function ensures the key mode is different because iirc you're not supposed to cross-use keys between ECDSA and ECDH.
What is the purpose
version
in the first word of keyblob and why only 7 words are used? Practically this means I'd need to truncate inputs to 7 words, say by truncating hash of provided data. Also, if I need to export such key I'd need to export these 8 words, words on my own and recreate key later, right?
You wouldn't be able to export or re-create such a key; the key derivation key in the KDF is the internal state of keymgr, so it by definition can't be constructed outside of OT firmware.
Then export can work in the same manner - whatever is in keyblob - export it, so I don't have to have another path. Sure, it may be not practical to protect "public" data, but this data may be not public as it can be a result of derivation/DRBG with other secrets mixed it.
I didn't understand what you're suggesting in this part, do you mind rephrasing?
You've mentioned KMAC-KDF is used for this salt - why not to apply it to arbitrary sized keyblob?
Hardware constraints; the keymgr only has 8 registers for diversification data.
Practically I think the major difference for me to adapt to is more paths to support in key gen. Say, in current implementation we follow same idea as TPM2 - construct DRBG in some way and then from that DRBG generate whatever is needed. We have a way to seed DRBG either with provided data (deterministic), hw-backed way where part comes from HW, part from inputs, and from other "primary" DRBG which is seeded from entropy source to get fully random keys. This primitive handles most kinds of keygen. On OT it is more complicated due to direct connection of CSRNG and certain design decisions which prohibits "software" DRBG on OTBN or having Ibex core to drive keygen on OTBN by generating candidates in the loop.
As you say, the "DRBG as argument" strategy is not very compatible with OTBN's isolated CSRNG instance. This isolation has a lot of security benefits in my opinion, but I understand that it causes some adaptation issues when you are trying to work with a codebase that assumes you can pass a DRBG as an argument.
For ECDSA and ECDH keys, however, I think we are lucky and there's a good solution here. The key generation algorithm is extremely simple. Given a DRBG it can be implemented in a three lines of code using the method in FIPS 186-5, section A.4.3:
do {
x = drbg.generate(len) // len is constant per-curve, e.g. 32 bytes for P-256
} while (x <= 1 || x >= n); // n is the curve order, also constant per-curve
Perhaps it makes sense, then, that in special cases of ECDSA/ECDH keygen where you need to do something specific, you could simply generate the key outside of the cryptolib? This does require a way for you to import the key, which is not yet implemented but is planned: https://github.com/lowRISC/opentitan/issues/20762
We could prioritize implementing the import function if it's blocking for you; it's not hard to implement, just one of many smaller tasks that are always limited by the person-hours available.
Thanks for explaining and updating the docs!
Do you mean what is the difference between ECDSA and ECDH keygen? Nothing algorithmically, but the function ensures the key mode is different because iirc you're not supposed to cross-use keys between ECDSA and ECDH.
I see. We have same functionality on higher level, so now I'd need to add a switch on what function to call based on key purpose. Looking at current TPM2 implementation it seems that CryptEccNewKeyPair
doesn't distinguish what it creates, so practically I'd either relax this requirement or make it a late choice with options:
1) Provide an API to set the purpose after key is created as a one-way function, so can be used when purpose is determined.
2) Make otcrypto_ecdsa_sign
or otcrypto_ecdh
to set the key purpose, so that after otcrypto_ecdh
was called once for this key, otcrypto_ecdsa_sign
will fail.
You wouldn't be able to export or re-create such a key; the key derivation key in the KDF is the internal state of keymgr, so it by definition can't be constructed outside of OT firmware.
I need to recreate key on same device and same state. If it is not the case, then what is purpose of hw backed keys? They behave same as random.
I didn't understand what you're suggesting in this part, do you mind rephrasing?
I wanted to say that otcrypto_export_blinded_key
better to export all kinds of otcrypto_blinded_key_t
, but when looked at API I noted that it doesn't actually generate an encrypted blob with a key, but return key in two shares, so encryption to be done on my end. Is it possible to just dump content of otcrypto_blinded_key_t
to flash and later restore, may be at different location in memory? Or keyblob is part of checksum so it won't work?
Practically I need a way to serialize a key blob, preferably in encrypted way, but practically I can add encryption on top, in such a way that I'll be able to recreate that key on the same device.
Hardware constraints; the keymgr only has 8 registers for diversification data. Then what is the "version"? Can I use all 8 registers for the diversification data?
As you say, the "DRBG as argument" strategy is not very compatible with OTBN's isolated CSRNG instance.
Since CSRNG state can't be exported/imported(?), it is useless outside of "atomic" use cases inside cryptolib and the model of use may look like following: provide all inputs for CSRNG - e.g. otcrypto_blinded_key_t
serving as entropy + arbitrary diversification data. Within every call to keygen - instantiate CSRNG with provided data, run a keygen, uninstantiate it. The way how otcrypto_drbg_instantiate
, etc API is defined is unusable for other cases unfortunately, as it is not possible to specify what context should be updated - it is just one. Not sure if reading internal state via https://opentitan.org/book/hw/ip/csrng/doc/registers.html#int_state_val can be used to reload state back, but practically it looks like the only way to use CSRNG is as part of keygen command. There are no details in documentation how otcrypto_drbg_instantiate
interplays with all kinds of keygen. I really try to avoid implicit state as a parameter due to hard to find issues unless it is guarded by some kind of sw lock.
Perhaps it makes sense, then, that in special cases of ECDSA/ECDH keygen where you need to do something specific
Yes, it may make sense to change a logic and feed candidates externally. This is how we implement it currently for all algorithms which require testing key candidates.
Besides ECDSA/ECDH it seems I need a DRBG with a state. Will create a separate issue once clarify if I can not workaround it.
I see. We have same functionality on higher level, so now I'd need to add a switch on what function to call based on key purpose. Looking at current TPM2 implementation it seems that
CryptEccNewKeyPair
doesn't distinguish what it creates, so practically I'd either relax this requirement or make it a late choice with options: ...
I can see it making sense for us to have one function call for ECDSA/ECDH keygen; the caller already has to set the mode in the blinded key configuration, so we could accept either mode in keygen and still produce a key that's limited to one mode.
You wouldn't be able to export or re-create such a key; the key derivation key in the KDF is the internal state of keymgr, so it by definition can't be constructed outside of OT firmware.
I need to recreate key on same device and same state. If it is not the case, then what is purpose of hw backed keys? They behave same as random.
I think we've crossed wires here -- what did you mean by "export"? You can't export the actual bits of the key, but you can save the entire otcrypto_blinded_key_t
handle so you can recreate the key later. On the same device with the same state, the same handle gives you the same key. You just can't reconstruct the same key somewhere totally different to e.g. run AES decrypt on a different device.
I wanted to say that
otcrypto_export_blinded_key
better to export all kinds ofotcrypto_blinded_key_t
, but when looked at API I noted that it doesn't actually generate an encrypted blob with a key, but return key in two shares, so encryption to be done on my end. Is it possible to just dump content ofotcrypto_blinded_key_t
to flash and later restore, may be at different location in memory? Or keyblob is part of checksum so it won't work? Practically I need a way to serialize a key blob, preferably in encrypted way, but practically I can add encryption on top, in such a way that I'll be able to recreate that key on the same device.
The export
function is to remove a key from the library so it can be used elsewhere in other libraries or devices, which is not always possible. For encrypted export, I recommend otcrypto_aes_kwp_wrap
and otcrypto_aes_kwp_unwrap
; these are not actually raw AES-KWP, they translate between blinded keys and bytes. At some point I meant to just move these to key_transport.h
and call them otcrypto_blinded_key_wrap
and otcrypto_blinded_key_unwrap
to make their purpose clearer, but so far I never found the time.
Hardware constraints; the keymgr only has 8 registers for diversification data. Then what is the "version"? Can I use all 8 registers for the diversification data?
The version is a separate register. The cryptolib automatically inserts the key mode as the last word of diversification, so that the same hardware-backed key can't be used across different algorithms/modes. This way different modes = different key, always.
Since CSRNG state can't be exported/imported(?), it is useless outside of "atomic" use cases inside cryptolib and the model of use may look like following: provide all inputs for CSRNG - e.g.
otcrypto_blinded_key_t
serving as entropy + arbitrary diversification data. Within every call to keygen - instantiate CSRNG with provided data, run a keygen, uninstantiate it. The way howotcrypto_drbg_instantiate
, etc API is defined is unusable for other cases unfortunately, as it is not possible to specify what context should be updated - it is just one. Not sure if reading internal state via https://opentitan.org/book/hw/ip/csrng/doc/registers.html#int_state_val can be used to reload state back, but practically it looks like the only way to use CSRNG is as part of keygen command. There are no details in documentation howotcrypto_drbg_instantiate
interplays with all kinds of keygen. I really try to avoid implicit state as a parameter due to hard to find issues unless it is guarded by some kind of sw lock.
Most keygen operations will check that the CSRNG is in a FIPS-compatible state and fail if it's not. Again, this is by design, because the cryptolib doesn't trust the caller to manage the DRBG state properly. You can create a manually-seeded DRBG and use it to generate random numbers for your own use, and you can import keys that you've created that way, but you cannot run any cryptolib routine that needs strong randomness without the CSRNG in the correct state because we thought it was too easy to mess that up without realizing.
The CSRNG block (rightly so, I think) does not allow its internal state to be saved and then restored. If you need to maintain multiple DRBGs and manipulate their states directly, I recommend a software implementation, or you could always store some manual seeds and a log of operations as the "DRBG state" and re-instantiate each time to bring CSRNG to the same state. Admittedly I still don't really understand why you would need this, though.
As for other key generations, I'll note that the full list of private key generations in cryptolib is:
generate
call)generate
call, as discussed above)generate
call)generate
, which can always be with a manually-instantiated or software DRBG if you wish.I think we've crossed wires here -- what did you mean by "export"?
Ability to produce a potentially encrypted blob with the something to store it on flash or on the host, which would allow to recreate this key on the same device. For not hw backed keys it would be an original key encrypted with device specific key encryption key. For hw-backed key it will include diversification seeds either encrypted or not. Ideally it should also include integrity property (like GCM or KWP).
You can't export the actual bits of the key, but you can save the entire otcrypto_blinded_key_t handle
I don't need actual bits. I need something that would allow me to recreate key to persist on flash or on the host after it is created on device.
You just can't reconstruct the same key somewhere totally different to e.g. run AES decrypt on a different device.
This is what is expected from hw_backed key.
this is by design, because the cryptolib doesn't trust the caller to manage the DRBG state properly.
I see this as a design which is not very compatible with real applications, unfortunately. On the current chip we use there is HW DRBG which we left mostly unused (there are only limited cases where it can be used) due to restricted capabilities in what sizes of entropy & additional data it can accept and most importantly - context switching. So we opted to software DRBG which used HW HMAC to implement it. To make it applicable it should have:
The CSRNG block (rightly so, I think) does not allow its internal state to be saved and then restored.
Yes, confirmed that too. But it may be plausible to improve it - say provide a way to export context encrypted with hw-backed key, so that it can be reloaded back. TPM2 use DRBG objects as a basis for key gen. In the use case we have - we typically prepare DRBG object with required data for the key (derivation parent, diversification data) which can be of different sizes, likely more than 256 bit. We use reseeding to add more diversification as needed after DRBG was created. And then invoke key gen. But after key is created we dispose DRBG. Unfortunately though, it is not "atomic" operation and turning this to "atomic" - provide all data to the time of key gen is a big refactoring.
So, it seems SW DRBG and its support in the APIs is a way to go. I like the property of CSRNG that testing for candidates, e.g. for RSA can be done fully on OTBN, so if adding a way to store/restore context of CSRNG is doable - it would be a nice features.
Ability to produce a potentially encrypted blob with the something to store it on flash or on the host, which would allow to recreate this key on the same device. For not hw backed keys it would be an original key encrypted with device specific key encryption key. For hw-backed key it will include diversification seeds either encrypted or not. Ideally it should also include integrity property (like GCM or KWP).
Okay, so it sounds like otcrypto_aes_kwp_wrap
is exactly what you need, no? It uses AES-KWP to encrypt a blinded key, which may be hw-backed or not (wrap
doesn't care; it encrypts the keyblob the same way regardless of whether it represents the actual key bits or the diversification).
I see this as a design which is not very compatible with real applications, unfortunately. On the current chip we use there is HW DRBG which we left mostly unused (there are only limited cases where it can be used) due to restricted capabilities in what sizes of entropy & additional data it can accept and most importantly - context switching. So we opted to software DRBG which used HW HMAC to implement it.
It sounds like you already have a software DRBG implemented. Given what I outlined above about how key generation operations can pretty much all be reduced to simple DRBG ops, why can you not just use that instead of calling the otcrypto keygen operations when you need a specifically seeded DRBG?
TPM2 use DRBG objects as a basis for key gen.
I looked at the TPM2 spec to try and understand this, but I can't find any evidence there of specific DRBG states being passed around in key generation. I don't find the spec very readable, so I'm probably missing it, but as far as I can tell you create keys with TPM2_Create()
, which generates a random key (so the CSRNG would be fine) or TPM2_CreatePrimary()
, which uses a KDF and not a DRBG. I'm looking at Part 3: Commands under TPM2_Create
, section 12.1.1:
_When a value is indicated as being TPM-generated, the value is filled in by bits from the RNG if the command is TPM2_Create() and with values from KDFa() if the command is TPM2CreatePrimary().
None of the arguments listed there seem to correspond to a DRBG state either. Where does the DRBG object that you mentioned come in?
Okay, so it sounds like otcrypto_aes_kwp_wrap is exactly what you need
Probably. Yes, I need to turn blinded key into a blob, encrypted with (other) hw-backed key, such that it can be stored elsewhere, but later be restored on the same device.
It sounds like you already have a software DRBG implemented... why can you not just use that instead of calling the otcrypto keygen operations when you need a specifically seeded DRBG?
I'm confused with implicit state transfers in OT cryptolib. How otcrypto_drbg_instantiate
interacts with keygen functions of all kinds? Also, it seems CSRNG can practically be either in fully random state when configured from entropy (mixing internal entropy with software inputs is practically same as just internal entropy) or configured with pure software. There is a missing feature - CSRNG can't be sideloaded from KEYMGR with "DRBG seed key". So, what I can do now is to use a cascade of DRBGs:
1) SW DRBG sponges all inputs relevant to application, e.g. TPM. I can also mix in some hw-bound constant, e.g. constant encrypted or kmac'ed with hw-backed key.
2) Immediately before calling keygen I'd instantiate CSRNG with entropy/diversification data generated by SW DRBG.
This gives me a property that I can use same primitive - SW DRBG which mixes in all software specific inputs. There is no need protect it as all data is public (except for hw-bound constant). It also makes CSRNG to work in only software defined state - need full support for it in key gen. I don't like it, but this will work.
One improvement I see is seeding CSRNG with a key from KEYMGR - this way I don't need to mix hw-backed constants into SW DRBG, so less likely they will leak.
but I can't find any evidence there of specific DRBG states being passed around in key generation
TPM2 specification doesn't say anything about passing DRBG states. It is its reference implementation which does. And this is what our firmware use. DRBG state becomes a common denominator for all keygens - we seed it in some way independently, mixing all inputs of interest when they are available. And then just pass it keygen for generation of candidates.
For awareness - relates to csrng @h-filali / @vogelpi
FYI: we are currently actively working on this. @ballifatih and @vsukhoml are looking for the best way to achieve the aim based on current OT HW and latest NIST docs and will report back.
FYI: we are currently actively working on this. @ballifatih and @vsukhoml are looking for the best way to achieve the aim based on current OT HW and latest NIST docs and will report back.
I really, really don't think this is a good idea. I don't want to be harsh, but I feel I need to object more strongly to be heard here so here is my view:
In short, I very strongly object to making changes to OpenTitan hardware or software in this direction.
CC @felixmiller in case he also has opinions about the properties of OTBN's CSRNG instance; I think this is really important
Most important assumption is that security boundary is not HW + cryptolib. It extends more, and in some case are at physical boundaries of the chip. Cryptolib shall provide building blocks for implementing required functionality, implementation of these blocks shall be secure enough, but not at the expense of the functionality or complicating implementation of firmware using cryptolib.
Deterministic DRBG use case is primarily around identity keys which have to be reconstructed from seeds representing state of the hardware and seeds unique to hardware. TPM2 also expects deterministic key generation from provided template for the current owner. Another requirement is to make sure it is FIPS compliant. Due to timing constraints we need to create identity keys earlier than we can realistically go through evaluation with the lab, to get their opinion on implemented solutions. Another consideration is simplifying firmware paths for key generation. DRBG is a unified mechanism which allows separation of concerns - properties & origin of the key can be handled by DRBG construction - fresh entropy, hw-bound or fully sw defined, and actual key generation is just withdrawing and testing candidates, vs. implementing different paths for different combinations.
For asymmetric keys and NIST compliance, you can XOR the DRBG output and the sideloaded key together like we do for attestation, or use NIST's case-by-case approval process for KDF-based asymmetric keys.
This is the risk and the inconvenience. When we XOR DRBG with key manager output we have DRBG seeded same way for different keys - something we should not have. NIST might be ok with that, might be not. We need to get their opinion. Using key manager output as one of the inputs for DRBG instantiation doesn't have this issue.
Using KDF for asymmetric keys doesn't make practical sense as it complicates processing of inputs - need different paths in firmware, not applicable for all algorithms.
There is no compelling need to do this; an easy workaround exists. Elliptic curve key generation is three lines and Vadim has said he already has a software DRBG that he can use.
Exactly. Provided DRBG can be software.
Regarding:
When we XOR DRBG with key manager output we have DRBG seeded same way for different keys - something we should not have.
Can we not use the similar personalization strings for the key manager KDF as we use for the DRBG, so either both parts would change or none?
Description
There is no way to implement "deterministic" key generation from DRBG. This is a common use case for TPM2 when object is recreated from provided data. This is usually done by seeding DRBG with some entropy stored on device and additional data from key public info, and then withdrawing random candidates from DRBG.
otcrypto_ecdsa_keygen
,otcrypto_x25519_keygen
,otcrypto_ecdh_keygen
,otcrypto_ed25519_keygen
@moidx, @cfrantz, @jadephilipoom