diff options
Diffstat (limited to 'security/manager/ssl/builtins')
-rw-r--r-- | security/manager/ssl/builtins/Cargo.toml | 18 | ||||
-rw-r--r-- | security/manager/ssl/builtins/build.rs | 568 | ||||
-rw-r--r-- | security/manager/ssl/builtins/dynamic-library/builtins.symbols | 1 | ||||
-rw-r--r-- | security/manager/ssl/builtins/dynamic-library/moz.build | 38 | ||||
-rw-r--r-- | security/manager/ssl/builtins/dynamic-library/stub.c | 27 | ||||
-rw-r--r-- | security/manager/ssl/builtins/dynamic-library/stub.cpp | 17 | ||||
-rw-r--r-- | security/manager/ssl/builtins/moz.build | 9 | ||||
-rw-r--r-- | security/manager/ssl/builtins/src/certdata.rs | 52 | ||||
-rw-r--r-- | security/manager/ssl/builtins/src/internal.rs | 344 | ||||
-rw-r--r-- | security/manager/ssl/builtins/src/lib.rs | 9 | ||||
-rw-r--r-- | security/manager/ssl/builtins/src/pkcs11.rs | 1229 | ||||
-rw-r--r-- | security/manager/ssl/builtins/src/version.rs | 6 |
12 files changed, 2318 insertions, 0 deletions
diff --git a/security/manager/ssl/builtins/Cargo.toml b/security/manager/ssl/builtins/Cargo.toml new file mode 100644 index 0000000000..b3f2e9c7b8 --- /dev/null +++ b/security/manager/ssl/builtins/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "builtins-static" +version = "0.1.0" +authors = ["John Schanck <jschanck@mozilla.com>"] +edition = "2021" +license = "MPL-2.0" + +[dependencies] +pkcs11-bindings = "0.1.1" +smallvec = { version = "1.9.0", features = ["const_new"] } +mozilla-central-workspace-hack = { version = "0.1", features = ["builtins-static"], optional = true } + +[build-dependencies] +bindgen = { default-features = false, features = ["runtime"], version = "0.69" } +nom = "7.1.1" + +[lib] +crate-type = ["staticlib"] diff --git a/security/manager/ssl/builtins/build.rs b/security/manager/ssl/builtins/build.rs new file mode 100644 index 0000000000..8375f3368d --- /dev/null +++ b/security/manager/ssl/builtins/build.rs @@ -0,0 +1,568 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate bindgen; +extern crate nom; + +use bindgen::callbacks::*; +use bindgen::*; + +use nom::branch::alt; +use nom::bytes::complete::{tag, take_until}; +use nom::character::complete::{ + char, multispace0, newline, not_line_ending, one_of, space0, space1, +}; +use nom::combinator::{fail, recognize}; +use nom::multi::{many1, separated_list0}; +use nom::sequence::{delimited, separated_pair, terminated, tuple}; +use nom::IResult; + +use std::collections::HashMap; +use std::env; +use std::fmt; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; + +fn octal_block_to_vec_u8(octal_block: &str) -> Vec<u8> { + octal_block + .lines() + .flat_map(|x| x.split('\\').skip(1)) + .map(|x| u8::from_str_radix(x, 8).expect("octal value out of range.")) + .collect() +} + +fn octal_block_to_hex_string(octal: &str) -> String { + octal_block_to_vec_u8(octal) + .iter() + .map(|x| format!("0x{:02X}, ", x)) + .collect() +} + +// Wrapper around values parsed out of certdata.txt +enum Ck<'a> { + Class(&'a str), + Comment(&'a str), + DistrustAfter(Option<&'a str>), + Empty, + MultilineOctal(&'a str), + OptionBool(&'a str), + Trust(&'a str), + Utf8(&'a str), +} + +// Translation of parsed values into the output rust code +impl fmt::Display for Ck<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Ck::Class(s) => write!(f, "{s}_BYTES"), + Ck::Comment(s) => write!(f, "{}", s.replace('#', "//")), + Ck::DistrustAfter(None) => write!(f, "Some(CK_FALSE_BYTES)"), + Ck::DistrustAfter(Some(s)) => write!(f, "Some(&[{}])", octal_block_to_hex_string(s)), + Ck::Empty => write!(f, "None"), + Ck::MultilineOctal(s) => write!(f, "&[{}]", octal_block_to_hex_string(s)), + Ck::OptionBool(s) => write!(f, "Some({s}_BYTES)"), + Ck::Trust(s) => write!(f, "{s}_BYTES"), + Ck::Utf8(s) => write!(f, "\"{s}\\0\""), + } + } +} + +impl PartialEq for Ck<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Ck::Class(s), Ck::Class(t)) => s.eq(t), + (Ck::Comment(s), Ck::Comment(t)) => s.eq(t), + (Ck::DistrustAfter(None), Ck::DistrustAfter(None)) => true, + (Ck::DistrustAfter(Some(s)), Ck::DistrustAfter(Some(t))) => { + // compare the data rather than the presentation + let vec_s = octal_block_to_vec_u8(s); + let vec_t = octal_block_to_vec_u8(t); + vec_s.eq(&vec_t) + } + (Ck::Empty, Ck::Empty) => true, + (Ck::MultilineOctal(s), Ck::MultilineOctal(t)) => { + // compare the data rather than the presentation + let vec_s = octal_block_to_vec_u8(s); + let vec_t = octal_block_to_vec_u8(t); + vec_s.eq(&vec_t) + } + (Ck::Trust(s), Ck::Trust(t)) => s.eq(t), + (Ck::Utf8(s), Ck::Utf8(t)) => s.eq(t), + _ => false, + } + } +} + +fn class(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_OBJECT_CLASS")(i)?; + let (i, _) = space1(i)?; + let (i, class) = alt(( + tag("CKO_NSS_BUILTIN_ROOT_LIST"), + tag("CKO_CERTIFICATE"), + tag("CKO_NSS_TRUST"), + ))(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Class(class))) +} + +fn trust(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_TRUST")(i)?; + let (i, _) = space1(i)?; + let (i, trust) = alt(( + tag("CKT_NSS_TRUSTED_DELEGATOR"), + tag("CKT_NSS_MUST_VERIFY_TRUST"), + tag("CKT_NSS_NOT_TRUSTED"), + ))(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Trust(trust))) +} + +// Parses a CK_BBOOL and wraps it with Ck::OptionBool so that it gets printed as +// "Some(CK_TRUE_BYTES)" instead of "CK_TRUE_BYTES". +fn option_bbool(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_BBOOL")(i)?; + let (i, _) = space1(i)?; + let (i, b) = alt((tag("CK_TRUE"), tag("CK_FALSE")))(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::OptionBool(b))) +} + +fn bbool_true(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_BBOOL")(i)?; + let (i, _) = space1(i)?; + let (i, _) = tag("CK_TRUE")(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Empty)) +} + +fn bbool_false(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_BBOOL")(i)?; + let (i, _) = space1(i)?; + let (i, _) = tag("CK_FALSE")(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Empty)) +} + +fn utf8(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("UTF8")(i)?; + let (i, _) = space1(i)?; + let (i, _) = char('"')(i)?; + let (i, utf8) = take_until("\"")(i)?; + let (i, _) = char('"')(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Utf8(utf8))) +} + +fn certificate_type(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("CK_CERTIFICATE_TYPE")(i)?; + let (i, _) = space1(i)?; + let (i, _) = tag("CKC_X_509")(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + Ok((i, Ck::Empty)) +} + +// A CKA_NSS_{EMAIL,SERVER}_DISTRUST_AFTER line in certdata.txt is encoded either as a CK_BBOOL +// with value CK_FALSE (when there is no distrust after date) or as a MULTILINE_OCTAL block. +fn distrust_after(i: &str) -> IResult<&str, Ck> { + let (i, value) = alt((multiline_octal, bbool_false))(i)?; + match value { + Ck::Empty => Ok((i, Ck::DistrustAfter(None))), + Ck::MultilineOctal(data) => Ok((i, Ck::DistrustAfter(Some(data)))), + _ => unreachable!(), + } +} + +fn octal_octet(i: &str) -> IResult<&str, &str> { + recognize(tuple(( + tag("\\"), + one_of("0123"), // 255 = \377 + one_of("01234567"), + one_of("01234567"), + )))(i) +} + +fn multiline_octal(i: &str) -> IResult<&str, Ck> { + let (i, _) = tag("MULTILINE_OCTAL")(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + let (i, lines) = recognize(many1(terminated(many1(octal_octet), newline)))(i)?; + let (i, _) = tag("END")(i)?; + let (i, _) = space0(i)?; + let (i, _) = newline(i)?; + return Ok((i, Ck::MultilineOctal(lines))); +} + +fn distrust_comment(i: &str) -> IResult<&str, (&str, Ck)> { + let (i, comment) = recognize(delimited( + alt(( + tag("# For Email Distrust After: "), + tag("# For Server Distrust After: "), + )), + not_line_ending, + newline, + ))(i)?; + Ok((i, ("DISTRUST_COMMENT", Ck::Comment(comment)))) +} + +fn comment(i: &str) -> IResult<&str, (&str, Ck)> { + let (i, comment) = recognize(many1(delimited(char('#'), not_line_ending, newline)))(i)?; + Ok((i, ("COMMENT", Ck::Comment(comment)))) +} + +fn certdata_line(i: &str) -> IResult<&str, (&str, Ck)> { + let (i, (attr, value)) = alt(( + distrust_comment, // must be listed before `comment` + comment, + separated_pair(tag("CKA_CLASS"), space1, class), + separated_pair(tag("CKA_CERTIFICATE_TYPE"), space1, certificate_type), + separated_pair(alt((tag("CKA_ID"), tag("CKA_LABEL"))), space1, utf8), + separated_pair( + alt(( + tag("CKA_ISSUER"), + tag("CKA_CERT_SHA1_HASH"), + tag("CKA_CERT_MD5_HASH"), + tag("CKA_SERIAL_NUMBER"), + tag("CKA_SUBJECT"), + tag("CKA_VALUE"), + )), + space1, + multiline_octal, + ), + separated_pair( + alt(( + tag("CKA_NSS_SERVER_DISTRUST_AFTER"), + tag("CKA_NSS_EMAIL_DISTRUST_AFTER"), + )), + space1, + distrust_after, + ), + separated_pair( + alt(( + tag("CKA_TRUST_EMAIL_PROTECTION"), + tag("CKA_TRUST_CODE_SIGNING"), + tag("CKA_TRUST_SERVER_AUTH"), + )), + space1, + trust, + ), + separated_pair(tag("CKA_NSS_MOZILLA_CA_POLICY"), space1, option_bbool), + separated_pair(tag("CKA_TOKEN"), space1, bbool_true), + separated_pair( + alt(( + tag("CKA_TRUST_STEP_UP_APPROVED"), + tag("CKA_PRIVATE"), + tag("CKA_MODIFIABLE"), + )), + space1, + bbool_false, + ), + ))(i)?; + Ok((i, (attr, value))) +} + +type Block<'a> = HashMap<&'a str, Ck<'a>>; + +fn attr<'a>(block: &'a Block, attr: &str) -> &'a Ck<'a> { + block.get(attr).unwrap_or(&Ck::Empty) +} + +fn parse(i: &str) -> IResult<&str, Vec<Block>> { + let mut out: Vec<Block> = vec![]; + let (i, _) = take_until("BEGINDATA\n")(i)?; + let (i, _) = tag("BEGINDATA\n")(i)?; + let (i, mut raw_blocks) = separated_list0(many1(char('\n')), many1(certdata_line))(i)?; + let (i, _) = multispace0(i)?; // allow trailing whitespace + if !i.is_empty() { + // The first line of i contains an error. + let (line, _) = i.split_once('\n').unwrap_or((i, "")); + fail::<_, &str, _>(line)?; + } + for raw_block in raw_blocks.drain(..) { + out.push(raw_block.into_iter().collect()) + } + Ok((i, out)) +} + +#[derive(Debug)] +struct PKCS11TypesParseCallbacks; + +impl ParseCallbacks for PKCS11TypesParseCallbacks { + fn int_macro(&self, _name: &str, _value: i64) -> Option<IntKind> { + Some(IntKind::U8) + } +} + +// If we encounter a problem parsing certdata.txt we'll try to turn it into a compile time +// error in builtins.rs. We need to output definitions for ROOT_LIST_LABEL and BUILTINS to +// cut down on the number of errors the compiler produces. +macro_rules! emit_build_error { + ($out:ident, $err:expr) => { + writeln!($out, "std::compile_error!(\"{}\");", $err)?; + writeln!($out, "pub static ROOT_LIST_LABEL: [u8; 0] = [];")?; + writeln!($out, "pub static BUILTINS: [Root; 0] = [];")?; + }; +} + +fn main() -> std::io::Result<()> { + println!("cargo:rerun-if-changed=../../../nss/lib/ckfw/builtins/certdata.txt"); + println!("cargo:rerun-if-changed=../../../nss/lib/ckfw/builtins/nssckbi.h"); + + let bindings = Builder::default() + .header("../../../nss/lib/ckfw/builtins/nssckbi.h") + .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MAJOR") + .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MINOR") + .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MAJOR") + .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MINOR") + .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MAJOR") + .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MINOR") + .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MAJOR") + .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MINOR") + .parse_callbacks(Box::new(PKCS11TypesParseCallbacks)) + .generate() + .expect("Unable to generate bindings."); + + let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR should be set in env.")); + bindings + .write_to_file(out_path.join("version.rs")) + .expect("Could not write version.rs."); + + let mut out = BufWriter::new( + File::create(out_path.join("builtins.rs")).expect("Could not write builtins.rs."), + ); + + let input: &str = &std::fs::read_to_string("../../../nss/lib/ckfw/builtins/certdata.txt") + .expect("Unable to read certdata.txt."); + + let blocks = match parse(input) { + Ok((_, blocks)) => blocks, + Err(e) => { + let input = match e { + nom::Err::Error(nom::error::Error { input, .. }) => input, + _ => "Unknown", + }; + emit_build_error!( + out, + &format!( + "Could not parse certdata.txt. Failed at: \'{}\'\");", + input.escape_debug().to_string().escape_debug() + ) + ); + return Ok(()); + } + }; + + let root_lists: Vec<&Block> = blocks + .iter() + .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_BUILTIN_ROOT_LIST")) + .collect(); + + if root_lists.len() != 1 { + emit_build_error!( + out, + "certdata.txt does not define a CKO_NSS_BUILTIN_ROOT_LIST object." + ); + return Ok(()); + } + + let mut certs: Vec<&Block> = blocks + .iter() + .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_CERTIFICATE")) + .collect(); + + let trusts: Vec<&Block> = blocks + .iter() + .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_TRUST")) + .collect(); + + if certs.len() != trusts.len() { + emit_build_error!( + out, + "certdata.txt has a mismatched number of certificate and trust objects" + ); + return Ok(()); + } + + // Ensure that every certificate has a CKA_SUBJECT attribute for the sort + for (i, cert) in certs.iter().enumerate() { + match cert.get("CKA_SUBJECT") { + Some(Ck::MultilineOctal(_)) => (), + _ => { + emit_build_error!( + out, + format!("Certificate {i} in certdata.txt has no CKA_SUBJECT attribute.") + ); + return Ok(()); + } + } + } + + certs.sort_by_cached_key(|x| match x.get("CKA_SUBJECT") { + Some(Ck::MultilineOctal(data)) => octal_block_to_vec_u8(data), + _ => unreachable!(), + }); + + // Write out arrays for the DER encoded certificate, serial number, and subject of each root. + // Since the serial number and the subject are in the DER cert, we don't need to store + // additional data for them. + for (i, cert) in certs.iter().enumerate() { + // Preserve the comment from certdata.txt + match attr(cert, "COMMENT") { + Ck::Empty => (), + comment => write!(out, "{comment}")?, + }; + + let der = attr(cert, "CKA_VALUE"); + writeln!(out, "static ROOT_{i}: &[u8] = {der};")?; + + // Search for the serial number and subject in the DER cert. We want to search on the raw + // bytes, not the octal presentation, so we have to unpack the enums. + let der_data = match der { + Ck::MultilineOctal(x) => octal_block_to_vec_u8(x), + _ => unreachable!(), + }; + let serial_data = match attr(cert, "CKA_SERIAL_NUMBER") { + Ck::MultilineOctal(x) => octal_block_to_vec_u8(x), + _ => unreachable!(), + }; + let subject_data = match attr(cert, "CKA_SUBJECT") { + Ck::MultilineOctal(x) => octal_block_to_vec_u8(x), + _ => unreachable!(), + }; + + fn need_u16(out: &mut impl Write, attr: &str, what: &str, i: usize) -> std::io::Result<()> { + emit_build_error!( + out, + format!("Certificate {i} in certdata.txt has a {attr} whose {what} doesn't fit in a u8. Time to upgrade to u16 at the expense of size?") + ); + Ok(()) + } + + let serial_len = serial_data.len(); + if let Some(serial_offset) = &der_data.windows(serial_len).position(|s| s == serial_data) { + if *serial_offset > u8::MAX.into() { + return need_u16(&mut out, "CKA_SERIAL_NUMBER", "offset", i); + } + if serial_len > u8::MAX.into() { + return need_u16(&mut out, "CKA_SERIAL_NUMBER", "length", i); + } + writeln!( + out, + "const SERIAL_{i}: (u8, u8) = ({serial_offset}, {serial_len});" + )?; + } else { + emit_build_error!( + out, + format!("Certificate {i} in certdata.txt has a CKA_SERIAL_NUMBER that does not match its CKA_VALUE.") + ); + return Ok(()); + } + + let subject_len = subject_data.len(); + if let Some(subject_offset) = &der_data + .windows(subject_len) + .position(|s| s == subject_data) + { + if *subject_offset > u8::MAX.into() { + return need_u16(&mut out, "CKA_SUBJECT", "offset", i); + } + if subject_len > u8::MAX.into() { + return need_u16(&mut out, "CKA_SUBJECT", "length", i); + } + writeln!( + out, + "const SUBJECT_{i}: (u8, u8) = ({subject_offset}, {subject_len});" + )?; + } else { + emit_build_error!( + out, + format!("Certificate {i} in certdata.txt has a CKA_SUBJECT that does not match its CKA_VALUE.") + ); + return Ok(()); + } + } + + let root_list_label = attr(root_lists[0], "CKA_LABEL"); + let root_list_label_len = match root_list_label { + Ck::Utf8(x) => x.len() + 1, + _ => unreachable!(), + }; + writeln!( + out, + "pub const ROOT_LIST_LABEL: [u8; {root_list_label_len}] = *b{root_list_label};" + )?; + + writeln!(out, "pub static BUILTINS: [Root; {}] = [", certs.len())?; + for (i, cert) in certs.iter().enumerate() { + let subject = attr(cert, "CKA_SUBJECT"); + let issuer = attr(cert, "CKA_ISSUER"); + let label = attr(cert, "CKA_LABEL"); + if !subject.eq(issuer) { + writeln!(out, "];")?; // end the definition of BUILTINS + let label = format!("{}", label); + writeln!( + out, + "std::compile_error!(\"Certificate with label {} is not self-signed\");", + label.escape_debug() + )?; + return Ok(()); + } + let mozpol = attr(cert, "CKA_NSS_MOZILLA_CA_POLICY"); + let server_distrust = attr(cert, "CKA_NSS_SERVER_DISTRUST_AFTER"); + let email_distrust = attr(cert, "CKA_NSS_EMAIL_DISTRUST_AFTER"); + let matching_trusts: Vec<&&Block> = trusts + .iter() + .filter(|trust| { + (attr(cert, "CKA_ISSUER") == attr(trust, "CKA_ISSUER")) + && (attr(cert, "CKA_SERIAL_NUMBER") == attr(trust, "CKA_SERIAL_NUMBER")) + }) + .collect(); + if matching_trusts.len() != 1 { + writeln!(out, "];")?; // end the definition of BUILTINS + let label = format!("{}", label); + writeln!(out, "std::compile_error!(\"Could not find unique trust object for {} in certdata.txt\");", label.escape_debug())?; + return Ok(()); + } + let trust = *matching_trusts[0]; + let sha1 = match attr(trust, "CKA_CERT_SHA1_HASH") { + Ck::MultilineOctal(x) => octal_block_to_hex_string(x), + _ => unreachable!(), + }; + let md5 = match attr(trust, "CKA_CERT_MD5_HASH") { + Ck::MultilineOctal(x) => octal_block_to_hex_string(x), + _ => unreachable!(), + }; + let server = attr(trust, "CKA_TRUST_SERVER_AUTH"); + let email = attr(trust, "CKA_TRUST_EMAIL_PROTECTION"); + + writeln!( + out, + " Root {{ + label: {label}, + der_name: SUBJECT_{i}, + der_serial: SERIAL_{i}, + der_cert: ROOT_{i}, + mozilla_ca_policy: {mozpol}, + server_distrust_after: {server_distrust}, + email_distrust_after: {email_distrust}, + sha1: [{sha1}], + md5: [{md5}], + trust_server: {server}, + trust_email: {email}, + }}," + )?; + } + writeln!(out, "];")?; + + let _ = out.flush(); + Ok(()) +} diff --git a/security/manager/ssl/builtins/dynamic-library/builtins.symbols b/security/manager/ssl/builtins/dynamic-library/builtins.symbols new file mode 100644 index 0000000000..562ecea21d --- /dev/null +++ b/security/manager/ssl/builtins/dynamic-library/builtins.symbols @@ -0,0 +1 @@ +C_GetFunctionList diff --git a/security/manager/ssl/builtins/dynamic-library/moz.build b/security/manager/ssl/builtins/dynamic-library/moz.build new file mode 100644 index 0000000000..2413a4c441 --- /dev/null +++ b/security/manager/ssl/builtins/dynamic-library/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +USE_LIBS += ["builtins-static"] + +# see notes in ipcclientcerts/dynamic-library/moz.build + +if CONFIG["OS_ARCH"] == "Linux" and CONFIG["OS_TARGET"] != "Android": + SOURCES += [ + "stub.cpp", + ] +else: + SOURCES += [ + "stub.c", + ] + +if CONFIG["OS_TARGET"] == "Android": + OS_LIBS += ["m"] + +if CONFIG["OS_ARCH"] == "WINNT": + OS_LIBS += [ + "advapi32", + "userenv", + "ws2_32", + ] + OS_LIBS += [ + "bcrypt", + "ntdll", + ] + + +SharedLibrary("nssckbi") + +NoVisibilityFlags() +SYMBOLS_FILE = "builtins.symbols" diff --git a/security/manager/ssl/builtins/dynamic-library/stub.c b/security/manager/ssl/builtins/dynamic-library/stub.c new file mode 100644 index 0000000000..75139c48bf --- /dev/null +++ b/security/manager/ssl/builtins/dynamic-library/stub.c @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "pkcs11.h" + +// see notes in ipcclientcerts/dynamic-library/stub.c + +CK_RV BUILTINSC_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList); + +CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) { + return BUILTINSC_GetFunctionList(ppFunctionList); +} + +#ifdef __MINGW32__ +# include "mozilla/Assertions.h" +void _Unwind_Resume() { MOZ_CRASH("Unexpected call to _Unwind_*"); } +void _Unwind_GetDataRelBase() { _Unwind_Resume(); } +void _Unwind_GetTextRelBase() { _Unwind_Resume(); } +void _Unwind_GetLanguageSpecificData() { _Unwind_Resume(); } +void _Unwind_GetIPInfo() { _Unwind_Resume(); } +void _Unwind_GetRegionStart() { _Unwind_Resume(); } +void _Unwind_SetGR() { _Unwind_Resume(); } +void _Unwind_SetIP() { _Unwind_Resume(); } +void _GCC_specific_handler() { _Unwind_Resume(); } +#endif diff --git a/security/manager/ssl/builtins/dynamic-library/stub.cpp b/security/manager/ssl/builtins/dynamic-library/stub.cpp new file mode 100644 index 0000000000..414f5a68c1 --- /dev/null +++ b/security/manager/ssl/builtins/dynamic-library/stub.cpp @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "pkcs11.h" + +// see notes in ipcclientcerts/dynamic-library/stub.cpp + +extern "C" { + +CK_RV BUILTINSC_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList); + +CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) { + return BUILTINSC_GetFunctionList(ppFunctionList); +} +} diff --git a/security/manager/ssl/builtins/moz.build b/security/manager/ssl/builtins/moz.build new file mode 100644 index 0000000000..f612798d79 --- /dev/null +++ b/security/manager/ssl/builtins/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["dynamic-library"] + +RustLibrary("builtins-static") diff --git a/security/manager/ssl/builtins/src/certdata.rs b/security/manager/ssl/builtins/src/certdata.rs new file mode 100644 index 0000000000..211abce475 --- /dev/null +++ b/security/manager/ssl/builtins/src/certdata.rs @@ -0,0 +1,52 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use pkcs11_bindings::nss::*; +use pkcs11_bindings::*; + +// We need to expand some PKCS#11 / NSS constants as byte arrays for pattern matching and +// C_GetAttributeValue queries. We use native endianness, because PKCS#11 sits between an +// application and a device driver that are running on the same machine. +pub const CKC_X_509_BYTES: &[u8] = &CKC_X_509.to_ne_bytes(); +pub const CKO_CERTIFICATE_BYTES: &[u8] = &CKO_CERTIFICATE.to_ne_bytes(); +pub const CKO_NSS_BUILTIN_ROOT_LIST_BYTES: &[u8] = &CKO_NSS_BUILTIN_ROOT_LIST.to_ne_bytes(); +pub const CKO_NSS_TRUST_BYTES: &[u8] = &CKO_NSS_TRUST.to_ne_bytes(); +pub const CKT_NSS_MUST_VERIFY_TRUST_BYTES: &[u8] = &CKT_NSS_MUST_VERIFY_TRUST.to_ne_bytes(); +pub const CKT_NSS_NOT_TRUSTED_BYTES: &[u8] = &CKT_NSS_NOT_TRUSTED.to_ne_bytes(); +pub const CKT_NSS_TRUSTED_DELEGATOR_BYTES: &[u8] = &CKT_NSS_TRUSTED_DELEGATOR.to_ne_bytes(); +pub const CK_FALSE_BYTES: &[u8] = &CK_FALSE.to_ne_bytes(); +pub const CK_TRUE_BYTES: &[u8] = &CK_TRUE.to_ne_bytes(); + +#[derive(PartialEq, Eq)] +pub struct Root { + pub label: &'static str, + pub der_name: (u8, u8), + pub der_serial: (u8, u8), + pub der_cert: &'static [u8], + pub mozilla_ca_policy: Option<&'static [u8]>, + pub server_distrust_after: Option<&'static [u8]>, + pub email_distrust_after: Option<&'static [u8]>, + pub sha1: [u8; 20], + pub md5: [u8; 16], + pub trust_server: &'static [u8], + pub trust_email: &'static [u8], +} + +impl Root { + pub fn der_name(&self) -> &'static [u8] { + &self.der_cert[self.der_name.0 as usize..][..self.der_name.1 as usize] + } + pub fn der_serial(&self) -> &'static [u8] { + &self.der_cert[self.der_serial.0 as usize..][..self.der_serial.1 as usize] + } +} + +impl PartialOrd for Root { + fn partial_cmp(&self, other: &Root) -> Option<std::cmp::Ordering> { + self.der_name().partial_cmp(other.der_name()) + } +} + +include!(concat!(env!("OUT_DIR"), "/builtins.rs")); diff --git a/security/manager/ssl/builtins/src/internal.rs b/security/manager/ssl/builtins/src/internal.rs new file mode 100644 index 0000000000..30bd1fcea7 --- /dev/null +++ b/security/manager/ssl/builtins/src/internal.rs @@ -0,0 +1,344 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use pkcs11_bindings::nss::*; +use pkcs11_bindings::*; + +use smallvec::SmallVec; + +use crate::certdata::*; + +// The token stores 2N+1 objects: one NSS root list object, N certificate objects, and N trust +// objects. +// +// Internally, the token identifies each object by its ObjectClass (RootList, Certificate, +// or Trust) and its index in the list of objects of the same class. +// +// The PKCS#11 interface, on the other hand, identifies each object with a unique, non-zero, +// unsigned long. This ulong is referred to as the object's CK_OBJECT_HANDLE. +// +// We're free to choose the mapping between ObjectHandles and CK_OBJECT_HANDLEs. Currently we +// encode the ObjectClass in the low 2 bits of the CK_OBJECT_HANDLE and the index in the higher +// bits. We use the values 1, 2, and 3 for ObjectClass to avoid using 0 as a CK_OBJECT_HANDLE. +// +#[derive(Clone, Copy)] +pub enum ObjectClass { + RootList = 1, + Certificate = 2, + Trust = 3, +} + +#[derive(Clone, Copy)] +pub struct ObjectHandle { + class: ObjectClass, + index: usize, +} + +impl TryFrom<CK_OBJECT_HANDLE> for ObjectHandle { + type Error = (); + fn try_from(handle: CK_OBJECT_HANDLE) -> Result<Self, Self::Error> { + if let Ok(handle) = usize::try_from(handle) { + let index = handle >> 2; + let class = match handle & 3 { + 1 if index == 0 => ObjectClass::RootList, + 2 if index < BUILTINS.len() => ObjectClass::Certificate, + 3 if index < BUILTINS.len() => ObjectClass::Trust, + _ => return Err(()), + }; + Ok(ObjectHandle { class, index }) + } else { + Err(()) + } + } +} + +impl From<ObjectHandle> for CK_OBJECT_HANDLE { + fn from(object_handle: ObjectHandle) -> CK_OBJECT_HANDLE { + match CK_OBJECT_HANDLE::try_from(object_handle.index) { + Ok(index) => (index << 2) | (object_handle.class as CK_OBJECT_HANDLE), + Err(_) => 0, + } + } +} + +pub fn get_attribute(attribute: CK_ATTRIBUTE_TYPE, object: &ObjectHandle) -> Option<&'static [u8]> { + match object.class { + ObjectClass::RootList => get_root_list_attribute(attribute), + ObjectClass::Certificate => get_cert_attribute(attribute, &BUILTINS[object.index]), + ObjectClass::Trust => get_trust_attribute(attribute, &BUILTINS[object.index]), + } +} + +// Every attribute that appears in certdata.txt must have a corresponding match arm in one of the +// get_*_attribute functions. +// +fn get_root_list_attribute(attribute: CK_ATTRIBUTE_TYPE) -> Option<&'static [u8]> { + match attribute { + CKA_CLASS => Some(CKO_NSS_BUILTIN_ROOT_LIST_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(&ROOT_LIST_LABEL[..]), + _ => None, + } +} + +fn get_cert_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&[u8]> { + match attribute { + CKA_CLASS => Some(CKO_CERTIFICATE_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(cert.label.as_bytes()), + CKA_CERTIFICATE_TYPE => Some(CKC_X_509_BYTES), + CKA_SUBJECT => Some(cert.der_name()), + CKA_ID => Some(b"0\0"), // null terminated to match C implementation + CKA_ISSUER => Some(cert.der_name()), + CKA_SERIAL_NUMBER => Some(cert.der_serial()), + CKA_VALUE => Some(cert.der_cert), + CKA_NSS_MOZILLA_CA_POLICY => cert.mozilla_ca_policy, + CKA_NSS_SERVER_DISTRUST_AFTER => cert.server_distrust_after, + CKA_NSS_EMAIL_DISTRUST_AFTER => cert.email_distrust_after, + _ => None, + } +} + +fn get_trust_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&[u8]> { + match attribute { + CKA_CLASS => Some(CKO_NSS_TRUST_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(cert.label.as_bytes()), + CKA_CERT_SHA1_HASH => Some(&cert.sha1[..]), + CKA_CERT_MD5_HASH => Some(&cert.md5[..]), + CKA_ISSUER => Some(cert.der_name()), + CKA_SERIAL_NUMBER => Some(cert.der_serial()), + CKA_TRUST_STEP_UP_APPROVED => Some(CK_FALSE_BYTES), + CKA_TRUST_SERVER_AUTH => Some(cert.trust_server), + CKA_TRUST_EMAIL_PROTECTION => Some(cert.trust_email), + CKA_TRUST_CODE_SIGNING => Some(CKT_NSS_MUST_VERIFY_TRUST_BYTES), + _ => None, + } +} + +// A query matches an object if each term matches some attribute of the object. A search result is +// a list of object handles. Typical queries yield zero or one results, so we optimize for this +// case. +// +pub type Query<'a> = [(CK_ATTRIBUTE_TYPE, &'a [u8])]; +pub type SearchResult = SmallVec<[ObjectHandle; 1]>; + +pub fn search(query: &Query) -> SearchResult { + // The BUILTINS list is sorted by name. So if the query includes a CKA_SUBJECT or CKA_ISSUER + // field we can binary search. + for &(attr, value) in query { + if attr == CKA_SUBJECT || attr == CKA_ISSUER { + return search_by_name(value, query); + } + } + + let mut results: SearchResult = SearchResult::default(); + + // A query with no name term might match the root list object + if match_root_list(query) { + results.push(ObjectHandle { + class: ObjectClass::RootList, + index: 0, + }); + } + + // A query with a CKA_CLASS term matches exactly one type of object, and we should avoid + // iterating over BUILTINS when CKO_CLASS is neither CKO_CERTIFICATE_BYTES nor + // CKO_NSS_TRUST_BYTES. + let mut maybe_cert = true; + let mut maybe_trust = true; + for &(attr, value) in query { + if attr == CKA_CLASS { + maybe_cert = value.eq(CKO_CERTIFICATE_BYTES); + maybe_trust = value.eq(CKO_NSS_TRUST_BYTES); + break; + } + } + + if !(maybe_cert || maybe_trust) { + return results; // The root list or nothing. + } + + for (index, builtin) in BUILTINS.iter().enumerate() { + if maybe_cert && match_cert(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Certificate, + index, + }); + } + if maybe_trust && match_trust(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Trust, + index, + }); + } + } + results +} + +fn search_by_name(name: &[u8], query: &Query) -> SearchResult { + let mut results: SearchResult = SearchResult::default(); + + let index = match BUILTINS.binary_search_by_key(&name, |r| r.der_name()) { + Ok(index) => index, + _ => return results, + }; + + // binary search returned a matching index, but maybe not the smallest + let mut min = index; + while min > 0 && name.eq(BUILTINS[min - 1].der_name()) { + min -= 1; + } + + // ... and maybe not the largest. + let mut max = index; + while max < BUILTINS.len() - 1 && name.eq(BUILTINS[max + 1].der_name()) { + max += 1; + } + + for (index, builtin) in BUILTINS.iter().enumerate().take(max + 1).skip(min) { + if match_cert(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Certificate, + index, + }); + } + if match_trust(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Trust, + index, + }); + } + } + + results +} + +fn match_root_list(query: &Query) -> bool { + for &(typ, x) in query { + match get_root_list_attribute(typ) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +fn match_cert(query: &Query, cert: &Root) -> bool { + for &(typ, x) in query { + match get_cert_attribute(typ, cert) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +fn match_trust(query: &Query, cert: &Root) -> bool { + for &(typ, x) in query { + match get_trust_attribute(typ, cert) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +#[cfg(test)] +mod internal_tests { + use crate::certdata::BUILTINS; + use crate::internal::*; + use pkcs11_bindings::*; + + // commented out to avoid vendoring x509_parser + // fn is_valid_utctime(utctime: &[u8]) -> bool { + // /* TODO: actual validation */ + // utctime.len() == 13 + // } + // #[test] + // fn test_certdata() { + // for root in BUILTINS { + // // the der_cert field is valid DER + // let parsed_cert = X509Certificate::from_der(root.der_cert); + // assert!(parsed_cert.is_ok()); + + // // the der_cert field has no trailing data + // let (trailing, parsed_cert) = parsed_cert.unwrap(); + // assert!(trailing.is_empty()); + + // // the der_serial field matches the encoded serial + // assert!(root.der_serial.len() > 2); + // assert!(root.der_serial[0] == 0x02); // der integer + // assert!(root.der_serial[1] <= 20); // no more than 20 bytes long + // assert!(root.der_serial[1] as usize == root.der_serial.len() - 2); + // assert!(parsed_cert.raw_serial().eq(&root.der_serial[2..])); + + // // the der_name field matches the encoded subject + // assert!(parsed_cert.subject.as_raw().eq(root.der_name)); + + // // the der_name field matches the encoded issuer + // assert!(parsed_cert.issuer.as_raw().eq(root.der_name)); + + // // The server_distrust_after field is None or a valid UTC time + // if let Some(utctime) = root.server_distrust_after { + // assert!(is_valid_utctime(&utctime)); + // } + + // // The email_distrust_after field is None or a valid UTC time + // if let Some(utctime) = root.email_distrust_after { + // assert!(is_valid_utctime(&utctime)); + // } + + // assert!( + // root.trust_server == CKT_NSS_MUST_VERIFY_TRUST_BYTES + // || root.trust_server == CKT_NSS_TRUSTED_DELEGATOR_BYTES + // || root.trust_server == CKT_NSS_NOT_TRUSTED_BYTES + // ); + // assert!( + // root.trust_email == CKT_NSS_MUST_VERIFY_TRUST_BYTES + // || root.trust_email == CKT_NSS_TRUSTED_DELEGATOR_BYTES + // || root.trust_email == CKT_NSS_NOT_TRUSTED_BYTES + // ); + // } + // } + + #[test] + fn test_builtins_sorted() { + for i in 0..(BUILTINS.len() - 1) { + assert!(BUILTINS[i].der_name.le(BUILTINS[i + 1].der_name)); + } + } + + #[test] + fn test_search() { + // search for an element that will not be found + let result = search(&[(CKA_TOKEN, &[CK_FALSE])]); + assert_eq!(result.len(), 0); + + // search for root list + let result = search(&[(CKA_CLASS, CKO_NSS_BUILTIN_ROOT_LIST_BYTES)]); + assert!(result.len() == 1); + + // search by name + let result = search(&[ + (CKA_CLASS, CKO_CERTIFICATE_BYTES), + (CKA_SUBJECT, BUILTINS[0].der_name), + ]); + assert!(result.len() >= 1); + + // search by issuer and serial + let result = search(&[ + (CKA_ISSUER, BUILTINS[0].der_name), + (CKA_SERIAL_NUMBER, BUILTINS[0].der_serial), + ]); + assert!(result.len() >= 1); + } +} diff --git a/security/manager/ssl/builtins/src/lib.rs b/security/manager/ssl/builtins/src/lib.rs new file mode 100644 index 0000000000..17b05b454d --- /dev/null +++ b/security/manager/ssl/builtins/src/lib.rs @@ -0,0 +1,9 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +mod certdata; +mod internal; +mod pkcs11; +mod version; diff --git a/security/manager/ssl/builtins/src/pkcs11.rs b/security/manager/ssl/builtins/src/pkcs11.rs new file mode 100644 index 0000000000..fb60aee187 --- /dev/null +++ b/security/manager/ssl/builtins/src/pkcs11.rs @@ -0,0 +1,1229 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +use pkcs11_bindings::*; +use std::slice; + +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Mutex, MutexGuard}; + +use crate::internal::{get_attribute, search}; +use crate::internal::{ObjectHandle, Query, SearchResult}; + +use crate::version::*; + +const BUILTINS_VERSION: CK_VERSION = CK_VERSION { + major: NSS_BUILTINS_LIBRARY_VERSION_MAJOR, + minor: NSS_BUILTINS_LIBRARY_VERSION_MINOR, +}; + +const FIRMWARE_VERSION: CK_VERSION = CK_VERSION { + major: NSS_BUILTINS_FIRMWARE_VERSION_MAJOR, + minor: NSS_BUILTINS_FIRMWARE_VERSION_MINOR, +}; + +const CRYPTOKI_VERSION: CK_VERSION = CK_VERSION { + major: NSS_BUILTINS_CRYPTOKI_VERSION_MAJOR, + minor: NSS_BUILTINS_CRYPTOKI_VERSION_MINOR, +}; +const HARDWARE_VERSION: CK_VERSION = CK_VERSION { + major: NSS_BUILTINS_HARDWARE_VERSION_MAJOR, + minor: NSS_BUILTINS_HARDWARE_VERSION_MINOR, +}; + +const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Foundation "; +const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"NSS Builtin Object Cryptoki Modu"; + +const SLOT_COUNT: CK_ULONG = 1; +const SLOT_ID_ROOTS: CK_SLOT_ID = 1; +const SLOT_DESCRIPTION_ROOTS_BYTES: &[u8; 64] = + b"NSS Builtin Objects "; + +const TOKEN_LABEL_ROOTS_BYTES: &[u8; 32] = b"Builtin Object Token "; +const TOKEN_MODEL_BYTES: &[u8; 16] = b"1 "; +const TOKEN_SERIAL_NUMBER_BYTES: &[u8; 16] = b"1 "; +const TOKEN_UTC_TIME: &[u8; 16] = b" "; + +#[derive(Debug)] +struct PK11Error(CK_RV); + +// The token assigns session handles using a counter. It would make sense to use a 64 bit counter, +// as there would then be no risk of exhausting the session handle space. However, +// CK_SESSION_HANDLE is defined as a C unsigned long, which is a u32 on some platforms. +// +// We start the counter at 1 since PKCS#11 reserves 0 to signal an invalid handle +// +type SessionHandle = u32; +static NEXT_HANDLE: AtomicU32 = AtomicU32::new(1); + +// The token needs to keep track of which sessions are open. +// +type SessionSet = BTreeSet<SessionHandle>; +static OPEN_SESSIONS: Mutex<Option<SessionSet>> = Mutex::new(None); + +// Helper functions for accessing OPEN_SESSIONS +// +type SessionSetGuard = MutexGuard<'static, Option<SessionSet>>; + +fn get_open_sessions_guard() -> Result<SessionSetGuard, PK11Error> { + OPEN_SESSIONS + .lock() + .map_err(|_| PK11Error(CKR_DEVICE_ERROR)) +} + +fn get_open_sessions(guard: &mut SessionSetGuard) -> Result<&mut SessionSet, PK11Error> { + let sessions = guard + .as_mut() + .ok_or(PK11Error(CKR_CRYPTOKI_NOT_INITIALIZED))?; + Ok(sessions) +} + +// The token needs to cache search results until the client reads them or closes the session. +// +type SearchCache = BTreeMap<SessionHandle, SearchResult>; +static SEARCHES: Mutex<Option<SearchCache>> = Mutex::new(None); + +// Helper functions for accessing SEARCHES +// +type SearchCacheGuard = MutexGuard<'static, Option<SearchCache>>; + +fn get_search_cache_guard() -> Result<SearchCacheGuard, PK11Error> { + SEARCHES.lock().map_err(|_| PK11Error(CKR_DEVICE_ERROR)) +} + +fn get_search_cache(guard: &mut SearchCacheGuard) -> Result<&mut SearchCache, PK11Error> { + let searches = guard + .as_mut() + .ok_or(PK11Error(CKR_CRYPTOKI_NOT_INITIALIZED))?; + Ok(searches) +} + +fn validate_session(handle: SessionHandle) -> Result<(), PK11Error> { + let mut guard = get_open_sessions_guard()?; + let sessions = get_open_sessions(&mut guard)?; + if sessions.contains(&handle) { + return Ok(()); + } + if handle < NEXT_HANDLE.load(Ordering::SeqCst) { + Err(PK11Error(CKR_SESSION_CLOSED)) + } else { + // Possible that NEXT_HANDLE wrapped and we should return CKR_SESSION_CLOSED. + // But this is best-effort. + Err(PK11Error(CKR_SESSION_HANDLE_INVALID)) + } +} + +// The internal implementation of C_Initialize +fn initialize() -> Result<(), PK11Error> { + { + let mut search_cache_guard = get_search_cache_guard()?; + if (*search_cache_guard).is_some() { + return Err(PK11Error(CKR_CRYPTOKI_ALREADY_INITIALIZED)); + } + *search_cache_guard = Some(SearchCache::default()); + } + + { + let mut session_guard = get_open_sessions_guard()?; + if (*session_guard).is_some() { + return Err(PK11Error(CKR_CRYPTOKI_ALREADY_INITIALIZED)); + } + *session_guard = Some(SessionSet::default()); + } + + Ok(()) +} + +// The internal implementation of C_Finalize +fn finalize() -> Result<(), PK11Error> { + { + let mut guard = get_search_cache_guard()?; + // Try to access the search cache to ensure we're initialized. + // Returns CKR_CRYPTOKI_NOT_INITIALIZED if we're not. + let _ = get_search_cache(&mut guard)?; + *guard = None; + } + + let mut guard = get_open_sessions_guard()?; + let _ = get_open_sessions(&mut guard)?; + *guard = None; + + Ok(()) +} + +// Internal implementation of C_OpenSession +fn open_session() -> Result<SessionHandle, PK11Error> { + let mut handle = NEXT_HANDLE.fetch_add(1, Ordering::SeqCst); + if handle == 0 { + // skip handle 0 if the addition wraps + handle = NEXT_HANDLE.fetch_add(1, Ordering::SeqCst); + } + + let mut guard = get_open_sessions_guard()?; + let sessions = get_open_sessions(&mut guard)?; + while !sessions.insert(handle) { + // this only executes if NEXT_HANDLE wraps while sessions with + // small handles are still open. + handle = NEXT_HANDLE.fetch_add(1, Ordering::SeqCst); + } + + Ok(handle) +} + +// Internal implementation of C_CloseSession +fn close_session(session: SessionHandle) -> Result<(), PK11Error> { + { + let mut guard = get_search_cache_guard()?; + let searches = get_search_cache(&mut guard)?; + searches.remove(&session); + } + + { + let mut guard = get_open_sessions_guard()?; + let sessions = get_open_sessions(&mut guard)?; + if sessions.remove(&session) { + Ok(()) + } else if session < NEXT_HANDLE.load(Ordering::SeqCst) { + Err(PK11Error(CKR_SESSION_CLOSED)) + } else { + Err(PK11Error(CKR_SESSION_HANDLE_INVALID)) + } + } +} + +// Internal implementation of C_CloseAllSessions +fn close_all_sessions() -> Result<(), PK11Error> { + { + let mut guard = get_search_cache_guard()?; + let searches = get_search_cache(&mut guard)?; + searches.clear(); + } + + { + let mut guard = get_open_sessions_guard()?; + let sessions = get_open_sessions(&mut guard)?; + sessions.clear(); + } + + Ok(()) +} + +// Internal implementation of C_FindObjectsInit +fn find_objects_init(session: SessionHandle, query: &Query) -> Result<usize, PK11Error> { + validate_session(session)?; + + let results = search(query); + let count = results.len(); + + let mut guard = get_search_cache_guard()?; + let searches = get_search_cache(&mut guard)?; + match searches.entry(session) { + Entry::Occupied(_) => Err(PK11Error(CKR_OPERATION_ACTIVE)), + Entry::Vacant(v) => { + v.insert(results); + Ok(count) + } + } +} + +// Internal implementation of C_FindObjects +fn find_objects(session: SessionHandle, out: &mut [CK_OBJECT_HANDLE]) -> Result<usize, PK11Error> { + validate_session(session)?; + + let mut guard = get_search_cache_guard()?; + let searches = get_search_cache(&mut guard)?; + if let Some(objects) = searches.get_mut(&session) { + for (i, out_i) in out.iter_mut().enumerate() { + match objects.pop() { + Some(object) => *out_i = object.into(), + None => return Ok(i), + } + } + Ok(out.len()) + } else { + Ok(0) + } +} + +// Internal implementation of C_FindObjectsFinal +fn find_objects_final(session: SessionHandle) -> Result<(), PK11Error> { + validate_session(session)?; + + let mut guard = get_search_cache_guard()?; + let searches = get_search_cache(&mut guard)?; + searches.remove(&session); + Ok(()) +} + +extern "C" fn C_Initialize(_pInitArgs: CK_VOID_PTR) -> CK_RV { + match initialize() { + Ok(_) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_Finalize(pReserved: CK_VOID_PTR) -> CK_RV { + if !pReserved.is_null() { + return CKR_ARGUMENTS_BAD; + } + match finalize() { + Ok(_) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV { + if pInfo.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *pInfo = CK_INFO { + cryptokiVersion: CRYPTOKI_VERSION, + manufacturerID: *MANUFACTURER_ID_BYTES, + flags: 0, + libraryDescription: *LIBRARY_DESCRIPTION_BYTES, + libraryVersion: BUILTINS_VERSION, + }; + } + CKR_OK +} + +extern "C" fn C_GetSlotList( + _tokenPresent: CK_BBOOL, + pSlotList: CK_SLOT_ID_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if pulCount.is_null() { + return CKR_ARGUMENTS_BAD; + } + if !pSlotList.is_null() { + if unsafe { *pulCount } < SLOT_COUNT { + return CKR_BUFFER_TOO_SMALL; + } + unsafe { + *pSlotList = SLOT_ID_ROOTS; + } + } + unsafe { + *pulCount = SLOT_COUNT; + } + CKR_OK +} + +extern "C" fn C_GetSlotInfo(slotID: CK_SLOT_ID, pInfo: CK_SLOT_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_ROOTS) || pInfo.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *pInfo = CK_SLOT_INFO { + slotDescription: *SLOT_DESCRIPTION_ROOTS_BYTES, + manufacturerID: *MANUFACTURER_ID_BYTES, + flags: CKF_TOKEN_PRESENT, + hardwareVersion: HARDWARE_VERSION, + firmwareVersion: FIRMWARE_VERSION, + }; + } + CKR_OK +} + +extern "C" fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_ROOTS) || pInfo.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *pInfo = CK_TOKEN_INFO { + label: *TOKEN_LABEL_ROOTS_BYTES, + manufacturerID: *MANUFACTURER_ID_BYTES, + model: *TOKEN_MODEL_BYTES, + serialNumber: *TOKEN_SERIAL_NUMBER_BYTES, + flags: CKF_WRITE_PROTECTED, + ulMaxSessionCount: CK_UNAVAILABLE_INFORMATION, + ulSessionCount: 0, + ulMaxRwSessionCount: CK_UNAVAILABLE_INFORMATION, + ulRwSessionCount: 0, + ulMaxPinLen: CK_UNAVAILABLE_INFORMATION, + ulMinPinLen: CK_UNAVAILABLE_INFORMATION, + ulTotalPublicMemory: CK_UNAVAILABLE_INFORMATION, + ulFreePublicMemory: CK_UNAVAILABLE_INFORMATION, + ulTotalPrivateMemory: CK_UNAVAILABLE_INFORMATION, + ulFreePrivateMemory: CK_UNAVAILABLE_INFORMATION, + hardwareVersion: HARDWARE_VERSION, + firmwareVersion: FIRMWARE_VERSION, + utcTime: *TOKEN_UTC_TIME, + }; + } + CKR_OK +} + +extern "C" fn C_GetMechanismList( + slotID: CK_SLOT_ID, + _pMechanismList: CK_MECHANISM_TYPE_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if slotID != SLOT_ID_ROOTS || pulCount.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *pulCount = 0; + } + CKR_OK +} + +extern "C" fn C_GetMechanismInfo( + _slotID: CK_SLOT_ID, + _type: CK_MECHANISM_TYPE, + _pInfo: CK_MECHANISM_INFO_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitToken( + _slotID: CK_SLOT_ID, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, + _pLabel: CK_UTF8CHAR_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitPIN( + _hSession: CK_SESSION_HANDLE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetPIN( + _hSession: CK_SESSION_HANDLE, + _pOldPin: CK_UTF8CHAR_PTR, + _ulOldLen: CK_ULONG, + _pNewPin: CK_UTF8CHAR_PTR, + _ulNewLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_OpenSession( + slotID: CK_SLOT_ID, + flags: CK_FLAGS, + _pApplication: CK_VOID_PTR, + _Notify: CK_NOTIFY, + phSession: CK_SESSION_HANDLE_PTR, +) -> CK_RV { + if slotID != SLOT_ID_ROOTS || phSession.is_null() { + return CKR_ARGUMENTS_BAD; + } + // [pkcs11-base-v3.0, Section 5.6.1] + // For legacy reasons, the CKF_SERIAL_SESSION bit MUST always be set; if a call to + // C_OpenSession does not have this bit set, the call should return unsuccessfully with the + // error code CKR_SESSION_PARALLEL_NOT_SUPPORTED. + if flags & CKF_SERIAL_SESSION == 0 { + return CKR_SESSION_PARALLEL_NOT_SUPPORTED; + } + let session_id = match open_session() { + Ok(session_id) => session_id as CK_SESSION_HANDLE, + Err(PK11Error(e)) => return e, + }; + unsafe { *phSession = session_id }; + CKR_OK +} + +extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV { + let session: SessionHandle = match hSession.try_into() { + Ok(session) => session, + Err(_) => return CKR_SESSION_HANDLE_INVALID, + }; + match close_session(session) { + Ok(_) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_CloseAllSessions(slotID: CK_SLOT_ID) -> CK_RV { + if slotID != SLOT_ID_ROOTS { + return CKR_ARGUMENTS_BAD; + } + match close_all_sessions() { + Ok(_) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_GetSessionInfo(_hSession: CK_SESSION_HANDLE, _pInfo: CK_SESSION_INFO_PTR) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _pulOperationStateLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _ulOperationStateLen: CK_ULONG, + _hEncryptionKey: CK_OBJECT_HANDLE, + _hAuthenticationKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Login( + _hSession: CK_SESSION_HANDLE, + _userType: CK_USER_TYPE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV { + CKR_OK +} + +extern "C" fn C_CreateObject( + _hSession: CK_SESSION_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CopyObject( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phNewObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DestroyObject(_hSession: CK_SESSION_HANDLE, _hObject: CK_OBJECT_HANDLE) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetObjectSize( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pulSize: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetAttributeValue( + _hSession: CK_SESSION_HANDLE, + hObject: CK_OBJECT_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + return CKR_ARGUMENTS_BAD; + } + + let count: usize = match ulCount.try_into() { + Ok(count) => count, + Err(_) => return CKR_ARGUMENTS_BAD, + }; + + // C_GetAttributeValue has a session handle parameter because PKCS#11 objects can have + // session-bound lifetimes and access controls. We don't have any session objects, and all of + // our token objects are public. So there's no good reason to validate the session handle. + // + //let session: SessionHandle = match hSession.try_into() { + // Ok(session) => session, + // Err(_) => return CKR_SESSION_HANDLE_INVALID, + //}; + // + //if let Err(PK11Error(e)) = validate_session(session) { + // return e; + //} + + let handle: ObjectHandle = match hObject.try_into() { + Ok(handle) => handle, + Err(_) => return CKR_OBJECT_HANDLE_INVALID, + }; + + let attrs: &mut [CK_ATTRIBUTE] = unsafe { slice::from_raw_parts_mut(pTemplate, count) }; + + let mut rv = CKR_OK; + + // Handle requests with null pValue fields + for attr in attrs.iter_mut().filter(|x| x.pValue.is_null()) { + attr.ulValueLen = match get_attribute(attr.type_, &handle) { + None => { + // [pkcs11-base-v3.0, Section 5.7.5] + // 2. [...] if the specified value for the object is invalid (the object does not possess + // such an attribute), then the ulValueLen field in that triple is modified to hold the + // value CK_UNAVAILABLE_INFORMATION. + rv = CKR_ATTRIBUTE_TYPE_INVALID; + CK_UNAVAILABLE_INFORMATION + } + Some(attr) => { + // [pkcs11-base-v3.0, Section 5.7.5] + // 3. [...] if the pValue field has the value NULL_PTR, then the ulValueLen field is modified + // to hold the exact length of the specified attribute for the object. + attr.len() as CK_ULONG + } + } + } + + // Handle requests with non-null pValue fields + for attr in attrs.iter_mut().filter(|x| !x.pValue.is_null()) { + let dst_len: usize = match attr.ulValueLen.try_into() { + Ok(dst_len) => dst_len, + Err(_) => return CKR_ARGUMENTS_BAD, + }; + attr.ulValueLen = match get_attribute(attr.type_, &handle) { + None => { + // [pkcs11-base-v3.0, Section 5.7.5] + // 2. [...] if the specified value for the object is invalid (the object does not possess + // such an attribute), then the ulValueLen field in that triple is modified to hold the + // value CK_UNAVAILABLE_INFORMATION. + rv = CKR_ATTRIBUTE_TYPE_INVALID; + CK_UNAVAILABLE_INFORMATION + } + Some(src) if dst_len >= src.len() => { + // [pkcs11-base-v3.0, Section 5.7.5] + // 4. [...] if the length specified in ulValueLen is large enough to hold the value + // of the specified attribute for the object, then that attribute is copied into + // the buffer located at pValue, and the ulValueLen field is modified to hold + // the exact length of the attribute. + let dst: &mut [u8] = + unsafe { slice::from_raw_parts_mut(attr.pValue as *mut u8, dst_len) }; + dst[..src.len()].copy_from_slice(src); + src.len() as CK_ULONG + } + _ => { + // [pkcs11-base-v3.0, Section 5.7.5] + // 5. Otherwise, the ulValueLen field is modified to hold the value + // CK_UNAVAILABLE_INFORMATION. + rv = CKR_BUFFER_TOO_SMALL; + CK_UNAVAILABLE_INFORMATION + } + }; + } + + // [pkcs11-base-v3.0, Section 5.7.5] + // If case 2 applies to any of the requested attributes, then the call should return the value + // CKR_ATTRIBUTE_TYPE_INVALID. If case 5 applies to any of the requested attributes, then the + // call should return the value CKR_BUFFER_TOO_SMALL. As usual, if more than one of these + // error codes is applicable, Cryptoki may return any of them. Only if none of them applies to + // any of the requested attributes will CKR_OK be returned. + rv +} + +extern "C" fn C_SetAttributeValue( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_FindObjectsInit( + hSession: CK_SESSION_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + return CKR_ARGUMENTS_BAD; + } + let count: usize = match ulCount.try_into() { + Ok(count) => count, + Err(_) => return CKR_ARGUMENTS_BAD, + }; + let session: SessionHandle = match hSession.try_into() { + Ok(session) => session, + Err(_) => return CKR_SESSION_HANDLE_INVALID, + }; + + let raw_attrs: &[CK_ATTRIBUTE] = unsafe { slice::from_raw_parts_mut(pTemplate, count) }; + + let mut query: Vec<(CK_ATTRIBUTE_TYPE, &[u8])> = Vec::with_capacity(raw_attrs.len()); + for attr in raw_attrs { + match usize::try_from(attr.ulValueLen) { + Ok(len) => query.push((attr.type_, unsafe { + slice::from_raw_parts_mut(attr.pValue as *mut u8, len) + })), + Err(_) => return CKR_ARGUMENTS_BAD, + } + } + + match find_objects_init(session, &query) { + Ok(_) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_FindObjects( + hSession: CK_SESSION_HANDLE, + phObject: CK_OBJECT_HANDLE_PTR, + ulMaxObjectCount: CK_ULONG, + pulObjectCount: CK_ULONG_PTR, +) -> CK_RV { + if phObject.is_null() || pulObjectCount.is_null() { + return CKR_ARGUMENTS_BAD; + } + let max_object_count: usize = match ulMaxObjectCount.try_into() { + Ok(max_object_count) => max_object_count, + Err(_) => return CKR_ARGUMENTS_BAD, + }; + let session: SessionHandle = match hSession.try_into() { + Ok(session) => session, + Err(_) => return CKR_SESSION_HANDLE_INVALID, + }; + let out: &mut [CK_OBJECT_HANDLE] = + unsafe { slice::from_raw_parts_mut(phObject, max_object_count) }; + match find_objects(session, out) { + Ok(num_found) => { + unsafe { *pulObjectCount = num_found as CK_ULONG }; + CKR_OK + } + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV { + let session: SessionHandle = match hSession.try_into() { + Ok(session) => session, + Err(_) => return CKR_SESSION_HANDLE_INVALID, + }; + match find_objects_final(session) { + Ok(()) => CKR_OK, + Err(PK11Error(e)) => e, + } +} + +extern "C" fn C_EncryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Encrypt( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pEncryptedData: CK_BYTE_PTR, + _pulEncryptedDataLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastEncryptedPart: CK_BYTE_PTR, + _pulLastEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Decrypt( + _hSession: CK_SESSION_HANDLE, + _pEncryptedData: CK_BYTE_PTR, + _ulEncryptedDataLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastPart: CK_BYTE_PTR, + _pulLastPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestInit(_hSession: CK_SESSION_HANDLE, _pMechanism: CK_MECHANISM_PTR) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Digest( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestKey(_hSession: CK_SESSION_HANDLE, _hKey: CK_OBJECT_HANDLE) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestFinal( + _hSession: CK_SESSION_HANDLE, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Sign( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecover( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Verify( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecover( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptDigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptVerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKeyPair( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pPublicKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPublicKeyAttributeCount: CK_ULONG, + _pPrivateKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPrivateKeyAttributeCount: CK_ULONG, + _phPublicKey: CK_OBJECT_HANDLE_PTR, + _phPrivateKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hWrappingKey: CK_OBJECT_HANDLE, + _hKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _pulWrappedKeyLen: CK_ULONG_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_UnwrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hUnwrappingKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _ulWrappedKeyLen: CK_ULONG, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DeriveKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hBaseKey: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SeedRandom( + _hSession: CK_SESSION_HANDLE, + _pSeed: CK_BYTE_PTR, + _ulSeedLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateRandom( + _hSession: CK_SESSION_HANDLE, + _RandomData: CK_BYTE_PTR, + _ulRandomLen: CK_ULONG, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetFunctionStatus(_hSession: CK_SESSION_HANDLE) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CancelFunction(_hSession: CK_SESSION_HANDLE) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WaitForSlotEvent( + _flags: CK_FLAGS, + _pSlot: CK_SLOT_ID_PTR, + _pRserved: CK_VOID_PTR, +) -> CK_RV { + CKR_FUNCTION_NOT_SUPPORTED +} + +pub static FUNCTION_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST { + version: CRYPTOKI_VERSION, + C_Initialize: Some(C_Initialize), + C_Finalize: Some(C_Finalize), + C_GetInfo: Some(C_GetInfo), + C_GetFunctionList: None, + C_GetSlotList: Some(C_GetSlotList), + C_GetSlotInfo: Some(C_GetSlotInfo), + C_GetTokenInfo: Some(C_GetTokenInfo), + C_GetMechanismList: Some(C_GetMechanismList), + C_GetMechanismInfo: Some(C_GetMechanismInfo), + C_InitToken: Some(C_InitToken), + C_InitPIN: Some(C_InitPIN), + C_SetPIN: Some(C_SetPIN), + C_OpenSession: Some(C_OpenSession), + C_CloseSession: Some(C_CloseSession), + C_CloseAllSessions: Some(C_CloseAllSessions), + C_GetSessionInfo: Some(C_GetSessionInfo), + C_GetOperationState: Some(C_GetOperationState), + C_SetOperationState: Some(C_SetOperationState), + C_Login: Some(C_Login), + C_Logout: Some(C_Logout), + C_CreateObject: Some(C_CreateObject), + C_CopyObject: Some(C_CopyObject), + C_DestroyObject: Some(C_DestroyObject), + C_GetObjectSize: Some(C_GetObjectSize), + C_GetAttributeValue: Some(C_GetAttributeValue), + C_SetAttributeValue: Some(C_SetAttributeValue), + C_FindObjectsInit: Some(C_FindObjectsInit), + C_FindObjects: Some(C_FindObjects), + C_FindObjectsFinal: Some(C_FindObjectsFinal), + C_EncryptInit: Some(C_EncryptInit), + C_Encrypt: Some(C_Encrypt), + C_EncryptUpdate: Some(C_EncryptUpdate), + C_EncryptFinal: Some(C_EncryptFinal), + C_DecryptInit: Some(C_DecryptInit), + C_Decrypt: Some(C_Decrypt), + C_DecryptUpdate: Some(C_DecryptUpdate), + C_DecryptFinal: Some(C_DecryptFinal), + C_DigestInit: Some(C_DigestInit), + C_Digest: Some(C_Digest), + C_DigestUpdate: Some(C_DigestUpdate), + C_DigestKey: Some(C_DigestKey), + C_DigestFinal: Some(C_DigestFinal), + C_SignInit: Some(C_SignInit), + C_Sign: Some(C_Sign), + C_SignUpdate: Some(C_SignUpdate), + C_SignFinal: Some(C_SignFinal), + C_SignRecoverInit: Some(C_SignRecoverInit), + C_SignRecover: Some(C_SignRecover), + C_VerifyInit: Some(C_VerifyInit), + C_Verify: Some(C_Verify), + C_VerifyUpdate: Some(C_VerifyUpdate), + C_VerifyFinal: Some(C_VerifyFinal), + C_VerifyRecoverInit: Some(C_VerifyRecoverInit), + C_VerifyRecover: Some(C_VerifyRecover), + C_DigestEncryptUpdate: Some(C_DigestEncryptUpdate), + C_DecryptDigestUpdate: Some(C_DecryptDigestUpdate), + C_SignEncryptUpdate: Some(C_SignEncryptUpdate), + C_DecryptVerifyUpdate: Some(C_DecryptVerifyUpdate), + C_GenerateKey: Some(C_GenerateKey), + C_GenerateKeyPair: Some(C_GenerateKeyPair), + C_WrapKey: Some(C_WrapKey), + C_UnwrapKey: Some(C_UnwrapKey), + C_DeriveKey: Some(C_DeriveKey), + C_SeedRandom: Some(C_SeedRandom), + C_GenerateRandom: Some(C_GenerateRandom), + C_GetFunctionStatus: Some(C_GetFunctionStatus), + C_CancelFunction: Some(C_CancelFunction), + C_WaitForSlotEvent: Some(C_WaitForSlotEvent), +}; + +#[no_mangle] +pub unsafe fn BUILTINSC_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV { + if ppFunctionList.is_null() { + return CKR_ARGUMENTS_BAD; + } + // CK_FUNCTION_LIST_PTR is a *mut CK_FUNCTION_LIST, but as per the + // specification, the caller must treat it as *const CK_FUNCTION_LIST. + *ppFunctionList = std::ptr::addr_of!(FUNCTION_LIST) as CK_FUNCTION_LIST_PTR; + CKR_OK +} + +#[cfg(test)] +mod pkcs11_tests { + use crate::certdata::*; + use crate::internal::*; + use crate::pkcs11::*; + + #[test] + fn test_main() { + // We need to run tests serially because of C_Initialize / C_Finalize calls. + test_simple(); + test_c_get_function_list(); + test_c_get_attribute(); + } + + fn test_simple() { + let query = &[(CKA_CLASS, CKO_CERTIFICATE_BYTES)]; + initialize().expect("initialize should not fail."); + let hSession = open_session().expect("open_session should not fail."); + let count = find_objects_init(hSession, query).expect("find_objects_init should not fail."); + assert_eq!(count, BUILTINS.len()); + let mut results: [CK_OBJECT_HANDLE; 10] = [0; 10]; + let n_read = + find_objects(hSession, &mut results).expect("find_objects_init should not fail."); + assert_eq!(n_read, 10); + finalize().expect("finalize should not fail."); + } + + fn test_c_get_function_list() { + let c_null = 0 as *mut std::ffi::c_void; + let mut pFunctionList: CK_FUNCTION_LIST_PTR = c_null as CK_FUNCTION_LIST_PTR; + let rv = unsafe { crate::pkcs11::BUILTINSC_GetFunctionList(&mut pFunctionList) }; + assert_eq!(CKR_OK, rv); + if let Some(pC_Initialize) = unsafe { (*pFunctionList).C_Initialize } { + let rv = unsafe { pC_Initialize(c_null) }; + assert_eq!(CKR_OK, rv); + } else { + assert!(false); + } + + if let Some(pC_Finalize) = unsafe { (*pFunctionList).C_Finalize } { + let rv = unsafe { pC_Finalize(c_null) }; + assert_eq!(CKR_OK, rv); + } else { + assert!(false); + } + } + + fn test_c_get_attribute() { + let c_null = 0 as *mut std::ffi::c_void; + let template: &mut [CK_ATTRIBUTE] = &mut [CK_ATTRIBUTE { + type_: CKA_SUBJECT, + pValue: c_null, + ulValueLen: 0, + }]; + let template_ptr = &mut template[0] as CK_ATTRIBUTE_PTR; + let object: CK_OBJECT_HANDLE = 2; + let mut session: CK_SESSION_HANDLE = 0; + assert_eq!(CKR_OK, C_Initialize(c_null)); + assert_eq!( + CKR_OK, + C_OpenSession( + SLOT_ID_ROOTS, + CKF_SERIAL_SESSION, + c_null, + None, + &mut session as *mut CK_SESSION_HANDLE + ) + ); + assert_eq!( + CKR_OK, + C_GetAttributeValue(session, object, template_ptr, 1) + ); + let len = template[0].ulValueLen as usize; + assert_eq!(len, BUILTINS[0].der_name.len()); + + let value: &mut [u8] = &mut vec![0; 1]; + let value_ptr: *mut u8 = &mut value[0] as *mut u8; + template[0].pValue = value_ptr as *mut std::ffi::c_void; + template[0].ulValueLen = 1; + assert_eq!( + CKR_BUFFER_TOO_SMALL, + C_GetAttributeValue(session, object, template_ptr, 1) + ); + assert_eq!(template[0].ulValueLen, CK_UNAVAILABLE_INFORMATION); + + let value: &mut [u8] = &mut vec![0; len]; + let value_ptr: *mut u8 = &mut value[0] as *mut u8; + template[0].pValue = value_ptr as *mut std::ffi::c_void; + template[0].ulValueLen = len as CK_ULONG; + assert_eq!( + CKR_OK, + C_GetAttributeValue(session, object, template_ptr, 1) + ); + assert_eq!(value, BUILTINS[0].der_name); + assert_eq!(CKR_OK, C_Finalize(c_null)); + } +} diff --git a/security/manager/ssl/builtins/src/version.rs b/security/manager/ssl/builtins/src/version.rs new file mode 100644 index 0000000000..f4fc63711d --- /dev/null +++ b/security/manager/ssl/builtins/src/version.rs @@ -0,0 +1,6 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include!(concat!(env!("OUT_DIR"), "/version.rs")); |