-
Notifications
You must be signed in to change notification settings - Fork 83
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
base: main
Are you sure you want to change the base?
Changes from all commits
1db24aa
90e734c
a229227
ca7b173
2e072d6
f333239
2c6186e
930d4ff
a54820b
b54ada2
eadf645
cfcb567
9736024
6ffe140
081bb1f
f4fff08
bc1215b
7f4cd92
af53442
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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" } |
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"] } |
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) | ||
} | ||
} |
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; | ||
|
||
baloo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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>; | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Source: https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html#tymethod.fill_bytes There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> {} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} |
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) | ||
} | ||
} |
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>() | ||
} | ||
} |
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) | ||
} | ||
} | ||
} |
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) | ||
} |
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(()) | ||
} |
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(()) | ||
} |
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(()) | ||
} |
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(()) | ||
} |
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(()) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.