From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 482511FF13C for ; Thu, 25 Jun 2026 13:23:09 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1F015E455; Thu, 25 Jun 2026 13:23:09 +0200 (CEST) From: Shannon Sterz To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox 3/3] http: tls: add integration tests for openssl verify callbacks Date: Thu, 25 Jun 2026 13:22:36 +0200 Message-ID: <20260625112236.188257-3-s.sterz@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260625112236.188257-1-s.sterz@proxmox.com> References: <20260625112236.188257-1-s.sterz@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1782386572763 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.107 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: MZU5MRUYEERQTD2XB464SNTVPYPFWAHK X-Message-ID-Hash: MZU5MRUYEERQTD2XB464SNTVPYPFWAHK X-MailFrom: s.sterz@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: these integration tests intend to clearly encode how TLS verification is handled. they work by spawning a TLS server via `openssl s_server` and connecting to it. to avoid having the tests fail due to there not being an open port, a unix socket is used for the connection. encoded in these tests is the following behaviour for the callback: 1. if a single certificate is used by the server (for example, a self-signed certificate): a. if the fingerprint matches the certificate -> valid connection b. if no fingerprint was provided -> fall back to OpenSSL's checks 2. if a certificate chain was provided by the server: a. if the fingerprint matches any certificate in the chain -> valid connection b. if no fingerprint was provided -> fall back to OpenSSL's checks tests for the new and old behavior differ in one key way: the old behavior accepted connections that did not provide a certificate (chain) matching the fingerprint but were valid according to OpenSSL. this broke the implicit assumption that providing a fingerprint acted like "pinning" a certificate. the new behavior does not accept such connections. note that 2.a.) technically specifies new, or at least previously undefined behavior. previously a fingerprint was only checked against a leaf certificate of a chain. however, pinning an (intermediate) ca instead of the specific leaf certificate represents a valid use-case. adding this ability to both, the new and old behavior makes sense as it only allows more flexibility and, thus, would not break any existing setups. Signed-off-by: Shannon Sterz --- Cargo.toml | 1 + proxmox-http/Cargo.toml | 2 + proxmox-http/tests/certs/cert-chain.pem | 46 ++ .../tests/certs/intermediate-cert.pem | 23 + proxmox-http/tests/certs/intermediate-csr.pem | 17 + proxmox-http/tests/certs/intermediate-key.pem | 28 ++ proxmox-http/tests/certs/leaf-cert.pem | 24 + proxmox-http/tests/certs/leaf-csr.pem | 17 + proxmox-http/tests/certs/leaf-key.pem | 28 ++ proxmox-http/tests/certs/root-cert.pem | 23 + proxmox-http/tests/certs/root-key.pem | 28 ++ proxmox-http/tests/certs/self-signed-cert.pem | 23 + proxmox-http/tests/certs/self-signed-key.pem | 28 ++ proxmox-http/tests/common/mod.rs | 412 ++++++++++++++++++ proxmox-http/tests/openssl_verify_cb_new.rs | 57 +++ proxmox-http/tests/openssl_verify_cb_old.rs | 49 +++ 16 files changed, 806 insertions(+) create mode 100644 proxmox-http/tests/certs/cert-chain.pem create mode 100644 proxmox-http/tests/certs/intermediate-cert.pem create mode 100644 proxmox-http/tests/certs/intermediate-csr.pem create mode 100644 proxmox-http/tests/certs/intermediate-key.pem create mode 100644 proxmox-http/tests/certs/leaf-cert.pem create mode 100644 proxmox-http/tests/certs/leaf-csr.pem create mode 100644 proxmox-http/tests/certs/leaf-key.pem create mode 100644 proxmox-http/tests/certs/root-cert.pem create mode 100644 proxmox-http/tests/certs/root-key.pem create mode 100644 proxmox-http/tests/certs/self-signed-cert.pem create mode 100644 proxmox-http/tests/certs/self-signed-key.pem create mode 100644 proxmox-http/tests/common/mod.rs create mode 100644 proxmox-http/tests/openssl_verify_cb_new.rs create mode 100644 proxmox-http/tests/openssl_verify_cb_old.rs diff --git a/Cargo.toml b/Cargo.toml index bef718ec..a47baa0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ serde_plain = "1.0" syn = { version = "2", features = [ "full", "visit-mut" ] } sync_wrapper = "1" tar = "0.4" +tempfile = "3.15" termcolor = "1.1.2" thiserror = "2" tokio = "1.6" diff --git a/proxmox-http/Cargo.toml b/proxmox-http/Cargo.toml index aadb6a42..bff65e9c 100644 --- a/proxmox-http/Cargo.toml +++ b/proxmox-http/Cargo.toml @@ -44,6 +44,8 @@ proxmox-compression = { workspace = true, optional = true } [dev-dependencies] tokio = { workspace = true, features = [ "macros" ] } flate2 = { workspace = true } +proxmox-sys = { workspace = true } +tempfile = { workspace = true } [features] default = [] diff --git a/proxmox-http/tests/certs/cert-chain.pem b/proxmox-http/tests/certs/cert-chain.pem new file mode 100644 index 00000000..a88ae376 --- /dev/null +++ b/proxmox-http/tests/certs/cert-chain.pem @@ -0,0 +1,46 @@ +-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCQVQx +DzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAdQcm94 +bW94MRAwDgYDVQQLDAdQcm94bW94MScwJQYDVQQDDB50bHMtY2VydC12YWxpZGF0 +aW9uLXJvb3QudGVzdHMwIBcNMjYwNjI0MTM0MjAxWhgPMzAyNTEwMjUxMzQyMDFa +MHMxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVubmExEDAOBgNVBAoMB1Byb3ht +b3gxEDAOBgNVBAsMB1Byb3htb3gxLzAtBgNVBAMMJnRscy1jZXJ0LXZhbGlkYXRp +b24taW50ZXJtZWRpYXRlLnRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2/UMNpaQ7WsTmUFvXO/FyGcKSaAjcLvfj15ezjcJxlDr1S580Z3T7kph +boR5K8iH8nJa0Ujv7ty2c0HgpKZIduYzZ5+hv1K7czbpQRqoY5956O06VCUuIYC5 +Oa2byEV10PBojckd/opFW0rhCR1+VTtRLXinQ64RVdcfnHVbWpV8QBks96ZeNHAX +mv5FlYICHfgCQm8FAljN1X3dQXkroHErmXDw6BXGrfYBnXlI4p+e5cj9pBJHqRJ0 +XGsnoAv1xmEmgsSm6S+wRZ/+dtY6rs3N//qz6J9ZuyAccjKpY5yS1Xa6/f+7IJFd +W7MGh+DZ6kWJ1MxAbTKcIUo4P4GOUQIDAQABo2YwZDAdBgNVHQ4EFgQUa+cbP/Az +gyufv3AtFJdQ7tY6iGswHwYDVR0jBBgwFoAUsI4iO4yr1STBmK9TWO0+bgf9fCsw +EgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggEBAFq9zeICWKkM7P5D2e0f1tUt4krV45D7avZqkNe7tlglm9g66UlDHXH9 +bvVPYkPG3mHSn0QtEykTgzCB5l1EgL3+OL32SoOBOQtrS0ePlmVhe05ydgqf7oFf +EECvghk+Z96cSRtgPwrWp8U93C3Zggaf56QI7KqZg9QpYCjyAG+pGrhoPTmyMv8H +PLxgGw7fJyviwHLrHCUxZridQ6w9fOT02aqsIoock9df9YBJuDKJILDI3oXaeKds +y09qJ/2kwJPlHrtyvqOdWrrJz/x3trpOO1cpH96VttYW0usHOsWdgqvc9tR7Axwf +TYthAFu4PV97914MfSvvPPEyP+C6NIA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID6zCCAtOgAwIBAgIUcHTmdgi8DylmBhrTr4NBKpnLBk4wDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmll +bm5hMRAwDgYDVQQKDAdQcm94bW94MRAwDgYDVQQLDAdQcm94bW94MScwJQYDVQQD +DB50bHMtY2VydC12YWxpZGF0aW9uLXJvb3QudGVzdHMwIBcNMjYwNjI0MTMzODIw +WhgPMzAyNTEwMjUxMzM4MjBaMHwxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVu +bmExDzANBgNVBAcMBlZpZW5uYTEQMA4GA1UECgwHUHJveG1veDEQMA4GA1UECwwH +UHJveG1veDEnMCUGA1UEAwwedGxzLWNlcnQtdmFsaWRhdGlvbi1yb290LnRlc3Rz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoIyemjaxlCB1jDMgBooZ +/Bhhprhbz6OxoAGFpI8T7ppzZFKoFOWIHEJcYrvqAQn0RqZe8EIUXbquIYjBZPnA +bwxrZHMgcC5J/f0pRD+N68uCUU2EPaoHqk+v/YMM+zTViftWIFpU17HfSmEkPR4l +9OljcYendp1nMRsRB3dxTPmoK/D1E87LVrz7GlsuoFDJE3CfBQ6eNUMSdNbicDF0 +xwzyzQsCGuCboHa1Q+fFU1Se3Lts1S+X7TdX2XySvFziq3wkAKaMZ46C4wM25+Z7 +Is+kBDxIxaPq13XdpneG5Iis2hT2ruWSbopuD57Zuh83/HQSSNlaaPFivib2O9kA +1QIDAQABo2MwYTAdBgNVHQ4EFgQUsI4iO4yr1STBmK9TWO0+bgf9fCswHwYDVR0j +BBgwFoAUsI4iO4yr1STBmK9TWO0+bgf9fCswDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAEM83xjtDyctuwYTCuwM7KRt +StuAmtnw/snfBJZgCKNPi0fOM1rzwM1g/h4LrH7go9VpxQ2VtT+9/20MBhlqWdAN +sI1IpUMArsmzlKaBZUZDrS3An9iRztsmnftLfkXyku6nUcb8TPDmE5r1arnDngsX ++EcINC1DgOTy4Sv9vWv6apJQtNg+/Xqs+Ax+4iIXDJde28SX7p8vTdkBKLhHnGLJ +zrEI2DzGqy8+sPTKSYGw3oNH3QUwf3FJnZKJGifmiehzdHkVKF3XesBddQjWOM/y +E4yYJqlwpDhykDEz5d6sD6F/5mw3LOqk2J2jfDbPN5IEagYEDMzAqx10Wi7U+94= +-----END CERTIFICATE----- diff --git a/proxmox-http/tests/certs/intermediate-cert.pem b/proxmox-http/tests/certs/intermediate-cert.pem new file mode 100644 index 00000000..4a56c76c --- /dev/null +++ b/proxmox-http/tests/certs/intermediate-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCQVQx +DzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmllbm5hMRAwDgYDVQQKDAdQcm94 +bW94MRAwDgYDVQQLDAdQcm94bW94MScwJQYDVQQDDB50bHMtY2VydC12YWxpZGF0 +aW9uLXJvb3QudGVzdHMwIBcNMjYwNjI0MTM0MjAxWhgPMzAyNTEwMjUxMzQyMDFa +MHMxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVubmExEDAOBgNVBAoMB1Byb3ht +b3gxEDAOBgNVBAsMB1Byb3htb3gxLzAtBgNVBAMMJnRscy1jZXJ0LXZhbGlkYXRp +b24taW50ZXJtZWRpYXRlLnRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2/UMNpaQ7WsTmUFvXO/FyGcKSaAjcLvfj15ezjcJxlDr1S580Z3T7kph +boR5K8iH8nJa0Ujv7ty2c0HgpKZIduYzZ5+hv1K7czbpQRqoY5956O06VCUuIYC5 +Oa2byEV10PBojckd/opFW0rhCR1+VTtRLXinQ64RVdcfnHVbWpV8QBks96ZeNHAX +mv5FlYICHfgCQm8FAljN1X3dQXkroHErmXDw6BXGrfYBnXlI4p+e5cj9pBJHqRJ0 +XGsnoAv1xmEmgsSm6S+wRZ/+dtY6rs3N//qz6J9ZuyAccjKpY5yS1Xa6/f+7IJFd +W7MGh+DZ6kWJ1MxAbTKcIUo4P4GOUQIDAQABo2YwZDAdBgNVHQ4EFgQUa+cbP/Az +gyufv3AtFJdQ7tY6iGswHwYDVR0jBBgwFoAUsI4iO4yr1STBmK9TWO0+bgf9fCsw +EgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggEBAFq9zeICWKkM7P5D2e0f1tUt4krV45D7avZqkNe7tlglm9g66UlDHXH9 +bvVPYkPG3mHSn0QtEykTgzCB5l1EgL3+OL32SoOBOQtrS0ePlmVhe05ydgqf7oFf +EECvghk+Z96cSRtgPwrWp8U93C3Zggaf56QI7KqZg9QpYCjyAG+pGrhoPTmyMv8H +PLxgGw7fJyviwHLrHCUxZridQ6w9fOT02aqsIoock9df9YBJuDKJILDI3oXaeKds +y09qJ/2kwJPlHrtyvqOdWrrJz/x3trpOO1cpH96VttYW0usHOsWdgqvc9tR7Axwf +TYthAFu4PV97914MfSvvPPEyP+C6NIA= +-----END CERTIFICATE----- diff --git a/proxmox-http/tests/certs/intermediate-csr.pem b/proxmox-http/tests/certs/intermediate-csr.pem new file mode 100644 index 00000000..824248d8 --- /dev/null +++ b/proxmox-http/tests/certs/intermediate-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICyjCCAbICAQAwgYQxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVubmExDzAN +BgNVBAcMBlZpZW5uYTEQMA4GA1UECgwHUHJveG1veDEQMA4GA1UECwwHUHJveG1v +eDEvMC0GA1UEAwwmdGxzLWNlcnQtdmFsaWRhdGlvbi1pbnRlcm1lZGlhdGUudGVz +dHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb9Qw2lpDtaxOZQW9c +78XIZwpJoCNwu9+PXl7ONwnGUOvVLnzRndPuSmFuhHkryIfyclrRSO/u3LZzQeCk +pkh25jNnn6G/UrtzNulBGqhjn3no7TpUJS4hgLk5rZvIRXXQ8GiNyR3+ikVbSuEJ +HX5VO1EteKdDrhFV1x+cdVtalXxAGSz3pl40cBea/kWVggId+AJCbwUCWM3Vfd1B +eSugcSuZcPDoFcat9gGdeUjin57lyP2kEkepEnRcayegC/XGYSaCxKbpL7BFn/52 +1jquzc3/+rPon1m7IBxyMqljnJLVdrr9/7sgkV1bswaH4NnqRYnUzEBtMpwhSjg/ +gY5RAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAsI2k85A0oMQRAqeZEG7mV48y +65FQ/WzdnIe1/SsmGYnrJ4ZWZF69+hRCTaVrEJEIlbD1lT+teKYlE1x4aMR8zaWo +CymBFfDAjRUyR4s38BliXslekivC7o8IUcyi7prjOvMHtK3p+1f+wPCyz6jDSD9V +9LuHi7kdZBfCUIxBtPtGrGdouf+s6LkTv64DGyldsturDl3CnRvUaRDt95qc6gUW +YhMLv/bzxet75htvk4H2VhpZn/ZKhJRNebetDFWH0LWB5IouLOBdDsDkWRMaV0x9 +HYbG3jZ8NgSHmqAxqxSq/dt6XSJ9mpVl9vTaAPxq05v337j54FC34oz0q0pK9Q== +-----END CERTIFICATE REQUEST----- diff --git a/proxmox-http/tests/certs/intermediate-key.pem b/proxmox-http/tests/certs/intermediate-key.pem new file mode 100644 index 00000000..beb4bad6 --- /dev/null +++ b/proxmox-http/tests/certs/intermediate-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDb9Qw2lpDtaxOZ +QW9c78XIZwpJoCNwu9+PXl7ONwnGUOvVLnzRndPuSmFuhHkryIfyclrRSO/u3LZz +QeCkpkh25jNnn6G/UrtzNulBGqhjn3no7TpUJS4hgLk5rZvIRXXQ8GiNyR3+ikVb +SuEJHX5VO1EteKdDrhFV1x+cdVtalXxAGSz3pl40cBea/kWVggId+AJCbwUCWM3V +fd1BeSugcSuZcPDoFcat9gGdeUjin57lyP2kEkepEnRcayegC/XGYSaCxKbpL7BF +n/521jquzc3/+rPon1m7IBxyMqljnJLVdrr9/7sgkV1bswaH4NnqRYnUzEBtMpwh +Sjg/gY5RAgMBAAECggEABQOjEaOBDkiAm9/IACBfK+Bddaw24p0FzajjFGRgzqqN +lcCHi+fDKw17Bx/x+zOJFdfRhi/ZeGKDrkD0NAyuXjeFOHmFIG1sZIX970QCTrMV +/l9aEwz97jmW/1+by4b51peEaqcJDgJs7lXYp3KKrLq7cQPtHDfdoU1UJSbvvDLd +Xl7gZ4WYla4EAncFsA1K5jBuANG66mO8Gmumz+eLmslUB2RVLxPrVx0PybSXxQII +rnzd6V24DV6+VkN0lBVm1k98Is4hbCEmMyZGweWX9wjdCzMPgqqPHbto7gY8UkPM +Iz+BwbTI59pM9/m0BLtPxUd7Sccy076L0PtC3XYJeQKBgQDwLISbPRjJaGnJMpU9 +8M5+6n0D8E+BlRCS6Qkav6X/gy6rdNAl5hQjDaSZk72D7a+DGJUBsa9F9GgIw8pW +Btkd+4/ukqLP3aoSKTKTRtt7tFZMPi+5CJJSGUDIDbmS0XrxxNMudu7kO8ODmPE3 +w5uHSYztw+I6yGMNWY5GmCdjOwKBgQDqc37YmadECvq1IwHZiXWprDowe/qL+VYy +VFZj/zGABRxSGOoIc/AW8MjbDh+WEyErIkVkxj3AZ8N85sziIJH0+K823DWtiILR +zuUJ1GrVfTzkGjFTrrCJd6Ev0fBKO7/jtpGOYD9XpCtEoxz30RDNjq+aIv57B53w +3+Gq7tKj4wKBgQCjKtt8S+nHC3SzB/Z0emEPwGbmgiDBvG/iHwfccE9qY8kVGus+ +lC0iE2a8H68lLhmLSuwQlpKpR/5V1g5km4pt4DZMsrqB1epxJCQEAqOiS0ZFzgnF +/5jIxfdI8moc4MxR7JI8gviRfji58vIOHIpRQxrHfcj4fqMssqcCNuSreQKBgQCt +EC5tQxcGkjg4p6vA4cg6REj76ziqRJaNNlZDIGhwwNUEASIYtURgGsOZd9Z3GI3e +YkDpP7Drq2zRcSmCLlqvgzcLfwgcne07ZMcLN4LZLsZY9sC8rfHgt68DNqxyj6J5 +PBY8C+4WCrhpxSIoCGqn4hDb7cL+HERJP2o8nGhe0wKBgDddzovqa7xEJAJAKFU9 +YiZGhSq7sB/1MXEAzmbQLFUmuMpQFEVv4hjXry1d3n51Y2M8AbOLRttZD5myN/QT +BFoKmfQzOY5IydfLy4XGmsB/dVovd2Sq3JUuqeA9rtPvkGZcLppiipGc8mkSxpI2 +RGcpj1GoYLQKcFQraBdFWxhG +-----END PRIVATE KEY----- diff --git a/proxmox-http/tests/certs/leaf-cert.pem b/proxmox-http/tests/certs/leaf-cert.pem new file mode 100644 index 00000000..a6260999 --- /dev/null +++ b/proxmox-http/tests/certs/leaf-cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID9DCCAtygAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwczELMAkGA1UEBhMCQVQx +DzANBgNVBAgMBlZpZW5uYTEQMA4GA1UECgwHUHJveG1veDEQMA4GA1UECwwHUHJv +eG1veDEvMC0GA1UEAwwmdGxzLWNlcnQtdmFsaWRhdGlvbi1pbnRlcm1lZGlhdGUu +dGVzdHMwIBcNMjYwNjI0MTM1MTA1WhgPMzAyNTEwMjUxMzUxMDVaMHwxCzAJBgNV +BAYTAkFUMQ8wDQYDVQQIDAZWaWVubmExDzANBgNVBAcMBlZpZW5uYTEQMA4GA1UE +CgwHUHJveG1veDEQMA4GA1UECwwHUHJveG1veDEnMCUGA1UEAwwedGxzLWNlcnQt +dmFsaWRhdGlvbi1sZWFmLnRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAkHL4lBzjkvGm0iNRwLijTKqLIYL5kjr2rfepjtFV0f/s+WDjSUcbgryv +VrjoAazt4G4huxFsWA1Yc/wq90ut5FQCn3zID/XUTjloTOE7S7jyc+a+agbZNMEj +R2udNqNPIMAUDnCss5PDq/S+15lnPdkK8YWk4vl2hT4GiU3P8xv6WZQPWlJA0gRe +mA2/sCdzxux0YP5yOUPJ9ZnYTgJyXdZ1WigsjEwzKgjWBzmRMDp81T02Pyan8oUq +frCzC/jIoeyC7JemZCzVaEhkbRRmh9eS3O9trIjjeXGVdi04JUDdeecFWXVRO9G2 +xRc71L4OxHuHUoJNyYbIkn6j5IiaewIDAQABo4GGMIGDMAkGA1UdEwQCMAAwEQYJ +YIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF +BQcDATAfBgNVHSMEGDAWgBRr5xs/8DODK5+/cC0Ul1Du1jqIazAdBgNVHQ4EFgQU +xakweDrqyFkO8jE7UamgXUN/fOcwDQYJKoZIhvcNAQELBQADggEBALZaE4lP+Krm +gEnFfG+CftOzC5zmjAI56aZJ661n87Lh/ECQdgRbvvtAqFGdmJi5GFRkesJSyfIx +YGFj1mc2TC1BCv7bKJnoM2YFebXHiTJ+/ODuboiGEdbm5xWaowjgWutaXHr7O/aa +KV9mzFasJegO3i4rCVFfnNVDW+aq23OTYkgNiJjp0H2sSckzsyQFmYLqMPzGXk1F +ed2YpoNv4GT+HOfdP6ExWH1cL6AZI+jJ7/fEbBiEFyAsfaW7c8knphe/MGztWgCp +ayCoe48IWwGS2nzdUvjw0w+QF6PpLo6CiFap8qiALlvKK6hijsrKFpVvD1PxggRA +3mYsWKaRuHo= +-----END CERTIFICATE----- diff --git a/proxmox-http/tests/certs/leaf-csr.pem b/proxmox-http/tests/certs/leaf-csr.pem new file mode 100644 index 00000000..318d2e37 --- /dev/null +++ b/proxmox-http/tests/certs/leaf-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICwTCCAakCAQAwfDELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0G +A1UEBwwGVmllbm5hMRAwDgYDVQQKDAdQcm94bW94MRAwDgYDVQQLDAdQcm94bW94 +MScwJQYDVQQDDB50bHMtY2VydC12YWxpZGF0aW9uLWxlYWYudGVzdHMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQcviUHOOS8abSI1HAuKNMqoshgvmS +Ovat96mO0VXR/+z5YONJRxuCvK9WuOgBrO3gbiG7EWxYDVhz/Cr3S63kVAKffMgP +9dROOWhM4TtLuPJz5r5qBtk0wSNHa502o08gwBQOcKyzk8Or9L7XmWc92QrxhaTi ++XaFPgaJTc/zG/pZlA9aUkDSBF6YDb+wJ3PG7HRg/nI5Q8n1mdhOAnJd1nVaKCyM +TDMqCNYHOZEwOnzVPTY/JqfyhSp+sLML+Mih7ILsl6ZkLNVoSGRtFGaH15Lc722s +iON5cZV2LTglQN155wVZdVE70bbFFzvUvg7Ee4dSgk3JhsiSfqPkiJp7AgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAQEAGtog/UF1Kfs4YP+T0SBoyvKKhPiKQo9muW1b +YvdcBbWdRSa9YGNVXRCmvDDLzfzH0DoDvQzRcf8OC2wtytB5s0NF5SA+BdRQFcoi +RIUPx1AmJ6fVXpE0lLB54hjdw+ngq2WBMJN/cWzC0lb+6vckTTH7g9LKp2yn0pUh +Ycf0O8sD4LCQNJn7yN3TwmpiALStW04yg6CY3KKn2ZeoHbDxfs3Tyyf4mVX2SppX +50Fnj16Ykypd5iQSGWETZc7/cYdO2d+BX/6G7x91HuQRLV/1/jYVd00G3horuaiw +P+V84LrJh6/ZOJc660IK+CEn9vjVQ5y/zrSQMfe4cFStc79dFA== +-----END CERTIFICATE REQUEST----- diff --git a/proxmox-http/tests/certs/leaf-key.pem b/proxmox-http/tests/certs/leaf-key.pem new file mode 100644 index 00000000..f64392e2 --- /dev/null +++ b/proxmox-http/tests/certs/leaf-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCQcviUHOOS8abS +I1HAuKNMqoshgvmSOvat96mO0VXR/+z5YONJRxuCvK9WuOgBrO3gbiG7EWxYDVhz +/Cr3S63kVAKffMgP9dROOWhM4TtLuPJz5r5qBtk0wSNHa502o08gwBQOcKyzk8Or +9L7XmWc92QrxhaTi+XaFPgaJTc/zG/pZlA9aUkDSBF6YDb+wJ3PG7HRg/nI5Q8n1 +mdhOAnJd1nVaKCyMTDMqCNYHOZEwOnzVPTY/JqfyhSp+sLML+Mih7ILsl6ZkLNVo +SGRtFGaH15Lc722siON5cZV2LTglQN155wVZdVE70bbFFzvUvg7Ee4dSgk3JhsiS +fqPkiJp7AgMBAAECggEAQ9brLcx/iNSbD1ftHkDY2LnDzAJSKb4teji1VlC0KIM0 +jU5WkGSn4/evtV/z/k10DpJKnyuooZXq89X7a9cMHQ7jiHm3D9/ZTL+jX2/sRDzh +CVPWG7+JpUALzJAa7r01/WCYSsvaICCGpiy0sFboaOCVRicI8FxOsHcX5MY5oqfN +M15GIA+jCwfiLQI2JuBE1Q39ieMpRsA0zG+itSm+EOYacHDduUjtLEgSh0fXhSrF +q1rwK7kdNMGZNg9FdDtROX4FCrQvEjWOj1hl2Z/GzOMkpJu4SCrDIzXPppfkjGbF +KUBcZ67EEChd8EjJ8LYOuKJf8XrIr4HJRhsJP51syQKBgQDMB27OrWeZHnCisXtr +Ve3CSCp/BaZ/6fgPe5fufqcymIDb61bgzk1MqwT3pfP8/YiRZfokZT87ehox3gML +HfAq8STWb4mVp7s/YuAFwlXFhTlBB4Fwge7wy7CjmCKgtknxMiiO8voCCsN0GQLw +MwGjqTHF4q98iGTNn0XoJWE49QKBgQC1PmEokad/9sHLNr1Jjke8R7K9tNUfFTQP +m5S/LUfXwcXsJx6QEWnVNgDXvWIq1WnTfwbSnfu+XTrjiaTAi4ZWXusicpTd30em +3VXDIdXwYJSCU3OFWPQhbCnWDx4aVoJEb44KrGvCC/LJkweXzEminoTDMWdsGscI +Ik25NFkfrwKBgQCJRqsAdl3RAVEpth7jXkKFyMaHBoc7Y3HbAP59okve2AtDbPnc +chJCdoL2GXurie6cXa/LUzATVZlQWh9UGIWibvOpMAyzW9K52E4AsfvB1Vxra6Bk +0Zex/mrP96m81kmz9lqhq8wZGaLed4GpmbgNpOZvTZFjSeYBD5wakSP0DQKBgEC+ +VtC6Lz6L9DBWjomfFMsSRax004j19xH4PsuILljJdJ1mYAmQ3uB2GRj4IwAwGkyd +3N8R5mLbRPURL1REwylJYO9+ROV5JExcVo2NIbJrncFsdCDXZOYnkE5SOiuoaYJu +4yu26gt4XzNYnWbBaDB6NezQUiSQ8DZcoq0dIRUrAoGBAMoYm7nV/q11i+08I9J1 +vbCoatf9BVp4kDlbAE3YxQw/QNxDkUfh9VDLIDrxYxDgrK8YS0tZJgHGEmWLzpo9 +8/L5NoD1ayaRHboSBuoVsYCy/DI98cuTPWdCDa29jKXToLfgJCdc9lvk7u6vr2zX +zAt212NdiNc+OWkwy3m7EYiL +-----END PRIVATE KEY----- diff --git a/proxmox-http/tests/certs/root-cert.pem b/proxmox-http/tests/certs/root-cert.pem new file mode 100644 index 00000000..d6231c81 --- /dev/null +++ b/proxmox-http/tests/certs/root-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID6zCCAtOgAwIBAgIUcHTmdgi8DylmBhrTr4NBKpnLBk4wDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCQVQxDzANBgNVBAgMBlZpZW5uYTEPMA0GA1UEBwwGVmll +bm5hMRAwDgYDVQQKDAdQcm94bW94MRAwDgYDVQQLDAdQcm94bW94MScwJQYDVQQD +DB50bHMtY2VydC12YWxpZGF0aW9uLXJvb3QudGVzdHMwIBcNMjYwNjI0MTMzODIw +WhgPMzAyNTEwMjUxMzM4MjBaMHwxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVu +bmExDzANBgNVBAcMBlZpZW5uYTEQMA4GA1UECgwHUHJveG1veDEQMA4GA1UECwwH +UHJveG1veDEnMCUGA1UEAwwedGxzLWNlcnQtdmFsaWRhdGlvbi1yb290LnRlc3Rz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoIyemjaxlCB1jDMgBooZ +/Bhhprhbz6OxoAGFpI8T7ppzZFKoFOWIHEJcYrvqAQn0RqZe8EIUXbquIYjBZPnA +bwxrZHMgcC5J/f0pRD+N68uCUU2EPaoHqk+v/YMM+zTViftWIFpU17HfSmEkPR4l +9OljcYendp1nMRsRB3dxTPmoK/D1E87LVrz7GlsuoFDJE3CfBQ6eNUMSdNbicDF0 +xwzyzQsCGuCboHa1Q+fFU1Se3Lts1S+X7TdX2XySvFziq3wkAKaMZ46C4wM25+Z7 +Is+kBDxIxaPq13XdpneG5Iis2hT2ruWSbopuD57Zuh83/HQSSNlaaPFivib2O9kA +1QIDAQABo2MwYTAdBgNVHQ4EFgQUsI4iO4yr1STBmK9TWO0+bgf9fCswHwYDVR0j +BBgwFoAUsI4iO4yr1STBmK9TWO0+bgf9fCswDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAEM83xjtDyctuwYTCuwM7KRt +StuAmtnw/snfBJZgCKNPi0fOM1rzwM1g/h4LrH7go9VpxQ2VtT+9/20MBhlqWdAN +sI1IpUMArsmzlKaBZUZDrS3An9iRztsmnftLfkXyku6nUcb8TPDmE5r1arnDngsX ++EcINC1DgOTy4Sv9vWv6apJQtNg+/Xqs+Ax+4iIXDJde28SX7p8vTdkBKLhHnGLJ +zrEI2DzGqy8+sPTKSYGw3oNH3QUwf3FJnZKJGifmiehzdHkVKF3XesBddQjWOM/y +E4yYJqlwpDhykDEz5d6sD6F/5mw3LOqk2J2jfDbPN5IEagYEDMzAqx10Wi7U+94= +-----END CERTIFICATE----- diff --git a/proxmox-http/tests/certs/root-key.pem b/proxmox-http/tests/certs/root-key.pem new file mode 100644 index 00000000..e123e674 --- /dev/null +++ b/proxmox-http/tests/certs/root-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCgjJ6aNrGUIHWM +MyAGihn8GGGmuFvPo7GgAYWkjxPumnNkUqgU5YgcQlxiu+oBCfRGpl7wQhRduq4h +iMFk+cBvDGtkcyBwLkn9/SlEP43ry4JRTYQ9qgeqT6/9gwz7NNWJ+1YgWlTXsd9K +YSQ9HiX06WNxh6d2nWcxGxEHd3FM+agr8PUTzstWvPsaWy6gUMkTcJ8FDp41QxJ0 +1uJwMXTHDPLNCwIa4JugdrVD58VTVJ7cu2zVL5ftN1fZfJK8XOKrfCQApoxnjoLj +Azbn5nsiz6QEPEjFo+rXdd2md4bkiKzaFPau5ZJuim4Pntm6Hzf8dBJI2Vpo8WK+ +JvY72QDVAgMBAAECggEAKhhkt3uSuQt/tqhrA7vvDz3XUM7y57D8cD8l6t1G9R9T +FSFlB8GdHAe8UHkD5IzXGzUhHG6/B0pcwNcqGg8wCQ3hFJ/pB/DjHrDjwozFaedc +vnOMMlzkEKA/PUHAxBb4zGp1jRsSNtHhAZAR3+KJQjt1gv12B7BCr8nwf5wuPWen +7fswPrd0JJO1p1RjQyWeiM91LfJYOuo7rt6Froi1KVSVNUt3H9U664nZTbZM36q5 ++e9d940yijX0ckggV35zwSOMyhhK3sISEhsaPulsPvp0nlGtDzhhZjDX6ocklbi/ +hE4fytuxZsEHjrJGA7XMsQ7GpJgxC96DB1Eczf3zOwKBgQDTCsLMlBHEhr+mj7GJ +6pf03udCzIovC/0QaUVHkItFhxVnrHBhn9VzbqGITYQu6MF8kXsC7rIyt7Ad59DM +n6L5r6imKt2R+UaaezkCC1c0MWBQQ9rjfO8Yw9f5wgw4nvKE5xEf1sq/374xJKfz +6rqexTsHWBluYK6BnhJS8oqJRwKBgQDCwDjnW9xrN5exS8zFg8c101O/+MpsJeCU +sx+oIcxIh/7Gn9ho/vmgL8uNyoro2t31BDhgrgBmUBs6cnDdg+P8X0IlySKsGKXj +FY7F6IzRDJVgD4Opsn2VnpMJEJGMGcBrDJPwVb1sSKL7ELQhDWTQ5EdTsmGyijkj +OvgsYj3zAwKBgQC1LLnK8xrFwoBpN1bM9Y56c5nJaNsARKR+IEGPjHFjwPIJTKo1 +xQdzz3fxEcr2km74x9P40n48uCEDq20/HZTGEZ0Q+h+5H20TVdG9BYtZjUIH5hjV +zv1cH1UcXxAq05mTquKymKz6R9R5T+S3q72Ga/+e8Gz0qx9kuxU0DHAOJQKBgQDC +U2/0W5MbYQN6I/qV86IpsU7WNXg2Za0sc3fZGrBuh1TP+NvGGPYYwthICZyGMS5c +t/NRdQ5tCO3CakL4pgwt3RdyALsaIhYU+4PVMvCgAABlM9Xa1IG/c9Wfq+qvc1qu +9oP/wm4ayHfoMYirmmPIlKAfgdU+g/Hzl3nfP8A05QKBgFHUfa79T7h7nK/YZqgf +/gVrJRv4vQn80fFQtcebmKmiRcjg9lziOjc1Dozv23WNQZ2y5Cw3u1K8Se86HTmk +sEK9JDc6q0icWA3bvVG8PGtdrv43CR1Gsg786T5/JcHyupWeqgLHUhvxDXKUU0Bf +HCdFi6WsyhVZ6k2KOP13k+QY +-----END PRIVATE KEY----- diff --git a/proxmox-http/tests/certs/self-signed-cert.pem b/proxmox-http/tests/certs/self-signed-cert.pem new file mode 100644 index 00000000..14e76081 --- /dev/null +++ b/proxmox-http/tests/certs/self-signed-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID6zCCAtOgAwIBAgIUSkItlHQ0qbnha5abhSSWT5BGJbQwDQYJKoZIhvcNAQEL +BQAwgYMxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIDAZWaWVubmExDzANBgNVBAcMBlZp +ZW5uYTEQMA4GA1UECgwHUHJveG1veDEQMA4GA1UECwwHUHJveG1veDEuMCwGA1UE +AwwldGxzLWNlcnQtdmFsaWRhdGlvbi1zZWxmLXNpZ25lZC50ZXN0czAgFw0yNjA2 +MjQxNDI0MzZaGA8zMDI1MTAyNTE0MjQzNlowgYMxCzAJBgNVBAYTAkFUMQ8wDQYD +VQQIDAZWaWVubmExDzANBgNVBAcMBlZpZW5uYTEQMA4GA1UECgwHUHJveG1veDEQ +MA4GA1UECwwHUHJveG1veDEuMCwGA1UEAwwldGxzLWNlcnQtdmFsaWRhdGlvbi1z +ZWxmLXNpZ25lZC50ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +APlUtV9F35NimI62w+nZVU/eUZPF2YD8j4sjLwle+SmYCXC9VHlYme9OthrQSYAS +p/ioNyjk8DVGhA6FqLpnKW7iCBRU5mai17uzULMkVcX2lcdHkjxOyNSiEwS8mb7s +pbiSXUqmuFsWzfNuGR4LUtW4bgqljUkhvEOD2DSI9kshJAOp1VvepvtXTVcLwXJf +sVyF9lWh5UBjRKhyAWNZQxxdPu3FadFwAcDedbPARVPb9jO/5CJWTG3GRC/4BMiM +d8HE1uVvW/O++xu26dzY1Vj3brwJUJIZJtVcMOdC2YkOGbe3+5B1tSGqoTuJlRjH +Js5rNgasviH0Y4PXzag+6z0CAwEAAaNTMFEwHQYDVR0OBBYEFDKcDkUpWP/PKzzT +PXsLpS2krdBFMB8GA1UdIwQYMBaAFDKcDkUpWP/PKzzTPXsLpS2krdBFMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB0uNKO+MA7MJ51CO282DULb +jH9r2srM4XRoUWwFu4bYgduUa50lOMv6FMIRq1S023HKXcBxSAoCLexOSXdvb5x4 +yEbyqYrzvc5RPZvI1sDqkBbvZ1ZB3SbboHSKzOV9ddYxp4XXA60fL17syPkIGpqG +UNeX0I/vZfKgS/EPvYi65WO/bVpnEEAIz4hUs5IJk5o74s7Mz7wz5C4Dv08kJKjH +E4yv5yDjIlID6ksorOTB5WFdEeTibB+mTSbBXZp6c6KUZnNKvVDAHRRCbsp/eNtr +Nr06RcBqzgwjXhJGD0JNJfe8r/loRlhDCpvXZlOV7dYKDEOODCeeKaFCQwQXYPo= +-----END CERTIFICATE----- diff --git a/proxmox-http/tests/certs/self-signed-key.pem b/proxmox-http/tests/certs/self-signed-key.pem new file mode 100644 index 00000000..97c8060a --- /dev/null +++ b/proxmox-http/tests/certs/self-signed-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD5VLVfRd+TYpiO +tsPp2VVP3lGTxdmA/I+LIy8JXvkpmAlwvVR5WJnvTrYa0EmAEqf4qDco5PA1RoQO +hai6Zylu4ggUVOZmote7s1CzJFXF9pXHR5I8TsjUohMEvJm+7KW4kl1KprhbFs3z +bhkeC1LVuG4KpY1JIbxDg9g0iPZLISQDqdVb3qb7V01XC8FyX7FchfZVoeVAY0So +cgFjWUMcXT7txWnRcAHA3nWzwEVT2/Yzv+QiVkxtxkQv+ATIjHfBxNblb1vzvvsb +tunc2NVY9268CVCSGSbVXDDnQtmJDhm3t/uQdbUhqqE7iZUYxybOazYGrL4h9GOD +182oPus9AgMBAAECggEAVhS3W9HRa16ughM6l4mX6S+95XF48w3/dw+qJSebiY86 +ryhGunBrERKRT7easLOAN5rIFH/aKOKUJDlkNBr61JQIKxDWzRequNyjX34LeQH1 +2yvsIpMmxjbArzF4OVEVtCAgQm5GFvjMGR/pXxSUwEHhCB75JQcXKw4hfp3Mvsnf +YxBPC6EV69B3mDwmJjcP6aAb0t4is0BFrkeg8os1DKKQPpc+NJaWZ8imhUmudEi2 +ZUwVQvJFqXbhFmWLw4nXK93/192uWNw7lxodPpJTJrfZU5uEWLV/1FjKysYWgtTd +Z3Z73eiq6RdWdPxXnx9g8k/hVycH66XGGNw/VPykpwKBgQD+WsCKk0GVw3J3QU7Y +VRMG3idkIV2VcM2SIalFG0t9sb7XRN354P0J2A3EBMuqWNw7SKo3GIsFscaIdh1t +m3fLJgmccZJaWAeQsSt7tU291p6AkeNu0UQw8tK/v+GbljlUxumBZZ009BbRCv0Y +qb05am2JWvBtmyfkntorg34bNwKBgQD68aL01KOW3MbJcGVFumTwaEuUsv9OTrA8 +SvlTxrVfXU5uncJ+P3g2a/IeS2IELKBsWNIuPAGFVrECPQjS9Y4oYgnaj5ahN30w +GWQ3lYnzxdvohPUOW1cCwDg6Mz5+Mmx+ygL0Sb0YG36assElipVHB9PiRukuGHSH +6oCkwpjvKwKBgAZXeOl7lm0HfHkgtbiLFnhbXZwPgOfS8i0sja3dalpt7hYr72Tl +iSmPq3gxrmpG4ObRfvz0rbKspgiM+VrcP3ZfMmomIsIB495lrHHfKVsMWNNXz9XZ +fdvCkiKZxCQ+8Jr+gp/pSqwhUdhQb9MHmGIwFx8Pl2MENVBr7YCcPK6tAoGAZzol +LY+XJ8Tz5QNeNXvCb/6HMMkdKspFxteUjrjL/Um1rN0ql6JmQgTPmVSrIkp1R3yW +ITy/52jM8b3HtnganVQO96BfdzwLPFEFn7PdBrFaj+C5qck7Fr+ZoZ9Y0rLNXK6e +3nzC03rj7qEfwOCsHYcDyy4eV77pmMuHVb9TB/cCgYBgh9o+io9RP5N6J16Tx7bt +6wQeBkiuixcKrUKGJKkgbacSRgiNNNFn/wbaOUhcVu7aktTI/yLDg8TFBBS2cbqn +9i9iMoxx14+jCXYRnOFRcHafBTi+1S2uqxn97CxNM8RzE2wh4Q44ZeKahiEDAtZP +fPO+8PfqWIBYr3NKod7Yeg== +-----END PRIVATE KEY----- diff --git a/proxmox-http/tests/common/mod.rs b/proxmox-http/tests/common/mod.rs new file mode 100644 index 00000000..3a787f06 --- /dev/null +++ b/proxmox-http/tests/common/mod.rs @@ -0,0 +1,412 @@ +use std::os::unix::net::UnixStream; +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; +use std::sync::atomic::AtomicU32; +use std::sync::{Arc, Mutex}; + +use openssl::ssl::{Ssl, SslContextBuilder, SslMethod, SslVerifyMode}; +use openssl::x509::X509; +use openssl::x509::store::X509StoreBuilder; +use tempfile::TempPath; + +use proxmox_http::{ + PROXMOX_NEW_TLS_CHECK_VAR, SslVerifyError, get_fingerprint_from_u8, openssl_verify_callback, +}; + +/// Helper type to easily get proper certificates for a given test and their fingerprints. +#[derive(Clone, Copy)] +pub enum CertificateType { + SelfSigned, + CertificateChain, +} + +impl CertificateType { + pub fn certificate_path(&self) -> &'static str { + match self { + Self::SelfSigned => "tests/certs/self-signed-cert.pem", + Self::CertificateChain => "tests/certs/leaf-cert.pem", + } + } + + pub fn key_path(&self) -> &'static str { + match self { + Self::SelfSigned => "tests/certs/self-signed-key.pem", + Self::CertificateChain => "tests/certs/leaf-key.pem", + } + } + + pub fn certificate_chain_path(&self) -> Option<&'static str> { + match self { + Self::SelfSigned => None, + Self::CertificateChain => Some("tests/certs/cert-chain.pem"), + } + } + + pub fn leaf_certificate(&self) -> X509 { + let bytes = match self { + Self::SelfSigned => include_str!("../certs/self-signed-cert.pem").as_bytes(), + Self::CertificateChain => include_str!("../certs/leaf-cert.pem").as_bytes(), + }; + + X509::from_pem(bytes).expect("could not get certificate") + } + + pub fn intermediate_certificate(&self) -> X509 { + let bytes = match self { + Self::SelfSigned => return self.leaf_certificate(), + Self::CertificateChain => include_str!("../certs/intermediate-cert.pem").as_bytes(), + }; + + X509::from_pem(bytes).expect("could not get certificate") + } + + pub fn root_certificate(&self) -> X509 { + let bytes = match self { + Self::SelfSigned => return self.leaf_certificate(), + Self::CertificateChain => include_str!("../certs/root-cert.pem").as_bytes(), + }; + + X509::from_pem(bytes).expect("could not get certificate") + } + + pub fn leaf_fingerprint(&self) -> String { + Self::fingerprint_for_cert(&self.leaf_certificate()) + } + + pub fn intermediate_fingerprint(&self) -> String { + Self::fingerprint_for_cert(&self.intermediate_certificate()) + } + + pub fn root_fingerprint(&self) -> String { + Self::fingerprint_for_cert(&self.root_certificate()) + } + + fn fingerprint_for_cert(cert: &X509) -> String { + let digest = cert + .digest(openssl::hash::MessageDigest::sha256()) + .expect("could not create certificate digest"); + get_fingerprint_from_u8(&digest) + } +} + +/// Used to spawn a test server that listens on a Unix socket that will be cleaned up once dropped. +pub struct Server { + child: Child, + socket: TempPath, +} + +impl Drop for Server { + fn drop(&mut self) { + let _ = self.child.kill(); + } +} + +impl Server { + fn new(cert_type: CertificateType) -> Server { + static TEST_NUMBER: AtomicU32 = AtomicU32::new(0); + + let socket = TempPath::from_path(PathBuf::from(format!( + "/tmp/.op-cb-test-{}.sock", + TEST_NUMBER.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + ))); + + let mut command = Command::new("openssl"); + + command + .arg("s_server") + .arg("-unix") + .arg(&socket) + .arg("-cert") + .arg(cert_type.certificate_path()) + .arg("-key") + .arg(cert_type.key_path()); + + if let Some(cert_chain) = cert_type.certificate_chain_path() { + command + .arg("-cert_chain") + .arg(cert_chain) + .arg("-build_chain"); + } + + let child = command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn() + .unwrap(); + + Server { child, socket } + } + + pub fn connect(cert_type: CertificateType) -> (Server, UnixStream) { + let server = Server::new(cert_type); + + // wait up to one second for the server to come up + for _i in 1..10 { + match UnixStream::connect(&server.socket) { + Ok(stream) => return (server, stream), + Err(_e) => std::thread::sleep(std::time::Duration::from_millis(100)), + } + } + + panic!("server did not come up"); + } +} + +/// Carry out a TLS handshake with the default `SslContext` and the provided certificate type and +/// fingerprint. +/// +/// Returns the last result of the `openssl_verify_callback`. +pub fn handshake( + case: CertificateType, + fingerprint: Option, +) -> Option> { + handshake_with_builder( + case, + fingerprint, + SslContextBuilder::new(SslMethod::tls()).expect("could not get ssl context builder"), + ) +} + +/// Carry out a TLS handshake with the provided certificate added to the client's `SslContext` for +/// the provided certificate type and fingerprint. +/// +/// Returns the last result of the `openssl_verify_callback`. +pub fn handshake_with_cert( + cert_type: CertificateType, + fingerprint: Option, + cert: X509, +) -> Option> { + let mut store = X509StoreBuilder::new().expect("could not create x509 store builder"); + + store + .add_cert(cert) + .expect("could not add custom certificate to context"); + + let store = store.build(); + let mut builder = + SslContextBuilder::new(SslMethod::tls()).expect("could not get ssl context builder"); + + builder.set_cert_store(store); + handshake_with_builder(cert_type, fingerprint, builder) +} + +/// Carry out a TLS handshake with the provided `SslContextBuilder` used to build the +/// `SslContext` for the provided certificate type and fingerprint. +/// +/// Returns the last result of the `openssl_verify_callback`. +fn handshake_with_builder( + case: CertificateType, + fingerprint: Option, + mut builder: SslContextBuilder, +) -> Option> { + let last_result = Arc::new(Mutex::new(None)); + let mv_result = last_result.clone(); + + builder.set_verify_callback(SslVerifyMode::PEER, move |p, x| { + let res = openssl_verify_callback(p, x, fingerprint.as_deref()); + let ret = res.is_ok(); + *mv_result.lock().unwrap() = Some(res); + + ret + }); + + if super::SSL_NEW_VERIFY { + unsafe { std::env::set_var(PROXMOX_NEW_TLS_CHECK_VAR, "1") }; + } + + let (_server, stream) = Server::connect(case); + let ssl = Ssl::new(builder.build().as_ref()).unwrap(); + // ignore the error from the connect below, we only care about the last result of the + // verify callback as that's what we are testing + let _res = ssl.connect(stream); + + last_result.lock().unwrap().take() +} + +/// Tests that should behave identical between the two versions of the callback. +#[cfg(feature = "tls")] +pub mod tests { + use proxmox_http::SslVerifyError; + + use super::*; + + #[test] + fn self_signed_certificate_with_fingerprint_valid() { + let cert = CertificateType::SelfSigned; + + assert_eq!(handshake(cert, Some(cert.leaf_fingerprint())), Some(Ok(()))); + } + + #[test] + fn self_signed_certificate_without_fingerprint_invalid() { + let cert = CertificateType::SelfSigned; + + assert_eq!( + handshake(cert, None), + Some(Err(SslVerifyError::UntrustedCertificate { + fingerprint: cert.leaf_fingerprint(), + })) + ); + } + + #[test] + fn self_signed_certificate_with_incorrect_fingerprint_invalid() { + let cert = CertificateType::SelfSigned; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake(cert, Some(expected.clone())), + Some(Err(SslVerifyError::FingerprintMismatch { + fingerprint: cert.leaf_fingerprint(), + expected, + })) + ); + } + + #[test] + fn self_signed_certificate_with_cert_in_context_valid() { + let cert = CertificateType::SelfSigned; + + assert_eq!( + handshake_with_cert(cert, None, cert.leaf_certificate()), + Some(Ok(())) + ); + } + + #[test] + fn self_signed_certificate_with_cert_in_context_and_correct_fingerprint_valid() { + let cert = CertificateType::SelfSigned; + + assert_eq!( + handshake_with_cert(cert, Some(cert.leaf_fingerprint()), cert.leaf_certificate()), + Some(Ok(())) + ); + } + + #[test] + fn certificate_chain_with_correct_leaf_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake(cert, Some(cert.leaf_fingerprint()),), + Some(Ok(())) + ); + } + + #[test] + fn certificate_chain_with_correct_intermediate_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake(cert, Some(cert.intermediate_fingerprint())), + Some(Ok(())) + ) + } + + #[test] + fn certificate_chain_with_correct_root_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!(handshake(cert, Some(cert.root_fingerprint())), Some(Ok(()))) + } + + #[test] + fn certificate_chain_with_incorrect_fingerprint_invalid() { + let cert = CertificateType::CertificateChain; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake(cert, Some(expected.clone())), + Some(Err(SslVerifyError::FingerprintMismatch { + fingerprint: cert.leaf_fingerprint(), + expected, + })) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert(cert, None, cert.root_certificate()), + Some(Ok(())) + ); + } + + #[test] + /// This is considered invalid, because that is how OpenSSL would handle such a chain. If the + /// root is not trusted, it would abort the handshake right away, not caring whether a + /// certificate further down the chain would be valid. + /// + /// > The certificate chain is checked starting with the deepest nesting level (the root CA + /// > certificate) and worked upward to the peer's certificate. + /// > [..] + /// > If verify_callback returns 0, the verification process is immediately stopped with + /// > "verification failed" state. If SSL_VERIFY_PEER is set, a verification failure alert is + /// > sent to the peer and the TLS/SSL handshake is terminated. + /// > + /// > - https://docs.openssl.org/master/man3/SSL_CTX_set_verify/#notes + fn certificate_chain_with_intermediate_cert_in_context_invalid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert(cert, None, cert.intermediate_certificate()), + Some(Err(SslVerifyError::UntrustedCertificate { + fingerprint: cert.leaf_fingerprint() + })) + ); + } + + #[test] + /// See description of [`certificate_chain_with_intermediate_cert_in_context_invalid`]. + fn certificate_chain_with_leaf_cert_in_context_invalid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert(cert, None, cert.leaf_certificate()), + Some(Err(SslVerifyError::UntrustedCertificate { + fingerprint: cert.leaf_fingerprint() + })) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_and_correct_root_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert( + cert, + Some(cert.intermediate_fingerprint()), + cert.root_certificate() + ), + Some(Ok(())) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_and_correct_intermediate_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert( + cert, + Some(cert.intermediate_fingerprint()), + cert.root_certificate() + ), + Some(Ok(())) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_and_correct_leaf_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + + assert_eq!( + handshake_with_cert(cert, Some(cert.leaf_fingerprint()), cert.root_certificate()), + Some(Ok(())) + ); + } +} diff --git a/proxmox-http/tests/openssl_verify_cb_new.rs b/proxmox-http/tests/openssl_verify_cb_new.rs new file mode 100644 index 00000000..b1bf53db --- /dev/null +++ b/proxmox-http/tests/openssl_verify_cb_new.rs @@ -0,0 +1,57 @@ +//! Integration tests for the `openssl_verify_callback` that should be used for all client TLS +//! verification going forward. This differs to the legacy behavior in one way. If a fingerprint is +//! provided and no certificate matches it, the connection is aborted, regardless of OpenSSL's +//! validity checks. +//! +//! Other than that the tests currently encode the following behavior for both flows: +//! +//! * A self-signed certificate is trusted either when: +//! 1. A matching fingerprint was provided. +//! 2. No fingerprint was provided, but the OpenSSL context trusts it. +//! * A certificate chain is trusted when: +//! 1. A fingerprint is provided that matches any certificate in the chain. +//! 2. No fingerprint is provided, but the OpenSSL trust context declares the whole chain +//! valid. +//! +#[cfg(feature = "tls")] +mod common; + +// Make sure tests in the common module use the new verify callback flow. +#[cfg(feature = "tls")] +const SSL_NEW_VERIFY: bool = true; + +#[cfg(feature = "tls")] +mod openssl_verify_cb_new { + + use proxmox_http::SslVerifyError; + + use super::common::*; + + #[test] + fn self_signed_certificate_with_cert_in_context_and_incorrect_fingerprint_invalid() { + let cert = CertificateType::SelfSigned; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake_with_cert(cert, Some(expected.clone()), cert.leaf_certificate()), + Some(Err(SslVerifyError::FingerprintMismatch { + fingerprint: cert.leaf_fingerprint(), + expected, + })) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_and_incorrect_fingerprint_invalid() { + let cert = CertificateType::CertificateChain; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake_with_cert(cert, Some(expected.clone()), cert.root_certificate()), + Some(Err(SslVerifyError::FingerprintMismatch { + fingerprint: cert.leaf_fingerprint(), + expected + })) + ); + } +} diff --git a/proxmox-http/tests/openssl_verify_cb_old.rs b/proxmox-http/tests/openssl_verify_cb_old.rs new file mode 100644 index 00000000..dbeaecd6 --- /dev/null +++ b/proxmox-http/tests/openssl_verify_cb_old.rs @@ -0,0 +1,49 @@ +//! Integration tests for the `openssl_verify_callback` that check that the intended legacy +//! behavior works correctly. The legacy behavior differs from the new intended behavior in one +//! main way. If a fingerprint was provided and it does not match any certificate provided by the +//! server, but OpenSSL declares the certificate valid, the callback accepted it as well. +//! +//! Other than that the tests currently encode the following behavior for both flows: +//! +//! * A self-signed certificate is trusted either when: +//! 1. A matching fingerprint was provided. +//! 2. No fingerprint was provided, but the OpenSSL context trusts it. +//! * A certificate chain is trusted when: +//! 1. A fingerprint is provided that matches any certificate in the chain. +//! 2. No fingerprint is provided, but the OpenSSL trust context declares the whole chain +//! valid. +//! +#[cfg(feature = "tls")] +mod common; + +// Make sure tests in the common module use the old verify callback flow. +#[cfg(feature = "tls")] +const SSL_NEW_VERIFY: bool = false; + +#[cfg(feature = "tls")] +pub mod openssl_verify_cb_old { + + use super::common::*; + + #[test] + fn self_signed_certificate_with_cert_in_context_and_incorrect_fingerprint_valid() { + let cert = CertificateType::SelfSigned; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake_with_cert(cert, Some(expected), cert.leaf_certificate()), + Some(Ok(())) + ); + } + + #[test] + fn certificate_chain_with_root_cert_in_context_and_incorrect_fingerprint_valid() { + let cert = CertificateType::CertificateChain; + let expected = "ba:dc:af:fe:e7:60:38:4e:f4:aa:bd:e2:a3:93:2b:99:af:46:34:96:ed:cf:e2:af:59:15:18:fc:ea:3b:fd:c5".to_string(); + + assert_eq!( + handshake_with_cert(cert, Some(expected), cert.root_certificate()), + Some(Ok(())) + ); + } +} -- 2.47.3