1 /* -*- Mode: rust; rust-indent-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 use bindgen::callbacks::*;
12 use mozbuild::TOPSRCDIR;
15 use nom::bytes::complete::{tag, take_until};
16 use nom::character::complete::{
17 char, multispace0, newline, not_line_ending, one_of, space0, space1,
19 use nom::combinator::{fail, recognize};
20 use nom::multi::{many1, separated_list0};
21 use nom::sequence::{delimited, separated_pair, terminated, tuple};
24 use std::collections::HashMap;
28 use std::io::{BufWriter, Write};
29 use std::path::PathBuf;
31 fn octal_block_to_vec_u8(octal_block: &str) -> Vec<u8> {
34 .flat_map(|x| x.split('\\').skip(1))
35 .map(|x| u8::from_str_radix(x, 8).expect("octal value out of range."))
39 fn octal_block_to_hex_string(octal: &str) -> String {
40 octal_block_to_vec_u8(octal)
42 .map(|x| format!("0x{:02X}, ", x))
46 // Wrapper around values parsed out of certdata.txt
50 DistrustAfter(Option<&'a str>),
52 MultilineOctal(&'a str),
58 // Translation of parsed values into the output rust code
59 impl fmt::Display for Ck<'_> {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62 Ck::Class(s) => write!(f, "{s}_BYTES"),
63 Ck::Comment(s) => write!(f, "{}", s.replace('#', "//")),
64 Ck::DistrustAfter(None) => write!(f, "Some(CK_FALSE_BYTES)"),
65 Ck::DistrustAfter(Some(s)) => write!(f, "Some(&[{}])", octal_block_to_hex_string(s)),
66 Ck::Empty => write!(f, "None"),
67 Ck::MultilineOctal(s) => write!(f, "&[{}]", octal_block_to_hex_string(s)),
68 Ck::OptionBool(s) => write!(f, "Some({s}_BYTES)"),
69 Ck::Trust(s) => write!(f, "{s}_BYTES"),
70 Ck::Utf8(s) => write!(f, "\"{s}\\0\""),
75 impl PartialEq for Ck<'_> {
76 fn eq(&self, other: &Self) -> bool {
78 (Ck::Class(s), Ck::Class(t)) => s.eq(t),
79 (Ck::Comment(s), Ck::Comment(t)) => s.eq(t),
80 (Ck::DistrustAfter(None), Ck::DistrustAfter(None)) => true,
81 (Ck::DistrustAfter(Some(s)), Ck::DistrustAfter(Some(t))) => {
82 // compare the data rather than the presentation
83 let vec_s = octal_block_to_vec_u8(s);
84 let vec_t = octal_block_to_vec_u8(t);
87 (Ck::Empty, Ck::Empty) => true,
88 (Ck::MultilineOctal(s), Ck::MultilineOctal(t)) => {
89 // compare the data rather than the presentation
90 let vec_s = octal_block_to_vec_u8(s);
91 let vec_t = octal_block_to_vec_u8(t);
94 (Ck::Trust(s), Ck::Trust(t)) => s.eq(t),
95 (Ck::Utf8(s), Ck::Utf8(t)) => s.eq(t),
101 fn class(i: &str) -> IResult<&str, Ck> {
102 let (i, _) = tag("CK_OBJECT_CLASS")(i)?;
103 let (i, _) = space1(i)?;
104 let (i, class) = alt((
105 tag("CKO_NSS_BUILTIN_ROOT_LIST"),
106 tag("CKO_CERTIFICATE"),
107 tag("CKO_NSS_TRUST"),
109 let (i, _) = space0(i)?;
110 let (i, _) = newline(i)?;
111 Ok((i, Ck::Class(class)))
114 fn trust(i: &str) -> IResult<&str, Ck> {
115 let (i, _) = tag("CK_TRUST")(i)?;
116 let (i, _) = space1(i)?;
117 let (i, trust) = alt((
118 tag("CKT_NSS_TRUSTED_DELEGATOR"),
119 tag("CKT_NSS_MUST_VERIFY_TRUST"),
120 tag("CKT_NSS_NOT_TRUSTED"),
122 let (i, _) = space0(i)?;
123 let (i, _) = newline(i)?;
124 Ok((i, Ck::Trust(trust)))
127 // Parses a CK_BBOOL and wraps it with Ck::OptionBool so that it gets printed as
128 // "Some(CK_TRUE_BYTES)" instead of "CK_TRUE_BYTES".
129 fn option_bbool(i: &str) -> IResult<&str, Ck> {
130 let (i, _) = tag("CK_BBOOL")(i)?;
131 let (i, _) = space1(i)?;
132 let (i, b) = alt((tag("CK_TRUE"), tag("CK_FALSE")))(i)?;
133 let (i, _) = space0(i)?;
134 let (i, _) = newline(i)?;
135 Ok((i, Ck::OptionBool(b)))
138 fn bbool_true(i: &str) -> IResult<&str, Ck> {
139 let (i, _) = tag("CK_BBOOL")(i)?;
140 let (i, _) = space1(i)?;
141 let (i, _) = tag("CK_TRUE")(i)?;
142 let (i, _) = space0(i)?;
143 let (i, _) = newline(i)?;
147 fn bbool_false(i: &str) -> IResult<&str, Ck> {
148 let (i, _) = tag("CK_BBOOL")(i)?;
149 let (i, _) = space1(i)?;
150 let (i, _) = tag("CK_FALSE")(i)?;
151 let (i, _) = space0(i)?;
152 let (i, _) = newline(i)?;
156 fn utf8(i: &str) -> IResult<&str, Ck> {
157 let (i, _) = tag("UTF8")(i)?;
158 let (i, _) = space1(i)?;
159 let (i, _) = char('"')(i)?;
160 let (i, utf8) = take_until("\"")(i)?;
161 let (i, _) = char('"')(i)?;
162 let (i, _) = space0(i)?;
163 let (i, _) = newline(i)?;
164 Ok((i, Ck::Utf8(utf8)))
167 fn certificate_type(i: &str) -> IResult<&str, Ck> {
168 let (i, _) = tag("CK_CERTIFICATE_TYPE")(i)?;
169 let (i, _) = space1(i)?;
170 let (i, _) = tag("CKC_X_509")(i)?;
171 let (i, _) = space0(i)?;
172 let (i, _) = newline(i)?;
176 // A CKA_NSS_{EMAIL,SERVER}_DISTRUST_AFTER line in certdata.txt is encoded either as a CK_BBOOL
177 // with value CK_FALSE (when there is no distrust after date) or as a MULTILINE_OCTAL block.
178 fn distrust_after(i: &str) -> IResult<&str, Ck> {
179 let (i, value) = alt((multiline_octal, bbool_false))(i)?;
181 Ck::Empty => Ok((i, Ck::DistrustAfter(None))),
182 Ck::MultilineOctal(data) => Ok((i, Ck::DistrustAfter(Some(data)))),
187 fn octal_octet(i: &str) -> IResult<&str, &str> {
190 one_of("0123"), // 255 = \377
196 fn multiline_octal(i: &str) -> IResult<&str, Ck> {
197 let (i, _) = tag("MULTILINE_OCTAL")(i)?;
198 let (i, _) = space0(i)?;
199 let (i, _) = newline(i)?;
200 let (i, lines) = recognize(many1(terminated(many1(octal_octet), newline)))(i)?;
201 let (i, _) = tag("END")(i)?;
202 let (i, _) = space0(i)?;
203 let (i, _) = newline(i)?;
204 return Ok((i, Ck::MultilineOctal(lines)));
207 fn distrust_comment(i: &str) -> IResult<&str, (&str, Ck)> {
208 let (i, comment) = recognize(delimited(
210 tag("# For Email Distrust After: "),
211 tag("# For Server Distrust After: "),
216 Ok((i, ("DISTRUST_COMMENT", Ck::Comment(comment))))
219 fn comment(i: &str) -> IResult<&str, (&str, Ck)> {
220 let (i, comment) = recognize(many1(delimited(char('#'), not_line_ending, newline)))(i)?;
221 Ok((i, ("COMMENT", Ck::Comment(comment))))
224 fn certdata_line(i: &str) -> IResult<&str, (&str, Ck)> {
225 let (i, (attr, value)) = alt((
226 distrust_comment, // must be listed before `comment`
228 separated_pair(tag("CKA_CLASS"), space1, class),
229 separated_pair(tag("CKA_CERTIFICATE_TYPE"), space1, certificate_type),
230 separated_pair(alt((tag("CKA_ID"), tag("CKA_LABEL"))), space1, utf8),
234 tag("CKA_CERT_SHA1_HASH"),
235 tag("CKA_CERT_MD5_HASH"),
236 tag("CKA_SERIAL_NUMBER"),
245 tag("CKA_NSS_SERVER_DISTRUST_AFTER"),
246 tag("CKA_NSS_EMAIL_DISTRUST_AFTER"),
253 tag("CKA_TRUST_EMAIL_PROTECTION"),
254 tag("CKA_TRUST_CODE_SIGNING"),
255 tag("CKA_TRUST_SERVER_AUTH"),
260 separated_pair(tag("CKA_NSS_MOZILLA_CA_POLICY"), space1, option_bbool),
261 separated_pair(tag("CKA_TOKEN"), space1, bbool_true),
264 tag("CKA_TRUST_STEP_UP_APPROVED"),
266 tag("CKA_MODIFIABLE"),
272 Ok((i, (attr, value)))
275 type Block<'a> = HashMap<&'a str, Ck<'a>>;
277 fn attr<'a>(block: &'a Block, attr: &str) -> &'a Ck<'a> {
278 block.get(attr).unwrap_or(&Ck::Empty)
281 fn parse(i: &str) -> IResult<&str, Vec<Block>> {
282 let mut out: Vec<Block> = vec![];
283 let (i, _) = take_until("BEGINDATA\n")(i)?;
284 let (i, _) = tag("BEGINDATA\n")(i)?;
285 let (i, mut raw_blocks) = separated_list0(many1(char('\n')), many1(certdata_line))(i)?;
286 let (i, _) = multispace0(i)?; // allow trailing whitespace
288 // The first line of i contains an error.
289 let (line, _) = i.split_once('\n').unwrap_or((i, ""));
290 fail::<_, &str, _>(line)?;
292 for raw_block in raw_blocks.drain(..) {
293 out.push(raw_block.into_iter().collect())
299 struct PKCS11TypesParseCallbacks;
301 impl ParseCallbacks for PKCS11TypesParseCallbacks {
302 fn int_macro(&self, _name: &str, _value: i64) -> Option<IntKind> {
307 // If we encounter a problem parsing certdata.txt we'll try to turn it into a compile time
308 // error in builtins.rs. We need to output definitions for ROOT_LIST_LABEL and BUILTINS to
309 // cut down on the number of errors the compiler produces.
310 macro_rules! emit_build_error {
311 ($out:ident, $err:expr) => {
312 writeln!($out, "std::compile_error!(\"{}\");", $err)?;
313 writeln!($out, "pub static ROOT_LIST_LABEL: [u8; 0] = [];")?;
314 writeln!($out, "pub static BUILTINS: [Root; 0] = [];")?;
318 fn main() -> std::io::Result<()> {
319 let testlib_certdata =
320 TOPSRCDIR.join("security/manager/ssl/tests/unit/test_builtins/certdata.txt");
321 let mozilla_certdata = TOPSRCDIR.join("security/nss/lib/ckfw/builtins/certdata.txt");
322 let nssckbi_header = TOPSRCDIR.join("security/nss/lib/ckfw/builtins/nssckbi.h");
323 println!("cargo:rerun-if-changed={}", testlib_certdata.display());
324 println!("cargo:rerun-if-changed={}", mozilla_certdata.display());
325 println!("cargo:rerun-if-changed={}", nssckbi_header.display());
327 let bindings = Builder::default()
328 .header(nssckbi_header.display().to_string())
329 .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MAJOR")
330 .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MINOR")
331 .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MAJOR")
332 .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MINOR")
333 .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MAJOR")
334 .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MINOR")
335 .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MAJOR")
336 .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MINOR")
337 .parse_callbacks(Box::new(PKCS11TypesParseCallbacks))
339 .expect("Unable to generate bindings.");
341 let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR should be set in env."));
343 .write_to_file(out_path.join("version.rs"))
344 .expect("Could not write version.rs.");
346 let mut out = BufWriter::new(
347 File::create(out_path.join("builtins.rs")).expect("Could not write builtins.rs."),
350 // If we are building the test module, use the certdata.txt in the test directory.
351 #[cfg(feature = "testlib")]
353 std::fs::read_to_string(testlib_certdata).expect("Unable to read certdata.txt.");
355 // Otherwise, use the official certdata.txt for the Mozilla root store.
356 #[cfg(not(feature = "testlib"))]
358 std::fs::read_to_string(mozilla_certdata).expect("Unable to read certdata.txt.");
360 // Add a trailing newline to simplify parsing.
363 let blocks = match parse(&input) {
364 Ok((_, blocks)) => blocks,
366 let input = match e {
367 nom::Err::Error(nom::error::Error { input, .. }) => input,
373 "Could not parse certdata.txt. Failed at: \'{}\');",
374 input.escape_debug().to_string().escape_debug()
381 let root_lists: Vec<&Block> = blocks
383 .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_BUILTIN_ROOT_LIST"))
386 if root_lists.len() != 1 {
389 "certdata.txt does not define a CKO_NSS_BUILTIN_ROOT_LIST object."
394 let mut certs: Vec<&Block> = blocks
396 .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_CERTIFICATE"))
399 let trusts: Vec<&Block> = blocks
401 .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_TRUST"))
404 if certs.len() != trusts.len() {
407 "certdata.txt has a mismatched number of certificate and trust objects"
412 // Ensure that every certificate has a CKA_SUBJECT attribute for the sort
413 for (i, cert) in certs.iter().enumerate() {
414 match cert.get("CKA_SUBJECT") {
415 Some(Ck::MultilineOctal(_)) => (),
419 format!("Certificate {i} in certdata.txt has no CKA_SUBJECT attribute.")
426 certs.sort_by_cached_key(|x| match x.get("CKA_SUBJECT") {
427 Some(Ck::MultilineOctal(data)) => octal_block_to_vec_u8(data),
431 // Write out arrays for the DER encoded certificate, serial number, and subject of each root.
432 // Since the serial number and the subject are in the DER cert, we don't need to store
433 // additional data for them.
434 for (i, cert) in certs.iter().enumerate() {
435 // Preserve the comment from certdata.txt
436 match attr(cert, "COMMENT") {
438 comment => write!(out, "{comment}")?,
441 let der = attr(cert, "CKA_VALUE");
442 writeln!(out, "static ROOT_{i}: &[u8] = {der};")?;
444 // Search for the serial number and subject in the DER cert. We want to search on the raw
445 // bytes, not the octal presentation, so we have to unpack the enums.
446 let der_data = match der {
447 Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
450 let serial_data = match attr(cert, "CKA_SERIAL_NUMBER") {
451 Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
454 let subject_data = match attr(cert, "CKA_SUBJECT") {
455 Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
459 fn need_u16(out: &mut impl Write, attr: &str, what: &str, i: usize) -> std::io::Result<()> {
462 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?")
467 let serial_len = serial_data.len();
468 if let Some(serial_offset) = &der_data.windows(serial_len).position(|s| s == serial_data) {
469 if *serial_offset > u8::MAX.into() {
470 return need_u16(&mut out, "CKA_SERIAL_NUMBER", "offset", i);
472 if serial_len > u8::MAX.into() {
473 return need_u16(&mut out, "CKA_SERIAL_NUMBER", "length", i);
477 "const SERIAL_{i}: (u8, u8) = ({serial_offset}, {serial_len});"
482 format!("Certificate {i} in certdata.txt has a CKA_SERIAL_NUMBER that does not match its CKA_VALUE.")
487 let subject_len = subject_data.len();
488 if let Some(subject_offset) = &der_data
489 .windows(subject_len)
490 .position(|s| s == subject_data)
492 if *subject_offset > u8::MAX.into() {
493 return need_u16(&mut out, "CKA_SUBJECT", "offset", i);
495 if subject_len > u8::MAX.into() {
496 return need_u16(&mut out, "CKA_SUBJECT", "length", i);
500 "const SUBJECT_{i}: (u8, u8) = ({subject_offset}, {subject_len});"
505 format!("Certificate {i} in certdata.txt has a CKA_SUBJECT that does not match its CKA_VALUE.")
511 let root_list_label = attr(root_lists[0], "CKA_LABEL");
512 let root_list_label_len = match root_list_label {
513 Ck::Utf8(x) => x.len() + 1,
518 "pub const ROOT_LIST_LABEL: [u8; {root_list_label_len}] = *b{root_list_label};"
521 writeln!(out, "pub static BUILTINS: [Root; {}] = [", certs.len())?;
522 for (i, cert) in certs.iter().enumerate() {
523 let subject = attr(cert, "CKA_SUBJECT");
524 let issuer = attr(cert, "CKA_ISSUER");
525 let label = attr(cert, "CKA_LABEL");
526 if !subject.eq(issuer) {
527 writeln!(out, "];")?; // end the definition of BUILTINS
528 let label = format!("{}", label);
531 "std::compile_error!(\"Certificate with label {} is not self-signed\");",
536 let mozpol = attr(cert, "CKA_NSS_MOZILLA_CA_POLICY");
537 let server_distrust = attr(cert, "CKA_NSS_SERVER_DISTRUST_AFTER");
538 let email_distrust = attr(cert, "CKA_NSS_EMAIL_DISTRUST_AFTER");
539 let matching_trusts: Vec<&&Block> = trusts
542 (attr(cert, "CKA_ISSUER") == attr(trust, "CKA_ISSUER"))
543 && (attr(cert, "CKA_SERIAL_NUMBER") == attr(trust, "CKA_SERIAL_NUMBER"))
546 if matching_trusts.len() != 1 {
547 writeln!(out, "];")?; // end the definition of BUILTINS
548 let label = format!("{}", label);
549 writeln!(out, "std::compile_error!(\"Could not find unique trust object for {} in certdata.txt\");", label.escape_debug())?;
552 let trust = *matching_trusts[0];
553 let sha1 = match attr(trust, "CKA_CERT_SHA1_HASH") {
554 Ck::MultilineOctal(x) => octal_block_to_hex_string(x),
557 let md5 = match attr(trust, "CKA_CERT_MD5_HASH") {
558 Ck::MultilineOctal(x) => octal_block_to_hex_string(x),
561 let server = attr(trust, "CKA_TRUST_SERVER_AUTH");
562 let email = attr(trust, "CKA_TRUST_EMAIL_PROTECTION");
568 der_name: SUBJECT_{i},
569 der_serial: SERIAL_{i},
571 mozilla_ca_policy: {mozpol},
572 server_distrust_after: {server_distrust},
573 email_distrust_after: {email_distrust},
576 trust_server: {server},
577 trust_email: {email},
581 writeln!(out, "];")?;