Skip to content

Conversation

@dwhjames
Copy link
Contributor

  • introduce Error enum with one and only existing error value
  • introduce specialized Result type for crate
  • rewrite references to der crate Error & Result types

@dwhjames
Copy link
Contributor Author

@tarcieri, admittedly I’m just trying to do easy/accessible things, with learning being the primary motivation. I hope this is actually useful work, and not just busy work. It it did seem that the one error value was actually rolling up a variety of distinct error categories, so I thought I’d rewrite the one and only error, before moving on to separating out the cases.

@dwhjames dwhjames force-pushed the pkcs5-error branch 2 times, most recently from ec05443 to a391e5b Compare September 15, 2021 01:02
@tarcieri
Copy link
Member

tarcieri commented Sep 15, 2021

It'd make sense if Error had more than one variant. Perhaps you can eliminate some of the map_errs that are discarding error values with From conversions?

Note that the only variants I think should be added are ones which distinguish encoding-related errors from actual cryptographic errors. I don't think you should try to categorize the various kinds of cryptographic errors or propagate information about it. That has the potential for leakage.

@dwhjames
Copy link
Contributor Author

Here were my ideas:

  1. ‘encrypt/decrypt not supported for PBES1’
  • formats/pkcs5/src/lib.rs

    Lines 89 to 90 in a391e5b

    Self::Pbes2(params) => params.decrypt_in_place(password, buffer),
    _ => Err(Error::Crypto),
  • and three other locations
  1. ‘invalid scrypt params’
  • block_size: params.r().try_into().map_err(|_| Error::Crypto)?,
    parallelization: params.p().try_into().map_err(|_| Error::Crypto)?,
  • if 1 << log_n != n {
    return Err(Error::Crypto);
    }
    scrypt::Params::new(
    log_n,
    params.block_size.into(),
    params.parallelization.into(),
    )
    .map_err(|_| Error::Crypto)
  1. ‘invalid block cipher params’
  • let cipher = Aes128Cbc::new_from_slices(encryption_key.as_slice(), iv)
    .map_err(|_| Error::Crypto)?;
  • and several other locations
  • one could argue that these could be panic’s at this point and that there could be validation at an earlier point
  1. ‘no buffer remaining for block cipher padding during encryption’
  1. ‘decryption error - invalid ciphertext size or malformed padding’
  1. ‘encryption not supported for cipher’
  1. ‘key derivation with Sha1 not enabled’
  1. ‘scrypt derivation failure’
  • let mut buffer = [0u8; MAX_KEY_LEN];
    scrypt(
    password,
    params.salt,
    &params.try_into()?,
    &mut buffer[..length],
    )
    .map_err(|_| Error::Crypto)?;
  • this also seems like a library bug thus panic, rather than a plausible runtime error — entirely prevented as long as the constants in the crate are set appropriately

@jklong
Copy link
Contributor

jklong commented Sep 15, 2021

1. ‘decryption error - invalid ciphertext size or malformed padding’


* https://github.com/RustCrypto/formats/blob/a391e5b0995ae7aa9224972749d4186625b6ec5c/pkcs5/src/pbes2/encryption.rs#L97

* and several other locations

I'd be careful with this one in particular. Once you start identifying information around padding you're introducing the possibility of a padding oracle. That's a large potential foot-gun if the library user isn't careful about that information leaking.

@tarcieri
Copy link
Member

Going through these one-by-one:

  1. ‘encrypt/decrypt not supported for PBES1’

I think it's fine to have a variant that says PBES1 is unsupported. This will effectively be a perpetual WONTFIX.

  1. ‘invalid scrypt params’
  2. ‘invalid block cipher params’

I would really like to avoid overspecializing variants for specific algorithms, especially if they are variants that are only available via conditional compilation.

How about something like: AlgorithmParametersInvalid { oid: ObjectIdentifier }?

If you'd really like to break the cases above down, perhaps it could be KdfParametersInvalid { oid } versus CipherParametersInvalid { oid }.

  1. ‘no buffer remaining for block cipher padding during encryption’

An error case for the output buffer being too small sounds fine. I would avoid mentioning anything about padding: the real problem is that the buffer is too small.

  1. ‘decryption error - invalid ciphertext size or malformed padding’

