Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New API design #34

Closed
str4d opened this issue Jan 2, 2020 · 40 comments
Closed

New API design #34

str4d opened this issue Jan 2, 2020 · 40 comments

Comments

@str4d
Copy link
Contributor

str4d commented Jan 2, 2020

Prompted by RustCrypto/signatures#25 (comment) and my desire to get #18 and #26 merged 😄

The main design difference compared to the current traits is that instead of making the "padding scheme" a parameter of the pubkey sign/verify functions, we make each scheme a first-class primitive that wraps the common public or private key. It needs to be an explicit choice by the user anyway.

Current draft proposal (as of 2020-03-07) (without changes to the signature crate, and without handling the encryption cases):

// This module has been merged into master
mod raw {
    pub trait EncryptionPrimitive {
        /// Do NOT use directly! Only for implementors.
        fn raw_encryption_primitive(&self, plaintext: &[u8]) -> Result<Vec<u8>>;
    }

    pub trait DecryptionPrimitive {
        /// Do NOT use directly! Only for implementors.
        fn raw_decryption_primitive<R: Rng>(
            &self,
            rng: Option<&mut R>,
            ciphertext: &[u8],
        ) -> Result<Vec<u8>>;
    }
}

mod key {
    pub trait PublicKeyParts {
        /// Returns the modulus of the key.
        fn n(&self) -> &BigUint;
        /// Returns the public exponent of the key.
        fn e(&self) -> &BigUint;
        /// Returns the modulus size in bytes. Raw signatures and ciphertexts for
        /// or by this public key will have the same size.
        fn size(&self) -> usize {
            (self.n().bits() + 7) / 8
        }
    }

    pub trait PrivateKey: crate::raw::DecryptionPrimitive + PublicKeyParts {
        /// Could have functions like this for usability?
        pub fn sign_pkcs1v15(&self) -> crate::pkcs1v15::Signer;
        pub fn sign_pkcs1v15_blinded<R: Rng>(&self, rng: R) -> crate::pkcs1v15::Signer;
        pub fn sign_pss(&self) -> crate::pss::Signer;
        pub fn sign_pss_blinded(&self) -> crate::pss::Signer;
    }

    pub trait PublicKey: crate::raw::EncryptionPrimitive + PublicKeyParts {
        /// Could have functions like this for usability?
        pub fn verify_pkcs1v15(&self) -> crate::pkcs1v15::Verifier;
        pub fn verify_pss(&self) -> crate::pss::Verifier;
    }

    pub struct RSAPrivateKey { ... }

    impl crate::raw::DecryptionPrimitive for RSAPrivateKey { ... }

    impl PublicKeyParts for RSAPrivateKey { ... }

    impl PrivateKey for RSAPrivateKey {
        pub fn sign_pkcs1v15(&self) -> crate::pkcs1v15::Signer {
            crate::pkcs1v15::Signer::unblinded(self)
        }

        pub fn sign_pkcs1v15_blinded<R: Rng>(&self, rng: R) -> crate::pkcs1v15::Signer {
            crate::pkcs1v15::Signer::blinded(rng, self)
        }

        pub fn sign_pss(&self) -> crate::pss::Signer {
            crate::pss::Signer::unblinded(self)
        }

        pub fn sign_pss_blinded(&self) -> crate::pss::Signer {
            crate::pss::Signer::blinded(self)
        }
    }

    pub struct RSAPublicKey { ... }

    impl crate::raw::EncryptionPrimitive for RSAPublicKey { ... }

    impl PublicKeyParts for RSAPublicKey { ... }

    impl PublicKey for RSAPublicKey {
        pub fn verify_pkcs1v15(&self) -> crate::pkcs1v15::Verifier {
            crate::pkcs1v15::Verifier::new(self)
        }

        pub fn verify_pss(&self) -> crate::pss::Verifier {
            crate::pss::Verifier::new(self)
        }
    }
}

/// PKCS#1 v1.5 signing (and encryption?)
mod pkcs1v15 {
    use signature::Error;

    use crate::{
        key::{PrivateKey, PublicKey},
        raw::DecryptionPrimitive,
    };

    pub struct Signature {
        bytes: Vec<u8>,
    }

