//! Tests for configuring and using a [`ClientCertVerifier`] for a server.

#![cfg(feature = "dangerous_configuration")]

mod common;

use crate::common::{
    dns_name, do_handshake_until_both_error, do_handshake_until_error, get_client_root_store,
    make_client_config_with_versions, make_client_config_with_versions_with_auth,
    make_pair_for_arc_configs, ErrorFromPeer, KeyType, ALL_KEY_TYPES,
};
use rustls::{
    client::WebPkiVerifier,
    internal::msgs::enums::{AlertDescription, ContentType},
    server::{ClientCertVerified, ClientCertVerifier},
    Certificate, ClientConnection, DistinguishedNames, Error, ServerConfig, ServerConnection,
    SignatureScheme,
};
use std::sync::Arc;

// Client is authorized!
fn ver_ok() -> Result<ClientCertVerified, Error> {
    Ok(rustls::server::ClientCertVerified::assertion())
}

// Use when we shouldn't even attempt verification
fn ver_unreachable() -> Result<ClientCertVerified, Error> {
    unreachable!()
}

// Verifier that returns an error that we can expect
fn ver_err() -> Result<ClientCertVerified, Error> {
    Err(Error::General("test err".to_string()))
}

fn server_config_with_verifier(
    kt: KeyType,
    client_cert_verifier: MockClientVerifier,
) -> ServerConfig {
    ServerConfig::builder()
        .with_safe_defaults()
        .with_client_cert_verifier(Arc::new(client_cert_verifier))
        .with_single_cert(kt.get_chain(), kt.get_key())
        .unwrap()
}

#[test]
// Happy path, we resolve to a root, it is verified OK, should be able to connect
fn client_verifier_works() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_ok,
            subjects: Some(get_client_root_store(*kt).subjects()),
            mandatory: Some(true),
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions_with_auth(*kt, &[version]);
            let (mut client, mut server) =
                make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config);
            let err = do_handshake_until_error(&mut client, &mut server);
            assert_eq!(err, Ok(()));
        }
    }
}

// Server offers no verification schemes
#[test]
fn client_verifier_no_schemes() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_ok,
            subjects: Some(get_client_root_store(*kt).subjects()),
            mandatory: Some(true),
            offered_schemes: Some(vec![]),
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions_with_auth(*kt, &[version]);
            let (mut client, mut server) =
                make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config);
            let err = do_handshake_until_error(&mut client, &mut server);
            assert_eq!(
                err,
                Err(ErrorFromPeer::Client(Error::CorruptMessagePayload(
                    ContentType::Handshake
                )))
            );
        }
    }
}

// Common case, we do not find a root store to resolve to
#[test]
fn client_verifier_no_root() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_ok,
            subjects: None,
            mandatory: Some(true),
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions_with_auth(*kt, &[version]);
            let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
            let mut client =
                ClientConnection::new(Arc::new(client_config), dns_name("notlocalhost")).unwrap();
            let errs = do_handshake_until_both_error(&mut client, &mut server);
            assert_eq!(
                errs,
                Err(vec![
                    ErrorFromPeer::Server(Error::General(
                        "client rejected by client_auth_root_subjects".into()
                    )),
                    ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied))
                ])
            );
        }
    }
}

// If we cannot resolve a root, we cannot decide if auth is mandatory
#[test]
fn client_verifier_no_auth_no_root() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_unreachable,
            subjects: None,
            mandatory: Some(true),
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions(*kt, &[version]);
            let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
            let mut client =
                ClientConnection::new(Arc::new(client_config), dns_name("notlocalhost")).unwrap();
            let errs = do_handshake_until_both_error(&mut client, &mut server);
            assert_eq!(
                errs,
                Err(vec![
                    ErrorFromPeer::Server(Error::General(
                        "client rejected by client_auth_root_subjects".into()
                    )),
                    ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied))
                ])
            );
        }
    }
}

// If we do have a root, we must do auth
#[test]
fn client_verifier_no_auth_yes_root() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_unreachable,
            subjects: Some(get_client_root_store(*kt).subjects()),
            mandatory: Some(true),
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions(*kt, &[version]);
            let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
            let mut client =
                ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap();
            let errs = do_handshake_until_both_error(&mut client, &mut server);
            assert_eq!(
                errs,
                Err(vec![
                    ErrorFromPeer::Server(Error::NoCertificatesPresented),
                    ErrorFromPeer::Client(Error::AlertReceived(
                        AlertDescription::CertificateRequired
                    ))
                ])
            );
        }
    }
}

#[test]
// Triple checks we propagate the rustls::Error through
fn client_verifier_fails_properly() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_err,
            subjects: Some(get_client_root_store(*kt).subjects()),
            mandatory: Some(true),
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions_with_auth(*kt, &[version]);
            let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
            let mut client =
                ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap();
            let err = do_handshake_until_error(&mut client, &mut server);
            assert_eq!(
                err,
                Err(ErrorFromPeer::Server(Error::General("test err".into())))
            );
        }
    }
}

#[test]
// If a verifier returns a None on Mandatory-ness, then we error out
fn client_verifier_must_determine_client_auth_requirement_to_continue() {
    for kt in ALL_KEY_TYPES.iter() {
        let client_verifier = MockClientVerifier {
            verified: ver_ok,
            subjects: Some(get_client_root_store(*kt).subjects()),
            mandatory: None,
            offered_schemes: None,
        };

        let server_config = server_config_with_verifier(*kt, client_verifier);
        let server_config = Arc::new(server_config);

        for version in rustls::ALL_VERSIONS {
            let client_config = make_client_config_with_versions_with_auth(*kt, &[version]);
            let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
            let mut client =
                ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap();
            let errs = do_handshake_until_both_error(&mut client, &mut server);
            assert_eq!(
                errs,
                Err(vec![
                    ErrorFromPeer::Server(Error::General(
                        "client rejected by client_auth_mandatory".into()
                    )),
                    ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied))
                ])
            );
        }
    }
}

pub struct MockClientVerifier {
    pub verified: fn() -> Result<ClientCertVerified, Error>,
    pub subjects: Option<DistinguishedNames>,
    pub mandatory: Option<bool>,
    pub offered_schemes: Option<Vec<SignatureScheme>>,
}

impl ClientCertVerifier for MockClientVerifier {
    fn client_auth_mandatory(&self) -> Option<bool> {
        self.mandatory
    }

    fn client_auth_root_subjects(&self) -> Option<DistinguishedNames> {
        self.subjects.as_ref().cloned()
    }

    fn verify_client_cert(
        &self,
        _end_entity: &Certificate,
        _intermediates: &[Certificate],
        _now: std::time::SystemTime,
    ) -> Result<ClientCertVerified, Error> {
        (self.verified)()
    }

    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
        if let Some(schemes) = &self.offered_schemes {
            schemes.clone()
        } else {
            WebPkiVerifier::verification_schemes()
        }
    }
}
