Skip to content

Commit 47c77dc

Browse files
authored
Cryptographic refactoring; adds ECDSA keys; removes pyopenssl as dependency (#212)
* Crypto refactoring, with the addition of ECDSA keys, including cli options. Incompatible changes in Client.__init__ interface and some internals! * Crypto fixes, clarifications, lots of documentation updates, and no, LE doesn't P-521 :-( * Final small adjustments to match what LE staging doesn't accept.
1 parent 3957224 commit 47c77dc

25 files changed

+866
-381
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
- run:
2929
name: run tests
3030
command: |
31+
mkdir test
32+
make testdata
3133
find . -type f -name *.pyc -delete | echo
3234
coverage erase
3335
coverage run --omit="*tests*,*.virtualenvs/*,*.venv/*,*__init__*,*/usr/local/lib/python2.7/dist-packages*" -m unittest discover && bash <(curl -s https://codecov.io/bash)

Makefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,27 @@ uploadprod_tag:
6060
# you can run single testcase as;
6161
# 1. python -m unittest sewer.tests.test_Client.TestClient.test_something
6262
# 2. python -m unittest discover -k test_find_dns_zone_id
63+
64+
.PHONY: test testdata rsatestkeys secptestkeys
65+
6366
test:
6467
@printf "\n removing pyc files::\n" && find . -type f -name *.pyc -delete | echo
6568
@printf "\n coverage erase::\n" && ${coverage} erase
6669
@printf "\n coverage run::\n" && ${coverage} run --omit="*tests*,*.virtualenvs/*,*.venv/*,*__init__*" -m unittest discover
6770
@printf "\n coverage report::\n" && ${coverage} report --show-missing --fail-under=85
68-
@printf "\n run black::\n" && ${black} --line-length=100 --py36 .
71+
@printf "\n run black::\n" && ${black} --line-length=100 --target-version py35 .
6972
@printf "\n run pylint::\n" && ${pylint} --enable=E --disable=W,R,C --unsafe-load-any-extension=y sewer/
73+
74+
75+
testdata: rsatestkeys secptestkeys
76+
77+
rsatestkeys:
78+
openssl genpkey -out test/rsa2048.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
79+
openssl genpkey -out test/rsa3072.pem -algorithm RSA -pkeyopt rsa_keygen_bits:3072
80+
openssl genpkey -out test/rsa4096.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096
81+
82+
secptestkeys:
83+
openssl genpkey -out test/secp256r1.pem -algorithm EC -pkeyopt ec_paramgen_curve:P-256
84+
openssl genpkey -out test/secp384r1.pem -algorithm EC -pkeyopt ec_paramgen_curve:P-384
85+
# not actually useful with LE at this time
86+
# openssl genpkey -out test/secp521r1.pem -algorithm EC -pkeyopt ec_paramgen_curve:P-521

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
## Sewer
22

3+
**Cryptography rework branch.**
4+
5+
The story so far:
6+
- the mixed openssl/cryptography libraries using code has all been ripped
7+
out of client.py
8+
- AcmeKey and AcmeCsr, in crypto.py, replace and extend (ECDSA keys) old code
9+
- Client NO LONGER GENERATES KEYS (account or certificate)
10+
- Client argument changes:
11+
+ acct_key:AcmeKey in place of account_key: str
12+
+ cert_key:AcmeKey in plaxe of certificate_key: str
13+
+ bits: int removed
14+
+ is_new_account:bool [False] if key requires registration
15+
- CLI options have changed a bit, mostly to add new features (ECDSA keys!)
16+
+ acct_key/cert_key added, preferred over account_key/certificate_key
17+
+ acct_key_type/cert_key_type allow selection of RSA or EC key generation
18+
+ is_new_account added to support registering your own account key
19+
320
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/ccf655afb3974e9698025cbb65949aa2)](https://www.codacy.com/app/komuW/sewer?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=komuW/sewer&amp;utm_campaign=Badge_Grade)
421
[![CircleCI](https://circleci.com/gh/komuw/sewer.svg?style=svg)](https://circleci.com/gh/komuw/sewer)
522
[![codecov](https://codecov.io/gh/komuW/sewer/branch/master/graph/badge.svg)](https://codecov.io/gh/komuW/sewer)
@@ -8,7 +25,7 @@
825
Sewer is a Let's Encrypt(ACME) client.
926
It's name is derived from Kenyan hip hop artiste, Kitu Sewer.
1027

11-
- This is the trunk, moving towards a 0.8.4 release. No notes yet.
28+
- This is crypto work, intended for the 0.8.4 release. No notes yet.
1229
- The stable release is [0.8.3](https://komuw.github.io/sewer/notes/0.8.3-notes).
1330
- More history in the [CHANGELOG](https://komuw.github.io/sewer/CHANGELOG).
1431

docs/ACME.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## ACME, RFCs, and confusion, oh my!
1+
# ACME, RFCs, and confusion, oh my!
22

33
ACME grew out of early, ad-hoc procedures designed to let CAs issue large
44
numbers of certificates with low overhead. As described in RFC855, these
@@ -40,9 +40,9 @@ make out, often-shifting schedule for various partial transitions, but I'm
4040
not going to try to make sense of them. As of the beginning of 2020, the
4141
only immediate effect on sewer was that one could no longer run it against
4242
the *staging* server. The next big change is when that same restriction is
43-
rolled out on LE's *production* server late in the year. Since sewer
43+
rolled out on LE's *production* server later in the year. Since sewer
4444
v0.8.2, which implemented the final RFC8555 protocol at least well enough to
4545
work with LE's server implementation, our tl;dr is just this:
4646

4747
> If you get a failure running an older version of sewer, get v0.8.2 or
48-
> later. This is a known problem: v0.8.2 is the fix.
48+
later. This is a known problem: v0.8.2 or later is the fix.

docs/CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
## `sewer` changelog:
22
most recent version is listed first.
33

4+
## **version:** 0.8.4 [unreleased]
5+
6+
- add support for ECDSA keys
7+
8+
CLI changes:
9+
10+
- `--account_key` & `--certificate_key` supported only for compatibility:
11+
use `--acct_key` and `--cert_key` to avoid future deprecation.
12+
13+
- add `--acct_key_type` & `--cert_key_type` to allow choice of RSA or EC
14+
keys and sizes.
15+
16+
- changed default for generated keys to 3072 bit RSA (had been 2048 bit)
17+
18+
- add `--is_new_key` to allow for first-time registration of your own
19+
account key (using `--acct_key`) generated outside of sewer.
20+
21+
Internal changes:
22+
23+
- Client methods cert() and renew() are deprecated; just call
24+
get_certificate() directly instead.
25+
26+
- crytographic refactoring
27+
28+
- AcmeKey & AcmeCsr in crypto.py; uses only cryptography library
29+
30+
- dropped `account_key` and `certificate_key` optional arguments
31+
32+
- added `acct_key` and `cert_key` REQUIRED arguments taking AcmeKey objects
33+
34+
- dropped `bits` argument because Client no longer generates keys!
35+
436
## **version:** 0.8.3
537

638
Features and Improvements:

docs/DNS-Propagation.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Waiting for Mr. DNS or Someone Like Him
1+
# Waiting for Mr. DNS or Someone Like Him
22

33
Q: How long does it take after you've setup the challenge response TXT records
44
until they're actually accessible to the ACME server?
@@ -8,14 +8,17 @@ A: Good Question!
88
According to [Let's Encrypt](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
99
it can take up to an hour. Depends on the DNS service. Some provide a way
1010
to check that your changes are fully propagated to all their servers. With
11-
many, however, you just have to wait.
11+
many, however, you just have to wait. But be sure you wait long enough,
12+
because Let's Encrypt DOES NOT implement automatic or triggered retry of a
13+
failed authorization - you have to restart the [same] order or else start
14+
all over again.
1215

1316
Sewer provides a flexible _delay until actually published_ mechanism through
1417
three optional driver parameters, `prop_delay`, `prop_timeout`,
1518
`prop_sleep_times`, and the [`unpropagated` method](unpropagated).
1619
Let's see how they're used in various circumstances.
1720

18-
### No API support, no reliable way to check: just delay
21+
## No API support, no reliable way to check: just delay
1922

2023
If you can't check that the TXT records are fully published, then all you
2124
can do is delay for a while. Perhaps the DNS service will suggest a safe
@@ -27,9 +30,9 @@ and sewer's engine will add that many seconds of delay after the challenge
2730
setup returns before it signals the ACME server to validate those
2831
challenges.
2932

30-
**CLI option --p_opt prop_delay=... is available for all providers as of 0.8.3**
33+
**CLI option --p_opt prop_delay=... is available for all drivers since 0.8.3**
3134

32-
### API support or can check: use a timeout
35+
## API support or can check: use a timeout
3336

3437
If the DNS service gives you a way to check that the propagation is
3538
complete, or if there are not too many authoritative servers (viz., not an
@@ -43,9 +46,9 @@ before it starts checking. And there's a delay between checks that has a
4346
hopefully sensible default, but which you can adjust if necessary through
4447
the `prop_sleep_times` parameter.
4548

46-
**legacy providers do not implement `unpropagated` as of 0.8.3**
49+
**no drivers implement `unpropagated` as of 0.8.3**
4750

48-
### You probably don't need to change `prop_sleep_times`
51+
## You probably don't need to change `prop_sleep_times`
4952

5053
Unless you do, but if it's not obvious, just leave it.
5154

@@ -77,7 +80,7 @@ ready. So the timeout isn't a hard maximum time, but it's bounded to be no
7780
more than one sleep interval (plus actual time to run `unpropagated`, of
7881
course) over `prop_timeout`.
7982

80-
### Other Notes and Advanced Use
83+
## Other Notes and Advanced Use
8184

8285
These values are setup through the Provider on the reasonable assumption
8386
that they will vary most directly with the choice of service provider, so
@@ -95,10 +98,10 @@ on the Provider instance. This is solidly in the categories of don't do it
9598
unless you're sure you need to, and be prepared to own both the pieces if
9699
you break it!
97100

98-
### Could this be used with non-DNS Providers?
101+
## Could this be used with non-DNS drivers?
99102

100103
Yes! I have no experience with http-01 in any setting where such a delay
101104
might be needed, but the mechanism is implemented in sewer's engine, and all
102105
that needs be done is to setup the parameters (and implement unpropagated in
103106
the driver if using more than just `prop_delay`) as described above and
104-
there you are!
107+
there you go!

docs/UnifiedProvider.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
## DNS and HTTP challenges unified
1+
# DNS and HTTP challenges unified
22

33
_There's still a draft when the wind is blowing, but it's getting less._
44

5-
### Dedication
5+
## Dedication
66

77
It is indisputable that this is, in the first instance, Alec Troemel's fault,
88
since he added support for http-01 challenges.
@@ -11,15 +11,15 @@ made in the process of unifying the two types of challenges,
1111
while influenced by Alec's code and our discussions, are entirely my fault.
1212
Alec cannot be blamed for my choices!
1313

14-
### A few words about words
14+
## A few words about words
1515

1616
Because the word "provider" is so overloaded, I'm going to refer to the
1717
service-specific implementations as "drivers", except when I forget, or
1818
missed changing an old use. "Provider" is still used in the class names. And
1919
then there are the "service providers", viz., DNS services or web hosts,
2020
etc.
2121

22-
### Overview (tl;dr)
22+
## Overview (tl;dr)
2323

2424
`ProviderBase` described here defines the interface the ACME engine uses
2525
with new-model drivers (all http-01 drivers, as there are no old ones). New
@@ -30,7 +30,7 @@ drivers normally should inherit from the `DNSProviderBase` or
3030
individual drivers need to be created (or modified) to support it at this
3131
time. _unbound_ssh is a quirky but working example that supports aliasing.`
3232

33-
### ProviderBase interface for ACME engine
33+
## ProviderBase interface for ACME engine
3434

3535
The interface between the ACME protocol code and any driver implementation
3636
consists of three methods, `setup`, `unpropagated` and `clear`. The first
@@ -75,11 +75,11 @@ This is the pattern which all three methods use: accept a list of challenges
7575
subset which have problems or are not ready. So in all cases an empty list
7676
returned means that all went well.
7777

78-
### `ProviderBase`
78+
## `ProviderBase`
7979

8080
Abstract base class for driver implementations ultimately inherit from.
8181

82-
#### ProviderBase __init__
82+
### ProviderBase __init__
8383

8484
__init__(self,
8585
*,
@@ -127,7 +127,7 @@ It is allowed to add, change, or even remove items from kwargs if necessary
127127

128128
--- (see where for args documentation? DNS-Alias and DNS-Propagation & ???)
129129

130-
#### `setup(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
130+
### `setup(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
131131

132132
The `setup` method is called to publish one or more challenges. Each item
133133
in the list describes one challenge.
@@ -171,7 +171,7 @@ defined values:
171171
| "skipped" | setup | may skip challenges after one has a hard failure |
172172
| "unready" | unpropagated | soft fail: record not deployed to authoritative server(s). If a non-recoverable error is detected, then use _failed_. |
173173

174-
#### `unpropagated(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
174+
### `unpropagated(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
175175

176176
This method is expected to be needed mostly for DNS challenges, but it
177177
should be used whenever a service provider has a relatively slow or
@@ -180,7 +180,7 @@ challenge data being visible to the world. When there's no expectation of
180180
such lag, or no way to reliably check that the challenge has propagated,
181181
this may as well just return an empty list, and we'll all hope for the best.
182182

183-
#### `clear(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
183+
### `clear(self, challenges: Sequence[Dict[str, str]]) -> Sequence[Tuple[str, str, Dict[str, str]]]`
184184

185185
`clear`, unlike `setup`, SHOULD NOT stop processing challenges after hitting
186186
an error. It's possible that any reported errors will be treated as
@@ -189,7 +189,7 @@ challenges).
189189

190190
_? should have a status word for "this one's hard failed, forget about it"?_
191191

192-
### `DNSProviderBase`
192+
## `DNSProviderBase`
193193

194194
The driver *interface* is the same for everything except legacy DNS drivers,
195195
but there are some differences which it makes no sense to push into
@@ -209,7 +209,7 @@ handling both the aliasing and non-aliasing case. `cname_domain` forms the
209209
DNS name for the CNAME that should exist in the aliasing case and returns it
210210
for the use of a hypothetical sanity check, or None when not aliasing.
211211

212-
### `HTTPProviderBase`
212+
## `HTTPProviderBase`
213213

214214
This intermediate base class stands ready to handle any HTTP-specific
215215
options or helper methods. No additions are expected until sewer has had

docs/catalog.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Driver Catalog
1+
# The Catalog of Drivers
22

33
The driver catalog, `sewer/catalog.json`, replaces scattered facilities that
44
were used to stitch things together. The import farms in `sewer.__init__`
@@ -15,7 +15,7 @@ facilities - it's all lists & dicts (see eg. setup.py which loads the
1515
catalog this way to avoid potential issues with trying to call into the
1616
package's code before it's installed).
1717

18-
### Catalog structure
18+
## Catalog structure
1919

2020
The catalog resides in a JSON file that loads as an array of dictionaries,
2121
one element for each registered driver. The per-driver record contains the
@@ -40,7 +40,7 @@ following items (some optional):
4040
- **memo** Additional text/comments about the driver, the descriptor, etc.
4141
- **deps** list of additional projects this driver requires (for setup)
4242

43-
### args - parameter desciptors
43+
## args - parameter desciptors
4444

4545
This is a bit of a mess due to legacy drivers that ignored the established
4646
conventions. To be fair to them, those conventions weren't clearly
@@ -102,7 +102,7 @@ so both the parameter and envvar name must be given explicitly. There is
102102
also an optional parameter that has never had an associated envvar that the
103103
implementation used.
104104

105-
### driver parameter and environment variable names
105+
## driver parameter and environment variable names
106106

107107
The convention is that the envvar name (if any) SHOULD be formed from the
108108
driver name and the individual args' names (see the first envvar rule
@@ -117,12 +117,12 @@ Obviously the drivers and envvar names are not so consistent among the
117117
legacy DNS drivers. Therefore the descriptor has both `param` and `envvar`
118118
values, along with a set of rules for resolving the names to be used.
119119

120-
#### parameter name rules
120+
### parameter name rules
121121

122122
1. `descriptor.args[n].name` is the "modern" name for the nth parameter
123123
2. if `param` is given, it overrides the "modern" name
124124

125-
#### environment name rules
125+
### environment name rules
126126

127127
1. f"{descriptor.name}_{descriptor.args[n].name}".upper() is the default
128128
2. if `envvar` is given, it overrides the default
@@ -136,7 +136,7 @@ Two guidelines for the use of envvars:
136136
2. If `envvar` is set to the empty string, then catalog using code will not
137137
look for a matching envvar at all.
138138

139-
### catalog representation in Python
139+
## catalog representation in Python
140140

141141
For now, see the brief implementation in sewer/catalog.py for the way the
142142
JSON structure is mapped into a ProviderDescriptor instance.

0 commit comments

Comments
 (0)