Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Signing and verification traits - #7

Merged
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits
Mar 26, 2019
Merged

Signing and verification traits#7
tarcieri merged 3 commits into
masterfrom
sign-and-verify-traits

Conversation

@tarcieri

Copy link
Copy Markdown
Member

This PR contains a number of commits adding a set of traits for creating and verifying digital signatures.

I would suggest reviewing them commit-by-commit:

  • 1f7efa6: Sign trait
  • 9fd884c: Verify trait
  • c438c2c: SignDigest and VerifyDigest
  • f5e2db8: SignSha256, SignSha384, SignSha512, VerifySha256, VerifySha384, VerifySha512

All traits are bounded by Send + Sync to ensure signers and verifiers are thread safe. Libraries which provide access to HSMs will need to e.g. Mutex guard access to the underlying device.

Trait for producing digital signatures
Support for signing and verifying prehashed message `Digest`s, for use
with signature algorithms that support Initialize-Update-Finalize usage.
@newpavlov

Copy link
Copy Markdown
Member

So after IRC discussion the only change for now is to remove SHA traits, right?

@dignifiedquire
I think it will be nice to support traits from signature crate in rsa as well, so your input will be quite valuable.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

@newpavlov yes, we can try completely removing the SignSha* and VerifySha* traits for now. Instead, ECDSA signers who want a raw message which they subsequently hash (as opposed to ones which take a raw Digest) can simply implement the Sign trait, and MUST hash the message with the SHA-2 function which is the same size as the modulus.

For posterity, the reason for having separate SignSha256, SignSha384, etc. is because ECDSA supports wacky mix-and-match combinations of curve moduli and hash functions, e.g. you can use SHA-384 with P-256, or SHA-256 with P-384. However, it's not clear to me it's actually worth considering such cases (e.g. ring labels both of these combinations as "Not recommended" despite supporting them).

So for now, I agree, let's try removing them and see if we can get by without them. Worst case, if someone does show up clamoring for the mix-and-match combinations, we can (potentially) add them back, but let's cross that bridge when we get there.

@dignifiedquire

dignifiedquire commented Mar 25, 2019

Copy link
Copy Markdown
Member

Hmm I am not entirely sure how this would fit into the requirements for RSA. It needs to express the following things

  1. sign vs sign with blinding (with blinded signing an Rng needs to be provided)
  2. specify a Padding Scheme
  3. getting the ASN1 prefix for the chosen hash
  4. variable length hash digests, as one needs to be able to choose the hash function at runtime in some scenarios (e.g. on my pgp implementation)
  5. knowing if the value should be hashed or not (my current api assumes it always gets the hashed digest)

@dignifiedquire

Copy link
Copy Markdown
Member

(3) could be solved by extending Digest to provide ASN1 prefixes
(5) seems to be solved by the sign_digest handling
I think (4) could be solved by doing a match on the selected hashing method.

@tarcieri

tarcieri commented Mar 25, 2019

Copy link
Copy Markdown
MemberAuthor

@dignifiedquire for RSA signatures I would suggest instantiating a signer type in your preferred way, then impling the Sign trait, ensuring the signer is set up with the user's preferred configuration prior to signing, with optional choices around how the signature is produced provided in advance.

This is more or less where we netted out around the complexities of selecting which hash function to use for ECDSA, which was previously solved by providing a trait for each with a differently named method. Instead of that, we are requiring the signer to decide that a priori.

