180 lines
5.2 KiB
Rust
180 lines
5.2 KiB
Rust
#[path = "../src/tls_binding.rs"]
|
|
mod tls_binding;
|
|
|
|
use sha2::{Digest, Sha224, Sha256, Sha384, Sha512};
|
|
use std::{path::Path, process::Command};
|
|
use tls_binding::server_end_point;
|
|
|
|
fn openssl(dir: &Path, args: &[&str]) {
|
|
let output = Command::new("openssl")
|
|
.current_dir(dir)
|
|
.args(args)
|
|
.output()
|
|
.expect("openssl must be installed for TLS certificate tests");
|
|
assert!(
|
|
output.status.success(),
|
|
"openssl {args:?}: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
fn certificate(dir: &Path, digest: &str, pss: bool) -> Vec<u8> {
|
|
let mut args = vec![
|
|
"req",
|
|
"-new",
|
|
"-x509",
|
|
"-key",
|
|
"key.pem",
|
|
"-subj",
|
|
"/CN=binding.test",
|
|
"-days",
|
|
"1",
|
|
"-outform",
|
|
"DER",
|
|
"-out",
|
|
"cert.der",
|
|
digest,
|
|
];
|
|
if pss {
|
|
args.extend([
|
|
"-sigopt",
|
|
"rsa_padding_mode:pss",
|
|
"-sigopt",
|
|
"rsa_pss_saltlen:digest",
|
|
]);
|
|
}
|
|
openssl(dir, &args);
|
|
std::fs::read(dir.join("cert.der")).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn rsa_signature_digest_and_weak_hash_upgrade() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
openssl(
|
|
dir.path(),
|
|
&[
|
|
"genpkey",
|
|
"-algorithm",
|
|
"RSA",
|
|
"-pkeyopt",
|
|
"rsa_keygen_bits:2048",
|
|
"-out",
|
|
"key.pem",
|
|
],
|
|
);
|
|
for digest in ["-md5", "-sha1", "-sha224", "-sha256", "-sha384", "-sha512"] {
|
|
let cert = certificate(dir.path(), digest, false);
|
|
let expected = match digest {
|
|
"-sha224" => Sha224::digest(&cert).to_vec(),
|
|
"-sha384" => Sha384::digest(&cert).to_vec(),
|
|
"-sha512" => Sha512::digest(&cert).to_vec(),
|
|
_ => Sha256::digest(&cert).to_vec(),
|
|
};
|
|
assert_eq!(server_end_point(&cert).unwrap(), expected, "{digest}");
|
|
}
|
|
for digest in ["-sha1", "-sha224", "-sha256", "-sha384", "-sha512"] {
|
|
let cert = certificate(dir.path(), digest, true);
|
|
let expected = match digest {
|
|
"-sha224" => Sha224::digest(&cert).to_vec(),
|
|
"-sha384" => Sha384::digest(&cert).to_vec(),
|
|
"-sha512" => Sha512::digest(&cert).to_vec(),
|
|
_ => Sha256::digest(&cert).to_vec(),
|
|
};
|
|
assert_eq!(server_end_point(&cert).unwrap(), expected, "PSS {digest}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ecdsa_uses_certificate_signature_hash() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
openssl(
|
|
dir.path(),
|
|
&[
|
|
"genpkey",
|
|
"-algorithm",
|
|
"EC",
|
|
"-pkeyopt",
|
|
"ec_paramgen_curve:P-256",
|
|
"-out",
|
|
"key.pem",
|
|
],
|
|
);
|
|
let cert = certificate(dir.path(), "-sha384", false);
|
|
assert_eq!(
|
|
server_end_point(&cert).unwrap(),
|
|
Sha384::digest(&cert).to_vec()
|
|
);
|
|
for end in 0..cert.len() {
|
|
assert!(
|
|
server_end_point(&cert[..end]).is_err(),
|
|
"accepted truncation at {end}"
|
|
);
|
|
}
|
|
let mut trailing = cert;
|
|
trailing.push(0);
|
|
assert!(server_end_point(&trailing).is_err());
|
|
}
|
|
|
|
fn tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
|
assert!(value.len() < 128);
|
|
let mut result = vec![tag, value.len() as u8];
|
|
result.extend(value);
|
|
result
|
|
}
|
|
fn envelope(algorithm: &[u8]) -> Vec<u8> {
|
|
let mut contents = tlv(0x30, &[]);
|
|
contents.extend(tlv(0x30, algorithm));
|
|
contents.extend(tlv(0x03, &[0, 1]));
|
|
tlv(0x30, &contents)
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_and_eddsa_algorithms_fail_closed() {
|
|
// Ed25519/Ed448 do not get a guessed SHA-256 binding.
|
|
for oid in [&[0x2b, 0x65, 0x70][..], &[0x2b, 0x65, 0x71], &[0x2a, 3, 4]] {
|
|
assert!(server_end_point(&envelope(&tlv(0x06, oid))).is_err());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_der_and_pss_parameters_fail_closed() {
|
|
for bytes in [
|
|
vec![],
|
|
vec![0x30, 0x80, 0, 0],
|
|
vec![0x30, 0x81, 0],
|
|
vec![0x30, 0xff],
|
|
vec![0x30, 0x88, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
|
] {
|
|
assert!(server_end_point(&bytes).is_err());
|
|
}
|
|
let pss_oid = tlv(0x06, b"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a");
|
|
assert!(server_end_point(&envelope(&pss_oid)).is_err());
|
|
let mut defaults = pss_oid.clone();
|
|
defaults.extend(tlv(0x30, &[]));
|
|
let cert = envelope(&defaults);
|
|
assert_eq!(
|
|
server_end_point(&cert).unwrap(),
|
|
Sha256::digest(&cert).to_vec()
|
|
);
|
|
let hash = tlv(0x30, &tlv(0x06, b"\x60\x86\x48\x01\x65\x03\x04\x02\x02"));
|
|
let sha384 = tlv(0xa0, &hash);
|
|
// SHA384 with default MGF1-SHA1 has multiple hash functions.
|
|
let mut mismatch = pss_oid.clone();
|
|
mismatch.extend(tlv(0x30, &sha384));
|
|
assert!(server_end_point(&envelope(&mismatch)).is_err());
|
|
for params in [
|
|
vec![0xa4, 0],
|
|
vec![0xa0, 0],
|
|
vec![0xa2, 3, 2, 1, 0xff],
|
|
vec![0xa3, 3, 2, 1, 2],
|
|
[sha384.clone(), sha384].concat(),
|
|
] {
|
|
let mut algorithm = pss_oid.clone();
|
|
algorithm.extend(tlv(0x30, ¶ms));
|
|
assert!(server_end_point(&envelope(&algorithm)).is_err());
|
|
}
|
|
let mut invalid_oid = tlv(0x06, &[0x80, 0x2a]);
|
|
invalid_oid.extend([5, 0]);
|
|
assert!(server_end_point(&envelope(&invalid_oid)).is_err());
|
|
}
|