Skip to content

Compatiblity layer with RustCrypto #192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
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
640 changes: 633 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[workspace]
members = ["cryptoki", "cryptoki-sys"]
members = ["cryptoki", "cryptoki-sys", "cryptoki-rustcrypto"]

[patch.crates-io]
pkcs12 = { git = "https://github.com/RustCrypto/formats.git" }
rsa = { path = "../RSA" }
34 changes: 34 additions & 0 deletions cryptoki-rustcrypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "cryptoki-rustcrypto"
version = "0.1.0"
edition = "2018"
authors = ["Contributors to the Parsec project"]
description = "Compatibility layer from PKCS #11 to the RustCrypto ecosystem"
readme = "README.md"
keywords = ["pkcs11", "cryptoki", "hsm"]
categories = ["cryptography", "hardware-support"]
license = "Apache-2.0"
repository = "https://github.com/parallaxsecond/rust-cryptoki"

[dependencies]
cryptoki = { path = "../cryptoki", version = "0.7.0" }
der = "=0.8.0-rc.1"
ecdsa = "=0.17.0-pre.9"
#p224 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
p256 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
p384 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
k256 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
pkcs12 = { version = "=0.2.0-pre" }
rsa = { version = "=0.10.0-pre.3", features = ["sha2"] }
signature = { version = "=2.3.0-pre.4", features = ["derive", "digest"] }
sha1 = { version = "=0.11.0-pre.4", features = ["oid"] }
sha2 = { version = "=0.11.0-pre.4", features = ["oid"] }
spki = "=0.8.0-rc.1"
x509-cert = "=0.3.0-pre.0"
thiserror = "1.0"

[dev-dependencies]
rand = "0.8.5"
serial_test = "0.5.1"
testresult = "0.2.0"
x509-cert = { version = "=0.3.0-pre.0", features = ["builder"] }
278 changes: 278 additions & 0 deletions cryptoki-rustcrypto/src/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::{
mechanism::Mechanism,
object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
};
use der::{
asn1::{ObjectIdentifier, OctetString, OctetStringRef},
oid::AssociatedOid,
AnyRef, Decode, Encode,
};
use ecdsa::{
elliptic_curve::{
array::ArraySize,
ops::Invert,
point::PointCompression,
sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
subtle::CtOption,
AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, Scalar, SecretKey,
},
hazmat::DigestPrimitive,
PrimeCurve, Signature, SignatureSize, VerifyingKey,
};
use signature::{digest::Digest, DigestSigner};
use spki::{
AlgorithmIdentifier, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
SignatureAlgorithmIdentifier,
};
use std::{convert::TryFrom, ops::Add};
use thiserror::Error;

use crate::{CryptokiImport, SessionLike};

pub fn read_key<S: SessionLike, C: SignAlgorithm>(
session: &S,
template: impl Into<Vec<Attribute>>,
) -> Result<PublicKey<C>, Error>
where
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
let mut template = template.into();
template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
template.push(Attribute::KeyType(KeyType::EC));
template.push(Attribute::EcParams(C::OID.to_der().unwrap()));

let keys = session.find_objects(&template)?;
if let Some(public_key) = keys.first() {
let attribute_pub = session.get_attributes(*public_key, &[AttributeType::EcPoint])?;

let mut ec_point = None;
for attribute in attribute_pub {
match attribute {
Attribute::EcPoint(p) if ec_point.is_none() => {
ec_point = Some(p);
break;
}
_ => {}
}
}

let ec_point = ec_point.ok_or(Error::MissingAttribute(AttributeType::EcPoint))?;

// documented as "DER-encoding of ANSI X9.62 ECPoint value Q"
// https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html#_Toc111203418
// https://www.rfc-editor.org/rfc/rfc5480#section-2.2
let ec_point = OctetStringRef::from_der(&ec_point).unwrap();

Ok(PublicKey::<C>::from_sec1_bytes(ec_point.as_bytes())?)
} else {
Err(Error::MissingKey)
}
}

impl<C> CryptokiImport for SecretKey<C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
SignatureSize<C>: ArraySize,

C: AssociatedOid,
{
fn put_key<S: SessionLike>(
&self,
session: &S,
template: impl Into<Vec<Attribute>>,
) -> cryptoki::error::Result<ObjectHandle> {
let mut template = template.into();
template.push(Attribute::Class(ObjectClass::PRIVATE_KEY));
template.push(Attribute::KeyType(KeyType::EC));
template.push(Attribute::EcParams(C::OID.to_der().unwrap()));
template.push(Attribute::Value(self.to_bytes().as_slice().to_vec()));

let handle = session.create_object(&template)?;

Ok(handle)
}
}

impl<C> CryptokiImport for PublicKey<C>
where
C: PrimeCurve + CurveArithmetic + PointCompression,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
C: AssociatedOid,
{
fn put_key<S: SessionLike>(
&self,
session: &S,
template: impl Into<Vec<Attribute>>,
) -> cryptoki::error::Result<ObjectHandle> {
let mut template = template.into();
template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
template.push(Attribute::KeyType(KeyType::EC));
template.push(Attribute::EcParams(C::OID.to_der().unwrap()));
let ec_point = OctetString::new(self.to_sec1_bytes()).unwrap();
template.push(Attribute::EcPoint(ec_point.to_der().unwrap()));

let handle = session.create_object(&template)?;

Ok(handle)
}
}