    impl signature::Signature for Signature {
        fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
            // Parse a PKCS#1 v1.5 signature here non-contextually
            // (i.e. length can't be verified as we don't know n)
        }
    }

    // Technically all we need is K: DecryptionPrimitive, but PrivateKey
    // is the correct user-level encapsulation, and we should keep
    // DecryptionPrimitive internal as much as possible.
    pub struct Signer<'a, R: Rng, K: PrivateKey> {
        // RefCell in lieu of a stateful or randomizable signature trait
        rng: Option<RefCell<R>>,
        priv_key: &'a K,
    }

    impl<'a, R: Rng, K: PrivateKey> Signer<'a, R, K> {
        pub fn unblinded(priv_key: &'a K) -> Self {
            Signer { rng: None, priv_key }
        }

        pub fn blinded(rng: R, priv_key: &'a K) -> Self {
            Signer { rng: Some(RefCell::new(rng)), priv_key }
        }
    }

    impl<'a, R: Rng, K: PrivateKey> signature::Signer<Signature> for Signer<'a, R, K> {
        fn try_sign(&self, msg: &[u8]) -> Result<Signature, Error> {
            // Sign the message directly (equivalent to current None case)
        }
    }

    impl<'a, R: Rng, K: PrivateKey> signature::DigestSigner<D: Digest, Signature> for Signer<'a, R, K> {
        fn try_sign_digest(&self, digest: D) -> Result<Signature, Error> {
            // Sign the digest (equivalent to current Some(Hash) case)
        }
    }

    pub struct Verifier<'a, PK: PublicKey> {
        pub_key: &'a PK,
    }

    impl<'a, PK: PublicKey> Verifier<'a, PK> {
        pub fn new(pub_key: &'a PK) -> Self {
            Verifier { pub_key }
        }
    }

    impl<'a, PK: PublicKey> signature::Verifier<Signature> for Verifier<'a, PK> {
        fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
            // Verify the message directly (equivalent to current None case)
        }
    }

    impl<'a, PK: PublicKey> signature::DigestVerifier<D: Digest, Signature> for Verifier<'a, PK> {
        fn verify_digest(&self, digest: D, signature: &Signature) -> Result<(), Error> {
            // Verify the digest (equivalent to current Some(Hash) case)
        }
    }
}

/// PSS signing
mod pss {
    use signature::Error;

    use crate::{
        key::{PrivateKey, PublicKey},
        raw::DecryptionPrimitive,
    };

    pub struct Signature {
        bytes: Vec<u8>,
    }

    impl signature::Signature for Signature {
        fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
            // Parse a PSS signature here non-contextually
            // (i.e. length can't be verified as we don't know n)
        }
    }

    pub struct Signer<'a, R: Rng, K: PrivateKey> {
        // RefCell in lieu of a stateful or randomizable signature trait
        rng: RefCell<R>,
        priv_key: &'a K,
        salt_len: Option<usize>,
        blind: bool
    }

    impl<'a, R: Rng, K: PrivateKey> Signer<'a, R, K> {
        pub fn unblinded(rng: R, priv_key: &'a K, salt_len: Option<usize>) -> Self {
            Signer { rng: RefCell::new(rng), priv_key, salt_len, blind: false }
        }

        pub fn blinded(rng: R, priv_key: &'a K, salt_len: Option<usize>) -> Self {
            Signer { rng: RefCell::new(rng), priv_key, salt_len, blind: true }
        }
    }

    impl<'a, R: Rng, K: PrivateKey> signature::DigestSigner<D: Digest, Signature> for Signer<'a, R, K> {
        fn try_sign_digest(&self, digest: D) -> Result<Signature, Error> { ... }
    }

    pub struct Verifier<'a, PK: PublicKey> {
        pub_key: &'a PK,
    }

    impl<'a, PK: PublicKey> Verifier<'a, PK> {
        pub fn new(pub_key: &'a PK) -> Self {
            Verifier { pub_key }
        }
    }

    impl<'a, PK: PublicKey> signature::DigestVerifier<D: Digest, Signature> for Verifier<'a, PK> {
        fn verify_digest(&self, digest: D, signature: &Signature) -> Result<(), Error> { ... }
    }
}

// Do these improve usability?
pub use pkcs1v15::{
    Signature as Pkcs1v15Signature,
    Signer as Pkcs1v15Signer,
    Verifier as Pkcs1v15Verifier,
};
pub use pss::{
    Signature as PssSignature,
    Signer as PssSigner,
    Verifier as PssVerifier,
};

I'll update the proposal in this post as we discuss it.

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

Just to be clear, the above is intended as something concrete we can stare at and work on, not as the final proposal (though I think the above is a reasonable design).

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

This looks good at first glance, and is more or less how the ecdsa crate works today.

Something else to consider is incorporating more of the parameters for how the signature is computed in the signature type itself, e.g. making pkcs1v15::Signature and pss::Signature generic around a digest applied to the message (and in the case of PSS, making it generic around an MGF type as well).

I'd probably recommend avoiding that initially and circling back on it later.

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

Updated draft to use PrivateKey, PublicKey traits inside Signer / Verifier structs, so they can be generalised over the backend (e.g. HSMs) a la #32.

@dignifiedquire
Copy link
Member

The api looks pretty good to me. The main question for me is how do we integrate this with the encryption part, as that is kinda needed for getting it integrated into the crate

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

My suggestion (which I'll write up next) is to have equivalent Encryptor / Decryptor objects. We could maybe treat these as block ciphers and use the block-cipher-trait traits, but it feels like not the right fit to me, so probably just equivalent methods (and maybe traits within the rsa crate). Then the full suite would be:

crate::{
    oaep::{Decryptor, Encryptor},
    pkcs1v15::{Decryptor, Encryptor, Signer, Verifier},
    pss::{Signer, Verifier},
};

@dignifiedquire
Copy link
Member

I worry a bit about this api becoming quite complex, having to create multiple structs for each operation seems like quite a lot of mental overhead for the user of the api, when all these operations could just be straightforwad function calls like they are now.

@dignifiedquire
Copy link
Member

Not saying the current API is perfect, not even great, but I am a big advocate of making cryptographic primitives easier to use, not harder.

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

The struct-based API would be used something like this:

use rsa::{pss::Signer, RsaPrivateKey};
use sha2::{Digest, Sha256};
use signature::DigestSigner;

let privkey = RsaPrivateKey::from_wherever();
let signer = Signer::unblinded(&privkey);

let digest = Sha256::digest(msg);
let signature = signer.sign_digest(digest);

This is where the APIs on PublicKey and PrivateKey could perhaps still be useful for usability:

use rsa::RsaPrivateKey;
use sha2::{Digest, Sha256};
use signature::DigestSigner;

let privkey = RsaPrivateKey::from_wherever();
let signer = privkey.sign_pss();

let digest = Sha256::digest(msg);
let signature = signer.sign_digest(digest);

This only saves an import, as the separate privkey object is still necessary for lifetimes. Alternatively, the Signer and Verifier structs could move the PublicKey / PrivateKey internally, in which case you could do:

use rsa::key::RsaPrivateKey;
use sha2::{Digest, Sha256};
use signature::DigestSigner;

let signer = RsaPrivateKey::from_wherever().sign_pss();

let digest = Sha256::digest(msg);
let signature = signer.sign_digest(digest);

The trade-off here is that this isn't as usable for HSM backends (where you probably only have a single reference to the connection that you are passing around) or for use-cases where the same key is used for both encryption and signing in the same logic (does this happen?)

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

Writing the above three more compactly (for more options to stare at):

use rsa::{pss, RsaPrivateKey};
use sha2::{Digest, Sha256};
use signature::DigestSigner;

let privkey = RsaPrivateKey::from_wherever();
let digest = Sha256::digest(msg);
let signature = pss::Signer::unblinded(&privkey).sign_digest(digest);
use rsa::RsaPrivateKey;
use signature::DigestSigner;
use sha2::{Digest, Sha256};

let privkey = RsaPrivateKey::from_wherever();
let digest = Sha256::digest(msg);
let signature = privkey.sign_pss().sign_digest(digest);
use rsa::RsaPrivateKey;
use signature::DigestSigner;
use sha2::{Digest, Sha256};

let signer = RsaPrivateKey::from_wherever().sign_pss();
let digest = Sha256::digest(msg);
let signature = signer.sign_digest(digest);

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

Note that you can use the signature_derive crate to derive a Signer and Verifier impl for types which impl DigestSigner and DigestVerifier. Here's an example:

https://github.com/tendermint/signatory/blob/develop/signatory-secp256k1/src/lib.rs#L25
https://github.com/tendermint/signatory/blob/develop/signatory-secp256k1/src/lib.rs#L80

That would reduce this:

use signature::DigestSigner;

let signer = PrivateKey::from_wherever().sign_pss();

let digest = Sha256::digest(msg);
let signature = signer.sign_digest(digest);

to:

use signature::Signer;

let signer = PrivateKey::from_wherever().sign_pss();
let signature = signer.sign(msg);

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

And the current API for comparison:

use rsa::{
    hash::Hashes,
    PaddingScheme, RSAPrivateKey,
};
use sha2::{Digest, Sha256};

let privkey = RsaPrivateKey::from_wherever();
let digest = Sha256::digest(msg);
let signature = privkey.sign(Padding::PKCS1v15, Some(Hashes::SHA2_256), digest.as_bytes());

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

Note that you can use the signature_derive crate to derive a Signer and Verifier impl for types which impl DigestSigner and DigestVerifier.

I considered adding that to my example, but I wondered whether that might result in confusing behaviour, given that for PKCS#1 v1.5 we'd be using the Signer interface for signing undigested input. Or should instead have the Signer interface accept any length message (rather than within the RSA modulus size) and have an RSA-specific impl Digest for NoneDigest solely for use in the signer API? Which is more in keeping with the signature API?

@dignifiedquire
Copy link
Member

where the same key is used for both encryption and signing in the same logic (does this happen?)

it does happen sometimes for RSA in my experience

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

I considered adding that to my example, but I wondered whether that might result in confusing behaviour, given that for PKCS#1 v1.5 we'd be using the Signer interface for signing undigested input.

Ugh yes RSASSA-PKCS#1v1.5, it is definitely a wart there, but I'd also argue it doesn't quite fit the (perhaps underdocumented) DigestSigner contract, which is intended for something more like a Fiat-Shamir scheme.

where the same key is used for both encryption and signing in the same logic (does this happen?)

It absolutely happens in pre-1.3 TLS when the same RSA key is permitted to be used for both key encipherment and signing, and both (EC)DHE and RSA key transport ciphersuites are enabled.

@dignifiedquire
Copy link
Member

Maybe we could provide wrapper methods, that hide the struct generation?

use signature::DigestSigner;
let digest = Sha256::digest(msg);
let signature = PrivateKey::from_wherever().sign_pss(digest);


fn sign_pss(&self, digest) -> Signature {
  let signer = self.create_pss_signer(); // this is what is currently named sign_pss
  signer.sign(digest)
}

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

That seems like a reasonable approach. The underlying structs allow for higher-level protocols to implement more descriptive typed APIs (e.g. an outer protocol struct that stores an rsa::oaep::Encryptor internally), while the key-level wrapper methods abstract over it for the simple use-cases (but are still type-safe due to the Signature type).

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

Maybe we could provide wrapper methods, that hide the struct generation?

Ideally library-level code which wants to target multiple RSA signing backends (so far we have the yubikey-piv, yubihsm, and keychain-services.rs crates as real-world examples) would use the Signature trait, which would allow the consumer of a library to use any of these backends.

This isn't orthogonal to your suggestion, but if that's the "blessed" API, would probably discourage (e.g. protocol) library authors from writing code in a multi-provider-friendly way.

@dignifiedquire
Copy link
Member

dignifiedquire commented Jan 2, 2020

couldn't we have something like

fn sign_pss<S: SignatureBackend = DefaultBackend>(&self, digest) -> Signature { .. }

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

What type are you proposing that's implemented on?

Note that in the case of an HSM/KMS/SEP-backed signer, by design there is no PrivateKey type by design.

However, something like this defined as rsa::sign_pss could work:

fn sign_pss<S: SignatureBackend = DefaultBackend>(signer: &S, digest) -> pss::Signature { .. }

@dignifiedquire
Copy link
Member

What type are you proposing that's implemented on?

My idea was this would be implemented for the PrivateKey trait.

Note that in the case of an HSM/KMS/SEP-backed signer, by design there is no PrivateKey type by design.

Couldn't those still have some kind of struct that implements a PrivateKey trait, holding necessary details required to make it work?

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

@tarcieri in my sketch proposal, PrivateKey (or more precisely, DecryptionPrimitive, but again that shouldn't be the first thing sitting in public interfaces) is basically serving the purpose that SignatureBackend is in your above line.

In my mind, PrivateKey is a logical abstraction over a private key, whether it be in-memory like RSAPrivateKey or a connection to an HSM that knows e.g. which slot to use.

@dignifiedquire
Copy link
Member

So we could actually have something like

let hsm: HSM = get_hsm();
let signature = hsm.sign_pss(digest);

// somewhere 
impl PrivateKey for HSM {}

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

That seems like the same intended usage as the Signer trait, although I suppose captures more usage patterns than just signatures (i.e. encryption).

Any reason why you can't have blanket impls of Signer<pkcs1v15::Signature> and Signer<pss::Signature> for PrivateKey?

Generally for supporting HSMs, I think it'd be better to split things apart into separate traits, as they may or may not support specific algorithms, and many by design do not support unpadded RSA, which it seems like PrivateKey necessitates.

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

The problem AIUI with that is how you specify all the necessary algorithm parameters. This is why I proposed an e.g. pss::Signer struct that wrapped PrivateKey (which could be an HSM) and could store those parameters, and then have impl Signer<pss::Signature> for pss::Signer etc. Is there another way to leverage the Signer trait to get around this?

You could probably get away with directly implementing Verifier<pss::Signature> for RsaPublicKey etc.

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

Is there another way to leverage the Signer trait to get around this?

Per my earlier suggestion, encode them all in the signature type, potentially making "what everyone uses by convention" default generic parameters.

I think this is where I'd like to go with the ecdsa crate, as it were.

Doing so would help prevent the hash substitution attacks against RSASSA-PSS raised by Peter Gutmann via type safety.

@dignifiedquire
Copy link
Member

One issue I see with making it generic over the digest type, is that whatever hashing library you choose, gets blessed, and it is really hard to use a different one. E.g. I use blake2b_simd in all my code, but if we were to use the Digest trait from rustcrypto, I suddenly have to copy and dance around, in order to use the different hashing crate.

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

How else would you propose supporting multiple digest backends other than having things be generic over a Digest trait?

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

encode them all in the signature type, potentially making "what everyone uses by convention" default generic parameters.

This would definitely require a signature::RandomizedSigner trait.

Mocking this out as an API:

mod pkcs1v15 {
    use digest::Digest;
    use signature::{RandomizedDigestSigner, DigestSigner};

    pub struct Signature<D: Digest> {
        bytes: Vec<u8>,
        _digest: PhantomData<D>,
    }

    // For blinded signing
    impl<R: Rng, D: Digest> RandomizedDigestSigner<R, Signature<D>> for RsaPrivateKey {
        fn try_sign_digest_randomized(
            &self, rng: R, digest: D,
        ) -> Result<Signature<D>, Error> { ... }
    }

    // For unblinded signing
    impl<D: Digest> DigestSigner<Signature<D>> for RsaPrivateKey {
        fn try_sign_digest(&self, digest: D) -> Result<Signature<D>, Error> { ... }
    }
}

mod pss {
    use digest::Digest;
    use generic_array::ArrayLength;
    use signature::RandomizedSigner;

    pub trait MaskGenFunc<D: Digest> { ... }

    // Define an ArrayLength of zero as the None case?
    pub struct Signature<D: Digest, MGF: MaskGenFunc<D>, SALT_LEN: ArrayLength> {
        bytes: Vec<u8>,
        _digest: PhantomData<D>,
        _salt_len: PhantomData<SALT_LEN>,
    }

    impl<R, D, MGF, SALT_LEN> RandomizedDigestSigner<R, Signature<D, MGF, SALT_LEN>>
    for RsaPrivateKey
    where
        R: Rng,
        D: Digest,
        MGF: MaskGenFunc<D>,
        SALT_LEN: ArrayLength {
        fn try_sign_digest_randomized(
            &self, rng: R, digest: D,
        ) -> Result<Signature<D, MGF, SALT_LEN>, Error> { ... }
    }

    impl<R, D, MGF, SALT_LEN> DigestSigner<R, Signature<D, MGF, SALT_LEN>>
    for RsaPrivateKey
    where
        R: Rng,
        D: Digest,
        MGF: MaskGenFunc<D>,
        SALT_LEN: ArrayLength {
        fn try_sign_digest(&self, rng: R, digest: D) -> Result<Signature<D, MGF, SALT_LEN>, Error> { ... }
    }
}

whatever hashing library you choose, gets blessed, and it is really hard to use a different one.

The concrete hashing libraries don't get blessed, but the Digest trait does, I agree. This would require blake2b_simd to impl Digest on their APIs.

@dignifiedquire
Copy link
Member

How else would you propose supporting multiple digest backends other than having things be generic over a Digest trait?

Great question, which I unfortunately don't have a great answer to, but maybe sth like DigestRef which implements From<&[u8]>? That would at least allow to avoid the unnecessary copies.

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

This would definitely require a signature::RandomizedSigner trait.

I'd be fine with adding that.

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

For a bit of precedent, the yubihsm crate presently captures the algorithm parameter choices as enums. For example, here's how it captures MGF algorithms for use with OAEP/PSS:

https://docs.rs/yubihsm/0.30.0/yubihsm/rsa/mgf/enum.Algorithm.html

The digests are encoded as part of the OAEP and PSS algorithm identifiers (Yubico's choice, not mine):

https://docs.rs/yubihsm/0.30.0/yubihsm/rsa/oaep/enum.Algorithm.html
https://docs.rs/yubihsm/0.30.0/yubihsm/rsa/pss/enum.Algorithm.html

If you want to be generic across more digest backends than what are available in RustCrypto, but still be type safe, you could have something like a DigestBackend type which includes the appropriate enum variant as an associated type.

@dignifiedquire
Copy link
Member

@tarcieri and those are then simply used as parameters?

@str4d
Copy link
Contributor Author

str4d commented Jan 2, 2020

For a bit of precedent, the yubihsm crate presently captures the algorithm parameter choices as enums.

AFAICT this doesn't work with using type signatures for parameters; we'd then still need a per-algo Signer object that stores the particular enum choice, on which signature::Signer is implemented.

@tarcieri
Copy link
Member

tarcieri commented Jan 2, 2020

That's also true... these are presently passed as parameters to the signing operation (which doesn't use the Signer trait as there aren't RSA signature types to use it with)

@str4d
Copy link
Contributor Author

str4d commented Jan 3, 2020

I'm going to try making branches that mock out the two different approaches:

  • Structs around PrivateKey + wrapper functions on PrivateKey
  • impls directly on PrivateKey

The latter will depend on having a signature::RandomizedDigestSigner trait, which I will assume the presence of for now.

@str4d
Copy link
Contributor Author

str4d commented Jan 4, 2020

One issue I've come across while implementing this: I don't have a good way to connect the ASN.1 DER prefixes to particular Digest implementations without manually implementing Hash for every digest that might come along. It feels like the Hash trait should belong at a different intermediate layer, and individual hash function crates like sha2 should implement (equivalent of) Hash themselves. Alternatively:

  • This crate could implement Hash for every hash function crate we know about, behind feature flags that users can turn on. This requires no changes to any other crate, and provides type safety, at the cost of being effectively an allowlist.
  • We keep the current type-unsafe design where the Option<H: Hash> is stored internally, and users just have to "get it right" by passing the correct Digest implementation in.

@kinosang
Copy link

kinosang commented Feb 6, 2021

any update to the draft?

@lumag
Copy link
Contributor

lumag commented Sep 10, 2022

@dignifiedquire @tarcieri I think this issue can be now closed, can't it?

@tarcieri
Copy link
Member

I think the ideas proposed here have either been implemented or obsoleted by other work /cc @str4d

@dignifiedquire
Copy link
Member

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants