From bb013b84bbcd9fc1af834b9ff02b4d263d2f9ca4 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Wed, 24 Sep 2025 16:13:42 +0200 Subject: [PATCH 1/2] feat(pki): Allow to verify signature using a certificate - Use `rustls` crates to provide the parsing of a DER/PEM certificate - Add example to verify a generated signature (will work on allow platforms, but on unix, we are currently not integrated with a cert store) --- Cargo.lock | 34 +++++ Cargo.toml | 2 + libparsec/crates/platform_pki/Cargo.toml | 6 +- .../platform_pki/examples/decrypt_message.rs | 18 +-- .../platform_pki/examples/encrypt_message.rs | 18 +-- .../platform_pki/examples/sign_message.rs | 21 +-- .../crates/platform_pki/examples/utils/mod.rs | 26 ++++ .../platform_pki/examples/verify_message.rs | 97 ++++++++++++++ .../examples/windows_signature_roundtrip.ps1 | 12 ++ libparsec/crates/platform_pki/src/errors.rs | 14 ++ libparsec/crates/platform_pki/src/lib.rs | 21 ++- libparsec/crates/platform_pki/src/shared.rs | 120 ++++++++++++++++++ .../crates/platform_pki/src/windows/mod.rs | 10 +- 13 files changed, 337 insertions(+), 62 deletions(-) create mode 100644 libparsec/crates/platform_pki/examples/verify_message.rs create mode 100644 libparsec/crates/platform_pki/examples/windows_signature_roundtrip.ps1 create mode 100644 libparsec/crates/platform_pki/src/shared.rs diff --git a/Cargo.lock b/Cargo.lock index 4d3ac093a9c..b81699da4e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2416,6 +2416,9 @@ dependencies = [ "data-encoding", "error_set", "percent-encoding", + "rsa", + "rustls-pki-types", + "rustls-webpki", "schannel", "sha2", "windows-sys 0.61.0", @@ -3789,6 +3792,20 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rle-decode-fast" version = "1.0.3" @@ -3935,6 +3952,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.103.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -5023,6 +5051,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "ureq" version = "2.12.1" diff --git a/Cargo.toml b/Cargo.toml index c9060c5fe96..c2f544d4779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -186,6 +186,8 @@ rsa = { version = "0.8.2", default-features = false } rstest = { version = "0.24.0", default-features = false } rstest_reuse = { version = "0.7.0", default-features = false } ruzstd = { version = "0.7.3", default-features = true } +rustls-webpki = { version = "0.103.6", default-features = false } +rustls-pki-types = { version = "1.12.0", default-features = false } # Crate to interact with windows credential store, it's used notably in `native-tls` crate. schannel = { version = "0.1.28", default-features = false } sentry = { version = "0.34.0", default-features = false } diff --git a/libparsec/crates/platform_pki/Cargo.toml b/libparsec/crates/platform_pki/Cargo.toml index 24ec402e8e1..ba8ff6e5818 100644 --- a/libparsec/crates/platform_pki/Cargo.toml +++ b/libparsec/crates/platform_pki/Cargo.toml @@ -19,16 +19,18 @@ hash-sri-display = ["dep:data-encoding"] bytes = { workspace = true } data-encoding = { workspace = true, optional = true } error_set = { workspace = true } +rustls-webpki = { workspace = true, features = ["std"] } +rustls-pki-types = { workspace = true } +rsa = { workspace = true } +sha2 = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] schannel = { workspace = true } windows-sys = { workspace = true, features = ["Win32_Security_Cryptography_UI"] } -sha2 = { workspace = true } [target.'cfg(target_os = "linux")'.dev-dependencies] cryptoki = { workspace = true } percent-encoding = { workspace = true } -sha2 = { workspace = true } [dev-dependencies] # We use clap to provide a CLI for examples. diff --git a/libparsec/crates/platform_pki/examples/decrypt_message.rs b/libparsec/crates/platform_pki/examples/decrypt_message.rs index 84aa2a31737..cfc745de719 100644 --- a/libparsec/crates/platform_pki/examples/decrypt_message.rs +++ b/libparsec/crates/platform_pki/examples/decrypt_message.rs @@ -1,7 +1,6 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS mod utils; -use std::path::PathBuf; use anyhow::Context; use clap::Parser; @@ -15,31 +14,18 @@ struct Args { #[arg(value_parser = utils::CertificateSRIHashParser)] certificate_hash: CertificateHash, #[command(flatten)] - content: ContentOpts, + content: utils::ContentOpts, /// The algorithm used to encrypt the content. #[arg(long, default_value_t = EncryptionAlgorithm::RsaesOaepSha256)] algorithm: EncryptionAlgorithm, } -#[derive(Debug, Clone, clap::Args)] -#[group(required = true, multiple = false)] -struct ContentOpts { - #[arg(long, conflicts_with = "content_file")] - content: Option, - #[arg(long)] - content_file: Option, -} - fn main() -> anyhow::Result<()> { let args = Args::parse(); println!("args={args:?}"); let cert_ref = CertificateReference::Hash(args.certificate_hash); - let b64_data: Vec = match (args.content.content, args.content.content_file) { - (Some(content), None) => content.into(), - (None, Some(filepath)) => std::fs::read(filepath).context("Failed to read file")?, - (Some(_), Some(_)) | (None, None) => unreachable!("Handled by clap"), - }; + let b64_data = args.content.into_bytes()?; let data = data_encoding::BASE64 .decode(&b64_data) .context("Failed to decode hex encoded data")?; diff --git a/libparsec/crates/platform_pki/examples/encrypt_message.rs b/libparsec/crates/platform_pki/examples/encrypt_message.rs index b222e9688f4..64462c1f905 100644 --- a/libparsec/crates/platform_pki/examples/encrypt_message.rs +++ b/libparsec/crates/platform_pki/examples/encrypt_message.rs @@ -1,7 +1,6 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS mod utils; -use std::path::PathBuf; use anyhow::Context; use clap::Parser; @@ -13,16 +12,7 @@ struct Args { #[arg(value_parser = utils::CertificateSRIHashParser)] certificate_hash: CertificateHash, #[command(flatten)] - content: ContentOpts, -} - -#[derive(Debug, Clone, clap::Args)] -#[group(required = true, multiple = false)] -struct ContentOpts { - #[arg(long, conflicts_with = "content_file")] - content: Option, - #[arg(long)] - content_file: Option, + content: utils::ContentOpts, } fn main() -> anyhow::Result<()> { @@ -30,11 +20,7 @@ fn main() -> anyhow::Result<()> { println!("args={args:?}"); let cert_ref = CertificateReference::Hash(args.certificate_hash); - let data: Vec = match (args.content.content, args.content.content_file) { - (Some(content), None) => content.into(), - (None, Some(filepath)) => std::fs::read(filepath).context("Failed to read file")?, - (Some(_), Some(_)) | (None, None) => unreachable!("Handled by clap"), - }; + let data = args.content.into_bytes()?; let res = encrypt_message(&data, &cert_ref).context("Failed to encrypt message")?; diff --git a/libparsec/crates/platform_pki/examples/sign_message.rs b/libparsec/crates/platform_pki/examples/sign_message.rs index e19126aa8d9..e6931d6cb57 100644 --- a/libparsec/crates/platform_pki/examples/sign_message.rs +++ b/libparsec/crates/platform_pki/examples/sign_message.rs @@ -1,7 +1,6 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS mod utils; -use std::path::PathBuf; use anyhow::Context; use clap::Parser; @@ -13,16 +12,7 @@ struct Args { #[arg(value_parser = utils::CertificateSRIHashParser)] certificate_hash: CertificateHash, #[command(flatten)] - content: ContentOpts, -} - -#[derive(Debug, Clone, clap::Args)] -#[group(required = true, multiple = false)] -struct ContentOpts { - #[arg(long, conflicts_with = "content_file")] - content: Option, - #[arg(long)] - content_file: Option, + content: utils::ContentOpts, } fn main() -> anyhow::Result<()> { @@ -30,12 +20,7 @@ fn main() -> anyhow::Result<()> { println!("args={args:?}"); let cert_ref = CertificateReference::Hash(args.certificate_hash); - let data: Vec = match (args.content.content, args.content.content_file) { - (Some(content), None) => content.into(), - (None, Some(filepath)) => std::fs::read(filepath).context("Failed to read file")?, - (Some(_), Some(_)) | (None, None) => unreachable!("Handled by clap"), - }; - + let data: Vec = args.content.into_bytes()?; let res = sign_message(&data, &cert_ref).context("Failed to sign message")?; println!( @@ -47,7 +32,7 @@ fn main() -> anyhow::Result<()> { println!("Signed by cert with fingerprint: {}", res.cert_ref.hash); println!( "Signature: {}", - data_encoding::BASE64.encode_display(&res.signed_message) + data_encoding::BASE64.encode_display(&res.signature) ); Ok(()) diff --git a/libparsec/crates/platform_pki/examples/utils/mod.rs b/libparsec/crates/platform_pki/examples/utils/mod.rs index 38f3bb5e1d5..588c1577e50 100644 --- a/libparsec/crates/platform_pki/examples/utils/mod.rs +++ b/libparsec/crates/platform_pki/examples/utils/mod.rs @@ -1,5 +1,8 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS +use std::path::PathBuf; + +use anyhow::Context; use clap::{ builder::{NonEmptyStringValueParser, TypedValueParser}, error::{Error, ErrorKind}, @@ -38,3 +41,26 @@ impl TypedValueParser for CertificateSRIHashParser { } } } + +#[derive(Debug, Clone, clap::Args)] +#[group(required = true, multiple = false)] +pub struct ContentOpts { + /// The content to use. + #[arg(long)] + pub content: Option, + /// Read content from a file + #[arg(long)] + pub content_file: Option, +} + +impl ContentOpts { + // Not all examples uses `ContentOpts` so `into_bytes` is not always used. + #[allow(dead_code)] + pub fn into_bytes(self) -> anyhow::Result> { + match (self.content, self.content_file) { + (Some(content), None) => Ok(content.into()), + (None, Some(filepath)) => std::fs::read(filepath).context("Failed to read file"), + (Some(_), Some(_)) | (None, None) => unreachable!("Handled by clap"), + } + } +} diff --git a/libparsec/crates/platform_pki/examples/verify_message.rs b/libparsec/crates/platform_pki/examples/verify_message.rs new file mode 100644 index 00000000000..23e2899bb0d --- /dev/null +++ b/libparsec/crates/platform_pki/examples/verify_message.rs @@ -0,0 +1,97 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::path::PathBuf; + +use anyhow::Context; +use clap::Parser; +use libparsec_platform_pki::{ + get_der_encoded_certificate, verify_message, Certificate, CertificateHash, SignatureAlgorithm, + SignedMessage, +}; +use sha2::Digest; + +mod utils; + +#[derive(Debug, Parser)] +struct Args { + #[command(flatten)] + cert: CertificateOrRef, + #[command(flatten)] + content: utils::ContentOpts, + signature_header: SignatureAlgorithm, + /// Signature in base64 + signature: String, +} + +#[derive(Debug, Clone, clap::Args)] +#[group(required = true, multiple = false)] +struct CertificateOrRef { + /// Hash of the certificate from the store to use to verify the signature. + #[arg(long, value_parser = utils::CertificateSRIHashParser)] + certificate_hash: Option, + /// Path to a file containing the certificate in DER format. + #[arg(long)] + der_file: Option, + /// Path to a file containing the certificate in PEM format. + #[arg(long)] + pem_file: Option, + /// Certificate in PEM format but without headers. + #[arg(long)] + pem: Option, +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + println!("args={args:?}"); + + let data = args.content.into_bytes()?; + + let signature = data_encoding::BASE64 + .decode(args.signature.as_bytes()) + .context("Invalid signature format")?; + + let cert = if let Some(hash) = args.cert.certificate_hash { + let res = + get_der_encoded_certificate(&libparsec_platform_pki::CertificateReference::Hash(hash))?; + println!( + "Will verify signature using cert with id {{{}}}", + data_encoding::BASE64.encode_display(&res.cert_ref.id) + ); + Certificate::from_der_owned(res.der_content.into()) + } else if let Some(der_file) = args.cert.der_file { + let raw = std::fs::read(der_file).context("Failed to read file")?; + Certificate::from_der_owned(raw) + } else if let Some(pem_file) = args.cert.pem_file { + let raw = std::fs::read(pem_file).context("Failed to read file")?; + Certificate::try_from_pem(&r#raw)?.into_owned() + } else if let Some(pem) = args.cert.pem { + let raw = data_encoding::BASE64 + .decode(pem.as_bytes()) + .context("Invalid pem base64")?; + Certificate::from_der_owned(raw) + } else { + unreachable!("Should be handle by clap") + }; + + #[cfg(feature = "hash-sri-display")] + { + let fingerprint = + CertificateHash::SHA256(Box::new(sha2::Sha256::digest(cert.as_ref()).into())); + println!("Certificate fingerprint: {fingerprint}"); + } + + let signed_message = SignedMessage { + algo: args.signature_header, + signature, + message: data, + }; + + match verify_message(&signed_message, cert) { + Ok(_) => { + println!("The message as a correct signature") + } + Err(e) => println!("The message as an incorrect signature: {e}"), + } + + Ok(()) +} diff --git a/libparsec/crates/platform_pki/examples/windows_signature_roundtrip.ps1 b/libparsec/crates/platform_pki/examples/windows_signature_roundtrip.ps1 new file mode 100644 index 00000000000..d836a86fb42 --- /dev/null +++ b/libparsec/crates/platform_pki/examples/windows_signature_roundtrip.ps1 @@ -0,0 +1,12 @@ +cargo build --examples -p libparsec_platform_pki + +$certificate_fingerprint = cargo run -p libparsec_platform_pki --example get_certificate_der | Select-String -Pattern '^fingerprint: .*$' | ForEach-Object { $_.line.Split(': ')[-1] } + +$sign_algo, $signature = cargo run -p libparsec_platform_pki --example sign_message -- $certificate_fingerprint --content-file Cargo.toml | Select-String -Pattern 'with algorithm .*','^Signature: .*$' | ForEach-Object { $_.line.Split(': ')[-1] } + +echo "Signing content of Cargo.toml" +echo "using algo: $sign_algo" +echo "and certificate: $certificate_fingerprint" +echo "Result in signature: $signature" + +cargo run -p libparsec_platform_pki --example verify_message -- --certificate-hash $certificate_fingerprint --content-file Cargo.toml $sign_algo $signature diff --git a/libparsec/crates/platform_pki/src/errors.rs b/libparsec/crates/platform_pki/src/errors.rs index de568c8766f..15f0f4739ca 100644 --- a/libparsec/crates/platform_pki/src/errors.rs +++ b/libparsec/crates/platform_pki/src/errors.rs @@ -26,4 +26,18 @@ error_set::error_set! { #[display("Cannot decrypt message: {0}")] CannotDecrypt(std::io::Error), }; + InvalidCertificateDer = { + #[display("Invalid certificate: {0}")] + InvalidCertificateDer(webpki::Error), + }; + VerifySignatureError = InvalidCertificateDer || { + #[display("Invalid signature for the given message and certificate")] + InvalidSignature, + #[display("Unexpected signature will verifying signature of a message: {0}")] + UnexpectedError(webpki::Error) + }; + InvalidPemContent = { + #[display("Invalid PEM content: {0}")] + InvalidPemContent(rustls_pki_types::pem::Error) + }; } diff --git a/libparsec/crates/platform_pki/src/lib.rs b/libparsec/crates/platform_pki/src/lib.rs index d46e0c38752..03807123fdd 100644 --- a/libparsec/crates/platform_pki/src/lib.rs +++ b/libparsec/crates/platform_pki/src/lib.rs @@ -1,6 +1,7 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS pub mod errors; +mod shared; #[cfg(target_os = "windows")] mod windows; @@ -11,7 +12,8 @@ use bytes::Bytes; #[cfg(target_os = "windows")] pub(crate) use windows as platform; -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(not(feature = "hash-sri-display"), derive(Debug))] pub enum CertificateHash { SHA256(Box<[u8; 32]>), } @@ -30,6 +32,13 @@ impl std::fmt::Display for CertificateHash { } } +#[cfg(feature = "hash-sri-display")] +impl std::fmt::Debug for CertificateHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + pub enum CertificateReference { Id(Bytes), Hash(CertificateHash), @@ -56,7 +65,7 @@ mod platform { use crate::{ CertificateDer, CertificateReference, DecryptMessageError, DecryptedMessage, EncryptMessageError, EncryptedMessage, EncryptionAlgorithm, GetDerEncodedCertificateError, - SignMessageError, SignedMessage, + SignMessageError, SignedMessageFromPki, }; pub fn get_der_encoded_certificate( @@ -69,7 +78,7 @@ mod platform { pub fn sign_message( message: &[u8], certificate_ref: &CertificateReference, - ) -> Result { + ) -> Result { let _ = message; let _ = certificate_ref; unimplemented!("platform not supported") @@ -131,15 +140,17 @@ impl FromStr for SignatureAlgorithm { } } -pub struct SignedMessage { +pub struct SignedMessageFromPki { pub algo: SignatureAlgorithm, pub cert_ref: CertificateReferenceIdOrHash, - pub signed_message: Bytes, + pub signature: Bytes, } pub use errors::SignMessageError; pub use platform::sign_message; +pub use shared::{verify_message, Certificate, SignedMessage}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EncryptionAlgorithm { RsaesOaepSha256, diff --git a/libparsec/crates/platform_pki/src/shared.rs b/libparsec/crates/platform_pki/src/shared.rs new file mode 100644 index 00000000000..4a799f21720 --- /dev/null +++ b/libparsec/crates/platform_pki/src/shared.rs @@ -0,0 +1,120 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use crate::{ + errors::{InvalidPemContent, VerifySignatureError}, + SignatureAlgorithm, +}; +use rsa::{ + pkcs1, + pss::{Signature, VerifyingKey}, + signature::Verifier, + RsaPublicKey, +}; +use rustls_pki_types::{ + pem::PemObject, CertificateDer, InvalidSignature, SignatureVerificationAlgorithm, +}; +use sha2::Sha256; +use webpki::{EndEntityCert, Error as WebPkiError}; + +pub struct Certificate<'a> { + internal: CertificateDer<'a>, +} + +impl<'a> Certificate<'a> { + pub fn try_from_pem(raw: &'a [u8]) -> Result { + CertificateDer::from_pem_slice(raw) + .map(Self::new) + .map_err(Into::into) + } + + pub fn from_der(raw: &'a [u8]) -> Self { + Self::new(raw.into()) + } + + pub fn new(cert: CertificateDer<'a>) -> Self { + Self { internal: cert } + } + + pub fn into_owned(&self) -> Certificate<'static> { + Certificate::new(self.internal.clone().into_owned()) + } +} + +impl Certificate<'static> { + pub fn from_der_owned(raw: Vec) -> Self { + Self::new(raw.into()) + } +} + +impl AsRef<[u8]> for Certificate<'_> { + fn as_ref(&self) -> &[u8] { + self.internal.as_ref() + } +} + +pub struct SignedMessage { + pub algo: SignatureAlgorithm, + pub signature: Vec, + pub message: Vec, +} + +pub fn verify_message<'message>( + signed_message: &'message SignedMessage, + certificate: Certificate<'_>, +) -> Result<&'message [u8], VerifySignatureError> { + let verifier = match signed_message.algo { + SignatureAlgorithm::RsassaPssSha256 => &RsassaPssSha256SignatureVerifier, + }; + EndEntityCert::try_from(&certificate.internal) + .map_err(VerifySignatureError::InvalidCertificateDer)? + .verify_signature(verifier, &signed_message.message, &signed_message.signature) + .map(|_| signed_message.message.as_ref()) + .map_err(|e| match e { + WebPkiError::InvalidSignatureForPublicKey => VerifySignatureError::InvalidSignature, + e => VerifySignatureError::UnexpectedError(e), + }) +} + +#[derive(Debug)] +struct RsassaPssSha256SignatureVerifier; + +impl SignatureVerificationAlgorithm for RsassaPssSha256SignatureVerifier { + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + // Webpki already checked that the key part correspond to an RSA public key. + // + // We are not using `pkcs8::DecodePublicKey::from_public_key_der` as + // `public_key` is already the unwrapped key from the `subjectPublicKeyInfo` structure + // which it's the expected data from the above method. + // + // Instead, we use `pkcs1::RsaPublicKey::try_from(&[u8])` which only expect the key element + // (without the algorithm identifier). + let pubkey = pkcs1::RsaPublicKey::try_from(public_key) + .map_err(|_| InvalidSignature) + // But `rsa` does not provide a conversion between the `pkcs1` and its `RsaPublicKey`, so + // we need to perform the manual conversion + .and_then(|pubkey| { + let n = rsa::BigUint::from_bytes_be(pubkey.modulus.as_bytes()); + let e = rsa::BigUint::from_bytes_be(pubkey.public_exponent.as_bytes()); + RsaPublicKey::new(n, e).map_err(|_| InvalidSignature) + })?; + let verifying_key = VerifyingKey::::new(pubkey); + + let signature = Signature::try_from(signature).map_err(|_| InvalidSignature)?; + verifying_key + .verify(message, &signature) + .map_err(|_| InvalidSignature) + } + + fn public_key_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier { + rustls_pki_types::alg_id::RSA_ENCRYPTION + } + + fn signature_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier { + rustls_pki_types::alg_id::RSA_PSS_SHA256 + } +} diff --git a/libparsec/crates/platform_pki/src/windows/mod.rs b/libparsec/crates/platform_pki/src/windows/mod.rs index c4616cc69f0..e900cace759 100644 --- a/libparsec/crates/platform_pki/src/windows/mod.rs +++ b/libparsec/crates/platform_pki/src/windows/mod.rs @@ -6,7 +6,7 @@ use crate::{ CertificateDer, CertificateHash, CertificateReference, CertificateReferenceIdOrHash, DecryptMessageError, DecryptedMessage, EncryptMessageError, EncryptedMessage, EncryptionAlgorithm, GetDerEncodedCertificateError, SignMessageError, SignatureAlgorithm, - SignedMessage, + SignedMessageFromPki, }; use bytes::Bytes; use schannel::{ @@ -215,14 +215,14 @@ fn ask_user_to_select_certificate(store: &CertStore) -> Option { pub fn sign_message( message: &[u8], certificate_ref: &CertificateReference, -) -> Result { +) -> Result { let store = open_store().map_err(SignMessageError::CannotOpenStore)?; let cert_context = find_certificate(&store, certificate_ref).ok_or(SignMessageError::NotFound)?; let reference = get_id_and_hash_from_cert_context(&cert_context) .map_err(SignMessageError::CannotGetCertificateInfo)?; let keypair = get_keypair(&cert_context)?; - let (algo, signed_message) = match keypair { + let (algo, signature) = match keypair { // We do not support a CryptoAPI provider as its API is marked for depreciation by windows. PrivateKey::CryptProv(..) => { todo!("Use CryptGetUserKey to get the keypair") @@ -232,9 +232,9 @@ pub fn sign_message( } .map_err(SignMessageError::CannotSign)?; - Ok(SignedMessage { + Ok(SignedMessageFromPki { algo, - signed_message: signed_message.into(), + signature: signature.into(), cert_ref: reference, }) } From 025e47cff5b5761fa8f8d471afb6277f75c8baa0 Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Fri, 26 Sep 2025 14:14:15 +0200 Subject: [PATCH 2/2] feat(pki): Add script to test encryption roundtrip --- .../examples/windows_encrypt_roundtrip.ps1 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 libparsec/crates/platform_pki/examples/windows_encrypt_roundtrip.ps1 diff --git a/libparsec/crates/platform_pki/examples/windows_encrypt_roundtrip.ps1 b/libparsec/crates/platform_pki/examples/windows_encrypt_roundtrip.ps1 new file mode 100644 index 00000000000..f8ddd52150e --- /dev/null +++ b/libparsec/crates/platform_pki/examples/windows_encrypt_roundtrip.ps1 @@ -0,0 +1,19 @@ +cargo build --examples -p libparsec_platform_pki + +$certificate_fingerprint = cargo run -p libparsec_platform_pki --example get_certificate_der | Select-String -Pattern '^fingerprint: .*$' | ForEach-Object { $_.line.Split(': ')[-1] } + +$content_string = "Hello world" + +if ($content_string.Length > 2048) { + echo "The content to encrypt is too big for the 2048-RSA key" + exit +} + +$encrypt_algo, $ciphered = cargo run -p libparsec_platform_pki --example encrypt_message -- $certificate_fingerprint --content $content_string | Select-String -Pattern 'using the algorithm .*','^Encrypted data: .*$' | ForEach-Object { $_.line.Split(': ')[-1] } + +echo "Encrypt message: $content_string" +echo "using algo: $encrypt_algo" +echo "and certificate: $certificate_fingerprint" +echo "Result in ciphered: $ciphered" + +cargo run -p libparsec_platform_pki --example decrypt_message -- $certificate_fingerprint --content $ciphered --algorithm $encrypt_algo