Conversation
All of our `kem` implementations either use `type Error = Infallible` or use the error type exclusively for handling RNG errors. That's good, because having an error case for decapsulation introduces a potential sidechannel, which can be eliminated by instead using implicit rejection that returns a pseudorandom rejection symbol as its output. This removes the `Error` types and makes `Decapsulate::decapsulate` infallible in order to close the potential sidechannel having fallible decapsulation provides. `Encapsulate::encapsulate_with_rng` now only uses the `Result` for handling RNG errors and returns `R::Error`, which should hopefully help mitigate the concerns in #2214.
85bd8c7 to
34c2d30
Compare
|
This seems reasonable to me. Fwiw, implicit rejection now seems to be more of a byproduct of analysis rather than a real security mitigation. Future KEMs may explicitly reject during decapsulation. But we can cross that bridge when we get there. Do you have opinions on removing |
I'm curious how you avoid adaptive chosen ciphertext attacks if there is a timing sidechannel around rejection. It doesn't seem like that's the sort of thing that paper is concerning itself with though, skimming it the conclusions seem to be mostly in regard to the security proof. I imagine with future KEMs there's going to be some kind of risk/benefit analysis in regard to implementing explicit rejection which will involve quite a bit of debate, so I agree we can cross that bridge when we get there.
We've generally tried to support both Currently this supports an infallible I'm going to go ahead and merge this and maybe we can circle back on |
Sounds good!
There's no side-channel. Say a KEM does Fujisaki-Okamoto with rejection, ie, it checks that the ciphertext is well-formed via re-computation, and rejects if not. In this setting, decapsulation fails if and only if the encapsulator deviated from the encapsulation algorithm. The encapsulator already knows they deviated, so that's not an interesting bit of information. What is leaky here? |
|
Rejecting a malformed ciphertext seems reasonably OK, but perhaps that could be a function of Sidebar: I guess the one remaining gap in the API right now is serializing/deserializing |
You can only see if they deviated from the encapsulation algorithm if you have the decapsulation key. So this has to happen after deserialization.
Yeah, it was tough to get something that encompassed all the aspects of a KEM and also made sense and also wasn't unwieldy. So far I haven't needed (de)serialization routines as a trait, so I don't have intuition for what should be covered. |
|
@rozbb I see. Well I guess the good news is |
|
We can always add a |
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
Companion PR to RustCrypto/traits#2216 The traits were changed to be infallible, since all of the current implementations are, and it's a pleasure to work with. However, things get trickier if we want a byte-oriented interface for ciphertexts/shared secrets as is proposed in RustCrypto/traits#2220 since `dhkem` needs decapsulation to handle point validation, which is fallible. Everything except `dhkem` can support infallible decapsulation, though.
Companion PR to RustCrypto/traits#2216 The traits were changed to be infallible, since all of the current implementations are, and it's a pleasure to work with. However, things get trickier if we want a byte-oriented interface for ciphertexts/shared secrets as is proposed in RustCrypto/traits#2220 since `dhkem` needs decapsulation to handle point validation, which is fallible. Everything except `dhkem` can support infallible decapsulation, though.
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
Companion PR to RustCrypto/traits#2216 The traits were changed to be infallible, since all of the current implementations are, and it's a pleasure to work with. However, things get trickier if we want a byte-oriented interface for ciphertexts/shared secrets as is proposed in RustCrypto/traits#2220 since `dhkem` needs decapsulation to handle point validation, which is fallible. Everything except `dhkem` can support infallible decapsulation, though.
Companion PR to RustCrypto/traits#2216 The traits were changed to be infallible, since all of the current implementations are, and it's a pleasure to work with. However, things get trickier if we want a byte-oriented interface for ciphertexts/shared secrets as is proposed in RustCrypto/traits#2220 since `dhkem` needs decapsulation to handle point validation, which is fallible. Everything except `dhkem` can support infallible decapsulation, though.
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `KemParams` trait which defines two associated `ArraySize`s: - `KemParams::CiphertextSize`: size of the ciphertext - `KemParams::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
Switches from `TryCryptoRng` back to `CryptoRng` for `encapsulate_with_rng`. We originally switched to #2049 with the rationale that the whole trait was fallible anyway, so we might as well handle the RNG errors. But then in #2216 we made the rest of the trait infallible, only using fallibility for the RNG. `Decapsulate` is also now fully infallible, but for cases where we need to handle errors there's a `TryDecapsulate` trait. Prospectively we could do the same thing here, and have a fallible `TryEncapsulate` trait that uses `TryCryptoRng` and handles RNG errors. This PR doesn't attempt to add one because it has some trait design issues around how we convert RNG errors into KEM-specific error types. Closes #2214
Switches from `TryCryptoRng` back to `CryptoRng` for `encapsulate_with_rng`. We originally switched to #2049 with the rationale that the whole trait was fallible anyway, so we might as well handle the RNG errors. But then in #2216 we made the rest of the trait infallible, only using fallibility for the RNG. `Decapsulate` is also now fully infallible, but for cases where we need to handle errors there's a `TryDecapsulate` trait. Prospectively we could do the same thing here, and have a fallible `TryEncapsulate` trait that uses `TryCryptoRng` and handles RNG errors. This PR doesn't attempt to add one because it has some trait design issues around how we convert RNG errors into KEM-specific error types. Closes #2214 (and see also that issue for the problems around error type conversions)
All of our
kemimplementations either usetype Error = Infallibleor use the error type exclusively for handling RNG errors.That's good, because having an error case for decapsulation introduces a potential sidechannel, which can be eliminated by instead using implicit rejection that returns a pseudorandom rejection symbol as its output.
This removes the
Errortypes and makesDecapsulate::decapsulateinfallible in order to close the potential sidechannel having fallible decapsulation provides.Encapsulate::encapsulate_with_rngnow only uses theResultfor handling RNG errors and returnsR::Error, which should hopefully help mitigate the concerns in #2214.For end users,
Encapsulate::encapsulatenow provides infallible encapsulation using the system RNG.