diff --git a/Cargo.toml b/Cargo.toml index 440ca14805..34cdc49c8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ picky = { version = "7.0.0-rc.3", default-features = false, features = [ "x509", regex = "1.5.5" serde_json = "1.0.79" serde = { version = "1.0.136", features = ["derive"] } -sha2 = "0.10.2" +sha2 = { version = "0.10.6", features = ["oid"] } thiserror = "1.0.30" tokio = { version = "1.17.0", features = ["full"] } tough = { version = "0.12.4", features = [ "http" ] } @@ -48,6 +48,8 @@ digest = "0.10.3" signature = { version = "1.5.0", features = [ "digest-preview" ] } ed25519 = { version = "1", features = [ "alloc" ] } ed25519-dalek-fiat = "0.1.0" +rsa = "0.7.0-rc.1" +pkcs1 = "0.4.0" [dev-dependencies] anyhow = "1.0.54" diff --git a/examples/key_interface/key_pair_import/main.rs b/examples/key_interface/key_pair_import/main.rs index 4314cb187b..50f46f77d3 100644 --- a/examples/key_interface/key_pair_import/main.rs +++ b/examples/key_interface/key_pair_import/main.rs @@ -71,7 +71,7 @@ fn main() -> Result<()> { inner.to_sigstore_signer()?; println!("Converted SigStoreKeyPair to SigStoreSigner."); } - SigStoreKeyPair::ED25519(_) => bail!("Wrong key pair type."), + _ => bail!("Wrong key pair type."), } Ok(()) diff --git a/src/cosign/verification_constraint.rs b/src/cosign/verification_constraint.rs index 085f416264..6886ce2beb 100644 --- a/src/cosign/verification_constraint.rs +++ b/src/cosign/verification_constraint.rs @@ -88,6 +88,7 @@ impl PublicKeyVerifier { /// The `key_raw` variable holds a PEM encoded rapresentation of the /// public key to be used at verification time. The verification /// algorithm will be derived from the public key type: + /// * `RSA public key`: `RSA_PSS_SHA256` /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` /// * `Ed25519 public key`: `Ed25519` diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 3d2f3edb02..5be3d21289 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -24,6 +24,19 @@ pub use signing_key::SigStoreSigner; pub use verification_key::CosignVerificationKey; /// Different digital signature algorithms. +/// * `RSA_PSS_SHA256`: RSA PSS padding using SHA-256 +/// for RSA signatures. All the `usize` member inside +/// an RSA enum represents the key size of the RSA key. +/// * `RSA_PSS_SHA384`: RSA PSS padding using SHA-384 +/// for RSA signatures. +/// * `RSA_PSS_SHA512`: RSA PSS padding using SHA-512 +/// for RSA signatures. +/// * `RSA_PKCS1_SHA256`: PKCS#1 1.5 padding using +/// SHA-256 for RSA signatures. +/// * `RSA_PKCS1_SHA384`: PKCS#1 1.5 padding using +/// SHA-384 for RSA signatures. +/// * `RSA_PKCS1_SHA512`: PKCS#1 1.5 padding using +/// SHA-512 for RSA signatures. /// * `ECDSA_P256_SHA256_ASN1`: ASN.1 DER-encoded ECDSA /// signatures using the P-256 curve and SHA-256. It /// is the default signing scheme. @@ -36,7 +49,12 @@ pub use verification_key::CosignVerificationKey; #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy)] pub enum SigningScheme { - // TODO: Support RSA + RSA_PSS_SHA256(usize), + RSA_PSS_SHA384(usize), + RSA_PSS_SHA512(usize), + RSA_PKCS1_SHA256(usize), + RSA_PKCS1_SHA384(usize), + RSA_PKCS1_SHA512(usize), ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, ED25519, @@ -50,6 +68,12 @@ impl TryFrom<&str> for SigningScheme { "ECDSA_P256_SHA256_ASN1" => Ok(Self::ECDSA_P256_SHA256_ASN1), "ECDSA_P384_SHA384_ASN1" => Ok(Self::ECDSA_P384_SHA384_ASN1), "ED25519" => Ok(Self::ED25519), + "RSA_PSS_SHA256" => Ok(Self::RSA_PSS_SHA256(DEFAULT_KEY_SIZE)), + "RSA_PSS_SHA384" => Ok(Self::RSA_PSS_SHA384(DEFAULT_KEY_SIZE)), + "RSA_PSS_SHA512" => Ok(Self::RSA_PSS_SHA512(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA256" => Ok(Self::RSA_PKCS1_SHA256(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA384" => Ok(Self::RSA_PKCS1_SHA384(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA512" => Ok(Self::RSA_PKCS1_SHA512(DEFAULT_KEY_SIZE)), unknown => Err(format!("Unsupported signing algorithm: {}", unknown)), } } @@ -68,6 +92,48 @@ impl SigningScheme { SigningScheme::ED25519 => { SigStoreSigner::ED25519(Ed25519Signer::from_ed25519_keys(&Ed25519Keys::new()?)?) } + SigningScheme::RSA_PSS_SHA256(bit_size) => { + SigStoreSigner::RSA_PSS_SHA256(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha256, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PSS_SHA384(bit_size) => { + SigStoreSigner::RSA_PSS_SHA384(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha384, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PSS_SHA512(bit_size) => { + SigStoreSigner::RSA_PSS_SHA512(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha512, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PKCS1_SHA256(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA256(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha256, + PaddingScheme::PKCS1v15, + )) + } + SigningScheme::RSA_PKCS1_SHA384(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA384(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha384, + PaddingScheme::PKCS1v15, + )) + } + SigningScheme::RSA_PKCS1_SHA512(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA512(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha512, + PaddingScheme::PKCS1v15, + )) + } }) } } @@ -98,6 +164,7 @@ pub mod verification_key; use self::signing_key::{ ecdsa::ec::{EcdsaKeys, EcdsaSigner}, ed25519::{Ed25519Keys, Ed25519Signer}, + rsa::{keypair::RSAKeys, DigestAlgorithm, PaddingScheme, RSASigner, DEFAULT_KEY_SIZE}, }; pub mod signing_key; diff --git a/src/crypto/signing_key/mod.rs b/src/crypto/signing_key/mod.rs index 7695cde4bc..cc324297d3 100644 --- a/src/crypto/signing_key/mod.rs +++ b/src/crypto/signing_key/mod.rs @@ -22,10 +22,17 @@ //! * [`SigStoreSigner`]: an abstraction for digital signing algorithms. //! //! The [`SigStoreKeyPair`] now includes the key types of the following algorithms: -//! * [`SigStoreKeyPair::ECDSA`]: Elliptic curve digital signing algorithm -//! * [`SigStoreKeyPair::ED25519`]: Edwards curve-25519 digital signing algorithm +//! * [`SigStoreKeyPair::RSA`]: RSA key pair +//! * [`SigStoreKeyPair::ECDSA`]: Elliptic curve key pair +//! * [`SigStoreKeyPair::ED25519`]: Edwards curve-25519 key pair //! //! The [`SigStoreSigner`] now includes the following signing schemes: +//! * [`SigStoreSigner::RSA_PSS_SHA256`]: RSA signatures using PSS padding and SHA-256. +//! * [`SigStoreSigner::RSA_PSS_SHA384`]: RSA signatures using PSS padding and SHA-384. +//! * [`SigStoreSigner::RSA_PSS_SHA512`]: RSA signatures using PSS padding and SHA-512. +//! * [`SigStoreSigner::RSA_PKCS1_SHA256`]: RSA signatures using PKCS#1v1.5 padding and SHA-256. +//! * [`SigStoreSigner::RSA_PKCS1_SHA384`]: RSA signatures using PKCS#1v1.5 padding and SHA-384. +//! * [`SigStoreSigner::RSA_PKCS1_SHA512`]: RSA signatures using PKCS#1v1.5 padding and SHA-512. //! * [`SigStoreSigner::ECDSA_P256_SHA256_ASN1`]: ASN.1 DER-encoded ECDSA //! signatures using the P-256 curve and SHA-256. //! * [`SigStoreSigner::ECDSA_P384_SHA384_ASN1`]: ASN.1 DER-encoded ECDSA @@ -68,6 +75,7 @@ use crate::errors::*; use self::{ ecdsa::{ec::EcdsaSigner, ECDSAKeys}, ed25519::{Ed25519Keys, Ed25519Signer}, + rsa::{keypair::RSAKeys, RSASigner}, }; use super::{verification_key::CosignVerificationKey, SigningScheme}; @@ -89,6 +97,9 @@ pub const SIGSTORE_PRIVATE_KEY_PEM_LABEL: &str = "ENCRYPTED SIGSTORE PRIVATE KEY /// The label for pem of private keys. pub const PRIVATE_KEY_PEM_LABEL: &str = "PRIVATE KEY"; +/// The label for pem of RSA private keys. +pub const RSA_PRIVATE_KEY_PEM_LABEL: &str = "RSA PRIVATE KEY"; + /// Every signing scheme must implement this interface. /// All private export methods using the wrapper `Zeroizing`. /// It will tell the compiler when the @@ -125,7 +136,7 @@ pub trait KeyPair { pub enum SigStoreKeyPair { ECDSA(ECDSAKeys), ED25519(Ed25519Keys), - // RSA, + RSA(RSAKeys), } /// This macro helps to reduce duplicated code. @@ -147,6 +158,7 @@ macro_rules! sigstore_keypair_code { match $obj { SigStoreKeyPair::ECDSA(keys) => keys.as_inner().$func($($args,)*), SigStoreKeyPair::ED25519(keys) => keys.$func($($args,)*), + SigStoreKeyPair::RSA(keys) => keys.$func($($args,)*), } } } @@ -217,6 +229,12 @@ pub trait Signer { #[allow(non_camel_case_types)] pub enum SigStoreSigner { + RSA_PSS_SHA256(RSASigner), + RSA_PSS_SHA384(RSASigner), + RSA_PSS_SHA512(RSASigner), + RSA_PKCS1_SHA256(RSASigner), + RSA_PKCS1_SHA384(RSASigner), + RSA_PKCS1_SHA512(RSASigner), ECDSA_P256_SHA256_ASN1(EcdsaSigner), ECDSA_P384_SHA384_ASN1(EcdsaSigner), ED25519(Ed25519Signer), @@ -230,6 +248,12 @@ impl SigStoreSigner { SigStoreSigner::ECDSA_P256_SHA256_ASN1(inner) => inner, SigStoreSigner::ECDSA_P384_SHA384_ASN1(inner) => inner, SigStoreSigner::ED25519(inner) => inner, + SigStoreSigner::RSA_PSS_SHA256(inner) => inner, + SigStoreSigner::RSA_PSS_SHA384(inner) => inner, + SigStoreSigner::RSA_PSS_SHA512(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA256(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA384(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA512(inner) => inner, } } @@ -244,6 +268,12 @@ impl SigStoreSigner { SigStoreSigner::ECDSA_P256_SHA256_ASN1(_) => SigningScheme::ECDSA_P256_SHA256_ASN1, SigStoreSigner::ECDSA_P384_SHA384_ASN1(_) => SigningScheme::ECDSA_P384_SHA384_ASN1, SigStoreSigner::ED25519(_) => SigningScheme::ED25519, + SigStoreSigner::RSA_PSS_SHA256(_) => SigningScheme::RSA_PSS_SHA256(0), + SigStoreSigner::RSA_PSS_SHA384(_) => SigningScheme::RSA_PSS_SHA384(0), + SigStoreSigner::RSA_PSS_SHA512(_) => SigningScheme::RSA_PSS_SHA512(0), + SigStoreSigner::RSA_PKCS1_SHA256(_) => SigningScheme::RSA_PKCS1_SHA256(0), + SigStoreSigner::RSA_PKCS1_SHA384(_) => SigningScheme::RSA_PKCS1_SHA384(0), + SigStoreSigner::RSA_PKCS1_SHA512(_) => SigningScheme::RSA_PKCS1_SHA512(0), }; self.as_inner() .key_pair() @@ -262,6 +292,18 @@ impl SigStoreSigner { SigStoreSigner::ED25519(inner) => { SigStoreKeyPair::ED25519(Ed25519Keys::from_ed25519key(inner.ed25519_keys())?) } + SigStoreSigner::RSA_PSS_SHA256(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PSS_SHA384(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PSS_SHA512(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PKCS1_SHA256(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } + SigStoreSigner::RSA_PKCS1_SHA384(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } + SigStoreSigner::RSA_PKCS1_SHA512(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } }) } } diff --git a/src/crypto/signing_key/rsa.rs b/src/crypto/signing_key/rsa.rs deleted file mode 100644 index 6713ec5235..0000000000 --- a/src/crypto/signing_key/rsa.rs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright 2022 The Sigstore Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # RSA Key Generation and Signing -//! This is a wrapper for Rust Crypto -// TODO: After https://github.com/RustCrypto/RSA/pull/174 is merged. diff --git a/src/crypto/signing_key/rsa/keypair.rs b/src/crypto/signing_key/rsa/keypair.rs new file mode 100644 index 0000000000..cdbfb810be --- /dev/null +++ b/src/crypto/signing_key/rsa/keypair.rs @@ -0,0 +1,425 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # RSA Key Pair +//! +//! This is a wrapper for Rust Crypto. RSA Key Pair +//! is the struct [`RSAKeys`], which implements [`KeyPair`] +//! trait, and provides different exportation and importation operations +//! from/to der/pem bytes. +//! +//! # RSA Key Pair Operations +//! +//! For example, we generate an RSA key pair and export. +//! +//! ```rust +//! use sigstore::crypto::signing_key::{rsa::keypair::RSAKeys, KeyPair}; +//! +//! let rsa_keys = RSAKeys::new(2048).unwrap(); +//! +//! // export the pem encoded public key. +//! let pubkey = rsa_keys.public_key_to_pem().unwrap(); +//! +//! // export the private key using sigstore encryption. +//! let privkey_pem = rsa_keys.private_key_to_encrypted_pem(b"password").unwrap(); +//! +//! // import the key pair from the encrypted pem. +//! let rsa_keys2 = RSAKeys::from_encrypted_pem(privkey_pem.as_bytes(), b"password").unwrap(); +//! ``` + +use std::convert::TryFrom; + +use pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey}; +use rsa::{ + pkcs1::DecodeRsaPrivateKey, pkcs1v15::SigningKey, pss::BlindedSigningKey, RsaPrivateKey, + RsaPublicKey, +}; + +use crate::{ + crypto::{CosignVerificationKey, SigStoreSigner, SigningScheme}, + errors::*, +}; + +use crate::crypto::signing_key::{ + kdf, KeyPair, COSIGN_PRIVATE_KEY_PEM_LABEL, PRIVATE_KEY_PEM_LABEL, RSA_PRIVATE_KEY_PEM_LABEL, + SIGSTORE_PRIVATE_KEY_PEM_LABEL, +}; + +use super::{DigestAlgorithm, PaddingScheme, RSASigner}; + +#[derive(Clone)] +pub struct RSAKeys { + pub(crate) private_key: RsaPrivateKey, + public_key: RsaPublicKey, +} + +impl RSAKeys { + /// Create a new `RSAKeys` Object. + /// The private key will be randomly + /// generated. + pub fn new(bit_size: usize) -> Result { + let mut rng = rand::rngs::OsRng {}; + let private_key = RsaPrivateKey::new(&mut rng, bit_size)?; + let public_key = RsaPublicKey::from(&private_key); + Ok(Self { + private_key, + public_key, + }) + } + + /// Create a new `RSAKeys` Object from given `RSAKeys` Object. + pub fn from_rsa_privatekey_key(key: &RSAKeys) -> Result { + let priv_key = key.private_key_to_der()?; + RSAKeys::from_der(&priv_key) + } + + /// Builds a `RSAKeys` from encrypted pkcs8 PEM-encided private key. + /// The label should be [`COSIGN_PRIVATE_KEY_PEM_LABEL`] or + /// [`SIGSTORE_PRIVATE_KEY_PEM_LABEL`]. + pub fn from_encrypted_pem(encrypted_pem: &[u8], password: &[u8]) -> Result { + let key = pem::parse(encrypted_pem)?; + match &key.tag[..] { + COSIGN_PRIVATE_KEY_PEM_LABEL | SIGSTORE_PRIVATE_KEY_PEM_LABEL => { + let der = kdf::decrypt(&key.contents, password)?; + let pkcs8 = pkcs8::PrivateKeyInfo::try_from(&der[..]).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {}", e)) + })?; + let private_key = RsaPrivateKey::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to rsa private key failed: {}", + e, + )) + })?; + Ok(Self::from(private_key)) + } + + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {}", + tag + ))), + } + } + + /// Builds a `RSAKeys` from a pkcs8 PEM-encoded private key. + /// The label of PEM should be [`PRIVATE_KEY_PEM_LABEL`] + pub fn from_pem(pem: &[u8]) -> Result { + let pem = std::str::from_utf8(pem)?; + let (label, document) = pkcs8::SecretDocument::from_pem(pem) + .map_err(|e| SigstoreError::PKCS8DerError(e.to_string()))?; + + match label { + PRIVATE_KEY_PEM_LABEL => { + let pkcs8 = pkcs8::PrivateKeyInfo::try_from(document.as_bytes()).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {}", e)) + })?; + let private_key = RsaPrivateKey::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to rsa private key failed: {}", + e, + )) + })?; + Ok(Self::from(private_key)) + } + + RSA_PRIVATE_KEY_PEM_LABEL => { + let private_key = RsaPrivateKey::from_pkcs1_der(document.as_bytes())?; + Ok(Self::from(private_key)) + } + + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {}", + tag + ))), + } + } + + /// Builds a `RSAKeys` from a pkcs8 asn.1 private key. + pub fn from_der(der_bytes: &[u8]) -> Result { + let private_key = RsaPrivateKey::from_pkcs8_der(der_bytes).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 der to rsa private key failed: {}", + e, + )) + })?; + Ok(Self::from(private_key)) + } + + /// `to_sigstore_signer` will create the [`SigStoreSigner`] using + /// this rsa key pair. + pub fn to_sigstore_signer( + &self, + digest_algorithm: DigestAlgorithm, + padding_scheme: PaddingScheme, + ) -> Result { + let private_key = self.private_key.clone(); + Ok(match padding_scheme { + PaddingScheme::PSS => match digest_algorithm { + DigestAlgorithm::Sha256 => { + SigStoreSigner::RSA_PSS_SHA256(RSASigner::RSA_PSS_SHA256( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha384 => { + SigStoreSigner::RSA_PSS_SHA384(RSASigner::RSA_PSS_SHA384( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha512 => { + SigStoreSigner::RSA_PSS_SHA512(RSASigner::RSA_PSS_SHA512( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + }, + PaddingScheme::PKCS1v15 => match digest_algorithm { + DigestAlgorithm::Sha256 => { + SigStoreSigner::RSA_PKCS1_SHA256(RSASigner::RSA_PKCS1_SHA256( + SigningKey::::new_with_prefix(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha384 => { + SigStoreSigner::RSA_PKCS1_SHA384(RSASigner::RSA_PKCS1_SHA384( + SigningKey::::new_with_prefix(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha512 => { + SigStoreSigner::RSA_PKCS1_SHA512(RSASigner::RSA_PKCS1_SHA512( + SigningKey::::new_with_prefix(private_key), + self.clone(), + )) + } + }, + }) + } +} + +impl From for RSAKeys { + fn from(private_key: RsaPrivateKey) -> Self { + Self { + private_key: private_key.clone(), + public_key: RsaPublicKey::from(private_key), + } + } +} + +impl KeyPair for RSAKeys { + /// Return the public key in PEM-encoded SPKI format. + fn public_key_to_pem(&self) -> Result { + self.public_key + .to_public_key_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the public key in asn.1 SPKI format. + fn public_key_to_der(&self) -> Result> { + Ok(self + .public_key + .to_public_key_der() + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string()))? + .to_vec()) + } + + /// Return the encrypted asn.1 pkcs8 private key. + fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result> { + let der = self.private_key_to_der()?; + let pem = match password.len() { + 0 => pem::Pem { + tag: PRIVATE_KEY_PEM_LABEL.to_string(), + contents: der.to_vec(), + }, + _ => pem::Pem { + tag: SIGSTORE_PRIVATE_KEY_PEM_LABEL.to_string(), + contents: kdf::encrypt(&der, password)?, + }, + }; + let pem = pem::encode(&pem); + Ok(zeroize::Zeroizing::new(pem)) + } + + /// Return the private key in pkcs8 PEM-encoded format. + fn private_key_to_pem(&self) -> Result> { + self.private_key + .to_pkcs8_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the private key in asn.1 pkcs8 format. + fn private_key_to_der(&self) -> Result>> { + let pkcs8 = self + .private_key + .to_pkcs8_der() + .map_err(|e| SigstoreError::PKCS8Error(e.to_string()))?; + Ok(pkcs8.to_bytes()) + } + + /// Derive the relative [`CosignVerificationKey`]. + fn to_verification_key(&self, signing_scheme: &SigningScheme) -> Result { + let der = self.public_key_to_der()?; + let res = CosignVerificationKey::from_der(&der, signing_scheme)?; + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use crate::crypto::{ + signing_key::{ + rsa::{DigestAlgorithm, PaddingScheme, RSASigner}, + tests::MESSAGE, + KeyPair, Signer, + }, + verification_key::CosignVerificationKey, + Signature, SigningScheme, + }; + + use super::RSAKeys; + + const PASSWORD: &[u8] = b"123"; + const KEY_SIZE: usize = 2048; + + /// This test will try to read an unencrypted rsa + /// private key file, which is generated by `sigstore`. + #[test] + fn rsa_from_unencrypted_pem() { + let content = fs::read("tests/data/keys/rsa_private.key") + .expect("read tests/data/keys/rsa_private.key failed."); + let key = RSAKeys::from_pem(&content); + assert!( + key.is_ok(), + "can not create RSAKeys from unencrypted PEM file." + ); + } + + /// This test will try to read an encrypted rsa + /// private key file, which is generated by `sigstore`. + #[test] + fn rsa_from_encrypted_pem() { + let content = fs::read("tests/data/keys/rsa_encrypted_private.key") + .expect("read tests/data/keys/rsa_encrypted_private.key failed."); + let key = RSAKeys::from_encrypted_pem(&content, PASSWORD); + assert!( + key.is_ok(), + "can not create RSAKeys from encrypted PEM file" + ); + } + + /// This test will try to encrypt a rsa keypair and + /// return the pem-encoded contents. The bit size + /// of the rsa key is [`KEY_SIZE`]. + #[test] + fn rsa_to_encrypted_pem() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key.private_key_to_encrypted_pem(PASSWORD); + assert!( + key.is_ok(), + "can not export private key in encrypted PEM format." + ); + } + + /// This test will generate a RSAKeys, encode the private key + /// it into pem, and decode a new key from the generated pem-encoded + /// private key. + #[test] + fn rsa_to_and_from_pem() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key + .private_key_to_pem() + .expect("export private key to PEM format failed."); + let key = RSAKeys::from_pem(key.as_bytes()); + assert!(key.is_ok(), "can not create RSAKeys from PEM string."); + } + + /// This test will generate a RSAKeys, encode the private key + /// it into der, and decode a new key from the generated der-encoded + /// private key. + #[test] + fn rsa_to_and_from_der() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key + .private_key_to_der() + .expect("export private key to DER format failed."); + let key = RSAKeys::from_der(&key); + assert!(key.is_ok(), "can not create RSAKeys from DER bytes.") + } + + /// This test will generate a rsa keypair. + /// And then use the verification key interface to instantial + /// a VerificationKey object. + #[test] + fn rsa_generate_public_key() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + assert!(CosignVerificationKey::from_pem( + pubkey.as_bytes(), + &SigningScheme::RSA_PSS_SHA256(0), + ) + .is_ok()); + let pubkey = key + .public_key_to_der() + .expect("export private key to DER format failed."); + assert!( + CosignVerificationKey::from_der(&pubkey, &SigningScheme::RSA_PSS_SHA256(0)).is_ok(), + "can not create CosignVerificationKey from der bytes." + ); + } + + /// This test will generate a rsa keypair. + /// And then derive a `CosignVerificationKey` from it. + #[test] + fn rsa_derive_verification_key() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + assert!( + key.to_verification_key(&SigningScheme::RSA_PSS_SHA256(0)) + .is_ok(), + "can not create CosignVerificationKey from RSAKeys via `to_verification_key`." + ); + } + + /// This test will do the following things: + /// * Generate a rsa keypair. + /// * Sign the MESSAGE with `RSA_PSS_SHA256` + /// * Verify the signature using the public key. + #[test] + fn rsa_sign_and_verify() { + let rsa_keys = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let pubkey = rsa_keys + .public_key_to_pem() + .expect("export private key to PEM format failed."); + let signer = + RSASigner::from_rsa_keys(&rsa_keys, DigestAlgorithm::Sha256, PaddingScheme::PSS); + + let sig = signer + .sign(MESSAGE.as_bytes()) + .expect("signing message failed."); + let verification_key = + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::RSA_PSS_SHA256(0)) + .expect("convert CosignVerificationKey from public key failed."); + let signature = Signature::Raw(&sig); + assert!( + verification_key + .verify_signature(signature, MESSAGE.as_bytes()) + .is_ok(), + "can not verify the signature." + ); + } +} diff --git a/src/crypto/signing_key/rsa/mod.rs b/src/crypto/signing_key/rsa/mod.rs new file mode 100644 index 0000000000..3d15998e97 --- /dev/null +++ b/src/crypto/signing_key/rsa/mod.rs @@ -0,0 +1,260 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # RSA Signer +//! +//! RSA Signer support the following padding schemes: +//! * `PSS` +//! * `PKCS#1 v1.5` +//! +//! And the following digest algorithms: +//! * `Sha256` +//! * `Sha384` +//! * `Sha512` +//! +//! # RSA Signer Operaion +//! +//! A [`RSASigner`] can be derived from a [`RSAKeys`] +//! ```rust +//! use sigstore::crypto::signing_key::{rsa::{RSASigner, keypair::RSAKeys, DigestAlgorithm, PaddingScheme}, KeyPair, Signer}; +//! use sigstore::crypto::Signature; +//! +//! let rsa_keys = RSAKeys::new(2048).unwrap(); +//! +//! // create a signer +//! let signer = RSASigner::from_rsa_keys(&rsa_keys, DigestAlgorithm::Sha256, PaddingScheme::PSS); +//! +//! // test message to be signed +//! let message = b"some message"; +//! +//! // sign +//! let signature_data = signer.sign(message).unwrap(); +//! +//! // export the [`CosignVerificationKey`] from the [`SigStoreSigner`], which +//! // is used to verify the signature. +//! let verification_key = signer.to_verification_key().unwrap(); +//! +//! // verify +//! assert!(verification_key.verify_signature(Signature::Raw(&signature_data),message).is_ok()); +//! ``` + +use ::rsa::{pkcs1v15::SigningKey, pss::BlindedSigningKey}; +use signature::RandomizedSigner; + +use self::keypair::RSAKeys; + +use crate::{crypto::CosignVerificationKey, errors::*}; + +use super::{KeyPair, Signer}; + +pub mod keypair; + +pub const DEFAULT_KEY_SIZE: usize = 2048; + +/// Different digest algorithms used in +/// RSA-based signing algorithm. +pub enum DigestAlgorithm { + Sha256, + Sha384, + Sha512, +} + +/// Different padding schemes used in +/// RSA-based signing algorithm. +/// * `PSS`: Probabilistic Signature Scheme, more secure than `PKCS1v15`. +/// * `PKCS1v15`: also known as simply PKCS1, is a simple padding +/// scheme developed for use with RSA keys. +pub enum PaddingScheme { + PSS, + PKCS1v15, +} + +/// Rsa signing scheme families: +/// * `PKCS1v15`: PKCS#1 1.5 padding for RSA signatures. +/// * `PSS`: RSA PSS padding for RSA signatures. +/// +/// Both schemes support the following digest algorithms: +/// * `Sha256` +/// * `Sha384` +/// * `Sha512` +#[allow(non_camel_case_types)] +pub enum RSASigner { + RSA_PSS_SHA256(BlindedSigningKey, RSAKeys), + RSA_PSS_SHA384(BlindedSigningKey, RSAKeys), + RSA_PSS_SHA512(BlindedSigningKey, RSAKeys), + RSA_PKCS1_SHA256(SigningKey, RSAKeys), + RSA_PKCS1_SHA384(SigningKey, RSAKeys), + RSA_PKCS1_SHA512(SigningKey, RSAKeys), +} + +/// helper to generate match arms +macro_rules! iter_on_rsa { + ($domain: ident, $match_item: expr, $signer: ident, $key: ident, $func: expr) => { + match $match_item { + $domain::RSA_PSS_SHA256($signer, $key) => $func, + $domain::RSA_PSS_SHA384($signer, $key) => $func, + $domain::RSA_PSS_SHA512($signer, $key) => $func, + $domain::RSA_PKCS1_SHA256($signer, $key) => $func, + $domain::RSA_PKCS1_SHA384($signer, $key) => $func, + $domain::RSA_PKCS1_SHA512($signer, $key) => $func, + } + }; +} + +impl RSASigner { + pub fn from_rsa_keys( + rsa_keys: &RSAKeys, + digest_algorithm: DigestAlgorithm, + padding_scheme: PaddingScheme, + ) -> Self { + let private_key = rsa_keys.private_key.clone(); + match padding_scheme { + PaddingScheme::PSS => match digest_algorithm { + DigestAlgorithm::Sha256 => RSASigner::RSA_PSS_SHA256( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha384 => RSASigner::RSA_PSS_SHA384( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha512 => RSASigner::RSA_PSS_SHA512( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + }, + PaddingScheme::PKCS1v15 => match digest_algorithm { + DigestAlgorithm::Sha256 => RSASigner::RSA_PKCS1_SHA256( + SigningKey::::new_with_prefix(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha384 => RSASigner::RSA_PKCS1_SHA384( + SigningKey::::new_with_prefix(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha512 => RSASigner::RSA_PKCS1_SHA512( + SigningKey::::new_with_prefix(private_key), + rsa_keys.clone(), + ), + }, + } + } + + /// Return the ref to the [`RSAKeys`] inside the RSASigner + pub fn rsa_keys(&self) -> &RSAKeys { + iter_on_rsa!(RSASigner, self, _signer, key, key) + } + + /// Return the related [`CosignVerificationKey`] of this RSASigner + pub fn to_verification_key(&self) -> Result { + Ok(match self { + RSASigner::RSA_PSS_SHA256(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA256(signer.into()) + } + RSASigner::RSA_PSS_SHA384(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA384(signer.into()) + } + RSASigner::RSA_PSS_SHA512(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA512(signer.into()) + } + RSASigner::RSA_PKCS1_SHA256(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA256(signer.into()) + } + RSASigner::RSA_PKCS1_SHA384(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA384(signer.into()) + } + RSASigner::RSA_PKCS1_SHA512(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA512(signer.into()) + } + }) + } +} + +impl Signer for RSASigner { + /// `sign` will sign the given data, and return the signature. + fn sign(&self, msg: &[u8]) -> Result> { + let mut rng = rand::thread_rng(); + Ok(iter_on_rsa!( + RSASigner, + self, + signer, + _key, + signer.try_sign_with_rng(&mut rng, msg)?.to_vec() + )) + } + + /// Return the ref to the [`KeyPair`] trait object inside the RSASigner + fn key_pair(&self) -> &dyn KeyPair { + iter_on_rsa!(RSASigner, self, _signer, key, key) + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::{keypair::RSAKeys, DigestAlgorithm, PaddingScheme, RSASigner, DEFAULT_KEY_SIZE}; + use crate::crypto::{ + signing_key::{tests::MESSAGE, KeyPair, Signer}, + Signature, SigningScheme, + }; + + #[rstest] + #[case( + DigestAlgorithm::Sha256, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA256(0) + )] + #[case( + DigestAlgorithm::Sha384, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA384(0) + )] + #[case( + DigestAlgorithm::Sha512, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA512(0) + )] + #[case( + DigestAlgorithm::Sha256, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA256(0) + )] + #[case( + DigestAlgorithm::Sha384, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA384(0) + )] + #[case( + DigestAlgorithm::Sha512, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA512(0) + )] + fn rsa_schemes( + #[case] digest_algorithm: DigestAlgorithm, + #[case] padding_scheme: PaddingScheme, + #[case] signing_scheme: SigningScheme, + ) { + let rsa_keys = RSAKeys::new(DEFAULT_KEY_SIZE).expect("RSA keys generated failed."); + let signer = RSASigner::from_rsa_keys(&rsa_keys, digest_algorithm, padding_scheme); + let sig = signer.sign(MESSAGE.as_bytes()).expect("sign failed."); + let vk = rsa_keys + .to_verification_key(&signing_scheme) + .expect("derive CosignVerificationKey failed."); + let signature = Signature::Raw(&sig); + vk.verify_signature(signature, MESSAGE.as_bytes()) + .expect("can not verify the signature."); + } +} diff --git a/src/crypto/verification_key.rs b/src/crypto/verification_key.rs index 0ea537b344..93845e5897 100644 --- a/src/crypto/verification_key.rs +++ b/src/crypto/verification_key.rs @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ecdsa::VerifyingKey; use pkcs8::DecodePublicKey; +use rsa::{pkcs1v15, pss}; use sha2::{Digest, Sha256, Sha384}; -use signature::{DigestVerifier, Verifier}; +use signature::{DigestVerifier, Signature as _, Verifier}; use x509_parser::{prelude::FromDer, x509::SubjectPublicKeyInfo}; use super::{ @@ -30,14 +30,26 @@ use crate::errors::*; /// /// Currently the following key formats are supported: /// +/// * RSA keys, using PSS padding and SHA-256 as the digest algorithm +/// * RSA keys, using PSS padding and SHA-384 as the digest algorithm +/// * RSA keys, using PSS padding and SHA-512 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-256 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-384 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-512 as the digest algorithm /// * Ed25519 keys, and SHA-512 as the digest algorithm /// * ECDSA keys, ASN.1 DER-encoded, using the P-256 curve and SHA-256 as digest algorithm /// * ECDSA keys, ASN.1 DER-encoded, using the P-384 curve and SHA-384 as digest algorithm #[allow(non_camel_case_types)] #[derive(Debug, Clone)] pub enum CosignVerificationKey { - ECDSA_P256_SHA256_ASN1(VerifyingKey), - ECDSA_P384_SHA384_ASN1(VerifyingKey), + RSA_PSS_SHA256(pss::VerifyingKey), + RSA_PSS_SHA384(pss::VerifyingKey), + RSA_PSS_SHA512(pss::VerifyingKey), + RSA_PKCS1_SHA256(pkcs1v15::VerifyingKey), + RSA_PKCS1_SHA384(pkcs1v15::VerifyingKey), + RSA_PKCS1_SHA512(pkcs1v15::VerifyingKey), + ECDSA_P256_SHA256_ASN1(ecdsa::VerifyingKey), + ECDSA_P384_SHA384_ASN1(ecdsa::VerifyingKey), ED25519(ed25519_dalek_fiat::PublicKey), } @@ -46,8 +58,68 @@ impl CosignVerificationKey { /// of extracting the SubjectPublicKeyInfo from the DER-encoded data. pub fn from_der(der_data: &[u8], signing_scheme: &SigningScheme) -> Result { Ok(match signing_scheme { + SigningScheme::RSA_PSS_SHA256(_) => { + CosignVerificationKey::RSA_PSS_SHA256(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } + SigningScheme::RSA_PSS_SHA384(_) => { + CosignVerificationKey::RSA_PSS_SHA384(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } + SigningScheme::RSA_PSS_SHA512(_) => { + CosignVerificationKey::RSA_PSS_SHA512(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA256(_) => { + CosignVerificationKey::RSA_PKCS1_SHA256(pkcs1v15::VerifyingKey::new_with_prefix( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA384(_) => { + CosignVerificationKey::RSA_PKCS1_SHA384(pkcs1v15::VerifyingKey::new_with_prefix( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA512(_) => { + CosignVerificationKey::RSA_PKCS1_SHA512(pkcs1v15::VerifyingKey::new_with_prefix( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {}", + e + )) + })?, + )) + } SigningScheme::ECDSA_P256_SHA256_ASN1 => CosignVerificationKey::ECDSA_P256_SHA256_ASN1( - VerifyingKey::from_public_key_der(der_data).map_err(|e| { + ecdsa::VerifyingKey::from_public_key_der(der_data).map_err(|e| { SigstoreError::PKCS8SpkiError(format!( "Ecdsa-P256 from der bytes to public key failed: {}", e, @@ -55,7 +127,7 @@ impl CosignVerificationKey { })?, ), SigningScheme::ECDSA_P384_SHA384_ASN1 => CosignVerificationKey::ECDSA_P384_SHA384_ASN1( - VerifyingKey::from_public_key_der(der_data).map_err(|e| { + ecdsa::VerifyingKey::from_public_key_der(der_data).map_err(|e| { SigstoreError::PKCS8SpkiError(format!( "Ecdsa-P384 from der bytes to public key failed: {}", e, @@ -73,22 +145,23 @@ impl CosignVerificationKey { /// Builds a [`CosignVerificationKey`] from DER-encoded public key data. This function will /// set the verification algorithm due to the public key type, s.t. + /// * `RSA public key`: `RSA_PSS_SHA256` /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` /// * `Ed25519 public key`: `Ed25519` pub fn try_from_der(der_data: &[u8]) -> Result { - if let Ok(p256vk) = VerifyingKey::from_public_key_der(der_data) { + if let Ok(p256vk) = ecdsa::VerifyingKey::from_public_key_der(der_data) { Ok(Self::ECDSA_P256_SHA256_ASN1(p256vk)) - } else if let Ok(p384vk) = VerifyingKey::from_public_key_der(der_data) { + } else if let Ok(p384vk) = ecdsa::VerifyingKey::from_public_key_der(der_data) { Ok(Self::ECDSA_P384_SHA384_ASN1(p384vk)) - } else if let Ok((_, pubkey)) = SubjectPublicKeyInfo::from_der(der_data) { - match ed25519_dalek_fiat::PublicKey::from_bytes(&pubkey.subject_public_key.data) { - Ok(key) => Ok(Self::ED25519(key)), - Err(_) => Err(SigstoreError::PublicKeyUnsupportedAlgorithmError(format!( - "Oid: {}", - pubkey.algorithm.algorithm.to_id_string() - ))), - } + } else if let Ok(ed25519bytes) = + ed25519::pkcs8::PublicKeyBytes::from_public_key_der(der_data) + { + Ok(Self::ED25519(ed25519_dalek_fiat::PublicKey::from_bytes( + ed25519bytes.as_ref(), + )?)) + } else if let Ok(rsapk) = rsa::RsaPublicKey::from_public_key_der(der_data) { + Ok(Self::RSA_PSS_SHA256(pss::VerifyingKey::new(rsapk))) } else { Err(SigstoreError::InvalidKeyFormat { error: "Failed to parse the public key.".to_string(), @@ -106,6 +179,7 @@ impl CosignVerificationKey { /// Builds a [`CosignVerificationKey`] from PEM-encoded public key data. This function will /// set the verification algorithm due to the public key type, s.t. + /// * `RSA public key`: `RSA_PSS_SHA256` /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` /// * `Ed25519 public key`: `Ed25519` @@ -135,6 +209,42 @@ impl CosignVerificationKey { }; match self { + CosignVerificationKey::RSA_PSS_SHA256(inner) => { + let sig = pss::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA384(inner) => { + let sig = pss::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA512(inner) => { + let sig = pss::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA256(inner) => { + let sig = pkcs1v15::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA384(inner) => { + let sig = pkcs1v15::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA512(inner) => { + let sig = pkcs1v15::Signature::from_bytes(&sig)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } // ECDSA signatures are encoded in der. CosignVerificationKey::ECDSA_P256_SHA256_ASN1(inner) => { let mut hasher = Sha256::new(); @@ -241,29 +351,28 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== assert!(found, "Didn't get expected error, got {:?} instead", err); } - // TODO: After add rsa signing and verification - // #[test] - // fn verify_rsa_signature() { - // let signature = Signature::Base64Encoded(b"umasnfYJyLbYPjiq1wIy086Ns+CrgiMoQUSGqPqlUmtWsY0hbngJ73hPfJFrppviPKdBeuUiiwgKagBKIXLEXjwxQp4eE3szwqkKoAnR/lByb7ahLgVQ4MB6xDQaHD53MYtj7aOvd4O7FqJltVVjEn7nM/Du2tL5y3jf6lD7VfHZE8uRocRlyppt8SfTc5L12mVlZ0YlfKYkd334A4y/reCy3Yws0j356Wj7GLScMU5uR11Y2y41rSyYm5uXhTerwNFXsRcPMAmenMarCdCmt4Lf4wpcJBCU172xiK+rIhbMgkLjjA772+auSYf1E8CySVah5CD0Td5YC3y8vIIYaA=="); + #[test] + fn verify_rsa_signature() { + let signature = Signature::Base64Encoded(b"umasnfYJyLbYPjiq1wIy086Ns+CrgiMoQUSGqPqlUmtWsY0hbngJ73hPfJFrppviPKdBeuUiiwgKagBKIXLEXjwxQp4eE3szwqkKoAnR/lByb7ahLgVQ4MB6xDQaHD53MYtj7aOvd4O7FqJltVVjEn7nM/Du2tL5y3jf6lD7VfHZE8uRocRlyppt8SfTc5L12mVlZ0YlfKYkd334A4y/reCy3Yws0j356Wj7GLScMU5uR11Y2y41rSyYm5uXhTerwNFXsRcPMAmenMarCdCmt4Lf4wpcJBCU172xiK+rIhbMgkLjjA772+auSYf1E8CySVah5CD0Td5YC3y8vIIYaA=="); - // let verification_key = CosignVerificationKey::from_pem( - // r#"-----BEGIN PUBLIC KEY----- - // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvM/dHoi6nSy7hbKHLYUr - // Xy6Bv35JbdoIzny5vSFiRXApr0KS56U8PugdGmh+vd7H8YNlx2YOJxzv02Blsrcm - // WDZcXjE3Xpsi/IHFfRZLOdwwR+u8MNFxwRUVzxyIzKGtbREVVfXPfb2Xc6FL5/tE - // vQtUKuR6XdzSaav2RnV5IybCB09s0Np0AUbdi5EfSe4INuqgY+VFYLjvM5onbAQL - // N3bFLS4Quk66Dhv93Zi6NwopwL1F07UPC5uadkyePStP3PA0OAOemj9vZADOWx5a - // dsGCKISs8iphNC5mDVoLy8Ry49Ms3eQXRjVQOMco3YNf8AhsIdxDNBVN8VTDKVkE - // DwIDAQAB - // -----END PUBLIC KEY-----"# - // .as_bytes(), - // &SigningScheme::RSA, - // ) - // .expect("Cannot create CosignVerificationKey"); - // let msg = r#"{"critical":{"identity":{"docker-reference":"registry.suse.com/suse/sle-micro/5.0/toolbox"},"image":{"docker-manifest-digest":"sha256:356631f7603526a0af827741f5fe005acf19b7ef7705a34241a91c2d47a6db5e"},"type":"cosign container image signature"},"optional":{"creator":"OBS"}}"#; + let verification_key = CosignVerificationKey::from_pem( + r#"-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvM/dHoi6nSy7hbKHLYUr +Xy6Bv35JbdoIzny5vSFiRXApr0KS56U8PugdGmh+vd7H8YNlx2YOJxzv02Blsrcm +WDZcXjE3Xpsi/IHFfRZLOdwwR+u8MNFxwRUVzxyIzKGtbREVVfXPfb2Xc6FL5/tE +vQtUKuR6XdzSaav2RnV5IybCB09s0Np0AUbdi5EfSe4INuqgY+VFYLjvM5onbAQL +N3bFLS4Quk66Dhv93Zi6NwopwL1F07UPC5uadkyePStP3PA0OAOemj9vZADOWx5a +dsGCKISs8iphNC5mDVoLy8Ry49Ms3eQXRjVQOMco3YNf8AhsIdxDNBVN8VTDKVkE +DwIDAQAB +-----END PUBLIC KEY-----"# + .as_bytes(), + &SigningScheme::RSA_PKCS1_SHA256(0), + ) + .expect("Cannot create CosignVerificationKey"); + let msg = r#"{"critical":{"identity":{"docker-reference":"registry.suse.com/suse/sle-micro/5.0/toolbox"},"image":{"docker-manifest-digest":"sha256:356631f7603526a0af827741f5fe005acf19b7ef7705a34241a91c2d47a6db5e"},"type":"cosign container image signature"},"optional":{"creator":"OBS"}}"#; - // assert!(verification_key - // .verify_signature(signature, &msg.as_bytes()) - // .is_ok()); - // } + assert!(verification_key + .verify_signature(signature, &msg.as_bytes()) + .is_ok()); + } } diff --git a/src/errors.rs b/src/errors.rs index 42ebd2a0c1..9a7f35d86b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -186,4 +186,10 @@ pub enum SigstoreError { #[error("Failed to parse the key: {0}")] KeyParseError(String), + + #[error(transparent)] + RSAError(#[from] rsa::errors::Error), + + #[error(transparent)] + PKCS1Error(#[from] pkcs1::Error), } diff --git a/tests/data/keys/rsa_encrypted_private.key b/tests/data/keys/rsa_encrypted_private.key new file mode 100644 index 0000000000..9d4ab62da7 --- /dev/null +++ b/tests/data/keys/rsa_encrypted_private.key @@ -0,0 +1,73 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiJMelBIYTM2VkllY01YZ1NkeXMyelIreHdpRWxTcGF0 +OXBUZFlLVXVOK3VVPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJkUncvUXMvOGFXUzdhZzJjS2JUM1pVMDhBdVR1QmswQiJ9LCJj +aXBoZXJ0ZXh0IjoiZDBRZUlIWmlpYVlCenlodXB5R0FFRmZtVzJCTFFxemJTRm9Y +VnRDek5JK2VJTm8xOU9RTW1VNHo0Z2NQY1FHbkE3ay9yc3h0NWRuWjFHOHpsZ0Fl +UzlUVFVMbWdSZjU5Y1oyRGRrOHRYVSs3R3R3YkNCTWFPY1hCSTBCUDc1VGJJK0F6 +MEUvT1drZjNhQlhOSE5veU5Ecklyc05obEluUTZ3ODIwZ3pPK0xUYmxVbnJ0UDVm +OFV6V0hDc09hazN1M0hIOXE4bytOZkJ2ZXV1cUp5MUNtckhYamJJWjRtaHR6b2tv +VnROeGQwTFpQN3c3aXJFbXBQaExLMHBWRHpnTlg1OTdGVXlNUzFGcVh1YnBDbFR6 +QVBQNUZmNG9kaXJMZVdGL3djUkxFNlhkcDkxeVRVZHoxZ2tQRnN3UmZPVmJYbWUz +d3VwNGhrMXQxeWpMZkdYbUYzWjdWWEFyRDhyenV5aDhsZUpaYlZ0ekRXSzlvVmw3 +STNIYmx6Qmx5eGplRjZZeVAvZ1JqUjBCOGZxN1F2a1RjdHlvcHgxdnNsM1E2S3ly +YitiWE5mMGdMSkxRQ01Id3h2TE0yaHFoek5jcDYxendoT1hoNjdpK2grbUtweXBk +a1FMOGZ2NEI1RTd0Y0prTVg2cTJoeHpMQVdWRXFwTUVqY1k0dmszd0dkLzhPckZQ +aFd3NEpMY1c5VTdSRUx3ZTM2RFpzbk9DNW0zK3VRZDJEYUNQSUhyUEw3eVFYOUhv +Qnp1UnFXQ3R3TXNXNWZmMFgycEhpY3l4MVdlY3JMc2crc2Uwck1QRUJNT3hXL3VZ +VjRKTnhBa0FuMTRsbGZqRmlDZlorUGNMcVhLOXVON3k4dU9VeGVYN0hCSmRCdmVT +emRiYXZoRVExbm4rU0NUK21YQjF0WXJBNFl5a2VMa2RzRWtXdGtHZFhnVjNFQmtM +TDByVzlWUENWdkEwTS9mU1ZScGpmQ3g3TkZiZ21GSkl0T1dWMmczWUxxN3h0WU1i +VFU5YWdpbzFBTktMeUtlYkFNdDJOUmw1T0sxSXdya29rUXo0WjZwZWlqeWJlMDBT +ekZ0akQ2cTVQK3VxTHZwT0NwMEphdTdScGRsOXJqN2lmMTRRRWozNFhvaml4Sm9a +ZXprY1k5SEp6QllHSWMyVnpQaEF0TjdIcnZoUDVlVHFaa2ozek5aZnBiT3Nja3BB +eFk5TTRNYWhjd3g4a0VGdy8vZFZKZWREVGx2WDc2ODAvL2t3RmROYmNTcmlZYnVD +UEkyR3Y2YXVnZ2RweCtWWTY2SmtZbzNYUVdmM255Z0xOUk0xMUVTTWVkVHFuL3NN +WnMrNGRDaG84b0tqbGhzZU9URkdDOEpWeENaV2IraHh2RUNNZ0RLTExZbW1PS2J1 +cC8vWjg3cVZtUGZjUjc5RVhmN2k1bUZ6QnpzRUFOSXYvaVNpTStSM1R4NXVIUEpr +eXRScTVJNkhvOWEzKzZPV3Q2V2dUbGd5aDdmZmZDWGJXamFGNVJHNE1TcDdQV2FD +QlBvZ3lnZ0ZFazRIWjUyRWt1S1B4TGZsc200QnpkT0FBdmZhaTloSmt0N2t4WUpS +YnVMb3cvMWZxaXNmNHJwcTU3WlZCelJBR09UYW1mT0Y3RnE1a1JNL0FEWStYZWlU +UVJ3ZVRFVk4wOWh1cHdhVm9Uc2lubmNJQlIrcUZMU2pERlJWOUI4M1BBdlNSU1hM +ZnpwbWgrdU4zY1dWL2k4N2xOb25wTlJUSXR3bUFvZjNRU2NPR0lsOUhiR2tWRDM1 +bUgvTDhQWVJMMXZlME0yUjdYbXBsWUY5NkhkU3NML3BiYlVLeGFpa0FwTmJCM2VW +U0FTYXdUUEdnYmhBWEtkNEE2YXRkSU5IRHRVbXVqWGxhYTlBL0JhN2h3SU1Ic1kr +R3RpL1RzQzhacmN2ZGUzQkh3cHZJdkpHdnRRUS9IZzVhSTMvRE5yazlUYjhQWUpH +RlBPTGU4ajU1YUtWYVdmeFZNZnBZYW9FL3pwN09ibEVFWnpLY3A4aFZ4S3p3eFJx +Vk1ueGJwYm9TbXBwVzd0Zjc1RXZiUVFyUEUwd2Mzc3EzSm9kMkRYUGlFQmFNOVhB +eGZvSTM5YmZ2L2tNUU9ES3p6U2x6SnV1cTFQZUFMVEh0SmVUQld0Wk1jdytFV3p4 +dTNkMndSMWJBQWtpdFVHNkxhRjdZN1BDR2dQWDdSelNVL3Y5bE9Qa0VFMUZhbUxM +TFJCZVU3NDU1WU9HSlFJb09TOVRsbGJyMWRVRi91cmRJbitDS2JhUHRWQ0k1TjNK +SWNFYzdiYTZsd3dBMkszQVNwY2lXM1MzUmxkcVV0NFdUbXBpcU9BVytETm1qcmts +eElFZ0YxOEZ0MzQ5MVBJMUlBWU05UVYrcDVEWnBzV28ycThTSWs3ZC9QL3hpVCtN +QkVxMDNNRjA0cWtPeDczeEplN0NqbWJ5UCtBbzluUWpocWpwWmxxWmJoc1RkS3FR +M252NXpMTmFDdUMwMFhiYUIvTmRjTGl3VEt4WnV3WHp6MFhmcE9ORnVheG9SWU9R +Z01WZlhSSTZ0RXNGaWdUVWgvQm5LaWNjWm5nYWVwNHNIL0Y5MjdES1FIdCtXalc5 +OHpHT24rZGFRVWxOV1hOOXlRNERoWFk1WmcremJHZ3hEZi90d1hMMjIzeS9zT0Rt +WXlJb2ZUWDNYRWN4YWdOOHBhSStkODNOWEJHdE4vRkNlNmpKejh2R2Fzci8wdnJy +c2w4RWozV0FaMXFLWlZsd1hYMTA3V2hzZXV5bEFVaVFZZWVKZ2NmSXRPVHpSYWs4 +WHVSekw1eVloR2gxU1hMTURPemNBTE5FWEY5cDg5TmFLSGFNcHUwU1hKWUZidlFS +UFNFUk42U2dnSkFLZXMxeDQxMk42STh2MXczdzJPUldDNEUya1RMUk5oNUhReWxp +MjhNMnUzNDNtVzlUU2NrN3ZPTWkza2ZuN0VxbUQ3bWl2TElTaENhR3I3TFpJY2R3 +bVFoNWtYdDN4NXVzdVZYMGhnMEtIZHo1TWpMenR3bnhxK1M4M3h6eVlHQlFmbnVa +Yk0vZ21OVlNiQVd3cm5pYmZuUi9aR2dZOGZFOG8vcFBiU295dWo0RE1GU0pQWmN6 +WHNSY004N21ZTWExd0RZdHl5ckYwSnFEQ3FZUmhQYmlSL25XSXhCckFZZW1wZXpu +V2RucjBJNjBaYnZ5VDhIc3M0dVQwNU0xN1VwZVdNTkxReUFtZHVqVE1FQUF4NWVx +N3llYmFGRWFvbVVFdWVFK3FmaWU0d3FjSEdJTlpVbmNFWlNqTXdBcXIwOFNGM0w4 +Z1M3ejk4bUtoYVIwM1p3QmVpLzJGa204SnZEbExRY1o4K2JPaEtwUGtXRTU1MjUr +Y082ZjRkUXd4cXpsL0YwYzRDWDZvSmJaVWNoM3BXN1JicE1oaU1vOTFuSFlEMnp2 +SFhnclhWMHBIRFlsaHFZVkJlWVRaZkQ5dTE1djluMDRwWHREbXltZXdlS2JIeWtH +Q0V1aG81WlI4T0JRRUl2czdaZGVtSFJrR2J1SENyMHpvVjFPam9MUk1xMmE2UzJI +ZWt0ZlJ0MlkyRHlIRzUzNDQrTmZzcGJYYWkxdVBhd1pkL1Y2eXRFMUJtLzdMYklQ +WVZRaHdINzY3aFFpUHY3bGZJaTQ4WGJoQ3RNakxDTmtaK1RHblBrQWw1Rmwxd0RC +eUNFMk13dlBUUlN5aEJHZlMzREJiM3JaSEFTL1Z1VFRmRHRnZFJJY2lyUzJBeWI4 +REE0S1lNdDlVQW5qOUxSc0NRdUNUdVVvTXhPRXdIVThRZm5MM3YvY1MrZy9vUG5L +U0crZ1hqVXozWWkyWFd6OHpWaHF1OHZJQWJoTVV2REtsWGtwTTJQVm9XTE5zQUtW +SVpKTTEvNlMxeDhnNkp2Y3ZNK1poQjd2eGdQVE1iZFVOZ24wRFd0em1wUHZtVWc2 +NHREOW00WFdlczJFODZXQ1JBY0lOY2FFYk1QaVFNeUlaUUQ5U2V0dXVQVDdUV3lq +SkRUQjNYWU0zc2lLN3ZiZ242N1cxRlZLamhZWGRCQm5tY2wyNnkzbWsyU0VPcmd4 +UVU5RDZCakRJOHhDQklTNWxnd212dXA0WWc3dCtSaUgwbG9XUE93RHEyRHBvSnJO +MEN3K0loMk4rRVNTNmgyaEpOTjFxTmN5c0pPK0NqR3U4d3ZIeG90OVlRaWptVXNE +YXJmQ3A0ZXRrZjF4Y0VkcVpnb1pqZ2M0K3k4M3Jhb1FEdz09In0= +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/tests/data/keys/rsa_private.key b/tests/data/keys/rsa_private.key new file mode 100644 index 0000000000..82fb5a73b4 --- /dev/null +++ b/tests/data/keys/rsa_private.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----