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
FEAT(server): Certificate Handling improvements #5119
base: master
Are you sure you want to change the base?
Conversation
While validating a clients certifiacte, the last certifiacte in the clients chain was used. The last certifiacte is the certifiacte of the CA. Since in this place, the clients own certifiacte should be validated, the first certifiacte in the chain needs to be used instead. Fixes mumble-voip#3523 (partially)
…ing the trust store While there are serveral options to configure, which ssl certificates a server should use, the way that they were processed was a bit hacky and messy. The original process, how the certificates were used is the following: 1. Read all certs from sslCA; these were Meta::mp.qlCA 2. If sslKey set and valid, use it as private key 3. If sslKey NOT set, try to get a private key from sslCert 4. If we have no key by now, fail, else we have found our key Meta::mp.qskKey 5. Read ALL certs from sslCert and ALL certs from sslKey to a cert list 6. Use the private key to find a matching cert in the cert list 7. If there is NO match, fail 8. If theres a match, remove it from the list and consider it our cert Meta::mp.qscCert 9. Treat all leftover certs in the list as intermediates; these were Meta::mp.qlIntermediates Now, when a client connection was made, the server sets the certificate itself presents to the client to qscCert. Secondly, it configured its own ssl context to trust all certificates from Meta::mp.qlCA and Meta::mp.qlIntermediates. But according to the wiki sslCA should be used to denote the servers intermediates, not certificates it want to trust. The reason why these certificates were added to the truststore is that, if they were added to the truststore Qt (or whatever is beneath Qt in ssl) will caltulate the servers chain of trust and present them to the client. So the server actually abused the truststore to do the chain calculation. This has the undesired side effect that the server will always trust all certificates in its own chain of trust itself (which for most usecases seem desirable, but not for all). This commit changes the chain calculation: Steps 2 to 9 from above are not altered, but instead of storing the leftover certificates from step 9 into Meta::mp.qlIntermediates, they are stored in a temperary list, called pool. And instead of doing step 1, the certificates form sslCA are feed directly to pool. Then, qscCert and pool are used to calculate the chain (using openssl), just the chain is stored and everything else is discarded. Calculating the chain once in advance and then only use it is actually better then building it every time it is needed. Since the certificates from sslCA and sslCert are no longer added to the trust store, separating the things the server presents to the client, and certificates the server wants to trust is possible now. Fixes mumble-voip#3523 (partially)
If a client makes a connection to a server and presents a ssl certificate, ssl errors can arise, e.g. because of an expired client certificate. Up till now, many of these errors were just ignored. This commit makes it possible to configure which ssl errors a server should allow to happen and on which a client should be force-disconnected. The allowed ssl errors are parsed from the servers .ini and transformed to QSslError:SslError by comparison using a map. This matching is case insenitive. The maps that are used for matching the names to errors and vice versa are given in Meta::getSslNameErrorMap() and Meta::getSslErrorNameMap(). The new ini-option is named "allowedClientSslErrors" and used as described below: allowedClientSslErrors (default: AllowNoPeerCertificate, AllowSelfSignedCertificate, AllowSelfSignedCertificateInChain, AllowUnableToGetLocalIssuerCertificate, AllowUnableToVerifyFirstCertificate, AllowHostNameMismatch, AllowCertificateNotYetValid, AllowCertificateExpired) If a client presents a certificate to the server, the certificate is validated according to Murmurs Client Truststore (MCTS), which by default contains murmurs own chain-of-trust as well as the hostmachines CAs. Errors might arrise during validation of the clients certificate, but some are not critical and thus ignored. A server admin can configure, what errors should ignored using a comma separated list of the following options: AllowUnableToGetIssuerCertificate, AllowUnableToDecryptCertificateSignature, AllowUnableToDecodeIssuerPublicKey, AllowCertificateSignatureFailed, AllowCertificateNotYetValid, AllowCertificateExpired, AllowInvalidNotBeforeField, AllowInvalidNotAfterField, AllowSelfSignedCertificate, AllowSelfSignedCertificateInChain, AllowUnableToGetLocalIssuerCertificate, AllowUnableToVerifyFirstCertificate, AllowCertificateRevoked, AllowInvalidCaCertificate, AllowPathLengthExceeded, AllowCertificateUntrusted, AllowCertificateRejected, AllowSubjectIssuerMismatch, AllowAuthorityIssuerSerialNumberMismatch, AllowNoPeerCertificate, AllowHostNameMismatch, AllowUnspecifiedError, AllowNoSslSupport, AllowCertificateBlacklisted. If no errors should be allowed, an empty list can be provided, or the value "None". An exception to the above rule are "Invalid Purpose" ssl errors, which are not configurable and always allowed. The default values where choosen this way to match the current behaviour. Impelments mumble-voip#3523 (partially)
Currently, if a client makes a connection to a server and provides a ssl certificate, this certificate is validated and errors are treated according to the "allowedClientSslErrors" ini-option. This commit makes it configurable, which client certificates should be trusted in this validation process. The general idea is to introduce the Murmur Client Truststore (MCTS), which contains all the certificate authorities to trust. The following ini-options were added: mctsAddOwnCertificateAsCA (default: true) Adds murmurs own certificate (without the chain) as CA to the MCTS. mctsAddOwnCAs (default: true) Adds the chain of murmurs own certificate (but not the its own certificate) as CAs to the MCTS. mctsHostsCAs (default: true) If this is set to true, the CAs of the hostmachine are added to the MCTS. mctsExtraCAs (default: empty) Path to a pem file containing one to multiple certificates to add to the MCTS. The default values where choosen this way to match the current behaviour. Impelments mumble-voip#3523 (partially)
It might be desirable to force a user to name him/herself according to the certificate provided to the server. This behaviour is complementary to a client who only trusts a server whichs certificates common name machtes it's hostname. To achive this, a new ini-option is introduced: forceUsernameCertSubjectEquality (default: false) If this is set to true, a user can only enter the server, if her username equals the common-name-subject-field of the provided certificate. If client certificates are not required on a server (set using "certrequired"), this property is ignored. The check, wheter the name machtes the certificate is done right after an authenticate message is received. For failed checks, a new reject type is introduced: UsernameCertMissmatch. Implements mumble-voip#4940 Closes mumble-voip#4940
Qt 5.13 introduces new QtSslErrors. If the server was build using Qt 5.13 or newer, these errors should also be targetable by "allowedClientSslErrors".
This is an exceptionally well described PR. Great job on that! It provides great detail and context which will allow for an easier review and discussion. It also allowed me (and anyone curious now or in the future) to go through and learn about it without deep diving into implementation or code, and I could raise any concerns if I had any on a conceptual level. I just wanted to point this out. Thank you for that. I would love to see more of these kind of PRs. :) FYI I fixed a bunch of typos in the text, so it should be (even) more easily readable than before. |
@Kissaki Thanks! I really appriciate your feedback😄 @Krzmbrzl made already clear that:
Originally posted by @Krzmbrzl in #4963 (comment) what I can totally understand. The thing I recently notices is that the first two commits in this PR are not features but fixes, so maybe they could be considered for 1.4.x (hasn't to be 1.4.0 right away)? |
Yes the first two commits can probably be backported to the 1.4.x branch once this PR is merged 👍 |
If they make sense by themselves you can create a separate PR for them. |
@davidebeatrici if you have the time, it'd be great if you could review this PR (as well) since for me it will probably still take quite some more time until I have the necessary time to dive into this topic... |
Take your time! We are currently using this with an own static build, so we are not in a hurry (althought it would be cool to be able to use an official build, for security reasons). Speaking of that static build, I encountered some problems with the PRs code there and hadn't had the time to create something pushable for this PR yet. It will (unfortunately 😢) take at least two more weeks until I can start working on mumble server again to provide these fixes and rebase everything on upstream, so again, there is no need to hurry right now (except of course you guys have the capacity to do this fixes and rebases on your own 😄). |
So it has been longer than two weeks, sorry about that... but now I finally got time to start working on this again. I have several questions to the procedure now:
So, as you can see I'm a bit lost here and I'm looking forward to your advice😅. |
Updating to the most recent code is probably a good idea (even though no conflicts have surfaced yet). You can simply rebase your branch against the current master and then force-push to GitHub in order to update this PR. As for fixing any typos or actual bugs, please fix these in the respective commits and then again force-push to update this PR. Therefore: Eventually all changes that are changes to you changes, should not end up in a new commit at the time this is merged but instead be incorporated into the commit that introduces the initial set of changes. |
So working through the comments and integrating them takes more time and effort then planned. I also realized that while this PR adds a lot of usefull things, it might be easier to split it in multiple PRs. This way, the features can also be tested more independendly and we can identify possible bugs easier. If we agree on this procedere I will open a new PR for the first commit in this PR, and if that is merged for the next one and so on. I would also let this PR open to keep track of the whole process. |
Sounds good to me 👍 |
Apply documentation related changes from review of commit f211492 Co-authored-by: Davide Beatrici <[email protected]>
Apply for-loop related changes from review of commit f211492 Co-authored-by: Davide Beatrici <[email protected]>
Apply vector related changes from review of commit f211492 Co-authored-by: Davide Beatrici <[email protected]>
Apply documentation related changes from review of commit 2fd4c1b Co-authored-by: Davide Beatrici <[email protected]>
This is a reopening of #4963 with a commit targeting #4960 removed, since it was rejected after discussion.
This PR addresses SSL Error Configuration and Client Certificate Trusting (both #3523), as well as Username Client Certificate CN Equality (#4940).
It is structured the following way:
What is this and why do we need it?
In current murmur, clients present certificates to murmur to proof their identity. This is in theory a very strong security approach, but murmur is limited when it comes to processing these client certificates. While validating a clients certificate, multiple ssl exceptions can occur, but murmur ignores many of them (see #3523). In some usecases, this makes total sense, but in others it is necessary to act on them. So this PR makes them configurable.
After being able to configure which errors should be ignored, a logical next step is to make it configurable whom to trust. In the current implementation of ssl handling, this is actually possible, but only to a certain extend and only by abusing some configuration options (more on this in the technical part). To clean this up, this PR introduces the Murmur Client Truststore (MCTS), which can be freely configured and is then used when deciding whether to trust a clients certificate or not.
Finally, when a user provided a trusted certificate accepted by murmur, it may be desirable to force that user to use the name the certificate was issued to (as requested in #4940).
User perspective
To use this improvements, new options for
murmur.ini
are created. The default values of these options are set in a way, that if a server admin just ignores them, murmurs behaviour does not change compared to how it acts currently. Without further ado, these are the options (the below explanations should be added to to wiki if this PR is accepted):How we achieve this technically
Most of this is also discussed in depth in #3523, here is a short summary, starting with murmur gets its own certificate from the
murmur.ini
:sslCA
; these areMeta::mp.qlCA
sslKey
set and valid, use it as private keysslKey
NOT set, try to get a private key fromsslCert
Meta::mp.qskKey
sslCert
and ALL certs fromsslKey
to acert list
cert list
Meta::mp.qscCert
Meta::mp.qlIntermediates
Now, when a client connection is made, murmur sets the certificate itself presents to the client to
qscCert
. Secondly, it configures its own ssl context to trust all certificates fromMeta::mp.qlCA
andMeta::mp.qlIntermediates
. This is the abusive way I mentioned above to get murmur to trust certain client certificates: According to the wiki we should usesslCA
to denote our intermediates, not certificates we want to trust. If influencing the current client certificates murmur trusts by abusing thesslCA
is possible, why do we need this PR and code changes and can not just update the wiki, saying that the servers own certificate and intermediates should go intosslCert
, and the certificates we want to trust intosslCA
? Well, firstly we would still tell our ssl context to trustMeta::mp.qlIntermediates
. This would still force murmur to trust all certificates that relate to the servers own certificate chain. While this seems reasonable there are usecases where one dont want to trust certificates in its own chain of trust. Secondly, a ssl context is initialized with the hostmachines CAs and maybe we don't want them either.The key to getting this right is asking: Why do we currently add
Meta::mp.qlCA
andMeta::mp.qlIntermediates
to our truststore? You know, according to the wiki, we don't want to trust them, we only want to construct our own certificate chain using the intermediates and CAs from these lists. The answer is simple: If we add them to our trust store, Qt (or whatever is beneath Qt in ssl) will calculate this chain for us. So murmur actually abuses the truststore itself.Now, with this PR we do steps 2 to 9 from above as we currently do them, but instead of storing the leftover certificates from step 9 into
Meta::mp.qlIntermediates
, we store them in a temporary list, calledpool
. And instead of doing step 1, the certificates formsslCA
are feed directly topool
. Then, we use theqscCert
andpool
to calculate the chain (using openssl), store just the chain and discard everything else. Calculating the chain once in advance and then only use it is actually better then building it every time we need it (see here).For the client certificates we want to trust we use the MCTS, so it is clearly separated: Things we want to trust start with
mcts
, things we want to present to clients start withssl
.This was the interesting part. The allowed ssl errors are simply parsed from the
murmur.ini
and transformed toQSslError:SslError
by comparision using a map. As suggested by @Krzmbrzl, this matching is actually case insensitive. You can see the names used to match the errors and vice versa inMeta::getSslNameErrorMap()
andMeta::getSslErrorNameMap()
.usernameMustMatchCertCommonName
simply rejects users when a authenticate message is received and username and certificate subject common name don't match. A new reject type was introduced for this, it might be necessary to implement this type in the mumble client.Additionally, this PR also fixes a bug in
Server.cpp
wherecerts.last();
is used instead ofcerts.first();
. Again, see #3523 for details.Testing
With this many internal changes, testing is very important. I also want to mention that I've very little C++ experience, and in particular never worked with Qt before. We have done some functionality testing ourselves, and while we could not find a bug it doesn't mean that there are no more ;)
I really hope that this changes get implemented in the next version, but take your time reviewing :)
I'm glad for every question and suggestion!