#[derive(Error, Debug)]
pub enum Error {
#[error("Cryptoki error: {0}")]
Cryptoki(#[from] cryptoki::error::Error),

#[error("Private key missing attribute: {0}")]
MissingAttribute(AttributeType),

#[error("Elliptic curve error: {0}")]
Ecdsa(#[from] ecdsa::elliptic_curve::Error),

#[error("Key not found")]
MissingKey,
}

pub trait SignAlgorithm: PrimeCurve + CurveArithmetic + AssociatedOid + DigestPrimitive {
fn sign_mechanism() -> Mechanism<'static>;
}

macro_rules! impl_sign_algorithm {
($ec:ty) => {
impl SignAlgorithm for $ec {
fn sign_mechanism() -> Mechanism<'static> {
Mechanism::Ecdsa
}
}
};
}

//impl_sign_algorithm!(p224::NistP224);
impl_sign_algorithm!(p256::NistP256);
impl_sign_algorithm!(p384::NistP384);
impl_sign_algorithm!(k256::Secp256k1);

#[derive(signature::Signer)]
pub struct Signer<C: SignAlgorithm, S: SessionLike> {
session: S,
private_key: ObjectHandle,
verifying_key: VerifyingKey<C>,
}

impl<C: SignAlgorithm, S: SessionLike> Signer<C, S>
where
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
// First we'll lookup a private key with that label.
let template = vec![
Attribute::Label(label.to_vec()),
Attribute::Class(ObjectClass::PRIVATE_KEY),
Attribute::KeyType(KeyType::EC),
Attribute::EcParams(C::OID.to_der().unwrap()),
Attribute::Sign(true),
];

let private_key = session.find_objects(&template)?.remove(0);
let attribute_priv = session.get_attributes(private_key, &[AttributeType::Id])?;

// Second we'll lookup a public key with the same label/ec params/ec point
let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
let mut id = None;
for attribute in attribute_priv {
match attribute {
Attribute::Id(i) if id.is_none() => {
template.push(Attribute::Id(i.clone()));
id = Some(i);
}
_ => {}
}
}

id.ok_or(Error::MissingAttribute(AttributeType::Id))?;

let public = read_key(&session, template)?;
let verifying_key = public.into();

Ok(Self {
session,
private_key,
verifying_key,
})
}

pub fn into_session(self) -> S {
self.session
}
}

impl<C: SignAlgorithm, S: SessionLike> AssociatedAlgorithmIdentifier for Signer<C, S>
where
C: AssociatedOid,
{
type Params = ObjectIdentifier;

const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> =
PublicKey::<C>::ALGORITHM_IDENTIFIER;
}

impl<C: SignAlgorithm, S: SessionLike> signature::Keypair for Signer<C, S> {
type VerifyingKey = VerifyingKey<C>;

fn verifying_key(&self) -> Self::VerifyingKey {
self.verifying_key
}
}

impl<C: SignAlgorithm, S: SessionLike> DigestSigner<C::Digest, Signature<C>> for Signer<C, S>
where
<<C as ecdsa::elliptic_curve::Curve>::FieldBytesSize as Add>::Output: ArraySize,
{
fn try_sign_digest(&self, digest: C::Digest) -> Result<Signature<C>, signature::Error> {
let msg = digest.finalize();

let bytes = self
.session
.sign(&C::sign_mechanism(), self.private_key, &msg)
.map_err(Error::Cryptoki)
.map_err(Box::new)
.map_err(signature::Error::from_source)?;

let signature = Signature::try_from(bytes.as_slice())?;

Ok(signature)
}
}

impl<C: SignAlgorithm, S: SessionLike> SignatureAlgorithmIdentifier for Signer<C, S>
where
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
Signature<C>: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Params = AnyRef<'static>;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
Signature::<C>::ALGORITHM_IDENTIFIER;
}

impl<C: SignAlgorithm, S: SessionLike> DigestSigner<C::Digest, ecdsa::der::Signature<C>>
for Signer<C, S>
where
ecdsa::der::MaxSize<C>: ArraySize,
<FieldBytesSize<C> as Add>::Output: Add<ecdsa::der::MaxOverhead> + ArraySize,
Self: DigestSigner<C::Digest, Signature<C>>,
{
fn try_sign_digest(
&self,
digest: C::Digest,
) -> Result<ecdsa::der::Signature<C>, signature::Error> {
DigestSigner::<C::Digest, Signature<C>>::try_sign_digest(self, digest).map(Into::into)
}
}
126 changes: 126 additions & 0 deletions cryptoki-rustcrypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::{
error::Result,
mechanism::Mechanism,
object::{Attribute, AttributeType, ObjectHandle},
session::{Session, UserType},
types::AuthPin,
};

pub use cryptoki;

pub mod ecdsa;
pub mod rng;
pub mod rsa;
pub mod x509;

pub trait SessionLike {
fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle>;
fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>>;
fn get_attributes(
&self,
object: ObjectHandle,
attributes: &[AttributeType],
) -> Result<Vec<Attribute>>;
fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()>;
fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>>;
fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()>;
fn wrap_key(
&self,
mechanism: &Mechanism,
wrapping_key: ObjectHandle,
key: ObjectHandle,
) -> Result<Vec<u8>>;
fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()>;
fn logout(&self) -> Result<()>;
}

impl SessionLike for Session {
fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle> {
Session::create_object(self, template)
}
fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>> {
Session::find_objects(self, template)
}
fn get_attributes(
&self,
object: ObjectHandle,
attributes: &[AttributeType],
) -> Result<Vec<Attribute>> {
Session::get_attributes(self, object, attributes)
}
fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()> {
Session::update_attributes(self, object, template)
}

fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>> {
Session::sign(self, mechanism, key, data)
}
fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()> {
Session::generate_random_slice(self, random_data)
}
fn wrap_key(
&self,
mechanism: &Mechanism,
wrapping_key: ObjectHandle,
key: ObjectHandle,
) -> Result<Vec<u8>> {
Session::wrap_key(self, mechanism, wrapping_key, key)
}
fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()> {
Session::login(self, user_type, pin)
}
fn logout(&self) -> Result<()> {
Session::logout(self)
}
}

impl<'s> SessionLike for &'s Session {
fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle> {
Session::create_object(self, template)
}
fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>> {
Session::find_objects(self, template)
}
fn get_attributes(
&self,
object: ObjectHandle,
attributes: &[AttributeType],
) -> Result<Vec<Attribute>> {
Session::get_attributes(self, object, attributes)
}
fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()> {
Session::update_attributes(self, object, template)
}

fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>> {
Session::sign(self, mechanism, key, data)
}
fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()> {
Session::generate_random_slice(self, random_data)
}
fn wrap_key(
&self,
mechanism: &Mechanism,
wrapping_key: ObjectHandle,
key: ObjectHandle,
) -> Result<Vec<u8>> {
Session::wrap_key(self, mechanism, wrapping_key, key)
}
fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()> {
Session::login(self, user_type, pin)
}
fn logout(&self) -> Result<()> {
Session::logout(self)
}
}

pub trait CryptokiImport {
fn put_key<S: SessionLike>(
&self,
session: &S,
template: impl Into<Vec<Attribute>>,
) -> Result<ObjectHandle>;
}
55 changes: 55 additions & 0 deletions cryptoki-rustcrypto/src/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use signature::rand_core::{CryptoRng, Error as RndError, RngCore};
use thiserror::Error;

use crate::SessionLike;

#[derive(Debug, Error)]
pub enum Error {}

/// [`Rng`] is a PKCS#11-backed CSPRNG.
///
/// ## Panics
///
/// The [`RngCore::fill_bytes`] implementation may panic if the provider was
/// unable to return enough bytes.
pub struct Rng<S: SessionLike>(S);

// TODO(baloo): check for CKF_RNG bit flag (CK_TOKEN_INFO struct -> flags)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this resolved, or will it result in a new issue once this is merged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'll need to make an API change to session to get back the Pkcs11 object and get_token_info on it.

impl<S: SessionLike> Rng<S> {
pub fn new(session: S) -> Result<Self, Error> {
Ok(Self(session))
}
}

macro_rules! impl_next_uint {
($self:ident, $u:ty) => {{
let mut buf = <$u>::MIN.to_be_bytes();
$self.fill_bytes(&mut buf[..]);

<$u>::from_be_bytes(buf)
}};
}

impl<S: SessionLike> RngCore for Rng<S> {
fn next_u32(&mut self) -> u32 {
impl_next_uint!(self, u32)
}

fn next_u64(&mut self) -> u64 {
impl_next_uint!(self, u64)
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
self.try_fill_bytes(dest)
.expect("Cryptoki provider failed to generate random");
}
Comment on lines +45 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it there's no way to not implement this and have the user always deal with the potential error. That's quite unfortunate. Maybe a comment on this method would be good to warn of a potential panic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I think the "panic warninig" is part of the documentation of the interface:

This method should guarantee that dest is entirely filled with new data, and may panic if this is impossible (e.g. reading past the end of a file that is being used as the source of randomness).

Source: https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html#tymethod.fill_bytes

Copy link
Contributor Author

@baloo baloo Feb 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a documentation for that panic on our Rng object.


fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), RndError> {
self.0.generate_random_slice(dest).map_err(RndError::new)
}
}

impl<S: SessionLike> CryptoRng for Rng<S> {}
146 changes: 146 additions & 0 deletions cryptoki-rustcrypto/src/rsa/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::{
mechanism::{
rsa::{PkcsMgfType, PkcsPssParams},
Mechanism, MechanismType,
},
object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
};
use der::oid::AssociatedOid;
use rsa::{traits::PublicKeyParts, BigUint, RsaPublicKey};
use signature::digest::Digest;
use std::convert::TryInto;
use thiserror::Error;

use crate::{CryptokiImport, SessionLike};

pub mod pkcs1v15;
pub mod pss;

pub fn read_key<S: SessionLike>(
session: &S,
template: impl Into<Vec<Attribute>>,
) -> Result<RsaPublicKey, Error> {
let mut template: Vec<Attribute> = template.into();
template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
template.push(Attribute::KeyType(KeyType::RSA));

let keys = session.find_objects(&template)?;
if let Some(key) = keys.first() {
let attribute_priv = session.get_attributes(
*key,
&[AttributeType::Modulus, AttributeType::PublicExponent],
)?;

let mut modulus = None;
let mut public_exponent = None;

for attribute in attribute_priv {
match attribute {
Attribute::Modulus(m) if modulus.is_none() => {
modulus = Some(m.clone());
}
Attribute::PublicExponent(e) if public_exponent.is_none() => {
public_exponent = Some(e.clone());
}
_ => {}
}
}

let modulus = modulus
.ok_or(Error::MissingAttribute(AttributeType::Modulus))
.map(|v| BigUint::from_bytes_be(v.as_slice()))?;
let public_exponent = public_exponent
.ok_or(Error::MissingAttribute(AttributeType::PublicExponent))
.map(|v| BigUint::from_bytes_be(v.as_slice()))?;

Ok(RsaPublicKey::new(modulus, public_exponent)?)
} else {
Err(Error::MissingKey)
}
}

#[derive(Debug, Error)]
pub enum Error {
#[error("Cryptoki error: {0}")]
Cryptoki(#[from] cryptoki::error::Error),

#[error("Private key missing attribute: {0}")]
MissingAttribute(AttributeType),

#[error("RSA error: {0}")]
Rsa(#[from] rsa::Error),

#[error("Key not found")]
MissingKey,
}
Comment on lines +66 to +78
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious - why have a different error struct per module? Seems like it could add some headaches if someone wants to use multiple crypto primitives in the same file, they'd have to import and rename them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. One Error type should be sufficient. ( ™️ )

(But seriously, it's not such a big crate to need so many Errors).

Copy link
Contributor Author

@baloo baloo Feb 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I go back and forth between "errors should be the most precise" and "dyn Error is enough for everyone". Looks like that day was the former.


pub trait DigestSigning: Digest + AssociatedOid {
fn pkcs_mechanism() -> Mechanism<'static>;

fn pss_mechanism() -> Mechanism<'static>;
}

macro_rules! impl_digest_signing {
($d:ty, $pkcs_mech:ident, $pss_mech:ident, $mt:ident, $mgf:ident) => {
impl DigestSigning for $d {
fn pkcs_mechanism() -> Mechanism<'static> {
Mechanism::$pkcs_mech
}

fn pss_mechanism() -> Mechanism<'static> {
Mechanism::$pss_mech(PkcsPssParams {
hash_alg: MechanismType::$mt,
mgf: PkcsMgfType::$mgf,
// Safety:
// the output_size of an hash function will not go over 2^32,
// this unwrap is safe.
s_len: Self::output_size().try_into().unwrap(),
})
}
}
};
}

impl_digest_signing!(sha1::Sha1, Sha1RsaPkcs, Sha1RsaPkcsPss, SHA1, MGF1_SHA1);
impl_digest_signing!(
sha2::Sha256,
Sha256RsaPkcs,
Sha256RsaPkcsPss,
SHA256,
MGF1_SHA256
);
impl_digest_signing!(
sha2::Sha384,
Sha384RsaPkcs,
Sha384RsaPkcsPss,
SHA384,
MGF1_SHA384
);
impl_digest_signing!(
sha2::Sha512,
Sha512RsaPkcs,
Sha512RsaPkcsPss,
SHA512,
MGF1_SHA512
);

impl CryptokiImport for RsaPublicKey {
fn put_key<S: SessionLike>(
&self,
session: &S,
template: impl Into<Vec<Attribute>>,
) -> cryptoki::error::Result<ObjectHandle> {
let mut template = template.into();
template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
template.push(Attribute::KeyType(KeyType::RSA));
template.push(Attribute::Modulus(self.n().to_bytes_be()));
template.push(Attribute::PublicExponent(self.e().to_bytes_be()));

let handle = session.create_object(&template)?;

Ok(handle)
}
}
150 changes: 150 additions & 0 deletions cryptoki-rustcrypto/src/rsa/pkcs1v15.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::{
mechanism::Mechanism,
object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
};
use rsa::pkcs1v15::{RsaSignatureAssociatedOid, Signature, VerifyingKey};
use spki::{
der::{asn1::OctetString, oid::AssociatedOid, referenced::RefToOwned, AnyRef, Encode},
AlgorithmIdentifier, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
SignatureAlgorithmIdentifier,
};
use std::convert::TryFrom;

use super::{read_key, DigestSigning, Error};
use crate::SessionLike;

pub struct Signer<D: DigestSigning, S: SessionLike> {
session: S,
private_key: ObjectHandle,
verifying_key: VerifyingKey<D>,
}

impl<D: DigestSigning, S: SessionLike> Signer<D, S> {
pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
// First we'll lookup a private key with that label.
let template = vec![
Attribute::Token(true),
Attribute::Private(true),
Attribute::Label(label.to_vec()),
Attribute::Class(ObjectClass::PRIVATE_KEY),
Attribute::KeyType(KeyType::RSA),
Attribute::Sign(true),
];

let private_key = session.find_objects(&template)?.remove(0);
let attribute_priv = session.get_attributes(
private_key,
&[AttributeType::Modulus, AttributeType::PublicExponent],
)?;

// Second we'll lookup a public key with the same label/modulus/public exponent
let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
let mut modulus = None;
let mut public_exponent = None;
for attribute in attribute_priv {
match attribute {
Attribute::Modulus(m) if modulus.is_none() => {
modulus = Some(m.clone());
template.push(Attribute::Modulus(m));
}
Attribute::PublicExponent(e) if public_exponent.is_none() => {
public_exponent = Some(e.clone());
template.push(Attribute::PublicExponent(e));
}
_ => {}
}
}

let public_key = read_key(&session, template)?;

let verifying_key = VerifyingKey::new(public_key);

Ok(Self {
session,
private_key,
verifying_key,
})
}

pub fn into_session(self) -> S {
self.session
}
}

