11use crate :: kumod:: { DaemonWithMaildir , DeliverySummary , MailGenParams } ;
22use k9:: assert_equal;
33use kumo_log_types:: RecordType ;
4- use rustls_cert_gen:: { Ca , CertificateBuilder , EndEntity } ;
4+ use rcgen:: {
5+ BasicConstraints , CertificateParams , CertifiedIssuer , DistinguishedName , DnType ,
6+ ExtendedKeyUsagePurpose , IsCa , KeyPair , KeyUsagePurpose ,
7+ } ;
58use std:: collections:: BTreeMap ;
69use std:: time:: Duration ;
710
@@ -39,17 +42,15 @@ async fn tls_client_certificate_openssl_no_client_cert() -> anyhow::Result<()> {
3942/// Use rustls as TLS library and confirm delivery is successful
4043#[ tokio:: test]
4144async fn tls_client_certificate_rustls_success ( ) -> anyhow:: Result < ( ) > {
42- let ( ca, entity) = generate_certs ( ) ?;
43- let ca_pem = ca. serialize_pem ( ) . cert_pem ;
44- let cert_pem = entity. serialize_pem ( ) . cert_pem ;
45- let key_pem = entity. serialize_pem ( ) . private_key_pem ;
45+ let ( ca_pem, intermediate_pem, entity_pem, key_pem) = generate_certs ( ) ?;
46+ let cert_pem = format ! ( "{entity_pem}\n {intermediate_pem}" ) ;
4647 let ex = DeliverySummary {
4748 source_counts : BTreeMap :: from ( [ ( RecordType :: Reception , 1 ) , ( RecordType :: Delivery , 1 ) ] ) ,
4849 sink_counts : BTreeMap :: from ( [ ( RecordType :: Reception , 1 ) , ( RecordType :: Delivery , 1 ) ] ) ,
4950 } ;
5051 let env = vec ! [
5152 ( "KUMOD_ENABLE_TLS" , "OpportunisticInsecure" ) ,
52- ( "KUMOD_PREFER_OPENSSL" , "true " ) ,
53+ ( "KUMOD_PREFER_OPENSSL" , "false " ) ,
5354 ( "KUMOD_CLIENT_CERTIFICATE" , & cert_pem) ,
5455 ( "KUMOD_CLIENT_PRIVATE_KEY" , & key_pem) ,
5556 ( "KUMOD_CLIENT_REQUIRED_CA" , & ca_pem) ,
@@ -60,8 +61,7 @@ async fn tls_client_certificate_rustls_success() -> anyhow::Result<()> {
6061/// Adding fake private key to confirm rustls injection succeeds
6162#[ tokio:: test]
6263async fn tls_client_certificate_rustls_fail ( ) -> anyhow:: Result < ( ) > {
63- let ( _ca, entity) = generate_certs ( ) ?;
64- let cert_pem = entity. serialize_pem ( ) . cert_pem ;
64+ let ( _ca_pem, _intermediate_pem, cert_pem, _key_pem) = generate_certs ( ) ?;
6565 let ex = DeliverySummary {
6666 source_counts : BTreeMap :: from ( [
6767 ( RecordType :: Reception , 1 ) ,
@@ -79,33 +79,72 @@ async fn tls_client_certificate_rustls_fail() -> anyhow::Result<()> {
7979
8080const COMMON_NAME : & str = "Testing Common Name" ;
8181
82- fn generate_certs ( ) -> anyhow:: Result < ( Ca , EndEntity ) > {
83- let ca = CertificateBuilder :: new ( )
84- . certificate_authority ( )
85- . country_name ( "GB" ) ?
86- . organization_name ( "kumo-testing" )
87- . build ( ) ?;
82+ fn generate_certs ( ) -> anyhow:: Result < ( String , String , String , String ) > {
83+ // Root CA
84+ let mut root_params = CertificateParams :: default ( ) ;
85+ root_params. distinguished_name = DistinguishedName :: new ( ) ;
86+ root_params
87+ . distinguished_name
88+ . push ( DnType :: CountryName , "GB" ) ;
89+ root_params
90+ . distinguished_name
91+ . push ( DnType :: OrganizationName , "kumo-testing" ) ;
92+ root_params. is_ca = IsCa :: Ca ( BasicConstraints :: Unconstrained ) ;
93+ root_params. key_usages = vec ! [
94+ KeyUsagePurpose :: DigitalSignature ,
95+ KeyUsagePurpose :: KeyCertSign ,
96+ KeyUsagePurpose :: CrlSign ,
97+ ] ;
98+ let root_key = KeyPair :: generate ( ) ?;
99+ let root_ca = CertifiedIssuer :: self_signed ( root_params, root_key) ?;
100+
101+ // Intermediate CA (signed by root)
102+ let mut intermediate_params = CertificateParams :: default ( ) ;
103+ intermediate_params. distinguished_name = DistinguishedName :: new ( ) ;
104+ intermediate_params
105+ . distinguished_name
106+ . push ( DnType :: CountryName , "GB" ) ;
107+ intermediate_params
108+ . distinguished_name
109+ . push ( DnType :: OrganizationName , "kumo-intermediate-testing" ) ;
110+ intermediate_params. is_ca = IsCa :: Ca ( BasicConstraints :: Unconstrained ) ;
111+ intermediate_params. key_usages = vec ! [
112+ KeyUsagePurpose :: DigitalSignature ,
113+ KeyUsagePurpose :: KeyCertSign ,
114+ KeyUsagePurpose :: CrlSign ,
115+ ] ;
116+ intermediate_params. use_authority_key_identifier_extension = true ;
117+ let intermediate_key = KeyPair :: generate ( ) ?;
118+ let intermediate_ca =
119+ CertifiedIssuer :: signed_by ( intermediate_params, intermediate_key, & root_ca) ?;
88120
89- let mut entity = CertificateBuilder :: new ( )
90- . end_entity ( )
91- . common_name ( COMMON_NAME )
92- . subject_alternative_names ( vec ! [ rcgen:: SanType :: DnsName (
93- "smtp.example.com" . try_into( ) . unwrap( ) ,
94- ) ] ) ;
95- entity. client_auth ( ) ;
121+ // End entity certificate for client authentication (signed by intermediate)
122+ let mut entity_params = CertificateParams :: new ( vec ! [ "smtp.example.com" . to_string( ) ] ) ?;
123+ entity_params. distinguished_name = DistinguishedName :: new ( ) ;
124+ entity_params
125+ . distinguished_name
126+ . push ( DnType :: CommonName , COMMON_NAME ) ;
127+ entity_params. is_ca = IsCa :: NoCa ;
128+ entity_params. key_usages = vec ! [ KeyUsagePurpose :: DigitalSignature ] ;
129+ entity_params. extended_key_usages = vec ! [ ExtendedKeyUsagePurpose :: ClientAuth ] ;
130+ entity_params. use_authority_key_identifier_extension = true ;
96131
97- let entity = entity. build ( & ca) ?;
132+ let entity_key = KeyPair :: generate ( ) ?;
133+ let entity = entity_params. signed_by ( & entity_key, & intermediate_ca) ?;
98134
99- Ok ( ( ca, entity) )
135+ Ok ( (
136+ root_ca. pem ( ) ,
137+ intermediate_ca. pem ( ) ,
138+ entity. pem ( ) ,
139+ entity_key. serialize_pem ( ) ,
140+ ) )
100141}
101142
102143/// Use openssl as TLS library, confirm delivery succeeds
103144#[ tokio:: test]
104145async fn tls_client_certificate_rustls_openssl_success ( ) -> anyhow:: Result < ( ) > {
105- let ( ca, entity) = generate_certs ( ) ?;
106- let ca_pem = ca. serialize_pem ( ) . cert_pem ;
107- let cert_pem = entity. serialize_pem ( ) . cert_pem ;
108- let key_pem = entity. serialize_pem ( ) . private_key_pem ;
146+ let ( ca_pem, intermediate_pem, entity_pem, key_pem) = generate_certs ( ) ?;
147+ let cert_pem = format ! ( "{entity_pem}\n {intermediate_pem}" ) ;
109148
110149 let ex = DeliverySummary {
111150 source_counts : BTreeMap :: from ( [ ( RecordType :: Reception , 1 ) , ( RecordType :: Delivery , 1 ) ] ) ,
@@ -122,11 +161,56 @@ async fn tls_client_certificate_rustls_openssl_success() -> anyhow::Result<()> {
122161 tls_client_certificate ( env, ex) . await
123162}
124163
164+ /// Use openssl as TLS library, confirm we're failing to deliver due to missing intermediate certificate in the chain.
165+ #[ tokio:: test]
166+ async fn tls_client_certificate_rustls_openssl_missing_intermediate ( ) -> anyhow:: Result < ( ) > {
167+ let ( ca_pem, _intermediate_pem, entity_pem, key_pem) = generate_certs ( ) ?;
168+
169+ let ex = DeliverySummary {
170+ source_counts : BTreeMap :: from ( [
171+ ( RecordType :: Reception , 1 ) ,
172+ ( RecordType :: TransientFailure , 1 ) ,
173+ ] ) ,
174+ sink_counts : BTreeMap :: new ( ) ,
175+ } ;
176+
177+ let env = vec ! [
178+ ( "KUMOD_ENABLE_TLS" , "OpportunisticInsecure" ) ,
179+ ( "KUMOD_PREFER_OPENSSL" , "true" ) ,
180+ ( "KUMOD_CLIENT_CERTIFICATE" , & entity_pem) ,
181+ ( "KUMOD_CLIENT_PRIVATE_KEY" , & key_pem) ,
182+ ( "KUMOD_CLIENT_REQUIRED_CA" , & ca_pem) ,
183+ ] ;
184+ tls_client_certificate ( env, ex) . await
185+ }
186+
187+ /// Use rustls as TLS library, confirm we're failing to deliver due to missing intermediate certificate in the chain.
188+ #[ tokio:: test]
189+ async fn tls_client_certificate_rustls_missing_intermediate ( ) -> anyhow:: Result < ( ) > {
190+ let ( ca_pem, _intermediate_pem, entity_pem, key_pem) = generate_certs ( ) ?;
191+
192+ let ex = DeliverySummary {
193+ source_counts : BTreeMap :: from ( [
194+ ( RecordType :: Reception , 1 ) ,
195+ ( RecordType :: TransientFailure , 1 ) ,
196+ ] ) ,
197+ sink_counts : BTreeMap :: new ( ) ,
198+ } ;
199+
200+ let env = vec ! [
201+ ( "KUMOD_ENABLE_TLS" , "OpportunisticInsecure" ) ,
202+ ( "KUMOD_PREFER_OPENSSL" , "false" ) ,
203+ ( "KUMOD_CLIENT_CERTIFICATE" , & entity_pem) ,
204+ ( "KUMOD_CLIENT_PRIVATE_KEY" , & key_pem) ,
205+ ( "KUMOD_CLIENT_REQUIRED_CA" , & ca_pem) ,
206+ ] ;
207+ tls_client_certificate ( env, ex) . await
208+ }
209+
125210/// Adding fake private key to confirm openssl injection would temp fail
126211#[ tokio:: test]
127212async fn tls_client_certificate_rustls_openssl_fail ( ) -> anyhow:: Result < ( ) > {
128- let ( _ca, entity) = generate_certs ( ) ?;
129- let cert_pem = entity. serialize_pem ( ) . cert_pem ;
213+ let ( _ca_pem, _intermediate_pem, cert_pem, _key_pem) = generate_certs ( ) ?;
130214 let ex = DeliverySummary {
131215 source_counts : BTreeMap :: from ( [
132216 ( RecordType :: Reception , 1 ) ,
0 commit comments