[pve-devel] [PATCH v3 proxmox-websocket-tunnel 3/4] add fingerprint validation
Josef Johansson
josef at oderland.se
Wed Jan 19 13:53:55 CET 2022
On 1/19/22 13:16, Fabian Ebner wrote:
> Am 19.01.22 um 11:34 schrieb Fabian Grünbichler:
>> On January 4, 2022 12:37 pm, Fabian Ebner wrote:
>>> Am 22.12.21 um 14:52 schrieb Fabian Grünbichler:
>>>> in case we have no explicit fingerprint, we use openssl's regular
>>>> "PEER"
>>>> verification. if we have a fingerprint, we ignore openssl
>>>> altogether and
>>>> just verify the fingerprint of the presented leaf certificate.
>>>>
>>>> Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
>>>> ---
>>>>
>>>> Notes:
>>>> v3: switch to using hex instead of no-longer-existing
>>>> digest_to_hex
>>>> v2: new
>>>>
>>>> src/main.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++---
>>>> 1 file changed, 44 insertions(+), 3 deletions(-)
>>>>
>>>> diff --git a/src/main.rs b/src/main.rs
>>>> index 582214c..49d6ffe 100644
>>>> --- a/src/main.rs
>>>> +++ b/src/main.rs
>>>> @@ -134,9 +134,50 @@ impl CtrlTunnel {
>>>> }
>>>> let mut ssl_connector_builder =
>>>> SslConnector::builder(SslMethod::tls())?;
>>>> - if fingerprint.is_some() {
>>>> - // FIXME actually verify fingerprint via callback!
>>>> -
>>>> ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
>>>> + if let Some(expected) = fingerprint {
>>>> + ssl_connector_builder.set_verify_callback(
>>>> + openssl::ssl::SslVerifyMode::NONE,
>>>> + move |_valid, ctx| {
>>>> + let cert = match ctx.current_cert() {
>>>> + Some(cert) => cert,
>>>> + None => {
>>>> + eprintln!("SSL context lacks current
>>>> certificate.");
>>>> + return false;
>>>> + }
>>>> + };
>>>> +
>>>> + let depth = ctx.error_depth();
>>>> + if depth != 0 {
>>>> + return true;
>>>> + }
>>>
>>> Sorry about my ignorance. Does using SslVerifyMode::NONE imply that
>>> there is an error? At depth 0? Why is it fine to return true if not?
>>
>> this is a bit.. tricky (did I mention I really really dislike openssl's
>> API? ;))
>>
>> basically what we do in this branch (if we have a pinned fingerprint to
>> check - the regular 'connect iff trusted by system' is the else branch
>> below) we set our own callback that gets called for each cert along the
>> chain (starting at the top, ending with the leaf/end certificate, but
>> the order is not relevant since a single failed callback fails the whole
>> verification).
>>
>> for each cert (== element of the chain == depth value) we get the result
>> of openssl's check (`_valid`) and the X509 store context (ctx).
>>
>> the context (among other things ;)) contains information about where
>> (depth) in the chain we currently are:
>> - depth 0 == peer certificate (the one we are interested in)
>> - depth 1 == CA certificate (signer of peer cert, not interesting)
>> - depth 2 == higher CA certificate (signer of CA at 1, not interesting)
>> - depth X == higher CA certificate (signer of CA at X-1, not
>> interesting)
>>
>> all but the peer certificate are optional (peer could give us just a
>> self-signed certificate, or an incomplete chain).
>>
>> that the methods here are all referring to 'error' is an OpenSSL
>> peculiarity - it basically gives us a cert store with the current cert
>> and error depth set to values that are valid if we fail (error) the
>> verification.
>>
>> for each cert/call we do the following:
>>
>> - ensure there is a current cert in the context or fail verification
>> - continue verification with next element of the chain if we are not
>> (yet) at the peer certificate (depth != 0)
>> - calculate fingerprint for current (== peer) cert, or fail
>> - compare fingerprint with pinned/expected one, fail if not expected
>>
>> since the verification fails as soon as single callback fails, we need
>> to
>> - return false if we fail some assumption (like ctx having a current
>> cert, or being able to calculate a cert's FP)
>> - return true if the current call is at a depth we are not interested in
>> verifying
>> - return true/false depending on result of FP check if current call
>> is at
>> a depth we are interested in
>>
>> I'll add a comment to the depth part that it is for skipping the CA
>> certs! also verify mode should technically be PEER, so I'll fix that up
>> as well.
>>
>
> Thanks for the thorough explanation! I think I get it now (especially
> knowing that the callback can be called multiple times helps a lot).
>
>>>
>>>> +
>>>> + let fp = match
>>>> cert.digest(openssl::hash::MessageDigest::sha256()) {
>>>> + Ok(fp) => fp,
>>>> + Err(err) => {
>>>> + // should not happen
>>>> + eprintln!("failed to calculate
>>>> certificate FP - {}", err);
>>>> + return false;
>>>> + }
>>>> + };
>>>> + let fp_string = hex::encode(&fp);
>>>> + let fp_string = fp_string
>>>> + .as_bytes()
>>>> + .chunks(2)
>>>> + .map(|v| std::str::from_utf8(v).unwrap())
>>>> + .collect::<Vec<&str>>()
>>>> + .join(":");
>>>> +
>>>> + let expected = expected.to_lowercase();
>>>> + if expected == fp_string {
>>>> + true
>>>> + } else {
>>>> + eprintln!("certificate fingerprint does
>>>> not match expected fingerprint!");
>>>> + eprintln!("expected: {}", expected);
>>>> + eprintln!("encountered: {}", fp_string);
>>>> + false
>>>> + }
>>>> + },
>>>> + );
>>>> } else {
>>>>
>>>> ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::PEER);
>>>> }
>>>
>
>
> _______________________________________________
> pve-devel mailing list
> pve-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Hi,
Just as a datapoint, I tested the step client
(https://github.com/smallstep/cli), and saw that it had a pretty sweet
system for verifying remote certificates.
{
"version": 3,
"serial_number": "347801840814762006190637676907327486230196",
"signature_algorithm": {
"name": "SHA256-RSA",
"oid": "1.2.840.113549.1.1.11"
},
"issuer": {
"common_name": [
"R3"
],
"country": [
"US"
],
"organization": [
"Let's Encrypt"
]
},
"issuer_dn": "C=US, O=Let's Encrypt, CN=R3",
"validity": {
"start": "2022-01-15T22:05:15Z",
"end": "2022-04-15T22:05:14Z",
"length": 7775999
},
"subject": {
"common_name": [
"www.proxmox.com"
]
},
"subject_dn": "CN=www.proxmox.com",
"subject_key_info": {
"key_algorithm": {
"name": "RSA"
},
"rsa_public_key": {
"exponent": 65537,
"modulus": "52/d0AcsH5pbdbTETwqKzKnNNaZqGt35Qs3ciLg3h+wkwqEZhxiSp7tmD4zyyywP84dDvUB+WxDngTsoGnkxhBNibFN+htDrhRiDIwXVqW42MRHOxnfGfyILcjmEPRcw0ej2e9LfR45Kd9o7QWL7L5ano8LgkG5rxoBHeEfnjyRyQR8q28z1EyKvR0/jE5fGRtEdtUIJn3Bu7+SsUBqQXcbKVDDcQFyEbICYxBTofXi2fxf1DVGT3ZGy9X4ja0WtPU8o6pjyfzUFW45mgY4W6UwhAWUtbDC0np+K7BiZjZcQTyuszHdcN0BY9skO9kH679DLzqD275+xkU89sb2xMQ==",
"length": 2048
},
"fingerprint_sha256": "e832ec8ba68141627a1d3b58fde78150553084b14623689cd0be655aafdc5a5d"
},
"extensions": {
"key_usage": {
"digital_signature": true,
"key_encipherment": true,
"value": 5
},
"basic_constraints": {
"is_ca": false
},
"subject_alt_name": {
"dns_names": [
"proxmox.com",
"www.proxmox.com"
]
},
"authority_key_id": "142eb317b75856cbae500940e61faf9d8b14c2c6",
"subject_key_id": "078f859fc1821a3f838c5b86ce319b8a70e962fc",
"extended_key_usage": {
"client_auth": true,
"server_auth": true
},
"certificate_policies": [
{
"id": "2.23.140.1.2.1"
},
{
"id": "1.3.6.1.4.1.44947.1.1.1",
"cps": [
"http://cps.letsencrypt.org"
]
}
],
"authority_info_access": {
"ocsp_urls": [
"http://r3.o.lencr.org"
],
"issuer_urls": [
"http://r3.i.lencr.org/"
]
},
"signed_certificate_timestamps": [
{
"version": 0,
"log_id": "QcjKsd8iRkoQxqE6CUKHXk4xixsD6+tLx2jwkGKWBvY=",
"timestamp": 1642287915,
"signature": "BAMASDBGAiEA6oCYHTwiAUo6xBAknN5K+IedikfwIETRFFjzVaX/d1ICIQC5QGnOlcA9J1kNNDUjwW8MyZp/kj8U3E7Vm1f8Dr6B4A=="
},
{
"version": 0,
"log_id": "KXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4Q=",
"timestamp": 1642287915,
"signature": "BAMASDBGAiEAtZrDq7wbgxUHzp2VKVDvuIYSJRVP72naI4oS3nS0rMYCIQDHCGH4TPVygVPE73P8jFzX++7ZhamkZbK2IFZgIVBdcg=="
}
]
},
"signature": {
"signature_algorithm": {
"name": "SHA256-RSA",
"oid": "1.2.840.113549.1.1.11"
},
"value": "iTb8dmJ1JvjG200/hmJ6mPmD3w7YYUR0XlmK8hu7YJTbPYxQtgiqew3ulrfx5z98fe7gbPmnR7fCtdSPqwz4HKbiGLS6kmqEiQn6eDkhbi2pIXLZTJ6qPpKkJJAUyT1b3rgIRF49PhVJZ0sbHTHeyBH41Lbkvs+mDWHBubhsCI6coN1f0+ZmZgctaK2cE9jKn8t1vRmMFW8MeM7SSX951EcRxO4LRrx/yNE9v8reMsLAeUbuQ6fboEh60+ZIK/6+PFupGYsINtp/76rn1AOcDCGeP1enzP7/T8qbFChNN5CnewSYEdzJYkn07y8H1nt+ioj60TsIHdW8JBd0tEajgg==",
"valid": false,
"self_signed": false
},
"fingerprint_md5": "9102932ce81f268eedddc6b9ff59e6fe",
"fingerprint_sha1": "19e4425b6321236f4dfd5ee26a592438fec4bcbd",
"fingerprint_sha256": "3b21f35ed0b6bbb5e7cd1c176038c077c86bd723c10587746ffd10dc9f87ba9b",
"tbs_noct_fingerprint": "0024bc21edbd4539da91a0a029fd7f3ed7f327336fd90dc150ff2c1f92e45d4a",
"spki_subject_fingerprint": "8b9da0a8a08a165b399932a91add980e63d2a59299fd1517ad8e70cbb360637a",
"tbs_fingerprint": "be6bf119f73c655983772411fc88e0142926ab3d5bd7adc14b017fc9c7d2f1a6",
"validation_level": "DV",
"names": [
"www.proxmox.com",
"proxmox.com"
],
"redacted": false
}
More information about the pve-devel
mailing list