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
Support RawPublicKey (non-X509) certificates, e.g. for P2P #423
Comments
A priori, one wants back certs however, which using a certificates inside the handshake provides. Story 1. We expand rustls by support for sr25519 and ecdsa secp256k1 (already support ed25519). We then ask that controller keys certify nodes TLS keys, which nodes supply upon opening connections thereby proving their intent to use that certificate. Story 2. We expand rustls by RawPublicKey. We then ask that controller keys certify nodes TLS keys on-chain like all other session keys now, although afaik our long-term transport key still never get handled this way even now. Story 3. We start like story 2 but go further by asking that nodes TLS keys certify their controller key on-chain ala paritytech/substrate#7398 Is there any difference? In story 2, Alice could register Bob's session key under her own controller key, perhaps creating strange effects, like Alice avoiding slashing while offline or whatever. Afaik, there are only negligible differences between stories 1 and 3 so long as the chain acts as a source of truth, but these are transport keys so they get used before the chain gets established, so maybe some mild shenanigans exist. At first blush, 1 sounds easier and slightly safer, although any safety might be outweighed by sending the certificate with every connections, plus the confusion of another session key that requires certification by a controller. I'm not open minded about RawPublicKey but it makes the system more fragile. |
I'm not sure how far sr25519 is to making it into any official RFC - that's the main benefit of RawPublicKey, it's already in TLS 1.3 Besides, all these other alternative mechanisms can be implemented outside of TLS - as long as you know the public key of the peer, that suffices to enable you to implement whatever you like beyond that, and you can avoid X509 totally. |
Authentication mistakes remain a concern, which TLS addresses somewhat. We're probably slightly better off specifying an authentication that works more inside TLS, so not RawPublicKey plus later messages. There is nothing wrong with custom X509 extensions. We never accept regular root certificates, so who cares? We could replace X509 with some scheme that better fits our trust root, like providing a Merkle proof from the chain's state root along with a controller signature, but then supply the certificate inside the channel exactly where an X509 certificate goes, so again not RawPublicKey plus later messages. We cannot avoid some chicken vs egg here since we do not know the chain's state roots when say syncing, but doing authentication greedily makes authentication errors more informative. |
Apologies for the Polkadot specific derail, but it's broadly relevant that peer-to-peer projects want to reinvent the wheel, and they do pay costs for doing so. |
This issue is about getting something simple that is already part of the TLS 1.3 specs, into rustls for general use, outside of the context of any specific p2p protocol.
This can be done as a TLS extension, as described by the same section of the RFC I quoted in the OP that mentions RawPublicKey. It would be more complex for rustls to implement and provide an API for this than RawPublicKey. For type-safety, the type of the extended certificate will have to be a parameter to the edit: To be clear it would be great if rustls did this too, and I think "Authentication mistakes remain a concern" is legitimate, although I'd personally think it's relatively minor and would be satisfied with just RawPublicKey.
Unrelated, but a common motivation for avoiding X509 is its over-complexity for what its common purpose is. When designing a cross language protocol, avoiding X509 in favour of something simpler, makes it easier to implement the protocol from scratch and to understand it fully. I don't know if it's theoretically possible to send a valid 1GB certificate during the handshake phase, and I don't want to have to know this, I'd rather just avoid X509. We don't need a self-driving car to crack a nut. |
@infinity0 you can use rcgen in the meantime. It makes generation of self signed certificates really easy. The simplest form just takes in a list of domain names, but you can customize it to your will: https://docs.rs/rcgen/0.8.5/rcgen/fn.generate_simple_self_signed.html |
Would someone with experience working on Rustls be able to comment on what they imagine support for this would look like? E.g. at least which bits of the code base would need to take into account the possibility of encountering a RawPublicKey? EDIT: From skimming some of the relevant source, it's not super-obvious to me at what point the distinction would be made between actual certificates and "not-actually-a-certificates" like RawPublicKey. E.g. I can see an argument for not bothering typical consumers of the API with having to think about the existence of the latter. |
I think we might want to change |
We could likely close this, but maybe certificates should be handled via some closure-like construction, but maybe |
It was brought to our attention today that the Solana community would like to see support for this. We discussed with the rustls maintainers privately to hash out how we might end up merging support for this; herewith a summary of that discussion.
As such, if someone were to take this on we'd suggest that they work on a design for the feature first, resulting in an RFC. After that is reviewed by the maintainers the author could move forward to implementation. |
I have been thinking a bit about how we could support this with the absolute minimum impact on other users. Here's some ideas, starting with three observations: First observation: a client that has a set of raw public keys (RPK) for a server will ~never accept a X509 certificate chain instead (it has pretty specific a-priori information about the server and how it will be authenticated), and the converse is also true. So, as a client, we know 100% ahead of time if RPKs are required for a particular connection. The same argument is also true of servers requiring client auth with RPKs. At protocol level, this means the Second observation: RFC7520 (and its follow-on support in RFC8446) works by modalizing the representation of certificates for a whole connection, if negotiated by the Third observation: RFC8446 incorporated RFC7520 unchanged, but I think that was a design error. The That means -- at minimum -- we need:
I think (1) and (3) are best achieved by adding something like
I think (2) and (4) similarly are best achieved by adding something like
We would also want to expose the two certificate type extensions in the I think that just about covers changes to the core of the library. In the implementation work for this, I would also expect to see:
@djc & @cpu -- how do you feel about this, as a minimal option? And, have I missed anything? |
This sounds like a good path forward, thanks for writing it up! (We can discuss the value of having an |
@ctz Thanks for sharing this.
This makes perfect sense to me, and seems indeed true in standard applications of RPK like DANE/TLSA or TOFU. I think I have an exception to this assumption though: It is possible to implement a pseudo-RPK mechanism via pinning of the X.509 SubjectPublicKeyInfo. This is the workaround Solana and libp2p are currently doing (due to lack of support for RPK in various languages). That X.509-based mechanism would obviously be obsoleted by RPK and these applications would undergo a transition period to RPK. But I understand the assumption "RPK xor X.509" might be important for sake of API cleanliness, and that rustls probably shouldn't encourage such hacks. |
Asking in the context of quinn: how do you recommend doing authentication here when you only have the peer's public key? I assume you'd configure quinn to disable all client/server authentication and then move that part to the application level, respectively having the client sign his self-signed certificate with the long-term static key and sending the signature to the server who can verify it by calling EDIT: I guess it's sufficient to just set the long-term static key in EDIT-2:
Or rather the |
Alright, working with all those different crates and converting between types is a little verbose but I came up with a proof of concept that works well. Implement Custom
|
I am looking into using rustls for a p2p network, which authenticates peers (specifically their public keys) via custom means outside of the public X509 root certs.
Currently in rustls it is possible for a client to omit server certificate verification via ClientConfig.dangerous(), however the server via ServerConfig.cert_resolver still has to provide a CertifiedKey which hard-codes a X509 certificate.
TLS 1.3 mandates support for different types of certificates, specifically including RawPublicKey. This makes p2p usage more convenient as we don't have to faff around with creating a self-signed X509 certificate. Please consider supporting this in rustls. Of course the default behaviour for a client would be to fail to verify these "certificates".
(TLS 1.2 has optional support for the same functionality, but implemented differently as an extension, RFC 7250. The use-case I mention here is only about new protocols, so I don't consider this a necessary part of this issue.)
The text was updated successfully, but these errors were encountered: