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

Swappable RSADP for YubiKey support #32

Closed
str4d opened this issue Dec 4, 2019 · 6 comments
Closed

Swappable RSADP for YubiKey support #32

str4d opened this issue Dec 4, 2019 · 6 comments

Comments

@str4d
Copy link
Contributor

str4d commented Dec 4, 2019

While working on yubikey-piv I was testing the RSA logic by implementing OAEP around it. What I ended up doing was copying in all the logic from #18, and replacing:

    let mut em = {
        let mut c = BigUint::from_bytes_be(ciphertext);
        let mut m = internals::decrypt(rng, priv_key, &c)?;
        let em = internals::left_pad(&m.to_bytes_be(), k);

        c.zeroize();
        m.zeroize();

        em
    };

with

    let mut em = {
        let m = yubikey.decrypt_data(&ciphertext, algorithm, slot).unwrap();
        // I forgot to check if the padding was actually necessary
        left_pad(&m, k)
    };

It seems like the way to achieve this more generally (across all decryption schemes) would be with a trait of the form:

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

    /// Decrypt the given message.
    fn decrypt(&self, padding: PaddingScheme, ciphertext: &[u8]) -> Result<Vec<u8>> {
        ...
    }

    /// Decrypt the given message.
    /// Uses `rng` to blind the decryption process.
    pub fn decrypt_blinded<R: Rng>(
        &self,
        rng: &mut R,
        padding: PaddingScheme,
        ciphertext: &[u8],
    ) -> Result<Vec<u8>> {
        ...
    }

    fn decrypt_oaep(...) -> Result<...> {
        oaep::decrypt(...)
    }
}

impl PrivateKey for RSAPrivateKey {
    fn raw_decryption_primitive<R: Rng>(
        &self,
        rng: Option<&mut R>,
        ciphertext: &[u8],
    ) -> Result<Vec<u8>> {
        let mut c = BigUint::from_bytes_be(ciphertext);
        let mut m = internals::decrypt(rng, priv_key, &c)?;
        let em = internals::left_pad(&m.to_bytes_be(), k);

        c.zeroize();
        m.zeroize();

        em
    }
}

Then in yubikey-piv we could do something like:

struct YubiKeyRsaPrivateKey {
    yubikey: &mut YubiKey,
    algorithm: AlgorithmId,
    slot: SlotId,
}

impl PrivateKey for YubiKeyRsaPrivateKey {
    fn raw_decryption_primitive<R: Rng>(
        &self,
        _rng: Option<&mut R>,
        ciphertext: &[u8],
    ) -> Result<Vec<u8>> {
        self.yubikey.decrypt_data(&ciphertext, self.algorithm, self.slot).map_err(|e| e.into())
    }
}

Thoughts? The part I dislike about the above sketch is that the raw RSA decryption primitive is exposed in the API, but this needs to happen somewhere if this kind of interoperability and code de-duplication were to happen at all.

@dignifiedquire
Copy link
Member

I like the idea of having this be a trait, and allow code reuse like this, especially as adding yubikey and smart card support is sth I would really like to see.

I think if we add a trait like this we could mark it as unsafe, to indicate the risk involved (I know this abuses a little bit the definition of unsafe, but seems worth the added explicitness). In addition it could be hidden behind a feature flag, disabled by default, such that if we just use this crate, you don't accidentally use it.

@tarcieri
Copy link
Member

tarcieri commented Dec 4, 2019

I think if we add a trait like this we could mark it as unsafe, to indicate the risk involved (I know this abuses a little bit the definition of unsafe, but seems worth the added explicitness).

I would strongly recommend against using unsafe for anything other than memory safety.

@str4d
Copy link
Contributor Author

str4d commented Dec 5, 2019

A disabled-by-default feature flag is probably the right way to handle this, along with clear documentation. Another possibility might be to have trait DecryptionPrimitive in an underlying rsa-core crate that just has the primitive API, and trait PrivateKey: DecryptionPrimitive in the rsa crate (kind of like the split between RngCore and Rng in the rand crate stack).

@str4d
Copy link
Contributor Author

str4d commented Apr 24, 2022

So, was there a preference between exposing DecryptionPrimitive and EncryptionPrimitive via a default-off feature flag, or by moving DecryptionPrimitive and EncryptionPrimitive into an rsa-core crate? That seems to be the final blocker for external implementations of the PrivateKey and PublicKey traits.

I figure that even if we created an rsa-core crate, the algorithm-specific methods like rsa::oaep::decrypt would still live here, so we'd either need to decide to have them in the normal public API (instead of only exposed via concrete wrappers like RsaPrivateKey::decrypt), or have a feature flag that exposes them. If the latter, I think it should be a different flag from the one that exposes DecryptionPrimitive if any, because that enables someone to expose the algorithm-specific methods for use with some other crate's implementation of PrivateKey (like the yubikey crate), without exposing DecryptionPrimitive within their own namespace.

@dignifiedquire
Copy link
Member

I generally prefer flags over adding another crate in this case.

@tarcieri
Copy link
Member

These were added and ultimately removed in #300.

The internals now decouple padding from the RSA operation, which makes it potentially possible to reuse the padding implementation for these cases.

There are also now traits for both signing and encryption that can be implemented by 3rd party RSA providers like the yubikey crate.

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

No branches or pull requests

3 participants