// Copyright 2022 Joseph Birr-Pixton.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#![cfg(all(feature = "alloc", any(feature = "ring", feature = "aws-lc-rs")))]

use core::time::Duration;

use pki_types::{CertificateDer, ServerName, UnixTime};
use webpki::{InvalidNameContext, KeyUsage, anchor_from_trusted_cert};

#[track_caller]
fn check_cert(
    ee: &[u8],
    ca: &[u8],
    valid_names: &[&str],
    invalid_names: &[&str],
    presented_names: &[&str],
) -> Result<(), webpki::Error> {
    let ca_cert_der = CertificateDer::from(ca);
    let anchors = [anchor_from_trusted_cert(&ca_cert_der).unwrap()];

    let ee_der = CertificateDer::from(ee);
    let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d));
    let cert = webpki::EndEntityCert::try_from(&ee_der).unwrap();
    cert.verify_for_usage(
        webpki::ALL_VERIFICATION_ALGS,
        &anchors,
        &[],
        time,
        KeyUsage::server_auth(),
        None,
        None,
    )?;

    for valid in valid_names {
        let name = ServerName::try_from(*valid).unwrap();
        assert_eq!(cert.verify_is_valid_for_subject_name(&name), Ok(()));
    }

    for invalid in invalid_names {
        let name = ServerName::try_from(*invalid).unwrap();
        assert_eq!(
            cert.verify_is_valid_for_subject_name(&name),
            Err(webpki::Error::CertNotValidForName(InvalidNameContext {
                expected: name.to_owned(),
                presented: presented_names.iter().map(|n| n.to_string()).collect(),
            }))
        );
    }

    Ok(())
}

// DO NOT EDIT BELOW: generated by tests/generate.py

#[test]
fn no_name_constraints() {
    let ee = include_bytes!("tls_server_certs/no_name_constraints.ee.der");
    let ca = include_bytes!("tls_server_certs/no_name_constraints.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["dns.example.com"],
            &["subject.example.com"],
            &["DnsName(\"dns.example.com\")"]
        ),
        Ok(())
    );
}

#[test]
fn additional_dns_labels() {
    let ee = include_bytes!("tls_server_certs/additional_dns_labels.ee.der");
    let ca = include_bytes!("tls_server_certs/additional_dns_labels.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["host1.example.com", "host2.example.com"],
            &["subject.example.com"],
            &[
                "DnsName(\"host1.example.com\")",
                "DnsName(\"host2.example.com\")"
            ]
        ),
        Ok(())
    );
}

#[test]
fn disallow_dns_san() {
    let ee = include_bytes!("tls_server_certs/disallow_dns_san.ee.der");
    let ca = include_bytes!("tls_server_certs/disallow_dns_san.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &["DnsName(\"disallowed.example.com\")"]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn allow_subject_common_name() {
    let ee = include_bytes!("tls_server_certs/allow_subject_common_name.ee.der");
    let ca = include_bytes!("tls_server_certs/allow_subject_common_name.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &["allowed.example.com"], &[]),
        Ok(())
    );
}

#[test]
fn allow_dns_san() {
    let ee = include_bytes!("tls_server_certs/allow_dns_san.ee.der");
    let ca = include_bytes!("tls_server_certs/allow_dns_san.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["allowed.example.com"],
            &[],
            &["DnsName(\"allowed.example.com\")"]
        ),
        Ok(())
    );
}

#[test]
fn allow_dns_san_and_subject_common_name() {
    let ee = include_bytes!("tls_server_certs/allow_dns_san_and_subject_common_name.ee.der");
    let ca = include_bytes!("tls_server_certs/allow_dns_san_and_subject_common_name.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["allowed-san.example.com"],
            &["allowed-cn.example.com"],
            &["DnsName(\"allowed-san.example.com\")"]
        ),
        Ok(())
    );
}

#[test]
fn disallow_dns_san_and_allow_subject_common_name() {
    let ee =
        include_bytes!("tls_server_certs/disallow_dns_san_and_allow_subject_common_name.ee.der");
    let ca =
        include_bytes!("tls_server_certs/disallow_dns_san_and_allow_subject_common_name.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &[],
            &[],
            &[
                "DnsName(\"allowed-san.example.com\")",
                "DnsName(\"disallowed-san.example.com\")"
            ]
        ),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn we_incorrectly_ignore_name_constraints_on_name_in_subject() {
    let ee = include_bytes!(
        "tls_server_certs/we_incorrectly_ignore_name_constraints_on_name_in_subject.ee.der"
    );
    let ca = include_bytes!(
        "tls_server_certs/we_incorrectly_ignore_name_constraints_on_name_in_subject.ca.der"
    );
    assert_eq!(check_cert(ee, ca, &[], &[], &[]), Ok(()));
}

#[test]
fn reject_constraints_on_unimplemented_names() {
    let ee = include_bytes!("tls_server_certs/reject_constraints_on_unimplemented_names.ee.der");
    let ca = include_bytes!("tls_server_certs/reject_constraints_on_unimplemented_names.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &[]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn we_ignore_constraints_on_names_that_do_not_appear_in_cert() {
    let ee = include_bytes!(
        "tls_server_certs/we_ignore_constraints_on_names_that_do_not_appear_in_cert.ee.der"
    );
    let ca = include_bytes!(
        "tls_server_certs/we_ignore_constraints_on_names_that_do_not_appear_in_cert.ca.der"
    );
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["notexample.com"],
            &["example.com"],
            &["DnsName(\"notexample.com\")"]
        ),
        Ok(())
    );
}

