Skip to content

What do SSL_get_finished/SSL_get_peer_finished return? #5509

Closed

Description

@addaleax

Hi! I’m opening an issue because there is a pull request to expose these two functions, which are public OpenSSL API (afaict), in Node.js.

There is almost no documentation on what the contents of the returned data are, or what guarantees could be made about them. They seem to be used in real-world applications, though.

Could anybody help with explaining? Even hints for where to start looking would be appreciated a lot.

Activity

  1. kroeckx commented on Mar 4, 2018

    @kroeckx
    Member

    This is what is currently in ssl.h:

    /*-
     * Obtain latest Finished message
     *   -- that we sent (SSL_get_finished)
     *   -- that we expected from peer (SSL_get_peer_finished).
     * Returns length (0 == no Finished so far), copies up to 'count' bytes.
     */
    size_t SSL_get_finished(const SSL *s, void *buf, size_t count);
    size_t SSL_get_peer_finished(const SSL *s, void *buf, size_t count);
    
  2. addaleax commented on Mar 4, 2018

    @addaleax
    ContributorAuthor

    @kroeckx Is there any information on what a “Finished” message contains and/or how a program could meaningfully use it?

  3. kroeckx commented on Mar 4, 2018

    @kroeckx
    Member

    The CHANGES files mentions:

      *) Clean up 'Finished' handling, and add functions SSL_get_finished and
         SSL_get_peer_finished to allow applications to obtain the latest
         Finished messages sent to the peer or expected from the peer,
         respectively.  (SSL_get_peer_finished is usually the Finished message
         actually received from the peer, otherwise the protocol will be aborted.)
    
         As the Finished message are message digests of the complete handshake
         (with a total of 192 bits for TLS 1.0 and more for SSL 3.0), they can
         be used for external authentication procedures when the authentication
         provided by SSL/TLS is not desired or is not enough.
         [Bodo Moeller]
    
  4. addaleax commented on Mar 4, 2018

    @addaleax
    ContributorAuthor

    Great, thank you a lot! This sounds like something we could almost directly carry over into our own documentation. (/cc @codedot)

    (I guess I could have come up with the idea of looking up why these methods were added in the first place myself… well, next time.)

  5. kroeckx commented on Mar 4, 2018

    @kroeckx
    Member

    I guess we would also like to properly document that function, so a pull request is always welcome.

  6. kroeckx commented on Mar 4, 2018

    @kroeckx
    Member

    As clear from the pull requests you pointed to, this can be used for tls-unique channel binding from rfc5929

  7. richsalz commented on Mar 4, 2018

    @richsalz
    Contributor

    There are probably betters ways to do channel-binding, although I forget which PR has it. Look for secret-exporter maybe? @addaleax you should push back on your requestor and ask what they want to do. There might be a better API for it.

  8. addaleax commented on Mar 4, 2018

    @addaleax
    ContributorAuthor

    @richsalz The requester wants this for Ripple, where apparently usage of the Finished messages is built into the protocol. :(

  9. richsalz commented on Mar 4, 2018

    @richsalz
    Contributor
  10. nbougalis commented on Jul 2, 2019

    @nbougalis

    Wow, what a mess.

    @richsalz, I know this is a somewhat ancient thread and I hate to resurrect the dead, but in the interest of improving our code and cleaning up the "mess" I am going to give it a shot.

    I am curious to know how you'd go about ensuring that two endpoints A and B can determine that they are talking directly to each other through one SSL session and not across two SSL connections that are intermediated by a proxy.

    You have several restrictions, but of particular interest are that:

    1. You don't know the certificate of the other endpoint; and
    2. You can't rely on a user "authorizing" unknown certificates (like in a web browser).

    In our case, each endpoint has a public/private keypair (the node identity). For our purposes, what we want is to know the identity of the other endpoint and to know that we are talking directly to that endpoint.

    Our solution: In the initial phases of a connection, each side independently calculates a "cookie" shared with the other side (derived by independently hashing both finished messages with SHA512, and then XORing the two values together) and signs the resulting hash with its node identity.

    The result: each endpoint knows the node identity of the other endpoint and is guaranteed that the SSL link is direct and not intermediated across two sessions through a proxy. Proxies that simply shuffle around encrypted packets aren't a concern in this scenario.

    Would your recommendation be to skip the complicated "cookie" calculation and use SSL_get_tls_unique instead? Or something else entirely?

  11. richsalz commented on Jul 2, 2019

    @richsalz
    Contributor

    Treat the TLS connection as an authenticated, DH-style connection. Then do the key exchange as your first application-level messages.

  12. nbougalis commented on Jul 9, 2019

    @nbougalis

    Thanks for the reply, @richsalz. I guess I'm not following because, in essence, that is exaclty what we do.

    Once the TLS connection is up, one of the two endpoints issues an HTTP GET with a request to Upgrade to our own protocol. The headers of that request (and the corresponding response) each include two fields: a public identity and a signature over a piece of data both endpoints have access to with the private key corresponding to the claimed identity.

    For example:

    GET / HTTP/1.1
    User-Agent: rippled-1.2.4
    Upgrade: RTXP/1.2
    Connection: Upgrade
    Public-Key: 145eaea5625cb1e76a21d93530aabc950b2655a97232e2b289f247c6f1149a22
    Session-Signature: 8f8a6bfea5a2850f7bf440f92371f8d839eb9077f35977483d8680d3cf2084da
    
    HTTP/1.1 101 Switching Protocols
    Server: rippled-1.2.4
    Upgrade: RTXP/1.2
    Connection: Upgrade
    Public-Key: 54e180ffeef882491c8dd4e51f9e40f5a3336d204ca37738b2d3f6772d8641c4
    Session-Signature: b35eb5ebe73352b00656ef5f3079c0811c2a0ba181a1ac288cbea2048f22ca3c
    

    I assume you take exception with the data that we sign which we derive from the finished messages by "reaching inside" the SSL session and pulling that data out. There's a reason for that: we need something that isn't under the sole control of either of the endpoints, since it's essential that the value being signed be random and not under the control of either party (coercing someone to sign an arbitrary value is bad).

    We could achieve this by having each side pick a random 256-bit value and then passing it to the other end; each endpoint would, then, combine the two values using XOR in a deterministic way and sign the result. But that would require the addition of at least two additional messages after the HTTP upgrade is negotiated.

    I'll grant you that that's not a huge burden. But even if we were willing to do that, there's still something else to consider: this solution would be vulnerable to an attack: an attacker could convince us both we're talking directly to each other when we are, in reality, talking through him over two independent TLS connections: it's an almost classic "triple handshake" attack.

    Using the TLS connection state effectively mitigates this scenario, strongly binds the TLS connection to its and eliminates the need for sending additional messages and complicating an already complex codebase.

    I don't mean to be difficult and I'm happy to redesign this scheme, but I'll need a little more than just "wow, what a mess."

  13. richsalz commented on Jul 9, 2019

    @richsalz
    Contributor

    Sorry if I offended you, but reaching inside the protocol to extra data is not a good idea unless you own the protocol implementation. And I did give more suggestions, as did Kurt. Here's more details: make the node identity an X25519 keypair, and do a X25519 key exchange at the application layer. Look at channel bindings.

    You have, I think, designed yourself into a box. The only way to avoid an active MitM attack is to have authentication at least on the server side, and your requirements reject the common way (X509 certs and WebPKI) of doing that.

  14. nbougalis commented on Jul 9, 2019

    @nbougalis

    I wasn't offended. I genuinely want to understand your concerns and to come up with a way to address them and I do appreciate your responses. I'm not arrogant or self-conceited enough to believe I have all the answers, and I do very much welcome feedback, especially from a subject matter expert, such as yourself.

    [...] reaching inside the protocol to extra data is not a good idea unless you own the protocol implementation

    I generally agree with you, although I guess what one does with the data matters. If I was, say, parsing the finished messages myself or somesuch, that would be bad.

    [...] make the node identity an X25519 keypair, and do a X25519 key exchange at the application layer.

    The node identity already is a secp256k1 keypair. The whole point of this scheme is to strongly "bind" the SSL session to the secp256k1 keypairs each endpoint claims to have, which, in turn ensures that given two endpoints A and B, both A and B can be sure that the TLS link is directly between each other and not via a third party C with independent TLS sessions A <-> C <-> B.

    I agree that this scheme doesn't prevent a MITM attack in the general and broadest sense, but it does mean that each endpoints knows what the node identity of the other side is. This means that C can't convince A he's talking directly to B, or vice versa. And I hope you'll agree that that is, in itself, useful.

    I can't think of a way to do this purely at the "application layer" (i.e. without reaching into the TLS session and getting some data out of it) especially given the restrictions we have (can't mandate certificates, for example).

    If there's a "standard" way for us to get the benefits of the current scheme considering the restrictions we're operating under, I'm all over it and I'll code it up today. If the answer is tls-unique (at least as outlined in RFC-5929) then I have some concerns, but maybe that's not the latest-and-greatest.

  15. richsalz commented on Jul 11, 2019

    @richsalz
    Contributor

    I can't think of a way to do this purely at the "application layer"

    A couple of thoughts:

    1. Use channel bindings (not common)
    2. Use TLS key exporting (clean way of getting the finished message but still in draft)
    3. Use SRP
    4. use a PAKE to exchange keys/id's at the application layer

    good luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions