[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