diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 48e7da4e08..a4200c46fb 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -19,6 +19,9 @@ qlog = ["quiche/qlog"] # Use BoringSSL provided by the boring crate. boringssl-boring-crate = ["quiche/boringssl-boring-crate"] +rustls-aws-lc-rs = ["quiche/rustls-aws-lc-rs"] +rustls-ring = ["quiche/rustls-ring"] + # Enable sfv support. sfv = ["quiche/sfv"] diff --git a/quiche/Cargo.toml b/quiche/Cargo.toml index 7b59c60e58..8c947d0e95 100644 --- a/quiche/Cargo.toml +++ b/quiche/Cargo.toml @@ -39,6 +39,11 @@ boringssl-boring-crate = ["boring", "foreign-types-shared"] # Build quiche against OpenSSL instead of BoringSSL. openssl = ["pkg-config"] +# Build quiche using rustls +rustls-aws-lc-rs = ["rustls/aws-lc-rs", "dep:aws-lc-rs", "dep:rustls", "dep:rustls-native-certs", "__rustls"] +rustls-ring = ["rustls/ring", "dep:rustls", "dep:ring", "dep:rustls-native-certs", "__rustls"] +__rustls = [] + # Generate pkg-config metadata file for libquiche. pkg-config-meta = [] @@ -83,6 +88,10 @@ sfv = { version = "0.9", optional = true } slab = "0.4" smallvec = { workspace = true, features = ["union"] } enum_dispatch = "0.3" +rustls = { version = "0.23.26", default-features = false, features = ["std", "logging"], optional = true } +rustls-native-certs = { version = "0.8.1", optional = true } +ring = { workspace = true, optional = true } +aws-lc-rs = { version = "1.13.0", default-features = false, optional = true } [target."cfg(windows)".dependencies] windows-sys = { version = "0.59", features = [ diff --git a/quiche/examples/cert-big_rustls.crt b/quiche/examples/cert-big_rustls.crt new file mode 100644 index 0000000000..2b0b6961c1 --- /dev/null +++ b/quiche/examples/cert-big_rustls.crt @@ -0,0 +1,100 @@ +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- diff --git a/quiche/examples/cert_rustls.crt b/quiche/examples/cert_rustls.crt new file mode 100644 index 0000000000..62532406a3 --- /dev/null +++ b/quiche/examples/cert_rustls.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUTZGqZKKEu0WNBBxhYLNpSOOwz3AwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owITELMAkGA1UEBhMCR0IxEjAQBgNVBAMMCXF1aWMudGVjaDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Wzi+yw/ZIiGoHFa2amdd2XEdC +oTLs7G66lC22SsqgvrgUYCDcWCeyz5yeCz2Lgrf5wGkOwi13yoMwDBbvHiCulBL2 +QOvu29tBOK7zqT20wf0+CW2GTUCsjPQBMK+o9D+rxYK1k+2gc4ZJoqhHub7/+2Th +Bm/gG/nLzHrM3wW5Pd58synTWlLofece/6dDsN9eZ6dj0bqE9EEJwPcxP/XmdyWo +d3DWZzbkOgQx5ZHoM01eSYWGG5UaP6ZrdhPRNIWvjVrES392SVGIZGEd1KjJbRCr +i4JQjlVgWz5ineCGVDSKLaSjFUElW/WoDQRePYR/YgYTd3wZux6fCgaYyS8CAwEA +AaNYMFYwFAYDVR0RBA0wC4IJcXVpYy50ZWNoMB0GA1UdDgQWBBSHabqAgfnBIkPF +Fjt3Xi3wZHQnNzAfBgNVHSMEGDAWgBQjyE843kJpz9KXoQnMi3Qq+Bcl+TANBgkq +hkiG9w0BAQsFAAOCAQEAeWVWygPY+iwlxEfo9uBhwo0OIfcy55QPzgwI5aGB1sPR +Ien+uxziOokXzyrtJWkY3eqzF2eYywH89GJoRv5hdcFKs2NZ/DWvGZHE8PoT0FBF +L47VjPg7k16IIhnOZlN5DeY0WfkeXFvANzPJGbdGYArg+ZQMCQ+xJu/UKs+vPF4+ +mxTQ31iCwHyGdLeXSk2RRkI1m5UhLWd4v5Pan37m9UujvOMwEIGRxlwr1Zhpb/QJ +w90iH5gMVzZ0iQxPYbYug0Ns7ekg+gaCckCt/CKpKmVOF26XmtKkXuCYhDi42CvO +o8rtE95MBjSXOreqH7ns2GdKGllvzGzzCvpDVmn3Cg== +-----END CERTIFICATE----- diff --git a/quiche/examples/gen-certs.sh b/quiche/examples/gen-certs.sh index 3df415b326..4c14c03990 100755 --- a/quiche/examples/gen-certs.sh +++ b/quiche/examples/gen-certs.sh @@ -13,3 +13,20 @@ cat cert.crt >> cert-big.crt rm cert.csr rm rootca.key rm rootca.srl + +# required as rustls does not support v1 certificates +# rustls also needs the subjectAltName to successfully verify the certificates +echo '[v3_req]' > openssl.cnf +openssl req -new -x509 -batch -nodes -days 10000 -keyout rootca_rustls.key -out rootca_rustls.crt + +openssl req -new -batch -nodes -sha256 -key cert.key -out cert_rustls.csr \ + -subj '/C=GB/CN=quic.tech' -addext "subjectAltName=DNS:quic.tech" +openssl x509 -req -days 10000 -CAcreateserial -CA rootca_rustls.crt -CAkey rootca_rustls.key \ + -in cert_rustls.csr -out cert_rustls.crt -copy_extensions copyall +openssl verify -CAfile rootca_rustls.crt cert_rustls.crt +cat cert_rustls.crt cert_rustls.crt cert_rustls.crt cert_rustls.crt cert_rustls.crt > cert-big_rustls.crt + +rm openssl.cnf +rm cert_rustls.csr +rm rootca_rustls.key +rm rootca_rustls.srl diff --git a/quiche/examples/rootca_rustls.crt b/quiche/examples/rootca_rustls.crt new file mode 100644 index 0000000000..75432ad504 --- /dev/null +++ b/quiche/examples/rootca_rustls.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUYhjE4OLyZdy528n3RT0KN2xro+owDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNTA1MDUxMjQzMDdaGA8yMDUyMDky +MDEyNDMwN1owQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc +MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAM2fTBsAiEQv58w//PQPJPECwXQ0pZ4oMP5babMl8EfT9W1j +cC8pEzV6T6Ao+YSRphLdI2oLNhCtqLggveaE08hMEFqUz4k7L+GMKv8H0jvKvC08 +3z0Xe6xG3BfghE+MFg+fEosWHPM36pg8gZTb/zj7ucWeeVBivxdtqTnLGO06yRRO +V4oT/xBlJKrvAzsIS8ZwQC7glldcQegStmofO/PBK4kSW2GTld3pAOsNxIh1haNY +g7moQiQQn3MsAigXNX3Y3p8Hg7iYip+09WT73cUXn1fKtbJjTu7U1DGwVv1mpxeg +1hSviTh5oeXUsbE4I1LEY5HHcq5RxUsbYoBMjv0CAwEAAaNTMFEwHQYDVR0OBBYE +FCPITzjeQmnP0pehCcyLdCr4FyX5MB8GA1UdIwQYMBaAFCPITzjeQmnP0pehCcyL +dCr4FyX5MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJRsINU5 +ZYE76j775n8vd9pS0auZzVa5vIWY9292rWAny4H3Pub5DTg7h6JlPmg3/rUjluIl +kK0RgFsRfDRCakOjdTfhBBCtfNARQSeoH3Y7gmELH8D0SojMUq1x4ttPyGOUXM39 +MngYOQemQMKk/OxB87rAJN4av46jv0NYK8unMLM5HKtmBK4nWG3T8KRZRfa5JeFl +mKrvn7C8O8iW8zlLHW43Upx4L+HLvvGZT3eFjfYOUghzWnbjIVmhy6toJcpdhIvD +kCDY1/CGBrgYFZskg3uOATMLAYIUjS6rWqSeKtGOwnTU1AqQ4rM/cjMGrjSsfYYs +TsXIILlIsXPy7/w= +-----END CERTIFICATE----- diff --git a/quiche/src/crypto/boringssl.rs b/quiche/src/crypto/boringssl_openssl/boringssl.rs similarity index 100% rename from quiche/src/crypto/boringssl.rs rename to quiche/src/crypto/boringssl_openssl/boringssl.rs diff --git a/quiche/src/crypto/boringssl_openssl/mod.rs b/quiche/src/crypto/boringssl_openssl/mod.rs new file mode 100644 index 0000000000..dff45686d8 --- /dev/null +++ b/quiche/src/crypto/boringssl_openssl/mod.rs @@ -0,0 +1,586 @@ +// Copyright (C) 2018-2019, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use libc::c_int; +use libc::c_void; + +use crate::Error; +use crate::Result; + +use crate::crypto::make_nonce; +use crate::crypto::Algorithm; + +// Length of header protection mask. +pub const HP_MASK_LEN: usize = 5; + +// Note: some vendor-specific methods are implemented by each vendor's submodule +// (openssl-quictls / boringssl). +impl Algorithm { + fn get_evp_digest(self) -> *const EVP_MD { + match self { + Algorithm::AES128_GCM => unsafe { EVP_sha256() }, + Algorithm::AES256_GCM => unsafe { EVP_sha384() }, + Algorithm::ChaCha20_Poly1305 => unsafe { EVP_sha256() }, + } + } + + pub const fn nonce_len(self) -> usize { + match self { + Algorithm::AES128_GCM => 12, + Algorithm::AES256_GCM => 12, + Algorithm::ChaCha20_Poly1305 => 12, + } + } +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +pub struct EVP_AEAD { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct EVP_MD { + _unused: c_void, +} + +type HeaderProtectionMask = [u8; HP_MASK_LEN]; + +pub struct Open { + alg: Algorithm, + + secret: Vec, + + header: HeaderProtectionKey, + + packet: PacketKey, +} + +impl Open { + // Note: some vendor-specific methods are implemented by each vendor's + // submodule (openssl-quictls / boringssl). + + pub const DECRYPT: u32 = 0; + + pub fn new( + alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, + secret: Vec, + ) -> Result { + Ok(Open { + alg, + + secret, + + header: HeaderProtectionKey::new(alg, hp_key)?, + + packet: PacketKey::new(alg, key, iv, Self::DECRYPT)?, + }) + } + + pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { + Ok(Open { + alg: aead, + + secret: secret.to_vec(), + + header: HeaderProtectionKey::from_secret(aead, secret)?, + + packet: PacketKey::from_secret(aead, secret, Self::DECRYPT)?, + }) + } + + pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> { + if cfg!(feature = "fuzzing") { + return Ok(<[u8; 5]>::default()); + } + + self.header.new_mask(sample) + } + + pub fn alg(&self) -> Algorithm { + self.alg + } + + pub fn derive_next_packet_key(&self) -> Result { + let next_secret = derive_next_secret(self.alg, &self.secret)?; + + let next_packet_key = + PacketKey::from_secret(self.alg, &next_secret, Self::DECRYPT)?; + + Ok(Open { + alg: self.alg, + + secret: next_secret, + + header: self.header.clone(), + + packet: next_packet_key, + }) + } + + pub fn open_with_u64_counter( + &self, counter: u64, ad: &[u8], buf: &mut [u8], + ) -> Result { + if cfg!(feature = "fuzzing") { + return Ok(buf.len()); + } + + self.packet.open_with_u64_counter(counter, ad, buf) + } +} + +pub struct Seal { + alg: Algorithm, + + secret: Vec, + + header: HeaderProtectionKey, + + packet: PacketKey, +} + +impl Seal { + // Note: some vendor-specific methods are implemented by each vendor's + // submodule (openssl-quictls / boringssl). + + pub const ENCRYPT: u32 = 1; + + pub fn new( + alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, + secret: Vec, + ) -> Result { + Ok(Seal { + alg, + + secret, + + header: HeaderProtectionKey::new(alg, hp_key)?, + + packet: PacketKey::new(alg, key, iv, Self::ENCRYPT)?, + }) + } + + pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { + Ok(Seal { + alg: aead, + + secret: secret.to_vec(), + + header: HeaderProtectionKey::from_secret(aead, secret)?, + + packet: PacketKey::from_secret(aead, secret, Self::ENCRYPT)?, + }) + } + + pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> { + if cfg!(feature = "fuzzing") { + return Ok(<[u8; 5]>::default()); + } + + self.header.new_mask(sample) + } + + pub fn alg(&self) -> Algorithm { + self.alg + } + + pub fn derive_next_packet_key(&self) -> Result { + let next_secret = derive_next_secret(self.alg, &self.secret)?; + + let next_packet_key = + PacketKey::from_secret(self.alg, &next_secret, Self::ENCRYPT)?; + + Ok(Seal { + alg: self.alg, + + secret: next_secret, + + header: self.header.clone(), + + packet: next_packet_key, + }) + } + + pub fn seal_with_u64_counter( + &self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize, + extra_in: Option<&[u8]>, + ) -> Result { + if cfg!(feature = "fuzzing") { + if let Some(extra) = extra_in { + buf[in_len..in_len + extra.len()].copy_from_slice(extra); + return Ok(in_len + extra.len()); + } + + return Ok(in_len); + } + + self.packet + .seal_with_u64_counter(counter, ad, buf, in_len, extra_in) + } +} + +impl HeaderProtectionKey { + pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { + let key_len = aead.key_len(); + + let mut hp_key = vec![0; key_len]; + + derive_hdr_key(aead, secret, &mut hp_key)?; + + Self::new(aead, hp_key) + } +} + +pub fn derive_initial_key_material( + cid: &[u8], version: u32, is_server: bool, did_reset: bool, +) -> Result<(Open, Seal)> { + let mut initial_secret = [0; 32]; + let mut client_secret = vec![0; 32]; + let mut server_secret = vec![0; 32]; + + let aead = Algorithm::AES128_GCM; + + let key_len = aead.key_len(); + let nonce_len = aead.nonce_len(); + + derive_initial_secret(cid, version, &mut initial_secret)?; + + derive_client_initial_secret(aead, &initial_secret, &mut client_secret)?; + + derive_server_initial_secret(aead, &initial_secret, &mut server_secret)?; + + // When the initial key material has been reset (e.g. due to retry or + // version negotiation), we need to prime the AEAD context as well, as the + // following packet will not start from 0 again. This is done through the + // `Open/Seal::from_secret()` path, rather than `Open/Seal::new()`. + if did_reset { + let (open, seal) = if is_server { + ( + Open::from_secret(aead, &client_secret)?, + Seal::from_secret(aead, &server_secret)?, + ) + } else { + ( + Open::from_secret(aead, &server_secret)?, + Seal::from_secret(aead, &client_secret)?, + ) + }; + + return Ok((open, seal)); + } + + // Client. + let mut client_key = vec![0; key_len]; + let mut client_iv = vec![0; nonce_len]; + let mut client_hp_key = vec![0; key_len]; + + derive_pkt_key(aead, &client_secret, &mut client_key)?; + derive_pkt_iv(aead, &client_secret, &mut client_iv)?; + derive_hdr_key(aead, &client_secret, &mut client_hp_key)?; + + // Server. + let mut server_key = vec![0; key_len]; + let mut server_iv = vec![0; nonce_len]; + let mut server_hp_key = vec![0; key_len]; + + derive_pkt_key(aead, &server_secret, &mut server_key)?; + derive_pkt_iv(aead, &server_secret, &mut server_iv)?; + derive_hdr_key(aead, &server_secret, &mut server_hp_key)?; + + let (open, seal) = if is_server { + ( + Open::new(aead, client_key, client_iv, client_hp_key, client_secret)?, + Seal::new(aead, server_key, server_iv, server_hp_key, server_secret)?, + ) + } else { + ( + Open::new(aead, server_key, server_iv, server_hp_key, server_secret)?, + Seal::new(aead, client_key, client_iv, client_hp_key, client_secret)?, + ) + }; + + Ok((open, seal)) +} + +fn derive_initial_secret( + secret: &[u8], version: u32, out_prk: &mut [u8], +) -> Result<()> { + const INITIAL_SALT_V1: [u8; 20] = [ + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, + 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, + ]; + + let salt = match version { + crate::PROTOCOL_VERSION_V1 => &INITIAL_SALT_V1, + + _ => &INITIAL_SALT_V1, + }; + + hkdf_extract(Algorithm::AES128_GCM, out_prk, secret, salt) +} + +fn derive_client_initial_secret( + aead: Algorithm, prk: &[u8], out: &mut [u8], +) -> Result<()> { + const LABEL: &[u8] = b"client in"; + hkdf_expand_label(aead, prk, LABEL, out) +} + +fn derive_server_initial_secret( + aead: Algorithm, prk: &[u8], out: &mut [u8], +) -> Result<()> { + const LABEL: &[u8] = b"server in"; + hkdf_expand_label(aead, prk, LABEL, out) +} + +fn derive_next_secret(aead: Algorithm, secret: &[u8]) -> Result> { + const LABEL: &[u8] = b"quic ku"; + + let mut next_secret = vec![0u8; 32]; + + hkdf_expand_label(aead, secret, LABEL, &mut next_secret)?; + + Ok(next_secret) +} + +pub fn derive_hdr_key( + aead: Algorithm, secret: &[u8], out: &mut [u8], +) -> Result<()> { + const LABEL: &[u8] = b"quic hp"; + + let key_len = aead.key_len(); + + if key_len > out.len() { + return Err(Error::CryptoFail); + } + + hkdf_expand_label(aead, secret, LABEL, &mut out[..key_len]) +} + +pub fn derive_pkt_key(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> { + const LABEL: &[u8] = b"quic key"; + + let key_len: usize = aead.key_len(); + + if key_len > out.len() { + return Err(Error::CryptoFail); + } + + hkdf_expand_label(aead, prk, LABEL, &mut out[..key_len]) +} + +pub fn derive_pkt_iv(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> { + const LABEL: &[u8] = b"quic iv"; + + let nonce_len = aead.nonce_len(); + + if nonce_len > out.len() { + return Err(Error::CryptoFail); + } + + hkdf_expand_label(aead, prk, LABEL, &mut out[..nonce_len]) +} + +fn hkdf_expand_label( + alg: Algorithm, prk: &[u8], label: &[u8], out: &mut [u8], +) -> Result<()> { + const LABEL_PREFIX: &[u8] = b"tls13 "; + + let out_len = (out.len() as u16).to_be_bytes(); + let label_len = (LABEL_PREFIX.len() + label.len()) as u8; + + let info = [&out_len, &[label_len][..], LABEL_PREFIX, label, &[0][..]]; + let info = info.concat(); + + hkdf_expand(alg, out, prk, &info)?; + + Ok(()) +} + +pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> { + if a.len() != b.len() { + return Err(Error::CryptoFail); + } + + let rc = unsafe { CRYPTO_memcmp(a.as_ptr(), b.as_ptr(), a.len()) }; + + if rc == 0 { + return Ok(()); + } + + Err(Error::CryptoFail) +} + +extern "C" { + fn EVP_sha256() -> *const EVP_MD; + + fn EVP_sha384() -> *const EVP_MD; + + // CRYPTO + fn CRYPTO_memcmp(a: *const u8, b: *const u8, len: usize) -> c_int; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn derive_initial_secrets_v1() { + let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; + + let mut initial_secret = [0; 32]; + + let mut secret = [0; 32]; + let mut pkt_key = [0; 16]; + let mut pkt_iv = [0; 12]; + let mut hdr_key = [0; 16]; + + let aead = Algorithm::AES128_GCM; + + assert!(derive_initial_secret( + &dcid, + crate::PROTOCOL_VERSION_V1, + &mut initial_secret, + ) + .is_ok()); + + // Client. + assert!( + derive_client_initial_secret(aead, &initial_secret, &mut secret) + .is_ok() + ); + let expected_client_initial_secret = [ + 0xc0, 0x0c, 0xf1, 0x51, 0xca, 0x5b, 0xe0, 0x75, 0xed, 0x0e, 0xbf, + 0xb5, 0xc8, 0x03, 0x23, 0xc4, 0x2d, 0x6b, 0x7d, 0xb6, 0x78, 0x81, + 0x28, 0x9a, 0xf4, 0x00, 0x8f, 0x1f, 0x6c, 0x35, 0x7a, 0xea, + ]; + assert_eq!(&secret, &expected_client_initial_secret); + + assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); + let expected_client_pkt_key = [ + 0x1f, 0x36, 0x96, 0x13, 0xdd, 0x76, 0xd5, 0x46, 0x77, 0x30, 0xef, + 0xcb, 0xe3, 0xb1, 0xa2, 0x2d, + ]; + assert_eq!(&pkt_key, &expected_client_pkt_key); + + assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); + let expected_client_pkt_iv = [ + 0xfa, 0x04, 0x4b, 0x2f, 0x42, 0xa3, 0xfd, 0x3b, 0x46, 0xfb, 0x25, + 0x5c, + ]; + assert_eq!(&pkt_iv, &expected_client_pkt_iv); + + assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); + let expected_client_hdr_key = [ + 0x9f, 0x50, 0x44, 0x9e, 0x04, 0xa0, 0xe8, 0x10, 0x28, 0x3a, 0x1e, + 0x99, 0x33, 0xad, 0xed, 0xd2, + ]; + assert_eq!(&hdr_key, &expected_client_hdr_key); + + // Server. + assert!( + derive_server_initial_secret(aead, &initial_secret, &mut secret) + .is_ok() + ); + + let expected_server_initial_secret = [ + 0x3c, 0x19, 0x98, 0x28, 0xfd, 0x13, 0x9e, 0xfd, 0x21, 0x6c, 0x15, + 0x5a, 0xd8, 0x44, 0xcc, 0x81, 0xfb, 0x82, 0xfa, 0x8d, 0x74, 0x46, + 0xfa, 0x7d, 0x78, 0xbe, 0x80, 0x3a, 0xcd, 0xda, 0x95, 0x1b, + ]; + assert_eq!(&secret, &expected_server_initial_secret); + + assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); + let expected_server_pkt_key = [ + 0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3, + 0x79, 0xb6, 0x06, 0x7e, 0x37, + ]; + assert_eq!(&pkt_key, &expected_server_pkt_key); + + assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); + let expected_server_pkt_iv = [ + 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, + 0x3e, + ]; + assert_eq!(&pkt_iv, &expected_server_pkt_iv); + + assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); + let expected_server_hdr_key = [ + 0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44, 0x43, 0x0b, + 0x49, 0x0e, 0xea, 0xa3, 0x14, + ]; + assert_eq!(&hdr_key, &expected_server_hdr_key); + } + + #[test] + fn derive_chacha20_secrets() { + let secret = [ + 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, + 0x27, 0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, + 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b, + ]; + + let aead = Algorithm::ChaCha20_Poly1305; + + let mut pkt_key = [0; 32]; + let mut pkt_iv = [0; 12]; + let mut hdr_key = [0; 32]; + + assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); + let expected_pkt_key = [ + 0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20, + 0x94, 0xf6, 0x9c, 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88, + 0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79, 0xfb, 0x23, 0xe1, 0xc8, + ]; + assert_eq!(&pkt_key, &expected_pkt_key); + + assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); + let expected_pkt_iv = [ + 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1, + 0x44, + ]; + assert_eq!(&pkt_iv, &expected_pkt_iv); + + assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); + let expected_hdr_key = [ + 0x25, 0xa2, 0x82, 0xb9, 0xe8, 0x2f, 0x06, 0xf2, 0x1f, 0x48, 0x89, + 0x17, 0xa4, 0xfc, 0x8f, 0x1b, 0x73, 0x57, 0x36, 0x85, 0x60, 0x85, + 0x97, 0xd0, 0xef, 0xcb, 0x07, 0x6b, 0x0a, 0xb7, 0xa7, 0xa4, + ]; + assert_eq!(&hdr_key, &expected_hdr_key); + } +} + +#[cfg(not(feature = "openssl"))] +mod boringssl; +#[cfg(not(feature = "openssl"))] +pub(crate) use boringssl::*; + +#[cfg(feature = "openssl")] +mod openssl_quictls; +#[cfg(feature = "openssl")] +pub(crate) use openssl_quictls::*; diff --git a/quiche/src/crypto/openssl_quictls.rs b/quiche/src/crypto/boringssl_openssl/openssl_quictls.rs similarity index 100% rename from quiche/src/crypto/openssl_quictls.rs rename to quiche/src/crypto/boringssl_openssl/openssl_quictls.rs diff --git a/quiche/src/crypto/mod.rs b/quiche/src/crypto/mod.rs index 1e1b62a0f1..acd9ca911c 100644 --- a/quiche/src/crypto/mod.rs +++ b/quiche/src/crypto/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2019, Cloudflare, Inc. +// Copyright (C) 2018-2025, Cloudflare, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -24,20 +24,21 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use libc::c_int; -use libc::c_void; +#[cfg(not(feature = "__rustls"))] +mod boringssl_openssl; +#[cfg(not(feature = "__rustls"))] +pub use boringssl_openssl::*; -use crate::Error; -use crate::Result; +#[cfg(feature = "__rustls")] +mod rustls; +#[cfg(feature = "__rustls")] +pub use rustls::*; use crate::packet; // All the AEAD algorithms we support use 96-bit nonces. pub const MAX_NONCE_LEN: usize = 12; -// Length of header protection mask. -pub const HP_MASK_LEN: usize = 5; - #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Level { @@ -71,17 +72,7 @@ pub enum Algorithm { ChaCha20_Poly1305, } -// Note: some vendor-specific methods are implemented by each vendor's submodule -// (openssl-quictls / boringssl). impl Algorithm { - fn get_evp_digest(self) -> *const EVP_MD { - match self { - Algorithm::AES128_GCM => unsafe { EVP_sha256() }, - Algorithm::AES256_GCM => unsafe { EVP_sha384() }, - Algorithm::ChaCha20_Poly1305 => unsafe { EVP_sha256() }, - } - } - pub const fn key_len(self) -> usize { match self { Algorithm::AES128_GCM => 16, @@ -101,379 +92,6 @@ impl Algorithm { Algorithm::ChaCha20_Poly1305 => 16, } } - - pub const fn nonce_len(self) -> usize { - match self { - Algorithm::AES128_GCM => 12, - Algorithm::AES256_GCM => 12, - Algorithm::ChaCha20_Poly1305 => 12, - } - } -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -pub struct EVP_AEAD { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct EVP_MD { - _unused: c_void, -} - -type HeaderProtectionMask = [u8; HP_MASK_LEN]; - -pub struct Open { - alg: Algorithm, - - secret: Vec, - - header: HeaderProtectionKey, - - packet: PacketKey, -} - -impl Open { - // Note: some vendor-specific methods are implemented by each vendor's - // submodule (openssl-quictls / boringssl). - - pub const DECRYPT: u32 = 0; - - pub fn new( - alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, - secret: Vec, - ) -> Result { - Ok(Open { - alg, - - secret, - - header: HeaderProtectionKey::new(alg, hp_key)?, - - packet: PacketKey::new(alg, key, iv, Self::DECRYPT)?, - }) - } - - pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { - Ok(Open { - alg: aead, - - secret: secret.to_vec(), - - header: HeaderProtectionKey::from_secret(aead, secret)?, - - packet: PacketKey::from_secret(aead, secret, Self::DECRYPT)?, - }) - } - - pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> { - if cfg!(feature = "fuzzing") { - return Ok(<[u8; 5]>::default()); - } - - self.header.new_mask(sample) - } - - pub fn alg(&self) -> Algorithm { - self.alg - } - - pub fn derive_next_packet_key(&self) -> Result { - let next_secret = derive_next_secret(self.alg, &self.secret)?; - - let next_packet_key = - PacketKey::from_secret(self.alg, &next_secret, Self::DECRYPT)?; - - Ok(Open { - alg: self.alg, - - secret: next_secret, - - header: self.header.clone(), - - packet: next_packet_key, - }) - } - - pub fn open_with_u64_counter( - &self, counter: u64, ad: &[u8], buf: &mut [u8], - ) -> Result { - if cfg!(feature = "fuzzing") { - return Ok(buf.len()); - } - - self.packet.open_with_u64_counter(counter, ad, buf) - } -} - -pub struct Seal { - alg: Algorithm, - - secret: Vec, - - header: HeaderProtectionKey, - - packet: PacketKey, -} - -impl Seal { - // Note: some vendor-specific methods are implemented by each vendor's - // submodule (openssl-quictls / boringssl). - - pub const ENCRYPT: u32 = 1; - - pub fn new( - alg: Algorithm, key: Vec, iv: Vec, hp_key: Vec, - secret: Vec, - ) -> Result { - Ok(Seal { - alg, - - secret, - - header: HeaderProtectionKey::new(alg, hp_key)?, - - packet: PacketKey::new(alg, key, iv, Self::ENCRYPT)?, - }) - } - - pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { - Ok(Seal { - alg: aead, - - secret: secret.to_vec(), - - header: HeaderProtectionKey::from_secret(aead, secret)?, - - packet: PacketKey::from_secret(aead, secret, Self::ENCRYPT)?, - }) - } - - pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> { - if cfg!(feature = "fuzzing") { - return Ok(<[u8; 5]>::default()); - } - - self.header.new_mask(sample) - } - - pub fn alg(&self) -> Algorithm { - self.alg - } - - pub fn derive_next_packet_key(&self) -> Result { - let next_secret = derive_next_secret(self.alg, &self.secret)?; - - let next_packet_key = - PacketKey::from_secret(self.alg, &next_secret, Self::ENCRYPT)?; - - Ok(Seal { - alg: self.alg, - - secret: next_secret, - - header: self.header.clone(), - - packet: next_packet_key, - }) - } - - pub fn seal_with_u64_counter( - &self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize, - extra_in: Option<&[u8]>, - ) -> Result { - if cfg!(feature = "fuzzing") { - if let Some(extra) = extra_in { - buf[in_len..in_len + extra.len()].copy_from_slice(extra); - return Ok(in_len + extra.len()); - } - - return Ok(in_len); - } - - self.packet - .seal_with_u64_counter(counter, ad, buf, in_len, extra_in) - } -} - -impl HeaderProtectionKey { - pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result { - let key_len = aead.key_len(); - - let mut hp_key = vec![0; key_len]; - - derive_hdr_key(aead, secret, &mut hp_key)?; - - Self::new(aead, hp_key) - } -} - -pub fn derive_initial_key_material( - cid: &[u8], version: u32, is_server: bool, did_reset: bool, -) -> Result<(Open, Seal)> { - let mut initial_secret = [0; 32]; - let mut client_secret = vec![0; 32]; - let mut server_secret = vec![0; 32]; - - let aead = Algorithm::AES128_GCM; - - let key_len = aead.key_len(); - let nonce_len = aead.nonce_len(); - - derive_initial_secret(cid, version, &mut initial_secret)?; - - derive_client_initial_secret(aead, &initial_secret, &mut client_secret)?; - - derive_server_initial_secret(aead, &initial_secret, &mut server_secret)?; - - // When the initial key material has been reset (e.g. due to retry or - // version negotiation), we need to prime the AEAD context as well, as the - // following packet will not start from 0 again. This is done through the - // `Open/Seal::from_secret()` path, rather than `Open/Seal::new()`. - if did_reset { - let (open, seal) = if is_server { - ( - Open::from_secret(aead, &client_secret)?, - Seal::from_secret(aead, &server_secret)?, - ) - } else { - ( - Open::from_secret(aead, &server_secret)?, - Seal::from_secret(aead, &client_secret)?, - ) - }; - - return Ok((open, seal)); - } - - // Client. - let mut client_key = vec![0; key_len]; - let mut client_iv = vec![0; nonce_len]; - let mut client_hp_key = vec![0; key_len]; - - derive_pkt_key(aead, &client_secret, &mut client_key)?; - derive_pkt_iv(aead, &client_secret, &mut client_iv)?; - derive_hdr_key(aead, &client_secret, &mut client_hp_key)?; - - // Server. - let mut server_key = vec![0; key_len]; - let mut server_iv = vec![0; nonce_len]; - let mut server_hp_key = vec![0; key_len]; - - derive_pkt_key(aead, &server_secret, &mut server_key)?; - derive_pkt_iv(aead, &server_secret, &mut server_iv)?; - derive_hdr_key(aead, &server_secret, &mut server_hp_key)?; - - let (open, seal) = if is_server { - ( - Open::new(aead, client_key, client_iv, client_hp_key, client_secret)?, - Seal::new(aead, server_key, server_iv, server_hp_key, server_secret)?, - ) - } else { - ( - Open::new(aead, server_key, server_iv, server_hp_key, server_secret)?, - Seal::new(aead, client_key, client_iv, client_hp_key, client_secret)?, - ) - }; - - Ok((open, seal)) -} - -fn derive_initial_secret( - secret: &[u8], version: u32, out_prk: &mut [u8], -) -> Result<()> { - const INITIAL_SALT_V1: [u8; 20] = [ - 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, - 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, - ]; - - let salt = match version { - crate::PROTOCOL_VERSION_V1 => &INITIAL_SALT_V1, - - _ => &INITIAL_SALT_V1, - }; - - hkdf_extract(Algorithm::AES128_GCM, out_prk, secret, salt) -} - -fn derive_client_initial_secret( - aead: Algorithm, prk: &[u8], out: &mut [u8], -) -> Result<()> { - const LABEL: &[u8] = b"client in"; - hkdf_expand_label(aead, prk, LABEL, out) -} - -fn derive_server_initial_secret( - aead: Algorithm, prk: &[u8], out: &mut [u8], -) -> Result<()> { - const LABEL: &[u8] = b"server in"; - hkdf_expand_label(aead, prk, LABEL, out) -} - -fn derive_next_secret(aead: Algorithm, secret: &[u8]) -> Result> { - const LABEL: &[u8] = b"quic ku"; - - let mut next_secret = vec![0u8; 32]; - - hkdf_expand_label(aead, secret, LABEL, &mut next_secret)?; - - Ok(next_secret) -} - -pub fn derive_hdr_key( - aead: Algorithm, secret: &[u8], out: &mut [u8], -) -> Result<()> { - const LABEL: &[u8] = b"quic hp"; - - let key_len = aead.key_len(); - - if key_len > out.len() { - return Err(Error::CryptoFail); - } - - hkdf_expand_label(aead, secret, LABEL, &mut out[..key_len]) -} - -pub fn derive_pkt_key(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> { - const LABEL: &[u8] = b"quic key"; - - let key_len: usize = aead.key_len(); - - if key_len > out.len() { - return Err(Error::CryptoFail); - } - - hkdf_expand_label(aead, prk, LABEL, &mut out[..key_len]) -} - -pub fn derive_pkt_iv(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> { - const LABEL: &[u8] = b"quic iv"; - - let nonce_len = aead.nonce_len(); - - if nonce_len > out.len() { - return Err(Error::CryptoFail); - } - - hkdf_expand_label(aead, prk, LABEL, &mut out[..nonce_len]) -} - -fn hkdf_expand_label( - alg: Algorithm, prk: &[u8], label: &[u8], out: &mut [u8], -) -> Result<()> { - const LABEL_PREFIX: &[u8] = b"tls13 "; - - let out_len = (out.len() as u16).to_be_bytes(); - let label_len = (LABEL_PREFIX.len() + label.len()) as u8; - - let info = [&out_len, &[label_len][..], LABEL_PREFIX, label, &[0][..]]; - let info = info.concat(); - - hkdf_expand(alg, out, prk, &info)?; - - Ok(()) } fn make_nonce(iv: &[u8], counter: u64) -> [u8; MAX_NONCE_LEN] { @@ -488,167 +106,3 @@ fn make_nonce(iv: &[u8], counter: u64) -> [u8; MAX_NONCE_LEN] { nonce } - -pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> { - if a.len() != b.len() { - return Err(Error::CryptoFail); - } - - let rc = unsafe { CRYPTO_memcmp(a.as_ptr(), b.as_ptr(), a.len()) }; - - if rc == 0 { - return Ok(()); - } - - Err(Error::CryptoFail) -} - -extern "C" { - fn EVP_sha256() -> *const EVP_MD; - - fn EVP_sha384() -> *const EVP_MD; - - // CRYPTO - fn CRYPTO_memcmp(a: *const u8, b: *const u8, len: usize) -> c_int; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn derive_initial_secrets_v1() { - let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; - - let mut initial_secret = [0; 32]; - - let mut secret = [0; 32]; - let mut pkt_key = [0; 16]; - let mut pkt_iv = [0; 12]; - let mut hdr_key = [0; 16]; - - let aead = Algorithm::AES128_GCM; - - assert!(derive_initial_secret( - &dcid, - crate::PROTOCOL_VERSION_V1, - &mut initial_secret, - ) - .is_ok()); - - // Client. - assert!( - derive_client_initial_secret(aead, &initial_secret, &mut secret) - .is_ok() - ); - let expected_client_initial_secret = [ - 0xc0, 0x0c, 0xf1, 0x51, 0xca, 0x5b, 0xe0, 0x75, 0xed, 0x0e, 0xbf, - 0xb5, 0xc8, 0x03, 0x23, 0xc4, 0x2d, 0x6b, 0x7d, 0xb6, 0x78, 0x81, - 0x28, 0x9a, 0xf4, 0x00, 0x8f, 0x1f, 0x6c, 0x35, 0x7a, 0xea, - ]; - assert_eq!(&secret, &expected_client_initial_secret); - - assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); - let expected_client_pkt_key = [ - 0x1f, 0x36, 0x96, 0x13, 0xdd, 0x76, 0xd5, 0x46, 0x77, 0x30, 0xef, - 0xcb, 0xe3, 0xb1, 0xa2, 0x2d, - ]; - assert_eq!(&pkt_key, &expected_client_pkt_key); - - assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); - let expected_client_pkt_iv = [ - 0xfa, 0x04, 0x4b, 0x2f, 0x42, 0xa3, 0xfd, 0x3b, 0x46, 0xfb, 0x25, - 0x5c, - ]; - assert_eq!(&pkt_iv, &expected_client_pkt_iv); - - assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); - let expected_client_hdr_key = [ - 0x9f, 0x50, 0x44, 0x9e, 0x04, 0xa0, 0xe8, 0x10, 0x28, 0x3a, 0x1e, - 0x99, 0x33, 0xad, 0xed, 0xd2, - ]; - assert_eq!(&hdr_key, &expected_client_hdr_key); - - // Server. - assert!( - derive_server_initial_secret(aead, &initial_secret, &mut secret) - .is_ok() - ); - - let expected_server_initial_secret = [ - 0x3c, 0x19, 0x98, 0x28, 0xfd, 0x13, 0x9e, 0xfd, 0x21, 0x6c, 0x15, - 0x5a, 0xd8, 0x44, 0xcc, 0x81, 0xfb, 0x82, 0xfa, 0x8d, 0x74, 0x46, - 0xfa, 0x7d, 0x78, 0xbe, 0x80, 0x3a, 0xcd, 0xda, 0x95, 0x1b, - ]; - assert_eq!(&secret, &expected_server_initial_secret); - - assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); - let expected_server_pkt_key = [ - 0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3, - 0x79, 0xb6, 0x06, 0x7e, 0x37, - ]; - assert_eq!(&pkt_key, &expected_server_pkt_key); - - assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); - let expected_server_pkt_iv = [ - 0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, - 0x3e, - ]; - assert_eq!(&pkt_iv, &expected_server_pkt_iv); - - assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); - let expected_server_hdr_key = [ - 0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44, 0x43, 0x0b, - 0x49, 0x0e, 0xea, 0xa3, 0x14, - ]; - assert_eq!(&hdr_key, &expected_server_hdr_key); - } - - #[test] - fn derive_chacha20_secrets() { - let secret = [ - 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, - 0x27, 0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, - 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b, - ]; - - let aead = Algorithm::ChaCha20_Poly1305; - - let mut pkt_key = [0; 32]; - let mut pkt_iv = [0; 12]; - let mut hdr_key = [0; 32]; - - assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok()); - let expected_pkt_key = [ - 0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20, - 0x94, 0xf6, 0x9c, 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88, - 0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79, 0xfb, 0x23, 0xe1, 0xc8, - ]; - assert_eq!(&pkt_key, &expected_pkt_key); - - assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok()); - let expected_pkt_iv = [ - 0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1, - 0x44, - ]; - assert_eq!(&pkt_iv, &expected_pkt_iv); - - assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok()); - let expected_hdr_key = [ - 0x25, 0xa2, 0x82, 0xb9, 0xe8, 0x2f, 0x06, 0xf2, 0x1f, 0x48, 0x89, - 0x17, 0xa4, 0xfc, 0x8f, 0x1b, 0x73, 0x57, 0x36, 0x85, 0x60, 0x85, - 0x97, 0xd0, 0xef, 0xcb, 0x07, 0x6b, 0x0a, 0xb7, 0xa7, 0xa4, - ]; - assert_eq!(&hdr_key, &expected_hdr_key); - } -} - -#[cfg(not(feature = "openssl"))] -mod boringssl; -#[cfg(not(feature = "openssl"))] -pub(crate) use boringssl::*; - -#[cfg(feature = "openssl")] -mod openssl_quictls; -#[cfg(feature = "openssl")] -pub(crate) use openssl_quictls::*; diff --git a/quiche/src/crypto/rustls/mod.rs b/quiche/src/crypto/rustls/mod.rs new file mode 100644 index 0000000000..fef581bf53 --- /dev/null +++ b/quiche/src/crypto/rustls/mod.rs @@ -0,0 +1,480 @@ +// Copyright (C) 2025, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[cfg(feature = "rustls-aws-lc-rs")] +mod aws_lc_rs { + pub(super) use aws_lc_rs::aead::Aad; + pub(super) use aws_lc_rs::aead::LessSafeKey; + pub(super) use aws_lc_rs::aead::Nonce; + pub(super) use aws_lc_rs::aead::UnboundKey; + pub(super) use aws_lc_rs::aead::AES_128_GCM; + pub(super) use aws_lc_rs::aead::AES_256_GCM; + pub(super) use aws_lc_rs::aead::CHACHA20_POLY1305; + pub(super) use aws_lc_rs::aead::MAX_TAG_LEN; +} +#[cfg(feature = "rustls-ring")] +mod ring { + pub(super) use ring::aead::Aad; + pub(super) use ring::aead::LessSafeKey; + pub(super) use ring::aead::Nonce; + pub(super) use ring::aead::UnboundKey; + pub(super) use ring::aead::AES_128_GCM; + pub(super) use ring::aead::AES_256_GCM; + pub(super) use ring::aead::CHACHA20_POLY1305; + pub(super) use ring::aead::MAX_TAG_LEN; +} + +#[cfg(feature = "rustls-aws-lc-rs")] +use crate::crypto::rustls::aws_lc_rs::*; +#[cfg(feature = "rustls-ring")] +use crate::crypto::rustls::ring::*; + +use crate::crypto::make_nonce; +use crate::crypto::Algorithm; +use crate::Error; +use crate::Result; +use rustls::crypto::CryptoProvider; +use rustls::quic::DirectionalKeys; +use rustls::quic::HeaderProtectionKey; +use rustls::quic::Keys; +use rustls::quic::PacketKey as RustlsPacketKey; +use rustls::quic::Secrets; +use rustls::quic::Suite; +use rustls::quic::Version; +use rustls::CipherSuite; +use rustls::Side; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::MutexGuard; + +pub struct PacketKey { + key: LessSafeKey, + nonce: Vec, +} + +impl PacketKey { + pub fn new( + alg: Algorithm, key: Vec, iv: Vec, _enc: u32, + ) -> Result { + let key = match alg { + Algorithm::AES128_GCM => LessSafeKey::new( + UnboundKey::new(&AES_128_GCM, &key) + .map_err(|_| Error::CryptoFail)?, + ), + Algorithm::AES256_GCM => LessSafeKey::new( + UnboundKey::new(&AES_256_GCM, &key) + .map_err(|_| Error::CryptoFail)?, + ), + Algorithm::ChaCha20_Poly1305 => LessSafeKey::new( + UnboundKey::new(&CHACHA20_POLY1305, &key) + .map_err(|_| Error::CryptoFail)?, + ), + }; + + Ok(Self { key, nonce: iv }) + } + + pub fn seal_with_u64_counter( + &self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize, + extra_in: Option<&[u8]>, + ) -> Result { + if let Some(_extra_in) = extra_in { + error!("extra_in is not supported when using rustls"); + return Err(Error::CryptoFail); + }; + + let nonce = + Nonce::assume_unique_for_key(make_nonce(&self.nonce, counter)); + + let tag = self + .key + .seal_in_place_separate_tag(nonce, Aad::from(ad), &mut buf[..in_len]) + .map_err(|e| { + error!("failed to seal with packet key: {}", e); + Error::CryptoFail + })?; + + buf.copy_from_slice(tag.as_ref()); + Ok(MAX_TAG_LEN) + } +} + +pub struct Open { + packet_key: Box, + header_protection_key: Arc, + algorithm: Algorithm, + secrets: Option>, +} + +impl Open { + pub(crate) fn from(keys: DirectionalKeys) -> Self { + Self { + packet_key: keys.packet, + header_protection_key: Arc::from(keys.header), + algorithm: Algorithm::AES128_GCM, + secrets: None, + } + } + + pub fn decrypt_hdr( + &self, sample: &[u8], first: &mut u8, packet_number: &mut [u8], + ) -> Result<()> { + self.header_protection_key + .decrypt_in_place(sample, first, packet_number) + .map_err(|e| { + debug!("failed to decrypt packet header: {:?}", e); + Error::CryptoFail + }) + } + + pub fn open_with_u64_counter( + &self, packet_number: u64, header: &[u8], payload: &mut [u8], + ) -> Result { + let decrypted = self + .packet_key + .decrypt_in_place(packet_number, header, payload) + .map_err(|e| { + debug!("failed to decrypt packet: {:?}", e); + Error::CryptoFail + })?; + + Ok(decrypted.len()) + } + + pub fn alg(&self) -> Algorithm { + self.algorithm + } + + pub fn derive_next_packet_key(&self) -> Result { + let Some(secrets) = &self.secrets else { + error!("no secrets present for next packet key"); + return Err(Error::CryptoFail); + }; + + let Some(remote_key) = secrets.next_remote_key()? else { + error!("no remote key available for next packet key, previous local key was not consumed"); + return Err(Error::CryptoFail); + }; + + Ok(Open { + packet_key: remote_key, + header_protection_key: self.header_protection_key.clone(), + algorithm: Algorithm::AES128_GCM, + secrets: Some(secrets.clone()), + }) + } + + pub fn return_next_key(self) -> Result<()> { + let Some(secrets) = &self.secrets else { + error!("no secrets present to return packet key"); + return Err(Error::CryptoFail); + }; + + secrets.return_next_remote_key(self.packet_key) + } +} + +pub struct Seal { + packet_key: Box, + header_protection_key: Arc, + algorithm: Algorithm, + secrets: Option>, +} + +impl Seal { + pub const ENCRYPT: u32 = 1; + + pub(crate) fn from(keys: DirectionalKeys) -> Self { + Self { + packet_key: keys.packet, + header_protection_key: Arc::from(keys.header), + algorithm: Algorithm::AES128_GCM, + secrets: None, + } + } + + pub fn encrypt_hdr( + &self, sample: &[u8], first: &mut u8, packet_number: &mut [u8], + ) -> Result<()> { + self.header_protection_key + .encrypt_in_place(sample, first, packet_number) + .map_err(|e| { + error!("failed to encrypt packet header: {:?}", e); + Error::CryptoFail + }) + } + + pub fn seal_with_u64_counter( + &self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize, + extra_in: Option<&[u8]>, + ) -> Result { + if let Some(_extra_in) = extra_in { + error!("extra_in is not supported when using rustls"); + return Err(Error::CryptoFail); + } + + if (in_len + self.packet_key.tag_len()) > buf.len() { + error!("provided buffer size not sufficient for data and tag"); + return Err(Error::CryptoFail); + } + + let tag = self + .packet_key + .encrypt_in_place(counter, ad, &mut buf[..in_len]) + .map_err(|e| { + error!("failed to encrypt packet: {:?}", e); + Error::CryptoFail + })?; + + let tag_len = tag.as_ref().len(); + let tag = tag.as_ref(); + for ti in 0..tag_len { + buf[in_len + ti] = tag[ti]; + } + + Ok(in_len + tag_len) + } + + pub fn alg(&self) -> Algorithm { + self.algorithm + } + + pub fn derive_next_packet_key(&self) -> Result { + let Some(secrets) = &self.secrets else { + error!("no secrets present for next packet key"); + return Err(Error::CryptoFail); + }; + + let Some(local_key) = secrets.next_local_key()? else { + error!("no local key available for next packet key, previous remote key was not consumed"); + return Err(Error::CryptoFail); + }; + + Ok(Seal { + packet_key: local_key, + header_protection_key: self.header_protection_key.clone(), + algorithm: Algorithm::AES128_GCM, + secrets: Some(secrets.clone()), + }) + } + + pub fn return_next_key(self) -> Result<()> { + let Some(secrets) = &self.secrets else { + error!("no secrets present to return packet key"); + return Err(Error::CryptoFail); + }; + + secrets.return_next_local_key(self.packet_key) + } +} + +pub struct SecretsNextKeys { + inner: Mutex, +} + +impl SecretsNextKeys { + fn from(next_secrets: Secrets) -> Self { + Self { + inner: Mutex::new(SecretsNextKeysInner { + secrets: next_secrets, + local_key: None, + remote_key: None, + }), + } + } + + fn lock(&self) -> Result> { + self.inner.lock().map_err(|e| { + error!("failed to acquire mutex: {:?}", e); + Error::CryptoFail + }) + } + + fn next_local_key(&self) -> Result>> { + let mut me = self.lock()?; + me.maybe_update_keys(); + Ok(me.local_key.take()) + } + + fn next_remote_key(&self) -> Result>> { + let mut me = self.lock()?; + me.maybe_update_keys(); + Ok(me.remote_key.take()) + } + + fn return_next_local_key( + &self, local_key: Box, + ) -> Result<()> { + let mut me = self.lock()?; + me.local_key = Some(local_key); + debug!("returned local key"); + Ok(()) + } + + fn return_next_remote_key( + &self, remote_key: Box, + ) -> Result<()> { + let mut me = self.lock()?; + me.remote_key = Some(remote_key); + debug!("returned remote key"); + Ok(()) + } +} + +pub struct SecretsNextKeysInner { + secrets: Secrets, + local_key: Option>, + remote_key: Option>, +} + +impl SecretsNextKeysInner { + fn maybe_update_keys(&mut self) { + if self.local_key.is_none() && self.remote_key.is_none() { + let keys = self.secrets.next_packet_keys(); + self.local_key = Some(keys.local); + self.remote_key = Some(keys.remote); + }; + } +} + +pub(crate) fn key_material_from_keys( + keys: Keys, next: Option, +) -> Result<(Open, Seal)> { + let next_secrets = if let Some(next) = next { + debug!("creating key material from keys with secrets"); + Some(Arc::new(SecretsNextKeys::from(next))) + } else { + None + }; + + let open = Open { + packet_key: keys.remote.packet, + header_protection_key: Arc::from(keys.remote.header), + algorithm: Algorithm::AES128_GCM, + secrets: next_secrets.clone(), + }; + let seal = Seal { + packet_key: keys.local.packet, + header_protection_key: Arc::from(keys.local.header), + algorithm: Algorithm::AES128_GCM, + secrets: next_secrets, + }; + + Ok((open, seal)) +} + +pub fn derive_initial_key_material( + cid: &[u8], version: u32, is_server: bool, _did_reset: bool, +) -> Result<(Open, Seal)> { + let quic_suite = quic_suite_from_algorithm(Algorithm::AES128_GCM)?; + + let side = if is_server { + Side::Server + } else { + Side::Client + }; + + let version = match version { + 1 => Version::V1, + _ => Version::V1, + }; + + let keys = + Keys::initial(version, quic_suite.suite, quic_suite.quic, cid, side); + + let open = Open { + packet_key: keys.remote.packet, + header_protection_key: Arc::from(keys.remote.header), + algorithm: Algorithm::AES128_GCM, + secrets: None, + }; + let seal = Seal { + packet_key: keys.local.packet, + header_protection_key: Arc::from(keys.local.header), + algorithm: Algorithm::AES128_GCM, + secrets: None, + }; + + Ok((open, seal)) +} + +fn quic_suite_from_algorithm(algo: Algorithm) -> Result { + let provider = crypto_provider(); + + let cipher_suite = match algo { + Algorithm::AES128_GCM => CipherSuite::TLS13_AES_128_GCM_SHA256, + Algorithm::AES256_GCM => CipherSuite::TLS13_AES_256_GCM_SHA384, + Algorithm::ChaCha20_Poly1305 => + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + }; + + let suite = provider + .cipher_suites + .iter() + .find(|s| s.suite() == cipher_suite) + .ok_or_else(|| { + error!("default crypto suite not available"); + Error::CryptoFail + })?; + + let tls_13_suite = suite.tls13().ok_or_else(|| { + error!("crypto suite not a TLS 1.3 suite"); + Error::CryptoFail + })?; + + let quic_suite = tls_13_suite.quic_suite().ok_or_else(|| { + error!("crypto suite not a TLS 1.3 suite"); + Error::CryptoFail + })?; + + Ok(quic_suite) +} + +pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> { + if a.len() != b.len() { + return Err(Error::CryptoFail); + } + + match a == b { + true => Ok(()), + false => Err(Error::CryptoFail), + } +} + +// early setup is required as the crypto provider is used before ServerConfig / +// ClientConfig builders which typically set up the crypto providers +pub fn crypto_provider() -> &'static Arc { + if let Some(provider) = CryptoProvider::get_default() { + provider + } else { + #[cfg(feature = "rustls-aws-lc-rs")] + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + #[cfg(feature = "rustls-ring")] + let _ = rustls::crypto::ring::default_provider().install_default(); + + CryptoProvider::get_default().unwrap() + } +} diff --git a/quiche/src/h3/mod.rs b/quiche/src/h3/mod.rs index 4eaf0f78e6..a3ce948ff5 100644 --- a/quiche/src/h3/mod.rs +++ b/quiche/src/h3/mod.rs @@ -3225,6 +3225,14 @@ pub mod testing { use crate::testing; + pub(super) static KEY: &str = "examples/cert.key"; + + #[cfg(not(feature = "__rustls"))] + pub(super) static CERT: &str = "examples/cert.crt"; + + #[cfg(feature = "__rustls")] + pub(super) static CERT: &str = "examples/cert_rustls.crt"; + /// Session is an HTTP/3 test helper structure. It holds a client, server /// and pipe that allows them to communicate. /// @@ -3258,10 +3266,10 @@ pub mod testing { let mut config = crate::Config::new(crate::PROTOCOL_VERSION)?; config.load_cert_chain_from_pem_file( - &path_relative_to_manifest_dir("examples/cert.crt"), + &path_relative_to_manifest_dir(CERT), )?; config.load_priv_key_from_pem_file( - &path_relative_to_manifest_dir("examples/cert.key"), + &path_relative_to_manifest_dir(KEY), )?; config.set_application_protos(&[b"h3"])?; config.set_initial_max_data(1500); @@ -3594,12 +3602,8 @@ mod tests { let mut buf = [0; 65535]; let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -5553,12 +5557,8 @@ mod tests { /// Tests that the max header list size setting is enforced. fn request_max_header_size_limit() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); @@ -5690,12 +5690,8 @@ mod tests { /// Tests that we limit sending HEADERS based on the stream capacity. fn headers_blocked() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -5743,12 +5739,8 @@ mod tests { /// Ensure StreamBlocked when connection flow control prevents headers. fn headers_blocked_on_conn() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -5804,12 +5796,8 @@ mod tests { use crate::testing::decode_pkt; let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(10000); // large connection-level flow control config.set_initial_max_stream_data_bidi_local(80); @@ -5938,12 +5926,8 @@ mod tests { /// Ensure stream doesn't hang due to small cwnd. fn send_body_stream_blocked_by_small_cwnd() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(100000); // large connection-level flow control config.set_initial_max_stream_data_bidi_local(100000); @@ -6010,12 +5994,8 @@ mod tests { /// Ensure stream doesn't hang due to small cwnd. fn send_body_stream_blocked_zero_length() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(100000); // large connection-level flow control config.set_initial_max_stream_data_bidi_local(100000); @@ -6143,12 +6123,8 @@ mod tests { /// Tests that blocked 0-length DATA writes are reported correctly. fn zero_length_data_blocked() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(69); config.set_initial_max_stream_data_bidi_local(150); @@ -6198,12 +6174,8 @@ mod tests { /// Tests that receiving an empty SETTINGS frame is handled and reported. fn empty_settings() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); @@ -6228,12 +6200,8 @@ mod tests { /// Tests that receiving a H3_DATAGRAM setting is ok. fn dgram_setting() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -6273,12 +6241,8 @@ mod tests { /// an error. fn dgram_setting_no_tp() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -6325,12 +6289,8 @@ mod tests { /// Tests that receiving SETTINGS with prohibited values generates an error. fn settings_h2_prohibited() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -6435,12 +6395,8 @@ mod tests { /// Tests additional settings are actually exchanged by the peers. fn set_additional_settings() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150); @@ -6559,12 +6515,8 @@ mod tests { /// Send a single DATAGRAM and request. fn poll_datagram_cycling_no_read() { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); @@ -6603,12 +6555,8 @@ mod tests { let mut buf = [0; 65535]; let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); @@ -6688,12 +6636,8 @@ mod tests { let mut buf = [0; 65535]; let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); @@ -7063,12 +7007,8 @@ mod tests { let mut buf = [0; 65535]; let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(1500); config.set_initial_max_stream_data_bidi_local(150); diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs index 8578877043..2c7312d674 100644 --- a/quiche/src/lib.rs +++ b/quiche/src/lib.rs @@ -1023,6 +1023,7 @@ impl Config { /// specific key (e.g. in order to support resumption across multiple /// servers), in which case the application is also responsible for /// rotating the key to provide forward secrecy. + #[cfg(not(feature = "__rustls"))] pub fn set_ticket_key(&mut self, key: &[u8]) -> Result<()> { self.tls_ctx.set_ticket_key(key) } @@ -1462,6 +1463,7 @@ where path_challenge_rx_count: u64, /// List of supported application protocols. + #[cfg(not(feature = "__rustls"))] application_protos: Vec>, /// Total number of received packets. @@ -2019,6 +2021,7 @@ impl Connection { .path_challenge_recv_max_queue_len, path_challenge_rx_count: 0, + #[cfg(not(feature = "__rustls"))] application_protos: config.application_protos.clone(), recv_count: 0, @@ -3059,7 +3062,7 @@ impl Connection { } } - let mut payload = packet::decrypt_pkt( + let payload_res = packet::decrypt_pkt( &mut b, pn, pn_len, @@ -3068,7 +3071,22 @@ impl Connection { ) .map_err(|e| { drop_pkt_on_err(e, self.recv_count, self.is_server, &self.trace_id) - })?; + }); + + let mut payload = match payload_res { + Ok(payload) => payload, + Err(e) => { + #[cfg(feature = "__rustls")] + // rustls updates the secrets when deriving the next packet keys + // therefore needed to return the keys in case they are not + // verified successfully + if let Some((open, seal)) = aead_next { + let _ = open.return_next_key(); + let _ = seal.return_next_key(); + } + return Err(e); + }, + }; if self.pkt_num_spaces[epoch].recv_pkt_num.contains(pn) { trace!("{} ignored duplicate packet {}", self.trace_id, pn); @@ -7147,6 +7165,7 @@ impl Connection { /// If the connection is already established, it does nothing. fn do_handshake(&mut self, now: time::Instant) -> Result<()> { let mut ex_data = tls::ExData { + #[cfg(not(feature = "__rustls"))] application_protos: &self.application_protos, crypto_ctx: &mut self.crypto_ctx, @@ -7155,14 +7174,17 @@ impl Connection { local_error: &mut self.local_error, + #[cfg(not(feature = "__rustls"))] keylog: self.keylog.as_mut(), + #[cfg(not(feature = "__rustls"))] trace_id: &self.trace_id, recovery_config: self.recovery_config, tx_cap_factor: self.tx_cap_factor, + #[cfg(not(feature = "__rustls"))] is_server: self.is_server, }; @@ -9033,6 +9055,13 @@ impl TransportParams { pub mod testing { use super::*; + pub(super) static KEY: &str = "examples/cert.key"; + + #[cfg(not(feature = "__rustls"))] + pub(super) static CERT: &str = "examples/cert.crt"; + #[cfg(feature = "__rustls")] + pub(super) static CERT: &str = "examples/cert_rustls.crt"; + pub struct Pipe { pub client: Connection, pub server: Connection, @@ -9042,8 +9071,8 @@ pub mod testing { pub fn new(cc_algorithm_name: &str) -> Result { let mut config = Config::new(crate::PROTOCOL_VERSION)?; assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config.load_cert_chain_from_pem_file("examples/cert.crt")?; - config.load_priv_key_from_pem_file("examples/cert.key")?; + config.load_cert_chain_from_pem_file(CERT)?; + config.load_priv_key_from_pem_file(KEY)?; config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -9138,8 +9167,8 @@ pub mod testing { let server_addr = Pipe::server_addr(); let mut config = Config::new(crate::PROTOCOL_VERSION)?; - config.load_cert_chain_from_pem_file("examples/cert.crt")?; - config.load_priv_key_from_pem_file("examples/cert.key")?; + config.load_cert_chain_from_pem_file(CERT)?; + config.load_priv_key_from_pem_file(KEY)?; config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(30); config.set_initial_max_stream_data_bidi_local(15); @@ -9548,8 +9577,19 @@ mod tests { use crate::range_buf::RangeBuf; use rstest::rstest; + use super::testing::*; use super::*; + #[cfg(not(feature = "__rustls"))] + pub(super) static CERT_BIG: &str = "examples/cert-big.crt"; + #[cfg(not(feature = "__rustls"))] + pub(super) static ROOTCA: &str = "examples/rootca.crt"; + + #[cfg(feature = "__rustls")] + pub(super) static CERT_BIG: &str = "examples/cert-big_rustls.crt"; + #[cfg(feature = "__rustls")] + pub(super) static ROOTCA: &str = "examples/rootca_rustls.crt"; + #[test] fn transport_params() { // Server encodes, client decodes. @@ -9790,9 +9830,7 @@ mod tests { fn verify_custom_root() { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); config.verify_peer(true); - config - .load_verify_locations_from_file("examples/rootca.crt") - .unwrap(); + config.load_verify_locations_from_file(ROOTCA).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -9807,12 +9845,8 @@ mod tests { #[test] fn verify_client_invalid() { let mut server_config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - server_config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - server_config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + server_config.load_cert_chain_from_pem_file(CERT).unwrap(); + server_config.load_priv_key_from_pem_file(KEY).unwrap(); server_config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -9826,12 +9860,8 @@ mod tests { server_config.verify_peer(true); let mut client_config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - client_config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - client_config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + client_config.load_cert_chain_from_pem_file(CERT).unwrap(); + client_config.load_priv_key_from_pem_file(KEY).unwrap(); client_config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -9843,7 +9873,7 @@ mod tests { // The client is able to verify the server's certificate with the // appropriate CA. client_config - .load_verify_locations_from_file("examples/rootca.crt") + .load_verify_locations_from_file(ROOTCA) .unwrap(); client_config.verify_peer(true); @@ -9854,19 +9884,19 @@ mod tests { .unwrap(); assert_eq!(pipe.handshake(), Err(Error::TlsFail)); + #[cfg(not(feature = "__rustls"))] // Client did send a certificate. assert!(pipe.server.peer_cert().is_some()); + #[cfg(feature = "__rustls")] + // rustls does not provide the peer certificate when verification failed + assert!(pipe.server.peer_cert().is_none()); } #[test] fn verify_client_anonymous() { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -9993,6 +10023,7 @@ mod tests { // Disable session tickets on the server (SSL_OP_NO_TICKET) to avoid // triggering 1-RTT packet send with a CRYPTO frame. + #[cfg(not(feature = "__rustls"))] pipe.server.handshake.set_options(0x0000_4000); assert_eq!(pipe.handshake(), Ok(())); @@ -10062,6 +10093,7 @@ mod tests { } #[rstest] + #[cfg(not(feature = "__rustls"))] fn handshake_resumption( #[values("cubic", "bbr2", "bbr2_gcongestion")] cc_algorithm_name: &str, ) { @@ -10077,12 +10109,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10107,12 +10135,8 @@ mod tests { // Configure session on new connection and perform handshake. let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10170,12 +10194,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10235,12 +10255,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10310,12 +10326,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10371,12 +10383,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10440,12 +10448,8 @@ mod tests { ) { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert-big.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT_BIG).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10470,12 +10474,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert-big.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT_BIG).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10525,12 +10525,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -10586,12 +10582,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -12137,12 +12129,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -12342,12 +12330,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -12511,12 +12495,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -12789,12 +12769,8 @@ mod tests { ) { let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_application_protos(&[b"h3"]).unwrap(); config.set_initial_max_data(70); config.set_initial_max_stream_data_bidi_local(150000); @@ -13164,12 +13140,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13226,12 +13198,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13305,12 +13273,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13571,7 +13535,12 @@ mod tests { assert_eq!(pipe.handshake(), Ok(())); match pipe.client.peer_cert() { - Some(c) => assert_eq!(c.len(), 753), + Some(c) => { + #[cfg(not(feature = "__rustls"))] + assert_eq!(c.len(), 753); + #[cfg(feature = "__rustls")] + assert_eq!(c.len(), 847); + }, None => panic!("missing server certificate"), } @@ -13583,12 +13552,8 @@ mod tests { ) { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert-big.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT_BIG).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13611,12 +13576,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13680,12 +13641,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13754,12 +13711,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -13817,12 +13770,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -14246,12 +14195,8 @@ mod tests { config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config.set_initial_max_data(50000); config.set_initial_max_stream_data_bidi_local(12000); config.set_initial_max_stream_data_bidi_remote(12000); @@ -14298,12 +14243,8 @@ mod tests { ) { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -14328,6 +14269,10 @@ mod tests { } else { if cfg!(feature = "openssl") { Ok(12345) + } else if cfg!(feature = "rustls-ring") { + Ok(12320) + } else if cfg!(feature = "rustls-aws-lc-rs") { + Ok(12324) } else { Ok(12299) } @@ -14377,12 +14322,8 @@ mod tests { ) { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -14407,6 +14348,10 @@ mod tests { } else { if cfg!(feature = "openssl") { Ok(12345) + } else if cfg!(feature = "rustls-ring") { + Ok(12320) + } else if cfg!(feature = "rustls-aws-lc-rs") { + Ok(12324) } else { Ok(12299) } @@ -14663,12 +14608,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -14889,12 +14830,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15012,12 +14949,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15333,12 +15266,8 @@ mod tests { let mut config = Config::new(PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert-big.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT_BIG).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15448,12 +15377,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15534,12 +15459,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15574,12 +15495,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15651,12 +15568,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15700,12 +15613,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15750,12 +15659,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15804,12 +15709,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -15944,6 +15845,7 @@ mod tests { // OpenSSL does not provide a straightforward interface to deal with custom // off-load key signing. #[cfg(not(feature = "openssl"))] + #[cfg(not(feature = "__rustls"))] #[rstest] fn app_close_by_server_during_handshake_private_key_failure( #[values("cubic", "bbr2", "bbr2_gcongestion")] cc_algorithm_name: &str, @@ -16245,12 +16147,8 @@ mod tests { server_config.set_cc_algorithm_name(cc_algorithm_name), Ok(()) ); - server_config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - server_config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + server_config.load_cert_chain_from_pem_file(CERT).unwrap(); + server_config.load_priv_key_from_pem_file(KEY).unwrap(); server_config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16316,6 +16214,8 @@ mod tests { } else { if cfg!(feature = "openssl") { 13437 + } else if cfg!(feature = "__rustls") { + 13535 } else { 13421 } @@ -16333,12 +16233,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16382,6 +16278,8 @@ mod tests { } else { if cfg!(feature = "openssl") { 13959 + } else if cfg!(feature = "__rustls") { + 13753 } else { 13873 } @@ -16397,6 +16295,8 @@ mod tests { } else { if cfg!(feature = "openssl") { Ok(3959) + } else if cfg!(feature = "__rustls") { + Ok(3753) } else { Ok(3873) } @@ -16423,13 +16323,10 @@ mod tests { boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls()) .unwrap(); server_tls_ctx_builder - .set_certificate_chain_file("examples/cert.crt") + .set_certificate_chain_file(CERT) .unwrap(); server_tls_ctx_builder - .set_private_key_file( - "examples/cert.key", - boring::ssl::SslFiletype::PEM, - ) + .set_private_key_file(KEY, boring::ssl::SslFiletype::PEM) .unwrap(); let mut server_config = Config::with_boring_ssl_ctx_builder( @@ -16441,8 +16338,8 @@ mod tests { client_config.set_cc_algorithm_name(cc_algorithm_name), Ok(()) ); - client_config.load_cert_chain_from_pem_file("examples/cert.crt")?; - client_config.load_priv_key_from_pem_file("examples/cert.key")?; + client_config.load_cert_chain_from_pem_file(CERT)?; + client_config.load_priv_key_from_pem_file(KEY)?; for config in [&mut client_config, &mut server_config] { config.set_application_protos(&[b"proto1", b"proto2"])?; @@ -16481,13 +16378,10 @@ mod tests { boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls()) .unwrap(); server_tls_ctx_builder - .set_certificate_chain_file("examples/cert.crt") + .set_certificate_chain_file(CERT) .unwrap(); server_tls_ctx_builder - .set_private_key_file( - "examples/cert.key", - boring::ssl::SslFiletype::PEM, - ) + .set_private_key_file(KEY, boring::ssl::SslFiletype::PEM) .unwrap(); server_tls_ctx_builder.set_select_certificate_callback(|mut hello| { ::set_initial_congestion_window_packets_in_handshake( @@ -16509,8 +16403,8 @@ mod tests { ); let mut client_config = Config::new(crate::PROTOCOL_VERSION)?; - client_config.load_cert_chain_from_pem_file("examples/cert.crt")?; - client_config.load_priv_key_from_pem_file("examples/cert.key")?; + client_config.load_cert_chain_from_pem_file(CERT)?; + client_config.load_priv_key_from_pem_file(KEY)?; for config in [&mut client_config, &mut server_config] { config.set_application_protos(&[b"proto1", b"proto2"])?; @@ -16564,8 +16458,8 @@ mod tests { CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS, ); // From Pipe::new() - config.load_cert_chain_from_pem_file("examples/cert.crt")?; - config.load_priv_key_from_pem_file("examples/cert.key")?; + config.load_cert_chain_from_pem_file(CERT)?; + config.load_priv_key_from_pem_file(KEY)?; config.set_application_protos(&[b"proto1", b"proto2"])?; config.set_initial_max_data(1000000); config.set_initial_max_stream_data_bidi_local(15); @@ -16591,6 +16485,8 @@ mod tests { let expected = CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS * 1200 + if cfg!(feature = "openssl") { 1463 + } else if cfg!(feature = "__rustls") { + 1561 } else { 1447 }; @@ -16684,12 +16580,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16747,12 +16639,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16820,12 +16708,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16893,12 +16777,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -16981,12 +16861,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17041,12 +16917,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17091,12 +16963,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17240,12 +17108,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17328,12 +17192,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17396,12 +17256,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17451,12 +17307,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17487,12 +17339,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17543,12 +17391,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17612,12 +17456,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17638,12 +17478,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -17829,12 +17665,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18045,12 +17877,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18125,12 +17953,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18195,12 +18019,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18222,14 +18042,22 @@ mod tests { let mut recv_buf = [0; DATA_BYTES]; let send1_bytes = pipe.server.stream_send(1, &buf, true).unwrap(); assert_eq!(send1_bytes, match cc_algorithm_name { - #[cfg(feature = "openssl")] - "bbr2" => 14041, - #[cfg(not(feature = "openssl"))] - "bbr2" => 13955, - #[cfg(feature = "openssl")] - "bbr2_gcongestion" => 13966, - #[cfg(not(feature = "openssl"))] - "bbr2_gcongestion" => 13880, + "bbr2" => + if cfg!(feature = "openssl") { + 14041 + } else if cfg!(feature = "__rustls") { + 13835 + } else { + 13955 + }, + "bbr2_gcongestion" => + if cfg!(feature = "openssl") { + 13966 + } else if cfg!(feature = "__rustls") { + 13760 + } else { + 13880 + }, _ => 12000, }); assert_eq!( @@ -18445,12 +18273,8 @@ mod tests { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18631,12 +18455,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18733,12 +18553,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); @@ -18782,12 +18598,8 @@ mod tests { ) { let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap(); assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(())); - config - .load_cert_chain_from_pem_file("examples/cert.crt") - .unwrap(); - config - .load_priv_key_from_pem_file("examples/cert.key") - .unwrap(); + config.load_cert_chain_from_pem_file(CERT).unwrap(); + config.load_priv_key_from_pem_file(KEY).unwrap(); config .set_application_protos(&[b"proto1", b"proto2"]) .unwrap(); diff --git a/quiche/src/packet.rs b/quiche/src/packet.rs index d4399f9f37..28b0fae901 100644 --- a/quiche/src/packet.rs +++ b/quiche/src/packet.rs @@ -579,21 +579,32 @@ pub fn decrypt_hdr( let ciphertext = ciphertext.as_mut(); - let mask = aead.new_mask(sample.as_ref())?; + #[cfg(not(feature = "__rustls"))] + let pn_len = { + let mask = aead.new_mask(sample.as_ref())?; - if Header::is_long(first) { - first ^= mask[0] & 0x0f; - } else { - first ^= mask[0] & 0x1f; - } + if Header::is_long(first) { + first ^= mask[0] & 0x0f; + } else { + first ^= mask[0] & 0x1f; + } - let pn_len = usize::from((first & PKT_NUM_MASK) + 1); + let pn_len = usize::from((first & PKT_NUM_MASK) + 1); - let ciphertext = &mut ciphertext[..pn_len]; + let ciphertext = &mut ciphertext[..pn_len]; - for i in 0..pn_len { - ciphertext[i] ^= mask[i + 1]; - } + for i in 0..pn_len { + ciphertext[i] ^= mask[i + 1]; + } + + pn_len + }; + + #[cfg(feature = "__rustls")] + let pn_len = { + aead.decrypt_hdr(sample.as_ref(), &mut first, ciphertext)?; + usize::from((first & PKT_NUM_MASK) + 1) + }; // Extract packet number corresponding to the decoded length. let pn = match pn_len { @@ -668,23 +679,30 @@ pub fn encrypt_hdr( let sample = &payload [MAX_PKT_NUM_LEN - pn_len..SAMPLE_LEN + (MAX_PKT_NUM_LEN - pn_len)]; - let mask = aead.new_mask(sample)?; - let (mut first, mut rest) = b.split_at(1)?; let first = first.as_mut(); - if Header::is_long(first[0]) { - first[0] ^= mask[0] & 0x0f; - } else { - first[0] ^= mask[0] & 0x1f; - } - let pn_buf = rest.slice_last(pn_len)?; - for i in 0..pn_len { - pn_buf[i] ^= mask[i + 1]; + + #[cfg(not(feature = "__rustls"))] + { + let mask = aead.new_mask(sample)?; + + if Header::is_long(first[0]) { + first[0] ^= mask[0] & 0x0f; + } else { + first[0] ^= mask[0] & 0x1f; + } + + for i in 0..pn_len { + pn_buf[i] ^= mask[i + 1]; + } } + #[cfg(feature = "__rustls")] + aead.encrypt_hdr(sample, &mut first[0], pn_buf)?; + Ok(()) } @@ -1527,6 +1545,7 @@ mod tests { } #[test] + #[cfg(not(feature = "__rustls"))] fn decrypt_chacha20() { let secret = [ 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, @@ -1889,6 +1908,7 @@ mod tests { } #[test] + #[cfg(not(feature = "__rustls"))] fn encrypt_chacha20() { let secret = [ 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, diff --git a/quiche/src/rand.rs b/quiche/src/rand.rs index 40ea086347..0b2d3575da 100644 --- a/quiche/src/rand.rs +++ b/quiche/src/rand.rs @@ -24,12 +24,22 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(feature = "__rustls")] +use crate::crypto::crypto_provider; + +#[cfg(not(feature = "__rustls"))] pub fn rand_bytes(buf: &mut [u8]) { unsafe { RAND_bytes(buf.as_mut_ptr(), buf.len()); } } +#[cfg(feature = "__rustls")] +pub fn rand_bytes(buf: &mut [u8]) { + let provider = crypto_provider(); + provider.secure_random.fill(buf).unwrap() +} + pub fn rand_u8() -> u8 { let mut buf = [0; 1]; @@ -59,6 +69,7 @@ pub fn rand_u64_uniform(max: u64) -> u64 { r / chunk_size } +#[cfg(not(feature = "__rustls"))] extern "C" { fn RAND_bytes(buf: *mut u8, len: libc::size_t) -> libc::c_int; } diff --git a/quiche/src/tls/boringssl.rs b/quiche/src/tls/boringssl_openssl/boringssl.rs similarity index 100% rename from quiche/src/tls/boringssl.rs rename to quiche/src/tls/boringssl_openssl/boringssl.rs diff --git a/quiche/src/tls/boringssl_openssl/mod.rs b/quiche/src/tls/boringssl_openssl/mod.rs new file mode 100644 index 0000000000..bdd880077d --- /dev/null +++ b/quiche/src/tls/boringssl_openssl/mod.rs @@ -0,0 +1,1226 @@ +// Copyright (C) 2018-2019, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ffi; +use std::ptr; +use std::slice; + +use std::io::Write; + +use std::sync::LazyLock; + +use libc::c_char; +use libc::c_int; +use libc::c_uint; +use libc::c_void; + +use crate::Error; +use crate::Result; + +use crate::Connection; +use crate::ConnectionError; + +use crate::crypto; +use crate::packet; + +use crate::tls::ExData; + +const TLS1_3_VERSION: u16 = 0x0304; +const TLS_ALERT_ERROR: u64 = 0x100; +const INTERNAL_ERROR: u64 = 0x01; + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct SSL_METHOD { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct SSL_CTX { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct SSL { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct SSL_CIPHER { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct SSL_SESSION { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct X509_VERIFY_PARAM { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +#[cfg(windows)] +struct X509_STORE { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct X509_STORE_CTX { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +#[cfg(windows)] +struct X509 { + _unused: c_void, +} + +#[allow(non_camel_case_types)] +#[repr(transparent)] +struct STACK_OF { + _unused: c_void, +} + +#[cfg(test)] +#[repr(C)] +#[allow(non_camel_case_types)] +#[allow(dead_code)] +enum ssl_private_key_result_t { + ssl_private_key_success, + ssl_private_key_retry, + ssl_private_key_failure, +} + +/// BoringSSL ex_data index for quiche connections. +pub static QUICHE_EX_DATA_INDEX: LazyLock = LazyLock::new(|| unsafe { + SSL_get_ex_new_index(0, ptr::null(), ptr::null(), ptr::null(), ptr::null()) +}); + +pub struct Context(*mut SSL_CTX); + +impl Context { + // Note: some vendor-specific methods are implemented by each vendor's + // submodule (openssl-quictls / boringssl). + pub fn new() -> Result { + unsafe { + let ctx_raw = SSL_CTX_new(TLS_method()); + + let mut ctx = Context(ctx_raw); + + ctx.set_session_callback(); + + ctx.load_ca_certs()?; + + Ok(ctx) + } + } + + #[cfg(feature = "boringssl-boring-crate")] + pub fn from_boring( + ssl_ctx_builder: boring::ssl::SslContextBuilder, + ) -> Context { + use foreign_types_shared::ForeignType; + + let mut ctx = Context(ssl_ctx_builder.build().into_ptr() as _); + ctx.set_session_callback(); + + ctx + } + + pub fn new_handshake(&mut self) -> Result { + unsafe { + let ssl = SSL_new(self.as_mut_ptr()); + Ok(Handshake::new(ssl)) + } + } + + pub fn load_verify_locations_from_file(&mut self, file: &str) -> Result<()> { + let file = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; + map_result(unsafe { + SSL_CTX_load_verify_locations( + self.as_mut_ptr(), + file.as_ptr(), + std::ptr::null(), + ) + }) + } + + pub fn load_verify_locations_from_directory( + &mut self, path: &str, + ) -> Result<()> { + let path = ffi::CString::new(path).map_err(|_| Error::TlsFail)?; + map_result(unsafe { + SSL_CTX_load_verify_locations( + self.as_mut_ptr(), + std::ptr::null(), + path.as_ptr(), + ) + }) + } + + pub fn use_certificate_chain_file(&mut self, file: &str) -> Result<()> { + let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; + map_result(unsafe { + SSL_CTX_use_certificate_chain_file(self.as_mut_ptr(), cstr.as_ptr()) + }) + } + + pub fn use_privkey_file(&mut self, file: &str) -> Result<()> { + let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; + map_result(unsafe { + SSL_CTX_use_PrivateKey_file(self.as_mut_ptr(), cstr.as_ptr(), 1) + }) + } + + #[cfg(not(windows))] + fn load_ca_certs(&mut self) -> Result<()> { + unsafe { map_result(SSL_CTX_set_default_verify_paths(self.as_mut_ptr())) } + } + + #[cfg(windows)] + fn load_ca_certs(&mut self) -> Result<()> { + unsafe { + let cstr = ffi::CString::new("Root").map_err(|_| Error::TlsFail)?; + let sys_store = + windows_sys::Win32::Security::Cryptography::CertOpenSystemStoreA( + 0, + cstr.as_ptr() as windows_sys::core::PCSTR, + ); + if sys_store.is_null() { + return Err(Error::TlsFail); + } + + let ctx_store = SSL_CTX_get_cert_store(self.as_mut_ptr()); + if ctx_store.is_null() { + return Err(Error::TlsFail); + } + + let mut ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore( + sys_store, + ptr::null(), + ); + + while !ctx_p.is_null() { + let in_p = (*ctx_p).pbCertEncoded as *const u8; + + let cert = d2i_X509( + ptr::null_mut(), + &in_p, + (*ctx_p).cbCertEncoded as i32, + ); + if !cert.is_null() { + X509_STORE_add_cert(ctx_store, cert); + } + + X509_free(cert); + + ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore( + sys_store, ctx_p, + ); + } + + // tidy up + windows_sys::Win32::Security::Cryptography::CertFreeCertificateContext(ctx_p); + windows_sys::Win32::Security::Cryptography::CertCloseStore( + sys_store, 0, + ); + } + + Ok(()) + } + + fn set_session_callback(&mut self) { + unsafe { + // This is needed to enable the session callback on the client. On + // the server it doesn't do anything. + SSL_CTX_set_session_cache_mode( + self.as_mut_ptr(), + 0x0001, // SSL_SESS_CACHE_CLIENT + ); + + SSL_CTX_sess_set_new_cb(self.as_mut_ptr(), Some(new_session)); + }; + } + + pub fn set_verify(&mut self, verify: bool) { + // true -> 0x01 SSL_VERIFY_PEER + // false -> 0x00 SSL_VERIFY_NONE + let mode = i32::from(verify); + + // Note: Base on two used modes(see above), it seems ok for both, bssl and + // ossl. If mode needs to be ored then it may need to be adjusted. + unsafe { + SSL_CTX_set_verify(self.as_mut_ptr(), mode, None); + } + } + + pub fn enable_keylog(&mut self) { + unsafe { + SSL_CTX_set_keylog_callback(self.as_mut_ptr(), Some(keylog)); + } + } + + pub fn set_alpn(&mut self, v: &[&[u8]]) -> Result<()> { + let mut protos: Vec = Vec::new(); + + for proto in v { + protos.push(proto.len() as u8); + protos.extend_from_slice(proto); + } + + // Configure ALPN for servers. + unsafe { + SSL_CTX_set_alpn_select_cb( + self.as_mut_ptr(), + Some(select_alpn), + ptr::null_mut(), + ); + } + + // Configure ALPN for clients. + map_result_zero_is_success(unsafe { + SSL_CTX_set_alpn_protos( + self.as_mut_ptr(), + protos.as_ptr(), + protos.len(), + ) + }) + } + + pub fn set_ticket_key(&mut self, key: &[u8]) -> Result<()> { + map_result(unsafe { + SSL_CTX_set_tlsext_ticket_keys( + self.as_mut_ptr(), + key.as_ptr(), + key.len(), + ) + }) + } + + fn as_mut_ptr(&mut self) -> *mut SSL_CTX { + self.0 + } +} + +// NOTE: These traits are not automatically implemented for Context due to the +// raw pointer it wraps. However, the underlying data is not aliased (as Context +// should be its only owner), and there is no interior mutability, as the +// pointer is not accessed directly outside of this module, and the Context +// object API should preserve Rust's borrowing guarantees. +unsafe impl std::marker::Send for Context {} +unsafe impl std::marker::Sync for Context {} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { SSL_CTX_free(self.as_mut_ptr()) } + } +} + +pub struct Handshake { + /// Raw pointer + ptr: *mut SSL, + /// SSL_process_quic_post_handshake should be called when whenever + /// SSL_provide_quic_data is called to process the provided data. + provided_data_outstanding: bool, +} + +impl Handshake { + // Note: some vendor-specific methods are implemented by each vendor's + // submodule (openssl-quictls / boringssl). + #[cfg(feature = "ffi")] + pub unsafe fn from_ptr(ssl: *mut c_void) -> Handshake { + Handshake::new(ssl as *mut SSL) + } + + fn new(ptr: *mut SSL) -> Handshake { + Handshake { + ptr, + provided_data_outstanding: false, + } + } + + pub fn get_error(&self, ret_code: c_int) -> c_int { + unsafe { SSL_get_error(self.as_ptr(), ret_code) } + } + + pub fn init(&mut self, is_server: bool) -> Result<()> { + self.set_state(is_server); + + self.set_min_proto_version(TLS1_3_VERSION)?; + self.set_max_proto_version(TLS1_3_VERSION)?; + + self.set_quic_method()?; + + // TODO: the early data context should include transport parameters and + // HTTP/3 SETTINGS in wire format. + self.set_quic_early_data_context(b"quiche")?; + + self.set_quiet_shutdown(true); + + Ok(()) + } + + pub fn use_legacy_codepoint(&mut self, use_legacy: bool) { + unsafe { + SSL_set_quic_use_legacy_codepoint( + self.as_mut_ptr(), + use_legacy as c_int, + ); + } + } + + pub fn set_state(&mut self, is_server: bool) { + unsafe { + if is_server { + SSL_set_accept_state(self.as_mut_ptr()); + } else { + SSL_set_connect_state(self.as_mut_ptr()); + } + } + } + + pub fn set_ex_data(&mut self, idx: c_int, data: *const T) -> Result<()> { + map_result(unsafe { + let ptr = data as *mut c_void; + SSL_set_ex_data(self.as_mut_ptr(), idx, ptr) + }) + } + + pub fn set_quic_method(&mut self) -> Result<()> { + map_result(unsafe { + SSL_set_quic_method(self.as_mut_ptr(), &QUICHE_STREAM_METHOD) + }) + } + + pub fn set_min_proto_version(&mut self, version: u16) -> Result<()> { + map_result(unsafe { + SSL_set_min_proto_version(self.as_mut_ptr(), version) + }) + } + + pub fn set_max_proto_version(&mut self, version: u16) -> Result<()> { + map_result(unsafe { + SSL_set_max_proto_version(self.as_mut_ptr(), version) + }) + } + + pub fn set_quiet_shutdown(&mut self, mode: bool) { + unsafe { SSL_set_quiet_shutdown(self.as_mut_ptr(), i32::from(mode)) } + } + + pub fn set_host_name(&mut self, name: &str) -> Result<()> { + let cstr = ffi::CString::new(name).map_err(|_| Error::TlsFail)?; + let rc = + unsafe { SSL_set_tlsext_host_name(self.as_mut_ptr(), cstr.as_ptr()) }; + self.map_result_ssl(rc)?; + + let param = unsafe { SSL_get0_param(self.as_mut_ptr()) }; + + map_result(unsafe { + X509_VERIFY_PARAM_set1_host(param, cstr.as_ptr(), name.len()) + }) + } + + pub fn set_quic_transport_params(&mut self, buf: &[u8]) -> Result<()> { + let rc = unsafe { + SSL_set_quic_transport_params( + self.as_mut_ptr(), + buf.as_ptr(), + buf.len(), + ) + }; + self.map_result_ssl(rc) + } + + pub fn quic_transport_params(&self) -> &[u8] { + let mut ptr: *const u8 = ptr::null(); + let mut len: usize = 0; + + unsafe { + SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len); + } + + if len == 0 { + return &mut []; + } + + unsafe { slice::from_raw_parts(ptr, len) } + } + + pub fn alpn_protocol(&self) -> &[u8] { + let mut ptr: *const u8 = ptr::null(); + let mut len: u32 = 0; + + unsafe { + SSL_get0_alpn_selected(self.as_ptr(), &mut ptr, &mut len); + } + + if len == 0 { + return &mut []; + } + + unsafe { slice::from_raw_parts(ptr, len as usize) } + } + + pub fn server_name(&self) -> Option<&str> { + let s = unsafe { + let ptr = SSL_get_servername( + self.as_ptr(), + 0, // TLSEXT_NAMETYPE_host_name + ); + + if ptr.is_null() { + return None; + } + + ffi::CStr::from_ptr(ptr) + }; + + s.to_str().ok() + } + + pub fn provide_data( + &mut self, level: crypto::Level, buf: &[u8], + ) -> Result<()> { + self.provided_data_outstanding = true; + let rc = unsafe { + SSL_provide_quic_data( + self.as_mut_ptr(), + level, + buf.as_ptr(), + buf.len(), + ) + }; + self.map_result_ssl(rc) + } + + pub fn do_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { + self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?; + let rc = unsafe { SSL_do_handshake(self.as_mut_ptr()) }; + self.set_ex_data::(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; + + self.set_transport_error(ex_data, rc); + self.map_result_ssl(rc) + } + + pub fn process_post_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { + // If SSL_provide_quic_data hasn't been called since we last called + // SSL_process_quic_post_handshake, then there's nothing to do. + if !self.provided_data_outstanding { + return Ok(()); + } + self.provided_data_outstanding = false; + + self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?; + let rc = unsafe { SSL_process_quic_post_handshake(self.as_mut_ptr()) }; + self.set_ex_data::(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; + + self.set_transport_error(ex_data, rc); + self.map_result_ssl(rc) + } + + pub fn write_level(&self) -> crypto::Level { + unsafe { SSL_quic_write_level(self.as_ptr()) } + } + + pub fn cipher(&self) -> Option { + let cipher = + map_result_ptr(unsafe { SSL_get_current_cipher(self.as_ptr()) }); + + get_cipher_from_ptr(cipher.ok()?).ok() + } + + #[cfg(test)] + pub fn set_options(&mut self, opts: u32) { + unsafe { + SSL_set_options(self.as_mut_ptr(), opts); + } + } + + pub fn is_completed(&self) -> bool { + unsafe { SSL_in_init(self.as_ptr()) == 0 } + } + + pub fn is_resumed(&self) -> bool { + unsafe { SSL_session_reused(self.as_ptr()) == 1 } + } + + pub fn clear(&mut self) -> Result<()> { + let rc = unsafe { SSL_clear(self.as_mut_ptr()) }; + self.map_result_ssl(rc) + } + + fn as_ptr(&self) -> *const SSL { + self.ptr + } + + fn as_mut_ptr(&mut self) -> *mut SSL { + self.ptr + } + + fn map_result_ssl(&mut self, bssl_result: c_int) -> Result<()> { + match bssl_result { + 1 => Ok(()), + + _ => { + let ssl_err = self.get_error(bssl_result); + match ssl_err { + // SSL_ERROR_SSL + 1 => { + log_ssl_error(); + + Err(Error::TlsFail) + }, + + // SSL_ERROR_WANT_READ + 2 => Err(Error::Done), + + // SSL_ERROR_WANT_WRITE + 3 => Err(Error::Done), + + // SSL_ERROR_WANT_X509_LOOKUP + 4 => Err(Error::Done), + + // SSL_ERROR_SYSCALL + 5 => Err(Error::TlsFail), + + // SSL_ERROR_PENDING_SESSION + 11 => Err(Error::Done), + + // SSL_ERROR_PENDING_CERTIFICATE + 12 => Err(Error::Done), + + // SSL_ERROR_WANT_PRIVATE_KEY_OPERATION + 13 => Err(Error::Done), + + // SSL_ERROR_PENDING_TICKET + 14 => Err(Error::Done), + + // SSL_ERROR_EARLY_DATA_REJECTED + 15 => { + self.reset_early_data_reject(); + Err(Error::Done) + }, + + // SSL_ERROR_WANT_CERTIFICATE_VERIFY + 16 => Err(Error::Done), + + _ => Err(Error::TlsFail), + } + }, + } + } + + fn set_transport_error(&mut self, ex_data: &mut ExData, bssl_result: c_int) { + // SSL_ERROR_SSL + if self.get_error(bssl_result) == 1 { + // SSL_ERROR_SSL can't be recovered so ensure we set a + // local_error so the connection is closed. + // See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html + if ex_data.local_error.is_none() { + *ex_data.local_error = Some(ConnectionError { + is_app: false, + error_code: INTERNAL_ERROR, + reason: Vec::new(), + }) + } + } + } + + #[cfg(feature = "boringssl-boring-crate")] + pub(crate) fn ssl_mut(&mut self) -> &mut boring::ssl::SslRef { + use foreign_types_shared::ForeignTypeRef; + + unsafe { boring::ssl::SslRef::from_ptr_mut(self.as_mut_ptr() as _) } + } +} + +// NOTE: These traits are not automatically implemented for Handshake due to the +// raw pointer it wraps. However, the underlying data is not aliased (as +// Handshake should be its only owner), and there is no interior mutability, as +// the pointer is not accessed directly outside of this module, and the +// Handshake object API should preserve Rust's borrowing guarantees. +unsafe impl std::marker::Send for Handshake {} +unsafe impl std::marker::Sync for Handshake {} + +impl Drop for Handshake { + fn drop(&mut self) { + unsafe { SSL_free(self.as_mut_ptr()) } + } +} + +impl<'a> ExData<'a> { + fn from_ssl_ptr(ptr: *const SSL) -> Option<&'a mut Self> { + get_ex_data_from_ptr::(ptr, *QUICHE_EX_DATA_INDEX) + } + + #[cfg(feature = "boringssl-boring-crate")] + pub fn from_ssl_ref(ssl: &mut boring::ssl::SslRef) -> Option<&mut Self> { + use boring::ex_data::Index; + + // SAFETY: the QUICHE_EX_DATA_INDEX index is guaranteed to be created, + // and the associated data is always `ExData`. + let idx: Index = + unsafe { Index::from_raw(*QUICHE_EX_DATA_INDEX) }; + + ssl.ex_data_mut(idx) + } +} + +fn get_ex_data_from_ptr<'a, T>(ptr: *const SSL, idx: c_int) -> Option<&'a mut T> { + unsafe { + let data = SSL_get_ex_data(ptr, idx) as *mut T; + data.as_mut() + } +} + +fn get_cipher_from_ptr(cipher: *const SSL_CIPHER) -> Result { + let cipher_id = unsafe { SSL_CIPHER_get_id(cipher) }; + + let alg = match cipher_id { + 0x0300_1301 => crypto::Algorithm::AES128_GCM, + 0x0300_1302 => crypto::Algorithm::AES256_GCM, + 0x0300_1303 => crypto::Algorithm::ChaCha20_Poly1305, + _ => return Err(Error::TlsFail), + }; + + Ok(alg) +} + +extern "C" fn set_read_secret( + ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER, + secret: *const u8, secret_len: usize, +) -> c_int { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return 0, + }; + + trace!("{} set read secret lvl={:?}", ex_data.trace_id, level); + + let space = match level { + crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], + crypto::Level::ZeroRTT => + &mut ex_data.crypto_ctx[packet::Epoch::Application], + crypto::Level::Handshake => + &mut ex_data.crypto_ctx[packet::Epoch::Handshake], + crypto::Level::OneRTT => + &mut ex_data.crypto_ctx[packet::Epoch::Application], + }; + + let aead = match get_cipher_from_ptr(cipher) { + Ok(v) => v, + + Err(_) => return 0, + }; + + // 0-RTT read secrets are present only on the server. + if level != crypto::Level::ZeroRTT || ex_data.is_server { + let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; + + let open = match crypto::Open::from_secret(aead, secret) { + Ok(v) => v, + + Err(_) => return 0, + }; + + if level == crypto::Level::ZeroRTT { + space.crypto_0rtt_open = Some(open); + return 1; + } + + space.crypto_open = Some(open); + } + + 1 +} + +extern "C" fn set_write_secret( + ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER, + secret: *const u8, secret_len: usize, +) -> c_int { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return 0, + }; + + trace!("{} set write secret lvl={:?}", ex_data.trace_id, level); + + let space = match level { + crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], + crypto::Level::ZeroRTT => + &mut ex_data.crypto_ctx[packet::Epoch::Application], + crypto::Level::Handshake => + &mut ex_data.crypto_ctx[packet::Epoch::Handshake], + crypto::Level::OneRTT => + &mut ex_data.crypto_ctx[packet::Epoch::Application], + }; + + let aead = match get_cipher_from_ptr(cipher) { + Ok(v) => v, + + Err(_) => return 0, + }; + + // 0-RTT write secrets are present only on the client. + if level != crypto::Level::ZeroRTT || !ex_data.is_server { + let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; + + let seal = match crypto::Seal::from_secret(aead, secret) { + Ok(v) => v, + + Err(_) => return 0, + }; + + space.crypto_seal = Some(seal); + } + + 1 +} + +extern "C" fn add_handshake_data( + ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize, +) -> c_int { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return 0, + }; + + trace!( + "{} write message lvl={:?} len={}", + ex_data.trace_id, + level, + len + ); + + let buf = unsafe { slice::from_raw_parts(data, len) }; + + let space = match level { + crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], + crypto::Level::ZeroRTT => unreachable!(), + crypto::Level::Handshake => + &mut ex_data.crypto_ctx[packet::Epoch::Handshake], + crypto::Level::OneRTT => + &mut ex_data.crypto_ctx[packet::Epoch::Application], + }; + + if space.crypto_stream.send.write(buf, false).is_err() { + return 0; + } + + 1 +} + +extern "C" fn flush_flight(_ssl: *mut SSL) -> c_int { + // We don't really need to anything here since the output packets are + // generated separately, when conn.send() is called. + + 1 +} + +extern "C" fn send_alert( + ssl: *mut SSL, level: crypto::Level, alert: u8, +) -> c_int { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return 0, + }; + + trace!( + "{} send alert lvl={:?} alert={:x}", + ex_data.trace_id, + level, + alert + ); + + let error: u64 = TLS_ALERT_ERROR + u64::from(alert); + *ex_data.local_error = Some(ConnectionError { + is_app: false, + error_code: error, + reason: Vec::new(), + }); + + 1 +} + +extern "C" fn keylog(ssl: *const SSL, line: *const c_char) { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return, + }; + + if let Some(keylog) = &mut ex_data.keylog { + let data = unsafe { ffi::CStr::from_ptr(line).to_bytes() }; + + let mut full_line = Vec::with_capacity(data.len() + 1); + full_line.extend_from_slice(data); + full_line.push(b'\n'); + + keylog.write_all(&full_line[..]).ok(); + keylog.flush().ok(); + } +} + +extern "C" fn select_alpn( + ssl: *mut SSL, out: *mut *const u8, out_len: *mut u8, inp: *mut u8, + in_len: c_uint, _arg: *mut c_void, +) -> c_int { + // SSL_TLSEXT_ERR_OK 0 + // SSL_TLSEXT_ERR_ALERT_WARNING 1 + // SSL_TLSEXT_ERR_ALERT_FATAL 2 + // SSL_TLSEXT_ERR_NOACK 3 + + // Boringssl internally overwrite the return value from this callback, if the + // returned value is SSL_TLSEXT_ERR_NOACK and is quic, then the value gets + // overwritten to SSL_TLSEXT_ERR_ALERT_FATAL. In contrast openssl/quictls does + // not do that, so we need to explicitly respond with + // SSL_TLSEXT_ERR_ALERT_FATAL in case it is needed. + // TLS_ERROR is redefined for each vendor. + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return TLS_ERROR, + }; + + if ex_data.application_protos.is_empty() { + return TLS_ERROR; + } + + let mut protos = octets::Octets::with_slice(unsafe { + slice::from_raw_parts(inp, in_len as usize) + }); + + while let Ok(proto) = protos.get_bytes_with_u8_length() { + let found = ex_data.application_protos.iter().any(|expected| { + trace!( + "checking peer ALPN {:?} against {:?}", + std::str::from_utf8(proto.as_ref()), + std::str::from_utf8(expected.as_slice()) + ); + + if expected.len() == proto.len() && + expected.as_slice() == proto.as_ref() + { + unsafe { + *out = expected.as_slice().as_ptr(); + *out_len = expected.len() as u8; + } + + return true; + } + + false + }); + + if found { + return 0; // SSL_TLSEXT_ERR_OK + } + } + + TLS_ERROR +} + +extern "C" fn new_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int { + let ex_data = match ExData::from_ssl_ptr(ssl) { + Some(v) => v, + + None => return 0, + }; + + let handshake = Handshake::new(ssl); + let peer_params = handshake.quic_transport_params(); + + // Serialize session object into buffer. + let session_bytes = match get_session_bytes(session) { + Ok(v) => v, + Err(_) => return 0, + }; + + let mut buffer = + Vec::with_capacity(8 + peer_params.len() + 8 + session_bytes.len()); + + let session_bytes_len = session_bytes.len() as u64; + + if buffer.write(&session_bytes_len.to_be_bytes()).is_err() { + std::mem::forget(handshake); + return 0; + } + + if buffer.write(&session_bytes).is_err() { + std::mem::forget(handshake); + return 0; + } + + let peer_params_len = peer_params.len() as u64; + + if buffer.write(&peer_params_len.to_be_bytes()).is_err() { + std::mem::forget(handshake); + return 0; + } + + if buffer.write(peer_params).is_err() { + std::mem::forget(handshake); + return 0; + } + + *ex_data.session = Some(buffer); + + // Prevent handshake from being freed, as we still need it. + std::mem::forget(handshake); + + 0 +} + +pub fn map_result(bssl_result: c_int) -> Result<()> { + match bssl_result { + 1 => Ok(()), + _ => Err(Error::TlsFail), + } +} + +pub fn map_result_zero_is_success(bssl_result: c_int) -> Result<()> { + match bssl_result { + 0 => Ok(()), + _ => Err(Error::TlsFail), + } +} + +pub fn map_result_ptr<'a, T>(bssl_result: *const T) -> Result<&'a T> { + match unsafe { bssl_result.as_ref() } { + Some(v) => Ok(v), + None => Err(Error::TlsFail), + } +} + +fn log_ssl_error() { + let mut err = [0u8; 1024]; + + unsafe { + let e = ERR_peek_error(); + ERR_error_string_n(e, err.as_mut_ptr() as *mut c_char, err.len()); + } + + let cstr = ffi::CStr::from_bytes_until_nul(&err) + .expect("ERR_error_string_n should write a null terminated string"); + + trace!( + "{}", + cstr.to_str() + .expect("ERR_error_string_n should create a valid UTF-8 message") + ); +} + +extern "C" { + // Note: some vendor-specific methods are implemented by each vendor's + // submodule (openssl-quictls / boringssl). + + // SSL_METHOD + fn TLS_method() -> *const SSL_METHOD; + + // SSL_CTX + fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX; + fn SSL_CTX_free(ctx: *mut SSL_CTX); + + fn SSL_CTX_use_certificate_chain_file( + ctx: *mut SSL_CTX, file: *const c_char, + ) -> c_int; + + fn SSL_CTX_use_PrivateKey_file( + ctx: *mut SSL_CTX, file: *const c_char, ty: c_int, + ) -> c_int; + + fn SSL_CTX_load_verify_locations( + ctx: *mut SSL_CTX, file: *const c_char, path: *const c_char, + ) -> c_int; + + #[cfg(not(windows))] + fn SSL_CTX_set_default_verify_paths(ctx: *mut SSL_CTX) -> c_int; + + #[cfg(windows)] + fn SSL_CTX_get_cert_store(ctx: *mut SSL_CTX) -> *mut X509_STORE; + + fn SSL_CTX_set_verify( + ctx: *mut SSL_CTX, mode: c_int, + cb: Option< + unsafe extern "C" fn( + ok: c_int, + store_ctx: *mut X509_STORE_CTX, + ) -> c_int, + >, + ); + + fn SSL_CTX_set_keylog_callback( + ctx: *mut SSL_CTX, + cb: Option, + ); + + fn SSL_CTX_set_alpn_protos( + ctx: *mut SSL_CTX, protos: *const u8, protos_len: usize, + ) -> c_int; + + fn SSL_CTX_set_alpn_select_cb( + ctx: *mut SSL_CTX, + cb: Option< + unsafe extern "C" fn( + ssl: *mut SSL, + out: *mut *const u8, + out_len: *mut u8, + inp: *mut u8, + in_len: c_uint, + arg: *mut c_void, + ) -> c_int, + >, + arg: *mut c_void, + ); + + fn SSL_CTX_sess_set_new_cb( + ctx: *mut SSL_CTX, + cb: Option< + unsafe extern "C" fn( + ssl: *mut SSL, + session: *mut SSL_SESSION, + ) -> c_int, + >, + ); + + fn SSL_new(ctx: *mut SSL_CTX) -> *mut SSL; + + fn SSL_get_error(ssl: *const SSL, ret_code: c_int) -> c_int; + + fn SSL_set_accept_state(ssl: *mut SSL); + fn SSL_set_connect_state(ssl: *mut SSL); + + fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM; + + fn SSL_set_ex_data(ssl: *mut SSL, idx: c_int, ptr: *mut c_void) -> c_int; + fn SSL_get_ex_data(ssl: *const SSL, idx: c_int) -> *mut c_void; + + fn SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER; + + fn SSL_set_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int; + + fn SSL_get_SSL_CTX(ssl: *const SSL) -> *mut SSL_CTX; + + fn SSL_set_quiet_shutdown(ssl: *mut SSL, mode: c_int); + + fn SSL_set_quic_transport_params( + ssl: *mut SSL, params: *const u8, params_len: usize, + ) -> c_int; + + fn SSL_set_quic_method( + ssl: *mut SSL, quic_method: *const SSL_QUIC_METHOD, + ) -> c_int; + + fn SSL_set_quic_use_legacy_codepoint(ssl: *mut SSL, use_legacy: c_int); + + #[cfg(test)] + fn SSL_set_options(ssl: *mut SSL, opts: u32) -> u32; + + fn SSL_get_peer_quic_transport_params( + ssl: *const SSL, out_params: *mut *const u8, out_params_len: *mut usize, + ); + + fn SSL_get0_alpn_selected( + ssl: *const SSL, out: *mut *const u8, out_len: *mut u32, + ); + + fn SSL_get_servername(ssl: *const SSL, ty: c_int) -> *const c_char; + + fn SSL_provide_quic_data( + ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize, + ) -> c_int; + + fn SSL_process_quic_post_handshake(ssl: *mut SSL) -> c_int; + + fn SSL_do_handshake(ssl: *mut SSL) -> c_int; + + fn SSL_quic_write_level(ssl: *const SSL) -> crypto::Level; + + fn SSL_session_reused(ssl: *const SSL) -> c_int; + + fn SSL_in_init(ssl: *const SSL) -> c_int; + + fn SSL_clear(ssl: *mut SSL) -> c_int; + + fn SSL_free(ssl: *mut SSL); + + // SSL_CIPHER + fn SSL_CIPHER_get_id(cipher: *const SSL_CIPHER) -> c_uint; + + // SSL_SESSION + + fn SSL_SESSION_free(session: *mut SSL_SESSION); + + // X509_VERIFY_PARAM + fn X509_VERIFY_PARAM_set1_host( + param: *mut X509_VERIFY_PARAM, name: *const c_char, namelen: usize, + ) -> c_int; + + // X509_STORE + #[cfg(windows)] + fn X509_STORE_add_cert(ctx: *mut X509_STORE, x: *mut X509) -> c_int; + + // X509 + #[cfg(windows)] + fn X509_free(x: *mut X509); + #[cfg(windows)] + fn d2i_X509(px: *mut X509, input: *const *const u8, len: c_int) -> *mut X509; + + // ERR + fn ERR_peek_error() -> c_uint; + + fn ERR_error_string_n(err: c_uint, buf: *mut c_char, len: usize); + + // OPENSSL + #[allow(dead_code)] + fn OPENSSL_free(ptr: *mut c_void); + +} + +#[cfg(not(feature = "openssl"))] +mod boringssl; +#[cfg(not(feature = "openssl"))] +use boringssl::*; + +#[cfg(feature = "openssl")] +mod openssl_quictls; +#[cfg(feature = "openssl")] +use openssl_quictls::*; diff --git a/quiche/src/tls/openssl_quictls.rs b/quiche/src/tls/boringssl_openssl/openssl_quictls.rs similarity index 100% rename from quiche/src/tls/openssl_quictls.rs rename to quiche/src/tls/boringssl_openssl/openssl_quictls.rs diff --git a/quiche/src/tls/mod.rs b/quiche/src/tls/mod.rs index f72038dd2a..abe58d9d1b 100644 --- a/quiche/src/tls/mod.rs +++ b/quiche/src/tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2019, Cloudflare, Inc. +// Copyright (C) 2018-2025, Cloudflare, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -24,1221 +24,39 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::ffi; -use std::ptr; -use std::slice; +#[cfg(not(feature = "__rustls"))] +mod boringssl_openssl; +#[cfg(not(feature = "__rustls"))] +pub use boringssl_openssl::*; -use std::io::Write; +#[cfg(feature = "__rustls")] +mod rustls; +#[cfg(feature = "__rustls")] +pub use rustls::*; -use std::sync::LazyLock; - -use libc::c_char; -use libc::c_int; -use libc::c_uint; -use libc::c_void; - -use crate::Error; -use crate::Result; - -use crate::Connection; -use crate::ConnectionError; - -use crate::crypto; use crate::packet; - -const TLS1_3_VERSION: u16 = 0x0304; -const TLS_ALERT_ERROR: u64 = 0x100; -const INTERNAL_ERROR: u64 = 0x01; - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct SSL_METHOD { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct SSL_CTX { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct SSL { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct SSL_CIPHER { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct SSL_SESSION { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct X509_VERIFY_PARAM { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -#[cfg(windows)] -struct X509_STORE { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct X509_STORE_CTX { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -#[cfg(windows)] -struct X509 { - _unused: c_void, -} - -#[allow(non_camel_case_types)] -#[repr(transparent)] -struct STACK_OF { - _unused: c_void, -} - -#[cfg(test)] -#[repr(C)] -#[allow(non_camel_case_types)] -#[allow(dead_code)] -enum ssl_private_key_result_t { - ssl_private_key_success, - ssl_private_key_retry, - ssl_private_key_failure, -} - -/// BoringSSL ex_data index for quiche connections. -pub static QUICHE_EX_DATA_INDEX: LazyLock = LazyLock::new(|| unsafe { - SSL_get_ex_new_index(0, ptr::null(), ptr::null(), ptr::null(), ptr::null()) -}); - -pub struct Context(*mut SSL_CTX); - -impl Context { - // Note: some vendor-specific methods are implemented by each vendor's - // submodule (openssl-quictls / boringssl). - pub fn new() -> Result { - unsafe { - let ctx_raw = SSL_CTX_new(TLS_method()); - - let mut ctx = Context(ctx_raw); - - ctx.set_session_callback(); - - ctx.load_ca_certs()?; - - Ok(ctx) - } - } - - #[cfg(feature = "boringssl-boring-crate")] - pub fn from_boring( - ssl_ctx_builder: boring::ssl::SslContextBuilder, - ) -> Context { - use foreign_types_shared::ForeignType; - - let mut ctx = Context(ssl_ctx_builder.build().into_ptr() as _); - ctx.set_session_callback(); - - ctx - } - - pub fn new_handshake(&mut self) -> Result { - unsafe { - let ssl = SSL_new(self.as_mut_ptr()); - Ok(Handshake::new(ssl)) - } - } - - pub fn load_verify_locations_from_file(&mut self, file: &str) -> Result<()> { - let file = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; - map_result(unsafe { - SSL_CTX_load_verify_locations( - self.as_mut_ptr(), - file.as_ptr(), - std::ptr::null(), - ) - }) - } - - pub fn load_verify_locations_from_directory( - &mut self, path: &str, - ) -> Result<()> { - let path = ffi::CString::new(path).map_err(|_| Error::TlsFail)?; - map_result(unsafe { - SSL_CTX_load_verify_locations( - self.as_mut_ptr(), - std::ptr::null(), - path.as_ptr(), - ) - }) - } - - pub fn use_certificate_chain_file(&mut self, file: &str) -> Result<()> { - let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; - map_result(unsafe { - SSL_CTX_use_certificate_chain_file(self.as_mut_ptr(), cstr.as_ptr()) - }) - } - - pub fn use_privkey_file(&mut self, file: &str) -> Result<()> { - let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?; - map_result(unsafe { - SSL_CTX_use_PrivateKey_file(self.as_mut_ptr(), cstr.as_ptr(), 1) - }) - } - - #[cfg(not(windows))] - fn load_ca_certs(&mut self) -> Result<()> { - unsafe { map_result(SSL_CTX_set_default_verify_paths(self.as_mut_ptr())) } - } - - #[cfg(windows)] - fn load_ca_certs(&mut self) -> Result<()> { - unsafe { - let cstr = ffi::CString::new("Root").map_err(|_| Error::TlsFail)?; - let sys_store = - windows_sys::Win32::Security::Cryptography::CertOpenSystemStoreA( - 0, - cstr.as_ptr() as windows_sys::core::PCSTR, - ); - if sys_store.is_null() { - return Err(Error::TlsFail); - } - - let ctx_store = SSL_CTX_get_cert_store(self.as_mut_ptr()); - if ctx_store.is_null() { - return Err(Error::TlsFail); - } - - let mut ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore( - sys_store, - ptr::null(), - ); - - while !ctx_p.is_null() { - let in_p = (*ctx_p).pbCertEncoded as *const u8; - - let cert = d2i_X509( - ptr::null_mut(), - &in_p, - (*ctx_p).cbCertEncoded as i32, - ); - if !cert.is_null() { - X509_STORE_add_cert(ctx_store, cert); - } - - X509_free(cert); - - ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore( - sys_store, ctx_p, - ); - } - - // tidy up - windows_sys::Win32::Security::Cryptography::CertFreeCertificateContext(ctx_p); - windows_sys::Win32::Security::Cryptography::CertCloseStore( - sys_store, 0, - ); - } - - Ok(()) - } - - fn set_session_callback(&mut self) { - unsafe { - // This is needed to enable the session callback on the client. On - // the server it doesn't do anything. - SSL_CTX_set_session_cache_mode( - self.as_mut_ptr(), - 0x0001, // SSL_SESS_CACHE_CLIENT - ); - - SSL_CTX_sess_set_new_cb(self.as_mut_ptr(), Some(new_session)); - }; - } - - pub fn set_verify(&mut self, verify: bool) { - // true -> 0x01 SSL_VERIFY_PEER - // false -> 0x00 SSL_VERIFY_NONE - let mode = i32::from(verify); - - // Note: Base on two used modes(see above), it seems ok for both, bssl and - // ossl. If mode needs to be ored then it may need to be adjusted. - unsafe { - SSL_CTX_set_verify(self.as_mut_ptr(), mode, None); - } - } - - pub fn enable_keylog(&mut self) { - unsafe { - SSL_CTX_set_keylog_callback(self.as_mut_ptr(), Some(keylog)); - } - } - - pub fn set_alpn(&mut self, v: &[&[u8]]) -> Result<()> { - let mut protos: Vec = Vec::new(); - - for proto in v { - protos.push(proto.len() as u8); - protos.extend_from_slice(proto); - } - - // Configure ALPN for servers. - unsafe { - SSL_CTX_set_alpn_select_cb( - self.as_mut_ptr(), - Some(select_alpn), - ptr::null_mut(), - ); - } - - // Configure ALPN for clients. - map_result_zero_is_success(unsafe { - SSL_CTX_set_alpn_protos( - self.as_mut_ptr(), - protos.as_ptr(), - protos.len(), - ) - }) - } - - pub fn set_ticket_key(&mut self, key: &[u8]) -> Result<()> { - map_result(unsafe { - SSL_CTX_set_tlsext_ticket_keys( - self.as_mut_ptr(), - key.as_ptr(), - key.len(), - ) - }) - } - - fn as_mut_ptr(&mut self) -> *mut SSL_CTX { - self.0 - } -} - -// NOTE: These traits are not automatically implemented for Context due to the -// raw pointer it wraps. However, the underlying data is not aliased (as Context -// should be its only owner), and there is no interior mutability, as the -// pointer is not accessed directly outside of this module, and the Context -// object API should preserve Rust's borrowing guarantees. -unsafe impl std::marker::Send for Context {} -unsafe impl std::marker::Sync for Context {} - -impl Drop for Context { - fn drop(&mut self) { - unsafe { SSL_CTX_free(self.as_mut_ptr()) } - } -} - -pub struct Handshake { - /// Raw pointer - ptr: *mut SSL, - /// SSL_process_quic_post_handshake should be called when whenever - /// SSL_provide_quic_data is called to process the provided data. - provided_data_outstanding: bool, -} - -impl Handshake { - // Note: some vendor-specific methods are implemented by each vendor's - // submodule (openssl-quictls / boringssl). - #[cfg(feature = "ffi")] - pub unsafe fn from_ptr(ssl: *mut c_void) -> Handshake { - Handshake::new(ssl as *mut SSL) - } - - fn new(ptr: *mut SSL) -> Handshake { - Handshake { - ptr, - provided_data_outstanding: false, - } - } - - pub fn get_error(&self, ret_code: c_int) -> c_int { - unsafe { SSL_get_error(self.as_ptr(), ret_code) } - } - - pub fn init(&mut self, is_server: bool) -> Result<()> { - self.set_state(is_server); - - self.set_min_proto_version(TLS1_3_VERSION)?; - self.set_max_proto_version(TLS1_3_VERSION)?; - - self.set_quic_method()?; - - // TODO: the early data context should include transport parameters and - // HTTP/3 SETTINGS in wire format. - self.set_quic_early_data_context(b"quiche")?; - - self.set_quiet_shutdown(true); - - Ok(()) - } - - pub fn use_legacy_codepoint(&mut self, use_legacy: bool) { - unsafe { - SSL_set_quic_use_legacy_codepoint( - self.as_mut_ptr(), - use_legacy as c_int, - ); - } - } - - pub fn set_state(&mut self, is_server: bool) { - unsafe { - if is_server { - SSL_set_accept_state(self.as_mut_ptr()); - } else { - SSL_set_connect_state(self.as_mut_ptr()); - } - } - } - - pub fn set_ex_data(&mut self, idx: c_int, data: *const T) -> Result<()> { - map_result(unsafe { - let ptr = data as *mut c_void; - SSL_set_ex_data(self.as_mut_ptr(), idx, ptr) - }) - } - - pub fn set_quic_method(&mut self) -> Result<()> { - map_result(unsafe { - SSL_set_quic_method(self.as_mut_ptr(), &QUICHE_STREAM_METHOD) - }) - } - - pub fn set_min_proto_version(&mut self, version: u16) -> Result<()> { - map_result(unsafe { - SSL_set_min_proto_version(self.as_mut_ptr(), version) - }) - } - - pub fn set_max_proto_version(&mut self, version: u16) -> Result<()> { - map_result(unsafe { - SSL_set_max_proto_version(self.as_mut_ptr(), version) - }) - } - - pub fn set_quiet_shutdown(&mut self, mode: bool) { - unsafe { SSL_set_quiet_shutdown(self.as_mut_ptr(), i32::from(mode)) } - } - - pub fn set_host_name(&mut self, name: &str) -> Result<()> { - let cstr = ffi::CString::new(name).map_err(|_| Error::TlsFail)?; - let rc = - unsafe { SSL_set_tlsext_host_name(self.as_mut_ptr(), cstr.as_ptr()) }; - self.map_result_ssl(rc)?; - - let param = unsafe { SSL_get0_param(self.as_mut_ptr()) }; - - map_result(unsafe { - X509_VERIFY_PARAM_set1_host(param, cstr.as_ptr(), name.len()) - }) - } - - pub fn set_quic_transport_params(&mut self, buf: &[u8]) -> Result<()> { - let rc = unsafe { - SSL_set_quic_transport_params( - self.as_mut_ptr(), - buf.as_ptr(), - buf.len(), - ) - }; - self.map_result_ssl(rc) - } - - pub fn quic_transport_params(&self) -> &[u8] { - let mut ptr: *const u8 = ptr::null(); - let mut len: usize = 0; - - unsafe { - SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len); - } - - if len == 0 { - return &mut []; - } - - unsafe { slice::from_raw_parts(ptr, len) } - } - - pub fn alpn_protocol(&self) -> &[u8] { - let mut ptr: *const u8 = ptr::null(); - let mut len: u32 = 0; - - unsafe { - SSL_get0_alpn_selected(self.as_ptr(), &mut ptr, &mut len); - } - - if len == 0 { - return &mut []; - } - - unsafe { slice::from_raw_parts(ptr, len as usize) } - } - - pub fn server_name(&self) -> Option<&str> { - let s = unsafe { - let ptr = SSL_get_servername( - self.as_ptr(), - 0, // TLSEXT_NAMETYPE_host_name - ); - - if ptr.is_null() { - return None; - } - - ffi::CStr::from_ptr(ptr) - }; - - s.to_str().ok() - } - - pub fn provide_data( - &mut self, level: crypto::Level, buf: &[u8], - ) -> Result<()> { - self.provided_data_outstanding = true; - let rc = unsafe { - SSL_provide_quic_data( - self.as_mut_ptr(), - level, - buf.as_ptr(), - buf.len(), - ) - }; - self.map_result_ssl(rc) - } - - pub fn do_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { - self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?; - let rc = unsafe { SSL_do_handshake(self.as_mut_ptr()) }; - self.set_ex_data::(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; - - self.set_transport_error(ex_data, rc); - self.map_result_ssl(rc) - } - - pub fn process_post_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { - // If SSL_provide_quic_data hasn't been called since we last called - // SSL_process_quic_post_handshake, then there's nothing to do. - if !self.provided_data_outstanding { - return Ok(()); - } - self.provided_data_outstanding = false; - - self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?; - let rc = unsafe { SSL_process_quic_post_handshake(self.as_mut_ptr()) }; - self.set_ex_data::(*QUICHE_EX_DATA_INDEX, std::ptr::null())?; - - self.set_transport_error(ex_data, rc); - self.map_result_ssl(rc) - } - - pub fn write_level(&self) -> crypto::Level { - unsafe { SSL_quic_write_level(self.as_ptr()) } - } - - pub fn cipher(&self) -> Option { - let cipher = - map_result_ptr(unsafe { SSL_get_current_cipher(self.as_ptr()) }); - - get_cipher_from_ptr(cipher.ok()?).ok() - } - - #[cfg(test)] - pub fn set_options(&mut self, opts: u32) { - unsafe { - SSL_set_options(self.as_mut_ptr(), opts); - } - } - - pub fn is_completed(&self) -> bool { - unsafe { SSL_in_init(self.as_ptr()) == 0 } - } - - pub fn is_resumed(&self) -> bool { - unsafe { SSL_session_reused(self.as_ptr()) == 1 } - } - - pub fn clear(&mut self) -> Result<()> { - let rc = unsafe { SSL_clear(self.as_mut_ptr()) }; - self.map_result_ssl(rc) - } - - fn as_ptr(&self) -> *const SSL { - self.ptr - } - - fn as_mut_ptr(&mut self) -> *mut SSL { - self.ptr - } - - fn map_result_ssl(&mut self, bssl_result: c_int) -> Result<()> { - match bssl_result { - 1 => Ok(()), - - _ => { - let ssl_err = self.get_error(bssl_result); - match ssl_err { - // SSL_ERROR_SSL - 1 => { - log_ssl_error(); - - Err(Error::TlsFail) - }, - - // SSL_ERROR_WANT_READ - 2 => Err(Error::Done), - - // SSL_ERROR_WANT_WRITE - 3 => Err(Error::Done), - - // SSL_ERROR_WANT_X509_LOOKUP - 4 => Err(Error::Done), - - // SSL_ERROR_SYSCALL - 5 => Err(Error::TlsFail), - - // SSL_ERROR_PENDING_SESSION - 11 => Err(Error::Done), - - // SSL_ERROR_PENDING_CERTIFICATE - 12 => Err(Error::Done), - - // SSL_ERROR_WANT_PRIVATE_KEY_OPERATION - 13 => Err(Error::Done), - - // SSL_ERROR_PENDING_TICKET - 14 => Err(Error::Done), - - // SSL_ERROR_EARLY_DATA_REJECTED - 15 => { - self.reset_early_data_reject(); - Err(Error::Done) - }, - - // SSL_ERROR_WANT_CERTIFICATE_VERIFY - 16 => Err(Error::Done), - - _ => Err(Error::TlsFail), - } - }, - } - } - - fn set_transport_error(&mut self, ex_data: &mut ExData, bssl_result: c_int) { - // SSL_ERROR_SSL - if self.get_error(bssl_result) == 1 { - // SSL_ERROR_SSL can't be recovered so ensure we set a - // local_error so the connection is closed. - // See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html - if ex_data.local_error.is_none() { - *ex_data.local_error = Some(ConnectionError { - is_app: false, - error_code: INTERNAL_ERROR, - reason: Vec::new(), - }) - } - } - } - - #[cfg(feature = "boringssl-boring-crate")] - pub(crate) fn ssl_mut(&mut self) -> &mut boring::ssl::SslRef { - use foreign_types_shared::ForeignTypeRef; - - unsafe { boring::ssl::SslRef::from_ptr_mut(self.as_mut_ptr() as _) } - } -} - -// NOTE: These traits are not automatically implemented for Handshake due to the -// raw pointer it wraps. However, the underlying data is not aliased (as -// Handshake should be its only owner), and there is no interior mutability, as -// the pointer is not accessed directly outside of this module, and the -// Handshake object API should preserve Rust's borrowing guarantees. -unsafe impl std::marker::Send for Handshake {} -unsafe impl std::marker::Sync for Handshake {} - -impl Drop for Handshake { - fn drop(&mut self) { - unsafe { SSL_free(self.as_mut_ptr()) } - } -} +use crate::ConnectionError; pub struct ExData<'a> { + #[cfg(not(feature = "__rustls"))] pub application_protos: &'a Vec>, pub crypto_ctx: &'a mut [packet::CryptoContext; packet::Epoch::count()], pub session: &'a mut Option>, - pub local_error: &'a mut Option, + pub local_error: &'a mut Option, + #[cfg(not(feature = "__rustls"))] pub keylog: Option<&'a mut Box>, + #[cfg(not(feature = "__rustls"))] pub trace_id: &'a str, pub recovery_config: crate::recovery::RecoveryConfig, pub tx_cap_factor: f64, + #[cfg(not(feature = "__rustls"))] pub is_server: bool, } - -impl<'a> ExData<'a> { - fn from_ssl_ptr(ptr: *const SSL) -> Option<&'a mut Self> { - get_ex_data_from_ptr::(ptr, *QUICHE_EX_DATA_INDEX) - } - - #[cfg(feature = "boringssl-boring-crate")] - pub fn from_ssl_ref(ssl: &mut boring::ssl::SslRef) -> Option<&mut Self> { - use boring::ex_data::Index; - - // SAFETY: the QUICHE_EX_DATA_INDEX index is guaranteed to be created, - // and the associated data is always `ExData`. - let idx: Index = - unsafe { Index::from_raw(*QUICHE_EX_DATA_INDEX) }; - - ssl.ex_data_mut(idx) - } -} - -fn get_ex_data_from_ptr<'a, T>(ptr: *const SSL, idx: c_int) -> Option<&'a mut T> { - unsafe { - let data = SSL_get_ex_data(ptr, idx) as *mut T; - data.as_mut() - } -} - -fn get_cipher_from_ptr(cipher: *const SSL_CIPHER) -> Result { - let cipher_id = unsafe { SSL_CIPHER_get_id(cipher) }; - - let alg = match cipher_id { - 0x0300_1301 => crypto::Algorithm::AES128_GCM, - 0x0300_1302 => crypto::Algorithm::AES256_GCM, - 0x0300_1303 => crypto::Algorithm::ChaCha20_Poly1305, - _ => return Err(Error::TlsFail), - }; - - Ok(alg) -} - -extern "C" fn set_read_secret( - ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER, - secret: *const u8, secret_len: usize, -) -> c_int { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return 0, - }; - - trace!("{} set read secret lvl={:?}", ex_data.trace_id, level); - - let space = match level { - crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], - crypto::Level::ZeroRTT => - &mut ex_data.crypto_ctx[packet::Epoch::Application], - crypto::Level::Handshake => - &mut ex_data.crypto_ctx[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.crypto_ctx[packet::Epoch::Application], - }; - - let aead = match get_cipher_from_ptr(cipher) { - Ok(v) => v, - - Err(_) => return 0, - }; - - // 0-RTT read secrets are present only on the server. - if level != crypto::Level::ZeroRTT || ex_data.is_server { - let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; - - let open = match crypto::Open::from_secret(aead, secret) { - Ok(v) => v, - - Err(_) => return 0, - }; - - if level == crypto::Level::ZeroRTT { - space.crypto_0rtt_open = Some(open); - return 1; - } - - space.crypto_open = Some(open); - } - - 1 -} - -extern "C" fn set_write_secret( - ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER, - secret: *const u8, secret_len: usize, -) -> c_int { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return 0, - }; - - trace!("{} set write secret lvl={:?}", ex_data.trace_id, level); - - let space = match level { - crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], - crypto::Level::ZeroRTT => - &mut ex_data.crypto_ctx[packet::Epoch::Application], - crypto::Level::Handshake => - &mut ex_data.crypto_ctx[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.crypto_ctx[packet::Epoch::Application], - }; - - let aead = match get_cipher_from_ptr(cipher) { - Ok(v) => v, - - Err(_) => return 0, - }; - - // 0-RTT write secrets are present only on the client. - if level != crypto::Level::ZeroRTT || !ex_data.is_server { - let secret = unsafe { slice::from_raw_parts(secret, secret_len) }; - - let seal = match crypto::Seal::from_secret(aead, secret) { - Ok(v) => v, - - Err(_) => return 0, - }; - - space.crypto_seal = Some(seal); - } - - 1 -} - -extern "C" fn add_handshake_data( - ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize, -) -> c_int { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return 0, - }; - - trace!( - "{} write message lvl={:?} len={}", - ex_data.trace_id, - level, - len - ); - - let buf = unsafe { slice::from_raw_parts(data, len) }; - - let space = match level { - crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], - crypto::Level::ZeroRTT => unreachable!(), - crypto::Level::Handshake => - &mut ex_data.crypto_ctx[packet::Epoch::Handshake], - crypto::Level::OneRTT => - &mut ex_data.crypto_ctx[packet::Epoch::Application], - }; - - if space.crypto_stream.send.write(buf, false).is_err() { - return 0; - } - - 1 -} - -extern "C" fn flush_flight(_ssl: *mut SSL) -> c_int { - // We don't really need to anything here since the output packets are - // generated separately, when conn.send() is called. - - 1 -} - -extern "C" fn send_alert( - ssl: *mut SSL, level: crypto::Level, alert: u8, -) -> c_int { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return 0, - }; - - trace!( - "{} send alert lvl={:?} alert={:x}", - ex_data.trace_id, - level, - alert - ); - - let error: u64 = TLS_ALERT_ERROR + u64::from(alert); - *ex_data.local_error = Some(ConnectionError { - is_app: false, - error_code: error, - reason: Vec::new(), - }); - - 1 -} - -extern "C" fn keylog(ssl: *const SSL, line: *const c_char) { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return, - }; - - if let Some(keylog) = &mut ex_data.keylog { - let data = unsafe { ffi::CStr::from_ptr(line).to_bytes() }; - - let mut full_line = Vec::with_capacity(data.len() + 1); - full_line.extend_from_slice(data); - full_line.push(b'\n'); - - keylog.write_all(&full_line[..]).ok(); - keylog.flush().ok(); - } -} - -extern "C" fn select_alpn( - ssl: *mut SSL, out: *mut *const u8, out_len: *mut u8, inp: *mut u8, - in_len: c_uint, _arg: *mut c_void, -) -> c_int { - // SSL_TLSEXT_ERR_OK 0 - // SSL_TLSEXT_ERR_ALERT_WARNING 1 - // SSL_TLSEXT_ERR_ALERT_FATAL 2 - // SSL_TLSEXT_ERR_NOACK 3 - - // Boringssl internally overwrite the return value from this callback, if the - // returned value is SSL_TLSEXT_ERR_NOACK and is quic, then the value gets - // overwritten to SSL_TLSEXT_ERR_ALERT_FATAL. In contrast openssl/quictls does - // not do that, so we need to explicitly respond with - // SSL_TLSEXT_ERR_ALERT_FATAL in case it is needed. - // TLS_ERROR is redefined for each vendor. - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return TLS_ERROR, - }; - - if ex_data.application_protos.is_empty() { - return TLS_ERROR; - } - - let mut protos = octets::Octets::with_slice(unsafe { - slice::from_raw_parts(inp, in_len as usize) - }); - - while let Ok(proto) = protos.get_bytes_with_u8_length() { - let found = ex_data.application_protos.iter().any(|expected| { - trace!( - "checking peer ALPN {:?} against {:?}", - std::str::from_utf8(proto.as_ref()), - std::str::from_utf8(expected.as_slice()) - ); - - if expected.len() == proto.len() && - expected.as_slice() == proto.as_ref() - { - unsafe { - *out = expected.as_slice().as_ptr(); - *out_len = expected.len() as u8; - } - - return true; - } - - false - }); - - if found { - return 0; // SSL_TLSEXT_ERR_OK - } - } - - TLS_ERROR -} - -extern "C" fn new_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int { - let ex_data = match ExData::from_ssl_ptr(ssl) { - Some(v) => v, - - None => return 0, - }; - - let handshake = Handshake::new(ssl); - let peer_params = handshake.quic_transport_params(); - - // Serialize session object into buffer. - let session_bytes = match get_session_bytes(session) { - Ok(v) => v, - Err(_) => return 0, - }; - - let mut buffer = - Vec::with_capacity(8 + peer_params.len() + 8 + session_bytes.len()); - - let session_bytes_len = session_bytes.len() as u64; - - if buffer.write(&session_bytes_len.to_be_bytes()).is_err() { - std::mem::forget(handshake); - return 0; - } - - if buffer.write(&session_bytes).is_err() { - std::mem::forget(handshake); - return 0; - } - - let peer_params_len = peer_params.len() as u64; - - if buffer.write(&peer_params_len.to_be_bytes()).is_err() { - std::mem::forget(handshake); - return 0; - } - - if buffer.write(peer_params).is_err() { - std::mem::forget(handshake); - return 0; - } - - *ex_data.session = Some(buffer); - - // Prevent handshake from being freed, as we still need it. - std::mem::forget(handshake); - - 0 -} - -pub fn map_result(bssl_result: c_int) -> Result<()> { - match bssl_result { - 1 => Ok(()), - _ => Err(Error::TlsFail), - } -} - -pub fn map_result_zero_is_success(bssl_result: c_int) -> Result<()> { - match bssl_result { - 0 => Ok(()), - _ => Err(Error::TlsFail), - } -} - -pub fn map_result_ptr<'a, T>(bssl_result: *const T) -> Result<&'a T> { - match unsafe { bssl_result.as_ref() } { - Some(v) => Ok(v), - None => Err(Error::TlsFail), - } -} - -fn log_ssl_error() { - let mut err = [0u8; 1024]; - - unsafe { - let e = ERR_peek_error(); - ERR_error_string_n(e, err.as_mut_ptr() as *mut c_char, err.len()); - } - - let cstr = ffi::CStr::from_bytes_until_nul(&err) - .expect("ERR_error_string_n should write a null terminated string"); - - trace!( - "{}", - cstr.to_str() - .expect("ERR_error_string_n should create a valid UTF-8 message") - ); -} - -extern "C" { - // Note: some vendor-specific methods are implemented by each vendor's - // submodule (openssl-quictls / boringssl). - - // SSL_METHOD - fn TLS_method() -> *const SSL_METHOD; - - // SSL_CTX - fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX; - fn SSL_CTX_free(ctx: *mut SSL_CTX); - - fn SSL_CTX_use_certificate_chain_file( - ctx: *mut SSL_CTX, file: *const c_char, - ) -> c_int; - - fn SSL_CTX_use_PrivateKey_file( - ctx: *mut SSL_CTX, file: *const c_char, ty: c_int, - ) -> c_int; - - fn SSL_CTX_load_verify_locations( - ctx: *mut SSL_CTX, file: *const c_char, path: *const c_char, - ) -> c_int; - - #[cfg(not(windows))] - fn SSL_CTX_set_default_verify_paths(ctx: *mut SSL_CTX) -> c_int; - - #[cfg(windows)] - fn SSL_CTX_get_cert_store(ctx: *mut SSL_CTX) -> *mut X509_STORE; - - fn SSL_CTX_set_verify( - ctx: *mut SSL_CTX, mode: c_int, - cb: Option< - unsafe extern "C" fn( - ok: c_int, - store_ctx: *mut X509_STORE_CTX, - ) -> c_int, - >, - ); - - fn SSL_CTX_set_keylog_callback( - ctx: *mut SSL_CTX, - cb: Option, - ); - - fn SSL_CTX_set_alpn_protos( - ctx: *mut SSL_CTX, protos: *const u8, protos_len: usize, - ) -> c_int; - - fn SSL_CTX_set_alpn_select_cb( - ctx: *mut SSL_CTX, - cb: Option< - unsafe extern "C" fn( - ssl: *mut SSL, - out: *mut *const u8, - out_len: *mut u8, - inp: *mut u8, - in_len: c_uint, - arg: *mut c_void, - ) -> c_int, - >, - arg: *mut c_void, - ); - - fn SSL_CTX_sess_set_new_cb( - ctx: *mut SSL_CTX, - cb: Option< - unsafe extern "C" fn( - ssl: *mut SSL, - session: *mut SSL_SESSION, - ) -> c_int, - >, - ); - - fn SSL_new(ctx: *mut SSL_CTX) -> *mut SSL; - - fn SSL_get_error(ssl: *const SSL, ret_code: c_int) -> c_int; - - fn SSL_set_accept_state(ssl: *mut SSL); - fn SSL_set_connect_state(ssl: *mut SSL); - - fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM; - - fn SSL_set_ex_data(ssl: *mut SSL, idx: c_int, ptr: *mut c_void) -> c_int; - fn SSL_get_ex_data(ssl: *const SSL, idx: c_int) -> *mut c_void; - - fn SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER; - - fn SSL_set_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int; - - fn SSL_get_SSL_CTX(ssl: *const SSL) -> *mut SSL_CTX; - - fn SSL_set_quiet_shutdown(ssl: *mut SSL, mode: c_int); - - fn SSL_set_quic_transport_params( - ssl: *mut SSL, params: *const u8, params_len: usize, - ) -> c_int; - - fn SSL_set_quic_method( - ssl: *mut SSL, quic_method: *const SSL_QUIC_METHOD, - ) -> c_int; - - fn SSL_set_quic_use_legacy_codepoint(ssl: *mut SSL, use_legacy: c_int); - - #[cfg(test)] - fn SSL_set_options(ssl: *mut SSL, opts: u32) -> u32; - - fn SSL_get_peer_quic_transport_params( - ssl: *const SSL, out_params: *mut *const u8, out_params_len: *mut usize, - ); - - fn SSL_get0_alpn_selected( - ssl: *const SSL, out: *mut *const u8, out_len: *mut u32, - ); - - fn SSL_get_servername(ssl: *const SSL, ty: c_int) -> *const c_char; - - fn SSL_provide_quic_data( - ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize, - ) -> c_int; - - fn SSL_process_quic_post_handshake(ssl: *mut SSL) -> c_int; - - fn SSL_do_handshake(ssl: *mut SSL) -> c_int; - - fn SSL_quic_write_level(ssl: *const SSL) -> crypto::Level; - - fn SSL_session_reused(ssl: *const SSL) -> c_int; - - fn SSL_in_init(ssl: *const SSL) -> c_int; - - fn SSL_clear(ssl: *mut SSL) -> c_int; - - fn SSL_free(ssl: *mut SSL); - - // SSL_CIPHER - fn SSL_CIPHER_get_id(cipher: *const SSL_CIPHER) -> c_uint; - - // SSL_SESSION - - fn SSL_SESSION_free(session: *mut SSL_SESSION); - - // X509_VERIFY_PARAM - fn X509_VERIFY_PARAM_set1_host( - param: *mut X509_VERIFY_PARAM, name: *const c_char, namelen: usize, - ) -> c_int; - - // X509_STORE - #[cfg(windows)] - fn X509_STORE_add_cert(ctx: *mut X509_STORE, x: *mut X509) -> c_int; - - // X509 - #[cfg(windows)] - fn X509_free(x: *mut X509); - #[cfg(windows)] - fn d2i_X509(px: *mut X509, input: *const *const u8, len: c_int) -> *mut X509; - - // ERR - fn ERR_peek_error() -> c_uint; - - fn ERR_error_string_n(err: c_uint, buf: *mut c_char, len: usize); - - // OPENSSL - #[allow(dead_code)] - fn OPENSSL_free(ptr: *mut c_void); - -} - -#[cfg(not(feature = "openssl"))] -mod boringssl; -#[cfg(not(feature = "openssl"))] -use boringssl::*; - -#[cfg(feature = "openssl")] -mod openssl_quictls; -#[cfg(feature = "openssl")] -use openssl_quictls::*; diff --git a/quiche/src/tls/rustls/mod.rs b/quiche/src/tls/rustls/mod.rs new file mode 100644 index 0000000000..9a5da0a34c --- /dev/null +++ b/quiche/src/tls/rustls/mod.rs @@ -0,0 +1,876 @@ +// Copyright (C) 2025, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod verifiers; + +use crate::crypto::crypto_provider; +use crate::crypto::key_material_from_keys; +use crate::crypto::Algorithm; +use crate::crypto::Level; +use crate::crypto::Open; +use crate::crypto::Seal; +use crate::packet; +use crate::tls::rustls::verifiers::DisabledServerCertVerifier; +use crate::tls::rustls::verifiers::RejectedClientCertAllowedAnonymousVerifier; +use crate::tls::ExData; +use crate::ConnectionError; +use crate::Error; +use crate::Result; +use rustls::client::ClientSessionMemoryCache; +use rustls::client::Resumption; +use rustls::client::WebPkiServerVerifier; +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::CertificateDer; +use rustls::pki_types::PrivateKeyDer; +use rustls::pki_types::ServerName; +use rustls::quic::ClientConnection; +use rustls::quic::Connection; +use rustls::quic::KeyChange; +use rustls::quic::Keys; +use rustls::quic::Secrets; +use rustls::quic::ServerConnection; +use rustls::quic::Version; +use rustls::server::WebPkiClientVerifier; +use rustls::version::TLS13; +use rustls::CipherSuite; +use rustls::ClientConfig; +use rustls::HandshakeKind; +use rustls::KeyLogFile; +use rustls::RootCertStore; +use rustls::ServerConfig; +use rustls::Side; +use std::fs::DirEntry; +use std::sync::Arc; + +const INTERNAL_ERROR: u64 = 0x01; + +pub struct Context { + client_config: Option>, + server_config: Option>, + // required to build the above configs + // are consumed during configs building + private_key_client: Option>, + private_key_server: Option>, + ca_certificates: Option>>, + verify_ca_certificates_store: Option, + system_default_cert_store: Option, + alpns: Vec>, + enable_verify_ca_certificates: bool, + enable_keylog: bool, + enable_early_data: bool, + quic_version: Version, + client_resumption_store: Arc, +} + +impl Context { + pub fn new() -> Result { + let _ = crypto_provider(); + + Ok(Self { + client_config: None, + server_config: None, + private_key_client: None, + private_key_server: None, + ca_certificates: None, + enable_verify_ca_certificates: false, + verify_ca_certificates_store: None, + system_default_cert_store: None, + enable_keylog: false, + enable_early_data: false, + quic_version: Default::default(), + alpns: vec![], + client_resumption_store: Arc::new(ClientSessionMemoryCache::new(256)), + }) + } + + pub fn new_handshake(&mut self) -> Result { + // user supplied verification store when enabled and available + // used for server mTLS validation on server or + // used for server certificate validation on client + let verify_store = if self.enable_verify_ca_certificates { + self.verify_ca_certificates_store.clone().take() + } else { + None + }; + + if self.server_config.is_none() && + self.private_key_server.is_some() && + self.ca_certificates.is_some() + { + let builder = + ServerConfig::builder_with_provider(crypto_provider().clone()) + .with_protocol_versions(&[&TLS13]) + .map_err(|e| { + error!("failed to set protocol version for server config builder: {}", e); + Error::TlsFail + })?; + // setup user supplied and enabled CA certificates for mTLS auth + let builder = if let Some(verify_store) = verify_store.clone() { + let client_verifier = + WebPkiClientVerifier::builder(verify_store.into()) + .allow_unauthenticated() + .build() + .map_err(|e| { + error!("client_verifier: failed to build {}", e); + Error::TlsFail + })?; + + builder.with_client_cert_verifier(client_verifier) + } else { + if self.enable_verify_ca_certificates { + // in case no store is present fail on mTLS authentication + // user warning is issued during Handshake.do_handshake() + builder.with_client_cert_verifier(Arc::new( + RejectedClientCertAllowedAnonymousVerifier::new()?, + )) + } else { + builder.with_no_client_auth() + } + }; + + let (Some(certs), Some(key)) = + (self.ca_certificates.clone(), self.private_key_server.take()) + else { + error!( + "server without certificate and key config is not supported" + ); + return Err(Error::TlsFail); + }; + + let mut config = + builder.with_single_cert(certs, key).map_err(|e| { + error!("loading certificate or key failed: {}", e); + Error::TlsFail + })?; + + if self.enable_keylog { + config.key_log = Arc::new(KeyLogFile::new()); + } + + if self.enable_early_data { + // rustls currently only allows 0 or 2^32-1 for max early data + // size + config.max_early_data_size = u32::MAX; + } + + if self.alpns.len() > 0 { + config.alpn_protocols = self.alpns.clone(); + } + + self.server_config = Some(Arc::new(config)); + }; + + if self.client_config.is_none() { + let builder = + ClientConfig::builder_with_provider(crypto_provider().clone()) + .with_protocol_versions(&[&TLS13]) + .map_err(|e| { + error!("failed to set protocol version for client config builder: {}", e); + Error::TlsFail + })?; + + // setup user supplied and enabled CA certificates for server + // certificate validation + let builder = if let Some(verify_store) = verify_store.clone() { + let server_verifier = + WebPkiServerVerifier::builder(verify_store.into()) + .build() + .map_err(|e| { + error!("failed to build server verifier: {}", e); + Error::TlsFail + })?; + + builder.with_webpki_verifier(server_verifier) + } else { + // in case enabled but no CA certificates provided use system + // default CAs + if self.enable_verify_ca_certificates { + // default to env variables or system store + let store = self.load_system_default_certificate_store(); + builder.with_root_certificates(store) + } else { + // completely deactivate validation + let builder = builder.dangerous(); + let disabled_server_verification = + Arc::new(DisabledServerCertVerifier::new()?); + builder.with_custom_certificate_verifier( + disabled_server_verification, + ) + } + }; + + let mut config = if let (Some(certs), Some(key)) = + (self.ca_certificates.take(), self.private_key_client.take()) + { + builder.with_client_auth_cert(certs, key).map_err(|e| { + error!("failed to set client auth: {}", e); + Error::TlsFail + })? + } else { + builder.with_no_client_auth() + }; + + if self.enable_keylog { + config.key_log = Arc::new(KeyLogFile::new()); + } + + if self.enable_early_data { + config.enable_early_data = true; + } + + if self.alpns.len() > 0 { + config.alpn_protocols = self.alpns.clone(); + } + + // required for 0-rtt, the resumption store is persisted within the + // Context and propagated to all connections on the + // ClientConfig + config.resumption = + Resumption::store(self.client_resumption_store.clone()); + + self.client_config = Some(Arc::new(config)) + } + + Ok(Handshake { + client_config: self.client_config.clone().ok_or_else(|| { + error!("no client config available"); + Error::TlsFail + })?, + server_config: self.server_config.clone(), + quic_version: self.quic_version.clone(), + connection: None, + side: Side::Client, // dummy value, correctly set during init() + enable_verify_ca_certificates: self.enable_verify_ca_certificates, + quic_transport_params: None, + provided_data: None, + highest_level: Level::Initial, + hostname: None, + one_rtt_keys_secrets: None, + }) + } + + fn load_system_default_certificate_store(&mut self) -> RootCertStore { + // loading the files is an expensive operation, ensure it's only done once + // system default cert store is used in some areas + if let Some(store) = &self.system_default_cert_store { + return store.clone(); + }; + + let mut system_default_certificate_store = RootCertStore::empty(); + let certificates_result = rustls_native_certs::load_native_certs(); + system_default_certificate_store + .add_parsable_certificates(certificates_result.certs); + + self.system_default_cert_store = Some(system_default_certificate_store); + self.system_default_cert_store.clone().unwrap() + } + + pub fn load_verify_locations_from_file(&mut self, file: &str) -> Result<()> { + let verify_certificates = Self::load_ca_certificates_from_file(file)?; + self.extend_verify_ca_certificates(verify_certificates); + Ok(()) + } + + pub fn load_verify_locations_from_directory( + &mut self, path: &str, + ) -> Result<()> { + let files: Result> = std::fs::read_dir(path) + .map_err(|e| { + error!("failed to load verify locations from directory: {:?}", e); + Error::TlsFail + })? + .into_iter() + .map(|rd| { + rd.map_err(|e| { + error!( + "failed to load verify locations from directory: {:?}", + e + ); + Error::TlsFail + }) + }) + .collect(); + + let verify_certificates: Vec = files? + .into_iter() + .flat_map(|f| Self::load_ca_certificates_from_file(f.path())) + .flatten() + .collect(); + + self.extend_verify_ca_certificates(verify_certificates); + Ok(()) + } + + pub fn use_certificate_chain_file(&mut self, file: &str) -> Result<()> { + self.ca_certificates = Some(Self::load_ca_certificates_from_file(file)?); + Ok(()) + } + + fn load_ca_certificates_from_file( + file: impl AsRef, + ) -> Result>> { + let certificates: Result> = + CertificateDer::pem_file_iter(file) + .map_err(|e| { + error!("failed to load ca certificates from pem file: {}", e); + Error::TlsFail + })? + .map(|r| { + r.map_err(|e| { + error!("failed to load pem certificate: {}", e); + Error::TlsFail + }) + }) + .collect(); + Ok(certificates?) + } + + fn extend_verify_ca_certificates( + &mut self, verify_certificates: Vec>, + ) { + if let Some(cert_store) = &mut self.verify_ca_certificates_store { + cert_store.add_parsable_certificates(verify_certificates); + } else { + let mut store = RootCertStore::empty(); + store.add_parsable_certificates(verify_certificates); + self.verify_ca_certificates_store = Some(store); + } + } + + pub fn use_privkey_file(&mut self, file: &str) -> Result<()> { + let private_key_client = + PrivateKeyDer::from_pem_file(file).map_err(|e| { + error!("failed to load private key from pem: {}", e); + Error::TlsFail + })?; + let private_key_server = + PrivateKeyDer::from_pem_file(file).map_err(|e| { + error!("failed to load private key from pem: {}", e); + Error::TlsFail + })?; + + // NOTE: storing it twice as PrivateKeyDer cannot be copied/cloned + // ClientConfig & ServerConfig are built in new_handshake() + self.private_key_client = Some(private_key_client); + self.private_key_server = Some(private_key_server); + Ok(()) + } + + pub fn set_verify(&mut self, verify: bool) { + self.enable_verify_ca_certificates = verify; + } + + /// uses env variable SSLKEYLOGFILE + pub fn enable_keylog(&mut self) { + self.enable_keylog = true; + } + + pub fn set_alpn(&mut self, v: &[&[u8]]) -> Result<()> { + let alpns: Vec> = v.iter().map(|a| a.to_vec()).collect(); + self.alpns = alpns; + Ok(()) + } + + // not supported in rustls + // pub fn set_ticket_key(&mut self, _key: &[u8]) -> Result<()> {} + + pub fn set_early_data_enabled(&mut self, enabled: bool) { + self.enable_early_data = enabled; + } +} + +pub struct Handshake { + client_config: Arc, + server_config: Option>, + quic_version: Version, + + side: Side, + enable_verify_ca_certificates: bool, + quic_transport_params: Option>, + hostname: Option>, + connection: Option, + + highest_level: Level, + provided_data: Option>, + one_rtt_keys_secrets: Option<(Keys, Secrets)>, +} + +impl Handshake { + pub fn init(&mut self, is_server: bool) -> Result<()> { + self.side = match is_server { + true => Side::Server, + false => Side::Client, + }; + + Ok(()) + } + + pub fn use_legacy_codepoint(&mut self, _use_legacy: bool) { + () // noop for rustls + } + + pub fn set_host_name(&mut self, name: &str) -> Result<()> { + let hostname = ServerName::try_from(name) + .map_err(|e| { + error!("failed to convert hostname: {}", e); + Error::TlsFail + })? + .to_owned(); + + self.hostname = Some(hostname); + Ok(()) + } + + pub fn set_quic_transport_params(&mut self, buf: &[u8]) -> Result<()> { + self.quic_transport_params = Some(buf.to_vec()); + Ok(()) + } + + pub fn quic_transport_params(&self) -> &[u8] { + // peer/remote transport parameters + if let Some(conn) = &self.connection { + // when resuming a tls session returning the transport parameters + // leads to errors as during decoding they are checked + // against the new connection ids which are not yet + // fully established and the previous connection ids would be present + if self.is_in_early_data() { + return &[]; + }; + + return if let Some(params) = conn.quic_transport_parameters() { + params + } else { + &[] + }; + } + + debug_assert!(false, "connection not available {:?}", self.side); + &[] + } + + pub fn alpn_protocol(&self) -> &[u8] { + if let Some(conn) = &self.connection { + if let Some(alpns) = conn.alpn_protocol() { + return alpns; + } + } + + &[] + } + + pub fn server_name(&self) -> Option<&str> { + self.connection.as_ref().and_then(|c| match c { + Connection::Client(_) => None, + Connection::Server(sc) => sc.server_name(), + }) + } + + // peer/receive Crypto frame data + pub fn provide_data(&mut self, _level: Level, buf: &[u8]) -> Result<()> { + debug!( + "provide_data side={:?} level={:?}", + self.side, self.highest_level + ); + + let Some(conn) = &mut self.connection else { + trace!("storing data as no connection present side={:?}", self.side); + self.provided_data = Some(buf.to_vec()); + return Ok(()); + }; + + conn.read_hs(&mut buf.to_vec()).map_err(|e| { + if let Some(alert) = conn.alert() { + error!("alert description: {:?}", alert) + } + error!("failed to read handshake data: {:?} {:?}", self.side, e); + Error::TlsFail + })?; + + Ok(()) + } + + // local/send Crypto frame data + pub fn do_handshake(&mut self, ex_data: &mut ExData) -> Result<()> { + if self.connection.is_none() { + debug!("no connection present side={:?}", self.side); + + let Some(params) = self.quic_transport_params.clone() else { + error!("missing transport parameters {:?}", self.side); + return Err(Error::TlsFail); + }; + + match self.side { + Side::Client => { + let Some(hostname) = self.hostname.clone() else { + error!("hostname not present"); + return Err(Error::TlsFail); + }; + + // NOTE: generates ClientHello + let client_conn = ClientConnection::new( + self.client_config.clone(), + self.quic_version.clone(), + hostname.to_owned(), + params, + ) + .map_err(|e| { + error!("failed to create client config {}", e); + Error::TlsFail + })?; + + self.connection = Some(client_conn.into()) + }, + Side::Server => { + let Some(server_config) = self.server_config.clone() else { + error!("server config not present for server side"); + return Err(Error::TlsFail); + }; + + if self.enable_verify_ca_certificates { + warn!("verify_peer: enabled but no CA store for mTLS verification configured."); + } + + let mut server_conn = ServerConnection::new( + server_config, + self.quic_version.clone(), + params.clone(), + ) + .map_err(|e| { + error!("failed to create server connection {}", e); + Error::TlsFail + })?; + + if let Some(crypto_data) = self.provided_data.take() { + match server_conn.read_hs(&crypto_data) { + Ok(()) => { /* continue */ }, + Err(e) => { + if ex_data.local_error.is_none() { + *ex_data.local_error = Some(ConnectionError { + is_app: false, + error_code: INTERNAL_ERROR, + reason: e.to_string().as_bytes().to_vec(), + }) + }; + error!("failed to read handshake data: {:?}", e); + return Err(Error::TlsFail); + }, + } + } + + self.connection = Some(server_conn.into()); + }, + } + }; + + loop { + let current_level = self.highest_level.clone(); + + let mut buf = Vec::new(); + let mut key_change = + self.connection.as_mut().unwrap().write_hs(&mut buf); + + let mut level_upgraded = false; + if let Some(key_change) = key_change.take() { + level_upgraded = self.process_key_change(ex_data, key_change)?; + } + + if !level_upgraded && self.one_rtt_keys_secrets.is_some() { + let keys_secrets = self.one_rtt_keys_secrets.take().unwrap(); + self.handle_application_keys(ex_data, keys_secrets)?; + } + + if buf.is_empty() { + break; + } else { + self.write_crypto_stream(current_level, ex_data, buf.as_slice())?; + } + } + + let Some(conn) = self.connection.as_mut() else { + return Err(Error::TlsFail); + }; + + if let Some(zero_rtt_keys) = conn.zero_rtt_keys() { + let space = &mut ex_data.crypto_ctx[packet::Epoch::Application]; + match self.side { + Side::Client => { + if space.crypto_seal.is_some() { + error!("client zero_rtt_keys are already present"); + }; + + space.crypto_seal = Some(Seal::from(zero_rtt_keys)); + }, + Side::Server => { + if space.crypto_0rtt_open.is_some() { + error!("server zero_rtt_keys are already present"); + }; + + space.crypto_0rtt_open = Some(Open::from(zero_rtt_keys)); + }, + } + self.highest_level = Level::ZeroRTT; + } + + trace!( + "handshake status side={:?}, kind={:?}, ongoing={:?}, alpn={:?}", + self.side, + conn.handshake_kind(), + conn.is_handshaking(), + match conn.alpn_protocol() { + None => "", + Some(alpn) => { + str::from_utf8(alpn).unwrap() + }, + } + ); + + // setting a session value to allow for tls resumption / zero-rtt + if matches!(self.side, Side::Client) && + !conn.is_handshaking() && + ex_data.session.is_none() + { + let mut session = Vec::new(); + + let local_params = + self.quic_transport_params.as_ref().unwrap().as_slice(); + let local_params_len: [u8; 8] = + (local_params.len() as u64).to_be_bytes(); + session.extend_from_slice(&local_params_len); + session.extend_from_slice(local_params); + + let peer_params = self.quic_transport_params(); + let peer_params_len: [u8; 8] = + (peer_params.len() as u64).to_be_bytes(); + session.extend_from_slice(&peer_params_len); + session.extend_from_slice(peer_params); + + *ex_data.session = Some(session); + } + + Ok(()) + } + + fn process_key_change( + &mut self, ex_data: &mut ExData, key_change: KeyChange, + ) -> Result { + match key_change { + KeyChange::Handshake { keys } => match self.highest_level { + Level::Initial => { + let next_space = + &mut ex_data.crypto_ctx[packet::Epoch::Handshake]; + + if next_space.crypto_seal.is_some() || + next_space.crypto_open.is_some() + { + debug_assert!( + false, + "keys are already present for Handshake" + ); + }; + + self.highest_level = Level::Handshake; + let (open, seal) = key_material_from_keys(keys, None)?; + next_space.crypto_open = Some(open); + next_space.crypto_seal = Some(seal); + + self.highest_level = Level::Handshake; + Ok(true) + }, + Level::ZeroRTT | Level::Handshake | Level::OneRTT => { + debug_assert!(false, "required to handle handshake keys"); + Ok(false) + }, + }, + + KeyChange::OneRtt { keys, next } => + self.handle_application_keys(ex_data, (keys, next)), + } + } + + fn handle_application_keys( + &mut self, ex_data: &mut ExData, keys_secrets: (Keys, Secrets), + ) -> Result { + self.highest_level = Level::OneRTT; + + if !self.is_completed() { + // avoid accepts of 1 RTT data before handshake is finished + // temporarily storing keys/secrets in handshake + // populate in space once handshake is compeleted + self.one_rtt_keys_secrets = Some((keys_secrets.0, keys_secrets.1)); + + Ok(false) + } else { + let next_space = &mut ex_data.crypto_ctx[packet::Epoch::Application]; + + let (open, seal) = + key_material_from_keys(keys_secrets.0, Some(keys_secrets.1))?; + next_space.crypto_open = Some(open); + next_space.crypto_seal = Some(seal); + + Ok(true) + } + } + + fn write_crypto_stream( + &self, level: Level, ex_data: &mut ExData, data: &[u8], + ) -> Result<()> { + let pkt_num_space = match level { + Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial], + Level::ZeroRTT => unreachable!(), + Level::Handshake => &mut ex_data.crypto_ctx[packet::Epoch::Handshake], + Level::OneRTT => &mut ex_data.crypto_ctx[packet::Epoch::Application], + }; + + pkt_num_space.crypto_stream.send.write(data, false)?; + + debug!( + "handshake crypto data written side={:?}, level={:?}, sent={}", + self.side, + self.highest_level, + data.len() + ); + Ok(()) + } + + pub fn process_post_handshake( + &mut self, _ex_data: &mut ExData, + ) -> Result<()> { + // no-op + Ok(()) + } + + pub fn write_level(&self) -> Level { + self.highest_level + } + + pub fn cipher(&self) -> Option { + let suite = self + .connection + .as_ref() + .and_then(|c| c.negotiated_cipher_suite()); + let Some(suite) = suite else { return None }; + + match suite.suite() { + CipherSuite::TLS13_AES_128_GCM_SHA256 => Some(Algorithm::AES128_GCM), + CipherSuite::TLS13_AES_256_GCM_SHA384 => Some(Algorithm::AES256_GCM), + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => + Some(Algorithm::ChaCha20_Poly1305), + _ => None, + } + } + + pub fn is_completed(&self) -> bool { + if let Some(conn) = &self.connection { + return !conn.is_handshaking(); + } + + false + } + + pub fn is_resumed(&self) -> bool { + if let Some(conn) = &self.connection { + if let Some(kind) = conn.handshake_kind() { + return matches!(kind, HandshakeKind::Resumed); + } + } + + false + } + + pub fn clear(&mut self) -> Result<()> { + self.connection = None; + Ok(()) + } + + pub fn set_session(&mut self, session: &[u8]) -> Result<()> { + match self.side { + // peer transport parameters are part of the resumption_store + Side::Client => { + self.set_quic_transport_params(session)?; + Ok(()) + }, + Side::Server => { + error!("set session is a client only operation"); + Err(Error::TlsFail) + }, + } + } + + pub fn curve(&self) -> Option { + let Some(conn) = &self.connection else { + return None; + }; + + let Some(kx_group) = conn.negotiated_key_exchange_group() else { + return None; + }; + + Some(format!("{:?}", kx_group.name())) + } + + pub fn sigalg(&self) -> Option { + // this information is not available through the connection + // only handled internally within rustls during handshake + // loggable with level trace + None + } + + pub fn peer_cert_chain(&self) -> Option> { + let Some(conn) = &self.connection else { + return None; + }; + + if let Some(certs) = conn.peer_certificates() { + let out: Vec<&[u8]> = certs.iter().map(|c| c.as_ref()).collect(); + if out.len() > 0 { + Some(out) + } else { + None + } + } else { + None + } + } + + pub fn peer_cert(&self) -> Option<&[u8]> { + let Some(conn) = &self.connection else { + return None; + }; + + if let Some(certs) = conn.peer_certificates() { + certs.first().map(|c| c.as_ref()) + } else { + None + } + } + + pub fn is_in_early_data(&self) -> bool { + let Some(conn) = &self.connection else { + return false; + }; + conn.zero_rtt_keys().is_some() + } +} diff --git a/quiche/src/tls/rustls/verifiers.rs b/quiche/src/tls/rustls/verifiers.rs new file mode 100644 index 0000000000..67b73bdfaf --- /dev/null +++ b/quiche/src/tls/rustls/verifiers.rs @@ -0,0 +1,157 @@ +// Copyright (C) 2025, Cloudflare, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::crypto::crypto_provider; +use rustls::client::danger::HandshakeSignatureValid; +use rustls::client::danger::ServerCertVerified; +use rustls::client::danger::ServerCertVerifier; +use rustls::crypto::WebPkiSupportedAlgorithms; +use rustls::pki_types::CertificateDer; +use rustls::pki_types::ServerName; +use rustls::pki_types::UnixTime; +use rustls::server::danger::ClientCertVerified; +use rustls::server::danger::ClientCertVerifier; +use rustls::DigitallySignedStruct; +use rustls::DistinguishedName; +use rustls::Error; +use rustls::InvalidMessage; +use rustls::SignatureScheme; +use std::fmt::Debug; + +#[derive(Debug)] +pub(super) struct DisabledServerCertVerifier { + supported_algorithms: WebPkiSupportedAlgorithms, +} + +impl DisabledServerCertVerifier { + pub(super) fn new() -> crate::Result { + let provider = crypto_provider(); + Ok(Self { + supported_algorithms: provider.signature_verification_algorithms, + }) + } +} + +impl ServerCertVerifier for DisabledServerCertVerifier { + fn verify_server_cert( + &self, _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>, + _ocsp_response: &[u8], _now: UnixTime, + ) -> Result { + warn!( + "Server certificate validation is disabled! \ + Use quiche::Config.verify_peer(true) to enable certificate validation." + ); + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + error!("TLS 1.2 is not supported within the Quic protocol."); + Err(Error::InvalidMessage( + InvalidMessage::UnknownProtocolVersion, + )) + } + + fn verify_tls13_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + warn!( + "Server certificate validation is disabled! \ + Use quiche::Config.verify_peer(true) to enable certificate validation." + ); + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + self.supported_algorithms + .mapping + .iter() + .map(|item| item.0) + .collect() + } +} + +#[derive(Debug)] +pub(super) struct RejectedClientCertAllowedAnonymousVerifier { + supported_algorithms: WebPkiSupportedAlgorithms, + client_auth_mandatory: bool, +} + +impl RejectedClientCertAllowedAnonymousVerifier { + pub(super) fn new() -> crate::Result { + let provider = crypto_provider(); + Ok(Self { + supported_algorithms: provider.signature_verification_algorithms, + client_auth_mandatory: false, + }) + } +} + +impl ClientCertVerifier for RejectedClientCertAllowedAnonymousVerifier { + fn client_auth_mandatory(&self) -> bool { + self.client_auth_mandatory + } + + fn root_hint_subjects(&self) -> &[DistinguishedName] { + &[] + } + + fn verify_client_cert( + &self, _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], _now: UnixTime, + ) -> Result { + Err(Error::General("no CA certificates configured to verify presented client auth certificate".to_string())) + } + + fn verify_tls12_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + error!("TLS 1.2 is not supported within the Quic protocol."); + Err(Error::InvalidMessage( + InvalidMessage::UnknownProtocolVersion, + )) + } + + fn verify_tls13_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Err(Error::General("no CA certificates configured to verify presented client auth certificate".to_string())) + } + + fn supported_verify_schemes(&self) -> Vec { + self.supported_algorithms + .mapping + .iter() + .map(|item| item.0) + .collect() + } +} diff --git a/tokio-quiche/Cargo.toml b/tokio-quiche/Cargo.toml index 9b6d530cba..d9cef92cf7 100644 --- a/tokio-quiche/Cargo.toml +++ b/tokio-quiche/Cargo.toml @@ -15,6 +15,12 @@ readme = "README.md" fuzzing = ["quiche/fuzzing"] quiche_internal = ["quiche/internal"] +default = ["quiche/boringssl-boring-crate"] + +rustls-aws-lc-rs = ["quiche/rustls-aws-lc-rs", "__rustls"] +rustls-ring = ["quiche/rustls-ring", "__rustls"] +__rustls = [] + # Enable extra timing instrumentation for QUIC handshakes, including protocol # overhead and network delays. perf-quic-listener-metrics = [] @@ -52,7 +58,7 @@ ipnetwork = { workspace = true } log = { workspace = true } octets = { workspace = true } pin-project = { workspace = true } -quiche = { workspace = true, features = ["boringssl-boring-crate", "qlog"] } +quiche = { workspace = true, features = ["qlog"] } serde = { workspace = true, features = ["derive", "rc"] } serde_with = { workspace = true } slog-scope = { workspace = true } @@ -71,6 +77,7 @@ tokio-util = { workspace = true, features = [ ] } triomphe = { workspace = true } url = { workspace = true } +env_logger = "0.10.2" [dev-dependencies] h3i = { workspace = true } diff --git a/tokio-quiche/src/quic/connection/mod.rs b/tokio-quiche/src/quic/connection/mod.rs index c9fa83d977..85b0220d0d 100644 --- a/tokio-quiche/src/quic/connection/mod.rs +++ b/tokio-quiche/src/quic/connection/mod.rs @@ -33,6 +33,7 @@ pub use self::id::ConnectionIdGenerator; pub use self::id::SimpleConnectionIdGenerator; pub(crate) use self::map::ConnectionMap; +#[cfg(not(feature = "__rustls"))] use boring::ssl::SslRef; use datagram_socket::AsSocketStats; use datagram_socket::DatagramSocketSend; @@ -230,6 +231,7 @@ where /// [boring]'s SSL object for this connection. #[doc(hidden)] + #[cfg(not(feature = "__rustls"))] pub fn ssl_mut(&mut self) -> &mut SslRef { self.params.quiche_conn.as_mut() } diff --git a/tokio-quiche/src/quic/hooks.rs b/tokio-quiche/src/quic/hooks.rs index 316c90e4d2..ed7bae8da1 100644 --- a/tokio-quiche/src/quic/hooks.rs +++ b/tokio-quiche/src/quic/hooks.rs @@ -24,7 +24,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(not(feature = "__rustls"))] use crate::settings::TlsCertificatePaths; +#[cfg(not(feature = "__rustls"))] use boring::ssl::SslContextBuilder; /// A set of hooks executed at the level of a [quiche::Connection]. @@ -38,6 +40,7 @@ pub trait ConnectionHook { /// /// Only called if both the hook and [`TlsCertificatePaths`] are set in /// [`ConnectionParams`](crate::ConnectionParams). + #[cfg(not(feature = "__rustls"))] fn create_custom_ssl_context_builder( &self, settings: TlsCertificatePaths<'_>, ) -> Option; diff --git a/tokio-quiche/src/quic/io/worker.rs b/tokio-quiche/src/quic/io/worker.rs index dce1d9a52b..8af757b92b 100644 --- a/tokio-quiche/src/quic/io/worker.rs +++ b/tokio-quiche/src/quic/io/worker.rs @@ -51,6 +51,7 @@ use crate::quic::router::ConnectionMapCommand; use crate::quic::QuicheConnection; use crate::QuicResult; +#[cfg(not(feature = "__rustls"))] use boring::ssl::SslRef; use datagram_socket::DatagramSocketSend; use datagram_socket::DatagramSocketSendExt; @@ -630,6 +631,7 @@ pub struct Running { } impl Running { + #[cfg(not(feature = "__rustls"))] pub fn ssl(&mut self) -> &mut SslRef { self.qconn.as_mut() } @@ -663,6 +665,7 @@ where // async callback from this task accross a channel, for example, will // cause issues as this waker will then be stale and attempt to // wake the wrong task. + #[cfg(not(feature = "__rustls"))] std::future::poll_fn(|cx| { let ssl = qconn.as_mut(); ssl.set_task_waker(Some(cx.waker().clone())); diff --git a/tokio-quiche/src/quic/router/mod.rs b/tokio-quiche/src/quic/router/mod.rs index 1ab8102c3d..e3f9b2c17e 100644 --- a/tokio-quiche/src/quic/router/mod.rs +++ b/tokio-quiche/src/quic/router/mod.rs @@ -788,11 +788,18 @@ mod tests { use tokio::net::UdpSocket; use tokio::time; + #[cfg(not(feature = "__rustls"))] const TEST_CERT_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/", "../quiche/examples/cert.crt" ); + #[cfg(feature = "__rustls")] + const TEST_CERT_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "../quiche/examples/cert_rustls.crt" + ); const TEST_KEY_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/", diff --git a/tokio-quiche/src/settings/config.rs b/tokio-quiche/src/settings/config.rs index da0d0f1c7a..6a09703a45 100644 --- a/tokio-quiche/src/settings/config.rs +++ b/tokio-quiche/src/settings/config.rs @@ -111,6 +111,7 @@ impl Config { fn make_quiche_config( params: &ConnectionParams, should_log_keys: bool, ) -> QuicResult { + #[cfg(not(feature = "__rustls"))] let ssl_ctx_builder = params .hooks .connection_hook @@ -118,6 +119,7 @@ fn make_quiche_config( .zip(params.tls_cert) .and_then(|(hook, tls)| hook.create_custom_ssl_context_builder(tls)); + #[cfg(not(feature = "__rustls"))] let mut config = if let Some(builder) = ssl_ctx_builder { quiche::Config::with_boring_ssl_ctx_builder( quiche::PROTOCOL_VERSION, @@ -127,6 +129,9 @@ fn make_quiche_config( quiche_config_with_tls(params.tls_cert)? }; + #[cfg(feature = "__rustls")] + let mut config = quiche_config_with_tls(params.tls_cert)?; + let quic_settings = ¶ms.settings; let alpns: Vec<&[u8]> = diff --git a/tokio-quiche/tests/fixtures/mod.rs b/tokio-quiche/tests/fixtures/mod.rs index 4a1a6f7862..1bda44ab16 100644 --- a/tokio-quiche/tests/fixtures/mod.rs +++ b/tokio-quiche/tests/fixtures/mod.rs @@ -72,11 +72,18 @@ pub mod h3i_fixtures; use h3i_fixtures::stream_body; +#[cfg(not(feature = "__rustls"))] pub const TEST_CERT_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/", "../quiche/examples/cert.crt" ); +#[cfg(feature = "__rustls")] +pub const TEST_CERT_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "../quiche/examples/cert_rustls.crt" +); pub const TEST_KEY_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/", @@ -100,6 +107,7 @@ impl TestConnectionHook { } impl ConnectionHook for TestConnectionHook { + #[cfg(not(feature = "__rustls"))] fn create_custom_ssl_context_builder( &self, _settings: TlsCertificatePaths<'_>, ) -> Option { diff --git a/tokio-quiche/tests/integration_tests/mod.rs b/tokio-quiche/tests/integration_tests/mod.rs index 3265064467..41cb8f766f 100644 --- a/tokio-quiche/tests/integration_tests/mod.rs +++ b/tokio-quiche/tests/integration_tests/mod.rs @@ -41,6 +41,7 @@ use tokio_quiche::settings::TlsCertificatePaths; use tokio_quiche::ConnectionParams; use tokio_quiche::InitialQuicConnection; +#[cfg(not(feature = "__rustls"))] pub mod async_callbacks; pub mod connection_close; pub mod headers; @@ -71,7 +72,10 @@ async fn echo() { assert_eq!(resps.len(), req_count(i)); } + #[cfg(not(feature = "__rustls"))] assert!(hook.was_called()); + #[cfg(feature = "__rustls")] + assert!(!hook.was_called()); } #[tokio::test] @@ -86,7 +90,10 @@ async fn e2e() { let resps = res_map.get(&1).unwrap(); assert_eq!(resps.len(), 1); + #[cfg(not(feature = "__rustls"))] assert!(hook.was_called()); + #[cfg(feature = "__rustls")] + assert!(!hook.was_called()); } #[tokio::test] @@ -115,7 +122,10 @@ async fn e2e_client_ip_validation_disabled() { let resps = res_map.get(&1).unwrap(); assert_eq!(resps.len(), 1); + #[cfg(not(feature = "__rustls"))] assert!(hook.was_called()); + #[cfg(feature = "__rustls")] + assert!(!hook.was_called()); } #[with_test_telemetry(tokio::test)] diff --git a/tokio-quiche/tests/integration_tests/timeouts.rs b/tokio-quiche/tests/integration_tests/timeouts.rs index 1dc153de9c..8119ab349a 100644 --- a/tokio-quiche/tests/integration_tests/timeouts.rs +++ b/tokio-quiche/tests/integration_tests/timeouts.rs @@ -24,17 +24,25 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; -use boring::ssl::BoxSelectCertFinish; -use boring::ssl::ClientHello; -use boring::ssl::SslContextBuilder; -use boring::ssl::SslFiletype; -use boring::ssl::SslMethod; +#[cfg(not(feature = "__rustls"))] +mod boringssl { + pub(super) use boring::ssl::BoxSelectCertFinish; + pub(super) use boring::ssl::ClientHello; + pub(super) use boring::ssl::SslContextBuilder; + pub(super) use boring::ssl::SslFiletype; + pub(super) use boring::ssl::SslMethod; + pub(super) use std::sync::atomic::AtomicBool; + pub(super) use tokio_quiche::quic::ConnectionHook; + pub(super) use tokio_quiche::settings::TlsCertificatePaths; +} +#[cfg(not(feature = "__rustls"))] +use self::boringssl::*; + use h3i::actions::h3::send_headers_frame; use h3i::actions::h3::Action; use h3i::actions::h3::WaitType; @@ -45,8 +53,6 @@ use h3i::quiche::{ use tokio::net::UdpSocket; use tokio::time::timeout; use tokio_quiche::http3::driver::H3ConnectionError; -use tokio_quiche::quic::ConnectionHook; -use tokio_quiche::settings::TlsCertificatePaths; use url::Url; use crate::fixtures::h3i_fixtures::*; @@ -54,6 +60,7 @@ use crate::fixtures::*; // TODO(erittenhouse): figure out a way to avoid all of this duplication #[tokio::test] +#[cfg(not(feature = "__rustls"))] async fn test_handshake_duration_ioworker() { use h3i::client::ClientError;