diff options
-rw-r--r-- | .gitlab-ci.yml | 18 | ||||
-rw-r--r-- | Cargo.lock | 16 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 5 | ||||
-rw-r--r-- | openpgp/NEWS | 2 | ||||
-rw-r--r-- | openpgp/README.md | 6 | ||||
-rw-r--r-- | openpgp/build.rs | 8 | ||||
-rw-r--r-- | openpgp/src/crypto/backend.rs | 9 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan.rs | 101 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/aead.rs | 63 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/asymmetric.rs | 670 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/ecdh.rs | 150 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/hash.rs | 103 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/symmetric.rs | 178 |
13 files changed, 1329 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 825db3ed..2d3ae64f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -113,6 +113,24 @@ fedora-crypto-openssl: CARGO_TARGET_DIR: /target CARGO_HOME: /cargo +rust-stable-crypto-botan: + tags: + - linux + stage: build + interruptible: true + image: 192.168.122.1:5000/sequoia-pgp/build-docker-image/rust-stable-prebuild:latest + before_script: + - *before_script_start + - apt -y install libbotan-2-dev + - rustup override set stable + - *before_script_end + script: + - cargo run --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-botan,compression --example supported-algorithms + - cargo test --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-botan,compression + variables: + CARGO_TARGET_DIR: /target + CARGO_HOME: /cargo + benchmarks: stage: test interruptible: true @@ -213,6 +213,21 @@ dependencies = [ ] [[package]] +name = "botan" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca148eb4be2a336a57efb859f664847cb1d268649e4a6032c5b16f21e8fd5027" +dependencies = [ + "botan-sys", +] + +[[package]] +name = "botan-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b986825430fa1dff571f2dc36562a7a228b98c42e9f9552727c60a4a16de6555" + +[[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2325,6 +2340,7 @@ dependencies = [ "block-modes", "block-padding", "blowfish", + "botan", "buffered-reader", "bzip2", "cast5", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index 0407d482..d8575d05 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -44,6 +44,7 @@ regex-syntax = "0.6" sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] } thiserror = "1.0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } + # At least 0.10.45 is needed due to CipherCtx::cipher_final_unchecked openssl = { version = ">= 0.10.45", optional = true } # We need to directly depend on the sys crate so that the metadata produced @@ -52,6 +53,9 @@ openssl = { version = ">= 0.10.45", optional = true } openssl-sys = { version = ">= 0.9.80", optional = true } foreign-types-shared = { version = "0.1.1", optional = true } +# Botan. +botan = { version = "0.10", optional = true } + # RustCrypto crates. aes = { version = "0.6.0", optional = true } block-modes = { version = "0.7.0", optional = true } @@ -109,6 +113,7 @@ crypto-rust = [ ] crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"] crypto-openssl = ["openssl", "openssl-sys", "foreign-types-shared"] +crypto-botan = ["botan"] __implicit-crypto-backend-for-tests = [] # Experimental and variable-time cryptographic backends opt-ins diff --git a/openpgp/NEWS b/openpgp/NEWS index 7a6080ed..cfabfe07 100644 --- a/openpgp/NEWS +++ b/openpgp/NEWS @@ -4,6 +4,8 @@ #+STARTUP: content hidestars * Changes in 1.14.0 +** New cryptographic backends + - We added a backend that uses Botan. ** New functionality - crypto::mem::Protected::new - crypto::mpi::SecretKeyMaterial::from_bytes diff --git a/openpgp/README.md b/openpgp/README.md index fcaaed89..ecf3eb61 100644 --- a/openpgp/README.md +++ b/openpgp/README.md @@ -80,6 +80,12 @@ at compile time. Currently, these libraries are available: `default-features = false`, and explicitly include the `crypto-openssl` feature to enable it. + - The Botan backend. To select this backend, use + `default-features = false`, and explicitly include the + `crypto-botan` feature to enable it. + + **Note**: Using the Botan backend raises the MSRV to 1.64. + - The Windows Cryptography API: Next Generation (CNG). To select this backend, use `default-features = false`, and explicitly include the `crypto-cng` feature to enable it. Currently, the CNG diff --git a/openpgp/build.rs b/openpgp/build.rs index 60f07620..2a2aa9a1 100644 --- a/openpgp/build.rs +++ b/openpgp/build.rs @@ -66,6 +66,7 @@ fn crypto_backends_sanity_check() { (cfg!(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust"))))), Backend { name: "Nettle", @@ -76,6 +77,7 @@ fn crypto_backends_sanity_check() { not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust"))))), Backend { name: "Windows CNG", @@ -94,6 +96,12 @@ fn crypto_backends_sanity_check() { production_ready: true, constant_time: true, }), + (cfg!(feature = "crypto-botan"), + Backend { + name: "Botan", + production_ready: true, + constant_time: true, + }), ].into_iter().filter_map(|(selected, backend)| { if selected { Some(backend) } else { None } }).collect::<Vec<_>>(); diff --git a/openpgp/src/crypto/backend.rs b/openpgp/src/crypto/backend.rs index 28fcfe14..7cae8a8a 100644 --- a/openpgp/src/crypto/backend.rs +++ b/openpgp/src/crypto/backend.rs @@ -14,11 +14,13 @@ pub(crate) mod sha1cd; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] mod nettle; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] pub use self::nettle::*; @@ -34,12 +36,14 @@ pub use self::nettle::*; not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] mod cng; #[cfg(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] pub use self::cng::*; @@ -52,3 +56,8 @@ pub use self::rust::*; mod openssl; #[cfg(feature = "crypto-openssl")] pub use self::openssl::*; + +#[cfg(feature = "crypto-botan")] +mod botan; +#[cfg(feature = "crypto-botan")] +pub use self::botan::*; diff --git a/openpgp/src/crypto/backend/botan.rs b/openpgp/src/crypto/backend/botan.rs new file mode 100644 index 00000000..30f42a67 --- /dev/null +++ b/openpgp/src/crypto/backend/botan.rs @@ -0,0 +1,101 @@ +//! Implementation of Sequoia crypto API using the Botan cryptographic library. + +use crate::types::*; + +pub mod aead; +#[allow(unused_variables)] +pub mod asymmetric; +#[allow(unused_variables)] +pub mod ecdh; +pub mod hash; +pub mod symmetric; + +/// Returns a short, human-readable description of the backend. +pub fn backend() -> String { + "Botan".to_string() +} + +/// Fills the given buffer with random data. +pub fn random(buf: &mut [u8]) { + let mut rng = botan::RandomNumberGenerator::new_system().unwrap(); + rng.fill(buf).unwrap(); +} + +impl PublicKeyAlgorithm { + pub(crate) fn is_supported_by_backend(&self) -> bool { + use PublicKeyAlgorithm::*; + #[allow(deprecated)] + match &self { + RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA | + ElGamalEncrypt | ElGamalEncryptSign + => true, + Private(_) | Unknown(_) + => false, + } + } +} + +impl Curve { + pub(crate) fn is_supported_by_backend(&self) -> bool { + use self::Curve::*; + match &self { + NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 | + BrainpoolP256 | BrainpoolP512 + => true, + Unknown(_) if self.is_brainpoolp384() // XXX + => true, + Unknown(_) + => false, + } + } +} + +impl AEADAlgorithm { + /// Returns the best AEAD mode supported by the backend. + /// + /// This SHOULD return OCB, which is the mandatory-to-implement + /// algorithm and the most performing one, but fall back to any + /// supported algorithm. + pub(crate) const fn const_default() -> AEADAlgorithm { + AEADAlgorithm::OCB + } + + pub(crate) fn is_supported_by_backend(&self) -> bool { + use self::AEADAlgorithm::*; + match &self { + EAX | OCB + => true, + Private(_) | Unknown(_) + => false, + } + } + + #[cfg(test)] + pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { + match &self { + AEADAlgorithm::EAX => + match algo { + SymmetricAlgorithm::AES128 | + SymmetricAlgorithm::AES192 | + SymmetricAlgorithm::AES256 | + SymmetricAlgorithm::Twofish | + SymmetricAlgorithm::Camellia128 | + SymmetricAlgorithm::Camellia192 | + SymmetricAlgorithm::Camellia256 => true, + _ => false, + }, + AEADAlgorithm::OCB => + match algo { + SymmetricAlgorithm::AES128 | + SymmetricAlgorithm::AES192 | + SymmetricAlgorithm::AES256 | + SymmetricAlgorithm::Twofish | + SymmetricAlgorithm::Camellia128 | + SymmetricAlgorithm::Camellia192 | + SymmetricAlgorithm::Camellia256 => true, + _ => false, + }, + _ => false + } + } +} diff --git a/openpgp/src/crypto/backend/botan/aead.rs b/openpgp/src/crypto/backend/botan/aead.rs new file mode 100644 index 00000000..c7f4bbf2 --- /dev/null +++ b/openpgp/src/crypto/backend/botan/aead.rs @@ -0,0 +1,63 @@ +//! Implementation of AEAD using the Botan cryptographic library. + +use crate::{Error, Result}; + +use crate::crypto::aead::{Aead, CipherOp}; +use crate::seal; +use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; + +struct Cipher(botan::Cipher, usize); + +impl seal::Sealed for Cipher {} +impl Aead for Cipher { + fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + debug_assert_eq!(dst.len(), src.len() + self.digest_size()); + self.0.finish_into(src, dst)?; + Ok(()) + } + + fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + debug_assert_eq!(dst.len() + self.digest_size(), src.len()); + self.0.finish_into(src, dst)?; + Ok(()) + } + fn digest_size(&self) -> usize { + self.1 + } +} + +impl AEADAlgorithm { + /// Returns the name of the algorithm for use with Botan's + /// constructor. + fn botan_name(self) -> Result<&'static str> { + match self { + AEADAlgorithm::EAX => Ok("EAX"), + AEADAlgorithm::OCB => Ok("OCB"), + _ => Err(Error::UnsupportedAEADAlgorithm(self).into()), + } + } + + pub(crate) fn context(&self, + sym_algo: SymmetricAlgorithm, + key: &[u8], + aad: &[u8], + nonce: &[u8], + op: CipherOp) + -> Result<Box<dyn Aead>> + { + let mut cipher = botan::Cipher::new( + &format!("{}/{}", sym_algo.botan_name()?, self.botan_name()?), + match op { + CipherOp::Encrypt => botan::CipherDirection::Encrypt, + CipherOp::Decrypt => botan::CipherDirection::Decrypt, + }) + // XXX it could be the cipher that is not supported. + .map_err(|_| Error::UnsupportedAEADAlgorithm(*self))?; + + cipher.set_key(key)?; + cipher.set_associated_data(aad)?; + cipher.start(nonce)?; + + Ok(Box::new(Cipher(cipher, self.digest_size()?))) + } +} diff --git a/openpgp/src/crypto/backend/botan/asymmetric.rs b/openpgp/src/crypto/backend/botan/asymmetric.rs new file mode 100644 index 00000000..4cc2da5b --- /dev/null +++ b/openpgp/src/crypto/backend/botan/asymmetric.rs @@ -0,0 +1,670 @@ +//! Hold the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`]. +//! +//! [`Signer`]: super::super::asymmetric::Signer +//! [`Decryptor`]: super::super::asymmetric::Decryptor +//! [`KeyPair`]: super::super::asymmetric::KeyPair + +use std::time::SystemTime; + +use botan::{ + RandomNumberGenerator, + Pubkey, + Privkey, +}; + +use crate::{ + Error, + Result, + crypto::{ + asymmetric::{KeyPair, Decryptor, Signer}, + mem::Protected, + mpi::{self, MPI, ProtectedMPI, PublicKey}, + SessionKey, + }, + packet::{ + key::{self, Key4, SecretParts}, + Key, + }, + types::{ + Curve, + HashAlgorithm, + PublicKeyAlgorithm, + SymmetricAlgorithm, + }, +}; + +// CONFIDENTIALITY: Botan clears the MPIs after use. +impl TryFrom<&ProtectedMPI> for botan::MPI { + type Error = anyhow::Error; + fn try_from(mpi: &ProtectedMPI) -> anyhow::Result<botan::MPI> { + Ok(botan::MPI::new_from_bytes(mpi.value())?) + } +} + +impl TryFrom<&botan::MPI> for ProtectedMPI { + type Error = anyhow::Error; + fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<botan::MPI> for ProtectedMPI { + type Error = anyhow::Error; + fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<&MPI> for botan::MPI { + type Error = anyhow::Error; + fn try_from(mpi: &MPI) -> anyhow::Result<botan::MPI> { + Ok(botan::MPI::new_from_bytes(mpi.value())?) + } +} + +impl TryFrom<&botan::MPI> for MPI { + type Error = anyhow::Error; + fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<botan::MPI> for MPI { + type Error = anyhow::Error; + fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl Signer for KeyPair { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + KeyPair::public(self) + } + + fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) + -> Result<mpi::Signature> + { + use crate::PublicKeyAlgorithm::*; + + let mut rng = RandomNumberGenerator::new_userspace()?; + + self.secret().map(|secret| { + #[allow(deprecated)] + match (self.public().pk_algo(), self.public().mpis(), secret) + { + (RSASign, + PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }) | + (RSAEncryptSign, + PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }) => { + let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, + &e.try_into()?)?; + let sig = secret.sign( + digest, + &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?), + &mut rng)?; + Ok(mpi::Signature::RSA { + s: MPI::new(&sig), + }) + }, + + (DSA, + PublicKey::DSA { p, q, g, .. }, + mpi::SecretKeyMaterial::DSA { x }) => { + let secret = Privkey::load_dsa(&p.try_into()?, &q.try_into()?, + &g.try_into()?, &x.try_into()?)?; + let size = q.value().len(); + let truncated_digest = &digest[..size.min(digest.len())]; + let sig = secret.sign(truncated_digest, "Raw", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::DSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + + (EdDSA, + PublicKey::EdDSA { curve, .. }, + mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve { + Curve::Ed25519 => { + let size = 32; + let scalar = scalar.value_padded(size); + let secret = Privkey::load_ed25519(&scalar)?; + let sig = secret.sign(digest, "", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::EdDSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + _ => Err( + Error::UnsupportedEllipticCurve(curve.clone()).into()), + }, + + (ECDSA, + PublicKey::ECDSA { curve, .. }, + mpi::SecretKeyMaterial::ECDSA { scalar }) => { + let size = curve.field_size()?; + let secret = Privkey::load_ecdsa( + &scalar.try_into()?, curve.botan_name()?)?; + let sig = secret.sign(digest, "Raw", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::ECDSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + + (pk_algo, _, _) => Err(Error::InvalidOperation(format!( + "unsupported combination of algorithm {:?}, key {:?}, \ + and secret key {:?}", + pk_algo, self.public(), self.secret())).into()), + }}) + } +} + +impl Decryptor for KeyPair { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + KeyPair::public(self) + } + + fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, + plaintext_len: Option<usize>) + -> Result<SessionKey> + { + fn bad(e: impl ToString) -> anyhow::Error { + // XXX: Not a great error to return. + Error::MalformedMessage(e.to_string()).into() + } + + self.secret().map( + |secret| Ok(match (self.public().mpis(), secret, ciphertext) + { + (PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }, + mpi::Ciphertext::RSA { c }) => { + let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, + &e.try_into()?)?; + secret.decrypt(c.value(), "PKCS1v15")?.into() + }, + + (PublicKey::ElGamal{ p, g, .. }, + mpi::SecretKeyMaterial::ElGamal{ x }, + mpi::Ciphertext::ElGamal{ e, c }) => { + // OpenPGP encodes E and C separately, but our + // cryptographic library expects them to be + // concatenated. + let size = p.value().len(); + let mut ctxt = Vec::with_capacity(2 * size); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + ctxt.extend_from_slice(&e.value_padded(size).map_err(bad)?); + ctxt.extend_from_slice(&c.value_padded(size).map_err(bad)?); + + let secret = + Privkey::load_elgamal(&p.try_into()?, &g.try_into()?, + &x.try_into()?)?; + secret.decrypt(&ctxt, "PKCS1v15")?.into() + }, + + (PublicKey::ECDH{ .. }, + mpi::SecretKeyMaterial::ECDH { .. }, + mpi::Ciphertext::ECDH { .. }) => + crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, + + (public, secret, ciphertext) => + return Err(Error::InvalidOperation(format!( + "unsupported combination of key pair {:?}/{:?} \ + and ciphertext {:?}", + public, secret, ciphertext)).into()), + })) + } +} + + +impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { + /// Encrypts the given data with this key. + pub fn encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { + use crate::PublicKeyAlgorithm::*; + + #[allow(deprecated)] + match (self.pk_algo(), self.mpis()) { + (RSAEncryptSign, mpi::PublicKey::RSA { e, n }) | + (RSAEncrypt, mpi::PublicKey::RSA { e, n }) => { + // The ciphertext has the length of the modulus. + let ciphertext_len = n.value().len(); + if data.len() + 11 > ciphertext_len { + return Err(Error::InvalidArgument( + "Plaintext data too large".into()).into()); + } + + let mut rng = RandomNumberGenerator::new_userspace()?; + let pk = + Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; + let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; + Ok(mpi::Ciphertext::RSA { + c: MPI::new(&esk), + }) + }, + + (ElGamalEncryptSign, mpi::PublicKey::ElGamal { p, g, y }) | + (ElGamalEncrypt, mpi::PublicKey::ElGamal { p, g, y }) => { + // OpenPGP encodes E and C separately, but our + // cryptographic library concatenates them. + let size = p.value().len(); + + let mut rng = RandomNumberGenerator::new_userspace()?; + let pk = + Pubkey::load_elgamal(&p.try_into()?, &g.try_into()?, + &y.try_into()?)?; + let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; + + if esk.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected ciphertext with length {}, got {}", + size * 2, esk.len())).into()); + } + + Ok(mpi::Ciphertext::ElGamal { + e: MPI::new(&esk[..size]), + c: MPI::new(&esk[size..]), + }) + }, + + (ECDH, mpi::PublicKey::ECDH { .. }) => + crate::crypto::ecdh::encrypt(self.parts_as_public(), data), + + _ => return Err(Error::MalformedPacket(format!( + "unsupported combination of key {} and mpis {:?}.", + self.pk_algo(), self.mpis())).into()), + } + } + + /// Verifies the given signature. + pub fn verify(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, + digest: &[u8]) -> Result<()> + { + use crate::crypto::mpi::Signature; + + fn bad(e: impl ToString) -> anyhow::Error { + Error::BadSignature(e.to_string()).into() + } + + let ok = match (self.mpis(), sig) { + (PublicKey::RSA { e, n }, Signature::RSA { s }) => { + let pk = Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; + pk.verify(digest, s.value(), + &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?))? + }, + (PublicKey::DSA { y, q, p, g }, Signature::DSA { s, r }) => { + // OpenPGP encodes R and S separately, but our + // cryptographic library expects them to be + // concatenated. + let size = q.value().len(); + let mut sig = Vec::with_capacity(2 * size); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + sig.extend_from_slice(&r.value_padded(size).map_err(bad)?); + sig.extend_from_slice(&s.value_padded(size).map_err(bad)?); + + let pk = Pubkey::load_dsa(&p.try_into()?, &q.try_into()?, + &g.try_into()?, &y.try_into()?)?; |