summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/builtins
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/builtins')
-rw-r--r--security/manager/ssl/builtins/Cargo.toml17
-rw-r--r--security/manager/ssl/builtins/build.rs568
-rw-r--r--security/manager/ssl/builtins/dynamic-library/builtins.symbols1
-rw-r--r--security/manager/ssl/builtins/dynamic-library/moz.build38
-rw-r--r--security/manager/ssl/builtins/dynamic-library/stub.c27
-rw-r--r--security/manager/ssl/builtins/dynamic-library/stub.cpp17
-rw-r--r--security/manager/ssl/builtins/moz.build9
-rw-r--r--security/manager/ssl/builtins/src/certdata.rs52
-rw-r--r--security/manager/ssl/builtins/src/internal.rs344
-rw-r--r--security/manager/ssl/builtins/src/lib.rs9
-rw-r--r--security/manager/ssl/builtins/src/pkcs11.rs1227
-rw-r--r--security/manager/ssl/builtins/src/version.rs6
12 files changed, 2315 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..df54db8491
--- /dev/null
+++ b/security/manager/ssl/builtins/Cargo.toml
@@ -0,0 +1,17 @@
+[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"] }
+
+[build-dependencies]
+bindgen = { default-features = false, features = ["runtime"], version = "0.64" }
+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..56620f4a68
--- /dev/null
+++ b/security/manager/ssl/builtins/src/pkcs11.rs
@@ -0,0 +1,1227 @@
+/* -*- 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 mut 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;
+ }
+ *ppFunctionList = &mut FUNCTION_LIST;
+ 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"));