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

end_entity: add verify_private_key function. #67

Closed
wants to merge 2 commits into from
Closed

end_entity: add verify_private_key function. #67

wants to merge 2 commits into from

Conversation

cpu
Copy link
Member

@cpu cpu commented May 20, 2023

This branch introduces a function on the EndEntityCert type that can be used to check if a DER encoded private key matches the EE certificate's subject public key. This resolves the user request made in rustls/rustls#618.

This is done by un-marshalling the encoded subject public key information (SPKI) of the EE certificate and performing a byte-for-byte comparison of the encoded SPKI public key with the encoded public key derived from the private key. If the two match, we consider the private key usable for the certificate.

The verify_private_key function supports the following private key algorithms and encodings (matching the supported formats used by Rustls):

Key algorithms:

  • RSA
  • ECDSA (P-256 or P-384)
  • Ed25519

Encodings:

  • PKCS8v1 (RSA, ECDSA, Ed25519)
  • PKCS8v2 (Ed25519 only)
  • PKCS1 (RSA only)
  • Sec1 (ECDSA only)

@codecov
Copy link

codecov bot commented May 20, 2023

Codecov Report

Merging #67 (e39e161) into main (d41bdc8) will increase coverage by 0.11%.
The diff coverage is 97.45%.

@@            Coverage Diff             @@
##             main      #67      +/-   ##
==========================================
+ Coverage   95.34%   95.45%   +0.11%     
==========================================
  Files          13       13              
  Lines        2662     2819     +157     
==========================================
+ Hits         2538     2691     +153     
- Misses        124      128       +4     
Impacted Files Coverage Δ
src/error.rs 25.00% <ø> (ø)
src/end_entity.rs 97.47% <94.23%> (-2.53%) ⬇️
src/der.rs 97.52% <99.03%> (+0.87%) ⬆️
src/signed_data.rs 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

src/signed_data.rs Outdated Show resolved Hide resolved
src/end_entity.rs Outdated Show resolved Hide resolved
@cpu cpu marked this pull request as draft May 20, 2023 21:51
@cpu cpu marked this pull request as ready for review May 22, 2023 17:35
src/der.rs Outdated Show resolved Hide resolved
tests/generate.py Outdated Show resolved Hide resolved
@cpu
Copy link
Member Author

cpu commented May 22, 2023

@djc I think this is ready for a review pass/your input when you have a chance. Thanks!

@cpu
Copy link
Member Author

cpu commented May 22, 2023

cpu force-pushed the cpu-rustls-618-verify-key branch from 9c17609 to 4db6f39

  • Replaced the manually generated SEC1 test cases w/ pyca cryptography generated cases (thanks to Alex for the pointer!)
  • Refactored the code soup that I had in verify_private_key previously to use smaller helper functions for the keypair parsing. I think it reads better now 🤞

@cpu cpu self-assigned this May 25, 2023
@cpu
Copy link
Member Author

cpu commented May 26, 2023

cpu force-pushed the cpu-rustls-618-verify-key branch from 4db6f39 to 0507c2f

Rebased to resolve conflicts.

src/der.rs Outdated Show resolved Hide resolved
src/end_entity.rs Outdated Show resolved Hide resolved
src/end_entity.rs Outdated Show resolved Hide resolved
cpu added 2 commits June 5, 2023 11:55
This commit introduces a function on the `EndEntityCert` type that can
be used to check if a DER encoded private key matches the
certificate's subject public key.

This is done by un-marshalling the encoded subject public key
information of the EE certificate and performing a byte-for-byte
comparison of the encoded public key with the encoded public key derived
from the encoded private key. If the two match, we consider the private
key usable for the certificate.

Care is taken to match the algorithms and encoding formats supported by
Rustls.
extractor: impl Fn(&[u8]) -> Option<K>,
cert_pub_key: &[u8],
) -> Option<Result<(), Error>> {
match extractor(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.

You can replace the outer match with let key_pair = extractor(private_key_der)?;. Also consider extracting the Some from the inner match arms.

/// * PKCS8v2 (Ed25519 only)
/// * PKCS1 (RSA only)
/// * Sec1 (ECDSA only)
pub fn verify_private_key(&self, private_key_der: &[u8]) -> Result<(), Error> {
Copy link
Member

Choose a reason for hiding this comment

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

Thinking aloud: if we restate this API as "is this the right public key for the certificate?" then we'd get some good advantages:

  • it would unduplicate all the private key handling from here,
  • webpki as a crate would maintain its "never deals with secrets" property,
  • the result would be more linker friendly (eg, a program that otherwise only uses RSA but uses this function ends up with ECDSA and ed25519 inside)

I guess that would be on the rustls side we'd add a fn public_key(&self) -> &[u8] to SigningKey and then do the key identity validation in cross_check_end_entity_cert?

Copy link
Member Author

Choose a reason for hiding this comment

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

Those are compelling advantages 👍 In retrospect I was probably overfitting Brian's suggestion on Rustls#618 by jumping straight to this design.

cert_pub_key: &[u8],
) -> Option<Result<(), Error>> {
match extractor(private_key_der) {
Some(keypair) => match cert_pub_key == keypair.public_key().as_ref() {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure this is completely correct, because neither cert_pub_key nor keypair.public_key() expresses the key type. This would mean we could compare as equal (say) a nistp256 and ed25519 key as equal if they had the same encoding, even though they semantically are never equal because they're are not in the same domain.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point. We should also be asserting the key types match somewhere in this logic. I'll give this more attention in the reworked formulation. Thanks for calling this out as a potential pitfall.

@cpu
Copy link
Member Author

cpu commented Jun 12, 2023

I think given ctz's feedback above I'm going to close this PR and look at replacing it with a reworked version that doesn't have webpki handling private keys. If that ends up being a dead end for some reason we can always re-open, but I suspect it will work out better and the end shape will be sufficiently different that it will be cleaner to start w/ a fresh branch.

Thanks for the input!

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