Note that in such a scheme, it is still possible to allow the signer to select from different hash functions to perform on the input. You could either select things a priori at runtime via initializers (e.g. KeyPair::new(privkey) vs KeyPair::new_with_digest(privkey, Sha384), or by encoding the default hash function as a generic parameter with a default, e.g. KeyPair::new(privkey) versus KeyPair::<Sha384>::new(privkey), which would be more type safe.

Either way, the point is the incidental complexity around each signature algorithm can be kept out-of-band from the core signature trait.

As it were, this is how ring's signing APIs work.

(5) seems to be solved by the sign_digest handling

Yep! And really I think "to prehash or not to prehash" is the only decision that actually needs to be handled by the signing traits, as many ECDSA libraries (as well as RSA) only accept a prehashed digest at input, and leave how to calculate that as an exercise to the user. So it's convenient for users of these libraries to be able to leverage Digest for that prehashing out-of-the-box.

I imagine we can do some blanket Sign impls for types which implement SignDigest, possibly using a marker trait ala the following (not sure these are the greatest names, but you get the idea):

traitSignUsingDigest{typeAlgorithm:Digest;}impl<D,S,T>Sign<S>forTwhereD:DigestS:Signature,T:SignDigest<D,S> + SignUsingDigest{fnsign(&self,msg:&[u8]) -> Result<S,Error>{self.sign_digest(T::Algorithm::new().chain(msg))}}

@dignifiedquire

Copy link
Copy Markdown
Member

I like that, it makes the key pair a bit more complicated, but other than that this should work out. In all use cases I have seen so far a key pair is only used for a single combination of params anyway.

@dignifiedquire

Copy link
Copy Markdown
Member

The only thing I am unsure about then is blinding, as I would like to preserve the ability to pass in an rng every time the method is called, instead of per key pair.

@tarcieri

Copy link
Copy Markdown
MemberAuthor

ECDSA has similar concerns around the secure RNG for nonces, however I guess I have the opposite preference and like only having to configure the RNG once.

I'm not sure how it's possible to design a least-common-denominator API which supports passing in an RNG on a per-signature basis, as many signature algorithms are deterministic and don't require one (e.g. Ed25519 or RFC 6979 deterministic ECDSA).

What's the use case for doing so?

@dignifiedquire

Copy link
Copy Markdown
Member

I honestly don't have a good use case, this was mostly an intuition I had, and the way I have seen this being handled in other places. If I really want I can always have sign_blinded method which doesn't match the trait, so I don't think this is a blocker in any case.

@tarcieri
tarcieriforce-pushed the sign-and-verify-traits branch from f5e2db8 to c438c2cCompareMarch 26, 2019 16:56
@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

I removed commit f5e2db8 (SignSha* and VerifySha*) from the PR. Will merge after the test pass.

Will submit a followup PR for the blanket impl of Sign for SignDigest after this lands.

@tarcieri
tarcieri merged commit 502c507 into masterMar 26, 2019
@tarcieri
tarcieri deleted the sign-and-verify-traits branch March 26, 2019 17:00
@newpavlov

newpavlov commented Mar 26, 2019

Copy link
Copy Markdown
Member

I wonder if we can do something like this (replace const generics with typenum for now):

traitCoreSigner{constDIGEST_SIZE:usize;typeSig:Signature;fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;fndigest_sign<D>(&self,msg:&[u8]) -> Result<Self::Sig,Error>whereD:Digest<Output=Self::DIGEST_SIZE>{self.core_sign(&D::digest(msg))}}traitSigner<S:Signature>:Send + Sync{/// Sign the given message and return a digital signaturefnsign(&self,msg:&[u8]) -> Result<S,Error>;}structCoreWrapper<C:CoreSigner,D:Digest>{ .. }impl<C:CoreSigner,D:Digest>CoreSignerforCoreWrapper<C,D>{ .. }impl<C,S,D>Signer<S>forCoreWrapper<C,D>whereC:CoreSigner,D:Digest,S:Signature + From<C::Sig>{fnsign(&self,msg:&[u8]) -> Result<S,Error>{let sig = self.digest_sign::<D>(msg)?;Ok(sig.into())}}

And same for verification.

BTW are you sure about Sign and Verify trait names? In this case Sign can be confused with noun and at least for me Signer/Verifier is easier to understand. Plus agent nouns are already used for trait names in std and ecosystem.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

@newpavlov I don't think that makes sense. It presupposes all signers will have the ability to sign both unhashed messages and digests, which is not the case (or at least, I feel very strongly about these traits being usable with all signers imaginable, be they HSMs/hardware tokens, cloud KMS services, or any existing Rust crate which may already do hashing internally)

It also adds a superfluous method in order to achieve object safety which is redundant with the method in the non-object-safe versions. All in all it seems more complicated and if it has any advantages, I'm failing to see them. I think to reach equivalence with what I have in #9 (as of 2bff7eb), you'd need to add an additional DigestSigner trait beyond all that to provide an object-safe digest signing API.

What are you trying to accomplish with this change?

BTW are you sure about Sign and Verify trait names?

Haha, as it were I used Signer and Verifier in Signatory, and would be fine with going back to them. I somewhat capriciously changed them to Sign and Verify as they are effectively single method traits.

@newpavlov

Copy link
Copy Markdown
Member

It presupposes all signers will have the ability to sign both unhashed messages and digests

No, HSMs which do hashing themselves will implement only Signer trait and not CoreSigner. In other words users usually will not use CoreSigner directly, except when they'll need to sign/verify pre-computed hash. In other words in terms of RFC 8032 CoreSigner is for "PureEdDSA" and Signer for HashEdDSA, while CoreWrapper is used for converting one into another.

Which method is superfluous here in your opinion? sign? It plays a distinctively different role from core_sign and digest_sign as it does not allow to choose Digest function and pass pre-computed hash value.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

@tarcieri

tarcieri commented Mar 26, 2019

Copy link
Copy Markdown
MemberAuthor

Which method is superfluous here in your opinion?

core_sign and sign have nearly identical method signatures, aside from the use of an associated type versus a generic:

fncore_sign(&self,msg_digest:&[u8;DIGEST_SIZE]) -> Result<Self::Sig,Error>;
fnsign(&self,msg:&[u8]) -> Result<S,Error>;

I feel like what you're doing is accomplishing less than #9 with a whole lot of added complexity. Just looking at your code I have no idea what a CoreWrapper is or what it's supposed to do, but it just feels like a bunch of glue code which doesn't need to exist in a cleaner design.

I want a clear distinction between algorithms and their levels. Your approach with UseDigestToSign and SignDigest feels somewhat weird and non-idiomatic.

These are trying to communicate an important property of the underlying signature algorithm, which is that the message-based form of the algorithm is equivalent to computing the digest of afforementioned message with the IUF API, which is a property that does not hold for Ed25519 vs Ed25519ph. I'd suggest reviewing the notes I left about this on #9.

They could perhaps use a better name, but I sure had trouble coming up with a proper one to convey that particular idea.

Sign up for freeto 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.

3 participants

@tarcieri@newpavlov@dignifiedquire