#[test]
fn wildcard_san_accepted_if_in_subtree() {
    let ee = include_bytes!("tls_server_certs/wildcard_san_accepted_if_in_subtree.ee.der");
    let ca = include_bytes!("tls_server_certs/wildcard_san_accepted_if_in_subtree.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["bob.example.com", "jane.example.com"],
            &["example.com", "uh.oh.example.com"],
            &["DnsName(\"*.example.com\")"]
        ),
        Ok(())
    );
}

#[test]
fn wildcard_san_rejected_if_in_excluded_subtree() {
    let ee = include_bytes!("tls_server_certs/wildcard_san_rejected_if_in_excluded_subtree.ee.der");
    let ca = include_bytes!("tls_server_certs/wildcard_san_rejected_if_in_excluded_subtree.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &["DnsName(\"*.example.com\")"]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn ip4_address_san_rejected_if_in_excluded_subtree() {
    let ee =
        include_bytes!("tls_server_certs/ip4_address_san_rejected_if_in_excluded_subtree.ee.der");
    let ca =
        include_bytes!("tls_server_certs/ip4_address_san_rejected_if_in_excluded_subtree.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &["IpAddress(12.34.56.78)"]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn ip4_address_san_allowed_if_outside_excluded_subtree() {
    let ee = include_bytes!(
        "tls_server_certs/ip4_address_san_allowed_if_outside_excluded_subtree.ee.der"
    );
    let ca = include_bytes!(
        "tls_server_certs/ip4_address_san_allowed_if_outside_excluded_subtree.ca.der"
    );
    assert_eq!(
        check_cert(ee, ca, &["12.34.56.78"], &[], &["IpAddress(12.34.56.78)"]),
        Ok(())
    );
}

#[test]
fn ip4_address_san_rejected_if_excluded_is_sparse_cidr_mask() {
    let ee = include_bytes!(
        "tls_server_certs/ip4_address_san_rejected_if_excluded_is_sparse_cidr_mask.ee.der"
    );
    let ca = include_bytes!(
        "tls_server_certs/ip4_address_san_rejected_if_excluded_is_sparse_cidr_mask.ca.der"
    );
    assert_eq!(
        check_cert(ee, ca, &[], &[], &["IpAddress(12.34.56.79)"]),
        Err(webpki::Error::InvalidNetworkMaskConstraint)
    );
}

#[test]
fn ip4_address_san_allowed() {
    let ee = include_bytes!("tls_server_certs/ip4_address_san_allowed.ee.der");
    let ca = include_bytes!("tls_server_certs/ip4_address_san_allowed.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["12.34.56.78"],
            &[
                "12.34.56.77",
                "12.34.56.79",
                "0000:0000:0000:0000:0000:ffff:0c22:384e"
            ],
            &["IpAddress(12.34.56.78)"]
        ),
        Ok(())
    );
}

#[test]
fn ip6_address_san_rejected_if_in_excluded_subtree() {
    let ee =
        include_bytes!("tls_server_certs/ip6_address_san_rejected_if_in_excluded_subtree.ee.der");
    let ca =
        include_bytes!("tls_server_certs/ip6_address_san_rejected_if_in_excluded_subtree.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &["IpAddress(2001:db8::1)"]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn ip6_address_san_allowed_if_outside_excluded_subtree() {
    let ee = include_bytes!(
        "tls_server_certs/ip6_address_san_allowed_if_outside_excluded_subtree.ee.der"
    );
    let ca = include_bytes!(
        "tls_server_certs/ip6_address_san_allowed_if_outside_excluded_subtree.ca.der"
    );
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["2001:0db9:0000:0000:0000:0000:0000:0001"],
            &[],
            &["IpAddress(2001:db9::1)"]
        ),
        Ok(())
    );
}

#[test]
fn ip6_address_san_allowed() {
    let ee = include_bytes!("tls_server_certs/ip6_address_san_allowed.ee.der");
    let ca = include_bytes!("tls_server_certs/ip6_address_san_allowed.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["2001:0db9:0000:0000:0000:0000:0000:0001"],
            &["12.34.56.78"],
            &["IpAddress(2001:db9::1)"]
        ),
        Ok(())
    );
}

#[test]
fn ip46_mixed_address_san_allowed() {
    let ee = include_bytes!("tls_server_certs/ip46_mixed_address_san_allowed.ee.der");
    let ca = include_bytes!("tls_server_certs/ip46_mixed_address_san_allowed.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["12.34.56.78", "2001:0db9:0000:0000:0000:0000:0000:0001"],
            &[
                "12.34.56.77",
                "12.34.56.79",
                "0000:0000:0000:0000:0000:ffff:0c22:384e"
            ],
            &["IpAddress(12.34.56.78)", "IpAddress(2001:db9::1)"]
        ),
        Ok(())
    );
}

#[test]
fn permit_directory_name_not_implemented() {
    let ee = include_bytes!("tls_server_certs/permit_directory_name_not_implemented.ee.der");
    let ca = include_bytes!("tls_server_certs/permit_directory_name_not_implemented.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &[]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn exclude_directory_name_not_implemented() {
    let ee = include_bytes!("tls_server_certs/exclude_directory_name_not_implemented.ee.der");
    let ca = include_bytes!("tls_server_certs/exclude_directory_name_not_implemented.ca.der");
    assert_eq!(
        check_cert(ee, ca, &[], &[], &[]),
        Err(webpki::Error::NameConstraintViolation)
    );
}

#[test]
fn invalid_dns_name_matching() {
    let ee = include_bytes!("tls_server_certs/invalid_dns_name_matching.ee.der");
    let ca = include_bytes!("tls_server_certs/invalid_dns_name_matching.ca.der");
    assert_eq!(
        check_cert(
            ee,
            ca,
            &["dns.example.com"],
            &[],
            &[
                "DnsName(\"{invalid}.example.com\")",
                "DnsName(\"dns.example.com\")"
            ]
        ),
        Ok(())
    );
}
