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

Add TryFrom from &[u8] and Vec<u8> to PrivateKeyDer #39

Closed
wants to merge 1 commit into from

Conversation

Alvenix
Copy link
Contributor

@Alvenix Alvenix commented Mar 20, 2024

I am not very confident about this yet.

I am mainly using asn1 decoders to learn the differences manually.

In addition, the approach is heuristic in nature because it would be hard to implement a more complex parser.

I am planning to add more test private key files.

Copy link
Member

@djc djc left a comment

Choose a reason for hiding this comment

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

Looks pretty good, thanks for picking this up!

I'll let @cpu and @ctz judge the specifics of the DER parsing.

src/lib.rs Outdated

fn try_from(key: &'a [u8]) -> Result<Self, Self::Error> {
if key.len() <= 5 || key[0] != 0x30 {
return Err("Invalid key format");
Copy link
Member

@djc djc Mar 20, 2024

Choose a reason for hiding this comment

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

Nit: I think we prefer lowercase error strings (also for the other instance).

src/lib.rs Outdated
fn try_from(key: Vec<u8>) -> Result<Self, Self::Error> {
let private_key_der = PrivateKeyDer::try_from(&key[..])?;

match private_key_der {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: would be nice to hoist the Ok() out of the match.

src/lib.rs Outdated
type Error = &'static str;

fn try_from(key: Vec<u8>) -> Result<Self, Self::Error> {
let private_key_der = PrivateKeyDer::try_from(&key[..])?;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I think it would be okay to just inline the binding here? (As in, match PrivateKeyDer::try_from(&key[..])? { .. }.)

@djc djc requested review from cpu and ctz March 20, 2024 14:57
@cpu
Copy link
Member

cpu commented Mar 20, 2024

I'll let @cpu and @ctz judge the specifics of the DER parsing.

I will try to give this a review pass shortly. Likely ~tomorrow, I have some other things ahead of it.

src/lib.rs Outdated
skip_len += key[1] - 0x80;
}

match key[skip_len as usize..] {
Copy link
Member

Choose a reason for hiding this comment

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

Not a real review, but I think this panics with this path:

  • key[0] := 0x30 and key.len() > 5 to get us here
  • skip_len := 2
  • key[1] := 0xff, say
  • key[1] >= 0x81 passes
  • skip_len += 0xff - 0x80, ie. 129
  • key[129..] would panic if key is too short

I would suggest using get(skip_len as usize..) and then adding Some() around the match arms.

Copy link
Member

Choose a reason for hiding this comment

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

In the limit, this could benefit from a little fuzzing, I suppose? Shouldn't be too hard to bring up a cargo fuzz harness...

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 did as you said. Maybe we should additionally limit skip length to small number. For example 4 bytes, as that would indicate 4GB payload. (If I understood it correctly)

Copy link
Contributor Author

@Alvenix Alvenix Mar 20, 2024

Choose a reason for hiding this comment

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

In the limit, this could benefit from a little fuzzing, I suppose? Shouldn't be too hard to bring up a cargo fuzz harness...

If it is okay to add a more complex test, I would like to use openssl to generate large number of private keys programmatically and ensure the function chooses a correct key type.

Fuzzing to detect panics is lesser concern for me (as the function is small).

Copy link
Member

@djc djc Mar 20, 2024

Choose a reason for hiding this comment

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

If it is okay to add a more complex test, I would like to use openssl to generate large number of private keys programmatically and ensure the function chooses a correct key type.

Depends on the complexity? I think several hundred lines of code would be worth it if it doesn't blow up test cycle times beyond a few minutes.

Fuzzing to detect panics is lesser concern for me (as the function is small).

Given that the code already tried to be pretty careful and a panic could still happen makes me a little less optimistic, I guess!

@Alvenix Alvenix force-pushed the add_from_bytes branch 2 times, most recently from 6a8c6ea to 58c67dc Compare March 20, 2024 16:13
@Alvenix
Copy link
Contributor Author

Alvenix commented Mar 20, 2024

I tried with more keys generated programmatically and the conversion failed. Please wait for me to fix the issues before review. (I guess the function will become more complex to cover different types)

@Alvenix
Copy link
Contributor Author

Alvenix commented Mar 20, 2024

It seems openssl bindings somehow flipped some of the formats for RSA (use pkcs1 for pkcs8 and vice versa) or at least I hope so. I will investigate this more later. I removed the key files as I added programmatic tests which passes without changing the conversion function.

Edit:

I have opened this issue at openssl crate. It seems to me that the formats are indeed flipped somehow.

@Alvenix Alvenix force-pushed the add_from_bytes branch 2 times, most recently from e8868b9 to 037cfe1 Compare March 20, 2024 17:37
src/lib.rs Outdated
Comment on lines 149 to 163
Some([0x02, 0x01, 0x00, // Version = 0
0x30, 0x0D, // Sequence of two elements, which have 13 bytes length
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, // Object Identifier for RSA Encryption (PKCS #1)
..]) => {
Ok(Self::Pkcs1(key.into()))
}

Some([0x02, 0x01, 0x01, // This is decoded as PKI header with value 1
..]) => {
Ok(Self::Sec1(key.into()))
}

Some([0x02, 0x01, 0x00, // Version = 0
..]) => {
Ok(Self::Pkcs8(key.into()))
}
Copy link
Member

Choose a reason for hiding this comment

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

At the moment PKCS#1 and PKCS#8 cases are switched. Additionally, PKCS#8 shouldn't be specific to the rsaEncryption OID. I think we should look for:

  • PKCS#8: sequence, integer 0, sequence (ie, Some([0x02, 0x01, 0x00, 0x30]))
  • PKCS#1: sequence, integer 0
  • SEC1: sequence, integer 1

I think a static set of sample/test files is nicer than an openssl dependency (even if it it's just a dev-dependency). It also has the advantage of being 100% reproducible.

Copy link
Member

Choose a reason for hiding this comment

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

There are some suitable files in https://github.com/rustls/rustls/tree/main/rustls/src/testdata (rsa*.der, eddsakey.der, nist*.der)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the moment PKCS#1 and PKCS#8 cases are switched.

Oh sorry.........

I think a static set of sample/test files is nicer than an openssl dependency (even if it it's just a dev-dependency). It also has the advantage of being 100% reproducible.

Yes I could add it but I prefer to also keep the openssl tests, as I can simply increase the iterations number and try many keys of the same algorithm to check that they are valid.

@Alvenix Alvenix force-pushed the add_from_bytes branch 3 times, most recently from 5dd8225 to f53fbcc Compare March 20, 2024 18:45
@Alvenix
Copy link
Contributor Author

Alvenix commented Mar 20, 2024

I removed openssl test and went back to static files.

Copy link
Member

@cpu cpu left a comment

Choose a reason for hiding this comment

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

I think the general approach here is workable, but I'd like to see it written with less magic values and more information about the heuristics in use and why we think they're stable signals.

In reviewing the diff I adapted your code to be closer to what I'm thinking of. Rather than deliver a bunch of review feedback I opened a second PR based on your own with what I'm thinking: #40

WDYT?

// We skip the tag and the length of the sequence
let mut skip_len = 2;

// We skip additonal length bytes
Copy link
Member

Choose a reason for hiding this comment

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

typo here:

Suggested change
// We skip additonal length bytes
// We skip additional length bytes

src/lib.rs Show resolved Hide resolved
@Alvenix
Copy link
Contributor Author

Alvenix commented Mar 21, 2024

I am closing this PR because I think the PR there is much better. We can continue discussion there.

@Alvenix Alvenix closed this Mar 21, 2024
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