-
Notifications
You must be signed in to change notification settings - Fork 30
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
Direct support for PEM-decoding of this crate's types #53
Conversation
adedf4a
to
54d7ca9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I like this 👍 I didn't have too much to add over what Djc has already commented.
One meta-question: what sort of testing have you done for the constant-time properties of the new base64 decoder for the secret data paths? I know this is famously very difficult to nail down concretely when the compiler has so much power to optimize the code differently based on a number of properties, but is there some sanity checking we could do at this stage to gain more confidence? Maybe you already have?
Good question! During development I did look at the disassembly of The other thing I tried (just now, in fact) was a test like: #[test]
fn decode_secret_does_not_branch_or_index_on_secret_input() {
// this is using the same theory as <https://github.com/agl/ctgrind>
use crabgrind as cg;
if matches!(cg::run_mode(), cg::RunMode::Native) {
std::println!("SKIPPED: must be run under valgrind");
return;
}
let input = b"aGVsbG8gd29ybGQh".to_vec();
cg::monitor_command(format!(
"make_memory undefined {:p} {}",
input.as_ptr(),
input.len()
))
.unwrap();
let mut output = [0u8; 32];
let output = decode_secret(&input, &mut output).unwrap();
cg::monitor_command(format!(
"make_memory defined {:p} {}",
output.as_ptr(),
output.len()
))
.unwrap();
std::println!("{output:?}");
} But this is messy: it confirms there are no branches inside Verifying just #[test]
fn codepoint_decode_secret_does_not_branch_or_index_on_secret_input() {
// this is using the same theory as <https://github.com/agl/ctgrind>
use crabgrind as cg;
let input = [b'a'];
cg::monitor_command(format!(
"make_memory undefined {:p} {}",
input.as_ptr(),
input.len()
))
.unwrap();
let out = CodePoint::decode_secret(input[0]);
} And that passes. If you println
(As expected, since that function definitely will contain a computed jump or array index on the secret input.) |
9c06637
to
a76ad42
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's some more feedback (almost universally small nits, sorry!) from reviewing this branch to remind myself where we left off.
24a4f6a
to
97ef98d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool!
Also noticed that rustls/pemfile#55 is taking this as a dep w/ a git patch but didn't need to change the targetted version. I think this branch could use a |
4c04e90
to
9b34632
Compare
That looks good, but I've been trying to avoid relying on the private key type autodetection -- I think it should be reserved for cases where someone has some opaque bytes containing a private key (but no type information), and need to determine the type to make a It seems odd for With that said, I think you've avoided the earlier issue I had with the trait (by publicising the iterator types) so there's certainly a happy medium to be found here. |
Since
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good direction!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping an eye on this branch as it evolves. Just had a few small things to surface from my catch-up pass:
addressed those comments in the latest commit. i'll give some space for any extra comments, and then work on the commit history (which should address the remaining comments) |
I think I'm satisfied with the direction here, would like to do a last pass after commit history cleanup. Would maybe suggest trying to rebase the downstream PR in rustls-pemfile before cleaning up commits here. |
That is done, and I've addressed the comments over there. |
6cd7872
to
dddfb8d
Compare
OK, I think the commit history is a bit more sensible. I did a docs and doctest pass and left that separate for now, but would squash that into the "directly support PEM decoding" commit. But that probably deserves a careful look in case I a word. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tidied up commit history & the updated rustdoc comments all look good to me. I think where this API landed is a really nice end-state. Great work 🌠
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So do we now end up with two different Item
types across this crate and rustls-pemfile? Why is that necessary/helpful? I thought it might be because of inherent methods on Item
, but it looks like Item
has no public inherent methods in 2.1.3?
(I also wonder if we should not have Item
in this crate at all, or maybe make it #[doc(hidden)]
for now in favor of the newer API.)
#[cfg(feature = "alloc")] | ||
impl PemObject for PrivateKeyDer<'static> { | ||
fn from_pem(kind: SectionKind, value: Vec<u8>) -> Option<Self> { | ||
match kind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to call this out specifically: should we validate here that the DER is in the correct format? If we don't, I suppose rustls is going to error out at a later stage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is maintaining the status-quo. We could have a check_format()
or something on PrivateKeyDer
(extracts the data, guesses the format, and checks that is consistent with the used variant) but it would come short of any sort of key validation or thorough validation of the format -- I think these things need to happen at the level that fully supports DER decoding and cryptography.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would come short of any sort of key validation or thorough validation of the format -- I think these things need to happen at the level that fully supports DER decoding and cryptography.
Not sure I agree? The earlier we can signal errors, the more context might be available to the user, especially in this case of format confusion that I imagine would be tricky for inexperienced users. But, it is the status quo so no need to block this PR on it.
- base64.rs: build, msrv & clippy fixes - pem.rs: fix location of types
Unfortunately existing test cases weren't expanded to cover this new API, so the preexisting `different_line_endings` test case didn't work at all. A later commit instates test coverage for this change.
The `pem::PemObject` trait is the high-level entry point into this, and allows (eg) an iterator of `CertificateDer`s to be created from a filename (amongst other options). This caters to types that are typically plural (certificates, CRLs) and ones that are singular (keys) -- the later with APIs that select the first matching object or error if there are none. At a lower level, there are iterator-based APIs for consuming all the supported PEM sections in a file, stream or slice in one pass. Simple types who have a one-to-one mapping between PEM section (ie, `CertificateDer` is only ever from a PEM `BEGIN CERTIFICATE` section) impl `pem::PemObject` via `pem::PemObjectFilter` which minimises boilerplate. `PrivateKeyDer` is more complex, and impls `pem::PemObject` itself so it can dispatch the three source PEM section types (`PRIVATE KEY`, `RSA PRIVATE KEY`, & `EC PRIVATE KEY`) to the right enum variant.
Extend zen.pem to include: - add an unknown section that is base64 - add an unknown section that is not base64 - add an SPKI Delete unused zen2.pem (which was two copies of zen.pem originally, but fell out of sync and is now unreferenced).
This incorporates the contents of the pemfile crate, including rustls/pemfile#53
rustls/pemfile#55 is a draft over there, to rebase that crate on top of this.