veeti / manuale

A fully manual Let's Encrypt/ACME client
MIT License
197 stars 21 forks source link

Full elliptic curve support #14

Open veeti opened 8 years ago

veeti commented 8 years ago

Allow creation of certificates and accounts (if supported?) using EC keys. Also allow the user to specify the curve.

(Pull requests welcome!)

szepeviktor commented 7 years ago

@veeti It turned out - for me at least - SSL handshake is faster on EC keys.

Is it possible that existing EC private keys are accepted by manuale? https://github.com/veeti/manuale/blob/master/manuale/crypto.py#L92-L98

szepeviktor commented 7 years ago

Yes it is:

        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    *
                ASN1 OID: prime256v1
                NIST CURVE: P-256
veeti commented 7 years ago

Yes, you can already bring your own EC key. This issue is for generating EC keys through the client.

zengxs commented 7 years ago

Maybe we can make account using EC key through this way?

import copy
import json
import requests

# PyJWT
from jwt.algorithms import get_default_algorithms
from jwt.utils import base64url_encode, to_base64url_uint, force_bytes

# cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec

# generate ec key
pkey = ec.generate_private_key(curve=ec.SECP384R1(), backend=default_backend())

# get nonce & acme directory
r = requests.get('https://acme-staging.api.letsencrypt.org/directory')
nonce = r.headers['Replay-Nonce']
urls = r.json()

def sign_request(header, protected, payload, key, algorithm='ES384'):
    """JWS Sign Request"""
    protected = base64url_encode(force_bytes(json.dumps(protected)))
    payload = base64url_encode(force_bytes(json.dumps(payload)))
    try:
        alg_obj = get_default_algorithms()[algorithm]
        key = alg_obj.prepare_key(key)
        signing_input = b'.'.join((protected, payload))
        signature = alg_obj.sign(signing_input, key)
    except KeyError:
        raise NotImplementedError('Algorithm not supported')
    return {
        'header': header,
        'protected': protected.decode('ascii'),
        'payload': payload.decode('ascii'),
        'signature': base64url_encode(signature).decode('ascii'),
    }

header = {
    "alg": "ES384",
    "jwk": {
        "kty": "EC",
        "crv": {
            'secp256r1': 'P-256',
            'secp384r1': 'P-384',
        }[pkey.public_key().curve.name],
        "x": to_base64url_uint(pkey.public_key().public_numbers().x).decode('ascii'),
        "y": to_base64url_uint(pkey.public_key().public_numbers().y).decode('ascii'),
    }
}
protected = copy.deepcopy(header)
protected['nonce'] = nonce
payload = {'resource':'new-reg', 'contact':['mailto:someone@example.com']}

r = requests.post(urls['new-reg'], json=sign_request(header, protected, payload, pkey))
print(r.json())
zengxs commented 7 years ago

Let's Encrypt only support curve P-256 and P-384, and curve P-256 must signed with ES256, P-384 must signed with ES384.