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 }