veeti / manuale

A fully manual Let's Encrypt/ACME client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Full elliptic curve support

veeti opened this issue · comments

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

(Pull requests welcome!)

@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

Yes it is:

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

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

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())

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.