impl<D, S> AssociatedAlgorithmIdentifier for Signer<D, S>
where
D: DigestSigning,
S: SessionLike,
{
type Params = <VerifyingKey<D> as AssociatedAlgorithmIdentifier>::Params;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
<VerifyingKey<D> as AssociatedAlgorithmIdentifier>::ALGORITHM_IDENTIFIER;
}

impl<D: DigestSigning, S: SessionLike> signature::Keypair for Signer<D, S> {
type VerifyingKey = VerifyingKey<D>;

fn verifying_key(&self) -> Self::VerifyingKey {
self.verifying_key.clone()
}
}

impl<D: DigestSigning, S: SessionLike> signature::Signer<Signature> for Signer<D, S> {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
let bytes = self
.session
.sign(&D::pkcs_mechanism(), self.private_key, msg)
.map_err(Error::Cryptoki)
.map_err(Box::new)
.map_err(signature::Error::from_source)?;

let signature = Signature::try_from(bytes.as_slice())?;

Ok(signature)
}
}

impl<D, S> SignatureAlgorithmIdentifier for Signer<D, S>
where
S: SessionLike,
D: DigestSigning + RsaSignatureAssociatedOid,
{
type Params = <VerifyingKey<D> as SignatureAlgorithmIdentifier>::Params;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
<VerifyingKey<D> as SignatureAlgorithmIdentifier>::SIGNATURE_ALGORITHM_IDENTIFIER;
}

impl<D, S> signature::hazmat::PrehashSigner<Signature> for Signer<D, S>
where
S: SessionLike,
D: DigestSigning + RsaSignatureAssociatedOid,
{
fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature, signature::Error> {
let payload = pkcs12::DigestInfo {
algorithm: (AlgorithmIdentifierRef {
oid: <D as AssociatedOid>::OID,
parameters: Some(AnyRef::NULL),
})
.ref_to_owned(),
digest: OctetString::new(prehash).unwrap(),
};

let msg = payload.to_der().unwrap();
println!("msg: {msg:x?}");

let bytes = self
.session
.sign(&Mechanism::RsaPkcs, self.private_key, &msg)
.map_err(Error::Cryptoki)
.map_err(Box::new)
.map_err(signature::Error::from_source)?;

let signature = Signature::try_from(bytes.as_slice())?;

Ok(signature)
}
}
110 changes: 110 additions & 0 deletions cryptoki-rustcrypto/src/rsa/pss.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
use der::AnyRef;
use rsa::{
pkcs1,
pkcs8::{self},
pss::{get_default_pss_signature_algo_id, Signature, VerifyingKey},
};
use spki::{
AlgorithmIdentifierOwned, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
DynSignatureAlgorithmIdentifier,
};
use std::convert::TryFrom;

use super::{read_key, DigestSigning, Error};
use crate::SessionLike;

pub struct Signer<D: DigestSigning, S: SessionLike> {
session: S,
private_key: ObjectHandle,
verifying_key: VerifyingKey<D>,
}

impl<D: DigestSigning, S: SessionLike> Signer<D, S> {
pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
// First we'll lookup a private key with that label.
let template = vec![
Attribute::Token(true),
Attribute::Private(true),
Attribute::Label(label.to_vec()),
Attribute::Class(ObjectClass::PRIVATE_KEY),
Attribute::KeyType(KeyType::RSA),
Attribute::Sign(true),
];

let private_key = session.find_objects(&template)?.remove(0);
let attribute_priv = session.get_attributes(
private_key,
&[AttributeType::Modulus, AttributeType::PublicExponent],
)?;

// Second we'll lookup a public key with the same label/modulus/public exponent
let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
let mut modulus = None;
let mut public_exponent = None;
for attribute in attribute_priv {
match attribute {
Attribute::Modulus(m) if modulus.is_none() => {
modulus = Some(m.clone());
template.push(Attribute::Modulus(m));
}
Attribute::PublicExponent(e) if public_exponent.is_none() => {
public_exponent = Some(e.clone());
template.push(Attribute::PublicExponent(e));
}
_ => {}
}
}

let public_key = read_key(&session, template)?;

let verifying_key = VerifyingKey::new(public_key);

Ok(Self {
session,
private_key,
verifying_key,
})
}

pub fn into_session(self) -> S {
self.session
}
}

impl<D: DigestSigning, S: SessionLike> AssociatedAlgorithmIdentifier for Signer<D, S> {
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}

impl<D: DigestSigning, S: SessionLike> signature::Keypair for Signer<D, S> {
type VerifyingKey = VerifyingKey<D>;

fn verifying_key(&self) -> Self::VerifyingKey {
self.verifying_key.clone()
}
}

impl<D: DigestSigning, S: SessionLike> signature::Signer<Signature> for Signer<D, S> {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
let bytes = self
.session
.sign(&D::pss_mechanism(), self.private_key, msg)
.map_err(Error::Cryptoki)
.map_err(Box::new)
.map_err(signature::Error::from_source)?;

let signature = Signature::try_from(bytes.as_slice())?;

Ok(signature)
}
}

impl<D: DigestSigning, S: SessionLike> DynSignatureAlgorithmIdentifier for Signer<D, S> {
fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<AlgorithmIdentifierOwned> {
get_default_pss_signature_algo_id::<D>()
}
}
97 changes: 97 additions & 0 deletions cryptoki-rustcrypto/src/x509.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::object::{Attribute, AttributeType, CertificateType, ObjectClass, ObjectHandle};
use thiserror::Error;
use x509_cert::{
certificate::{CertificateInner, Profile},
der::{Decode, Encode},
};

use crate::SessionLike;

#[derive(Debug, Error)]
pub enum Error {
#[error("Cryptoki error: {0}")]
Cryptoki(#[from] cryptoki::error::Error),

#[error("Missing attribute: {0}")]
MissingAttribute(AttributeType),

#[error(transparent)]
Der(#[from] x509_cert::der::Error),

#[error("No such certificate found")]
MissingCert,
}

pub trait CertPkcs11 {
fn pkcs11_store<S: SessionLike, T: Into<Vec<Attribute>>>(
&self,
session: &S,
base_template: T,
) -> Result<ObjectHandle, Error>;

fn pkcs11_load<S: SessionLike, T: Into<Vec<Attribute>>>(
session: &S,
template: T,
) -> Result<Self, Error>
where
Self: Sized;
}

impl<P> CertPkcs11 for CertificateInner<P>
where
P: Profile,
{
fn pkcs11_store<S: SessionLike, T: Into<Vec<Attribute>>>(
&self,
session: &S,
base_template: T,
) -> Result<ObjectHandle, Error> {
let mut template = base_template.into();
template.push(Attribute::Class(ObjectClass::CERTIFICATE));
template.push(Attribute::CertificateType(CertificateType::X_509));
template.push(Attribute::Token(true));
template.push(Attribute::Value(self.to_der()?));
if !self.tbs_certificate().subject().is_empty() {
template.push(Attribute::Subject(
self.tbs_certificate().subject().to_der()?,
));
}

Ok(session.create_object(&template)?)
}

fn pkcs11_load<S: SessionLike, T: Into<Vec<Attribute>>>(
session: &S,
template: T,
) -> Result<Self, Error> {
let mut template = template.into();
template.push(Attribute::Class(ObjectClass::CERTIFICATE));
template.push(Attribute::CertificateType(CertificateType::X_509));

let certs = session.find_objects(&template)?;
if let Some(cert) = certs.first() {
let attributes = session.get_attributes(*cert, &[AttributeType::Value])?;

let mut value = None;
for attribute in attributes {
match attribute {
Attribute::Value(v) if value.is_none() => {
value = Some(v);
}
_ => {}
}
}

let value = value.ok_or(Error::MissingAttribute(AttributeType::Value))?;

let cert = Self::from_der(&value)?;

Ok(cert)
} else {
Err(Error::MissingCert)
}
}
}
44 changes: 44 additions & 0 deletions cryptoki-rustcrypto/tests/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::session::UserType;
use cryptoki::slot::Slot;
use cryptoki::types::AuthPin;
use std::env;

// The default user pin
pub static USER_PIN: &str = "fedcba";
// The default SO pin
pub static SO_PIN: &str = "abcdef";

pub fn get_pkcs11() -> Pkcs11 {
Pkcs11::new(
env::var("PKCS11_SOFTHSM2_MODULE")
.unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()),
)
.unwrap()
}

pub fn init_pins() -> (Pkcs11, Slot) {
let pkcs11 = get_pkcs11();

// initialize the library
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();

// find a slot, get the first one
let slot = pkcs11.get_slots_with_token().unwrap().remove(0);

let so_pin = AuthPin::new(SO_PIN.into());
pkcs11.init_token(slot, &so_pin, "Test Token").unwrap();

{
// open a session
let session = pkcs11.open_rw_session(slot).unwrap();
// log in the session
session.login(UserType::So, Some(&so_pin)).unwrap();
session.init_pin(&AuthPin::new(USER_PIN.into())).unwrap();
}

(pkcs11, slot)
}
118 changes: 118 additions & 0 deletions cryptoki-rustcrypto/tests/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

mod common;

use crate::common::USER_PIN;
use common::init_pins;
use cryptoki::{
mechanism::Mechanism,
object::{Attribute, KeyType},
session::UserType,
types::AuthPin,
};
use cryptoki_rustcrypto::{ecdsa, CryptokiImport};
use der::Encode;
use p256::pkcs8::AssociatedOid;
use serial_test::serial;
use signature::{Keypair, Signer, Verifier};
use testresult::TestResult;

#[test]
#[serial]
fn sign_verify() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::EccKeyPairGen;

let secp256r1_oid: Vec<u8> = p256::NistP256::OID.to_der().unwrap();
println!("oid: {:x?}", secp256r1_oid);

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(false),
Attribute::Private(false),
Attribute::KeyType(KeyType::EC),
Attribute::Verify(true),
Attribute::EcParams(secp256r1_oid.clone()),
Attribute::Label(label.to_vec()),
];

// priv key template
let priv_key_template = vec![
Attribute::Token(false),
Attribute::Private(true),
Attribute::Sign(true),
Attribute::Label(label.to_vec()),
];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

// data to sign
let data = [0xFF, 0x55, 0xDD];

let signer =
ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");

let signature: p256::ecdsa::Signature = signer.sign(&data);

let verifying_key = signer.verifying_key();
verifying_key.verify(&data, &signature)?;

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}

#[test]
#[serial]
fn test_import() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

let mut rng = rand::thread_rng();
let private = p256::ecdsa::SigningKey::random(&mut rng);

let label = b"demo-import";

let template = vec![Attribute::Token(false), Attribute::Label(label.to_vec())];

let private_handle = p256::SecretKey::from(&private).put_key(&session, template.clone())?;
let public_handle =
p256::PublicKey::from(private.verifying_key()).put_key(&session, template)?;

// data to sign
let data = [0xFF, 0x55, 0xDD];

let signer =
ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");

let signature: p256::ecdsa::Signature = signer.sign(&data);

let verifying_key = private.verifying_key();
verifying_key.verify(&data, &signature)?;

// delete keys
session.destroy_object(private_handle)?;
session.destroy_object(public_handle)?;

Ok(())
}
57 changes: 57 additions & 0 deletions cryptoki-rustcrypto/tests/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

mod common;

use crate::common::USER_PIN;
use common::init_pins;
use cryptoki::{session::UserType, types::AuthPin};
use cryptoki_rustcrypto::rng::Rng;
use serial_test::serial;
use signature::rand_core::{CryptoRngCore, RngCore};
use testresult::TestResult;

// This test is meant to ensure we provide [`rand_core::CryptoRngCore`].
// This is the trait consumers will use throughout the RustCrypto ecosystem
// to express interest in a CSPRNG.
#[test]
#[serial]
fn ensure_crypto_rng_core() -> TestResult {
fn just_making_sure<R: CryptoRngCore>(_r: &mut R) {
// Hi! just making sure you provide a CSPRNG.
}
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

let mut rng = Rng::new(session).unwrap();
just_making_sure(&mut rng);

Ok(())
}

#[test]
#[serial]
fn generate_random() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

let mut rng = Rng::new(session).unwrap();
rng.next_u32();
rng.next_u64();

let mut buf = vec![0; 123];
rng.fill_bytes(&mut buf);
rng.try_fill_bytes(&mut buf).unwrap();

Ok(())
}
175 changes: 175 additions & 0 deletions cryptoki-rustcrypto/tests/rsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

mod common;

use crate::common::USER_PIN;
use common::init_pins;
use cryptoki::{mechanism::Mechanism, object::Attribute, session::UserType, types::AuthPin};
use cryptoki_rustcrypto::rsa::{pkcs1v15, pss};
use rand::{thread_rng, RngCore};
use serial_test::serial;
use sha2::{Digest, Sha256};
use signature::{hazmat::PrehashSigner, Keypair, Signer, Verifier};
use testresult::TestResult;

#[test]
#[serial]
fn pkcs1v15_sign_verify() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::RsaPkcsKeyPairGen;

let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
let modulus_bits = 1024;

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(true),
Attribute::Private(false),
Attribute::Label(label.to_vec()),
Attribute::PublicExponent(public_exponent),
Attribute::ModulusBits(modulus_bits.into()),
];

// priv key template
let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

// data to sign
let data = [0xFF, 0x55, 0xDD];

let signer = pkcs1v15::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");

let signature = signer.sign(&data);

let verifying_key = signer.verifying_key();
verifying_key.verify(&data, &signature)?;

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}

#[test]
#[serial]
fn pss_sign_verify() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::RsaPkcsKeyPairGen;

let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
let modulus_bits = 1024;

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(true),
Attribute::Private(false),
Attribute::Label(label.to_vec()),
Attribute::PublicExponent(public_exponent),
Attribute::ModulusBits(modulus_bits.into()),
];

// priv key template
let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

// data to sign
let data = [0xFF, 0x55, 0xDD];

let signer = pss::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");

let signature = signer.sign(&data);

let verifying_key = signer.verifying_key();
verifying_key.verify(&data, &signature)?;

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}

#[test]
#[serial]
fn pkcs1v15_sign_verify_prehashed() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::RsaPkcsKeyPairGen;

