diff options
Diffstat (limited to 'security/manager/ssl/builtins/build.rs')
-rw-r--r-- | security/manager/ssl/builtins/build.rs | 568 |
1 files changed, 568 insertions, 0 deletions
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(()) +} |