Bug 1789520 - rust implementation of nssckbi. r=keeler,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D156612
This commit is contained in:
John Schanck 2022-10-31 17:09:43 +00:00
parent d11dd7596d
commit 479f9ec25e
21 changed files with 2293 additions and 7 deletions

14
Cargo.lock generated
View File

@ -574,6 +574,16 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "builtins-static"
version = "0.1.0"
dependencies = [
"bindgen",
"nom 7.1.1",
"pkcs11-bindings",
"smallvec",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
@ -4094,9 +4104,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs11-bindings"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054f65db435bb7cf99db2f38bbcf568dfa66c342201df54e700c772b178e2f20"
checksum = "780bf71a814823f87d50b5bfbfd4ba5f2af46819a01f0ac31238f650693eb592"
dependencies = [
"bindgen",
]

View File

@ -8,12 +8,13 @@ members = [
"js/src/frontend/smoosh",
"js/src/rust",
"netwerk/test/http3server",
"security/manager/ssl/builtins",
"security/manager/ssl/ipcclientcerts",
"security/manager/ssl/osclientcerts",
"testing/geckodriver",
"toolkit/components/uniffi-bindgen-gecko-js",
"toolkit/crashreporter/rust_minidump_writer_linux",
"toolkit/crashreporter/mozwer-rust",
"toolkit/crashreporter/rust_minidump_writer_linux",
"toolkit/library/gtest/rust",
"toolkit/library/rust/",
"toolkit/mozapps/defaultagent/rust",

View File

@ -0,0 +1,16 @@
[package]
name = "builtins-static"
version = "0.1.0"
authors = ["John Schanck <jschanck@mozilla.com>"]
edition = "2021"
[dependencies]
pkcs11-bindings = "0.1.1"
smallvec = { version = "1.9.0", features = ["const_new"] }
[build-dependencies]
bindgen = { default-features = false, features = ["runtime"], version = "0.59" }
nom = "7.1.1"
[lib]
crate-type = ["staticlib"]

View File

@ -0,0 +1,495 @@
/* -*- 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] = b\"\";")?;
writeln!($out, "pub static BUILTINS: &[Root] = &[];")?;
};
}
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 {} in certdata.txt has no CKA_SUBJECT attribute.",
i
)
);
return Ok(());
}
}
}
certs.sort_by_cached_key(|x| match x.get("CKA_SUBJECT") {
Some(Ck::MultilineOctal(data)) => octal_block_to_vec_u8(data),
_ => unreachable!(),
});
let root_list_label = attr(root_lists[0], "CKA_LABEL");
writeln!(
out,
"pub const ROOT_LIST_LABEL: &[u8] = b{root_list_label};"
)?;
// Output all of the certificates, so we can take take sub-slices for
// components of the Root structs later.
for (i, cert) in certs.iter().enumerate() {
let comment = match attr(cert, "COMMENT") {
Ck::Empty => &Ck::Comment(""),
comment => comment,
};
let der = attr(cert, "CKA_VALUE");
writeln!(out, "{comment}static ROOT_{i}: &[u8] = {der};")?;
}
writeln!(out, "pub static BUILTINS: &[Root] = &[")?;
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 serial = attr(cert, "CKA_SERIAL_NUMBER");
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 = attr(trust, "CKA_CERT_SHA1_HASH");
let md5 = attr(trust, "CKA_CERT_MD5_HASH");
let server = attr(trust, "CKA_TRUST_SERVER_AUTH");
let email = attr(trust, "CKA_TRUST_EMAIL_PROTECTION");
// TODO(Bug 1794045): We could make the library smaller by encoding der_name and der_serial
// as subslices of ROOT_i. Should be possible in rust 1.64 using slice::from_raw_parts.
writeln!(
out,
" Root {{
label: {label},
der_name: {subject},
der_serial: {serial},
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(())
}

View File

@ -0,0 +1 @@
C_GetFunctionList

View File

@ -0,0 +1,37 @@
# -*- 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",
]
SharedLibrary("nssckbi")
NoVisibilityFlags()
SYMBOLS_FILE = "builtins.symbols"

View File

@ -0,0 +1,19 @@
/* -*- 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_Resume"); }
#endif

View File

@ -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);
}
}

View File

@ -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")

View File

@ -0,0 +1,43 @@
/* -*- 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: &'static [u8],
pub der_serial: &'static [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: &'static [u8],
pub md5: &'static [u8],
pub trust_server: &'static [u8],
pub trust_email: &'static [u8],
}
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"));

View File

@ -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<&'static [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<&'static [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);
}
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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"));

View File

@ -13,6 +13,7 @@ if (CONFIG["OS_ARCH"] == "WINNT" and CONFIG["CPU_ARCH"] != "aarch64") or CONFIG[
DIRS += ["osclientcerts"]
DIRS += ["ipcclientcerts"]
DIRS += ["builtins"]
TEST_DIRS += ["tests"]

View File

@ -86,6 +86,9 @@ gyp_vars["disable_tests"] = 1
gyp_vars["disable_dbm"] = 1
gyp_vars["disable_libpkix"] = 1
gyp_vars["enable_sslkeylogfile"] = 1
# Whether we're using system NSS or Rust nssckbi, we don't need
# to build C nssckbi
gyp_vars["disable_ckbi"] = 1
# pkg-config won't reliably find zlib on our builders, so just force it.
# System zlib is only used for modutil and signtool unless
# SSL zlib is enabled, which we are disabling immediately below this.

View File

@ -890,6 +890,11 @@ auto-generated by running bindgen on the PKCS#11 specification headers. Other
than the tests generated by bindgen, it consists of no runnable code.
"""
[[audits.pkcs11-bindings]]
who = "John M. Schanck <jmschanck@gmail.com>"
criteria = "safe-to-deploy"
version = "0.1.1"
[[audits.precomputed-hash]]
who = "Bobby Holley <bobbyholley@gmail.com>"
criteria = "safe-to-deploy"

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"89762b452690c9480c9ffdbccf7959e76ec2c65664840dc5dd5840b2a64459de","LICENSE":"c0813b208cee13e9e06931186dc83b9577dbaa02e6a9dfee170c4873f11878d9","build.rs":"d6b1fadb210fe7e614c8976192c0a72eaeb74a09fb99688d7b540c89c2c57745","pkcs11.h":"fb113f0bc62e0d0894dd1c499b10659e6c47a8ec00d467a186770e07f9c9e293","pkcs11f.h":"11ee1d5026b1590a6c4a12be1023a2aec9ee185ba31bfb470084e4b6254779ee","pkcs11t.h":"2a152db4d461e9a315428a3d037ff1ae896baa04af9d9409d696b3cbd4478c6c","src/lib.rs":"74bbfdf6d931e82c21e3c4335717d90ed60a46a8ec65e602fb7ac34f40c5c6ae","wrapper.h":"1b86ee3838c94bc5a5c9513ef6961f9b6b3a5f6dde7c6fbfdc0cf58cb0fbd318"},"package":"054f65db435bb7cf99db2f38bbcf568dfa66c342201df54e700c772b178e2f20"}
{"files":{"Cargo.toml":"3aa6a273775dc5e2045cab20190559e416371dbfe9fa89f50c2c44beb1c97d24","LICENSE":"c0813b208cee13e9e06931186dc83b9577dbaa02e6a9dfee170c4873f11878d9","build.rs":"803e766f05b2558a36873af2950e768f81f2aa954ce40d96ba7d83ac7c6dd953","pkcs11.h":"fb113f0bc62e0d0894dd1c499b10659e6c47a8ec00d467a186770e07f9c9e293","pkcs11f.h":"11ee1d5026b1590a6c4a12be1023a2aec9ee185ba31bfb470084e4b6254779ee","pkcs11t.h":"2a152db4d461e9a315428a3d037ff1ae896baa04af9d9409d696b3cbd4478c6c","src/lib.rs":"25f2ffe3aba98817b109b1bbfeccbde7a632aab353a032c6ccf7ea248b09af76","wrapper.h":"1b86ee3838c94bc5a5c9513ef6961f9b6b3a5f6dde7c6fbfdc0cf58cb0fbd318"},"package":"780bf71a814823f87d50b5bfbfd4ba5f2af46819a01f0ac31238f650693eb592"}

View File

@ -12,12 +12,16 @@
[package]
edition = "2018"
name = "pkcs11-bindings"
version = "0.1.0"
authors = ["Dana Keeler <dkeeler@mozilla.com>"]
version = "0.1.1"
authors = [
"Dana Keeler <dkeeler@mozilla.com>",
"John Schanck <jschanck@mozilla.com>",
]
build = "build.rs"
description = "Rust bindings for the PKCS#11 specification"
license = "MIT"
repository = "https://github.com/mozilla/pkcs11-bindings"
resolver = "1"
[build-dependencies.bindgen]
version = "0.59"

View File

@ -17,7 +17,7 @@ impl ParseCallbacks for PKCS11TypesParseCallbacks {
}
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
if name == "CK_TRUE" {
if name == "CK_TRUE" || name == "CK_FALSE" {
Some(IntKind::U8)
} else {
Some(IntKind::ULong)
@ -76,7 +76,10 @@ fn main() {
.allowlist_type("CK_KEY_TYPE")
.allowlist_type("CK_C_INITIALIZE_ARGS_PTR")
.allowlist_var("CK_TRUE")
.allowlist_var("CK_FALSE")
.allowlist_var("CK_UNAVAILABLE_INFORMATION")
.allowlist_var("CKA_.*")
.allowlist_var("CKC_.*")
.allowlist_var("CKF_.*")
.allowlist_var("CKK_.*")
.allowlist_var("CKM_.*")

View File

@ -3,3 +3,39 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/// Constants from NSS [pkcs11n.h](https://hg.mozilla.org/projects/nss/file/tip/lib/util/pkcs11n.h)
pub mod nss {
use crate::{CKA_VENDOR_DEFINED, CKO_VENDOR_DEFINED, CK_ULONG};
pub const NSSCK_VENDOR_NSS: CK_ULONG = 0x4E534350;
pub const CKT_VENDOR_DEFINED: CK_ULONG = 0x80000000;
pub const CKT_NSS: CK_ULONG = CKT_VENDOR_DEFINED | NSSCK_VENDOR_NSS;
pub const CKT_NSS_TRUSTED: CK_ULONG = CKT_NSS + 1;
pub const CKT_NSS_TRUSTED_DELEGATOR: CK_ULONG = CKT_NSS + 2;
pub const CKT_NSS_MUST_VERIFY_TRUST: CK_ULONG = CKT_NSS + 3;
pub const CKT_NSS_NOT_TRUSTED: CK_ULONG = CKT_NSS + 10;
pub const CKT_NSS_TRUST_UNKNOWN: CK_ULONG = CKT_NSS + 5;
pub const CKA_NSS: CK_ULONG = CKA_VENDOR_DEFINED | NSSCK_VENDOR_NSS;
pub const CKA_TRUST: CK_ULONG = CKA_NSS + 0x2000;
pub const CKA_TRUST_SERVER_AUTH: CK_ULONG = CKA_TRUST + 8;
pub const CKA_TRUST_CLIENT_AUTH: CK_ULONG = CKA_TRUST + 9;
pub const CKA_TRUST_CODE_SIGNING: CK_ULONG = CKA_TRUST + 10;
pub const CKA_TRUST_EMAIL_PROTECTION: CK_ULONG = CKA_TRUST + 11;
pub const CKA_TRUST_STEP_UP_APPROVED: CK_ULONG = CKA_TRUST + 16;
pub const CKA_CERT_SHA1_HASH: CK_ULONG = CKA_TRUST + 100;
pub const CKA_CERT_MD5_HASH: CK_ULONG = CKA_TRUST + 101;
pub const CKA_NSS_MOZILLA_CA_POLICY: CK_ULONG = CKA_NSS + 34;
pub const CKA_NSS_SERVER_DISTRUST_AFTER: CK_ULONG = CKA_NSS + 35;
pub const CKA_NSS_EMAIL_DISTRUST_AFTER: CK_ULONG = CKA_NSS + 36;
pub const CKO_NSS: CK_ULONG = CKO_VENDOR_DEFINED | NSSCK_VENDOR_NSS;
pub const CKO_NSS_TRUST: CK_ULONG = CKO_NSS + 3;
pub const CKO_NSS_BUILTIN_ROOT_LIST: CK_ULONG = CKO_NSS + 4;
}