let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
let modulus_bits = 1024;

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(true),
Attribute::Private(false),
Attribute::Label(label.to_vec()),
Attribute::PublicExponent(public_exponent),
Attribute::ModulusBits(modulus_bits.into()),
];

// priv key template
let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

// data to sign
let mut data = [0u8; 7123];
thread_rng().fill_bytes(&mut data[..]);

let prehashed = Sha256::digest(&data[..]);

let signer = pkcs1v15::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");

let signature1 = signer.sign(&data);
let signature2 = signer.sign_prehash(&prehashed).expect("Sign prehash");

let verifying_key = signer.verifying_key();
verifying_key.verify(&data, &signature1)?;
verifying_key.verify(&data, &signature2)?;

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}
Binary file added cryptoki-rustcrypto/tests/verisign.der
Binary file not shown.
154 changes: 154 additions & 0 deletions cryptoki-rustcrypto/tests/x509-ca.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

mod common;

use crate::common::USER_PIN;
use common::init_pins;
use cryptoki::{
mechanism::Mechanism,
object::{Attribute, KeyType},
session::UserType,
types::AuthPin,
};
use cryptoki_rustcrypto::{ecdsa, rsa::pss};
use der::{pem::LineEnding, Encode, EncodePem};
use p256::pkcs8::AssociatedOid;
use serial_test::serial;
use signature::Keypair;
use spki::SubjectPublicKeyInfoOwned;
use std::{str::FromStr, time::Duration};
use testresult::TestResult;
use x509_cert::{
builder::{profile::cabf, Builder, CertificateBuilder},
name::Name,
serial_number::SerialNumber,
time::Validity,
};

#[test]
#[serial]
fn pss_create_ca() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::RsaPkcsKeyPairGen;

let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
let modulus_bits = 1024;

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(true),
Attribute::Private(false),
Attribute::Label(label.to_vec()),
Attribute::PublicExponent(public_exponent),
Attribute::ModulusBits(modulus_bits.into()),
];

// priv key template
let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

let signer =
pss::Signer::<sha2::Sha256, _>::new(&session, label).expect("Lookup keys from HSM");

let serial_number = SerialNumber::from(42u32);
let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
let subject =
Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
let profile = cabf::Root::new(false, subject).expect("Create root profile");
let pub_key = SubjectPublicKeyInfoOwned::from_key(&signer.verifying_key()).unwrap();

let builder = CertificateBuilder::new(profile, serial_number, validity, pub_key)
.expect("Create certificate");

let certificate = builder.build(&signer).unwrap();

let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
println!("{}", pem);

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}

#[test]
#[serial]
fn ecdsa_create_ca() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

// get mechanism
let mechanism = Mechanism::EccKeyPairGen;

let secp256r1_oid: Vec<u8> = p256::NistP256::OID.to_der().unwrap();

let label = b"demo-signer";

// pub key template
let pub_key_template = vec![
Attribute::Token(true),
Attribute::Private(false),
Attribute::KeyType(KeyType::EC),
Attribute::Verify(true),
Attribute::EcParams(secp256r1_oid.clone()),
Attribute::Label(label.to_vec()),
];

// priv key template
let priv_key_template = vec![
Attribute::Token(true),
Attribute::Private(true),
Attribute::Sign(true),
Attribute::Label(label.to_vec()),
];

// generate a key pair
let (public, private) =
session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;

let signer =
ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");

let serial_number = SerialNumber::from(42u32);
let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
let subject =
Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
let profile = cabf::Root::new(false, subject).expect("create root profile");
let pub_key = SubjectPublicKeyInfoOwned::from_key(&signer.verifying_key()).unwrap();

let builder = CertificateBuilder::new(profile, serial_number, validity, pub_key)
.expect("Create certificate");

let certificate = builder
.build::<_, p256::ecdsa::DerSignature>(&signer)
.unwrap();

let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
println!("{}", pem);

// delete keys
session.destroy_object(public)?;
session.destroy_object(private)?;

Ok(())
}
41 changes: 41 additions & 0 deletions cryptoki-rustcrypto/tests/x509.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

mod common;

use crate::common::USER_PIN;
use common::init_pins;
use cryptoki::{object::Attribute, session::UserType, types::AuthPin};
use cryptoki_rustcrypto::x509::CertPkcs11;
use der::Decode;
use serial_test::serial;
use testresult::TestResult;
use x509_cert::Certificate;

const VERISIGN_CERT: &[u8] = include_bytes!("./verisign.der");

#[test]
#[serial]
fn test_x509() -> TestResult {
let (pkcs11, slot) = init_pins();

// open a session
let session = pkcs11.open_rw_session(slot)?;

// log in the session
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;

let cert = Certificate::from_der(VERISIGN_CERT).expect("read certificate from der");

let base_template = vec![Attribute::Label(b"demo-cert".to_vec())];

cert.pkcs11_store(&session, base_template.clone())
.expect("Store cert with the PKCS11 provider");

let new = Certificate::pkcs11_load(&session, base_template)
.expect("Lookup cert from PKCS11 provider");

assert_eq!(cert, new);

Ok(())
}