This is the kind of error I'd personally prefer to continue to keep an opaque Crypto error. If you really want to split out errors specifically related to decryption, something like Error::DecryptFailed would be OK.

  1. ‘encryption not supported for cipher’
  2. ‘key derivation with Sha1 not enabled’

These are effectively unsupported algorithm errors. As I mentioned earlier, I'd prefer not to get too off in the weeds with algorithm-specific error variants.

Something like UnsupportedAlgorithm { oid: ObjectIdentifier } seems fine to me though.

  1. ‘scrypt derivation failure’
    this also seems like a library bug thus panic, rather than a plausible runtime error — entirely prevented as long as the constants in the crate are set appropriately

Across the RustCrypto projects we generally try to minimize any potential runtime panics, and especially in low-level libraries like this we are aiming for panic-free code. The reason is we'd like to make these libraries usable in contexts where panics are unacceptable, like OS kernels or bare metal embedded device programming.

That's not a hard rule, or an easily enforceable one (see previous link for some discussion on that), but we would like to ensure that there is a baseline level of functionality which does not require a panic handler.

@tarcieri
Copy link
Member

I'd be careful with this one in particular. Once you start identifying information around padding you're introducing the possibility of a padding oracle. That's a large potential foot-gun if the library user isn't careful about that information leaking.

Yep, exactly, avoiding this sort of leakage is exactly why we've largely kept the error types opaque.

@dwhjames
Copy link
Contributor Author

@jklong @tarcieri thanks for the great feedback.

I’ll come back with something concrete.

Copy link
Member

@tarcieri tarcieri left a comment

Choose a reason for hiding this comment

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

Looks good! I'll hold off on merging this until we're ready to make breaking changes

@tarcieri
Copy link
Member

tarcieri commented Oct 6, 2021

@dwhjames can you rebase? If so, I can merge after that

- introduce Error enum with new error cases
- introduce specialized Result type for crate
- rewrite references to der crate Error & Result types
- some collateral refactoring as well
@dwhjames
Copy link
Contributor Author

dwhjames commented Oct 9, 2021

@tarcieri, 👍, conflicts resolved.

Comment on lines 33 to 35
let algo_params_invalid_error = Error::AlgorithmParametersInvalid {
oid: params.encryption.oid(),
};
Copy link
Member

Choose a reason for hiding this comment

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

Rather than eagerly calculating this, you could make it a function on Parameters, e.g. Parameters::invalid_error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see d30f931

Comment on lines 91 to 93
let algo_params_invalid_error = Error::AlgorithmParametersInvalid {
oid: params.encryption.oid(),
};
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see d30f931


impl<'a> Pbkdf2Params<'a> {
/// Initialize PBKDF2-SHA256 with the given iteration count and salt
pub fn hmac_with_sha256(iteration_count: u16, salt: &'a [u8]) -> Result<Self, CryptoError> {
Copy link
Member

Choose a reason for hiding this comment

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

It would probably be good to leave this fallible.

I'm not sure why u16 was used for the iteration count but it unnecessarily constrains the number of iterations. Using 100,000+ should be supported.

However, if this type is changed to a u32, it would also be good to set a sanity limit on the number of iterations.

RFC8018 says the sanity limit is implementation defined:

https://datatracker.ietf.org/doc/html/rfc8018#appendix-A.2

iterationCount specifies the iteration count. The maximum iteration count allowed depends on the implementation. It is expected that implementation profiles may further constrain the bounds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see d30f931

sanity limit chosen as 100 million


impl<'a> ScryptParams<'a> {
#[cfg(feature = "scrypt")]
const INVALID_ERR: Error = Error::AlgorithmParametersInvalid { oid: SCRYPT_OID };
Copy link
Member

@tarcieri tarcieri Oct 9, 2021

Choose a reason for hiding this comment

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

This can also be replaced with a function on params.

Edit: actually, since it's private I guess this is fine. You could also use this approach on the other params structs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see d30f931

same added for Pbkdf2Params

@tarcieri tarcieri merged commit 65fbe57 into RustCrypto:master Oct 9, 2021
@tarcieri
Copy link
Member

tarcieri commented Oct 9, 2021

Thank you!

@dwhjames dwhjames deleted the pkcs5-error branch October 9, 2021 21:25
@tarcieri tarcieri mentioned this pull request Nov 15, 2021
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.

3 participants