Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 4 additions & 2 deletions libparsec/crates/platform_pki/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 2 additions & 16 deletions libparsec/crates/platform_pki/examples/decrypt_message.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String>,
#[arg(long)]
content_file: Option<PathBuf>,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
println!("args={args:?}");

let cert_ref = CertificateReference::Hash(args.certificate_hash);
let b64_data: Vec<u8> = 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")?;
Expand Down
18 changes: 2 additions & 16 deletions libparsec/crates/platform_pki/examples/encrypt_message.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,28 +12,15 @@ 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<String>,
#[arg(long)]
content_file: Option<PathBuf>,
content: utils::ContentOpts,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
println!("args={args:?}");

let cert_ref = CertificateReference::Hash(args.certificate_hash);
let data: Vec<u8> = 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")?;

Expand Down
21 changes: 3 additions & 18 deletions libparsec/crates/platform_pki/examples/sign_message.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,29 +12,15 @@ 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<String>,
#[arg(long)]
content_file: Option<PathBuf>,
content: utils::ContentOpts,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
println!("args={args:?}");

let cert_ref = CertificateReference::Hash(args.certificate_hash);
let data: Vec<u8> = 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<u8> = args.content.into_bytes()?;
let res = sign_message(&data, &cert_ref).context("Failed to sign message")?;

println!(
Expand All @@ -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(())
Expand Down
26 changes: 26 additions & 0 deletions libparsec/crates/platform_pki/examples/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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<String>,
/// Read content from a file
#[arg(long)]
pub content_file: Option<PathBuf>,
}

impl ContentOpts {
// Not all examples uses `ContentOpts` so `into_bytes` is not always used.
#[allow(dead_code)]
pub fn into_bytes(self) -> anyhow::Result<Vec<u8>> {
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"),
}
}
}
97 changes: 97 additions & 0 deletions libparsec/crates/platform_pki/examples/verify_message.rs
Original file line number Diff line number Diff line change
@@ -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<CertificateHash>,
/// Path to a file containing the certificate in DER format.
#[arg(long)]
der_file: Option<PathBuf>,
/// Path to a file containing the certificate in PEM format.
#[arg(long)]
pem_file: Option<PathBuf>,
/// Certificate in PEM format but without headers.
#[arg(long)]
pem: Option<String>,
}

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(())
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading