Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / security / manager / ssl / builtins / build.rs
blob439f407fcfa88b372b0db50c012ad42824af11dc
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/. */
6 extern crate bindgen;
7 extern crate nom;
9 use bindgen::callbacks::*;
10 use bindgen::*;
12 use mozbuild::TOPSRCDIR;
14 use nom::branch::alt;
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};
22 use nom::IResult;
24 use std::collections::HashMap;
25 use std::env;
26 use std::fmt;
27 use std::fs::File;
28 use std::io::{BufWriter, Write};
29 use std::path::PathBuf;
31 fn octal_block_to_vec_u8(octal_block: &str) -> Vec<u8> {
32     octal_block
33         .lines()
34         .flat_map(|x| x.split('\\').skip(1))
35         .map(|x| u8::from_str_radix(x, 8).expect("octal value out of range."))
36         .collect()
39 fn octal_block_to_hex_string(octal: &str) -> String {
40     octal_block_to_vec_u8(octal)
41         .iter()
42         .map(|x| format!("0x{:02X}, ", x))
43         .collect()
46 // Wrapper around values parsed out of certdata.txt
47 enum Ck<'a> {
48     Class(&'a str),
49     Comment(&'a str),
50     DistrustAfter(Option<&'a str>),
51     Empty,
52     MultilineOctal(&'a str),
53     OptionBool(&'a str),
54     Trust(&'a str),
55     Utf8(&'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 {
61         match self {
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\""),
71         }
72     }
75 impl PartialEq for Ck<'_> {
76     fn eq(&self, other: &Self) -> bool {
77         match (self, other) {
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);
85                 vec_s.eq(&vec_t)
86             }
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);
92                 vec_s.eq(&vec_t)
93             }
94             (Ck::Trust(s), Ck::Trust(t)) => s.eq(t),
95             (Ck::Utf8(s), Ck::Utf8(t)) => s.eq(t),
96             _ => false,
97         }
98     }
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"),
108     ))(i)?;
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"),
121     ))(i)?;
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)?;
144     Ok((i, Ck::Empty))
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)?;
153     Ok((i, Ck::Empty))
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)?;
173     Ok((i, Ck::Empty))
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)?;
180     match value {
181         Ck::Empty => Ok((i, Ck::DistrustAfter(None))),
182         Ck::MultilineOctal(data) => Ok((i, Ck::DistrustAfter(Some(data)))),
183         _ => unreachable!(),
184     }
187 fn octal_octet(i: &str) -> IResult<&str, &str> {
188     recognize(tuple((
189         tag("\\"),
190         one_of("0123"), // 255 = \377
191         one_of("01234567"),
192         one_of("01234567"),
193     )))(i)
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(
209         alt((
210             tag("# For Email Distrust After: "),
211             tag("# For Server Distrust After: "),
212         )),
213         not_line_ending,
214         newline,
215     ))(i)?;
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`
227         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),
231         separated_pair(
232             alt((
233                 tag("CKA_ISSUER"),
234                 tag("CKA_CERT_SHA1_HASH"),
235                 tag("CKA_CERT_MD5_HASH"),
236                 tag("CKA_SERIAL_NUMBER"),
237                 tag("CKA_SUBJECT"),
238                 tag("CKA_VALUE"),
239             )),
240             space1,
241             multiline_octal,
242         ),
243         separated_pair(
244             alt((
245                 tag("CKA_NSS_SERVER_DISTRUST_AFTER"),
246                 tag("CKA_NSS_EMAIL_DISTRUST_AFTER"),
247             )),
248             space1,
249             distrust_after,
250         ),
251         separated_pair(
252             alt((
253                 tag("CKA_TRUST_EMAIL_PROTECTION"),
254                 tag("CKA_TRUST_CODE_SIGNING"),
255                 tag("CKA_TRUST_SERVER_AUTH"),
256             )),
257             space1,
258             trust,
259         ),
260         separated_pair(tag("CKA_NSS_MOZILLA_CA_POLICY"), space1, option_bbool),
261         separated_pair(tag("CKA_TOKEN"), space1, bbool_true),
262         separated_pair(
263             alt((
264                 tag("CKA_TRUST_STEP_UP_APPROVED"),
265                 tag("CKA_PRIVATE"),
266                 tag("CKA_MODIFIABLE"),
267             )),
268             space1,
269             bbool_false,
270         ),
271     ))(i)?;
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
287     if !i.is_empty() {
288         // The first line of i contains an error.
289         let (line, _) = i.split_once('\n').unwrap_or((i, ""));
290         fail::<_, &str, _>(line)?;
291     }
292     for raw_block in raw_blocks.drain(..) {
293         out.push(raw_block.into_iter().collect())
294     }
295     Ok((i, out))
298 #[derive(Debug)]
299 struct PKCS11TypesParseCallbacks;
301 impl ParseCallbacks for PKCS11TypesParseCallbacks {
302     fn int_macro(&self, _name: &str, _value: i64) -> Option<IntKind> {
303         Some(IntKind::U8)
304     }
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] = [];")?;
315     };
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))
338         .generate()
339         .expect("Unable to generate bindings.");
341     let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR should be set in env."));
342     bindings
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."),
348     );
350     // If we are building the test module, use the certdata.txt in the test directory.
351     #[cfg(feature = "testlib")]
352     let mut input =
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"))]
357     let mut input =
358         std::fs::read_to_string(mozilla_certdata).expect("Unable to read certdata.txt.");
360     // Add a trailing newline to simplify parsing.
361     input.push('\n');
363     let blocks = match parse(&input) {
364         Ok((_, blocks)) => blocks,
365         Err(e) => {
366             let input = match e {
367                 nom::Err::Error(nom::error::Error { input, .. }) => input,
368                 _ => "Unknown",
369             };
370             emit_build_error!(
371                 out,
372                 &format!(
373                     "Could not parse certdata.txt. Failed at: \'{}\');",
374                     input.escape_debug().to_string().escape_debug()
375                 )
376             );
377             return Ok(());
378         }
379     };
381     let root_lists: Vec<&Block> = blocks
382         .iter()
383         .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_BUILTIN_ROOT_LIST"))
384         .collect();
386     if root_lists.len() != 1 {
387         emit_build_error!(
388             out,
389             "certdata.txt does not define a CKO_NSS_BUILTIN_ROOT_LIST object."
390         );
391         return Ok(());
392     }
394     let mut certs: Vec<&Block> = blocks
395         .iter()
396         .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_CERTIFICATE"))
397         .collect();
399     let trusts: Vec<&Block> = blocks
400         .iter()
401         .filter(|x| attr(x, "CKA_CLASS") == &Ck::Class("CKO_NSS_TRUST"))
402         .collect();
404     if certs.len() != trusts.len() {
405         emit_build_error!(
406             out,
407             "certdata.txt has a mismatched number of certificate and trust objects"
408         );
409         return Ok(());
410     }
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(_)) => (),
416             _ => {
417                 emit_build_error!(
418                     out,
419                     format!("Certificate {i} in certdata.txt has no CKA_SUBJECT attribute.")
420                 );
421                 return Ok(());
422             }
423         }
424     }
426     certs.sort_by_cached_key(|x| match x.get("CKA_SUBJECT") {
427         Some(Ck::MultilineOctal(data)) => octal_block_to_vec_u8(data),
428         _ => unreachable!(),
429     });
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") {
437             Ck::Empty => (),
438             comment => write!(out, "{comment}")?,
439         };
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),
448             _ => unreachable!(),
449         };
450         let serial_data = match attr(cert, "CKA_SERIAL_NUMBER") {
451             Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
452             _ => unreachable!(),
453         };
454         let subject_data = match attr(cert, "CKA_SUBJECT") {
455             Ck::MultilineOctal(x) => octal_block_to_vec_u8(x),
456             _ => unreachable!(),
457         };
459         fn need_u16(out: &mut impl Write, attr: &str, what: &str, i: usize) -> std::io::Result<()> {
460             emit_build_error!(
461                 out,
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?")
463             );
464             Ok(())
465         }
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);
471             }
472             if serial_len > u8::MAX.into() {
473                 return need_u16(&mut out, "CKA_SERIAL_NUMBER", "length", i);
474             }
475             writeln!(
476                 out,
477                 "const SERIAL_{i}: (u8, u8) = ({serial_offset}, {serial_len});"
478             )?;
479         } else {
480             emit_build_error!(
481                 out,
482                 format!("Certificate {i} in certdata.txt has a CKA_SERIAL_NUMBER that does not match its CKA_VALUE.")
483             );
484             return Ok(());
485         }
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)
491         {
492             if *subject_offset > u8::MAX.into() {
493                 return need_u16(&mut out, "CKA_SUBJECT", "offset", i);
494             }
495             if subject_len > u8::MAX.into() {
496                 return need_u16(&mut out, "CKA_SUBJECT", "length", i);
497             }
498             writeln!(
499                 out,
500                 "const SUBJECT_{i}: (u8, u8) = ({subject_offset}, {subject_len});"
501             )?;
502         } else {
503             emit_build_error!(
504                 out,
505                 format!("Certificate {i} in certdata.txt has a CKA_SUBJECT that does not match its CKA_VALUE.")
506             );
507             return Ok(());
508         }
509     }
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,
514         _ => unreachable!(),
515     };
516     writeln!(
517         out,
518         "pub const ROOT_LIST_LABEL: [u8; {root_list_label_len}] = *b{root_list_label};"
519     )?;
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);
529             writeln!(
530                 out,
531                 "std::compile_error!(\"Certificate with label {} is not self-signed\");",
532                 label.escape_debug()
533             )?;
534             return Ok(());
535         }
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
540             .iter()
541             .filter(|trust| {
542                 (attr(cert, "CKA_ISSUER") == attr(trust, "CKA_ISSUER"))
543                     && (attr(cert, "CKA_SERIAL_NUMBER") == attr(trust, "CKA_SERIAL_NUMBER"))
544             })
545             .collect();
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())?;
550             return Ok(());
551         }
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),
555             _ => unreachable!(),
556         };
557         let md5 = match attr(trust, "CKA_CERT_MD5_HASH") {
558             Ck::MultilineOctal(x) => octal_block_to_hex_string(x),
559             _ => unreachable!(),
560         };
561         let server = attr(trust, "CKA_TRUST_SERVER_AUTH");
562         let email = attr(trust, "CKA_TRUST_EMAIL_PROTECTION");
564         writeln!(
565             out,
566             "        Root {{
567             label: {label},
568             der_name: SUBJECT_{i},
569             der_serial: SERIAL_{i},
570             der_cert: ROOT_{i},
571             mozilla_ca_policy: {mozpol},
572             server_distrust_after: {server_distrust},
573             email_distrust_after: {email_distrust},
574             sha1: [{sha1}],
575             md5: [{md5}],
576             trust_server: {server},
577             trust_email: {email},
578         }},"
579         )?;
580     }
581     writeln!(out, "];")?;
583     let _ = out.flush();
584     Ok(())