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

Test private key parsing for commentless edge case #216

Merged
merged 1 commit into from
Apr 24, 2024

Conversation

mpalmer
Copy link
Contributor

@mpalmer mpalmer commented Apr 22, 2024

If a "PEM-like" OpenSSH format private key:

  1. has no comment;
  2. is of a length such that there is no padding bytes at the end of the "Unencrypted list of N private keys"; and
  3. is of a length such that the base64-encoded form of the key does not require any = bytes for padding;

then ssh_key::private::PrivateKey::from_openssh fails to successfully parse the key.

The root cause is base64ct (prior to RustCrypto/formats#1387) incorrectly rejecting zero-length reads when the base64-encoded buffer was empty. This wouldn't happen if:

  1. the key had a comment, because that wouldn't be a zero-length read;
  2. there were padding bytes in the "Unencrypted list of N private keys", because those padding bytes would still be in the buffer, so the buffer wasn't empty; or
  3. there were base64 = padding characters, because those would cause the decoder to believe a read would succeed (even though it wouldn't if the read was longer than zero bytes).

Debugging this was... an amusement.

The most reliable way to demonstrate this is with an ECDSA P-256 key without a comment (easily generated with ssh-keygen -t ecdsa -b 256 -C '' -N '' -f <file>), because they usually (but not always) are of an appropriate length to trigger the bug. Amusingly (cough), the existing ECDSA P-256 key in ssh-key/tests/examples/id_ecdsa_p256 is one of the outliers, as its d is 33 bytes, rather than 32, which means that key (when stripped of its comment) doesn't trigger the bug.

This PR is marked as "Draft" because while it adds a test case for the bug, which I believe is valuable, the actual fix is in base64ct, which I've referenced as a Cargo.toml patch.
Thus in its current form, presumably, this PR is not suitable for merge.
It is possible to work around the bug in base64ct by guarding reads with reader.remaining_len() > 0 tests in various places, but to me that seems far uglier than just updating base64ct and depending on a fixed version.
However, if you'd like to keep the base64ct dependency open, I can rework this PR to include guards in the appropriate spot(s).

@mpalmer
Copy link
Contributor Author

mpalmer commented Apr 24, 2024

Based on this comment, I'll be reworking this PR to not make zero length reads.

Adjust to the changes to `base64ct` that reject zero-length decode() calls as invalid.

The underlying trigger for this change was to avoid problems with decoding certain OpenSSH private keys.

If a "PEM-like" OpenSSH format private key:

1. has no comment;
2. is of a length such that there is no padding bytes at the end of the "Unencrypted list of N private keys"; ***and***
3. is of a length such that the base64-encoded form of the key does not require any `=` bytes for padding;

then `ssh_key::private::PrivateKey::from_openssh` failed to successfully parse the key.

The root cause was `base64ct` (prior to RustCrypto/formats#1387) incorrectly rejecting zero-length reads, *only* when the base64-encoded buffer was empty.
This only happened for reading empty comments, because comments are at the end of the key.
It *wouldn't* happen if:

1. the key had a comment, because that wouldn't be a zero-length read;
2. there were padding bytes in the "Unencrypted list of N private keys", because those padding bytes would still be in the buffer, so the buffer wasn't empty; *or*
3. there were base64 `=` padding characters, because those would cause the decoder to believe a read would succeed (even though it wouldn't if the read was longer than zero bytes).

Debugging this was... an amusement.

The most reliable way to demonstrate this was with an ECDSA P-256 key without a comment (easily generated with `ssh-keygen -t ecdsa -b 256 -C '' -N '' -f <file>`),
because they're *usually* (but not *always*) an appropriate length to trigger the bug.
Amusingly (cough), the existing ECDSA P-256 test key in `ssh-key/tests/examples/id_ecdsa_p256` is one of the outliers, as its `d` is 33 bytes, rather than 32, which means that key (when stripped of its comment) *doesn't* trigger the bug.
@mpalmer mpalmer marked this pull request as ready for review April 24, 2024 09:15
@tarcieri tarcieri merged commit ff20ecf into RustCrypto:master Apr 24, 2024
25 checks passed
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.

2 participants