Skip to content

Commit 6d4cc4c

Browse files
larseggertNot-Nik
andauthored
chore: Move over final neqo-crypt changes (#37)
* chore: Move over final neqo-crypt changes I think with this landed, we're ready for cutting 0.0.1 and landing mozilla/neqo#3399. * Add tests for disable-encryption --------- Co-authored-by: Nikolas Wipper <nwipper@mozilla.com>
1 parent ca429c3 commit 6d4cc4c

4 files changed

Lines changed: 213 additions & 49 deletions

File tree

build.rs

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -65,36 +65,70 @@ struct Bindings {
6565
// bindgen needs access to libclang.
6666
// On windows, this doesn't just work, you have to set LIBCLANG_PATH.
6767
// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
68+
// On macOS, clang-sys prefers the highest-versioned libclang it can find, which may be a
69+
// Homebrew LLVM that doesn't have the correct macOS SDK include paths, resulting in broken
70+
// bindings. Force use of Xcode's libclang instead.
6871
fn setup_clang() {
69-
// If this isn't Windows, or we're in CI, then we don't need to do anything.
70-
if env::consts::OS != "windows" || env::var("GITHUB_WORKFLOW").unwrap_or_default() == "CI" {
72+
println!("cargo:rerun-if-env-changed=LIBCLANG_PATH");
73+
println!("cargo:rerun-if-env-changed=CI");
74+
// In CI, the environment is already configured correctly.
75+
if env::var("CI").is_ok() {
7176
return;
7277
}
73-
println!("rerun-if-env-changed=LIBCLANG_PATH");
74-
println!("rerun-if-env-changed=MOZBUILD_STATE_PATH");
7578
if env::var("LIBCLANG_PATH").is_ok() {
7679
return;
7780
}
78-
let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") {
79-
PathBuf::from(dir.trim())
80-
} else {
81-
eprintln!("warning: Building without a gecko setup is not likely to work.");
82-
eprintln!(" A working libclang is needed to build nss-rs.");
83-
eprintln!(" Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
84-
eprintln!();
85-
eprintln!(" We recommend checking out https://github.com/mozilla/gecko-dev");
86-
eprintln!(" Then run `./mach bootstrap` which will retrieve clang.");
87-
eprintln!(" Make sure to export MOZBUILD_STATE_PATH when building.");
88-
return;
89-
};
90-
let libclang_dir = mozbuild_root.join("clang").join("lib");
91-
if libclang_dir.is_dir() {
92-
unsafe {
93-
env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
81+
if env::consts::OS == "macos" {
82+
if let Ok(output) = Command::new("xcode-select").arg("--print-path").output() {
83+
if output.status.success() {
84+
let xcode_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
85+
let candidates = [
86+
PathBuf::from(&xcode_path).join("Toolchains/XcodeDefault.xctoolchain/usr/lib"),
87+
PathBuf::from(&xcode_path).join("usr/lib"),
88+
];
89+
if let Some(libclang_dir) = candidates.iter().find(|p| p.is_dir()) {
90+
unsafe {
91+
env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
92+
}
93+
} else {
94+
println!(
95+
"cargo:warning=Xcode toolchain libclang not found at {}; set LIBCLANG_PATH if build fails",
96+
candidates[0].display()
97+
);
98+
}
99+
} else {
100+
println!(
101+
"cargo:warning=xcode-select returned an error; set LIBCLANG_PATH if build fails"
102+
);
103+
}
104+
} else {
105+
println!("cargo:warning=xcode-select not found; set LIBCLANG_PATH if build fails");
106+
}
107+
} else if env::consts::OS == "windows" {
108+
println!("cargo:rerun-if-env-changed=MOZBUILD_STATE_PATH");
109+
let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") {
110+
PathBuf::from(dir.trim())
111+
} else {
112+
println!("cargo:warning=Building without a gecko setup is not likely to work.");
113+
println!("cargo:warning=A working libclang is needed to build nss-rs.");
114+
println!("cargo:warning=Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
115+
println!(
116+
"cargo:warning=We recommend checking out https://github.com/mozilla/gecko-dev"
117+
);
118+
println!("cargo:warning=Then run `./mach bootstrap` which will retrieve clang.");
119+
println!("cargo:warning=Make sure to export MOZBUILD_STATE_PATH when building.");
120+
return;
121+
};
122+
let libclang_dir = mozbuild_root.join("clang").join("lib");
123+
if libclang_dir.is_dir() {
124+
unsafe {
125+
env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
126+
}
127+
} else {
128+
println!(
129+
"cargo:warning=LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko"
130+
);
94131
}
95-
println!("rustc-env:LIBCLANG_PATH={}", libclang_dir.to_str().unwrap());
96-
} else {
97-
println!("warning: LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko");
98132
}
99133
}
100134

@@ -167,8 +201,7 @@ fn build_nss(dir: PathBuf) {
167201
// Generate static libraries in addition to shared libraries.
168202
String::from("--static"),
169203
];
170-
let target = env::var("TARGET").unwrap();
171-
if target.starts_with("aarch64-") {
204+
if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "aarch64" {
172205
build_nss.push(String::from("--target=arm64"));
173206
}
174207
let status = Command::new(get_bash())
@@ -180,7 +213,8 @@ fn build_nss(dir: PathBuf) {
180213
}
181214

182215
fn dynamic_link() {
183-
let dynamic_libs = if env::consts::OS == "windows" {
216+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
217+
let dynamic_libs = if target_os == "windows" {
184218
[
185219
"nssutil3.dll",
186220
"nss3.dll",
@@ -198,12 +232,13 @@ fn dynamic_link() {
198232
}
199233

200234
fn static_link() {
235+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
201236
let mut static_libs = vec![
202237
"certdb",
203238
"certhi",
204239
"cryptohi",
205240
"freebl_static",
206-
if env::consts::OS == "windows" {
241+
if target_os == "windows" {
207242
"libnspr4"
208243
} else {
209244
"nspr4"
@@ -215,12 +250,12 @@ fn static_link() {
215250
"nsspki",
216251
"nssutil",
217252
"pk11wrap_static",
218-
if env::consts::OS == "windows" {
253+
if target_os == "windows" {
219254
"libplc4"
220255
} else {
221256
"plc4"
222257
},
223-
if env::consts::OS == "windows" {
258+
if target_os == "windows" {
224259
"libplds4"
225260
} else {
226261
"plds4"
@@ -230,7 +265,7 @@ fn static_link() {
230265
];
231266
// macOS always dynamically links against the system sqlite library.
232267
// See https://github.com/nss-dev/nss/blob/a8c22d8fc0458db3e261acc5e19b436ab573a961/coreconf/Darwin.mk#L130-L135
233-
if env::consts::OS == "macos" {
268+
if target_os == "macos" {
234269
println!("cargo:rustc-link-lib=dylib=sqlite3");
235270
} else {
236271
static_libs.push("sqlite");
@@ -263,11 +298,7 @@ fn static_link() {
263298
fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
264299
let nsprinclude = nsstarget.join("include").join("nspr");
265300
let nssinclude = nssdist.join("public").join("nss");
266-
let includes = vec![nsprinclude, nssinclude];
267-
for i in &includes {
268-
println!("cargo:include={}", i.to_str().unwrap());
269-
}
270-
includes
301+
vec![nsprinclude, nssinclude]
271302
}
272303

273304
fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) {
@@ -285,14 +316,15 @@ fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool
285316
builder = builder.clang_arg("-v");
286317

287318
if !gecko {
319+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
288320
builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT");
289-
if env::consts::OS == "windows" {
321+
if target_os == "windows" {
290322
builder = builder.clang_arg("-DWIN");
291-
} else if env::consts::OS == "macos" {
323+
} else if target_os == "macos" {
292324
builder = builder.clang_arg("-DDARWIN");
293-
} else if env::consts::OS == "linux" {
325+
} else if target_os == "linux" {
294326
builder = builder.clang_arg("-DLINUX");
295-
} else if env::consts::OS == "android" {
327+
} else if target_os == "android" {
296328
builder = builder.clang_arg("-DLINUX");
297329
builder = builder.clang_arg("-DANDROID");
298330
}
@@ -365,11 +397,9 @@ fn pkg_config() -> Result<Vec<String>, Box<dyn Error>> {
365397

366398
let mut flags: Vec<String> = Vec::new();
367399

368-
for f in cfg_str.split(' ') {
369-
if let Some(include) = f.strip_prefix("-I") {
400+
for f in cfg_str.split_whitespace() {
401+
if f.starts_with("-I") {
370402
flags.push(String::from(f));
371-
372-
println!("cargo:include={include}");
373403
} else if let Some(path) = f.strip_prefix("-L") {
374404
println!("cargo:rustc-link-search=native={path}");
375405
} else if let Some(lib) = f.strip_prefix("-l") {
@@ -383,15 +413,12 @@ fn pkg_config() -> Result<Vec<String>, Box<dyn Error>> {
383413
}
384414

385415
fn setup_standalone(nss_dir: String) -> Vec<String> {
386-
setup_clang();
387-
388416
let nss = PathBuf::from(nss_dir);
389-
println!("cargo:rerun-if-env-changed={}", nss.display());
417+
println!("cargo:rerun-if-env-changed=NSS_DIR");
390418
println!("cargo:rerun-if-env-changed=NSS_PREBUILT");
391419

392420
// $NSS_DIR/../dist/
393421
let nssdist = nss.parent().unwrap().join("dist");
394-
println!("cargo:rerun-if-env-changed={}", nssdist.display());
395422
let nsstarget = "Release";
396423

397424
// If NSS_PREBUILT is set to a non-zero value, we assume that the NSS libraries are already
@@ -411,7 +438,7 @@ fn setup_standalone(nss_dir: String) -> Vec<String> {
411438
if env::var("CARGO_CFG_FUZZING").is_ok()
412439
|| env::var("PROFILE").unwrap_or_default() == "debug"
413440
// FIXME: NSPR doesn't build proper dynamic libraries on Windows.
414-
|| env::consts::OS == "windows"
441+
|| env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows"
415442
{
416443
static_link();
417444
} else {
@@ -537,7 +564,10 @@ fn process_config(config: &mut HashMap<String, Bindings>) {
537564
}
538565

539566
fn main() {
567+
println!("cargo:rerun-if-changed=src/min_version.rs");
568+
println!("cargo:rerun-if-changed=min_version.txt");
540569
println!("cargo:rustc-check-cfg=cfg(nss_nodb)");
570+
setup_clang();
541571

542572
let flags = if cfg!(feature = "gecko") {
543573
setup_for_gecko()

src/aead.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,95 @@ mod recprot {
361361
write!(f, "[NULL AEAD]")
362362
}
363363
}
364+
365+
#[cfg(test)]
366+
#[cfg_attr(coverage_nightly, coverage(off))]
367+
mod tests {
368+
use super::{AEAD_NULL_TAG, RecordProtection};
369+
370+
fn aead() -> RecordProtection {
371+
RecordProtection {}
372+
}
373+
374+
#[test]
375+
fn expansion() {
376+
assert_eq!(aead().expansion(), AEAD_NULL_TAG.len());
377+
}
378+
379+
#[test]
380+
fn debug() {
381+
assert_eq!(format!("{:?}", aead()), "[NULL AEAD]");
382+
}
383+
384+
#[test]
385+
fn encrypt_decrypt_roundtrip() {
386+
let a = aead();
387+
let plaintext = b"hello world";
388+
let mut out = vec![0u8; plaintext.len() + a.expansion()];
389+
let encrypted = a.encrypt(0, b"aad", plaintext, &mut out).unwrap();
390+
assert_eq!(encrypted.len(), plaintext.len() + a.expansion());
391+
assert_eq!(&encrypted[..plaintext.len()], plaintext);
392+
assert_eq!(&encrypted[plaintext.len()..], AEAD_NULL_TAG);
393+
394+
let mut dec_out = vec![0u8; plaintext.len()];
395+
let decrypted = a.decrypt(0, b"aad", encrypted, &mut dec_out).unwrap();
396+
assert_eq!(decrypted, plaintext);
397+
}
398+
399+
#[test]
400+
fn encrypt_in_place_roundtrip() {
401+
let a = aead();
402+
let plaintext = b"hello";
403+
let mut buf = plaintext.to_vec();
404+
buf.resize(plaintext.len() + a.expansion(), 0);
405+
let len = a.encrypt_in_place(0, b"", &mut buf).unwrap();
406+
assert_eq!(len, buf.len());
407+
assert_eq!(&buf[plaintext.len()..], AEAD_NULL_TAG);
408+
409+
let dec_len = a.decrypt_in_place(0, b"", &mut buf).unwrap();
410+
assert_eq!(dec_len, plaintext.len());
411+
assert_eq!(&buf[..dec_len], plaintext);
412+
}
413+
414+
#[test]
415+
fn decrypt_empty_plaintext() {
416+
// Zero-length plaintext (just the tag) is valid.
417+
let a = aead();
418+
let mut out = vec![0u8; a.expansion()];
419+
a.encrypt(0, b"", b"", &mut out).unwrap();
420+
let mut dec = vec![];
421+
let res = a.decrypt(0, b"", &out, &mut dec).unwrap();
422+
assert_eq!(res, b"");
423+
}
424+
425+
#[test]
426+
fn decrypt_fails_too_short() {
427+
let a = aead();
428+
let short = &AEAD_NULL_TAG[..a.expansion() - 1];
429+
assert!(a.decrypt(0, b"", short, &mut []).is_err());
430+
}
431+
432+
#[test]
433+
fn decrypt_fails_bad_tag() {
434+
let a = aead();
435+
let plaintext = b"test";
436+
let mut buf = vec![0u8; plaintext.len() + a.expansion()];
437+
a.encrypt(0, b"", plaintext, &mut buf).unwrap();
438+
// Corrupt the tag.
439+
let tag_start = plaintext.len();
440+
buf[tag_start] ^= 0xff;
441+
assert!(a.decrypt(0, b"", &buf, &mut []).is_err());
442+
}
443+
444+
#[test]
445+
fn decrypt_rejects_all_zero_data_bytes() {
446+
// All-zero plaintext with correct tag should fail (looks like padding).
447+
let a = aead();
448+
let mut buf = vec![0u8; 4 + a.expansion()];
449+
buf[4..].copy_from_slice(AEAD_NULL_TAG);
450+
assert!(a.decrypt(0, b"", &buf, &mut []).is_err());
451+
}
452+
}
364453
}
365454

366455
/// All the nonces are the same length. Exploit that.

src/constants.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,17 @@ remap_enum! {
165165
TLS_SIG_RSA_PSS_PSS_SHA512 = ssl_sig_rsa_pss_pss_sha512,
166166
}
167167
}
168+
169+
#[cfg(test)]
170+
#[cfg_attr(coverage_nightly, coverage(off))]
171+
mod tests {
172+
use super::Epoch;
173+
174+
#[test]
175+
fn epoch_into_usize() {
176+
assert_eq!(usize::from(Epoch::Initial), 0);
177+
assert_eq!(usize::from(Epoch::ZeroRtt), 1);
178+
assert_eq!(usize::from(Epoch::Handshake), 2);
179+
assert_eq!(usize::from(Epoch::ApplicationData), 3);
180+
}
181+
}

src/err.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub mod nspr {
2222
include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
2323
}
2424

25-
#[expect(dead_code, reason = "Some constants are not used.")]
25+
#[cfg_attr(not(test), expect(dead_code, reason = "Some constants are not used."))]
2626
pub mod mozpkix {
2727
// These are manually extracted from the many bindings generated
2828
// by bindgen when provided with the simple header:
@@ -306,4 +306,35 @@ mod tests {
306306
Error::String
307307
);
308308
}
309+
310+
#[test]
311+
fn mozpkix_error_codes_are_negative() {
312+
use crate::err::mozpkix::*;
313+
let codes = [
314+
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE,
315+
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY,
316+
MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE,
317+
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA,
318+
MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH,
319+
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE,
320+
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE,
321+
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH,
322+
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING,
323+
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG,
324+
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING,
325+
MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING,
326+
MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME,
327+
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED,
328+
MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,
329+
MOZILLA_PKIX_ERROR_MITM_DETECTED,
330+
END_OF_LIST,
331+
];
332+
for (i, &code) in codes.iter().enumerate() {
333+
assert_eq!(
334+
code,
335+
-16384 + i32::try_from(i).unwrap(),
336+
"mozpkix code at index {i}"
337+
);
338+
}
339+
}
309340
}

0 commit comments

Comments
 (0)