mail-in-a-box / mailinabox

Mail-in-a-Box helps individuals take back control of their email by defining a one-click, easy-to-deploy SMTP+everything else server: a mail server in a box.
https://mailinabox.email/
Creative Commons Zero v1.0 Universal
13.9k stars 1.43k forks source link

Use elliptic SSL certs by default #814

Open ned14 opened 8 years ago

ned14 commented 8 years ago

RSA works everywhere, but at modern key sizes it is slow, and it bloats anything signed with it. Elliptic curves are the future, and a 384 bit elliptic curve SSL cert is as strong as a 7000 bit RSA one but at a tiny fraction of the bloat and computation overhead.

Any recent mobile device made in the past five years supports secp256r1 and secp384r1 SSL certs, as does any recent PC OS. Some would argue now is the time for elliptic SSL certs by default.

A list of elliptic curve compatible browsers, OSs and mobile devices can be found at https://www.namecheap.com/support/knowledgebase/article.aspx/9503/38/what-is-an-ecc-elliptic-curve-cryptography-certificate

I tried hacking in an elliptic curve SSL key and cert into mailinabox in the /home/user-data/ssl. Unfortunately your parsing code in ssl_certificates.py appears to be hardwired to expect RSA keys.

ned14 commented 8 years ago

You may find the following diagnostic of use:

Apr 30 19:26:47 mail Exception on /system/status [POST]#012multiprocessing.pool.RemoteTraceback: #012"""#012Traceback (most recent call last):#012  File "/usr/lib/python3.4/multiprocessing/pool.py", line 119, in worker#012    result = (True, func(*args, **kwds))#012  File "/usr/lib/python3.4/multiprocessing/pool.py", line 47, in starmapstar#012    return list(itertools.starmap(args[0], args[1]))#012  File "/root/mailinabox/management/status_checks.py", line 297, in run_domain_checks_on_domain#012    ssl_certificates = get_ssl_certificates(env)#012  File "/root/mailinabox/management/ssl_certificates.py", line 71, in get_ssl_certificates#012    private_key = private_keys.get(cert.public_key().public_numbers())#012TypeError: unhashable type: 'EllipticCurvePublicNumbers'#012"""#012#012The above exception was the direct cause of the following exception:#012#012Traceback (most recent call last):#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1817, in wsgi_app#012    response = self.full_dispatch_request()#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1477, in full_dispatch_request#012    rv = self.handle_user_exception(e)#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1381, in handle_user_exception#012    reraise(exc_type, exc_value, tb)#012  File "/usr/lib/python3/dist-packages/flask/_compat.py", line 33, in reraise#012    raise value#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1475, in full_dispatch_request#012    rv = self.dispatch_request()#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1461, in dispatch_request#012    return self.view_functions[rule.endpoint](**req.view_args)#012  File "/usr/local/bin/mailinabox-daemon", line 57, in newview#012    return viewfunc(*args, **kwargs)#012  File "/usr/local/bin/mailinabox-daemon", line 439, in system_status#012    run_checks(False, env, output, pool)#012  File "/root/mailinabox/management/status_checks.py", line 42, in run_checks#012    run_domain_checks(rounded_values, env, output, pool)#012  File "/root/mailinabox/management/status_checks.py", line 288, in run_domain_checks#012    ret = pool.starmap(run_domain_checks_on_domain, args, chunksize=1)#012  File "/usr/lib/python3.4/multiprocessing/pool.py", line 268, in starmap#012    return self._map_async(func, iterable, starmapstar, chunksize).get()#012  File "/usr/lib/python3.4/multiprocessing/pool.py", line 599, in get#012    raise self._value#012TypeError: unhashable type: 'EllipticCurvePublicNumbers'
Apr 30 19:26:58 mail Exception on /ssl/status [GET]#012Traceback (most recent call last):#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1817, in wsgi_app#012    response = self.full_dispatch_request()#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1477, in full_dispatch_request#012    rv = self.handle_user_exception(e)#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1381, in handle_user_exception#012    reraise(exc_type, exc_value, tb)#012  File "/usr/lib/python3/dist-packages/flask/_compat.py", line 33, in reraise#012    raise value#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1475, in full_dispatch_request#012    rv = self.dispatch_request()#012  File "/usr/lib/python3/dist-packages/flask/app.py", line 1461, in dispatch_request#012    return self.view_functions[rule.endpoint](**req.view_args)#012  File "/usr/local/bin/mailinabox-daemon", line 57, in newview#012    return viewfunc(*args, **kwargs)#012  File "/usr/local/bin/mailinabox-daemon", line 337, in ssl_get_status#012    provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False)#012  File "/root/mailinabox/management/ssl_certificates.py", line 173, in get_certificates_to_provision#012    certs = get_ssl_certificates(env)#012  File "/root/mailinabox/management/ssl_certificates.py", line 71, in get_ssl_certificates#012    private_key = private_keys.get(cert.public_key().public_numbers())#012TypeError: unhashable type: 'EllipticCurvePublicNumbers'

I can find no documentation guarantee that RSAPublicNumbers can be used in a Python dictionary, so you might want to fix that anyway. Either way, your script shouldn't die if parsing not a RSA cert, it should report a useful error to the user and keep going.

Edit: Reported as a bug to https://github.com/pyca/cryptography/issues/2887

JoshData commented 8 years ago

Show me how to create a self-signed cert so I can test?

ned14 commented 8 years ago

I got my SSL cert from my SSL provider, but openssl ecparam -out private.key -name prime256v1 -genkey will generate the private key. After that I think everything is the same as with RSA. https://www.digitalocean.com/community/tutorials/how-to-create-an-ecc-certificate-on-nginx-for-debian-8 suggests it's the same anyway.

BTW they fixed the bug reported at https://github.com/pyca/cryptography/issues/2887, so if you use the latest python cryptography package it should "just work".