Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What do SSL_get_finished/SSL_get_peer_finished return? #5509

Closed
addaleax opened this issue Mar 4, 2018 · 15 comments
Closed

What do SSL_get_finished/SSL_get_peer_finished return? #5509

addaleax opened this issue Mar 4, 2018 · 15 comments

Comments

@addaleax
Copy link
Contributor

addaleax commented Mar 4, 2018

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.

@kroeckx
Copy link
Member

kroeckx commented Mar 4, 2018

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);

@addaleax
Copy link
Contributor Author

addaleax commented Mar 4, 2018

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

@kroeckx
Copy link
Member

kroeckx commented Mar 4, 2018

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]

@addaleax
Copy link
Contributor Author

addaleax commented Mar 4, 2018

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.)

@addaleax addaleax closed this as completed Mar 4, 2018
@kroeckx
Copy link
Member

kroeckx commented Mar 4, 2018

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

@kroeckx
Copy link
Member

kroeckx commented Mar 4, 2018

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

@richsalz
Copy link
Contributor

richsalz commented Mar 4, 2018

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.

@addaleax
Copy link
Contributor Author

addaleax commented Mar 4, 2018

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

@richsalz
Copy link
Contributor

richsalz commented Mar 4, 2018 via email

@nbougalis
Copy link

nbougalis commented Jul 2, 2019

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?

@richsalz
Copy link
Contributor

richsalz commented Jul 2, 2019

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

@nbougalis
Copy link

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."

@richsalz
Copy link
Contributor

richsalz commented Jul 9, 2019

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.

@nbougalis
Copy link

nbougalis commented Jul 9, 2019

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.

@richsalz
Copy link
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.

nbougalis added a commit to Xahau/xahaud that referenced this issue Sep 25, 2023
Even with TLS encrypted connections, it is possible for a determined
attacker to mount certain types of relatively easy man-in-the-middle
attacks which, if successful, could allow an attacker to tamper with
messages exchanged between endpoints.

The risk can be mitigated if each side has a certificate issued by a
CA that the other side trusts. In the context of a decentralized and
permissionless network, this is neither reasonable nor desirable.

To prevent this problem all we need is to allow the two endpoints, A
and B, to be able to independently verify that they are connected to
each other over a single end-to-end TLS session, instead of separate
TLS sessions which the attacker bridges.

The protocol level handshake implements this security check by using
digital signatures: each endpoint derives a fingerprint from the TLS
session, which it signs with the private key associated with its own
node identity. This strongly binds the TLS session to the identities
of the two endpoints of the session.

This commit introduces a new fingerprint derivation that uses modern
and standardized TLS exporter functionality, instead of the existing
derivation whch uses OpenSSL APIs that are non-standard, and derives
different "incoming" and "outgoing" security cookies.

Lastly, this commit refines the "self-connection" check to allow for
the detection of accidental instances of node identity sharing. This
check was first introduced with #4195 but was partially reverted due
to a bug with #4438. By using distinct security cookies for incoming
and outgoing connections, an attacker is no longer able to claim the
identity of its peer by echoing its security cookie.

The change is backwards compatible and servers with this commit will
still generate and verify old-style fingerprints, in addition to the
new style fingerprints.

For a fuller discussion on this topic, please see:
    openssl/openssl#5509
    XRPLF/rippled#2413

This commit was previously introduced as #3929, which was closed. If
merged, it also fixes #2413 (which had been closed as a 'WONTFIX').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants