When using a self-signed certificate that is not known to the system, CVerifyPeer will raise an exception rejecting it. CVerifyNone does not raise an exception but functions that can be used to inspect the infringing identity, getPskIdentity() and getPeerCertificates(), return nothing.
Is it possible to work around this behavior and access the self-signed certificate after the handshake and manually check it with, for example, openssl functions? I tried unsuccessfully with both net and asyncnet.
The use case is:
Here is a minimal server example: https://gist.github.com/benob/f236f997b80c2664a757b79c988c0cd1
To connect as a client, use "openssl s_client -connect host:1965 -cert cert.pem -key key.pem -state -debug" with a self-signed certificate. Change verifyMode to see the effect of the two options.
Maybe there is something I just don't understand about Nim's implementation of SSL...
I had a similar problem while working on Geminim. Nim SSL implementation is mostly just a wrapper on libssl using FFI capabilities. The advantage of this is that if there's a function in libssl not in Nim openssl you can just import it with FFI.
Now, the thing is that it doesnt work, because the normal verification process will fail the handshake when it gets a self-signed certificate, raising the error.
What I did to get over this is import https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_cert_verify_callback.html and override the verification function with my own. According to the documentation, if the callback returns 1, it will continue the handshake, so if you set it to always return 1 the program will continue, allowing you to do your own verifications afterwards. I saw another gemini server use this practice.
I recently pushed the experimental code for client certificates support for geminim in a separate branch, you can check https://github.com/ardek66/geminim/blob/experimental/src/tls.nim and https://github.com/ardek66/geminim/blob/experimental/src/geminim.nim#L200 where i mostly handle these stuff, but it shouldnt really be considered done yet.
Thanks, I'll explore those options.
Since I am also working on a gemini implementation, we could join efforts :D
I managed to get a self-signed certificate checking client to work.
As per openssl documentation [1], you need to call SSL_CTX_set_verify(SSL_VERIFY_NONE) so that a failed verification does not kill the connection. Then you can call SSL_get_verify_result() to retrive the verification result. Values such as X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT or X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN indicate a self-signed certificate.
Here is a POC that does not use net/asyncnet: https://gist.github.com/benob/72d3a2fdefca3a586aa3dd6400892080
[1] https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html
Ah yea, totally forgot about SSL_VERIFY_NONE, that saves a procedure declaration. I lived with the impression that the certificate is never retrieved with SSL_VERIFY_NONE, due to the wording.
Also the fact that net and asycnet don't export sslHandle in the Socket and AsyncSocket respectively is really a bummer, I personally dont really want to rewrite the socket wrapper just for a few functions. And the openssl itself exports some functions that use it, so its kinda counter-productive. Some would argue about abstracting the lower-lever stuff, but it just makes no sense to me why would you rewrite stdlib(net) procedures to use another stdlib(openssl) functions
After fiddling quite a bit, I have succeeded in handling self-signed certificates in https://github.com/benob/gemini.
There were multiple tricks:
proc verify_callback*(preverify: int, x509_ctx: PX509_STORE_CTX): int {.cdecl.} =
let err = X509_STORE_CTX_get_error(x509_ctx)
#echo "err: " & $err
if err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT or err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
return 1
return preverify
I think that asyncnet would benefit from a getter for sslHandle to talk directly with libssl, and from a way to make sure the handshake is completed. In particular, getPeerCertificates(sslhandle) in net might have a bug in that regard. I'll create issues for that.
@benob My PR got merged,
should be available now as a getter.