--- 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)?;
