356 lines
12 KiB
Rust
356 lines
12 KiB
Rust
use std::marker::PhantomData;
|
|
use std::{mem, ptr};
|
|
use std::os::raw;
|
|
use std::os::raw::c_char;
|
|
use cose::SignatureAlgorithm;
|
|
|
|
type SECItemType = raw::c_uint; // TODO: actually an enum - is this the right size?
|
|
const SI_BUFFER: SECItemType = 0; // called siBuffer in NSS
|
|
|
|
#[repr(C)]
|
|
struct SECItem {
|
|
typ: SECItemType,
|
|
data: *const u8,
|
|
len: raw::c_uint,
|
|
}
|
|
|
|
impl SECItem {
|
|
fn maybe_new(data: &[u8]) -> Result<SECItem, NSSError> {
|
|
if data.len() > u32::max_value() as usize {
|
|
return Err(NSSError::InputTooLarge);
|
|
}
|
|
Ok(SECItem {
|
|
typ: SI_BUFFER,
|
|
data: data.as_ptr(),
|
|
len: data.len() as u32,
|
|
})
|
|
}
|
|
|
|
fn maybe_from_parts(data: *const u8, len: usize) -> Result<SECItem, NSSError> {
|
|
if len > u32::max_value() as usize {
|
|
return Err(NSSError::InputTooLarge);
|
|
}
|
|
Ok(SECItem {
|
|
typ: SI_BUFFER,
|
|
data: data,
|
|
len: len as u32,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Many NSS APIs take constant data input as SECItems. Some, however, output data as SECItems.
|
|
/// To represent this, we define another type of mutable SECItem.
|
|
#[repr(C)]
|
|
struct SECItemMut<'a> {
|
|
typ: SECItemType,
|
|
data: *mut u8,
|
|
len: raw::c_uint,
|
|
_marker: PhantomData<&'a mut Vec<u8>>,
|
|
}
|
|
|
|
impl<'a> SECItemMut<'a> {
|
|
/// Given a mutable reference to a Vec<u8> that has a particular allocated capacity, create a
|
|
/// SECItemMut that points to the vec and has the same capacity.
|
|
/// The input vec is not expected to have any actual contents, and in any case is cleared.
|
|
fn maybe_from_empty_preallocated_vec(vec: &'a mut Vec<u8>) -> Result<SECItemMut<'a>, NSSError> {
|
|
if vec.capacity() > u32::max_value() as usize {
|
|
return Err(NSSError::InputTooLarge);
|
|
}
|
|
vec.clear();
|
|
Ok(SECItemMut {
|
|
typ: SI_BUFFER,
|
|
data: vec.as_mut_ptr(),
|
|
len: vec.capacity() as u32,
|
|
_marker: PhantomData,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
struct CkRsaPkcsPssParams {
|
|
// Called CK_RSA_PKCS_PSS_PARAMS in NSS
|
|
hash_alg: CkMechanismType, // Called hashAlg in NSS
|
|
mgf: CkRsaPkcsMgfType,
|
|
s_len: raw::c_ulong, // Called sLen in NSS
|
|
}
|
|
|
|
impl CkRsaPkcsPssParams {
|
|
fn new() -> CkRsaPkcsPssParams {
|
|
CkRsaPkcsPssParams {
|
|
hash_alg: CKM_SHA256,
|
|
mgf: CKG_MGF1_SHA256,
|
|
s_len: 32,
|
|
}
|
|
}
|
|
|
|
fn get_params_item(&self) -> Result<SECItem, NSSError> {
|
|
// This isn't entirely NSS' fault, but it mostly is.
|
|
let params_ptr: *const CkRsaPkcsPssParams = self;
|
|
let params_ptr: *const u8 = params_ptr as *const u8;
|
|
let params_secitem =
|
|
SECItem::maybe_from_parts(params_ptr, mem::size_of::<CkRsaPkcsPssParams>())?;
|
|
Ok(params_secitem)
|
|
}
|
|
}
|
|
|
|
type CkMechanismType = raw::c_ulong; // called CK_MECHANISM_TYPE in NSS
|
|
const CKM_ECDSA: CkMechanismType = 0x0000_1041;
|
|
const CKM_RSA_PKCS_PSS: CkMechanismType = 0x0000_000D;
|
|
const CKM_SHA256: CkMechanismType = 0x0000_0250;
|
|
|
|
type CkRsaPkcsMgfType = raw::c_ulong; // called CK_RSA_PKCS_MGF_TYPE in NSS
|
|
const CKG_MGF1_SHA256: CkRsaPkcsMgfType = 0x0000_0002;
|
|
|
|
type SECStatus = raw::c_int; // TODO: enum - right size?
|
|
const SEC_SUCCESS: SECStatus = 0; // Called SECSuccess in NSS
|
|
const SEC_FAILURE: SECStatus = -1; // Called SECFailure in NSS
|
|
|
|
enum SECKEYPublicKey {}
|
|
enum SECKEYPrivateKey {}
|
|
enum PK11SlotInfo {}
|
|
enum CERTCertificate {}
|
|
enum CERTCertDBHandle {}
|
|
|
|
const SHA256_LENGTH: usize = 32;
|
|
const SHA384_LENGTH: usize = 48;
|
|
const SHA512_LENGTH: usize = 64;
|
|
|
|
// TODO: ugh this will probably have a platform-specific name...
|
|
#[link(name = "nss3")]
|
|
extern "C" {
|
|
fn PK11_HashBuf(
|
|
hashAlg: HashAlgorithm,
|
|
out: *mut u8,
|
|
data_in: *const u8, // called "in" in NSS
|
|
len: raw::c_int,
|
|
) -> SECStatus;
|
|
fn PK11_VerifyWithMechanism(
|
|
key: *const SECKEYPublicKey,
|
|
mechanism: CkMechanismType,
|
|
param: *const SECItem,
|
|
sig: *const SECItem,
|
|
hash: *const SECItem,
|
|
wincx: *const raw::c_void,
|
|
) -> SECStatus;
|
|
|
|
fn SECKEY_DestroyPublicKey(pubk: *const SECKEYPublicKey);
|
|
|
|
fn CERT_GetDefaultCertDB() -> *const CERTCertDBHandle;
|
|
fn CERT_DestroyCertificate(cert: *mut CERTCertificate);
|
|
fn CERT_NewTempCertificate(
|
|
handle: *const CERTCertDBHandle,
|
|
derCert: *const SECItem,
|
|
nickname: *const c_char,
|
|
isperm: bool,
|
|
copyDER: bool,
|
|
) -> *mut CERTCertificate;
|
|
fn CERT_ExtractPublicKey(cert: *const CERTCertificate) -> *const SECKEYPublicKey;
|
|
|
|
fn PK11_ImportDERPrivateKeyInfoAndReturnKey(
|
|
slot: *mut PK11SlotInfo,
|
|
derPKI: *const SECItem,
|
|
nickname: *const SECItem,
|
|
publicValue: *const SECItem,
|
|
isPerm: bool,
|
|
isPrivate: bool,
|
|
keyUsage: u32,
|
|
privk: *mut *mut SECKEYPrivateKey,
|
|
wincx: *const u8,
|
|
) -> SECStatus;
|
|
fn PK11_GetInternalSlot() -> *mut PK11SlotInfo;
|
|
fn PK11_FreeSlot(slot: *mut PK11SlotInfo);
|
|
fn PK11_SignatureLen(key: *const SECKEYPrivateKey) -> usize;
|
|
fn PK11_SignWithMechanism(
|
|
key: *const SECKEYPrivateKey,
|
|
mech: CkMechanismType,
|
|
param: *const SECItem,
|
|
sig: *mut SECItemMut,
|
|
hash: *const SECItem,
|
|
) -> SECStatus;
|
|
}
|
|
|
|
/// An error type describing errors that may be encountered during verification.
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum NSSError {
|
|
ImportCertError,
|
|
DecodingPKCS8Failed,
|
|
InputTooLarge,
|
|
LibraryFailure,
|
|
SignatureVerificationFailed,
|
|
SigningFailed,
|
|
ExtractPublicKeyFailed,
|
|
}
|
|
|
|
// https://searchfox.org/nss/rev/990c2e793aa731cd66238c6c4f00b9473943bc66/lib/util/secoidt.h#274
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
#[repr(C)]
|
|
enum HashAlgorithm {
|
|
SHA256 = 191,
|
|
SHA384 = 192,
|
|
SHA512 = 193,
|
|
}
|
|
|
|
fn hash(payload: &[u8], signature_algorithm: &SignatureAlgorithm) -> Result<Vec<u8>, NSSError> {
|
|
if payload.len() > raw::c_int::max_value() as usize {
|
|
return Err(NSSError::InputTooLarge);
|
|
}
|
|
let (hash_algorithm, digest_length) = match *signature_algorithm {
|
|
SignatureAlgorithm::ES256 => (HashAlgorithm::SHA256, SHA256_LENGTH),
|
|
SignatureAlgorithm::ES384 => (HashAlgorithm::SHA384, SHA384_LENGTH),
|
|
SignatureAlgorithm::ES512 => (HashAlgorithm::SHA512, SHA512_LENGTH),
|
|
SignatureAlgorithm::PS256 => (HashAlgorithm::SHA256, SHA256_LENGTH),
|
|
};
|
|
let mut hash_buf = vec![0; digest_length];
|
|
let len: raw::c_int = payload.len() as raw::c_int;
|
|
let hash_result =
|
|
unsafe { PK11_HashBuf(hash_algorithm, hash_buf.as_mut_ptr(), payload.as_ptr(), len) };
|
|
if hash_result != SEC_SUCCESS {
|
|
return Err(NSSError::LibraryFailure);
|
|
}
|
|
Ok(hash_buf)
|
|
}
|
|
|
|
/// Main entrypoint for verification. Given a signature algorithm, the bytes of a subject public key
|
|
/// info, a payload, and a signature over the payload, returns a result based on the outcome of
|
|
/// decoding the subject public key info and running the signature verification algorithm on the
|
|
/// signed data.
|
|
pub fn verify_signature(
|
|
signature_algorithm: &SignatureAlgorithm,
|
|
cert: &[u8],
|
|
payload: &[u8],
|
|
signature: &[u8],
|
|
) -> Result<(), NSSError> {
|
|
let slot = unsafe { PK11_GetInternalSlot() };
|
|
if slot.is_null() {
|
|
return Err(NSSError::LibraryFailure);
|
|
}
|
|
defer!(unsafe {
|
|
PK11_FreeSlot(slot);
|
|
});
|
|
|
|
let hash_buf = hash(payload, signature_algorithm).unwrap();
|
|
let hash_item = SECItem::maybe_new(hash_buf.as_slice())?;
|
|
|
|
// Import DER cert into NSS.
|
|
let der_cert = SECItem::maybe_new(cert)?;
|
|
let db_handle = unsafe { CERT_GetDefaultCertDB() };
|
|
if db_handle.is_null() {
|
|
// TODO #28
|
|
return Err(NSSError::LibraryFailure);
|
|
}
|
|
let nss_cert =
|
|
unsafe { CERT_NewTempCertificate(db_handle, &der_cert, ptr::null(), false, true) };
|
|
if nss_cert.is_null() {
|
|
return Err(NSSError::ImportCertError);
|
|
}
|
|
defer!(unsafe {
|
|
CERT_DestroyCertificate(nss_cert);
|
|
});
|
|
|
|
let key = unsafe { CERT_ExtractPublicKey(nss_cert) };
|
|
if key.is_null() {
|
|
return Err(NSSError::ExtractPublicKeyFailed);
|
|
}
|
|
defer!(unsafe {
|
|
SECKEY_DestroyPublicKey(key);
|
|
});
|
|
let signature_item = SECItem::maybe_new(signature)?;
|
|
let mechanism = match *signature_algorithm {
|
|
SignatureAlgorithm::ES256 => CKM_ECDSA,
|
|
SignatureAlgorithm::ES384 => CKM_ECDSA,
|
|
SignatureAlgorithm::ES512 => CKM_ECDSA,
|
|
SignatureAlgorithm::PS256 => CKM_RSA_PKCS_PSS,
|
|
};
|
|
let rsa_pss_params = CkRsaPkcsPssParams::new();
|
|
let rsa_pss_params_item = rsa_pss_params.get_params_item()?;
|
|
let params_item = match *signature_algorithm {
|
|
SignatureAlgorithm::ES256 => ptr::null(),
|
|
SignatureAlgorithm::ES384 => ptr::null(),
|
|
SignatureAlgorithm::ES512 => ptr::null(),
|
|
SignatureAlgorithm::PS256 => &rsa_pss_params_item,
|
|
};
|
|
let null_cx_ptr: *const raw::c_void = ptr::null();
|
|
let result = unsafe {
|
|
PK11_VerifyWithMechanism(
|
|
key,
|
|
mechanism,
|
|
params_item,
|
|
&signature_item,
|
|
&hash_item,
|
|
null_cx_ptr,
|
|
)
|
|
};
|
|
match result {
|
|
SEC_SUCCESS => Ok(()),
|
|
SEC_FAILURE => Err(NSSError::SignatureVerificationFailed),
|
|
_ => Err(NSSError::LibraryFailure),
|
|
}
|
|
}
|
|
|
|
pub fn sign(
|
|
signature_algorithm: &SignatureAlgorithm,
|
|
pk8: &[u8],
|
|
payload: &[u8],
|
|
) -> Result<Vec<u8>, NSSError> {
|
|
let slot = unsafe { PK11_GetInternalSlot() };
|
|
if slot.is_null() {
|
|
return Err(NSSError::LibraryFailure);
|
|
}
|
|
defer!(unsafe {
|
|
PK11_FreeSlot(slot);
|
|
});
|
|
let pkcs8item = SECItem::maybe_new(pk8)?;
|
|
let mut key: *mut SECKEYPrivateKey = ptr::null_mut();
|
|
let ku_all = 0xFF;
|
|
let rv = unsafe {
|
|
PK11_ImportDERPrivateKeyInfoAndReturnKey(
|
|
slot,
|
|
&pkcs8item,
|
|
ptr::null(),
|
|
ptr::null(),
|
|
false,
|
|
false,
|
|
ku_all,
|
|
&mut key,
|
|
ptr::null(),
|
|
)
|
|
};
|
|
if rv != SEC_SUCCESS || key.is_null() {
|
|
return Err(NSSError::DecodingPKCS8Failed);
|
|
}
|
|
let mechanism = match *signature_algorithm {
|
|
SignatureAlgorithm::ES256 => CKM_ECDSA,
|
|
SignatureAlgorithm::ES384 => CKM_ECDSA,
|
|
SignatureAlgorithm::ES512 => CKM_ECDSA,
|
|
SignatureAlgorithm::PS256 => CKM_RSA_PKCS_PSS,
|
|
};
|
|
let rsa_pss_params = CkRsaPkcsPssParams::new();
|
|
let rsa_pss_params_item = rsa_pss_params.get_params_item()?;
|
|
let params_item = match *signature_algorithm {
|
|
SignatureAlgorithm::ES256 => ptr::null(),
|
|
SignatureAlgorithm::ES384 => ptr::null(),
|
|
SignatureAlgorithm::ES512 => ptr::null(),
|
|
SignatureAlgorithm::PS256 => &rsa_pss_params_item,
|
|
};
|
|
let signature_len = unsafe { PK11_SignatureLen(key) };
|
|
// Allocate enough space for the signature.
|
|
let mut signature: Vec<u8> = Vec::with_capacity(signature_len);
|
|
let hash_buf = hash(payload, signature_algorithm).unwrap();
|
|
let hash_item = SECItem::maybe_new(hash_buf.as_slice())?;
|
|
{
|
|
// Get a mutable SECItem on the preallocated signature buffer. PK11_SignWithMechanism will
|
|
// fill the SECItem's buf with the bytes of the signature.
|
|
let mut signature_item = SECItemMut::maybe_from_empty_preallocated_vec(&mut signature)?;
|
|
let rv = unsafe {
|
|
PK11_SignWithMechanism(key, mechanism, params_item, &mut signature_item, &hash_item)
|
|
};
|
|
if rv != SEC_SUCCESS || signature_item.len as usize != signature_len {
|
|
return Err(NSSError::SigningFailed);
|
|
}
|
|
}
|
|
unsafe {
|
|
// Now that the bytes of the signature have been filled out, set its length.
|
|
signature.set_len(signature_len);
|
|
}
|
|
Ok(signature)
|
|
}
|