Wire Apple KMS release signing
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 11m0s
Build Site / Next.js Build (push) Failing after 5s
Lint Governance / BEP Metadata (push) Successful in 1s

This commit is contained in:
Conrad Kramer 2026-06-07 07:28:48 -07:00
parent ed73d7d16a
commit 126af6b5cf
17 changed files with 1913 additions and 16 deletions

View file

@ -0,0 +1,522 @@
--- a/apple-codesign/src/cli/certificate_source.rs
+++ b/apple-codesign/src/cli/certificate_source.rs
@@ -16,0 +17 @@
+ bytes::Bytes,
@@ -19,0 +21 @@
+ signature::Signer,
@@ -21,2 +23,10 @@
- std::path::PathBuf,
- x509_certificate::CapturedX509Certificate,
+ std::{
+ path::PathBuf,
+ process::Command,
+ sync::atomic::{AtomicU64, Ordering},
+ },
+ x509_certificate::{
+ CapturedX509Certificate, KeyAlgorithm, Sign, Signature, SignatureAlgorithm,
+ X509CertificateError,
+ },
+ zeroize::Zeroizing,
@@ -36,0 +47,2 @@
+static PKCS11_SIGN_COUNTER: AtomicU64 = AtomicU64::new(0);
+
@@ -204,0 +214,262 @@
+#[derive(Clone, Debug)]
+struct Pkcs11PrivateKey {
+ tool: String,
+ module: PathBuf,
+ slot_id: Option<String>,
+ token_label: Option<String>,
+ key_id: Option<String>,
+ key_label: Option<String>,
+ pin: String,
+ cert: CapturedX509Certificate,
+}
+
+impl signature::Signer<Signature> for Pkcs11PrivateKey {
+ fn try_sign(&self, message: &[u8]) -> Result<Signature, signature::Error> {
+ match self.cert.key_algorithm() {
+ Some(KeyAlgorithm::Rsa) => {}
+ Some(algorithm) => {
+ return Err(signature::Error::from_source(format!(
+ "PKCS#11 signing currently supports RSA certificates, not {algorithm:?}"
+ )));
+ }
+ None => {
+ return Err(signature::Error::from_source(
+ "failed to resolve PKCS#11 certificate key algorithm",
+ ));
+ }
+ }
+
+ let sequence = PKCS11_SIGN_COUNTER.fetch_add(1, Ordering::Relaxed);
+ let base = std::env::temp_dir().join(format!(
+ "rcodesign-pkcs11-{}-{sequence}",
+ std::process::id()
+ ));
+ let input_path = base.with_extension("in");
+ let output_path = base.with_extension("sig");
+ let openssl_config_path = base.with_extension("openssl.cnf");
+ let provider_debug_path = base.with_extension("pkcs11-provider.log");
+
+ std::fs::write(&input_path, message).map_err(signature::Error::from_source)?;
+
+ let provider_override =
+ std::env::var("BURROW_APPLE_PKCS11_SIGN_WITH_OPENSSL_PROVIDER")
+ .or_else(|_| std::env::var("BURROW_PKCS11_SIGN_WITH_OPENSSL_PROVIDER"))
+ .ok()
+ .and_then(|value| {
+ if value.trim().is_empty() {
+ None
+ } else {
+ Some(matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES" | "on" | "ON"))
+ }
+ });
+ let use_openssl_provider = provider_override.unwrap_or_else(|| {
+ std::env::var("BURROW_APPLE_PKCS11_BACKEND")
+ .or_else(|_| std::env::var("BURROW_PKCS11_BACKEND"))
+ .map(|value| value == "gcp-kms")
+ .unwrap_or(false)
+ || self.module.to_string_lossy().contains("kmsp11")
+ || std::env::var("BURROW_GCP_KMS_PKCS11_CONFIG").is_ok()
+ || std::env::var("KMS_PKCS11_CONFIG").is_ok()
+ });
+
+ if use_openssl_provider {
+ let token_label = self.token_label.as_ref().ok_or_else(|| {
+ signature::Error::from_source(
+ "OpenSSL PKCS#11 provider signing requires --pkcs11-token-label",
+ )
+ })?;
+ let mut key_uri = format!("pkcs11:token={token_label}");
+ if let Some(label) = &self.key_label {
+ key_uri.push_str(";object=");
+ key_uri.push_str(label);
+ } else if let Some(id) = &self.key_id {
+ key_uri.push_str(";id=");
+ key_uri.push_str(id);
+ } else {
+ return Err(signature::Error::from_source(
+ "OpenSSL PKCS#11 provider signing requires --pkcs11-key-label or --pkcs11-key-id",
+ ));
+ }
+ key_uri.push_str(";type=private");
+
+ let pin_path = base.with_extension("pin");
+ std::fs::write(&pin_path, &self.pin).map_err(signature::Error::from_source)?;
+
+ let module_path = self.module.to_string_lossy();
+ let kms_config = std::env::var("KMS_PKCS11_CONFIG")
+ .or_else(|_| std::env::var("BURROW_GCP_KMS_PKCS11_CONFIG"))
+ .ok();
+ let needs_kms_config = module_path.contains("kmsp11") || kms_config.is_some();
+ if needs_kms_config {
+ let config_path = kms_config.as_ref().ok_or_else(|| {
+ signature::Error::from_source(
+ "OpenSSL PKCS#11 provider signing with Google KMS requires KMS_PKCS11_CONFIG",
+ )
+ })?;
+ let metadata = std::fs::metadata(config_path).map_err(|err| {
+ signature::Error::from_source(format!(
+ "OpenSSL PKCS#11 provider signing could not read KMS_PKCS11_CONFIG at {config_path}: {err}"
+ ))
+ })?;
+ if !metadata.is_file() || metadata.len() == 0 {
+ return Err(signature::Error::from_source(format!(
+ "OpenSSL PKCS#11 provider signing requires a non-empty KMS_PKCS11_CONFIG file at {config_path}"
+ )));
+ }
+ }
+
+ let openssl_modules = std::env::var("BURROW_OPENSSL_MODULES")
+ .or_else(|_| std::env::var("OPENSSL_MODULES"))
+ .ok()
+ .filter(|value| !value.trim().is_empty());
+ if let Some(modules) = &openssl_modules {
+ let provider_extension = if cfg!(target_os = "macos") {
+ "dylib"
+ } else {
+ "so"
+ };
+ let provider_module = PathBuf::from(modules)
+ .join(format!("pkcs11.{provider_extension}"));
+ if provider_module.is_file() {
+ let config = format!(
+ "HOME = .\nopenssl_conf = openssl_init\n\n[openssl_init]\nproviders = provider_sect\n\n[provider_sect]\ndefault = default_sect\npkcs11 = pkcs11_sect\n\n[default_sect]\nactivate = 1\n\n[pkcs11_sect]\nmodule = {}\npkcs11-module-path = {}\npkcs11-module-token-pin = file:{}\npkcs11-module-cache-pins = cache\npkcs11-module-quirks = no-operation-state no-allowed-mechanisms\nactivate = 1\n",
+ provider_module.display(),
+ self.module.display(),
+ pin_path.display(),
+ );
+ std::fs::write(&openssl_config_path, config)
+ .map_err(signature::Error::from_source)?;
+ }
+ }
+
+ let openssl = std::env::var("BURROW_OPENSSL").unwrap_or_else(|_| "openssl".into());
+ let mut command = Command::new(openssl);
+ command
+ .env("PKCS11_PROVIDER_MODULE", &self.module)
+ .arg("dgst")
+ .arg("-provider")
+ .arg("pkcs11")
+ .arg("-provider")
+ .arg("default")
+ .arg("-sha256")
+ .arg("-sign")
+ .arg(&key_uri)
+ .arg("-passin")
+ .arg(format!("file:{}", pin_path.display()))
+ .arg("-out")
+ .arg(&output_path)
+ .arg(&input_path);
+ if let Some(config_path) = kms_config {
+ command
+ .env("KMS_PKCS11_CONFIG", &config_path)
+ .env("BURROW_GCP_KMS_PKCS11_CONFIG", &config_path);
+ }
+ if let Ok(credentials_path) = std::env::var("GOOGLE_APPLICATION_CREDENTIALS") {
+ command.env("GOOGLE_APPLICATION_CREDENTIALS", credentials_path);
+ }
+ if let Some(modules) = openssl_modules {
+ command.env("OPENSSL_MODULES", modules);
+ }
+ if openssl_config_path.is_file() {
+ command.env("OPENSSL_CONF", &openssl_config_path);
+ }
+ if let Ok(debug) = std::env::var("BURROW_PKCS11_PROVIDER_DEBUG")
+ .or_else(|_| std::env::var("PKCS11_PROVIDER_DEBUG"))
+ {
+ command.env("PKCS11_PROVIDER_DEBUG", debug);
+ } else {
+ command.env(
+ "PKCS11_PROVIDER_DEBUG",
+ format!("file:{},level:1", provider_debug_path.display()),
+ );
+ }
+
+ let output = command.output().map_err(signature::Error::from_source)?;
+ let _ = std::fs::remove_file(&input_path);
+ let _ = std::fs::remove_file(&pin_path);
+ let _ = std::fs::remove_file(&openssl_config_path);
+
+ if !output.status.success() {
+ let _ = std::fs::remove_file(&output_path);
+ let provider_debug = std::fs::read_to_string(&provider_debug_path)
+ .ok()
+ .filter(|value| !value.trim().is_empty())
+ .map(|value| {
+ let tail = value
+ .lines()
+ .rev()
+ .take(80)
+ .collect::<Vec<_>>()
+ .into_iter()
+ .rev()
+ .collect::<Vec<_>>()
+ .join("\n");
+ format!("\nPKCS#11 provider debug tail:\n{tail}\n")
+ })
+ .unwrap_or_default();
+ let _ = std::fs::remove_file(&provider_debug_path);
+ return Err(signature::Error::from_source(format!(
+ "OpenSSL PKCS#11 provider signing failed with status {}: {}{}{}",
+ output.status,
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr),
+ provider_debug
+ )));
+ }
+ let _ = std::fs::remove_file(&provider_debug_path);
+ } else {
+ let mut command = Command::new(&self.tool);
+ command
+ .arg("--module")
+ .arg(&self.module)
+ .arg("--login")
+ .arg("--pin")
+ .arg(&self.pin)
+ .arg("--sign")
+ .arg("--mechanism")
+ .arg("SHA256-RSA-PKCS")
+ .arg("--input-file")
+ .arg(&input_path)
+ .arg("--output-file")
+ .arg(&output_path);
+
+ if let Some(slot_id) = &self.slot_id {
+ command.arg("--slot").arg(slot_id);
+ }
+ if let Some(token_label) = &self.token_label {
+ command.arg("--token-label").arg(token_label);
+ }
+ if let Some(id) = &self.key_id {
+ command.arg("--id").arg(id);
+ }
+ if let Some(label) = &self.key_label {
+ command.arg("--label").arg(label);
+ }
+
+ let output = command.output().map_err(signature::Error::from_source)?;
+
+ let _ = std::fs::remove_file(&input_path);
+
+ if !output.status.success() {
+ let _ = std::fs::remove_file(&output_path);
+ return Err(signature::Error::from_source(format!(
+ "pkcs11-tool signing failed with status {}: {}{}",
+ output.status,
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ )));
+ }
+ }
+
+ let signature = std::fs::read(&output_path).map_err(signature::Error::from_source)?;
+ let _ = std::fs::remove_file(&output_path);
+
+ if signature.is_empty() {
+ return Err(signature::Error::from_source(
+ "pkcs11-tool produced an empty signature",
+ ));
+ }
+
+ Ok(signature.into())
+ }
+}
@@ -204,0 +384,214 @@
+impl Sign for Pkcs11PrivateKey {
+ fn sign(&self, message: &[u8]) -> Result<(Vec<u8>, SignatureAlgorithm), X509CertificateError> {
+ let algorithm = self.signature_algorithm()?;
+
+ Ok((self.try_sign(message)?.into(), algorithm))
+ }
+
+ fn key_algorithm(&self) -> Option<KeyAlgorithm> {
+ self.cert.key_algorithm()
+ }
+
+ fn public_key_data(&self) -> Bytes {
+ self.cert.public_key_data()
+ }
+
+ fn signature_algorithm(&self) -> Result<SignatureAlgorithm, X509CertificateError> {
+ match self.cert.key_algorithm() {
+ Some(KeyAlgorithm::Rsa) => Ok(SignatureAlgorithm::RsaSha256),
+ Some(algorithm) => Err(X509CertificateError::UnknownSignatureAlgorithm(format!(
+ "PKCS#11 signing currently supports RSA certificates, not {algorithm:?}"
+ ))),
+ None => Err(X509CertificateError::UnknownSignatureAlgorithm(
+ "failed to resolve PKCS#11 certificate key algorithm".into(),
+ )),
+ }
+ }
+
+ fn private_key_data(&self) -> Option<Zeroizing<Vec<u8>>> {
+ None
+ }
+
+ fn rsa_primes(
+ &self,
+ ) -> Result<Option<(Zeroizing<Vec<u8>>, Zeroizing<Vec<u8>>)>, X509CertificateError> {
+ Ok(None)
+ }
+}
+
+impl x509_certificate::KeyInfoSigner for Pkcs11PrivateKey {}
+
+impl PrivateKey for Pkcs11PrivateKey {
+ fn as_key_info_signer(&self) -> &dyn x509_certificate::KeyInfoSigner {
+ self
+ }
+
+ fn to_public_key_peer_decrypt(
+ &self,
+ ) -> Result<
+ Box<dyn crate::remote_signing::session_negotiation::PublicKeyPeerDecrypt>,
+ AppleCodesignError,
+ > {
+ Err(AppleCodesignError::CliGeneralError(
+ "PKCS#11 keys cannot decrypt remote-signing peer setup data".into(),
+ ))
+ }
+
+ fn finish(&self) -> Result<(), AppleCodesignError> {
+ Ok(())
+ }
+}
+
+#[derive(Args, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+pub struct Pkcs11SigningKey {
+ /// Path to pkcs11-tool
+ #[arg(id = "pkcs11_tool", long = "pkcs11-tool", default_value = "pkcs11-tool", value_name = "PATH")]
+ pub tool: String,
+
+ /// Path to the PKCS#11 module to use for signing
+ #[arg(
+ long = "pkcs11-library",
+ alias = "pkcs11-module",
+ id = "pkcs11_library",
+ value_name = "PATH"
+ )]
+ pub module: Option<PathBuf>,
+
+ /// PKCS#11 slot id containing the signing key
+ #[arg(id = "pkcs11_slot_id", long = "pkcs11-slot-id", value_name = "SLOT")]
+ pub slot_id: Option<String>,
+
+ /// PKCS#11 token label containing the signing key
+ #[arg(id = "pkcs11_token_label", long = "pkcs11-token-label", value_name = "LABEL")]
+ pub token_label: Option<String>,
+
+ /// PKCS#11 private-key object id, usually hex
+ #[arg(id = "pkcs11_key_id", long = "pkcs11-key-id", value_name = "ID")]
+ pub key_id: Option<String>,
+
+ /// PKCS#11 private-key object label
+ #[arg(id = "pkcs11_key_label", long = "pkcs11-key-label", value_name = "LABEL")]
+ pub key_label: Option<String>,
+
+ /// PIN used to unlock the PKCS#11 token
+ #[arg(id = "pkcs11_pin", long = "pkcs11-pin", group = "pkcs11-pin-source", value_name = "PIN")]
+ pub pin: Option<String>,
+
+ /// File containing the PIN used to unlock the PKCS#11 token
+ #[arg(id = "pkcs11_pin_file", long = "pkcs11-pin-file", group = "pkcs11-pin-source", value_name = "PATH")]
+ pub pin_path: Option<PathBuf>,
+
+ /// Environment variable holding the PIN used to unlock the PKCS#11 token
+ #[arg(id = "pkcs11_pin_env", long = "pkcs11-pin-env", group = "pkcs11-pin-source", value_name = "ENV")]
+ #[serde(skip)]
+ pub pin_env: Option<String>,
+
+ /// DER certificate file corresponding to the PKCS#11 private key
+ #[arg(
+ long = "pkcs11-certificate-file",
+ alias = "pkcs11-certificate-der-file",
+ id = "pkcs11_certificate_file",
+ value_name = "PATH"
+ )]
+ pub certificate_path: Option<PathBuf>,
+}
+
+impl Pkcs11SigningKey {
+ pub(crate) fn configured(&self) -> bool {
+ self.module.is_some()
+ || self.slot_id.is_some()
+ || self.token_label.is_some()
+ || self.key_id.is_some()
+ || self.key_label.is_some()
+ || self.pin.is_some()
+ || self.pin_path.is_some()
+ || self.pin_env.is_some()
+ || self.certificate_path.is_some()
+ }
+
+ fn not_configured(&self) -> bool {
+ !self.configured()
+ }
+
+ fn resolve_pin(&self) -> Result<String, AppleCodesignError> {
+ if let Some(pin) = &self.pin {
+ Ok(pin.clone())
+ } else if let Some(path) = &self.pin_path {
+ Ok(std::fs::read_to_string(path)?.trim().to_string())
+ } else if let Some(env) = &self.pin_env {
+ std::env::var(env).map_err(|_| {
+ AppleCodesignError::CliGeneralError(format!(
+ "failed reading PKCS#11 PIN from {env} environment variable"
+ ))
+ })
+ } else {
+ Err(AppleCodesignError::CliGeneralError(
+ "--pkcs11-pin, --pkcs11-pin-file, or --pkcs11-pin-env is required".into(),
+ ))
+ }
+ }
+}
+
+impl KeySource for Pkcs11SigningKey {
+ fn resolve_certificates(&self) -> Result<SigningCertificates, AppleCodesignError> {
+ if !self.configured() {
+ return Ok(Default::default());
+ }
+
+ let module = self.module.clone().ok_or_else(|| {
+ AppleCodesignError::CliGeneralError("--pkcs11-library is required".into())
+ })?;
+ if self.slot_id.is_none() && self.token_label.is_none() {
+ return Err(AppleCodesignError::CliGeneralError(
+ "--pkcs11-slot-id or --pkcs11-token-label is required".into(),
+ ));
+ }
+ if self.key_id.is_none() && self.key_label.is_none() {
+ return Err(AppleCodesignError::CliGeneralError(
+ "--pkcs11-key-id or --pkcs11-key-label is required".into(),
+ ));
+ }
+ let certificate_path = self.certificate_path.clone().ok_or_else(|| {
+ AppleCodesignError::CliGeneralError("--pkcs11-certificate-file is required".into())
+ })?;
+ let pin = self.resolve_pin()?;
+
+ warn!(
+ "reading PKCS#11 certificate data from {}",
+ certificate_path.display()
+ );
+ let cert = CapturedX509Certificate::from_der(std::fs::read(certificate_path)?)?;
+
+ match cert.key_algorithm() {
+ Some(KeyAlgorithm::Rsa) => {}
+ Some(algorithm) => {
+ return Err(AppleCodesignError::CliGeneralError(format!(
+ "PKCS#11 signing currently supports RSA certificates, not {algorithm:?}"
+ )));
+ }
+ None => {
+ return Err(AppleCodesignError::CliGeneralError(
+ "failed to resolve PKCS#11 certificate key algorithm".into(),
+ ));
+ }
+ }
+
+ let signer = Pkcs11PrivateKey {
+ tool: self.tool.clone(),
+ module,
+ slot_id: self.slot_id.clone(),
+ token_label: self.token_label.clone(),
+ key_id: self.key_id.clone(),
+ key_label: self.key_label.clone(),
+ pin,
+ cert: cert.clone(),
+ };
+
+ Ok(SigningCertificates {
+ keys: vec![Box::new(signer)],
+ certs: vec![cert],
+ })
+ }
+}
+
@@ -643,0 +1037,8 @@
+ rename = "pkcs11",
+ skip_serializing_if = "Pkcs11SigningKey::not_configured"
+ )]
+ pub pkcs11_key: Pkcs11SigningKey,
+
+ #[command(flatten)]
+ #[serde(
+ default,
@@ -680,0 +1082,2 @@
+ res.push(&self.pkcs11_key as &dyn KeySource);
+
--- a/apple-codesign/src/cli/mod.rs
+++ b/apple-codesign/src/cli/mod.rs
@@ -1529 +1532,6 @@
- let certs = c.signer.resolve_certificates(true)?;
+ let signer = if self.certificate.pkcs11_key.configured() {
+ &self.certificate
+ } else {
+ &c.signer
+ };
+ let certs = signer.resolve_certificates(true)?;