tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

build.rs (20690B)


      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/. */
      5 
      6 extern crate bindgen;
      7 extern crate nom;
      8 extern crate sha2;
      9 
     10 use bindgen::callbacks::*;
     11 use bindgen::*;
     12 
     13 use mozbuild::TOPSRCDIR;
     14 
     15 use nom::branch::alt;
     16 use nom::bytes::complete::{tag, take_until};
     17 use nom::character::complete::{
     18    char, multispace0, newline, not_line_ending, one_of, space0, space1,
     19 };
     20 use nom::combinator::{fail, recognize};
     21 use nom::multi::{many1, separated_list0};
     22 use nom::sequence::{delimited, separated_pair, terminated, tuple};
     23 use nom::IResult;
     24 
     25 use sha2::{Digest, Sha256};
     26 
     27 use std::collections::HashMap;
     28 use std::env;
     29 use std::fmt;
     30 use std::fs::File;
     31 use std::io::{BufWriter, Write};
     32 use std::path::PathBuf;
     33 
     34 fn octal_block_to_vec_u8(octal_block: &str) -> Vec<u8> {
     35    octal_block
     36        .lines()
     37        .flat_map(|x| x.split('\\').skip(1))
     38        .map(|x| u8::from_str_radix(x, 8).expect("octal value out of range."))
     39        .collect()
     40 }
     41 
     42 fn octal_block_to_hex_string(octal: &str) -> String {
     43    octal_block_to_vec_u8(octal)
     44        .iter()
     45        .map(|x| format!("0x{:02X}, ", x))
     46        .collect()
     47 }
     48 
     49 // Wrapper around values parsed out of certdata.txt
     50 enum Ck<'a> {
     51    Class(&'a str),
     52    Comment(&'a str),
     53    DistrustAfter(Option<&'a str>),
     54    Empty,
     55    MultilineOctal(&'a str),
     56    OptionBool(&'a str),
     57    Trust(&'a str),
     58    Utf8(&'a str),
     59 }
     60 
     61 // Translation of parsed values into the output rust code
     62 impl fmt::Display for Ck<'_> {
     63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     64        match self {
     65            Ck::Class(s) => write!(f, "{s}_BYTES"),
     66            Ck::Comment(s) => write!(f, "{}", s.replace('#', "//")),
     67            Ck::DistrustAfter(None) => write!(f, "Some(CK_FALSE_BYTES)"),
     68            Ck::DistrustAfter(Some(s)) => write!(f, "Some(&[{}])", octal_block_to_hex_string(s)),
     69            Ck::Empty => write!(f, "None"),
     70            Ck::MultilineOctal(s) => write!(f, "&[{}]", octal_block_to_hex_string(s)),
     71            Ck::OptionBool(s) => write!(f, "Some({s}_BYTES)"),
     72            Ck::Trust(s) => match *s {
     73                "CKT_NSS_TRUSTED_DELEGATOR" => write!(f, "CKT_TRUST_ANCHOR_BYTES"),
     74                "CKT_NSS_MUST_VERIFY_TRUST" => write!(f, "CKT_TRUST_MUST_VERIFY_TRUST_BYTES"),
     75                "CKT_NSS_NOT_TRUSTED" => write!(f, "CKT_NOT_TRUSTED_BYTES"),
     76                _ => unreachable!(),
     77            },
     78            Ck::Utf8(s) => write!(f, "\"{s}\\0\""),
     79        }
     80    }
     81 }
     82 
     83 impl PartialEq for Ck<'_> {
     84    fn eq(&self, other: &Self) -> bool {
     85        match (self, other) {
     86            (Ck::Class(s), Ck::Class(t)) => s.eq(t),
     87            (Ck::Comment(s), Ck::Comment(t)) => s.eq(t),
     88            (Ck::DistrustAfter(None), Ck::DistrustAfter(None)) => true,
     89            (Ck::DistrustAfter(Some(s)), Ck::DistrustAfter(Some(t))) => {
     90                // compare the data rather than the presentation
     91                let vec_s = octal_block_to_vec_u8(s);
     92                let vec_t = octal_block_to_vec_u8(t);
     93                vec_s.eq(&vec_t)
     94            }
     95            (Ck::Empty, Ck::Empty) => true,
     96            (Ck::MultilineOctal(s), Ck::MultilineOctal(t)) => {
     97                // compare the data rather than the presentation
     98                let vec_s = octal_block_to_vec_u8(s);
     99                let vec_t = octal_block_to_vec_u8(t);
    100                vec_s.eq(&vec_t)
    101            }
    102            (Ck::Trust(s), Ck::Trust(t)) => s.eq(t),
    103            (Ck::Utf8(s), Ck::Utf8(t)) => s.eq(t),
    104            _ => false,
    105        }
    106    }
    107 }
    108 
    109 fn class(i: &str) -> IResult<&str, Ck<'_>> {
    110    let (i, _) = tag("CK_OBJECT_CLASS")(i)?;
    111    let (i, _) = space1(i)?;
    112    let (i, class) = alt((
    113        tag("CKO_NSS_BUILTIN_ROOT_LIST"),
    114        tag("CKO_CERTIFICATE"),
    115        tag("CKO_NSS_TRUST"),
    116    ))(i)?;
    117    let (i, _) = space0(i)?;
    118    let (i, _) = newline(i)?;
    119    Ok((i, Ck::Class(class)))
    120 }
    121 
    122 fn trust(i: &str) -> IResult<&str, Ck<'_>> {
    123    let (i, _) = tag("CK_TRUST")(i)?;
    124    let (i, _) = space1(i)?;
    125    let (i, trust) = alt((
    126        tag("CKT_NSS_TRUSTED_DELEGATOR"),
    127        tag("CKT_NSS_MUST_VERIFY_TRUST"),
    128        tag("CKT_NSS_NOT_TRUSTED"),
    129    ))(i)?;
    130    let (i, _) = space0(i)?;
    131    let (i, _) = newline(i)?;
    132    Ok((i, Ck::Trust(trust)))
    133 }
    134 
    135 // Parses a CK_BBOOL and wraps it with Ck::OptionBool so that it gets printed as
    136 // "Some(CK_TRUE_BYTES)" instead of "CK_TRUE_BYTES".
    137 fn option_bbool(i: &str) -> IResult<&str, Ck<'_>> {
    138    let (i, _) = tag("CK_BBOOL")(i)?;
    139    let (i, _) = space1(i)?;
    140    let (i, b) = alt((tag("CK_TRUE"), tag("CK_FALSE")))(i)?;
    141    let (i, _) = space0(i)?;
    142    let (i, _) = newline(i)?;
    143    Ok((i, Ck::OptionBool(b)))
    144 }
    145 
    146 fn bbool_true(i: &str) -> IResult<&str, Ck<'_>> {
    147    let (i, _) = tag("CK_BBOOL")(i)?;
    148    let (i, _) = space1(i)?;
    149    let (i, _) = tag("CK_TRUE")(i)?;
    150    let (i, _) = space0(i)?;
    151    let (i, _) = newline(i)?;
    152    Ok((i, Ck::Empty))
    153 }
    154 
    155 fn bbool_false(i: &str) -> IResult<&str, Ck<'_>> {
    156    let (i, _) = tag("CK_BBOOL")(i)?;
    157    let (i, _) = space1(i)?;
    158    let (i, _) = tag("CK_FALSE")(i)?;
    159    let (i, _) = space0(i)?;
    160    let (i, _) = newline(i)?;
    161    Ok((i, Ck::Empty))
    162 }
    163 
    164 fn utf8(i: &str) -> IResult<&str, Ck<'_>> {
    165    let (i, _) = tag("UTF8")(i)?;
    166    let (i, _) = space1(i)?;
    167    let (i, _) = char('"')(i)?;
    168    let (i, utf8) = take_until("\"")(i)?;
    169    let (i, _) = char('"')(i)?;
    170    let (i, _) = space0(i)?;
    171    let (i, _) = newline(i)?;
    172    Ok((i, Ck::Utf8(utf8)))
    173 }
    174 
    175 fn certificate_type(i: &str) -> IResult<&str, Ck<'_>> {
    176    let (i, _) = tag("CK_CERTIFICATE_TYPE")(i)?;
    177    let (i, _) = space1(i)?;
    178    let (i, _) = tag("CKC_X_509")(i)?;
    179    let (i, _) = space0(i)?;
    180    let (i, _) = newline(i)?;
    181    Ok((i, Ck::Empty))
    182 }
    183 
    184 // A CKA_NSS_{EMAIL,SERVER}_DISTRUST_AFTER line in certdata.txt is encoded either as a CK_BBOOL
    185 // with value CK_FALSE (when there is no distrust after date) or as a MULTILINE_OCTAL block.
    186 fn distrust_after(i: &str) -> IResult<&str, Ck<'_>> {
    187    let (i, value) = alt((multiline_octal, bbool_false))(i)?;
    188    match value {
    189        Ck::Empty => Ok((i, Ck::DistrustAfter(None))),
    190        Ck::MultilineOctal(data) => Ok((i, Ck::DistrustAfter(Some(data)))),
    191        _ => unreachable!(),
    192    }
    193 }
    194 
    195 fn octal_octet(i: &str) -> IResult<&str, &str> {
    196    recognize(tuple((
    197        tag("\\"),
    198        one_of("0123"), // 255 = \377
    199        one_of("01234567"),
    200        one_of("01234567"),
    201    )))(i)
    202 }
    203 
    204 fn multiline_octal(i: &str) -> IResult<&str, Ck<'_>> {
    205    let (i, _) = tag("MULTILINE_OCTAL")(i)?;
    206    let (i, _) = space0(i)?;
    207    let (i, _) = newline(i)?;
    208    let (i, lines) = recognize(many1(terminated(many1(octal_octet), newline)))(i)?;
    209    let (i, _) = tag("END")(i)?;
    210    let (i, _) = space0(i)?;
    211    let (i, _) = newline(i)?;
    212    return Ok((i, Ck::MultilineOctal(lines)));
    213 }
    214 
    215 fn distrust_comment(i: &str) -> IResult<&str, (&str, Ck<'_>)> {
    216    let (i, comment) = recognize(delimited(
    217        alt((
    218            tag("# For Email Distrust After: "),
    219            tag("# For Server Distrust After: "),
    220        )),
    221        not_line_ending,
    222        newline,
    223    ))(i)?;
    224    Ok((i, ("DISTRUST_COMMENT", Ck::Comment(comment))))
    225 }
    226 
    227 fn comment(i: &str) -> IResult<&str, (&str, Ck<'_>)> {
    228    let (i, comment) = recognize(many1(delimited(char('#'), not_line_ending, newline)))(i)?;
    229    Ok((i, ("COMMENT", Ck::Comment(comment))))
    230 }
    231 
    232 fn certdata_line(i: &str) -> IResult<&str, (&str, Ck<'_>)> {
    233    let (i, (attr, value)) = alt((
    234        distrust_comment, // must be listed before `comment`
    235        comment,
    236        separated_pair(tag("CKA_CLASS"), space1, class),
    237        separated_pair(tag("CKA_CERTIFICATE_TYPE"), space1, certificate_type),
    238        separated_pair(alt((tag("CKA_ID"), tag("CKA_LABEL"))), space1, utf8),
    239        separated_pair(
    240            alt((
    241                tag("CKA_ISSUER"),
    242                tag("CKA_CERT_SHA1_HASH"),
    243                tag("CKA_CERT_MD5_HASH"),
    244                tag("CKA_SERIAL_NUMBER"),
    245                tag("CKA_SUBJECT"),
    246                tag("CKA_VALUE"),
    247            )),
    248            space1,
    249            multiline_octal,
    250        ),
    251        separated_pair(
    252            alt((
    253                tag("CKA_NSS_SERVER_DISTRUST_AFTER"),
    254                tag("CKA_NSS_EMAIL_DISTRUST_AFTER"),
    255            )),
    256            space1,
    257            distrust_after,
    258        ),
    259        separated_pair(
    260            alt((
    261                tag("CKA_TRUST_EMAIL_PROTECTION"),
    262                tag("CKA_TRUST_CODE_SIGNING"),
    263                tag("CKA_TRUST_SERVER_AUTH"),
    264            )),
    265            space1,
    266            trust,
    267        ),
    268        separated_pair(tag("CKA_NSS_MOZILLA_CA_POLICY"), space1, option_bbool),
    269        separated_pair(tag("CKA_TOKEN"), space1, bbool_true),
    270        separated_pair(
    271            alt((
    272                tag("CKA_TRUST_STEP_UP_APPROVED"),
    273                tag("CKA_PRIVATE"),
    274                tag("CKA_MODIFIABLE"),
    275            )),
    276            space1,
    277            bbool_false,
    278        ),
    279    ))(i)?;
    280    Ok((i, (attr, value)))
    281 }
    282 
    283 type Block<'a> = HashMap<&'a str, Ck<'a>>;
    284 
    285 fn attr<'a>(block: &'a Block, attr: &str) -> &'a Ck<'a> {
    286    block.get(attr).unwrap_or(&Ck::Empty)
    287 }
    288 
    289 fn parse(i: &str) -> IResult<&str, Vec<Block<'_>>> {
    290    let mut out: Vec<Block> = vec![];
    291    let (i, _) = take_until("BEGINDATA\n")(i)?;
    292    let (i, _) = tag("BEGINDATA\n")(i)?;
    293    let (i, mut raw_blocks) = separated_list0(many1(char('\n')), many1(certdata_line))(i)?;
    294    let (i, _) = multispace0(i)?; // allow trailing whitespace
    295    if !i.is_empty() {
    296        // The first line of i contains an error.
    297        let (line, _) = i.split_once('\n').unwrap_or((i, ""));
    298        fail::<_, &str, _>(line)?;
    299    }
    300    for raw_block in raw_blocks.drain(..) {
    301        out.push(raw_block.into_iter().collect())
    302    }
    303    Ok((i, out))
    304 }
    305 
    306 #[derive(Debug)]
    307 struct PKCS11TypesParseCallbacks;
    308 
    309 impl ParseCallbacks for PKCS11TypesParseCallbacks {
    310    fn int_macro(&self, _name: &str, _value: i64) -> Option<IntKind> {
    311        Some(IntKind::U8)
    312    }
    313 }
    314 
    315 // If we encounter a problem parsing certdata.txt we'll try to turn it into a compile time
    316 // error in builtins.rs. We need to output definitions for ROOT_LIST_LABEL and BUILTINS to
    317 // cut down on the number of errors the compiler produces.
    318 macro_rules! emit_build_error {
    319    ($out:ident, $err:expr) => {
    320        writeln!($out, "std::compile_error!(\"{}\");", $err)?;
    321        writeln!($out, "pub static ROOT_LIST_LABEL: [u8; 0] = [];")?;
    322        writeln!($out, "pub static BUILTINS: [Root; 0] = [];")?;
    323    };
    324 }
    325 
    326 fn main() -> std::io::Result<()> {
    327    let testlib_certdata =
    328        TOPSRCDIR.join("security/manager/ssl/tests/unit/test_trust_anchors/certdata.txt");
    329    let mozilla_certdata = TOPSRCDIR.join("security/nss/lib/ckfw/builtins/certdata.txt");
    330    let nssckbi_header = TOPSRCDIR.join("security/nss/lib/ckfw/builtins/nssckbi.h");
    331    let bundled_intermediates =
    332        TOPSRCDIR.join("security/manager/ssl/trust_anchors/bundled_intermediates.txt");
    333    println!("cargo:rerun-if-changed={}", testlib_certdata.display());
    334    println!("cargo:rerun-if-changed={}", mozilla_certdata.display());
    335    println!("cargo:rerun-if-changed={}", nssckbi_header.display());
    336    println!("cargo:rerun-if-changed={}", bundled_intermediates.display());
    337 
    338    let bindings = Builder::default()
    339        .header(nssckbi_header.display().to_string())
    340        .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MAJOR")
    341        .allowlist_var("NSS_BUILTINS_CRYPTOKI_VERSION_MINOR")
    342        .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MAJOR")
    343        .allowlist_var("NSS_BUILTINS_LIBRARY_VERSION_MINOR")
    344        .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MAJOR")
    345        .allowlist_var("NSS_BUILTINS_HARDWARE_VERSION_MINOR")
    346        .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MAJOR")
    347        .allowlist_var("NSS_BUILTINS_FIRMWARE_VERSION_MINOR")
    348        .parse_callbacks(Box::new(PKCS11TypesParseCallbacks))
    349        .generate()
    350        .expect("Unable to generate bindings.");
    351 
    352    let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR should be set in env."));
    353    bindings
    354        .write_to_file(out_path.join("version.rs"))
    355        .expect("Could not write version.rs.");
    356 
    357    let mut out = BufWriter::new(
    358        File::create(out_path.join("builtins.rs")).expect("Could not write builtins.rs."),
    359    );
    360 
    361    // If we are building the test module, use the certdata.txt in the test directory.
    362    #[cfg(feature = "testlib")]
    363    let mut input =
    364        std::fs::read_to_string(testlib_certdata).expect("Unable to read certdata.txt.");
    365 
    366    // Otherwise, use the official certdata.txt for the Mozilla root store.
    367    #[cfg(not(feature = "testlib"))]
    368    let mut input =
    369        std::fs::read_to_string(mozilla_certdata).expect("Unable to read certdata.txt.");
    370 
    371    #[cfg(not(feature = "testlib"))]
    372    input.push_str(
    373        &std::fs::read_to_string(bundled_intermediates)
    374            .expect("Unable to read bundled_intermediates.txt."),
    375    );
    376 
    377    // Add a trailing newline to simplify parsing.
    378    input.push('\n');
    379 
    380    let blocks = match parse(&input) {
    381        Ok((_, blocks)) => blocks,
    382        Err(e) => {
    383            let input = match e {
    384                nom::Err::Error(nom::error::Error { input, .. }) => input,
    385                _ => "Unknown",
    386            };
    387            emit_build_error!(
    388                out,
    389                &format!(
    390                    "Could not parse certdata.txt. Failed at: \'{}\');",
    391                    input.escape_debug().to_string().escape_debug()
    392                )
    393            );
    394            return Ok(());
    395        }
    396    };
    397 
    398    let root_lists: Vec<&Block> = blocks
    399        .iter()
    400        .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_BUILTIN_ROOT_LIST"))
    401        .collect();
    402 
    403    if root_lists.len() != 1 {
    404        emit_build_error!(
    405            out,
    406            "certdata.txt does not define a CKO_NSS_BUILTIN_ROOT_LIST object."
    407        );
    408        return Ok(());
    409    }
    410 
    411    let mut certs: Vec<&Block> = blocks
    412        .iter()
    413        .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_CERTIFICATE"))
    414        .collect();
    415 
    416    let trusts: Vec<&Block> = blocks
    417        .iter()
    418        .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_TRUST"))
    419        .collect();
    420 
    421    if certs.len() != trusts.len() {
    422        emit_build_error!(
    423            out,
    424            "certdata.txt has a mismatched number of certificate and trust objects"
    425        );
    426        return Ok(());
    427    }
    428 
    429    // Ensure that every certificate has a CKA_SUBJECT attribute for the sort
    430    for (i, cert) in certs.iter().enumerate() {
    431        match cert.get("CKA_SUBJECT") {
    432            Some(Ck::MultilineOctal(_)) => (),
    433            _ => {
    434                emit_build_error!(
    435                    out,
    436                    format!("Certificate {i} in certdata.txt has no CKA_SUBJECT attribute.")
    437                );
    438                return Ok(());
    439            }
    440        }
    441    }
    442 
    443    certs.sort_by_cached_key(|x| match x.get("CKA_SUBJECT") {
    444        Some(Ck::MultilineOctal(data)) => octal_block_to_vec_u8(data),
    445        _ => unreachable!(),
    446    });
    447 
    448    // Write out arrays for the DER encoded certificate, serial number, and subject of each root.
    449    // Since the serial number and the subject are in the DER cert, we don't need to store
    450    // additional data for them.
    451    for (i, cert) in certs.iter().enumerate() {
    452        // Preserve the comment from certdata.txt
    453        match attr(cert, "COMMENT") {
    454            Ck::Empty => (),
    455            comment => write!(out, "{comment}")?,
    456        };
    457 
    458        let der = attr(cert, "CKA_VALUE");
    459        writeln!(out, "static ROOT_{i}: &[u8] = {der};")?;
    460 
    461        // Search for the serial number and subject in the DER cert. We want to search on the raw
    462        // bytes, not the octal presentation, so we have to unpack the enums.
    463        let der_data = match der {
    464            Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
    465            _ => unreachable!(),
    466        };
    467        let serial_data = match attr(cert, "CKA_SERIAL_NUMBER") {
    468            Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
    469            _ => unreachable!(),
    470        };
    471        let subject_data = match attr(cert, "CKA_SUBJECT") {
    472            Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
    473            _ => unreachable!(),
    474        };
    475 
    476        fn need_u16(out: &mut impl Write, attr: &str, what: &str, i: usize) -> std::io::Result<()> {
    477            emit_build_error!(
    478                out,
    479                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?")
    480            );
    481            Ok(())
    482        }
    483 
    484        let serial_len = serial_data.len();
    485        if let Some(serial_offset) = &der_data.windows(serial_len).position(|s| s == serial_data) {
    486            if *serial_offset > u8::MAX.into() {
    487                return need_u16(&mut out, "CKA_SERIAL_NUMBER", "offset", i);
    488            }
    489            if serial_len > u8::MAX.into() {
    490                return need_u16(&mut out, "CKA_SERIAL_NUMBER", "length", i);
    491            }
    492            writeln!(
    493                out,
    494                "const SERIAL_{i}: (u8, u8) = ({serial_offset}, {serial_len});"
    495            )?;
    496        } else {
    497            emit_build_error!(
    498                out,
    499                format!("Certificate {i} in certdata.txt has a CKA_SERIAL_NUMBER that does not match its CKA_VALUE.")
    500            );
    501            return Ok(());
    502        }
    503 
    504        let subject_len = subject_data.len();
    505        if let Some(subject_offset) = &der_data
    506            .windows(subject_len)
    507            .position(|s| s == subject_data)
    508        {
    509            if *subject_offset > u8::MAX.into() {
    510                return need_u16(&mut out, "CKA_SUBJECT", "offset", i);
    511            }
    512            if subject_len > u8::MAX.into() {
    513                return need_u16(&mut out, "CKA_SUBJECT", "length", i);
    514            }
    515            writeln!(
    516                out,
    517                "const SUBJECT_{i}: (u8, u8) = ({subject_offset}, {subject_len});"
    518            )?;
    519        } else {
    520            emit_build_error!(
    521                out,
    522                format!("Certificate {i} in certdata.txt has a CKA_SUBJECT that does not match its CKA_VALUE.")
    523            );
    524            return Ok(());
    525        }
    526    }
    527 
    528    let root_list_label = attr(root_lists[0], "CKA_LABEL");
    529    let root_list_label_len = match root_list_label {
    530        Ck::Utf8(x) => x.len() + 1,
    531        _ => unreachable!(),
    532    };
    533    writeln!(
    534        out,
    535        "pub const ROOT_LIST_LABEL: [u8; {root_list_label_len}] = *b{root_list_label};"
    536    )?;
    537 
    538    writeln!(out, "pub static BUILTINS: [Root; {}] = [", certs.len())?;
    539    for (i, cert) in certs.iter().enumerate() {
    540        let label = attr(cert, "CKA_LABEL");
    541        let mozpol = attr(cert, "CKA_NSS_MOZILLA_CA_POLICY");
    542        let server_distrust = attr(cert, "CKA_NSS_SERVER_DISTRUST_AFTER");
    543        let email_distrust = attr(cert, "CKA_NSS_EMAIL_DISTRUST_AFTER");
    544        let matching_trusts: Vec<&&Block> = trusts
    545            .iter()
    546            .filter(|trust| {
    547                (attr(cert, "CKA_ISSUER") == attr(trust, "CKA_ISSUER"))
    548                    && (attr(cert, "CKA_SERIAL_NUMBER") == attr(trust, "CKA_SERIAL_NUMBER"))
    549            })
    550            .collect();
    551        if matching_trusts.len() != 1 {
    552            writeln!(out, "];")?; // end the definition of BUILTINS
    553            let label = format!("{}", label);
    554            writeln!(out, "std::compile_error!(\"Could not find unique trust object for {} in certdata.txt\");", label.escape_debug())?;
    555            return Ok(());
    556        }
    557        let trust = *matching_trusts[0];
    558 
    559        let sha256 = match attr(cert, "CKA_VALUE") {
    560            Ck::MultilineOctal(x) => Sha256::digest(octal_block_to_vec_u8(x))
    561                .iter()
    562                .map(|x| format!("0x{:02X}", x))
    563                .collect::<Vec<String>>()
    564                .join(", "),
    565            _ => unreachable!(),
    566        };
    567 
    568        let server = attr(trust, "CKA_TRUST_SERVER_AUTH");
    569        let email = attr(trust, "CKA_TRUST_EMAIL_PROTECTION");
    570 
    571        writeln!(
    572            out,
    573            "        Root {{
    574            label: {label},
    575            der_name: SUBJECT_{i},
    576            der_serial: SERIAL_{i},
    577            der_cert: ROOT_{i},
    578            mozilla_ca_policy: {mozpol},
    579            server_distrust_after: {server_distrust},
    580            email_distrust_after: {email_distrust},
    581            sha256: [{sha256}],
    582            trust_server: {server},
    583            trust_email: {email},
    584        }},"
    585        )?;
    586    }
    587    writeln!(out, "];")?;
    588 
    589    let _ = out.flush();
    590    Ok(())
    591 }