#![cfg(feature = "pkcs11")]
use cryptoki::{
context::{CInitializeArgs, CInitializeFlags, Pkcs11},
mechanism::Mechanism,
object::{Attribute, KeyType, ObjectClass},
session::UserType,
types::AuthPin,
};
use std::sync::Arc;
use xml_sec::provider::pkcs11::{Pkcs11Provider, cryptoki};
use xml_sec::provider::{
ContentDecryptionKey, CryptoProvider, ExternalProviderError, KeyAgreementParameters,
ProviderError, RustCryptoProvider,
};
use xml_sec::xmldsig::{DigestAlgorithm, SignatureAlgorithm};
use xml_sec::xmlenc::{DataEncryptionAlgorithm, KeyWrapAlgorithm};
#[test]
fn isolated_token_operations_and_failures() {
let store = std::env::var("XML_SEC_PKCS11_TEST_STORE").expect("run scripts/test-pkcs11.sh");
let config = std::env::var("SOFTHSM2_CONF").expect("isolated configuration");
assert_eq!(
std::path::Path::new(&config).parent(),
Some(std::path::Path::new(&store))
);
let module =
Pkcs11::new(std::env::var("XML_SEC_PKCS11_TEST_MODULE").expect("explicit test module"))
.unwrap();
module
.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))
.unwrap();
static MODULE: std::sync::OnceLock<Pkcs11> = std::sync::OnceLock::new();
let module = MODULE.get_or_init(|| module);
let slot = module.get_slots_with_token().unwrap()[0];
let so = AuthPin::new("test-so-1234".into());
let pin = AuthPin::new("test-user-1234".into());
module
.init_token(slot, &so, "xml-sec isolated test")
.unwrap();
let slot = module
.get_slots_with_token()
.unwrap()
.into_iter()
.find(|slot| module.get_token_info(*slot).unwrap().token_initialized())
.unwrap();
{
let session = module.open_rw_session(slot).unwrap();
session.login(UserType::So, Some(&so)).unwrap();
session.init_pin(&pin).unwrap();
}
let provider = Pkcs11Provider::new(module, slot).unwrap();
{
let admin = module.open_rw_session(slot).unwrap();
assert!(matches!(
admin.login(UserType::So, Some(&so)),
Err(cryptoki::error::Error::Pkcs11(
cryptoki::error::RvError::SessionReadOnlyExists,
_
))
));
}
let error = provider
.login(&AuthPin::new("wrong-pin".into()))
.unwrap_err();
assert!(matches!(
error,
ProviderError::External(ExternalProviderError::Credentials)
));
assert!(!error.to_string().contains("wrong-pin"));
provider.login(&pin).unwrap();
let session = module.open_rw_session(slot).unwrap();
session
.generate_key_pair(
&Mechanism::RsaPkcsKeyPairGen,
&[
Attribute::Token(true),
Attribute::Private(false),
Attribute::Id(vec![1]),
Attribute::PublicExponent(vec![1, 0, 1]),
Attribute::ModulusBits(2048.into()),
Attribute::Verify(true),
Attribute::Encrypt(true),
],
&[
Attribute::Token(true),
Attribute::Private(true),
Attribute::Id(vec![1]),
Attribute::Sensitive(true),
Attribute::Extractable(false),
Attribute::Sign(true),
Attribute::Decrypt(true),
Attribute::Unwrap(true),
],
)
.unwrap();
let private = Arc::new(provider.rsa_private_key(&[1]).unwrap());
let public = provider.rsa_public_key(&[1]).unwrap();
let message = b"opaque key operation";
for salt_len in [0, 32] {
let algorithm = SignatureAlgorithm::RsaPss(xml_sec::xmldsig::RsaPssParameters {
digest: DigestAlgorithm::Sha256,
mgf_digest: DigestAlgorithm::Sha256,
salt_len,
});
let signature = provider.sign(private.as_ref(), algorithm, message).unwrap();
assert!(
provider
.verify(&public, algorithm, message, &signature)
.unwrap()
);
assert!(
!provider
.verify(&public, algorithm, b"changed", &signature)
.unwrap()
);
let wrong = SignatureAlgorithm::RsaPss(xml_sec::xmldsig::RsaPssParameters {
digest: DigestAlgorithm::Sha256,
mgf_digest: DigestAlgorithm::Sha256,
salt_len: if salt_len == 0 { 32 } else { 0 },
});
assert!(
!provider
.verify(&public, wrong, message, &signature)
.unwrap()
);
}
let unsupported_tuple = SignatureAlgorithm::RsaPss(xml_sec::xmldsig::RsaPssParameters {
digest: DigestAlgorithm::Sha256,
mgf_digest: DigestAlgorithm::Sha384,
salt_len: 32,
});
assert!(
provider
.sign(private.as_ref(), unsupported_tuple, message)
.is_err()
);
let signature = provider
.sign(private.as_ref(), SignatureAlgorithm::RsaSha256, message)
.unwrap();
let builder = xml_sec::xmldsig::SignatureBuilder::new(
xml_sec::c14n::C14nAlgorithm::new(xml_sec::c14n::C14nMode::Exclusive1_0, false),
SignatureAlgorithm::RsaSha256,
)
.add_reference(
xml_sec::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256)
.uri("")
.transform(xml_sec::xmldsig::Transform::Enveloped),
);
let signed = xml_sec::xmldsig::SignContext::new(private.as_ref())
.provider(&provider)
.sign_with_builder("<root><payload>signed</payload></root>", &builder)
.unwrap();
let mut strict = xml_sec::policy::VerificationPolicy::default();
strict.key_trust.rsa_keys.minimum_modulus_bits = 3072;
assert!(
xml_sec::xmldsig::VerifyContext::new()
.key(&public)
.provider(&provider)
.policy(strict)
.verify(&signed)
.is_err()
);
assert!(
xml_sec::xmldsig::VerifyContext::new()
.key(&public)
.provider(&provider)
.verify(&signed)
.unwrap()
.status
== xml_sec::xmldsig::DsigStatus::Valid
);
assert!(
xml_sec::xmldsig::VerifyContext::new()
.key(&public)
.provider(&provider)
.verify(&signed.replace("signed", "changed"))
.unwrap()
.status
!= xml_sec::xmldsig::DsigStatus::Valid
);
assert!(
provider
.verify(&public, SignatureAlgorithm::RsaSha256, message, &signature)
.unwrap()
);
assert!(
!provider
.verify(
&public,
SignatureAlgorithm::RsaSha256,
b"tampered",
&signature
)
.unwrap()
);
assert_eq!(
provider.digest(DigestAlgorithm::Sha256, message).unwrap(),
RustCryptoProvider
.digest(DigestAlgorithm::Sha256, message)
.unwrap()
);
let other = Pkcs11Provider::new(module, slot).unwrap();
assert!(
other
.sign(private.as_ref(), SignatureAlgorithm::RsaSha256, message)
.is_err()
);
assert!(
other
.verify(&public, SignatureAlgorithm::RsaSha256, message, &signature)
.is_err()
);
assert!(
RustCryptoProvider
.verify(&public, SignatureAlgorithm::RsaSha256, message, &signature)
.is_err()
);
std::thread::scope(|scope| {
for _ in 0..4 {
let provider = &provider;
let private = &private;
scope.spawn(move || {
for _ in 0..4 {
assert_eq!(
provider
.sign(private.as_ref(), SignatureAlgorithm::RsaSha256, message)
.unwrap()
.len(),
256
);
}
});
}
});
let kek = [0x31; 16];
let cek = [0x72; 16];
let native_aes = session
.create_object(&[
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(KeyType::AES),
Attribute::Token(true),
Attribute::Id(vec![2]),
Attribute::Value(kek.to_vec()),
Attribute::Sensitive(true),
Attribute::Extractable(false),
Attribute::Unwrap(true),
Attribute::Decrypt(true),
])
.unwrap();
let opaque = provider.aes_key(&[2]).unwrap();
let ciphertext = RustCryptoProvider
.encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &kek, message)
.unwrap();
assert_eq!(
provider
.decrypt_content_key(DataEncryptionAlgorithm::Aes128Gcm, &opaque, &ciphertext)
.unwrap(),
message
);
assert!(
opaque
.decrypt_with_provider(&other, DataEncryptionAlgorithm::Aes128Gcm, &ciphertext)
.is_err()
);
let mut corrupted = ciphertext.clone();
*corrupted.last_mut().unwrap() ^= 1;
let mut nonce: [u8; 12] = corrupted[..12].try_into().unwrap();
let params = cryptoki::mechanism::aead::GcmParams::new(&mut nonce, &[], 128.into()).unwrap();
let native_error = session
.decrypt(&Mechanism::AesGcm(params), native_aes, &corrupted[12..])
.unwrap_err();
let expected = match native_error {
cryptoki::error::Error::Pkcs11(
cryptoki::error::RvError::EncryptedDataInvalid,
cryptoki::context::Function::Decrypt,
) => ProviderError::AuthenticationFailed,
cryptoki::error::Error::Pkcs11(
cryptoki::error::RvError::GeneralError,
cryptoki::context::Function::Decrypt,
) => ProviderError::External(ExternalProviderError::Operation),
error => panic!("unexpected native GCM failure: {error:?}"),
};
assert_eq!(
provider
.decrypt_content_key(DataEncryptionAlgorithm::Aes128Gcm, &opaque, &corrupted)
.unwrap_err(),
expected
);
assert_eq!(
provider
.decrypt_content_key(DataEncryptionAlgorithm::Aes128Gcm, &opaque, &ciphertext)
.unwrap(),
message
);
let cbc = RustCryptoProvider
.encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &kek, message)
.unwrap();
assert_eq!(
provider
.decrypt_content_key(DataEncryptionAlgorithm::Aes128Cbc, &opaque, &cbc)
.unwrap(),
message
);
assert!(
provider
.decrypt_content_key(
DataEncryptionAlgorithm::Aes128Cbc,
&opaque,
&cbc[..cbc.len() - 1]
)
.is_err()
);
let wrapped = RustCryptoProvider
.wrap_key(KeyWrapAlgorithm::AesKw128, &kek, &cek)
.unwrap();
let content = provider
.unwrap_content_key(
&opaque,
KeyWrapAlgorithm::AesKw128,
DataEncryptionAlgorithm::Aes128Gcm,
&wrapped,
)
.unwrap();
assert_eq!(content.key_len(), 16);
assert!(matches!(
content.into_key(),
Err(ProviderError::KeyNotExportable)
));
let wide_kek = [0x53; 32];
session
.create_object(&[
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(KeyType::AES),
Attribute::Token(true),
Attribute::Id(vec![8]),
Attribute::Value(wide_kek.to_vec()),
Attribute::Sensitive(true),
Attribute::Extractable(false),
Attribute::Decrypt(true),
Attribute::Unwrap(true),
])
.unwrap();
let wide = provider.aes_key(&[8]).unwrap();
for algorithm in [
DataEncryptionAlgorithm::Aes256Cbc,
DataEncryptionAlgorithm::Aes256Gcm,
] {
let cipher = RustCryptoProvider
.encrypt_data(algorithm, &wide_kek, message)
.unwrap();
assert_eq!(
provider
.decrypt_content_key(algorithm, &wide, &cipher)
.unwrap(),
message
);
}
let wrapped = RustCryptoProvider
.wrap_key(KeyWrapAlgorithm::AesKw256, &wide_kek, &wide_kek)
.unwrap();
assert_eq!(
provider
.unwrap_content_key(
&wide,
KeyWrapAlgorithm::AesKw256,
DataEncryptionAlgorithm::Aes256Gcm,
&wrapped
)
.unwrap()
.key_len(),
32
);
session
.generate_key_pair(
&Mechanism::RsaPkcsKeyPairGen,
&[
Attribute::Token(true),
Attribute::Id(vec![3]),
Attribute::PublicExponent(vec![1, 0, 1]),
Attribute::ModulusBits(2048.into()),
],
&[
Attribute::Token(true),
Attribute::Id(vec![3]),
Attribute::Sign(false),
],
)
.unwrap();
let denied = provider.rsa_private_key(&[3]).unwrap();
assert!(
provider
.sign(&denied, SignatureAlgorithm::RsaSha256, message)
.is_err()
);
assert!(provider.rsa_private_key(&[99]).is_err());
let resolver =
xml_sec::xmlenc::OpaqueContentKeyResolver::new(Arc::new(provider.aes_key(&[2]).unwrap()));
let encrypted = xml_sec::xmlenc::EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
.direct_key(kek.to_vec())
.encrypt_binary(message)
.unwrap();
assert_eq!(
xml_sec::xmlenc::DecryptContext::new(&resolver)
.provider(&provider)
.decrypt(&encrypted.encrypted_data_xml)
.unwrap(),
xml_sec::xmlenc::DecryptedContent::Bytes(message.to_vec())
);
let recipient = xml_sec::xmlenc::EncryptionRecipient::aes_key_wrap(
kek.to_vec(),
KeyWrapAlgorithm::AesKw128,
);
let encrypted = xml_sec::xmlenc::EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
.add_recipient(recipient)
.encrypt_binary(message)
.unwrap();
let resolver =
xml_sec::xmlenc::OpaqueKekDecryptor::new(Arc::new(provider.aes_key(&[2]).unwrap()));
assert_eq!(
xml_sec::xmlenc::DecryptContext::new(&resolver)
.provider(&provider)
.decrypt(&encrypted.encrypted_data_xml)
.unwrap(),
xml_sec::xmlenc::DecryptedContent::Bytes(message.to_vec())
);
assert!(matches!(
provider.transport_key(
&public,
&xml_sec::xmlenc::RsaOaepParameters::default(),
&cek
),
Err(ProviderError::Unsupported { .. })
));
let parameters = xml_sec::xmlenc::RsaOaepParameters::legacy();
let wrapped = provider.transport_key(&public, ¶meters, &cek).unwrap();
let wrong_width = provider
.transport_key(&public, ¶meters, &[1; 24])
.unwrap();
assert!(matches!(
provider.recover_content_key(
private.as_ref(),
¶meters,
DataEncryptionAlgorithm::Aes128Gcm,
&wrong_width
),
Err(ProviderError::InvalidKeySize {
expected: 16,
actual: 24
})
));
let recovered = provider
.recover_content_key(
private.as_ref(),
¶meters,
DataEncryptionAlgorithm::Aes128Gcm,
&wrapped,
)
.unwrap();
assert_eq!(recovered.key_len(), 16);
assert!(matches!(
recovered.into_key(),
Err(ProviderError::KeyNotExportable)
));
use xml_sec::rsa_encoding::RsaPublicKeyEncoding as _;
let xml_sec::xmldsig::SigningPublicKeyInfo::Rsa { spki_der, .. } =
xml_sec::xmldsig::SigningKey::public_key_info(private.as_ref()).unwrap()
else {
panic!("RSA metadata");
};
let recipient = xml_sec::xmlenc::EncryptionRecipient::rsa_oaep(
rsa::RsaPublicKey::from_public_key_der(&spki_der).unwrap(),
)
.oaep_parameters(parameters);
let encrypted = xml_sec::xmlenc::EncryptedDataBuilder::new(DataEncryptionAlgorithm::Aes128Gcm)
.add_recipient(recipient)
.encrypt_binary(message)
.unwrap();
let resolver = xml_sec::xmlenc::PrivateKeyDecryptor::provider_key(private.clone());
assert_eq!(
xml_sec::xmlenc::DecryptContext::new(&resolver)
.provider(&provider)
.decrypt(&encrypted.encrypted_data_xml)
.unwrap(),
xml_sec::xmlenc::DecryptedContent::Bytes(message.to_vec())
);
use pkcs8::der::{Decode, asn1::OctetStringRef};
for (curve, width, first_id) in [
(&[6, 8, 42, 134, 72, 206, 61, 3, 1, 7][..], 32, 4),
(&[6, 5, 43, 129, 4, 0, 34][..], 48, 6),
(&[6, 5, 43, 129, 4, 0, 35][..], 66, 10),
] {
let mut peers = Vec::new();
for id in [first_id, first_id + 1] {
let (public, _) = session
.generate_key_pair(
&Mechanism::EccKeyPairGen,
&[
Attribute::Token(true),
Attribute::Id(vec![id]),
Attribute::EcParams(curve.to_vec()),
],
&[
Attribute::Token(true),
Attribute::Id(vec![id]),
Attribute::Derive(true),
Attribute::Sensitive(true),
Attribute::Extractable(false),
],
)
.unwrap();
let attributes = session
.get_attributes(public, &[cryptoki::object::AttributeType::EcPoint])
.unwrap();
let encoded = attributes
.into_iter()
.find_map(|value| match value {
Attribute::EcPoint(value) => Some(value),
_ => None,
})
.unwrap();
let point = <&OctetStringRef>::from_der(&encoded).unwrap().as_bytes();
assert_eq!(point.len(), 1 + 2 * width);
assert_eq!(point[0], 4);
peers.push(point.to_vec());
}
let a = provider.agreement_key(&[first_id]).unwrap();
let b = provider.agreement_key(&[first_id + 1]).unwrap();
let parameters_a = KeyAgreementParameters {
algorithm: "http://www.w3.org/2009/xmlenc11#ECDH-ES",
peer_public_key: &peers[1],
};
let parameters_b = KeyAgreementParameters {
algorithm: parameters_a.algorithm,
peer_public_key: &peers[0],
};
let secret = zeroize::Zeroizing::new(provider.agree_key(&a, ¶meters_a).unwrap());
assert_eq!(secret.len(), width);
assert_eq!(*secret, provider.agree_key(&b, ¶meters_b).unwrap());
assert!(other.agree_key(&a, ¶meters_a).is_err());
let malformed_peer = KeyAgreementParameters {
algorithm: parameters_a.algorithm,
peer_public_key: &[],
};
assert!(provider.agree_key(&a, &malformed_peer).is_err());
}
let parameters = KeyAgreementParameters {
algorithm: "unsupported",
peer_public_key: &[],
};
assert!(
!provider.supports(xml_sec::provider::ProviderCapability::KeyAgreement(
¶meters
))
);
let stale = provider.aes_key(&[2]).unwrap();
let object = session.find_objects(&[Attribute::Id(vec![2])]).unwrap()[0];
session.destroy_object(object).unwrap();
assert!(matches!(
provider.decrypt_content_key(DataEncryptionAlgorithm::Aes128Gcm, &stale, &ciphertext),
Err(ProviderError::External(ExternalProviderError::Object))
));
for _ in 0..2 {
session
.create_object(&[
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::KeyType(KeyType::AES),
Attribute::Token(true),
Attribute::Id(vec![9]),
Attribute::Sensitive(true),
Attribute::Extractable(false),
Attribute::Value(vec![0; 16]),
])
.unwrap();
}
assert!(matches!(
provider.aes_key(&[9]),
Err(ProviderError::External(ExternalProviderError::Object))
));
assert!(matches!(
provider.aes_key(&[]),
Err(ProviderError::External(ExternalProviderError::Object))
));
assert_eq!(
provider
.decrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &kek, &ciphertext)
.unwrap(),
message
);
assert!(matches!(
provider.decrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0; 24], &ciphertext),
Err(ProviderError::InvalidKeySize {
expected: 16,
actual: 24
})
));
}