diff options
Diffstat (limited to 'third_party/rust/cose/examples/sign_verify/nss.rs')
-rw-r--r-- | third_party/rust/cose/examples/sign_verify/nss.rs | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/third_party/rust/cose/examples/sign_verify/nss.rs b/third_party/rust/cose/examples/sign_verify/nss.rs new file mode 100644 index 0000000000..a5a827ceba --- /dev/null +++ b/third_party/rust/cose/examples/sign_verify/nss.rs @@ -0,0 +1,356 @@ +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) +} |