-
Notifications
You must be signed in to change notification settings - Fork 24
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
Support EdDSA / ed25519 signing and verification #146
Conversation
5c6427b
to
1310cde
Compare
Self::Signature: AsRef<[u8]>, | ||
{ | ||
type Signature; | ||
fn sign<R: Read>(&self, data: R) -> Result<Self::Signature, RPMError>; | ||
fn algorithm(&self) -> AlgorithmType; |
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 here's my thinking:
If we are just handing the Signer / Verifier raw bytes so that it can do the PGP key parsing, then it makes no sense to use a compile-time generic for the algorithm type. It'll figure out the algorithm type when it parses the key, and all keys are just PGP keys so regardless of the algorithm it will be handled by the same codepath, and it can just return an error if the key is of an unsupported type or can't be parsed.
I'm sure we could make this more ergonomic still, but this is as far as I wanted to go in this PR, we can continue cleaning it up later.
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.
Do we really want automatic derivation of the algorithm from the key? That's my key (no pun intended) question here. I'd imagine that a user wants to use a specific key. The compile time guarantees could still be combined.
fn sign_with_key_given(self, key: ..) {
let key = key.parse()?;
match key.algorithm() {
Algorithm::RSA => signer.sign::<Algorithm::RSA>(),
// ..
}
now this guarantees compiletime consistency and only picks the path according to the convenience route involving algorithm derivation. People that do have the knowledge of the key type statically could maintain correctness by definition.
That's my thinking. Pushing out the runtime checks as far as possible.
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.
Do we really want automatic derivation of the algorithm from the key?
I mean, there's no avoiding that, that's what rpgp
does. There's no Signature<Algorithm>
, no PublicKey<Algorithm>
, these are just types with some details that they know at runtime. So we'd just be creating an artificial difference where none actually exists currently.
That example is still doing automatic derivation of the algorithm of from the key in the exact same way - the key is parsed at runtime, we check the algorithm type to make sure it's an acceptable one, and then sign with the key. The runtime check is happening in exactly the same place, which is first.
The signature creation process isn't the part that differs by algorithm, all that uses the exact same codepath either way (I mean it does different things inside rpgp
obviously, but API wise it's the same). It's only how the signatures are added to the header that differs, but the Signer
isn't involved in that process, so I don't see how Signer::sign<A>()
would provide a real benefit. All Signer
does is output a signature from some data provided, it's not attaching the signature anywhere itself.
As a user I have a key, I want to use this key to sign the package, and I can't really think of any reasons to make it more complicated then that. None of the checks are compile-time knowable.
@@ -148,34 +156,54 @@ impl RPMPackage { | |||
#[cfg(feature = "signature-meta")] | |||
pub fn verify_signature<V>(&self, verifier: V) -> Result<(), RPMError> | |||
where |
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.
There are a few open questions:
-
If you attempt to verify the signatures of a package with no signatures, what error message should it return?
RPMError::NoSignatureFound
is used for something different (signature packet inspection), but the signature ofRPMError::VerificationError
is designed in such a way that it can't easily be used outside ofVerifier
unless we make a few more changes to that API. New error type (e.g.KeyMismatchError
, or adjust the existing ones? -
Lets say the package is EdDSA signed and I attempt to verify using a Verifier built from an RSA public key. It ought to fail (but currently it doesn't). But again,
RPMError::VerificationError
can't be used easily outside ofVerifier
. I suppose that we could force it to try to verify the signature even though we know it will fail, which would raise the error we expect? It's not a terribly satisfying solution, though.
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.
@drahnr Any feedback?
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 I would create a new one and/or re-use RPMError::NoSignatureFound
.
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 would determine the key type when parsing the key, for most keys that's possible and the just check if there is a signature tag for that key type. Error handling is not pretty if one aims for a good and helpful one: NoSuchSignatureType{ present_signatures: Vec<KeyType> }
f9b748b
to
b61f14d
Compare
@@ -475,7 +475,7 @@ impl RPMBuilder { | |||
#[cfg(feature = "signature-meta")] | |||
pub fn build_and_sign<S>(self, signer: S) -> Result<RPMPackage, RPMError> | |||
where | |||
S: signature::Signing<signature::algorithm::RSA>, |
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 don't quite understand why we revert a compile time check back to a runtime check. Instead of removing it the Algorithm
could have become a generic too. The tag id could also be returned, based on if the signature is for header or content, which could be signaled as parameter. That maintains flexibility and extensibility while not having the repetitive sign_with_key_type_{a,b,c}
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.
There was no compile-time check because you don't get to avoid the part where the key itself is of the type you want. With build_and_sign::<algorithm::RSA>
you still have to make sure the key you pass in is RSA, except that we would then need to return a runtime error when you pass in a key of the wrong type. So either way you have a runtime check, just with additional boilerplate.
That just doesn't feel helpful at all.
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.
What I tried to say, if we can avoid runtime match
es on the key type, and expect the user to provide the correct one, it allows for a more meta code representation approach, which I'd personally prefer over match
in quite a few places. The compiler can then enforce a few more things and users that know their key type for sure, can have the match
outside of rpm-rs
and we can be happy OR possibly take advantage of the type based definition of key types iff they can based on meta programming on their end, too.
Reasons pro generic based crypto:
- user can use generics to extend type saftey
- compiler help
- less
match
based clutter - avoids the
build_and_sign_...
variants which all almost do the same - could separate concerns of doing the actual signing vs the mechanics of creating the tag
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.
Imho we should not parse AND sign in the same function, but provide key types that are external to the i.e. build_and_sign
call / Signer
.
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.
There is no rpgp
type that is specific to the algorithm used. No matter what, we have to check the algorithm at runtime, because it is a runtime property of all of the crypto types involved here. We cannot avoid match
es on the key type, and we cannot expect the user to pass a correct one - we have to verify that they actually did, and that requires a runtime check.
Further, the code for doing signing is identical and the code for adding signatures and verifying them is 80% identical (and will eventually be identical when we drop some legacy) for both algorithms, so I don't personally like the idea of duplicating the code paths, making maintenance harder and bloating the size of the library.
could separate concerns of doing the actual signing vs the mechanics of creating the tag
Those are already separate though. Signer
does the signing, RPMPackage::sign()
calls the signing code on the appropriate data regions and adds the signatures to the package.
avoids the build_and_sign_... variants which all almost do the same
We can get rid of that at any time really. Just call build()
and then sign()
separately. The only reason I haven't done it yet is that IIRC there was a disconnect between passing ownership vs reference.
edit: actually there is maybe a point to keeping build_and_sign()
it allows source_date
to be reused as the signature timestamp, which removes a step. But the implementations can be merged together.
@drahnr thanks, looking forward to this feature! |
847aacd
to
1826723
Compare
@TommyLike You can feel free to review also |
0e0fe5a
to
bbe2288
Compare
@drahnr Can I merge this? |
Support having this. btw, can we verify this signature by rpm command? |
Yes, it's just a normal RPM signature and |
RPM only supports PGP signing. PGP signatures have a baked in algorithm. So there's no need to bake the algorithm into the API and duplicate a bunch of code when we can just verify at runtime that the parsed PGP signature is one of the appropriate ones, and then do the appropriate thing with it.
With probably consolidate later, but not now
And also commit the scripts to recreate them when needed
5dc5da9
to
4ecf703
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.
A few nits, I am still not particularly excited about the changeset, but I do understand RPMSIGTAG_PGP
essentially voids any const generic approaches.
Yeah I'm not against any further improvements and we should be able to simplify this further in a year once the SIGTAG_PGP handling can be purged. It ought to be only EL7 which needs it. |
📜 Checklist
--all-features
enabled