bug 1591271 - osclientcerts: support RSA-PSS on Windows r=kjacobs

Differential Revision: https://phabricator.services.mozilla.com/D50662

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dana Keeler 2019-10-30 22:45:07 +00:00
Родитель 5da49ed3d7
Коммит 06dafb8707
9 изменённых файлов: 299 добавлений и 102 удалений

Просмотреть файл

@ -250,8 +250,87 @@ impl Deref for NCryptKeyHandle {
}
}
// In some cases, the ncrypt API takes a pointer to a null-terminated wide-character string as a way
// of specifying an algorithm. The "right" way to do this would be to take the corresponding
// &'static str constant provided by the winapi crate, create an OsString from it, encode it as wide
// characters, and collect it into a Vec<u16>. However, since the implementation that provides this
// functionality isn't constant, we would have to manage the memory this creates and uses. Since
// rust structures generally can't be self-referrential, this memory would have to live elsewhere,
// and the nice abstractions we've created for this implementation start to break down. It's much
// simpler to hard-code the identifiers we support, since there are only four of them.
// The following arrays represent the identifiers "SHA1", "SHA256", "SHA384", and "SHA512",
// respectively.
const SHA1_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 49, 0];
const SHA256_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 50, 53, 54, 0];
const SHA384_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 51, 56, 52, 0];
const SHA512_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 53, 49, 50, 0];
enum SignParams {
EC,
RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO),
RSA_PSS(BCRYPT_PSS_PADDING_INFO),
}
impl SignParams {
fn new(key_type: KeyType, params: &Option<CK_RSA_PKCS_PSS_PARAMS>) -> Result<SignParams, ()> {
// EC is easy, so handle that first.
match key_type {
KeyType::EC => return Ok(SignParams::EC),
KeyType::RSA => {}
}
// If `params` is `Some`, we're doing RSA-PSS. If it is `None`, we're doing RSA-PKCS1.
let pss_params = match params {
Some(pss_params) => pss_params,
None => {
// The hash algorithm should be encoded in the data to be signed, so we don't have to
// (and don't want to) specify a particular algorithm here.
return Ok(SignParams::RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO {
pszAlgId: std::ptr::null(),
}));
}
};
let algorithm_string = match pss_params.hashAlg {
CKM_SHA_1 => SHA1_ALGORITHM_STRING,
CKM_SHA256 => SHA256_ALGORITHM_STRING,
CKM_SHA384 => SHA384_ALGORITHM_STRING,
CKM_SHA512 => SHA512_ALGORITHM_STRING,
_ => {
error!(
"unsupported algorithm to use with RSA-PSS: {}",
unsafe_packed_field_access!(pss_params.hashAlg)
);
return Err(());
}
};
Ok(SignParams::RSA_PSS(BCRYPT_PSS_PADDING_INFO {
pszAlgId: algorithm_string.as_ptr(),
cbSalt: pss_params.sLen,
}))
}
fn params_ptr(&mut self) -> *mut std::ffi::c_void {
match self {
SignParams::EC => std::ptr::null_mut(),
SignParams::RSA_PKCS1(params) => {
params as *mut BCRYPT_PKCS1_PADDING_INFO as *mut std::ffi::c_void
}
SignParams::RSA_PSS(params) => {
params as *mut BCRYPT_PSS_PADDING_INFO as *mut std::ffi::c_void
}
}
}
fn flags(&self) -> u32 {
match self {
&SignParams::EC => 0,
&SignParams::RSA_PKCS1(_) => NCRYPT_PAD_PKCS1_FLAG,
&SignParams::RSA_PSS(_) => NCRYPT_PAD_PSS_FLAG,
}
}
}
/// A helper enum to identify a private key's type. We support EC and RSA.
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub enum KeyType {
EC,
RSA,
@ -405,45 +484,48 @@ impl Key {
}
}
pub fn get_signature_length(&self, data: &[u8]) -> Result<usize, ()> {
match self.sign_internal(data, false) {
pub fn get_signature_length(
&self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<usize, ()> {
match self.sign_internal(data, params, false) {
Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()),
Err(()) => Err(()),
}
}
pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>, ()> {
self.sign_internal(data, true)
pub fn sign(
&self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<Vec<u8>, ()> {
self.sign_internal(data, params, true)
}
/// data: the data to sign
/// do_signature: if true, actually perform the signature. Otherwise, return a `Vec<u8>` of the
/// length the signature would be, if performed.
fn sign_internal(&self, data: &[u8], do_signature: bool) -> Result<Vec<u8>, ()> {
fn sign_internal(
&self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
do_signature: bool,
) -> Result<Vec<u8>, ()> {
// Acquiring a handle on the key can cause the OS to show some UI to the user, so we do this
// as late as possible (i.e. here).
let key = NCryptKeyHandle::from_cert(&self.cert)?;
// This only applies to RSA.
let mut padding_info = BCRYPT_PKCS1_PADDING_INFO {
// Because the hash algorithm is encoded in `data`, we don't have to (and don't want to)
// specify a particular algorithm here.
pszAlgId: std::ptr::null(),
};
let (padding_info_ptr, flags) = match self.key_type_enum {
KeyType::EC => (std::ptr::null_mut(), 0),
KeyType::RSA => (
&mut padding_info as *mut BCRYPT_PKCS1_PADDING_INFO,
NCRYPT_PAD_PKCS1_FLAG,
),
};
let mut sign_params = SignParams::new(self.key_type_enum, params)?;
let params_ptr = sign_params.params_ptr();
let flags = sign_params.flags();
let mut data = data.to_vec();
let mut signature_len = 0;
// We call NCryptSignHash twice: the first time to get the size of the buffer we need to
// allocate and then again to actually sign the data.
// allocate and then again to actually sign the data, if `do_signature` is `true`.
let status = unsafe {
NCryptSignHash(
*key,
padding_info_ptr as *mut std::ffi::c_void,
params_ptr,
data.as_mut_ptr(),
data.len().try_into().map_err(|_| ())?,
std::ptr::null_mut(),
@ -468,7 +550,7 @@ impl Key {
let status = unsafe {
NCryptSignHash(
*key,
padding_info_ptr as *mut std::ffi::c_void,
params_ptr,
data.as_mut_ptr(),
data.len().try_into().map_err(|_| ())?,
signature.as_mut_ptr(),

Просмотреть файл

@ -19,10 +19,11 @@ extern crate winapi;
use pkcs11::types::*;
use std::sync::Mutex;
mod manager;
#[macro_use]
mod util;
#[cfg(target_os = "windows")]
mod backend_windows;
mod manager;
mod util;
use manager::Manager;
@ -167,8 +168,8 @@ extern "C" fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK
CKR_OK
}
/// This gets called to determine what mechanisms a slot supports. This implementation does not
/// support any mechanisms.
/// This gets called to determine what mechanisms a slot supports. This implementation supports
/// ECDSA, RSA PKCS, and RSA PSS.
extern "C" fn C_GetMechanismList(
slotID: CK_SLOT_ID,
pMechanismList: CK_MECHANISM_TYPE_PTR,
@ -178,10 +179,20 @@ extern "C" fn C_GetMechanismList(
error!("C_GetMechanismList: CKR_ARGUMENTS_BAD");
return CKR_ARGUMENTS_BAD;
}
if pMechanismList.is_null() {
unsafe {
*pulCount = 0;
let mechanisms = [CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS];
if !pMechanismList.is_null() {
if unsafe { *pulCount as usize } < mechanisms.len() {
error!("C_GetMechanismList: CKR_ARGUMENTS_BAD");
return CKR_ARGUMENTS_BAD;
}
for i in 0..mechanisms.len() {
unsafe {
*pMechanismList.offset(i as isize) = mechanisms[i];
}
}
}
unsafe {
*pulCount = mechanisms.len() as CK_ULONG;
}
debug!("C_GetMechanismList: CKR_OK");
CKR_OK
@ -618,12 +629,24 @@ extern "C" fn C_SignInit(
error!("C_SignInit: CKR_ARGUMENTS_BAD");
return CKR_ARGUMENTS_BAD;
}
// pMechanism generally appears to be empty (just mechanism is set).
// Presumably we should validate the mechanism against hKey, but the specification doesn't
// actually seem to require this.
debug!("C_SignInit: mechanism is {:?}", unsafe { *pMechanism });
let mechanism = unsafe { *pMechanism };
debug!("C_SignInit: mechanism is {:?}", mechanism);
let mechanism_params = if mechanism.mechanism == CKM_RSA_PKCS_PSS {
if mechanism.ulParameterLen as usize != std::mem::size_of::<CK_RSA_PKCS_PSS_PARAMS>() {
error!(
"C_SignInit: bad ulParameterLen for CKM_RSA_PKCS_PSS: {}",
unsafe_packed_field_access!(mechanism.ulParameterLen)
);
return CKR_ARGUMENTS_BAD;
}
Some(unsafe { *(mechanism.pParameter as *const CK_RSA_PKCS_PSS_PARAMS) })
} else {
None
};
let mut manager = try_to_get_manager!();
match manager.start_sign(hSession, hKey) {
match manager.start_sign(hSession, hKey, mechanism_params) {
Ok(()) => {}
Err(()) => {
error!("C_SignInit: CKR_GENERAL_ERROR");

Просмотреть файл

@ -18,8 +18,9 @@ pub struct Manager {
sessions: BTreeSet<CK_SESSION_HANDLE>,
/// A map of searches to PKCS #11 object handles that match those searches.
searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>,
/// A map of sign operations to the object handle being used by each one.
signs: BTreeMap<CK_SESSION_HANDLE, CK_OBJECT_HANDLE>,
/// A map of sign operations to a pair of the object handle and optionally some params being
/// used by each one.
signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>,
/// A map of object handles to the underlying objects.
objects: BTreeMap<CK_OBJECT_HANDLE, Object>,
/// A set of certificate identifiers (not the same as handles).
@ -177,6 +178,7 @@ impl Manager {
&mut self,
session: CK_SESSION_HANDLE,
key_handle: CK_OBJECT_HANDLE,
params: Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<(), ()> {
if self.signs.contains_key(&session) {
return Err(());
@ -185,7 +187,7 @@ impl Manager {
Some(Object::Key(_)) => {}
_ => return Err(()),
};
self.signs.insert(session, key_handle);
self.signs.insert(session, (key_handle, params));
Ok(())
}
@ -194,28 +196,28 @@ impl Manager {
session: CK_SESSION_HANDLE,
data: &[u8],
) -> Result<usize, ()> {
let key_handle = match self.signs.get(&session) {
Some(key_handle) => key_handle,
let (key_handle, params) = match self.signs.get(&session) {
Some((key_handle, params)) => (key_handle, params),
None => return Err(()),
};
let key = match self.objects.get(&key_handle) {
Some(Object::Key(key)) => key,
_ => return Err(()),
};
key.get_signature_length(data)
key.get_signature_length(data, params)
}
pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result<Vec<u8>, ()> {
// Performing the signature (via C_Sign, which is the only way we support) finishes the sign
// operation, so it needs to be removed here.
let key_handle = match self.signs.remove(&session) {
Some(key_handle) => key_handle,
let (key_handle, params) = match self.signs.remove(&session) {
Some((key_handle, params)) => (key_handle, params),
None => return Err(()),
};
let key = match self.objects.get(&key_handle) {
Some(Object::Key(key)) => key,
_ => return Err(()),
};
key.sign(data)
key.sign(data, &params)
}
}

Просмотреть файл

@ -6,6 +6,19 @@
use byteorder::{BigEndian, NativeEndian, ReadBytesExt, WriteBytesExt};
use std::convert::TryInto;
/// Accessing fields of packed structs is unsafe (it may be undefined behavior if the field isn't
/// aligned). Since we're implementing a PKCS#11 module, we already have to trust the caller not to
/// give us bad data, so normally we would deal with this by adding an unsafe block. If we do that,
/// though, the compiler complains that the unsafe block is unnecessary. Thus, we use this macro to
/// annotate the unsafe block to silence the compiler.
macro_rules! unsafe_packed_field_access {
($e:expr) => {{
#[allow(unused_unsafe)]
let tmp = unsafe { $e };
tmp
}};
}
// This is a helper function to take a value and lay it out in memory how
// PKCS#11 is expecting it.
pub fn serialize_uint<T: TryInto<u64>>(value: T) -> Result<Vec<u8>, ()> {

Просмотреть файл

@ -1038,6 +1038,11 @@ function asyncTestCertificateUsages(certdb, cert, expectedUsages) {
* Loads the pkcs11testmodule.cpp test PKCS #11 module, and registers a cleanup
* function that unloads it once the calling test completes.
*
* @param {nsIFile} libraryFile
* The dynamic library file that implements the module to
* load.
* @param {String} moduleName
* What to call the module.
* @param {Boolean} expectModuleUnloadToFail
* Should be set to true for tests that manually unload the
* test module, so the attempt to auto unload the test module
@ -1045,18 +1050,15 @@ function asyncTestCertificateUsages(certdb, cert, expectedUsages) {
* otherwise, so failure to automatically unload the test
* module gets reported.
*/
function loadPKCS11TestModule(expectModuleUnloadToFail) {
let libraryFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
libraryFile.append("pkcs11testmodule");
libraryFile.append(ctypes.libraryName("pkcs11testmodule"));
ok(libraryFile.exists(), "The pkcs11testmodule file should exist");
function loadPKCS11Module(libraryFile, moduleName, expectModuleUnloadToFail) {
ok(libraryFile.exists(), "The PKCS11 module file should exist");
let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB
);
registerCleanupFunction(() => {
try {
pkcs11ModuleDB.deleteModule("PKCS11 Test Module");
pkcs11ModuleDB.deleteModule(moduleName);
} catch (e) {
Assert.ok(
expectModuleUnloadToFail,
@ -1064,7 +1066,7 @@ function loadPKCS11TestModule(expectModuleUnloadToFail) {
);
}
});
pkcs11ModuleDB.addModule("PKCS11 Test Module", libraryFile.path, 0, 0);
pkcs11ModuleDB.addModule(moduleName, libraryFile.path, 0, 0);
}
/**
@ -1094,3 +1096,70 @@ function writeLinesAndClose(lines, outputStream) {
}
outputStream.close();
}
/**
* @param {String} moduleName
* The name of the module that should not be loaded.
* @param {String} libraryName
* A unique substring of name of the dynamic library file of the module
* that should not be loaded.
*/
function checkPKCS11ModuleNotPresent(moduleName, libraryName) {
let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB
);
let modules = moduleDB.listModules();
ok(
modules.hasMoreElements(),
"One or more modules should be present with test module not present"
);
for (let module of modules) {
notEqual(
module.name,
moduleName,
"Non-test module name shouldn't equal 'PKCS11 Test Module'"
);
ok(
!(module.libName && module.libName.includes(libraryName)),
`Non-test module lib name should not include '${libraryName}'`
);
}
}
/**
* Checks that the test module exists in the module list.
* Also checks various attributes of the test module for correctness.
*
* @param {String} moduleName
* The name of the module that should be present.
* @param {String} libraryName
* A unique substring of the name of the dynamic library file
* of the module that should be loaded.
* @returns {nsIPKCS11Module}
* The test module.
*/
function checkPKCS11ModuleExists(moduleName, libraryName) {
let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB
);
let modules = moduleDB.listModules();
ok(
modules.hasMoreElements(),
"One or more modules should be present with test module present"
);
let testModule = null;
for (let module of modules) {
if (module.name == moduleName) {
testModule = module;
break;
}
}
notEqual(testModule, null, "Test module should have been found");
notEqual(testModule.libName, null, "Test module lib name should not be null");
ok(
testModule.libName.includes(ctypes.libraryName(libraryName)),
`Test module lib name should include lib name of '${libraryName}'`
);
return testModule;
}

Просмотреть файл

@ -0,0 +1,46 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";
// Tests that the platform can load the osclientcerts module.
// Ensure that the appropriate initialization has happened.
do_get_profile();
function run_test() {
// Check that if we have never added the osclientcerts module, that we don't
// find it in the module list.
checkPKCS11ModuleNotPresent("OS Client Cert Module", "osclientcerts");
// Check that adding the osclientcerts module makes it appear in the module
// list.
let libraryFile = Services.dirsvc.get("GreBinD", Ci.nsIFile);
libraryFile.append(ctypes.libraryName("osclientcerts"));
loadPKCS11Module(libraryFile, "OS Client Cert Module", true);
let testModule = checkPKCS11ModuleExists(
"OS Client Cert Module",
"osclientcerts"
);
// Check that listing the slots for the osclientcerts module works.
let testModuleSlotNames = Array.from(
testModule.listSlots(),
slot => slot.name
);
testModuleSlotNames.sort();
const expectedSlotNames = ["OS Client Cert Slot"];
deepEqual(
testModuleSlotNames,
expectedSlotNames,
"Actual and expected slot names should be equal"
);
// Check that deleting the osclientcerts module makes it disappear from the
// module list.
let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB
);
pkcs11ModuleDB.deleteModule("OS Client Cert Module");
checkPKCS11ModuleNotPresent("OS Client Cert Module", "osclientcerts");
}

Просмотреть файл

@ -13,63 +13,20 @@ const gModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB
);
function checkTestModuleNotPresent() {
let modules = gModuleDB.listModules();
ok(
modules.hasMoreElements(),
"One or more modules should be present with test module not present"
);
for (let module of modules) {
notEqual(
module.name,
"PKCS11 Test Module",
"Non-test module name shouldn't equal 'PKCS11 Test Module'"
);
ok(
!(module.libName && module.libName.includes("pkcs11testmodule")),
"Non-test module lib name should not include 'pkcs11testmodule'"
);
}
}
/**
* Checks that the test module exists in the module list.
* Also checks various attributes of the test module for correctness.
*
* @returns {nsIPKCS11Module}
* The test module.
*/
function checkTestModuleExists() {
let modules = gModuleDB.listModules();
ok(
modules.hasMoreElements(),
"One or more modules should be present with test module present"
);
let testModule = null;
for (let module of modules) {
if (module.name == "PKCS11 Test Module") {
testModule = module;
break;
}
}
notEqual(testModule, null, "Test module should have been found");
notEqual(testModule.libName, null, "Test module lib name should not be null");
ok(
testModule.libName.includes(ctypes.libraryName("pkcs11testmodule")),
"Test module lib name should include lib name of 'pkcs11testmodule'"
);
return testModule;
}
function run_test() {
// Check that if we have never added the test module, that we don't find it
// in the module list.
checkTestModuleNotPresent();
checkPKCS11ModuleNotPresent("PKCS11 Test Module", "pkcs11testmodule");
// Check that adding the test module makes it appear in the module list.
loadPKCS11TestModule(true);
let testModule = checkTestModuleExists();
let libraryFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
libraryFile.append("pkcs11testmodule");
libraryFile.append(ctypes.libraryName("pkcs11testmodule"));
loadPKCS11Module(libraryFile, "PKCS11 Test Module", true);
let testModule = checkPKCS11ModuleExists(
"PKCS11 Test Module",
"pkcs11testmodule"
);
// Check that listing the slots for the test module works.
let testModuleSlotNames = Array.from(
@ -93,7 +50,7 @@ function run_test() {
Ci.nsIPKCS11ModuleDB
);
pkcs11ModuleDB.deleteModule("PKCS11 Test Module");
checkTestModuleNotPresent();
checkPKCS11ModuleNotPresent("PKCS11 Test Module", "pkcs11testmodule");
// Check miscellaneous module DB methods and attributes.
ok(!gModuleDB.canToggleFIPS, "It should NOT be possible to toggle FIPS");

Просмотреть файл

@ -27,7 +27,10 @@ function find_module_by_name(moduleDB, name) {
}
function run_test() {
loadPKCS11TestModule(false);
let libraryFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
libraryFile.append("pkcs11testmodule");
libraryFile.append(ctypes.libraryName("pkcs11testmodule"));
loadPKCS11Module(libraryFile, "PKCS11 Test Module", false);
let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
Ci.nsIPKCS11ModuleDB

Просмотреть файл

@ -5,6 +5,8 @@ tags = psm
skip-if = toolkit == 'android'
support-files =
[test_osclientcerts_module.js]
skip-if = os != 'win' || processor == 'aarch64'
[test_pkcs11_module.js]
[test_pkcs11_moduleDB.js]
[test_pkcs11_safe_mode.js]