Compare commits

...

1917 commits

Author SHA1 Message Date
Jeff Hodges
9ad7eedbfe revert the nginx port change
This was fixed in the boulder codebase with letsencrypt/boulder#482.

This is a partial reversion of #618.
2015-07-22 16:57:07 -07:00
James Kasten
8e63bbead4 Merge pull request #623 from PeterMosmans/typo
Fixed typo
2015-07-20 11:40:47 -07:00
Jacob Hoffman-Andrews
a724504b2e Merge pull request #621 from kuba/boulder-amqp
Travis: run Boulder in AMQP mode (fixes #620).
2015-07-20 10:23:36 -07:00
Peter Mosmans
7908ea0b86 Fixed typo
Changed config-changes in the short help (wrong) to config_changes (right)
2015-07-20 10:17:58 +02:00
James Kasten
4b8651274f Merge pull request #607 from kuba/dvsni-verify
Verify DVSNI
2015-07-19 02:30:51 -07:00
Jakub Warmuz
fe3c3be675
Travis: run Boulder in AMQP mode (fixes #620). 2015-07-18 19:31:56 +00:00
Jakub Warmuz
7a79915f0c
Common plugin: export key to PKCS8, not OpenSSL. 2015-07-18 13:00:05 +00:00
Jakub Warmuz
61e19c9882
DVSNIResponse.gen_cert, fix verify_cert, add tests. 2015-07-18 12:54:33 +00:00
Jakub Warmuz
f3538cd114
Add comment about _DEFAULT_DVSNI_SSL_METHOD. 2015-07-18 07:33:46 +00:00
Jakub Warmuz
33d7f205fa
Merge remote-tracking branch 'github/letsencrypt/master' into dvsni-verify
Conflicts:
	acme/acme/challenges.py
2015-07-18 06:51:26 +00:00
James Kasten
4543f7956c Merge pull request #619 from kuba/acme-resource
Fix "reg vs new-reg" encoding problem.
2015-07-17 12:20:32 -07:00
James Kasten
50729e6ff3 Merge pull request #615 from kuba/gitignore
Gitignore /.tox/
2015-07-17 12:09:13 -07:00
James Kasten
9252b1b269 Merge pull request #616 from kuba/pkgs_sep_prep
Remove subpackages symlinks.
2015-07-17 12:08:56 -07:00
James Kasten
2b573ce24a Merge pull request #618 from kuba/boulder-debug-server-8080
Change integration nginx port to 8081, move tests around.
2015-07-17 12:08:29 -07:00
Jakub Warmuz
fcc470d0a2
Fix "reg vs new-reg" encoding problem. 2015-07-17 14:59:32 +00:00
Jakub Warmuz
d618a66c2e
Remove old protocol ChallengeResponse code. 2015-07-17 14:59:32 +00:00
Jakub Warmuz
dc9fbfa4bb
Merge branch 'boulder-debug-server-8080' into gitignore 2015-07-17 08:45:39 +00:00
Jakub Warmuz
b09f750a85
Merge branch 'boulder-debug-server-8080' into pkgs_sep_prep 2015-07-17 08:44:57 +00:00
Jakub Warmuz
0d63c94b8e
Move nginx-boulder integration tests to subpkg dir. 2015-07-17 08:41:27 +00:00
Jakub Warmuz
5a15af5abe
Change integration nginx port to 8081.
Ref https://github.com/letsencrypt/boulder/issues/482.
2015-07-17 08:30:34 +00:00
Jakub Warmuz
d5a2638f22
Remove subpackages symlinks.
This is a follow-up for merged
https://github.com/letsencrypt/boulder/pull/459.
2015-07-17 08:12:00 +00:00
Jakub Warmuz
96f12fe668
Gitignore /.tox/ 2015-07-17 08:10:49 +00:00
James Kasten
3d8ecabf09 Merge pull request #613 from kuba/argparse-py26
Require argparse only in Python 2.6.
2015-07-15 12:11:20 -07:00
James Kasten
56ac7e8830 Merge pull request #612 from kuba/nginx-mock-external
Mock external resource in nginx plugin tests.
2015-07-15 12:11:00 -07:00
Jakub Warmuz
d7eccd5a1a
Merge branch 'nginx-mock-external' into argparse-py26 2015-07-15 16:20:17 +00:00
Jakub Warmuz
9522d847d7
Mock external resource in nginx plugin tests.
External DNS entry has been updated causing test failures, e.g.
https://travis-ci.org/kuba/letsencrypt/builds/71101879.
2015-07-15 15:57:51 +00:00
Jakub Warmuz
b943fab35a
Require argparse only in Python 2.6.
Fixes packaging issues.
2015-07-15 15:38:30 +00:00
Jakub Warmuz
d7d98d79ce
please pylint 2015-07-13 20:24:16 +00:00
Jakub Warmuz
bfe6adf215
py3 compat 2015-07-13 20:19:32 +00:00
Jakub Warmuz
deacfc8a74
Merge remote-tracking branch 'github/letsencrypt/master' into dvsni-verify 2015-07-13 19:27:13 +00:00
Jakub Warmuz
c546dddd7f
Add DVSNIResponse.verify_cert. 2015-07-13 19:26:26 +00:00
Jakub Warmuz
9f31976928
Add acme.crypto_util._serve_sni and ServeProbeSNITest. 2015-07-13 19:26:25 +00:00
James Kasten
01481aabd9 Merge pull request #602 from quinox/preserve_comments
Preserve comments, don't silently truncate config files
2015-07-13 11:47:29 -07:00
James Kasten
e8a00a1018 Merge pull request #605 from kuba/py3
Python 3 support in acme.
2015-07-13 11:39:17 -07:00
Ceesjan Luiten
88c824c0ec End nginx configuration files with a newline 2015-07-13 20:02:34 +02:00
Ceesjan Luiten
1f552cab74 Parse nginx comments 2015-07-13 20:02:34 +02:00
Ceesjan Luiten
86c8fb1410 Do not silently truncate nginx config files 2015-07-13 19:44:02 +02:00
Jakub Warmuz
c2a8195f19
Move _pyopenssl_cert_or_req_san to acme. 2015-07-13 11:02:27 +00:00
James Kasten
2b49443e86 Merge pull request #604 from kuba/rtfd
RTFD: install subpkgs.
2015-07-12 21:43:22 -07:00
Jakub Warmuz
ccc6a3212b
Simple DVSNI verification 2015-07-12 19:11:55 +00:00
Jakub Warmuz
7f46e69454
Update ignore files to remove shared tox.venv 2015-07-12 15:30:51 +00:00
Jakub Warmuz
f37b919210
Travis: run boulder-start.sh only if BOULDER_INTEGRATION is set. 2015-07-12 15:25:43 +00:00
Jakub Warmuz
160a96052b
tox/Travis: test Python 3.3 2015-07-12 15:20:52 +00:00
Jakub Warmuz
5859e87ced
b64encode: no support for bytearray (py2.6 problems) 2015-07-12 15:20:25 +00:00
Jakub Warmuz
596132292a
Travis: test Python 3.4. 2015-07-12 15:07:25 +00:00
Jakub Warmuz
7bc1cd4454
Remove shared tox venv 2015-07-12 15:04:01 +00:00
Jakub Warmuz
d361937b67
RTFD: install subpkgs. 2015-07-12 13:39:40 +00:00
Jakub Warmuz
12720a2252
Fix CSR loading in ACME example client script. 2015-07-12 11:53:18 +00:00
Jakub Warmuz
5d6d901655
Add py3 tox tests for acme.jose 2015-07-12 11:53:18 +00:00
Jakub Warmuz
c0ba26776a
Support for py3.3+ in acme 2015-07-12 11:53:18 +00:00
Jakub Warmuz
a876a664df
Add py3 tox tests for acme.jose 2015-07-12 11:37:56 +00:00
Jakub Warmuz
802b9d4a43
Support for py3.3+ in acme.jose. 2015-07-12 11:37:56 +00:00
James Kasten
2f2137ef6b Merge pull request #601 from kuba/pkgs_sep_prep
Update installation instructions for pkgs subdirs
2015-07-10 23:16:45 -07:00
Jakub Warmuz
10e993331c
Update installation instructions for pkgs subdirs (include trailing slash). 2015-07-11 06:09:52 +00:00
James Kasten
33224e7d77 Merge pull request #600 from kuba/subdirs-rename
Subdirs rename
2015-07-10 22:28:54 -07:00
Jakub Warmuz
04c12a5e38
Update package references after subdirs rename for Vagrant and Docker. 2015-07-11 05:15:10 +00:00
Jakub Warmuz
c85fa02495
Leave letsencrypt_ symlinks for Boulder integration testing. 2015-07-11 05:08:43 +00:00
Jakub Warmuz
a462e38cab
Update package references after subdirs rename. 2015-07-11 04:33:07 +00:00
Jakub Warmuz
120a94f84f
mv letsencrypt_* letsencrypt-*. 2015-07-11 04:31:18 +00:00
James Kasten
b7d3710579 Merge pull request #598 from kuba/pkgs_sep_prep
Separate packages
2015-07-10 15:10:51 -07:00
Jakub Warmuz
0b4a85c145
Once again fix travis/tox venv path. 2015-07-10 17:29:28 +00:00
Jakub Warmuz
ff8925d92f
Update sys.path in docs/conf.py for Sphinx to find subdir packages. 2015-07-10 17:22:52 +00:00
Jakub Warmuz
2ae6ac2bfd
Travis: tox-venv activation path adjustment 2015-07-10 17:13:25 +00:00
Jakub Warmuz
db1e078c06
Shared tox envdir 2015-07-10 16:40:46 +00:00
Jakub Warmuz
2e9cf9a5d5
Include requirements.txt in sdist 2015-07-10 16:39:05 +00:00
Jakub Warmuz
b9df69af9f
Basic dev/test setup for separate package subdirectories. 2015-07-10 16:38:42 +00:00
Jakub Warmuz
2f9cd68807
Move acme and plugins to respective subdirectories.
for x in acme letsencrypt_nginx letsencrypt_apache; do git mv $x _$x; mkdir $x; git mv _$x $x/$x; done
2015-07-10 16:18:15 +00:00
Jakub Warmuz
b0c72410ba
Unified vector loading in letsencrypt. 2015-07-10 15:49:18 +00:00
Jakub Warmuz
0e474436c4
Unified vector loading in acme. 2015-07-10 15:16:58 +00:00
Jakub Warmuz
19c73249ca
Sort vectors: acme/testdata, separate acme and letsencrypt. 2015-07-10 15:16:48 +00:00
James Kasten
cb3863b5fd Merge pull request #593 from kuba/account-resource-json
Rewrite accounts and registration
2015-07-10 00:37:17 -07:00
Jakub Warmuz
c57cd239c3
Register key creation before writing out. 2015-07-10 07:25:29 +00:00
Jakub Warmuz
4ebc20402b
Pin mock<1.1.0 (Python 2.6 support). 2015-07-10 07:21:07 +00:00
Jakub Warmuz
1d35946b4e
Fix PickPluginTest.test_no_defaults. 2015-07-10 06:51:09 +00:00
Jakub Warmuz
15f443dced
assert_called_once -> assertEqual(1, *.call_count) 2015-07-10 06:42:02 +00:00
Jakub Warmuz
56d8c60df6
Fix letsencrypt.tests.client_test. 2015-07-10 06:24:06 +00:00
Jakub Warmuz
0c46f80fdd
assert_callend_once -> assert_called_once 2015-07-10 05:44:21 +00:00
Jakub Warmuz
30a02d4487
Accounts: raise AccountStorageError on id mismatch (instead of assertion).
This allows find_all() to skip broken account, instead of failing with
AssertionError.
2015-07-10 05:34:33 +00:00
Jakub Warmuz
f4d5ce1986
Include Account.id in the Account.slug. 2015-07-10 05:27:01 +00:00
Jakub Warmuz
f24479ebfc
100% coverage for letsencrypt.account. 2015-07-10 05:17:24 +00:00
Jakub Warmuz
581875bde3
Account.id: use key md5 hexdigest. 2015-07-10 05:14:50 +00:00
James Kasten
b66c60731f Merge pull request #596 from kuba/acme-resource
Fix new-regr -> new-reg typo
2015-07-09 12:11:05 -07:00
Jakub Warmuz
517c9bd736
Fix new-regr -> new-reg typo 2015-07-09 19:04:41 +00:00
James Kasten
55b897537d Merge pull request #594 from kuba/acme-resource
Enforce "resource" field in request objects.
2015-07-09 11:42:13 -07:00
Jakub Warmuz
35c21d4cf4
Enforce "resource" field in request objects.
Corresponds to:
- https://github.com/letsencrypt/boulder/pull/442
- https://github.com/letsencrypt/acme-spec/pull/156
2015-07-09 13:37:25 +00:00
Jakub Warmuz
5e450e879c
Save account private_key.json as 0o400. 2015-07-09 11:33:02 +00:00
Jakub Warmuz
7aa749174b
Fix achall response key chmods security bug. 2015-07-09 11:26:27 +00:00
Jakub Warmuz
0d24f52f6e
Expose le_util.safe_open. 2015-07-09 11:14:25 +00:00
Jakub Warmuz
3e2d1c8abc
get_cert_file -> get_cert_path. 2015-07-09 10:51:21 +00:00
Jakub Warmuz
1bc9e7cb64
Registration: drop singular email/phone 2015-07-09 06:53:06 +00:00
Jakub Warmuz
7dc64e0387
Rewrite acccounts and registration.
Save accounts to:

    /etc/letsencrypt/accounts/www.letsencrypt-dmeo.org/acme/new-reg/ \
    kuba.le.wtf@2015-07-04T14:04:10Z/ \
    {regr.json,meta.json,private_key.json}

Account now represents a combination of private key, Registration
Resource and client account metadata. `Account.id` based on the
account metadata (creation host and datetime). UI interface
(`cli._determine_account`) based on the `id`, and not on email as
previously.

Add `AccountStorage` interface and `AccountFileStorage`,
`AccountMemoryStorage` implementations (latter, in-memory, useful for
testing).

Create Account only after Registration Resource is received
(`register()` returns `Account`).

Allow `client.Client(..., acme=acme, ...)`: API client might reuse
acme.client.Client as returned by `register()`.

Move report_new_account to letsencrypt.account, client.Client.register
into client.register.

Use Registration.from_data acme API.

achallenges.AChallenge.key is now the `acme.jose.JWK`, not
`le_util.Key`. Plugins have to export PEM/DER as necessary
(c.f. `letsencrypt.plugins.common.Dvsni.get_key_path`)

Add --agree-tos, save --agree-eula to "args.eula". Prompt for EULA as
soon as client is launched, add prompt for TOS.

Remove unnecessary letsencrypt.network. Remove, now irrelevant,
`IConfig.account_keys_dir`.

Based on the draft from
https://github.com/letsencrypt/letsencrypt/pull/362#issuecomment-97946817.
2015-07-09 06:43:45 +00:00
James Kasten
d850be2d73 Merge pull request #592 from kuba/acme-reg
acme: registration improvements
2015-07-08 13:53:20 -07:00
Jakub Warmuz
0d087788da
Accept new_reg in acme.client.Client.register. 2015-07-08 19:23:12 +00:00
Jakub Warmuz
7470bc8db6
RegistrationResource: return any phone/email from phones/emails or None. 2015-07-08 19:23:06 +00:00
James Kasten
97b09ea1c6 Merge pull request #587 from kuba/docs
Update docs
2015-07-08 12:06:47 -07:00
James Kasten
bb831206b5 Merge pull request #591 from kuba/cryptography
Drop M2Crypto and PyCrypto.
2015-07-08 12:04:45 -07:00
Jakub Warmuz
36eafde213
Use ComparableRSAKey autowrap throughout the code base. 2015-07-08 12:07:05 +00:00
Jakub Warmuz
a7817de4ab
Rewrite JWK.load, JWKRSA autowraps ComparableRSAKey. 2015-07-08 12:00:16 +00:00
Jakub Warmuz
0955012569
Move asn1_generalizedtime_to_dt todo comment to docstring. 2015-07-08 08:41:13 +00:00
Jakub Warmuz
90b27ff9cf
ComparableX509Test for cert and CSR 2015-07-07 17:00:08 +00:00
Jakub Warmuz
9ab40444b6
More Python data model fixes for acme. 2015-07-07 08:15:33 +00:00
Jakub Warmuz
20a08b50f2
ComparableX509 and ComparableX509Req: __eq__, __ne__, __hash__ data model fixes. 2015-07-07 08:05:41 +00:00
Jakub Warmuz
9a9f91b4ee
Fix typo 2015-07-07 07:21:48 +00:00
Jakub Warmuz
e05b10974c
test/acme_util.py: fix nonce lengths 2015-07-07 07:20:48 +00:00
James Kasten
302b50db5e Merge pull request #586 from kuba/acme-client
acme.client bug fixes and refactor
2015-07-06 16:42:10 -07:00
James Kasten
d9d620180b Merge pull request #585 from kuba/xdg-user-dirs
Example dev config file, config file docs, $XDG_CONFIG_HOME.
2015-07-06 13:53:25 -07:00
Jakub Warmuz
e276f2aa6b
crypto imports cleanup 2015-07-06 12:18:31 +00:00
Jakub Warmuz
02e7154c0d
Drop M2Crypto 2015-07-06 12:18:27 +00:00
Jakub Warmuz
61aa29d28c
Drop PyCrypto. 2015-07-06 12:18:22 +00:00
Jakub Warmuz
9197fa6b5c
acme: M2Crypto -> pyOpenSSL 2015-07-06 12:18:17 +00:00
Jakub Warmuz
2c6ef0feef
Update hacking docs (venv/bin/activate, ./tox-cover.sh, integration, ipdb). 2015-07-06 09:19:00 +00:00
Jakub Warmuz
25f1e45d94
Remove acme.util docs 2015-07-06 07:55:29 +00:00
Jakub Warmuz
e0293d81f3
acme: drop PyCrypto and use cryptography instead.
- Use cryptography in acme.jose.jwa/jwk.
- Change Crypto.Random to os.urandom,
  c.f. https://cryptography.io/en/latest/random-numbers/?highlight=urandom
2015-07-05 20:36:20 +00:00
Jakub Warmuz
7c3c52c2b1
Add example dev config file, config file docs. 2015-07-03 15:02:01 +00:00
Jakub Warmuz
c639673de5
Read config from $XDG_CONFIG_HOME/letsencrypt/cli.ini. 2015-07-03 14:38:09 +00:00
Jakub Warmuz
4407210e01
Fix --no-verify-ssl in HEAD, refactor acme.client_tests.
Fix #521 by introducing MissingNonceError, which by shows response
headers when printed to STDOUT. More sensible solution (a'la #523) is
blocked by boulder#417 (HTTP 405 response for HEAD).

Split out ClientNetworkWithMockedResponseTest from ClientNetworkTest,
which improves readability and makes it easier to test (less mocks).
2015-07-03 09:46:30 +00:00
Jakub Warmuz
2b32b94c0b
acme.client.ClientNetwork 2015-07-03 09:46:24 +00:00
James Kasten
108bd22ca3 Merge pull request #579 from PatrickHeppler/master
Update README.rst
2015-07-02 09:23:24 -07:00
James Kasten
1bd49cef82 Merge pull request #575 from kuba/nginx-integration
Do not include /etc/nginx/mime.types in nginx integration testing.
2015-07-02 09:22:11 -07:00
James Kasten
d0f26132bc Merge pull request #572 from bradmw/merge-fix
Fixed traceback when not run as root
2015-07-02 09:20:38 -07:00
James Kasten
e140eca4f3 Merge pull request #570 from kuba/simplehttp
SimpleHTTP fixes
2015-07-02 09:19:21 -07:00
PatrickHeppler
5d575e78b2 Update README.rst
Fixed missing newline
2015-07-02 10:37:35 +02:00
Jakub Warmuz
dc9ffdbb7f
Update old TODO comment. 2015-07-02 04:51:41 +00:00
Brad Warren
8b3a766dc1 Made logfile location more clear 2015-07-01 14:49:32 -07:00
PatrickHeppler
e682ae2503 Update README.rst
Added example for SAN certificates
2015-07-01 16:41:18 +02:00
Brad Warren
13913fd8e0 Added traceback dump 2015-06-30 12:57:51 -07:00
Jakub Warmuz
a7a863e1f2
Do not include /etc/nginx/mime.types in nginx integration testing.
This file (or /etc/nginx in whole) might not exist on the target
system.
2015-06-30 14:52:48 +00:00
Brad Warren
85b5bc0cb2 Reimplemented exception handling 2015-06-29 17:31:48 -07:00
Brad Warren
a248980952 Fixed traceback when not run as root 2015-06-29 11:53:03 -07:00
James Kasten
abe1aa999a Merge pull request #568 from kuba/cover
Bump coverage
2015-06-29 09:04:41 -07:00
James Kasten
1ec90a6c5b Merge pull request #567 from kuba/nginx-integration
Nginx bug fixes and integration tests
2015-06-29 09:01:59 -07:00
James Kasten
e0fd6ee018 Merge pull request #565 from bradmw/traceback
Tracebacks and Forks
2015-06-29 08:52:14 -07:00
James Kasten
b316cd2caa Merge pull request #566 from letsencrypt/apache_modules
Fix broken unittest
2015-06-29 08:51:17 -07:00
Jakub Warmuz
2ec451d00b
IConfig.simple_http_port (fixes #542). 2015-06-29 07:58:36 +00:00
Jakub Warmuz
29e56d442f
Fix line-too-long 2015-06-29 07:56:22 +00:00
Jakub Warmuz
87f197afb2
manual: make sure user doesn't serve /root, or cert.pem/key.pem 2015-06-29 07:56:22 +00:00
Jakub Warmuz
ce32de023d
Move simple_http_simple_verify to SimpleHTTPResponse.simple_verify. 2015-06-29 07:56:21 +00:00
Jakub Warmuz
36752a3dab
simpleHttp needs text/plain or absent. 2015-06-29 07:53:28 +00:00
Jakub Warmuz
a0acf7c703
acme.verify.simple_http_simple_verify 2015-06-29 07:50:31 +00:00
Jakub Warmuz
60478e213b
Bump apache coverage. 2015-06-28 09:42:43 +00:00
Jakub Warmuz
bfba30701e
Bump core coverage 2015-06-28 09:41:33 +00:00
Jakub Warmuz
051a351a43
Move test_add_chal from letsencrypt_nginx (plugins.common 100% coverage). 2015-06-28 09:39:21 +00:00
Jakub Warmuz
46707406b5
Tests: don't cover plugins.common test functions. 2015-06-28 09:38:03 +00:00
Jakub Warmuz
7abff038dc
Display tests: move test_visual to tests/display.py script. 2015-06-28 09:31:42 +00:00
Jakub Warmuz
cfbd33809e
Remove acme.util 2015-06-28 09:27:17 +00:00
Jakub Warmuz
4fbb5cb80f
Address review comments 2015-06-27 16:52:52 +00:00
Jakub Warmuz
096920b8b3
Refactor integration scripts, use --debug. 2015-06-27 13:34:23 +00:00
Jakub Warmuz
98844a196c
Add test for PluginError catching in disco 2015-06-27 10:02:02 +00:00
Jakub Warmuz
c96fc7e33a
travis_retry sudo apt-get ... 2015-06-27 09:43:33 +00:00
Jakub Warmuz
49b02e7740
Travis CI: test nginx plugin. 2015-06-27 09:20:37 +00:00
Jakub Warmuz
9652656e14
Integration tests for nginx plugin (without root). 2015-06-27 09:20:37 +00:00
Jakub Warmuz
c459102a04
letsencrypt_nginx: call "nginx -c server_root/nginx.conf ..." 2015-06-27 09:18:47 +00:00
Jakub Warmuz
30dfb6a1a9
letsencrypt_nginx: respect IConfig.dvsni_port (partially fixes #479). 2015-06-27 09:18:47 +00:00
Jakub Warmuz
be889d3794
letsencrypt_nginx: generate snakeoil cert/key (fixes #481). 2015-06-27 09:18:37 +00:00
Jakub Warmuz
b7a19486ed
Plugins disco: catch PluginError in prepare(), debug log exceptions. 2015-06-27 08:14:00 +00:00
James Kasten
3ab5addb81 Merge branch 'master' into apache_modules 2015-06-27 00:53:34 -07:00
James Kasten
62e7eb236d Merge pull request #558 from kuba/485-cleanup
Null installer, integration tests for install/run, backport #440 CLI changes, fix --help all.
2015-06-27 00:18:54 -07:00
Jakub Warmuz
fd333d39bb
Merge remote-tracking branch 'github/letsencrypt/master' into 485-cleanup
Conflicts:
	letsencrypt/cli.py
2015-06-27 07:01:58 +00:00
Brad Warren
88507fea87 Stopped catching silly things 2015-06-26 18:07:40 -07:00
schoen
4e221eb9bd Merge pull request #562 from bradmw/traceback
Log and hide tracebacks by default
2015-06-26 16:55:18 -07:00
schoen
83e6208c3b Merge pull request #563 from bradmw/reporter_test
Fixed reporter coverage
2015-06-26 16:41:18 -07:00
Brad Warren
95a1432476 Come on Python 2.6... 2015-06-26 16:40:59 -07:00
Brad Warren
3f1c6e4a9c Fixed reporter coverage 2015-06-26 16:29:13 -07:00
Brad Warren
554139cb1a Reformatted error message 2015-06-26 16:14:21 -07:00
Brad Warren
07642a4a96 Merge remote-tracking branch 'upstream/master' into traceback 2015-06-26 16:09:53 -07:00
Brad Warren
d6246ae309 Special cased KeyboardInterrupt 2015-06-26 16:09:46 -07:00
James Kasten
d10186214b fix unittest 2015-06-26 16:07:39 -07:00
Jakub Warmuz
e3cd6fc709
Fix review comments. 2015-06-26 23:03:27 +00:00
James Kasten
7665e8c8c1 Merge pull request #561 from letsencrypt/fix_plugin_prepare
Quick fix for Apache prepare
2015-06-26 15:43:17 -07:00
Brad Warren
e97719b367 Merge remote-tracking branch 'upstream/master' into traceback 2015-06-26 15:26:44 -07:00
Brad Warren
93b736a224 Logs and hides tracebacks 2015-06-26 15:26:33 -07:00
James Kasten
f99af51de3 Address comments 2015-06-26 14:47:03 -07:00
Jakub Warmuz
79252e7940
Merge remote-tracking branch 'github/letsencrypt/master' into 485-cleanup
Conflicts:
	tests/boulder-integration.sh
2015-06-26 21:11:42 +00:00
James Kasten
91f20e9a4a OMerge branch 'master' into fix_plugin_prepare 2015-06-26 13:31:38 -07:00
James Kasten
3705594628 Merge pull request #554 from letsencrypt/renew_before_deploy
Try to renew certs before trying to deploy them
2015-06-26 16:25:30 -04:00
James Kasten
aea74f3c46 Change appropriate misconfiguration errors 2015-06-26 12:05:25 -07:00
James Kasten
1a63c774d1 Merge pull request #557 from kuba/integration-testing
boulder-integration.sh: SERVER env variable
2015-06-26 12:43:07 -04:00
James Kasten
5027d70002 Ignore pycharm project files 2015-06-26 09:30:10 -07:00
James Kasten
93a9a8b268 ConfiguratorError -> PluginError 2015-06-26 09:29:40 -07:00
James Kasten
36161c306a update tests 2015-06-26 08:47:26 -07:00
James Kasten
fdfd395707 Merge branch 'master' into apache_failed_vhost
Conflicts:
	letsencrypt_apache/configurator.py
	letsencrypt_apache/dvsni.py
2015-06-26 08:29:37 -07:00
Jakub Warmuz
9d6cf6c6a2
Fix --help all to display options without topic. 2015-06-26 13:31:03 +00:00
Jakub Warmuz
abbd2483a5
install --key-path (fixes #550) 2015-06-26 13:26:09 +00:00
Jakub Warmuz
b1b3befd04
Backport #440 CLI changes, clean up after #485
Additonally:
- remove "-s", it will not be used often
- remove --accountkey (former --authkey), as it is not used
- CLI_DEFAULTS: cert_path -> auth_cert_path, chain_path -> auth_chain_path
- remove remaining "LetsEncrypt" error prefixes (fixes #487).
- HeplfulParser:
  - call add_plugin_args from outside (this makes sure plugins are
    displayed last in --help all)
2015-06-26 13:26:09 +00:00
Jakub Warmuz
c8dc9af7bf
Fix integration test for install. 2015-06-26 13:26:09 +00:00
Jakub Warmuz
7d775ae9f3
Add null installer, integration tests for install/run
Fixes #438.
2015-06-26 13:26:09 +00:00
Jakub Warmuz
3383dedbcf
boulder-integration.sh: SERVER env variable
Blocks https://github.com/letsencrypt/boulder/pull/416
2015-06-26 07:20:01 +00:00
schoen
7a24499f15 Merge pull request #553 from kuba/logging
Logging refactor, logfile
2015-06-25 21:49:51 -07:00
James Kasten
bda7c9f5ce refine vhost display 2015-06-25 18:57:10 -07:00
Seth Schoen
fbb0058bcd Enable renewer test that was failing 2015-06-25 18:15:59 -07:00
Seth Schoen
c54f154ea6 Try to renew certs before trying to deploy them 2015-06-25 18:12:45 -07:00
James Kasten
1973bdbc2f Merge branch 'master' into apache_failed_vhost 2015-06-25 17:03:22 -07:00
James Kasten
99fcb4f230 Handle missing vhosts gracefully 2015-06-25 17:02:09 -07:00
Jakub Warmuz
f080aae332
Merge remote-tracking branch 'github/letsencrypt/master' into logging 2015-06-25 21:11:46 +00:00
James Kasten
a96a7e2641 Merge pull request #551 from kuba/augeas_configurator-apache
Move AugeasConfigurator to letsencrypt_apache.
2015-06-25 17:10:21 -04:00
Jakub Warmuz
4ce4e2f930
logger = logging.getLogger(__name__) 2015-06-25 19:55:22 +00:00
Jakub Warmuz
865d9074e4
log.DialogHandler: print formatted record, not just message.
Fixes bug, where DialogHandler would display only %(message) part of
the record, instead of the entire formatted record string.
2015-06-25 19:55:22 +00:00
Jakub Warmuz
eb149b9129
CLI debug log file 2015-06-25 19:55:16 +00:00
Jakub Warmuz
5b9a2f9637
cli._setup_logging 2015-06-25 19:45:01 +00:00
Jakub Warmuz
6ba133fc7c
Debug log CLI arguments 2015-06-25 19:45:01 +00:00
Jakub Warmuz
60b6834ab0
Debug log registration errors. 2015-06-25 19:45:01 +00:00
Jakub Warmuz
3ade1d13f3
Merge remote-tracking branch 'github/letsencrypt/master' into renewer
Conflicts:
	tests/boulder-integration.sh
2015-06-25 19:07:21 +00:00
Jakub Warmuz
f312bdac22
Fix unittests for dvsni_port. 2015-06-25 18:36:06 +00:00
James Kasten
bf69cf1f05 Merge pull request #504 from kuba/csr
Initial support for user-supplied CSRs (fixes: #370)
2015-06-25 14:09:53 -04:00
James Kasten
3f2e427a71 Merge pull request #545 from letsencrypt/ssl_labs_urls
Add SSLLab URLs at the end of installation
2015-06-25 14:05:12 -04:00
Jakub Warmuz
3789922c0b
Move AugeasConfigurator to letsencrypt_apache.
https://github.com/letsencrypt/lets-encrypt-preview/pull/531#issuecomment-114285541
2015-06-25 17:55:49 +00:00
Jakub Warmuz
040f434a61
Renewer integration tests for standalone. 2015-06-25 17:43:17 +00:00
Jakub Warmuz
d543716adf
Merge remote-tracking branch 'github/letsencrypt/master' into renewer 2015-06-25 16:13:46 +00:00
Jakub Warmuz
d804853958
Remove commented suject fields in make_csr 2015-06-25 16:08:52 +00:00
Jakub Warmuz
4057734c33
Add integrations tests for CSR. 2015-06-25 16:05:25 +00:00
Jakub Warmuz
dc561ea3df
Merge remote-tracking branch 'github/letsencrypt/master' into csr 2015-06-25 15:26:41 +00:00
James Kasten
db1558a16c Merge pull request #499 from kuba/integration-testing
Integration testing
2015-06-25 10:58:15 -04:00
Jakub Warmuz
e51f300ee6
Merge remote-tracking branch 'github/letsencrypt/master' into csr
Conflicts:
	letsencrypt/cli.py
	letsencrypt/client.py
	letsencrypt/tests/client_test.py
2015-06-25 13:36:41 +00:00
Jakub Warmuz
e0bc6aeb50
Travis CI setup for integration testing (fixes #530).
Candidate issues to be addressed:

- race condition: start.sh might fail to start Boulder WFE before
  ./tests/boulder-integration.sh is run (unlikely, but possible,
  leading to flaky tests)

- intertwined build logs (./start.sh boostraps in the background,
  integration test debug logs on server and client side)
2015-06-25 09:50:23 +00:00
Jakub Warmuz
8bde250ff2
boulder-integration.sh: agree-tos -> agree-eula, force standalone. 2015-06-25 08:25:10 +00:00
Jakub Warmuz
434699031b
Merge remote-tracking branch 'github/letsencrypt/master' into integration-testing 2015-06-25 06:02:32 +00:00
Jakub Warmuz
77ddc68d8e
Quickfix for wrong renewer target paths.
IOError: [Errno 2] No such file or directory: '/tmp/le/config/archive/renewer/privkey2.pem'
2015-06-25 05:53:39 +00:00
schoen
376bf9de6d Merge pull request #548 from kuba/renewer
Renewer quickfixes
2015-06-24 22:47:16 -07:00
Jakub Warmuz
38b497ef73
renewer: fix bug where renewer.conf wasn't read. 2015-06-25 05:36:02 +00:00
Jakub Warmuz
4de60f68ab
Pass cli_config to RenewableCert 2015-06-25 05:35:49 +00:00
Jakub Warmuz
b9f2823d6b
renewer: _paths_parser 2015-06-25 05:35:15 +00:00
schoen
f5def6b6d6 Merge pull request #535 from kuba/473-no-cli
Renewer dynamic dirs based on --config-dir/--work-dir (#473 without touching CLI)
2015-06-24 22:18:40 -07:00
James Kasten
7d2023c64e Merge pull request #547 from bradmw/errors
Validation errors
2015-06-25 01:04:08 -04:00
James Kasten
b606bdf749 double quotes 2015-06-24 18:36:30 -07:00
Brad Warren
f35d8a5228 Merge remote-tracking branch 'upstream/master' into errors 2015-06-24 18:25:02 -07:00
Brad Warren
d15a386f92 Incorporated jdkasten's feedback 2015-06-24 18:24:54 -07:00
James Kasten
138ebc6d4e Merge pull request #539 from coolaj86/patch-3
minor update for raspbian
2015-06-24 19:17:32 -04:00
James Kasten
04440179f4 Merge pull request #534 from kuba/440-no-cli
Dynamic dirs based on --config-dir and --work-dir (#440 without touching CLI)
2015-06-24 19:06:33 -04:00
James Kasten
81c7b30926 Merge pull request #546 from letsencrypt/formatting_doc
formatting and documentation
2015-06-24 19:01:33 -04:00
Brad Warren
76d12e5e34 Merge remote-tracking branch 'upstream/master' into errors 2015-06-24 15:28:14 -07:00
Brad Warren
9637142c4c Added auth_handler tests 2015-06-24 15:27:34 -07:00
James Kasten
67f67fea02 Remove trailing whitespace 2015-06-24 14:53:28 -07:00
James Kasten
06d7e51f22 formatting and documentation 2015-06-24 14:38:30 -07:00
James Kasten
e2be3a5fa9 Merge pull request #541 from kuba/repo-rename
Update references after repo rename.
2015-06-24 16:38:45 -04:00
Peter Eckersley
271ba9a7e7 Merge pull request #485 from letsencrypt/help
Organise command line help into topics
2015-06-24 13:18:05 -07:00
AJ ONeal
fba2de706e move lsb install into newer with --no-install-recommends 2015-06-24 13:29:26 -06:00
Brad Warren
8f760cf828 Cleaned up multiline statements 2015-06-24 11:51:50 -07:00
Brad Warren
512e02c837 Added reporter messages for failed challenges. 2015-06-24 11:46:39 -07:00
James Kasten
f8384127c0 Format and fix ssl_labs printout 2015-06-24 10:55:08 -07:00
Jakub Warmuz
15258cc50a
Update references after repo rename.
https://github.com/letsencrypt/letsencrypt/issues/505
2015-06-24 04:56:31 +00:00
Peter Eckersley
7fe5b8233b Retry, with sanity. 2015-06-23 17:38:48 -07:00
Peter Eckersley
4f3753b644 Merge remote-tracking branch 'letsencrypt/master' into help
Conflicts:
	letsencrypt/le_util.py
2015-06-23 16:23:14 -07:00
Seth Schoen
d4bdba9726 Merge branch 'acme-spec-158' of https://github.com/kuba/lets-encrypt-preview
Resolved conflict in errors.py (LetsEncryptContAuthError renamed
to ContAuthError)

Conflicts:
	letsencrypt/errors.py
2015-06-23 15:55:13 -07:00
AJ ONeal
a1f025980a minor update for raspbian 2015-06-23 16:38:39 -06:00
James Kasten
fc4c599187 Remove preview from github and travis 2015-06-23 18:38:24 -04:00
James Kasten
0069f408b1 Merge pull request #533 from kuba/bugs/487
Remove "LetsEncrypt" prefix from errors (does not touch cli.py)
2015-06-23 18:14:15 -04:00
Peter Eckersley
079eb93e53 Satisfy the lintmonster 2015-06-23 15:10:42 -07:00
Peter Eckersley
02f3bb4f05 Use a completely custom usage mesage for plain --help
Keep argparse in place for --help <TOPIC>, but try to make that match
the customised short help as much as possible.
2015-06-23 14:13:52 -07:00
Jakub Warmuz
457279adb2
Add errors.FailedChallenges and update AuthHandler to use it. 2015-06-23 20:10:20 +00:00
Jakub Warmuz
4fb1685b55
Update error codes, add "error" field to ChallengeBody (acme-spec#158). 2015-06-23 17:57:11 +00:00
Jakub Warmuz
e82f605c22
Merge branch '440-no-cli' into 473-no-cli
Conflicts:
	letsencrypt/cli.py
        letsencrypt/renewer.py
2015-06-23 08:17:07 +00:00
Jakub Warmuz
f1e747ac1a
Revert CLI changes, blocked by #485. 2015-06-23 07:48:45 +00:00
Jakub Warmuz
278bd8deb2
Rename IConfig.csr_dir back to IConfig.cert_dir.
This will be used in #504.
2015-06-23 07:48:45 +00:00
Jakub Warmuz
8e582dfff8
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt/constants.py
2015-06-23 07:47:48 +00:00
James Kasten
1cd47d4af3 first pass for ssl labs 2015-06-22 23:23:57 -07:00
Jakub Warmuz
a1e750f432
Errors prefix: do not touch CLI. 2015-06-22 22:45:45 +00:00
Jakub Warmuz
57f67c4109
Rewrap after errors rename, doc fixes. 2015-06-22 22:43:42 +00:00
Jakub Warmuz
cfa7e28106
errors.LetsEncrypt -> errors. (fixes: #487) 2015-06-22 22:41:24 +00:00
James Kasten
655331c9cf Merge pull request #532 from kuba/logging-without-cli
Logging improvements (without touching CLI)
2015-06-22 18:32:47 -04:00
James Kasten
62d26f1dcd Merge pull request #531 from kuba/pkgs_sep_prep
setup.py: separate install_requires
2015-06-22 18:23:15 -04:00
James Kasten
3448490734 Merge pull request #529 from kuba/acme-resource-json
JSONDeSerializable acme.messages.Resource.
2015-06-22 18:15:12 -04:00
Jakub Warmuz
8e39a3a0ef
Collate multi-line logs, use logging.exception, other fixes. 2015-06-22 22:05:27 +00:00
Jakub Warmuz
28f5c7d666
logs: collate omitted empty fields 2015-06-22 22:05:27 +00:00
Jakub Warmuz
e17bd684bb
Debug log received response for GET/POST 2015-06-22 22:05:27 +00:00
Jakub Warmuz
b6ef25e911
Fix review comments (typo, inheritance fix). 2015-06-22 22:02:43 +00:00
Jakub Warmuz
c5bf273024
setup.py: separate install_requires 2015-06-22 21:58:13 +00:00
Jakub Warmuz
bff89936af
Merge remote-tracking branch 'github/letsencrypt/master' into acme-resource-json
Conflicts:
	acme/messages_test.py
2015-06-22 21:03:57 +00:00
James Kasten
cd7c6d8220 Merge pull request #528 from kuba/acme-spec-118-revoke
acme: up to date revoke
2015-06-22 16:56:06 -04:00
Jakub Warmuz
e0a1e8f4e8
JSONDeSerializable acme.messages.Resource.
Provides API necessary to implement JSON-based account storage as
described at
https://github.com/letsencrypt/lets-encrypt-preview/pull/362#issuecomment-97946817
2015-06-22 20:41:45 +00:00
Jakub Warmuz
d970987b79
Fix comment typo 2015-06-22 20:30:17 +00:00
Jakub Warmuz
52d6e9b674
acme-spec#118 revoke. 2015-06-22 20:26:52 +00:00
James Kasten
ac8d9e4ded Merge pull request #527 from kuba/acme-client
Remove ACME v00 code, move networking to acme.client
2015-06-22 16:24:06 -04:00
Jakub Warmuz
1720864b44
acme.client: locally disable too-many-instance-attributes. 2015-06-22 19:55:47 +00:00
James Kasten
e6dede6560 Merge pull request #526 from kuba/rm-netstat-server-doc
Remove constants.NETSTAT. Update docs for IConfig.server.
2015-06-22 15:36:27 -04:00
Jakub Warmuz
cf76593fa7
Remove constants.NETSTAT. Update docs for IConfig.server. 2015-06-22 18:03:04 +00:00
Peter Eckersley
f408ac7296 Draft basic usage text 2015-06-22 09:37:57 -07:00
Jakub Warmuz
90dae9fd88
Update restified example script and rename to acme_client.py 2015-06-22 09:28:16 +00:00
Peter Eckersley
fa09882892 Change permission error message
Commonly, this will be caused by a failure to sudo, so the previous text was
not necessarily going to be helpful.
2015-06-22 01:10:22 -07:00
Jakub Warmuz
b4d63cbbb3
Move letsencrypt.network to acme.client. 2015-06-22 04:52:08 +00:00
Jakub Warmuz
a278d53f52
Rename messages2 to messages. 2015-06-22 04:52:08 +00:00
Jakub Warmuz
71a01d139c
Rename network2 to network. 2015-06-22 04:52:08 +00:00
Jakub Warmuz
aa6faadb5c
Add ChallangeResponseTest 2015-06-22 04:52:08 +00:00
Jakub Warmuz
c208e810ee
Remove old messages schemata. 2015-06-22 04:52:07 +00:00
Jakub Warmuz
c5d4f91bf7
Remove old messages and network 2015-06-22 04:52:07 +00:00
Jakub Warmuz
960b070c22
Dummy use of network2 in revoker 2015-06-22 04:52:07 +00:00
James Kasten
e503601d3b Merge pull request #524 from kuba/work-dir-dvsni-tmp
Store temporary DVSNI files in IConfig.work_dir.
2015-06-21 15:50:55 -04:00
Jakub Warmuz
061282fa66
Store temporary DVSNI files in IConfig.work_dir. 2015-06-20 21:32:24 +00:00
Jakub Warmuz
8f4280c2df
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt_apache/tests/util.py
	letsencrypt_nginx/tests/util.py
2015-06-20 20:04:58 +00:00
James Kasten
5dcac29e3b Merge pull request #474 from kuba/nginx-apache-split
letsencrypt_nginx should not depend on letsencrypt_apache.
2015-06-20 12:57:31 -04:00
James Kasten
d6d8bf250b Merge pull request #519 from kuba/docs
Docs improvements
2015-06-20 12:38:41 -04:00
Jakub Warmuz
50e509604c
Docs: remove wrong re-install comment 2015-06-20 10:20:54 +00:00
James Kasten
99ff33e581 Merge pull request #502 from kuba/manual
ManualAuthenticator for SimpleHTTP
2015-06-19 13:45:14 -04:00
Jakub Warmuz
048fd8d249
Merge remote-tracking branch 'github/letsencrypt/master' into docs
Conflicts:
	docs/using.rst
2015-06-19 17:17:17 +00:00
James Kasten
c89925ad04 Merge pull request #516 from kuba/requirements.txt
Fix merge conflicts between #486 and #510 (pip install .).
2015-06-19 13:14:08 -04:00
James Kasten
f3e5757297 Merge pull request #501 from kuba/simplehttp
SimpleHTTP.tls -> SimpleHTTPResponse.tls bug, MAX_PATH_LEN, good_path, scheme
2015-06-19 13:05:59 -04:00
James Kasten
b61e57c5fc Merge pull request #517 from kuba/functools32
Fix for functools32 that works with Python 2.6
2015-06-19 12:55:23 -04:00
Jakub Warmuz
23c5a1fd90
Docs: "." and functools32 adjustements 2015-06-19 16:13:53 +00:00
Jakub Warmuz
4d39699bef
Remove doubled :members: from acme errors docs 2015-06-19 16:11:09 +00:00
Jakub Warmuz
e176ad8f43
Remove old Boulder incompatibility issue 2015-06-19 16:11:03 +00:00
Jakub Warmuz
4040fd0204
Docs: extend usage section with "letsencrypt auth" call. 2015-06-19 11:21:51 +00:00
Jakub Warmuz
b8ebb0ab16
Docs: backticks nit. 2015-06-19 11:17:09 +00:00
Jakub Warmuz
8292eab3f7
Docs: add link to Docker docs. 2015-06-19 11:17:09 +00:00
Jakub Warmuz
ca6b326371
Docs: add "Getting the code" section. 2015-06-19 11:13:38 +00:00
Jakub Warmuz
1a013eae6e
Docs: no support for setup.py, root, or non-Virtualenv installation. 2015-06-19 11:13:38 +00:00
Jakub Warmuz
673a6d4f37
Docs: move SWIG notes below installation cmd, Mac OS X note. 2015-06-19 11:13:38 +00:00
Jakub Warmuz
a1f5ea8e8c
Docs: note about pip editable mode. 2015-06-19 11:13:38 +00:00
Jakub Warmuz
3382dac793
README: FAQ link at the top. 2015-06-19 11:13:38 +00:00
Jakub Warmuz
f46e2aeedd
README: documentation link at the top.
Hopefully helps to mitigate problems mentioned in
https://groups.google.com/a/letsencrypt.org/forum/#!topic/client-dev/4xpVpy4EVz0
2015-06-19 11:13:37 +00:00
Jakub Warmuz
dbd024f77c
Inline docs fixes 2015-06-19 10:18:01 +00:00
Jakub Warmuz
6d2c81138e
Revert "Remove support for python2.6"
This reverts commit dee1b7f049.
2015-06-19 08:17:49 +00:00
Jakub Warmuz
ed7ba28211
Pin jsonschema (quickfix for missing functools32).
https://github.com/Julian/jsonschema/issues/233
2015-06-19 08:17:49 +00:00
Jakub Warmuz
5f41c9f191
Dockerfile: note about missing requirements.txt 2015-06-19 08:16:03 +00:00
Jakub Warmuz
b3be239061
Fix merge conflicts between #486 and #510 (pip install .). 2015-06-19 08:15:38 +00:00
Jakub Warmuz
8afc26a736
Fix typo 2015-06-19 04:10:51 +00:00
James Kasten
a71ef63c18 Merge pull request #497 from letsencrypt/hide-email-optional
Don't suggest optional email.
2015-06-18 23:11:27 -04:00
James Kasten
25f25c63b4 Merge pull request #513 from Hainish/master
functools32 required - add to setup.py
2015-06-18 21:52:09 -04:00
Jacob Hoffman-Andrews
79c06102e6 Merge pull request #514 from letsencrypt/fix-cleanup
Make sure cleanup_challenges happens
2015-06-18 18:36:15 -07:00
William Budington
dee1b7f049 Remove support for python2.6 2015-06-18 18:35:35 -07:00
James Kasten
b1bb5a6843 Make sure cleanup_challenges happens 2015-06-18 18:02:51 -07:00
William Budington
a873e8ea33 functools32 required - add to setup.py 2015-06-18 17:45:04 -07:00
James Kasten
c97573ec7c Merge pull request #486 from kuba/requirements.txt
requirements.txt: no editable (-e) mode, no "." install.
2015-06-18 20:02:32 -04:00
James Kasten
a11364458a Merge pull request #495 from kuba/bootstrap
Bootstrap: swig 3.0.5+ and other improvements
2015-06-18 19:58:03 -04:00
James Kasten
5b2f74c92a Merge pull request #491 from kuba/tox.cover.sh-erase
tox.cover.sh: erase coverage before tests
2015-06-18 19:53:14 -04:00
James Kasten
a41c0f7590 Merge pull request #510 from kuba/rpm-bootstrap
Fedora and CentOS bootstrap
2015-06-18 18:39:50 -04:00
Jakub Warmuz
db6f9ecf86
Fedora installation instructions. 2015-06-18 14:13:13 +00:00
Jakub Warmuz
9b4cff8cd6
bootstrap: _rpm_common.sh, centos fixes 2015-06-18 13:55:32 +00:00
Jakub Warmuz
67768641cc
Merge branch 'centos-2' into rpm-bootstrap 2015-06-18 13:44:05 +00:00
Jakub Warmuz
bb61db97f0
Merge branch 'centos-1' into rpm-bootstrap 2015-06-18 13:43:00 +00:00
Jakub Warmuz
85d9047f4e
Fedora: augeas -> augeas-libs 2015-06-18 13:13:29 +00:00
PatrickHeppler
8d0334d2de Update using.rst
Additional informations about installing on Centos 7
2015-06-18 14:58:55 +02:00
PatrickHeppler
63d5273ed1 Create centos.sh 2015-06-18 14:55:12 +02:00
Peter Eckersley
52e20769bb Revert workaround for #482 2015-06-16 14:19:19 -07:00
Peter Eckersley
40871d4c29 Fix merge error 2015-06-16 14:04:41 -07:00
Peter Eckersley
9855ac81bb Merge remote-tracking branch 'letsencrypt/master' into help
Conflicts:
	letsencrypt/cli.py

Not clear what topic that tweak flag should be under; leaving it under testing
for now...
2015-06-16 12:59:09 -07:00
Peter Eckersley
88a03afe7b Prevent pylint from complaining about some silly things 2015-06-16 12:47:07 -07:00
Peter Eckersley
8dc9cc67d9 Satiate the pylint daemons 2015-06-16 12:46:37 -07:00
Jakub Warmuz
8a9759bf88
Update Client.obtain_* docs, simplify obtain_certificate() rtype. 2015-06-16 06:26:44 +00:00
Jakub Warmuz
60cc025658
Add generete-csr.sh script to examples. 2015-06-15 11:40:17 +00:00
Jakub Warmuz
635e585226
Initial support for "auth --csr" (fixes: #370) 2015-06-15 11:40:17 +00:00
Jakub Warmuz
d4b9499e2b
ManualAuthenticator for SimpleHTTP.
Inspired by quite popular [1] letsencrypt-nosudo [2] by
@diafygi. Together with #440 and #473, it allows Let's Encrypt to be
used without sudo (root) on the target machine (c.f. [3]). Possibly
fixes #500.

[1] https://news.ycombinator.com/item?id=9707170
[2] https://github.com/diafygi/letsencrypt-nosudo
[3] https://groups.google.com/a/letsencrypt.org/forum/#!topic/client-dev/JAqxSvXlln4
2015-06-14 18:12:10 +00:00
Jakub Warmuz
896d2be1db
SimpleHTTP.tls -> SimpleHTTPResponse.tls bug, MAX_PATH_LEN, good_path, scheme 2015-06-14 17:32:25 +00:00
Jacob Hoffman-Andrews
41defbe5e7 Merge pull request #498 from kuba/simplehttp
SimpleHTTPS -> SimpleHTTP[S]
2015-06-12 13:28:47 -07:00
Jakub Warmuz
5f01a90ce6
Add simple Boulder integration test 2015-06-12 18:14:33 +00:00
Jakub Warmuz
848528599e
Merge remote-tracking branch 'github/letsencrypt/master' into integration-testing 2015-06-12 17:49:25 +00:00
Jakub Warmuz
ad79d7c8b6
Adjust client reports to use RenewerConfiguration. Fix docs. 2015-06-12 17:43:19 +00:00
Jakub Warmuz
3dbfab7b9f
Merge branch 'swig-3.0.5' into fedora-bootstrap 2015-06-12 12:58:53 +00:00
Jakub Warmuz
d585b44680
Nit: character upper case fixes. 2015-06-12 12:58:35 +00:00
Jakub Warmuz
2c96ea68b8
Merge branch 'swig-3.0.5' into bootstrap 2015-06-12 12:56:19 +00:00
Jakub Warmuz
6b9d5c1daa
Separate requirements.txt for SWIG 3.0.5+ 2015-06-12 12:55:33 +00:00
Jakub Warmuz
8ba5166563
Vagrant: remove explicit git-core install 2015-06-12 11:54:12 +00:00
Jakub Warmuz
d53120f25f
Fix SimpleHTTP tests and omitempty bug. 2015-06-12 09:21:30 +00:00
Jakub Warmuz
8883bd76fd
Add --no-simple-http-tls. 2015-06-12 08:25:59 +00:00
Jakub Warmuz
bc9373929a
Add SimpleHTTP.tls 2015-06-12 08:23:43 +00:00
Jakub Warmuz
7f5abba83e
Rename SimpleHTTPS to SimpleHTTP. 2015-06-12 08:23:32 +00:00
Jacob Hoffman-Andrews
922f8e5dad Merge pull request #490 from kuba/anti-replay
Add an anti-replay nonce facility (fixes: #488).
2015-06-11 14:53:16 -07:00
Jakub Warmuz
b94c3614b9
Merge branch 'cli-config-fixes' into renewer-cli 2015-06-11 21:48:29 +00:00
Jakub Warmuz
196884652e
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt_apache/constants.py
	letsencrypt_nginx/constants.py
2015-06-11 21:46:16 +00:00
James Kasten
4147993b4d Merge pull request #484 from fmarier/rename-apache-config
Rename the apache config file to match the nginx one
2015-06-11 17:34:48 -04:00
James Kasten
50de40e780 Merge pull request #457 from fmarier/rename-nginx-config
Rename the nginx ssl config file to match the final filename
2015-06-11 17:34:38 -04:00
Jakub Warmuz
c02db78417
Merge remote-tracking branch 'github/letsencrypt/master' into renewer-cli
Conflicts:
	letsencrypt/client.py
2015-06-11 21:16:47 +00:00
Jakub Warmuz
e346bfafd6
Merge branch 'cli-config-fixes' into renewer-cli 2015-06-11 21:14:28 +00:00
Jakub Warmuz
eeb477d85e
Merge branch 'swig-3.0.5' into fedora-bootstrap 2015-06-11 21:11:53 +00:00
Jakub Warmuz
fb4bd1a013
Merge branch 'swig-3.0.5' into bootstrap 2015-06-11 21:11:31 +00:00
Jakub Warmuz
30545e1c54
requirements.txt: fix M2Crypto URL 2015-06-11 21:11:25 +00:00
Jakub Warmuz
2be914f0d5
Bootstrap Debian: add git-core dep 2015-06-11 21:06:44 +00:00
Jakub Warmuz
2139971212
nit: add EOF newline 2015-06-11 19:35:29 +00:00
Jakub Warmuz
b44014b06e
Bootstrap README: deps rationale 2015-06-11 19:28:15 +00:00
Jakub Warmuz
19aea37203
Bootstrap Debian: remove python-setuptools dep 2015-06-11 19:26:17 +00:00
Jakub Warmuz
4ed1a1c2d6
Bootstrap Debian: one dep per line 2015-06-11 19:25:49 +00:00
Jakub Warmuz
c4b495aa37
Bootstrap Fedora 22 (fixes: #493) 2015-06-11 19:24:07 +00:00
Jakub Warmuz
ad5c3ff1b2
Support M2Crypto with swig 3.0.5+
Fixes issues recognized in
https://github.com/letsencrypt/lets-encrypt-preview/issues/413#issuecomment-106245456
and https://github.com/letsencrypt/lets-encrypt-preview/issues/493.
2015-06-11 19:22:27 +00:00
Jakub Warmuz
0543f040bf
Raise error on missing replay nonce. 2015-06-11 13:06:56 +00:00
Jakub Warmuz
22fd9d4cd7
tox.cover.sh: erase coverage before tests 2015-06-11 12:28:46 +00:00
Jakub Warmuz
fd39479810
Add an anti-replay nonce facility (fixes: #488). 2015-06-11 12:21:26 +00:00
Jakub Warmuz
887f91bdac
requirements.txt: no editable (-e) mode, no "." install. 2015-06-09 07:51:42 +00:00
Peter Eckersley
c70bce8b7a Some cleaning up 2015-06-08 16:51:15 -07:00
Peter Eckersley
2d65026d6d Help topics now working 2015-06-08 11:42:15 -07:00
Peter Eckersley
1fa5a64abd Draft (somewhat buggy) implementation of help topics 2015-06-08 01:38:39 -07:00
Francois Marier
bce01419da Rename the apache config file to match the nginx one
Since both config files end up in /etc/letsencrypt/, if we want the two
plugins be to installable at the same time, their config files need different
names.
2015-06-07 20:31:50 +12:00
Francois Marier
8198e6c105 Rename the nginx ssl config file to match the final filename
This config file will ultimately exist as /etc/letsencrypt/options-ssl-nginx.conf
so we may as well use the right filename everywhere.

This is much easier to deal with in the Debian packaging.
2015-06-07 20:22:43 +12:00
Peter Eckersley
3457a01da2 WIP: Cleaning up the command line help 2015-06-06 15:45:17 -07:00
Jacob Hoffman-Andrews
8cf9a152de Don't suggest optional email.
Email is still optional, by the same mechanism, but removing the suggestion to
leave it out we will greatly increase the percentage of users that supply one,
which in turn will reduce customer support requests.
2015-06-04 14:22:05 -07:00
schoen
bd130a8cd8 Merge pull request #480 from bradmw/reporter
Core reporter messages
2015-06-04 14:07:57 -07:00
Brad Warren
2c40cc77fb Added atexit_print_messages test 2015-06-03 12:06:51 -07:00
Brad Warren
3f37e3a241 Merge remote-tracking branch 'upstream/master' into reporter 2015-06-03 11:41:25 -07:00
Brad Warren
9f37a46c92 Added client tests 2015-06-03 11:40:14 -07:00
Brad Warren
34a66b1bff Added key notifications to the client 2015-06-02 17:11:00 -07:00
schoen
9394c26a9b Merge pull request #467 from kuba/bugs/456
Use timedelta instead of now.month + 1 (partially fixes: #456).
2015-06-02 17:03:57 -07:00
Brad Warren
a417d881f1 Merge remote-tracking branch 'upstream/master' into reporter 2015-06-02 17:00:40 -07:00
schoen
a53efb1737 Merge pull request #470 from kuba/gitignore
gitignore: +htmlcov, -m3, reorg
2015-06-02 16:47:16 -07:00
schoen
06a989e5a3 Merge pull request #468 from kuba/le_util-refactor
le_util refactor
2015-06-02 12:50:06 -07:00
schoen
e04f52368d Merge pull request #477 from bradmw/flush
Duplicate output fix
2015-06-02 12:36:38 -07:00
schoen
925904f54b Merge pull request #476 from kuba/bugs/381
Standalone Authenticator: no fail when port taken (fixes #381)
2015-06-02 12:25:57 -07:00
Brad Warren
986ca6562f Merge remote-tracking branch 'upstream/master' into reporter 2015-06-02 12:19:10 -07:00
Brad Warren
59c5a77731 Fixed duplicate output 2015-06-02 12:16:10 -07:00
Jakub Warmuz
05ad602b43
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt/cli.py
2015-06-02 18:42:04 +00:00
James Kasten
7b23147daf Merge pull request #475 from e00E/master
Fixed not passing a string as the private key to deploy_certificate w…
2015-06-02 14:37:55 -04:00
Jakub Warmuz
4646d8d6bf
Standalone Authenticator: no fail when port taken (fixes #381) 2015-06-02 18:31:09 +00:00
Brad Warren
f82aa5836e Merge remote-tracking branch 'upstream/master' into reporter 2015-06-02 11:28:03 -07:00
Brad Warren
814ab083bd Added account registration message and fixed double output 2015-06-02 11:16:24 -07:00
Jakub Warmuz
f06c0017db
Lower letsencrypt_apache coverage 2015-06-02 17:49:13 +00:00
Jakub Warmuz
9a7ade7cba
Rename cert_dir to csr_dir. 2015-06-02 17:42:23 +00:00
e00E
73e9956a48 Fixed not passing a string as the private key to deploy_certificate when using an Installer plugin. 2015-06-02 18:17:18 +02:00
James Kasten
34ed54079d Merge pull request #466 from kuba/bugs/462
Fix --no-verify-ssl negation bug
2015-06-02 11:54:09 -04:00
Jakub Warmuz
8c6d1ad50a
letsencrypt_nginx should not depend on letsencrypt_apache. 2015-06-02 14:28:20 +00:00
Jakub Warmuz
0b57daf473
Renewer dynamic dirs based on --config-dir/--work-dir (fixes #469). 2015-06-02 12:10:22 +00:00
Jakub Warmuz
d3ad5f8b56
gitignore: +htmlcov, -m3, reorg 2015-06-02 10:41:33 +00:00
Jakub Warmuz
9e5bd7c90d
Do not use generic exceptions in le_util_test. 2015-06-02 10:01:24 +00:00
Jakub Warmuz
efde7d4eff
Refactor le_util, 100% cover 2015-06-02 10:01:24 +00:00
Jakub Warmuz
9ecfecbc7a
Add missing docstring. 2015-06-02 09:19:51 +00:00
Jakub Warmuz
2ae9bf3d49
Use timedelta instead of now.month + 1 (partially fixes: #456).
`ValueError: day is out of range for month`
2015-06-02 07:14:01 +00:00
Jakub Warmuz
b420e2a1da
Fix --no-verify-ssl negation bug 2015-06-02 06:58:35 +00:00
Jakub Warmuz
cd7a5ec24a
Fix pylint 2015-06-02 01:01:29 +00:00
Jakub Warmuz
8fed612fef
Fix apache/nginx tests. 2015-06-02 00:40:33 +00:00
Jakub Warmuz
ed5e8df8d7
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt/constants.py
	letsencrypt/interfaces.py
2015-06-02 00:19:05 +00:00
Jakub Warmuz
c440b0354d
Fix typo 2015-06-02 00:16:24 +00:00
Jakub Warmuz
8fe6584336
Don't allow user supplied mod_ssl conf destination (fixes #451). 2015-06-02 00:14:10 +00:00
Jakub Warmuz
3fefd28080
Only configure --config-dir/--work-dir (rest dynamic). 2015-06-01 23:49:07 +00:00
Jacob Hoffman-Andrews
325f2ae4ad Merge pull request #463 from kuba/bugs/462
Split --test-mode into --no-verify-ssl and --dvsni-port (fixes #462).
2015-06-01 16:28:47 -07:00
schoen
97489f85fa Merge pull request #464 from kuba/verbose-help
Add help for the --verbose flag.
2015-06-01 14:47:48 -07:00
schoen
171e37894b Merge pull request #465 from kuba/reporter-cleanup
Reporter cleanup
2015-06-01 14:47:19 -07:00
Jakub Warmuz
5b957f27d6
Fix typo: config.port -> config.dvsni_port. 2015-06-01 21:40:22 +00:00
Jakub Warmuz
f5613ee074
Add help for the --verbose flag. 2015-06-01 21:08:42 +00:00
Jakub Warmuz
2929039cf4
Split --test-mode into --no-verify-ssl and --dvsni-port (fixes #462). 2015-06-01 20:56:58 +00:00
Jakub Warmuz
3ba41de2ba
Split Reporter priority constants (improves docs readability). 2015-06-01 19:16:58 +00:00
Jakub Warmuz
c4195c6cdf
Unify test docstrings. 2015-06-01 19:09:40 +00:00
Jakub Warmuz
e5dd4ba70c
Minor fixes for #453 and reporter API docs. 2015-06-01 19:09:39 +00:00
Seth Schoen
2027110512 Add a single performance-related TODO comment 2015-06-01 11:34:12 -07:00
schoen
569a70f6aa Merge pull request #453 from bradmw/notify
Reporter (tells user about important stuff)
2015-05-29 12:55:35 -07:00
schoen
d92a795b09 Merge pull request #452 from kuba/docker
Add Docker container badge in README
2015-05-29 11:59:29 -07:00
Brad Warren
4f7d83d274 Corrected comment in cli.py 2015-05-29 11:45:06 -07:00
Brad Warren
0558aa1c01 Merge remote-tracking branch 'upstream/master' into notify 2015-05-29 11:34:11 -07:00
Brad Warren
8bcc8f8024 Finished basic reporter 2015-05-29 11:33:11 -07:00
Jakub Warmuz
e520efe41e
Add continer badge in README 2015-05-29 11:24:11 +00:00
Jakub Warmuz
9ea5e20b44
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes
Conflicts:
	letsencrypt/cli.py
	letsencrypt/constants.py
	letsencrypt/interfaces.py
2015-05-29 07:42:24 +00:00
Seth Schoen
e15b7b4deb Merge branch 'master' into renewer_config_location
Conflicts:
	letsencrypt/cli.py
	letsencrypt/client.py
	letsencrypt/interfaces.py
2015-05-28 14:55:47 -07:00
Jakub Warmuz
71aa1a5348
Fix merge problems and pylint 2015-05-28 20:52:59 +00:00
Jakub Warmuz
eef1ce6cf5
Merge remote-tracking branch 'github/letsencrypt/master' into cli-config-fixes-2 2015-05-28 20:47:35 +00:00
Seth Schoen
3dbf47eb3f Merge branch 'renewer-cleanup' of https://github.com/kuba/lets-encrypt-preview 2015-05-28 12:36:32 -07:00
Jakub Warmuz
a3d452c112
storage.config_with_defaults 2015-05-28 19:27:30 +00:00
Jakub Warmuz
5547d13f12
Merge branch 'renewer-cleanup' into cli-config-fixes-2
Conflicts:
	letsencrypt/cli.py
	letsencrypt/client.py
	letsencrypt/interfaces.py
2015-05-28 19:07:58 +00:00
Jakub Warmuz
d01b17f1e2
IInstaller.deploy_cert docs/naming fixes 2015-05-28 18:31:06 +00:00
James Kasten
a7df8abb78 Merge pull request #448 from kuba/docs
Various docstring fixes.
2015-05-28 14:24:09 -04:00
Jakub Warmuz
8d1135c049
Private _AttrDict 2015-05-28 18:09:05 +00:00
Jakub Warmuz
c813efdce7
Merge obtain_certificate and _obtain_certificate 2015-05-28 18:07:49 +00:00
Jakub Warmuz
cc969fc406
Fix deploy_certificate (docs and use abspath) 2015-05-28 18:07:26 +00:00
Jakub Warmuz
2178315f8a
Various docstring fixes.
- Use r""" \* """
- transform plugins note to ..warning
- ' -> ` for cross-reference
- fix some "more than one target found for cross-reference" warnings
2015-05-28 15:17:55 +00:00
Jakub Warmuz
3809a817ea
Bump coverage 2015-05-28 08:02:43 +00:00
Jakub Warmuz
f798c117f7
assertEqual(False, ...) -> assertFalse 2015-05-28 08:01:44 +00:00
Jakub Warmuz
da8f3e19a4
Remove confusingly unused --enroll-autorenew. 2015-05-28 07:58:40 +00:00
Jakub Warmuz
a00dc88ad1
Fix renewer pr docstrings 2015-05-28 07:45:41 +00:00
Jakub Warmuz
81ac25f89c
Add API docs for renewer and storage 2015-05-28 07:45:41 +00:00
Jakub Warmuz
b75df9f4de
range -> xrange 2015-05-28 07:45:41 +00:00
Jakub Warmuz
74f089ae3b
Revert pre-renewer deploy_certificate API 2015-05-28 07:12:19 +00:00
Brad Warren
31fbcd46e0 Merge remote-tracking branch 'upstream/master' into notify 2015-05-27 22:39:46 -04:00
Brad Warren
0e1f6b24f3 Added basic notifier 2015-05-27 19:29:06 -04:00
Seth Schoen
b617305927 Inelegant approach to support --renewer-config-file command-line argument 2015-05-27 12:38:47 -07:00
Peter Eckersley
9bc9abecbe Merge pull request #404 from letsencrypt/renewer
Automatically renew certificates!
2015-05-26 15:56:11 -07:00
Seth Schoen
b2b70279c2 Merge branch 'kuba-test-mode' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-26 14:34:31 -07:00
Seth Schoen
f7718d14aa API documentation on obtain_and_enroll_cert 2015-05-22 14:42:19 -07:00
Seth Schoen
cdd365af9a Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-22 14:39:51 -07:00
Peter Eckersley
6283ced29c Allow 100 character lines when necessary. 2015-05-22 14:39:22 -07:00
Seth Schoen
1cddd0fba1 Use standard plugins interface in config serialization 2015-05-22 14:29:50 -07:00
Jacob Hoffman-Andrews
958b6b1048 Only check config if it is defined. 2015-05-22 13:57:41 -07:00
Jacob Hoffman-Andrews
1a5d6ba90d Use more verbose exception catch. 2015-05-22 13:16:55 -07:00
Jacob Hoffman-Andrews
8562496f82 Fixes from review comments. 2015-05-22 13:06:17 -07:00
Seth Schoen
5c6d833fc1 Merge branch 'master' into renewer 2015-05-22 12:49:03 -07:00
schoen
149cd56c0b Merge pull request #430 from Hainish/vagrantfile-fix
Fix Debian dependencies, and add the required openssl header file
2015-05-22 12:47:21 -07:00
William Budington
93c09f19be Install git-core for vagrant (smaller dependency) and add comment 2015-05-22 11:23:18 -07:00
Jakub Warmuz
53d65d005e
CLI: improve plugins help messages, refactor code 2015-05-22 08:43:58 +00:00
Jakub Warmuz
f00b674131
Move --cert-path and --chain-path from global IConfig to subparsers. 2015-05-22 08:06:32 +00:00
Jakub Warmuz
ae6c7cb936
--le-vhost-ext -> --apache-le-vhost-ext 2015-05-22 07:28:21 +00:00
Jacob Hoffman-Andrews
969c2c052d Re-remove server_url. 2015-05-21 23:44:13 -07:00
Jacob Hoffman-Andrews
d2425e5283 Merge branch 'master' into kuba-test-mode 2015-05-21 23:43:30 -07:00
Jacob Hoffman-Andrews
ef8daba47f Merge pull request #400 from kuba/test-mode
Test mode and --server with scheme
2015-05-21 23:41:00 -07:00
Jakub Warmuz
7495145563
Merge remote-tracking branch 'github/letsencrypt/master' into test-mode
Conflicts:
	letsencrypt/network2.py
2015-05-22 06:26:16 +00:00
Jakub Warmuz
8dac7daf87
config.server_url -> config.server 2015-05-22 06:21:53 +00:00
Jacob Hoffman-Andrews
e1e6135313 Use a different port for test mode. 2015-05-21 19:08:05 -07:00
Jacob Hoffman-Andrews
233d1fb7f6 Merge branch 'master' into kuba-test-mode 2015-05-21 19:01:03 -07:00
Jacob Hoffman-Andrews
424acfe16e Fixes to running on command line.
Use cert_dir instead of cert_path
Restore server_url
When creating a unique file, only loop for EEXISTS, not other OS errors like
  permission denied.
Pass uid explicitly to make_or_verify_dir.
2015-05-21 18:58:40 -07:00
William Budington
eebe710884 Vagrantfile: add git to requirements, and install from requirements.txt and dev deps 2015-05-21 16:41:37 -07:00
Seth Schoen
b8a024b65b More generality for renewer config. (Still no CLI flags.) 2015-05-21 16:33:38 -07:00
Seth Schoen
536e99dd40 Merge branch 'master' into renewer 2015-05-21 15:19:03 -07:00
Seth Schoen
fcd8670baf Merge branch 'master' into renewer 2015-05-21 13:06:12 -07:00
Jacob Hoffman-Andrews
1ca6016bb0 Merge branch 'test-mode' of https://github.com/kuba/lets-encrypt-preview into kuba-test-mode
Conflicts:
	letsencrypt/network2.py
2015-05-21 12:28:39 -07:00
William Budington
73021673ff Fix for Vagrantfile: pip install -e ., so we don't break on git requirement 2015-05-20 18:56:07 -07:00
schoen
ead60d8f4b Merge pull request #437 from Hainish/kuba-docker
Kuba docker
2015-05-19 18:00:32 -07:00
William Budington
2fe8a75200 Use a discrete path for venv in docker, rather than /opt/letsencrypt.
This is useful for the docker development container, which we will want
venv to persist for across runs.
2015-05-19 17:39:53 -07:00
Seth Schoen
58156a29d3 Fix typos 2015-05-19 17:06:06 -07:00
schoen
1681fbea3a Merge pull request #435 from kuba/boulder-compat
Spec and Boulder compatibility fixes
2015-05-19 17:04:07 -07:00
William Budington
e4e4c69f36 sharng docker-compose.yml to add two separate container specifications:
one for development (that mounts the host git root to
/opt/letsencrypt/src) and one for production (that doesn't).
2015-05-19 16:53:31 -07:00
William Budington
6a7e3438a9 Adding self as maintainer 2015-05-19 16:43:51 -07:00
William Budington
1b1763b011 Removing cruft from Dockerfile which copies entire project working directory 2015-05-19 16:41:32 -07:00
William Budington
ea667744f5 Being more verbose in explanation of EXPOSE instruction 2015-05-19 16:39:54 -07:00
James Kasten
36bb03df63 Merge pull request #427 from diracdeltas/fix/nginxparser-issues
fix some nginxparser issues
2015-05-19 15:12:55 -07:00
Jakub Warmuz
e7cf4792b3
Fix typos 2015-05-19 22:01:01 +00:00
Jakub Warmuz
5a22ff17d0
Dockerfile: debian.sh -> ubuntu.sh 2015-05-19 21:49:57 +00:00
Seth Schoen
9571c7288c Merge branch 'kuba-crypto_util_100' into renewer
Conflicts:
	letsencrypt/crypto_util.py
	letsencrypt/tests/crypto_util_test.py
2015-05-19 14:10:24 -07:00
Jakub Warmuz
3c0ce923b2
Dockerfile: use ubuntu:trusty (based on review feedback). 2015-05-19 20:51:11 +00:00
Jakub Warmuz
ac0868b6de
acme.messages2.Error title is omitempty 2015-05-19 20:13:55 +00:00
Jakub Warmuz
cd6b9bc9c7
Fix coverage for acme.messages2.Error 2015-05-19 20:09:11 +00:00
Jakub Warmuz
0018bc0500
Error: typ/title no omitempty 2015-05-19 19:50:00 +00:00
Jakub Warmuz
41115bfc77
Spec and Boulder compatibility fixes.
Relevant acme-spec:
- https://github.com/letsencrypt/acme-spec/issues/127
- https://github.com/letsencrypt/acme-spec/pull/119
- https://github.com/letsencrypt/acme-spec/issues/98
- https://github.com/letsencrypt/acme-spec/issues/92

Relevant boulder:
- https://github.com/letsencrypt/boulder/pull/170
- https://github.com/letsencrypt/boulder/issues/128
2015-05-19 19:42:53 +00:00
Jakub Warmuz
2cadfdaae1
response might carry binary data, use repr() in network2 logging 2015-05-19 19:21:04 +00:00
Jakub Warmuz
c0acf8239d
network2: Log GET/POST uri 2015-05-19 19:19:29 +00:00
Jakub Warmuz
083bd8701b
get_sans_from_cert, 100% test coverage for crypto_util. 2015-05-19 14:14:34 +00:00
Seth Schoen
42b3e2180a Check latest, not current cert version. Fixes #423. 2015-05-18 16:50:46 -07:00
Seth Schoen
8f25241170 Introduce proper renewer config via constants.py 2015-05-18 15:57:12 -07:00
Seth Schoen
83b0e0a20e Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer
(Resolve conflicts due to get_sans branch merge.)

Conflicts:
	letsencrypt/crypto_util.py
	letsencrypt/tests/crypto_util_test.py
2015-05-18 15:07:29 -07:00
yan
24f9da5275 Add support and tests for some Nginx config edge cases
1. Match "if" statements
2. Allow special characters in nginx directives when enclosed in single or
   double quotes.
2015-05-18 17:58:28 -04:00
James Kasten
1ada2cab15 Merge pull request #417 from kuba/plugins
Plugins fixes (unhashable PluginEntryPoint, typo in examples)
2015-05-18 11:22:04 -07:00
James Kasten
064ec73903 Merge pull request #425 from kuba/tests
Do not depend on letsencrypt_apache in core
2015-05-18 11:15:45 -07:00
James Kasten
8b8ce85c59 Merge pull request #426 from jmdcal/patch-1
typeo
2015-05-18 10:42:42 -07:00
James Kasten
7c1248dba1 Merge pull request #399 from kuba/get_sans
get_sans_from_csr using pyOpenSSL
2015-05-18 10:28:55 -07:00
James Kasten
7416f3fbbc Merge pull request #419 from letsencrypt/fix_standalone_signal_race
Attempt to fix #378
2015-05-18 10:18:12 -07:00
James Kasten
77d4b5a28a Merge pull request #382 from kuba/test-dirs-chmods
Fix test dirs chmods errors.
2015-05-18 10:17:11 -07:00
confidential
b0d98edcfe typeo
fixing spelling
2015-05-18 09:58:14 -05:00
Jakub Warmuz
3fd4f2a94a
Do not depend on letsencrypt_apache in core tests 2015-05-17 07:52:25 +00:00
Seth Schoen
cd74e7f20d Merge branch 'master' into renewer 2015-05-16 23:53:01 -07:00
Seth Schoen
6f4212dcf1 Fix trivial documentation typo 2015-05-16 23:52:33 -07:00
Seth Schoen
52fefad693 Basic functionality of run/auth CLI verbs 2015-05-16 23:51:58 -07:00
Seth Schoen
0f64082f1d Document newly-added functions and methods 2015-05-16 21:27:06 -07:00
Seth Schoen
dd18040e47 Use getattr() instead of .__getattribute__() 2015-05-15 15:55:11 -07:00
Jakub Warmuz
74d6d4e0b3
Fix typo in examples 2015-05-15 19:53:41 +00:00
Jakub Warmuz
a941cf61b7
Merge remote-tracking branch 'github/letsencrypt/master' into test-dirs-chmods
Conflicts:
	letsencrypt_nginx/dvsni.py
2015-05-15 19:44:07 +00:00
Seth Schoen
6db7cb5210 Merge remote-tracking branch 'origin/master' into renewer 2015-05-15 12:10:14 -07:00
Jakub Warmuz
834691278e
Fix repr for PluginsRegistry (unhashable PluginEntryPoint).
(venv)root@le:~/lets-encrypt-preview# letsencrypt -vv auth
DEBUG:root:Logging level set at 10
Traceback (most recent call last):
File "/usr/lib/python2.7/logging/__init__.py", line 859, in emit
msg = self.format(record)
File "/usr/lib/python2.7/logging/__init__.py", line 732, in format
return fmt.format(record)
File "/usr/lib/python2.7/logging/__init__.py", line 471, in format
record.message = record.getMessage()
File "/usr/lib/python2.7/logging/__init__.py", line 335, in getMessage
msg = msg % self.args
File "/root/lets-encrypt-preview/letsencrypt/plugins/disco.py", line 219, in __repr__
self.__class__.__name__, set(self._plugins.itervalues()))
TypeError: unhashable type: 'PluginEntryPoint'
Logged from file cli.py, line 356
Traceback (most recent call last):
File "/root/lets-encrypt-preview/venv/bin/letsencrypt", line 9, in <module>
load_entry_point('letsencrypt==0.1', 'console_scripts', 'letsencrypt')()
File "/root/lets-encrypt-preview/letsencrypt/cli.py", line 356, in main
logging.debug("Discovered plugins: %r", plugins)
File "/usr/lib/python2.7/logging/__init__.py", line 1630, in debug
root.debug(msg, *args, **kwargs)
File "/usr/lib/python2.7/logging/__init__.py", line 1148, in debug
self._log(DEBUG, msg, args, **kwargs)
File "/usr/lib/python2.7/logging/__init__.py", line 1279, in _log
self.handle(record)
File "/usr/lib/python2.7/logging/__init__.py", line 1289, in handle
self.callHandlers(record)
File "/usr/lib/python2.7/logging/__init__.py", line 1329, in callHandlers
hdlr.handle(record)
File "/usr/lib/python2.7/logging/__init__.py", line 757, in handle
self.emit(record)
File "/root/lets-encrypt-preview/letsencrypt/log.py", line 40, in emit
for line in record.getMessage().splitlines():
File "/usr/lib/python2.7/logging/__init__.py", line 335, in getMessage
msg = msg % self.args
File "/root/lets-encrypt-preview/letsencrypt/plugins/disco.py", line 219, in __repr__
self.__class__.__name__, set(self._plugins.itervalues()))
TypeError: unhashable type: 'PluginEntryPoint'
2015-05-15 15:00:53 +00:00
James Kasten
e166c4159e Merge pull request #416 from kuba/tests
Small tests improvements
2015-05-14 22:26:10 -07:00
Seth Schoen
4a100490a1 Readability improvements for storage.py 2015-05-14 17:36:30 -07:00
Seth Schoen
9a144b46bc Remove TODO referring to obsolete .config feature 2015-05-14 17:03:20 -07:00
Seth Schoen
64d4e6249c Fix some PEP8 issues 2015-05-14 17:01:04 -07:00
Seth Schoen
2201e7944d Unit tests for le_util.unique_lineage_name() 2015-05-14 16:47:41 -07:00
Seth Schoen
ca4bece393 Attempt to fix #378 2015-05-14 15:36:51 -07:00
Seth Schoen
018201170c Fix indentation 2015-05-14 15:06:22 -07:00
Seth Schoen
183b49fbc2 Consolidate and shorten some renewer tests 2015-05-14 14:59:24 -07:00
Jakub Warmuz
0bc5791a55
More tests for cli.py 2015-05-14 21:44:36 +00:00
Seth Schoen
c951429895 Separate stdlib imports from third-party 2015-05-14 14:36:43 -07:00
Seth Schoen
866d236249 Style cleanups in renewer test 2015-05-14 14:26:01 -07:00
Jakub Warmuz
82bd808ab3
100% coverage for network2_test 2015-05-14 21:16:38 +00:00
Jakub Warmuz
484fd8fe9e
Fix randomly created mock_dir 2015-05-14 21:13:12 +00:00
Jakub Warmuz
a2767d30a1
Remove dead code 2015-05-14 21:12:58 +00:00
Seth Schoen
87592d64a9 Moving code outside of try block 2015-05-14 12:42:48 -07:00
Seth Schoen
fb8b2f1415 Moving code outside of try block 2015-05-14 12:40:03 -07:00
Seth Schoen
e612d52693 Indentation fix 2015-05-14 12:39:57 -07:00
Seth Schoen
9556203ae9 Explicit "is None" 2015-05-14 12:28:24 -07:00
Seth Schoen
e469ae4ed8 Remove magic constant 2015-05-14 12:27:52 -07:00
Seth Schoen
ff41397ccf Consolidate redundant tests in a loop 2015-05-14 12:27:38 -07:00
Seth Schoen
b8fef70bf5 Making clear that other files don't actually exist 2015-05-14 12:12:26 -07:00
Seth Schoen
c5a44f3e39 Removing magic constants 2015-05-14 12:12:16 -07:00
Seth Schoen
56b71e3b32 Replacing magic constant 2015-05-14 12:07:39 -07:00
Seth Schoen
d443fd9074 Explicit "is not None" 2015-05-14 12:06:21 -07:00
Seth Schoen
fc81f18864 Clarify that these test files never exist 2015-05-14 11:53:48 -07:00
Seth Schoen
995759abad pragma: no cover for test main function 2015-05-14 11:49:38 -07:00
Seth Schoen
93953604a2 Don't redefine ALL_FOUR 2015-05-14 11:48:30 -07:00
Seth Schoen
35308bfc7d Unify implementation of notbefore and notafter 2015-05-14 11:45:40 -07:00
Seth Schoen
af767f917b Unit tests for notify.py and get_sans_from_cert 2015-05-13 22:35:00 -07:00
Seth Schoen
0a62bd6ebe Reorganize and shorten some renewer tests 2015-05-13 14:29:19 -07:00
Seth Schoen
2c6cfe3f81 Cleanup to get rid of scripts directory 2015-05-13 13:18:37 -07:00
Seth Schoen
6f6ee897f8 Changes to fix problems after reorganization 2015-05-13 12:37:04 -07:00
Seth Schoen
5d22925108 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-13 12:11:04 -07:00
Seth Schoen
82a4c61eff Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer
Conflicts:
	letsencrypt/client.py
2015-05-13 12:10:36 -07:00
James Kasten
c57443812b Merge pull request #409 from kuba/pkgs_sep_prep
Fix references to letsencrypt.client
2015-05-13 11:59:50 -07:00
Jakub Warmuz
e06c0cbbf7
Revert "Bump coverage"
This reverts commit da53813770.
2015-05-12 22:09:45 +00:00
Jakub Warmuz
787c64c546
Fix references to letsencrypt.client 2015-05-12 22:08:00 +00:00
Jakub Warmuz
514d319662
Use debian:jessie as docker base image
le                  latest              e1f2e8ce3a0e        14 minutes ago      780.2 MB
vs
  le                  latest              d3276dd3976c        About a minute ago   393.6 MB

where:
  buildpack-deps      jessie              ecff3a5a9760        12 days ago          677.4 MB
  debian              jessie              41b730702607        13 days ago          125.1 MB
2015-05-12 22:01:20 +00:00
Jakub Warmuz
125ba6449e
Dockerfile: copy dep modules dirs 2015-05-12 21:35:43 +00:00
Jakub Warmuz
aeef964fb6
Merge remote-tracking branch 'github/letsencrypt/master' into get_sans 2015-05-12 21:27:10 +00:00
Jakub Warmuz
da53813770
Bump coverage 2015-05-12 21:18:45 +00:00
Jakub Warmuz
ac9f97100c
Merge remote-tracking branch 'github/letsencrypt/master' into test-mode
Conflicts:
	letsencrypt/tests/configuration_test.py
	letsencrypt/tests/network2_test.py
2015-05-12 21:16:56 +00:00
Jakub Warmuz
38d2f8613d
Merge remote-tracking branch 'github/letsencrypt/master' into docker 2015-05-12 21:07:22 +00:00
Jakub Warmuz
bb94952830
Merge remote-tracking branch 'github/letsencrypt/master' into test-dirs-chmods
Conflicts:
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/foo.conf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/nginx.conf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/nginx.new.conf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/server.conf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/sites-enabled/default
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/sites-enabled/example.com
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf
	letsencrypt/client/plugins/nginx/tests/testdata/foo.conf
	letsencrypt/client/plugins/nginx/tests/testdata/nginx.conf
	letsencrypt/client/plugins/nginx/tests/testdata/nginx.new.conf
	letsencrypt/client/plugins/nginx/tests/testdata/server.conf
	letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/default
	letsencrypt/client/plugins/nginx/tests/testdata/sites-enabled/example.com
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params
	letsencrypt/client/plugins/nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf
	letsencrypt_apache/tests/util.py
	letsencrypt_nginx/tests/testdata/foo.conf
	letsencrypt_nginx/tests/testdata/nginx.conf
	letsencrypt_nginx/tests/testdata/nginx.new.conf
	letsencrypt_nginx/tests/testdata/server.conf
	letsencrypt_nginx/tests/testdata/sites-enabled/default
	letsencrypt_nginx/tests/testdata/sites-enabled/example.com
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params
	letsencrypt_nginx/tests/testdata/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf
	letsencrypt_nginx/tests/util.py
2015-05-12 21:06:17 +00:00
James Kasten
d6e554b1fd Merge pull request #401 from kuba/pkgs_sep_prep
Repo reorganisation
2015-05-12 13:41:27 -07:00
Jakub Warmuz
cf9a4b6012
Remove unused import 2015-05-12 20:15:10 +00:00
Jakub Warmuz
55b619853b
Merge remote-tracking branch 'github/letsencrypt/master' into pkgs_sep_prep
Conflicts:
	letsencrypt/continuity_auth.py
	letsencrypt_nginx/configurator.py
	letsencrypt_nginx/dvsni.py
	letsencrypt_nginx/tests/configurator_test.py
	letsencrypt_nginx/tests/dvsni_test.py
2015-05-12 20:13:50 +00:00
James Kasten
eb2f019144 Merge pull request #405 from diracdeltas/doc/nginx-dvsni
Update docs to reflect nginx support
2015-05-12 12:27:28 -07:00
yan
f4144f0017 Update docs to reflect nginx support 2015-05-12 12:05:17 -07:00
Seth Schoen
587c81ebb4 Merge remote-tracking branch 'origin' into renewer 2015-05-12 11:30:01 -07:00
James Kasten
aed3c7f877 Merge pull request #396 from bradmw/master
Minimal Proof of Possession Challenge
2015-05-12 10:44:17 -07:00
Brad Warren
27511d4822 Added no cover line to unittest.main() 2015-05-12 12:42:59 -04:00
Brad Warren
de974ca51b Merge remote-tracking branch 'upstream/master' 2015-05-12 11:57:17 -04:00
Brad Warren
a496179f74 Incorporated feedback and added extra error checking to POP 2015-05-12 11:56:26 -04:00
Seth Schoen
b3029a5436 Merge remote-tracking branch 'origin' into renewer 2015-05-11 20:54:26 -07:00
James Kasten
8229c72b9c Merge pull request #387 from diracdeltas/feature/nginx-dvsni
dvsni support for nginx
2015-05-11 20:21:20 -07:00
Seth Schoen
5a85e1e46e Changes to satisfy pylint 2015-05-11 15:02:02 -07:00
Seth Schoen
db93b7b3c6 Complete test coverage for renewer.py/storage.py 2015-05-11 13:50:07 -07:00
Seth Schoen
38cdd422d2 Improve test coverage 2015-05-11 12:00:54 -07:00
Seth Schoen
6f56dc1418 Again require RenewableCert to be given a ConfigObj 2015-05-11 11:49:20 -07:00
Seth Schoen
06a3f54c92 Updated target for @mock.patch in new storage.py 2015-05-11 11:38:50 -07:00
Seth Schoen
2fb684d784 Fix some existing tests 2015-05-11 11:20:53 -07:00
Seth Schoen
3fe8f4ce03 Merge remote-tracking branch 'origin/master' into renewer 2015-05-10 22:46:16 -07:00
Seth Schoen
1e39b72ab7 Updating TODO items 2015-05-10 22:45:35 -07:00
Seth Schoen
b235dc4c80 Work in progress: make renewer renew 2015-05-10 22:43:58 -07:00
James Kasten
65cda7efbd Merge pull request #403 from kuba/bugs/402
Cover tests (fixes #402).
2015-05-10 13:25:59 -07:00
Jakub Warmuz
c7aff67132
Merge branch 'bugs/402' into pkgs_sep_prep 2015-05-10 19:18:27 +00:00
Jakub Warmuz
53fbedda53
100% acme coverage 2015-05-10 19:16:31 +00:00
Jakub Warmuz
b4c747a283
Merge branch 'bugs/402' into pkgs_sep_prep
Conflicts:
	letsencrypt/tests/client_test.py
	tox.ini
2015-05-10 17:56:41 +00:00
Jakub Warmuz
99b2003e61
Cover tests (fixes #402).
1. --cover-tests, to make sure every test is run, helped to find
   broken determine_account tests
2. fix determine_account test
3. unittest.main()  # pragma: no cover
4. bump coverage

coveralls.io always reported higher coverage, because it also looked
at test files.
2015-05-10 17:38:26 +00:00
Jakub Warmuz
5fbc5cee2e
Add tox.cover.sh for proper coveralls experience. 2015-05-10 16:53:22 +00:00
Seth Schoen
30a4f72649 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-10 09:19:51 -07:00
Seth Schoen
f0ee6f1257 Make saving files, recording configurator names work 2015-05-10 09:18:55 -07:00
Jakub Warmuz
edce116fed
Merge remote-tracking branch 'github/letsencrypt/master' into docker 2015-05-10 15:31:19 +00:00
Jakub Warmuz
734868b015
Merge remote-tracking branch 'github/letsencrypt/master' into pkgs_sep_prep 2015-05-10 15:29:04 +00:00
James Kasten
1ce1363288 Merge pull request #376 from kuba/cli
Plugins discovery and new CLI with subcommands
2015-05-10 08:09:10 -07:00
Jakub Warmuz
a5e927c657
name_with_description -> description_with_name 2015-05-10 15:04:44 +00:00
Jakub Warmuz
771ddf0aaf
Update docs for the new CLI 2015-05-10 14:53:59 +00:00
Jakub Warmuz
973672761d
.dockerignore venv and docs 2015-05-10 14:26:33 +00:00
James Kasten
2bfd4d7b77 Merge pull request #397 from kuba/client-fetch_chain-bugs
Fix client to work with cert_chain_uri, "is (not) None" fixes
2015-05-10 07:04:41 -07:00
Jakub Warmuz
0604a393f0
examples: update restified 2015-05-10 13:11:51 +00:00
Jakub Warmuz
6f3b03db77
Fix doumentation for bundled packages 2015-05-10 13:03:48 +00:00
Jakub Warmuz
b103aae808
tox: split test/cover per pkg 2015-05-10 12:52:40 +00:00
Jakub Warmuz
41e86df252
Move letsencrypt.client to letsencrypt 2015-05-10 12:32:05 +00:00
Jakub Warmuz
d408ec5a95
Move plugins to top-level 2015-05-10 12:26:54 +00:00
Jakub Warmuz
3a6bd7123d
Move acme to top-level 2015-05-10 12:26:17 +00:00
Jakub Warmuz
81e8ba7daf
Update MANIFEST.in 2015-05-10 11:12:03 +00:00
Jakub Warmuz
e8eae2dab2
find_packages() in setup.py 2015-05-10 11:08:19 +00:00
Jakub Warmuz
dc0f78dd15
Merge remote-tracking branch 'github/letsencrypt/master' into get_sans
Conflicts:
	letsencrypt/client/crypto_util.py
	letsencrypt/client/tests/crypto_util_test.py
	setup.py
2015-05-10 09:40:25 +00:00
Jakub Warmuz
532d155b1c
get_sans_from_csr using pyOpenSSL 2015-05-10 09:36:18 +00:00
Seth Schoen
a9d6735bce Update for changed new_lineage call prototype 2015-05-09 21:24:38 -07:00
Seth Schoen
eaa0bae45f We also need .as_pem() for the chain in this method 2015-05-09 21:01:34 -07:00
Jakub Warmuz
29146d1225 Fix client to work with cert_chain_uri, "is (not) None" fixes 2015-05-09 20:53:02 -07:00
Seth Schoen
b30a8fbe90 Merge branch 'cli' of https://github.com/kuba/lets-encrypt-preview into renewer 2015-05-09 20:46:30 -07:00
Seth Schoen
d98edd1394 Merge branch 'cli' of https://github.com/kuba/lets-encrypt-preview into renewer
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/interfaces.py
	letsencrypt/scripts/main.py

This merge only addresses the "run" verb; the other client verbs still
need work to address the way they integrate with the renewal database.
2015-05-09 20:45:17 -07:00
Jakub Warmuz
87c00ab259
Test mode (no certificate validation). 2015-05-09 20:01:34 +00:00
Jakub Warmuz
5914b7a59a
--server with scheme:// 2015-05-09 19:53:31 +00:00
Jakub Warmuz
42e7ec4bd2
CLI: use_curses -> text_mode
Previously, the --help output seemed to be broken:

-t, --text            Use the text output instead of the curses UI.
                      (default: True)
2015-05-09 19:51:02 +00:00
Jakub Warmuz
29fdde5f5f
Dockerfile: set PATH 2015-05-09 18:50:40 +00:00
Jakub Warmuz
a3c8753dcf
Fix client to work with cert_chain_uri, "is (not) None" fixes 2015-05-09 15:09:29 +00:00
Jakub Warmuz
2141126608
clean up disco logging 2015-05-09 07:36:52 +00:00
Jakub Warmuz
e415a63d1f
PluginsRegistry.plugins -> PluginsRegistry._plugins 2015-05-09 07:33:50 +00:00
Jakub Warmuz
75a7b7605b
PluginsRegistry.find_init 2015-05-09 07:33:50 +00:00
Jakub Warmuz
e197f156a7
choose_plugin can return None 2015-05-09 05:47:09 +00:00
Brad Warren
e3d95c5a68 Final changes 2015-05-08 23:43:06 -04:00
Brad Warren
ae6b13cd5b Finished basic POP challenge with tests 2015-05-08 23:14:04 -04:00
yan
dfb94613bf Add nginx server block for target_name if one doesn't exist 2015-05-08 17:30:34 -07:00
Seth Schoen
2ee1ab05b3 Work in progress toward renewer enrollment 2015-05-08 15:00:35 -07:00
Jakub Warmuz
fce08ea30c
Use CLI_DEFAULTS in plugins 2015-05-08 21:32:13 +00:00
yan
f7116a7388 Reduce logging severity for unparseable config files 2015-05-08 12:36:32 -07:00
yan
1533eea351 Start nginx if it's not already running 2015-05-08 12:34:48 -07:00
Brad Warren
fbab449694 Finished basic POP challenge 2015-05-08 15:17:29 -04:00
yan
4dc566a871 Update vhost object when making nginx SSL block 2015-05-07 18:30:50 -07:00
Seth Schoen
953b57453e Test for creating renewer db dirs that don't exist 2015-05-07 15:25:02 -07:00
Seth Schoen
28438dd111 Changes to get successful enrollment in renewer DB 2015-05-07 15:07:04 -07:00
Seth Schoen
3b2ffff154 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-07 12:04:11 -07:00
James Kasten
ae31b81e7a Merge pull request #393 from Ter0/patch-1
Fix typo in authenticator description
2015-05-07 11:59:41 -07:00
Chris Johns
32b7705080 Fix typo in authenticator description 2015-05-06 17:24:34 +01:00
Jakub Warmuz
9a0073fff5
docker: use quay.io, move quick start section to the top 2015-05-06 09:33:56 +00:00
Jakub Warmuz
ca00cce22c
Merge remote-tracking branch 'github/letsencrypt/master' into cli
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/scripts/main.py
2015-05-06 09:13:36 +00:00
yan
cf48791686 Address @kuba review comments 2015-05-05 23:57:40 -07:00
yan
6f4af62f61 Add dvsni tests 2015-05-05 23:40:40 -07:00
yan
23d27f4659 Bump min nginx version to 0.8.48
We are assuming that if a server_name isn't specified, it matches the empty
string. Prior to 0.8.48, it would match the machine's hostname.
2015-05-05 23:40:40 -07:00
James Kasten
a0b410f460 Merge pull request #389 from letsencrypt/separate_keys
Separate keys
2015-05-05 18:29:09 -07:00
James Kasten
94d8a1ca01 update documentation of obtain_certificate 2015-05-05 14:54:15 -07:00
James Kasten
394e18c4c6 use certificate_key throughout 2015-05-05 14:43:00 -07:00
James Kasten
73679a4e85 generate a separate key for the certificate 2015-05-05 14:15:48 -07:00
Seth Schoen
5fe9c079a0 Merge remote-tracking branch 'origin' into renewer 2015-05-05 12:49:24 -07:00
James Kasten
84cae41b38 Merge pull request #388 from kuba/acme
More readable message2.Error exceptions + small test.
2015-05-05 12:38:52 -07:00
Jakub Warmuz
6580d3a85b
Add test for ChallengeBody proxy behaviour 2015-05-05 19:10:51 +00:00
Jakub Warmuz
3c645a9916
messages2.Error.__str__ (more readable exceptions)
This partially fixes #349.
2015-05-05 19:09:57 +00:00
Jakub Warmuz
b6b86e44ce
Fix typo 2015-05-05 10:47:36 +00:00
Jakub Warmuz
d0b63a3500
nit: EOF newline 2015-05-05 08:40:05 +00:00
Jakub Warmuz
64a00d37bb
Update docker setup.
Changes:
- uses debian:jessie as base image (more lightweight)
- .dockerignore .git/.tox to speed up build process considerably
- more caching-aware Dockerfile
- copy current directory instead of git cloning the repo inside the container
- /etc/letsencrypt and /var/lib/letsencrypt volumes;
  no need for "if os.environ.get" hack

bootstrap script for debian had to be adjusted, as lsb_release is not
present in debian:jessie image.
2015-05-05 08:26:23 +00:00
Seth Schoen
b0dfea33c6 Remove a debug print statement 2015-05-04 16:33:17 -07:00
Seth Schoen
45541a3a23 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-04 15:59:14 -07:00
Jakub Warmuz
3a0db7efa1
Merge remote-tracking branch 'github/letsencrypt/master' into docker
Conflicts:
	letsencrypt/client/client.py
2015-05-04 21:22:38 +00:00
Jakub Warmuz
4c871b57c8
Remove a bit of nginx code dedup from apache 2015-05-04 15:36:00 +00:00
Jakub Warmuz
5863323eb0
PluginEntryPoint.description, show in CLI 2015-05-04 14:30:02 +00:00
Jakub Warmuz
aa6984e310
Attempt at cleaning {cert,chain}_path mess 2015-05-04 14:29:32 +00:00
Jakub Warmuz
c185480ae9
setup.cfg aliases don't work with pip 2015-05-04 14:02:03 +00:00
Jakub Warmuz
803bb9595c
Fix test dirs chmods errors.
Stragely, when run on digitalocean jessie x64 droplet (as root), the
following error were produced before this fix:

======================================================================
ERROR: test_add_name_vhost (letsencrypt.client.plugins.apache.tests.configurator_test.TwoVhost80Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/root/lets-encrypt-preview/letsencrypt/client/plugins/apache/tests/configurator_test.py", line 35, in setUp
self.ssl_options)
File "/root/lets-encrypt-preview/letsencrypt/client/plugins/apache/tests/util.py", line 76, in get_apache_configurator
version)
File "/root/lets-encrypt-preview/letsencrypt/client/plugins/apache/configurator.py", line 96, in __init__
self.verify_setup()
File "/root/lets-encrypt-preview/letsencrypt/client/plugins/apache/configurator.py", line 938, in verify_setup
self.config.config_dir, constants.CONFIG_DIRS_MODE, uid)
File "/root/lets-encrypt-preview/letsencrypt/client/le_util.py", line 37, in make_or_verify_dir
"permissions or owner" % directory)
LetsEncryptClientError: /tmp/tmp1wYWIMconfig exists, but does not have the proper permissions or owner
2015-05-04 13:48:18 +00:00
Jakub Warmuz
73ac2e36cc
Fix plugins command 2015-05-04 12:47:08 +00:00
Jakub Warmuz
f38911eb10
Actually install ConfigArgParse@python2.6 branch 2015-05-04 12:04:27 +00:00
Jakub Warmuz
a2df245567
Temporary fox for ConfigArgParse#17 2015-05-04 11:56:09 +00:00
Jakub Warmuz
8ae6a60fba
pep8 2015-05-04 08:26:08 +00:00
Jakub Warmuz
1c75100f79
Merge remote-tracking branch 'github/letsencrypt/master' into cli
Conflicts:
	letsencrypt/client/plugins/apache/configurator.py
2015-05-04 08:14:19 +00:00
Jakub Warmuz
9e7918fc75
Test CLI plugins command 2015-05-04 08:10:52 +00:00
Jakub Warmuz
b4f99df798
Full coverage and lint for display.ops 2015-05-03 22:15:30 +00:00
James Kasten
e4ac29ef81 Merge pull request #380 from kuba/boulder-130-fixed
boulder#130 fixed
2015-05-03 13:06:32 -07:00
Jakub Warmuz
0216ea3f26
boulder#130 fixed 2015-05-03 19:44:53 +00:00
Jakub Warmuz
138a9e9f01
Full coverage and lint for disco/disco_test 2015-05-03 14:28:23 +00:00
Jakub Warmuz
1878db3416
More coverage for plugins.disco 2015-05-03 13:21:36 +00:00
Jakub Warmuz
b600e2d270
PLuginsRegistry: verify, prepare, misconfigured, available. 2015-05-03 12:38:53 +00:00
Jakub Warmuz
595230fd8e
PluginsEntryPoint.prepare, move pick_* to display_ops 2015-05-03 07:56:49 +00:00
Jakub Warmuz
2ffc3c37cb
Fix missing comma 2015-05-02 18:00:57 +00:00
James Kasten
93cdad120d Merge pull request #375 from kuba/apache-no-sudo
Don't use sudo in apache plugin
2015-05-02 09:17:43 -07:00
Jakub Warmuz
8a5be3ee3a
Remove determine_{authenticator,installer} 2015-05-02 13:14:03 +00:00
Jakub Warmuz
bd45d5ceea
constants.CLI_DEFAULTS / flag_default() 2015-05-02 12:38:33 +00:00
Jakub Warmuz
9c6bb5e63c
Tests and lint for plugins disco 2015-05-02 12:18:57 +00:00
Jakub Warmuz
fc059b6269
Add docs for plugins.common and disco 2015-05-02 12:18:57 +00:00
Jakub Warmuz
5672916cb2
Tests and lint for plugins.common 2015-05-02 11:19:46 +00:00
Jakub Warmuz
140ca2b4d0
Merge remote-tracking branch 'github/letsencrypt/master' into cli
Conflicts:
	letsencrypt/client/plugins/standalone/tests/authenticator_test.py
2015-05-02 08:56:35 +00:00
Jakub Warmuz
c9a7172388
Somewhat usable CLI+disco 2015-05-02 08:53:06 +00:00
Jakub Warmuz
7a4d37e320
Don't use sudo in apache plugin. 2015-05-02 08:08:11 +00:00
Jakub Warmuz
19cff00835
common.Plugin (with .conf(var), PluginEntryPoint, PluginRegistry 2015-05-02 07:01:44 +00:00
Seth Schoen
bdcb94a2b5 pylint seems strict about the local variable count 2015-05-01 21:57:22 -07:00
Seth Schoen
40091cf60e Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-01 21:47:47 -07:00
Seth Schoen
eeb625063b Changes to pass pylint 2015-05-01 18:38:04 -07:00
Jakub Warmuz
17e8ddcb5c
Assert CLI test (--help) raises SystemExit 2015-05-01 20:19:31 +00:00
Jakub Warmuz
cc32eeb7cf
Merge remote-tracking branch 'github/letsencrypt/master' into cli
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/constants.py
	letsencrypt/client/interfaces.py
	letsencrypt/client/tests/client_test.py
	letsencrypt/scripts/main.py
	setup.py
2015-05-01 20:17:30 +00:00
James Kasten
828e09543b Merge pull request #368 from kuba/bugs/273
rsa256_key.pem -> jose/testdata/rsa512_key.pem (fixes #273)
2015-05-01 12:32:03 -07:00
James Kasten
963c95dfe0 Merge pull request #366 from kuba/constants-cleanup
Move relevant constants to acme module.
2015-05-01 12:23:25 -07:00
Jakub Warmuz
c838faffd8
Merge remote-tracking branch 'github/letsencrypt/master' into bugs/273
Conflicts:
	letsencrypt/client/plugins/standalone/tests/authenticator_test.py
2015-05-01 19:18:19 +00:00
Jakub Warmuz
57ae12745d
Merge remote-tracking branch 'github/letsencrypt/master' into constants-cleanup
Conflicts:
	letsencrypt/client/constants.py
2015-05-01 19:10:00 +00:00
James Kasten
fcb774f71b Merge pull request #367 from kuba/bugs/336
Call record.getMessage() in DialogHandler (fixes #336)
2015-05-01 12:08:21 -07:00
Seth Schoen
996b598eb3 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview into renewer 2015-05-01 11:49:49 -07:00
James Kasten
1e62579688 Merge pull request #362 from letsencrypt/boulder
Boulder
2015-05-01 11:46:22 -07:00
Jakub Warmuz
5b762e51be
Update ACME docs (protocol version info) 2015-05-01 10:55:26 +00:00
Jakub Warmuz
0845d82f65
Update Configuration test 2015-05-01 10:24:48 +00:00
Jakub Warmuz
8c43404015
pep8 2015-05-01 10:10:04 +00:00
Jakub Warmuz
9b5ea88abd
acme-spec#93 solved, ref boulder#130, acme-spec#110 2015-05-01 10:09:56 +00:00
Jakub Warmuz
0cb012a9fd
Configuration: server_url, server_path 2015-05-01 10:08:33 +00:00
Jakub Warmuz
54955009eb
constants.CONFIG_DIRS_MODE, fix #362 config dir bug 2015-05-01 10:07:32 +00:00
James Kasten
4d7f67684d Move eula to registration 2015-04-30 20:55:26 -07:00
James Kasten
5ba23a6047 fixes to address comments 2015-04-30 20:01:32 -07:00
James Kasten
f11f5bca73 address comments 2015-04-30 18:35:49 -07:00
James Kasten
84fd90841c Merge branch 'boulder' of https://github.com/kuba/lets-encrypt-preview into kuba-boulder 2015-04-30 17:57:19 -07:00
Jakub Warmuz
18a1d01d8f
Ref to letsencrypt/boulder#130 2015-04-29 19:49:24 +00:00
Jakub Warmuz
3ba8acc57e
Ref to letsencrypt/boulder#128 2015-04-29 09:15:31 +00:00
Jakub Warmuz
79b0ed5cd3
log act_cert_path 2015-04-29 08:23:42 +00:00
Jakub Warmuz
ff569084f8
Fix empty email problem, EMAIL_REGEX = re.compile(...), pep8 2015-04-29 08:21:16 +00:00
Jakub Warmuz
659e07c5b3
Add comment, split too long line 2015-04-28 20:06:50 +00:00
Jakub Warmuz
106d2bfbbe
Explicit errors for too small key / only public 2015-04-28 20:05:14 +00:00
Jakub Warmuz
1ea5fbdf9e
rsa256_key.pem -> jose/testdata/rsa512_key.pem (fixees #273) 2015-04-28 19:58:58 +00:00
Jakub Warmuz
319932bed5
Call record.getMessage() in DialogHandler (fixes #336) 2015-04-28 18:13:34 +00:00
Jakub Warmuz
bf5d132582
Move relevant constants to acme module. 2015-04-28 17:51:46 +00:00
Jakub Warmuz
5efdda0922
Fix some of #362 nitpicks 2015-04-28 11:31:14 +00:00
James Kasten
752b3b687f cleanup 2015-04-27 14:59:44 -07:00
James Kasten
bdcf8fc91e remove disable pylint from file 2015-04-27 13:14:39 -07:00
James Kasten
d6026d7573 Add todo for obtain certificate. 2015-04-27 13:07:13 -07:00
James Kasten
0371838a91 more unit tests/better flow 2015-04-27 12:42:50 -07:00
James Kasten
ee2e0948f4 fix py26 2015-04-24 03:38:49 -07:00
James Kasten
016e10f415 100% test coverage, account, auth_handler 2015-04-23 19:12:15 -07:00
James Kasten
1d8281d15d Merge pull request #359 from letsencrypt/fix_nginx_default_vhost
fix default symlink
2015-04-23 14:11:31 -07:00
James Kasten
cdfdee2ebc Merge branch 'fix_nginx_default_vhost' into boulder 2015-04-23 14:08:09 -07:00
James Kasten
4288ece394 fix default symlink 2015-04-23 14:01:21 -07:00
James Kasten
36f3cdeddc fix/add tests 2015-04-23 13:57:35 -07:00
James Kasten
cb0aca6727 Merge branch 'master' into boulder 2015-04-22 23:19:12 -07:00
James Kasten
12899d0c38 unittest and lint cleanup 2015-04-22 23:17:53 -07:00
James Kasten
95ba2730f1 start of tests for auth_handler 2015-04-22 16:27:54 -07:00
Seth Schoen
e4f59e60e1 import configobj 2015-04-22 12:42:32 -07:00
Seth Schoen
cdf8ccbd34 Split out runnable renewer into separate file 2015-04-22 12:36:57 -07:00
Seth Schoen
b13b6de79f Small fixes; new_lineage method and tests for it 2015-04-22 12:24:33 -07:00
James Kasten
e301c71fea Merge pull request #357 from diracdeltas/doc/nginx
Add docs for Nginx plugin stub
2015-04-22 10:13:24 -07:00
Jakub Warmuz
b0fe02f732
merge github/letsencrypt/master 2015-04-22 09:16:13 +00:00
Jakub Warmuz
f26549dc58
Revert "Add cmd line arg for the authenticator"
This reverts commit 5d2abc30f0.
2015-04-22 09:02:41 +00:00
Jakub Warmuz
b76e8b6c41
Revert "Update unit tests for determine_authenticator"
This reverts commit 79f5ebe734.
2015-04-22 09:02:39 +00:00
Jakub Warmuz
cfe95323f6
Revert "Unit tests for setting authenticator via cmd line"
This reverts commit 0d7f32fa98.
2015-04-22 09:02:38 +00:00
Jakub Warmuz
4f0d0936af
IPluginFactory 2015-04-22 08:49:08 +00:00
Jakub Warmuz
88dc56186e
Move apache constants/args/IConfig to its subdirectory 2015-04-22 08:49:08 +00:00
Jakub Warmuz
bad3a959d4
random cli cleanup 2015-04-22 07:23:26 +00:00
Jakub Warmuz
0e3504cecc
stub cli_test 2015-04-22 07:20:45 +00:00
Jakub Warmuz
048876a1df
default_config_files 2015-04-22 07:19:31 +00:00
Jakub Warmuz
1001b027cb
ConfArgParse -> ConfigArgParse
ConfArgParse + subparsers = config scoped only to subcommand
ConfArgParse doesn't seem to respect formatter_class (no defaults)

ConfigArgParse works with ENV variables

https://github.com/bw2/ConfigArgParse#design-notes
2015-04-22 07:19:30 +00:00
Jakub Warmuz
975fe1c65b
Move scripts/main to client/cli.py 2015-04-22 06:23:25 +00:00
Seth Schoen
aabbee3608 Also add format indices to renewer tests 2015-04-21 21:52:04 -07:00
Seth Schoen
64eaa1b1a4 Add string format indices for Python 2.6 2015-04-21 21:46:07 -07:00
Seth Schoen
5180bda7e1 Initial tests for renewer 2015-04-21 19:13:08 -07:00
Seth Schoen
9463de3367 Initial implementation of renewer 2015-04-21 19:12:49 -07:00
yan
4c09b9882f Add docs for Nginx plugin stub 2015-04-21 10:31:25 -07:00
James Kasten
ea88fc6401 Merge pull request #348 from kuba/bootstrap
Bootstrap script improvements
2015-04-20 14:26:48 -07:00
James Kasten
ba8052250a Merge pull request #351 from diracdeltas/feature/nginx
Start Nginx configurator
2015-04-20 14:22:03 -07:00
yan
6a0dc2b960 Improve comments based on PR #351 review 2015-04-20 11:04:58 -07:00
yan
18582e8ca0 Fix tuple comparison, add ssl check in nginx get_version 2015-04-20 10:58:02 -07:00
James Kasten
cbee249c38 Merge branch 'master' into boulder
Conflicts:
	letsencrypt/acme/jose/json_util.py
	letsencrypt/acme/jose/jwk.py
	letsencrypt/acme/messages2_test.py
2015-04-19 20:33:20 -07:00
James Kasten
849415f71b prep testing infrastructure 2015-04-19 20:10:40 -07:00
James Kasten
98ecc5d25a Merge pull request #354 from kuba/to_partial_json
to_json -> to_partial_json, fully_serialize -> to_json
2015-04-19 20:08:14 -07:00
James Kasten
408f0141fa Merge pull request #353 from kuba/test_from_json_hashable
test_from_json_hashable
2015-04-18 22:27:03 -07:00
yan
4bcc18d9d3 Address @kuba's review comments 2015-04-18 10:22:36 -07:00
Jakub Warmuz
f8843c64e1
to_json -> to_partial_json, fully_serialize -> to_json 2015-04-18 08:11:28 +00:00
yan
636f5aa313 Remove commented-out code in nginx dvsni.py 2015-04-17 23:21:55 -07:00
Jakub Warmuz
33ba8b9dac
Remove explicit Registration.key.encoder 2015-04-18 06:19:54 +00:00
Jakub Warmuz
aca82c1771
lint, style, test registration hashable 2015-04-18 06:01:21 +00:00
James Kasten
82dded9128
Add Registration encoding/fix hashable JWKRSA 2015-04-18 05:48:55 +00:00
Jakub Warmuz
5b0efa2e64
Add test_from_json_hashable 2015-04-18 05:48:32 +00:00
yan
c67f1c11b4 Update LICENSE.txt for nginxparser attribution 2015-04-17 22:24:19 -07:00
yan
f3126e77a7 Fix duplicate code lint errors 2015-04-17 22:24:19 -07:00
yan
995b5622f8 Fix most pylint errors 2015-04-17 22:24:19 -07:00
yan
1505f5e7bc Empty format field not allowed in python 2.6 2015-04-17 22:24:19 -07:00
yan
03e5f3c6c6 Add default nginx config files from Ubuntu 2015-04-17 22:24:19 -07:00
yan
78458b1348 Add nginx obj.py test 2015-04-17 22:24:19 -07:00
yan
f05771b704 Add placeholder dvsni tests to bump coverage % 2015-04-17 22:24:19 -07:00
yan
f83a77d8ad Add regex servername test, correct conf syntax
Running the configtest (nginx -c -t /path/to/nginx.conf) should now say "The
configuration file /path/to/nginx.conf syntax is ok"
2015-04-17 22:24:19 -07:00
yan
f050fcfa58 Add unit test for deploying cert 2015-04-17 22:24:19 -07:00
yan
eeb81cbf1f Remove vhosts instance variable
For now, rebuild vhosts from parser.parsed on every invocation to ensure that
vhosts is up-to-date with parser.parsed.
2015-04-17 22:24:19 -07:00
yan
154db5a757 Start adding tests for nginx configurator 2015-04-17 22:24:19 -07:00
yan
d2588de4fd Add get_all_certs_keys method to parser 2015-04-17 22:24:19 -07:00
yan
fe1ba9dad6 Add test for nginx name matching 2015-04-17 22:24:18 -07:00
yan
d9c8c13f9a Fix and add test for get_vhosts 2015-04-17 22:24:18 -07:00
yan
e5a027ce30 Make nginx deploy_cert 2015-04-17 22:24:18 -07:00
yan
2a9c707dbd Update method to make server SSL ready 2015-04-17 22:24:18 -07:00
yan
7b72262811 Fix nginx choose_vhost to use nginx host-choosing rules 2015-04-17 22:24:18 -07:00
yan
3c806b120a Update configurator.save and configurator.get_all_names 2015-04-17 22:24:18 -07:00
yan
2a86936410 Delete unused methods or replace with placeholders 2015-04-17 22:22:06 -07:00
yan
0ba12c9f46 Fix typo: _get_vhosts -> get_vhosts 2015-04-17 22:21:14 -07:00
yan
efe1f2b2ff Fill out get_vhosts 2015-04-17 22:18:57 -07:00
yan
4f53c7a3c0 Define addr object for nginx 2015-04-17 22:18:56 -07:00
yan
d8ac31acae Add method and test for dumping nginx configs 2015-04-17 22:18:56 -07:00
yan
4f3bf3d720 Add test for recursive file parsing 2015-04-17 22:18:56 -07:00
yan
eaef4065e3 Rename NginxParser to RawNginxParser 2015-04-17 22:18:56 -07:00
yan
b245394355 Add test server.conf file 2015-04-17 22:18:56 -07:00
yan
13232452f8 Add recursive 'include' parsing to nginx parser 2015-04-17 22:18:56 -07:00
yan
61b98210f9 Mark semiprivate methods in configurator 2015-04-17 22:18:56 -07:00
yan
8caf03dcbb Update nginxparser test, remove other tests for now 2015-04-17 22:18:56 -07:00
yan
d36d0eeb30 Group nginx configurator methods more logically 2015-04-17 22:18:13 -07:00
James Kasten
932edbaf75 Select all domains by default 2015-04-17 16:45:10 -07:00
James Kasten
fcf4f69279 Cleanup RegistrationTest 2015-04-17 15:35:43 -07:00
James Kasten
495e1adaca Add Registration encoding/fix hashable JWKRSA 2015-04-17 15:09:19 -07:00
James Kasten
ab616a598f account integration 2015-04-17 03:40:22 -07:00
James Kasten
3d9d0627d7 add filename scrub 2015-04-17 00:57:51 -07:00
James Kasten
1e97c0c598 Add accounts and tests 2015-04-16 23:18:26 -07:00
yan
2460f85dbe Add save and reverter methods 2015-04-16 13:47:26 -07:00
yan
33ff366171 Remove redirect enhancement, fix reload 2015-04-16 13:47:26 -07:00
yan
37649966c2 Nginx versioning and other config changes 2015-04-16 13:47:26 -07:00
yan
46db59d774 start adding nginx stubs 2015-04-16 13:47:20 -07:00
James Kasten
214c0e9355 towards accounts 2015-04-15 16:53:39 -07:00
James Kasten
55188c52e8 Merge pull request #337 from kuba/bugs/280
dialog display on squeeze (fixes #280)
2015-04-14 13:02:29 -07:00
James Kasten
31915c5a01 Merge pull request #347 from kuba/boulder
Revert to ChallengeResource/ChallengeBody/Challenge triplet
2015-04-14 13:00:59 -07:00
Jakub Warmuz
cd90df8920
Update Travis and Vagrantfile to use sudo 2015-04-14 14:57:53 +00:00
Jakub Warmuz
990049bdd1
squeeze does not need --text 2015-04-14 14:21:38 +00:00
Jakub Warmuz
578680285f
Take out sudo from bootstrap scripts 2015-04-14 14:18:11 +00:00
Jakub Warmuz
a3164ae2d9
bootstrap: _deb_common.sh 2015-04-14 14:12:41 +00:00
Jakub Warmuz
7dd72ceac1
bootstrap: virtualenv for jessie+/utopic+ (fixes #346) 2015-04-14 14:11:35 +00:00
Jakub Warmuz
fc52600c4d
Adjust achallanges to be used with ChallengeBody 2015-04-14 13:40:56 +00:00
Jakub Warmuz
1672e07b2c
Return RegistrationResource in agree_to_tos 2015-04-14 13:06:21 +00:00
Jakub Warmuz
5298d8123d
ChallengeBody __getattr__ proxy 2015-04-14 13:06:14 +00:00
Jakub Warmuz
458a61a177
Revert to ChallengeResource/ChallengeBody/Challenge triplet 2015-04-14 13:06:08 +00:00
James Kasten
8857cb738b fix get_chall_status call 2015-04-14 02:13:12 -07:00
James Kasten
75578a1906 Merge branch 'master' into boulder 2015-04-13 17:33:35 -07:00
James Kasten
e9ffaf5793 Better authorization handling 2015-04-13 17:33:11 -07:00
James Kasten
37620ebe39 works with boulder 2015-04-10 23:02:01 -07:00
James Kasten
d4aa97cf3e Merge pull request #343 from garrettr/authenticator-cmd-line-arg
Add command line argument for Authenticator
2015-04-09 18:26:18 -07:00
Garrett Robinson
0d7f32fa98 Unit tests for setting authenticator via cmd line 2015-04-09 18:04:59 -07:00
Garrett Robinson
79f5ebe734 Update unit tests for determine_authenticator
The last commit refactored determine_authenticator to take a mapping of
authenticator names to authenticators instead of a list of
authenticators. This commit updates the existing unit tests to work with
this refactor.
2015-04-09 15:46:19 -07:00
Garrett Robinson
5d2abc30f0 Add cmd line arg for the authenticator 2015-04-09 15:45:50 -07:00
Garrett Robinson
73824c859a Ignore emacs autosave files 2015-04-09 15:45:18 -07:00
James Kasten
f5d25c392b separate out functions rename errors 2015-04-07 14:36:59 -07:00
Peter Eckersley
f36d143094 Link to interfaces.py 2015-04-07 11:46:48 -07:00
James Kasten
8a60b87081 Towards interoperability 2015-04-06 17:03:07 -07:00
Jakub Warmuz
6a47bc66d1
dialog display on squeeze (fixes #280) 2015-04-05 07:58:15 +00:00
James Kasten
99c5b7e290 Update ubuntu.sh test info 2015-04-02 10:36:26 -07:00
James Kasten
9a09ac8872 Merge pull request #313 from kuba/bugs/302
Bootstrap scripts (fixes: #302)
2015-04-02 10:31:04 -07:00
Jakub Warmuz
a45dab35bf
bootstrap Debian, squeeze notes (cf. #280) 2015-04-02 11:32:52 +00:00
Jakub Warmuz
d6d0c76f42
Merge remote-tracking branch 'github/letsencrypt/master' into bugs/302 2015-04-02 09:40:58 +00:00
Jakub Warmuz
7eee393b80
apt-get install dpkg-dev (fixes #276) 2015-04-02 09:23:28 +00:00
Jakub Warmuz
b76542afb3
constants:DEFAULT_* 2015-04-02 06:55:50 +00:00
James Kasten
e41acf72a0 Merge pull request #325 from kuba/network2
Restified messages2 and network2
2015-04-01 15:14:20 -07:00
Jakub Warmuz
7a4c7acdfb
Fix review comments 2015-04-01 07:58:04 +00:00
James Kasten
d638221e55 Merge pull request #329 from letsencrypt/rename-client-challenges
Rename client authenticator/challenges
2015-03-31 18:52:55 -07:00
James Kasten
c0dc49b192 fix documentation 2015-03-31 18:43:20 -07:00
James Kasten
bbcfda59e4 Merge pull request #330 from letsencrypt/cleanup_contributing
update/cleanup docs
2015-03-31 11:44:08 -07:00
James Kasten
495a090503 Merge pull request #331 from letsencrypt/fix_travis_irc
Reduce travis noise
2015-03-31 11:43:11 -07:00
James Kasten
dd68d98ac6 Reduce travis noise 2015-03-31 11:33:14 -07:00
James Kasten
ce3cabfd2f Fix mistake, rework sentence 2015-03-30 18:28:36 -07:00
James Kasten
162f41d45e update/cleanup docs 2015-03-30 18:18:59 -07:00
James Kasten
2bd451a964 fix continuity_auth docs 2015-03-30 17:36:09 -07:00
James Kasten
f7619c6204 fix unittests/formatting 2015-03-30 17:28:50 -07:00
James Kasten
0c50e02c6d Merge branch 'Hainish-rename-authenticator' into rename-client-challenges 2015-03-30 17:18:03 -07:00
James Kasten
dd3f4acbd0 rename renewal->cont 2015-03-30 17:15:08 -07:00
James Kasten
26074c1399 rid project of refs to client challenges 2015-03-30 17:13:27 -07:00
James Kasten
176c1a8b93 Merge branch 'master' into Hainish-rename-authenticator 2015-03-30 16:01:34 -07:00
James Kasten
d4336b3ca1 finish renaming/shorten name 2015-03-30 16:01:26 -07:00
James Kasten
5b0c9f2c11 Merge branch 'rename-authenticator' of git://github.com/Hainish/lets-encrypt-preview into Hainish-rename-authenticator 2015-03-30 15:34:10 -07:00
James Kasten
5c3b192433 Merge pull request #328 from letsencrypt/revert_for_327
revert to set
2015-03-30 14:03:08 -07:00
James Kasten
1c254d64ef remove old comment 2015-03-30 13:54:06 -07:00
James Kasten
9ffcbf9934 revert to set 2015-03-30 13:33:44 -07:00
James Kasten
846cbc728c Merge pull request #327 from kuba/hashable-key
HashableRSAKey
2015-03-30 13:17:47 -07:00
James Kasten
114bd19377 Merge pull request #326 from letsencrypt/fix_chall_selection
Fix chall selection
2015-03-30 12:25:01 -07:00
James Kasten
8561de7e73 Small doc change and formatting 2015-03-30 12:09:07 -07:00
Jakub Warmuz
8bc55899e6
Subparsers CLI 2015-03-30 12:43:00 +00:00
Jakub Warmuz
8faa877c45
plugins.disco 2015-03-30 12:36:21 +00:00
Seth Schoen
cd0b99ae5d Fix ambiguity about describing a port as "open" 2015-03-29 23:11:05 -07:00
Jakub Warmuz
d4594f02ed
HashableRSAKey 2015-03-28 07:14:11 +00:00
James Kasten
da14e149b1 add exception documentation 2015-03-27 21:36:06 -07:00
James Kasten
b67068e986 fix typo in challenges doc 2015-03-27 21:09:03 -07:00
James Kasten
567cec1824 Fix gen_chall_path, add unittests 2015-03-27 21:08:14 -07:00
Jakub Warmuz
4b829603d0
py26 compat 2015-03-27 23:49:09 +00:00
Jakub Warmuz
fadad74d48
Test, lint, and docs for network2 2015-03-27 23:38:13 +00:00
James Kasten
60a52943f6 Merge pull request #324 from letsencrypt/plugins_dir
Move IAuthenticators and IInstallers to plugins directory
2015-03-27 15:51:11 -07:00
James Kasten
fa79e3c5ef fix pylint >80 character errors 2015-03-27 15:37:34 -07:00
James Kasten
989b8f059b Update documentation 2015-03-27 15:20:43 -07:00
Jakub Warmuz
1349b5241c
More sensible full serialization 2015-03-27 20:08:34 +00:00
James Kasten
fbd3b8f06d Merge branch 'master' into plugins_dir 2015-03-27 12:57:28 -07:00
James Kasten
5763da07ff Finish contributing.md update
Fix relevant links in README
2015-03-27 12:38:46 -07:00
James Kasten
13157c75b1 Merge pull request #323 from ChristianGaertner/patch-1
Fixed wrong linking to CONTRIBUTING
2015-03-27 12:33:25 -07:00
Christian Gärtner
0a1687eed5 Fixed wrong linking to CONTRIBUTING 2015-03-27 20:31:29 +01:00
James Kasten
bb350b0b5f Merge branch 'kuba-plugins' 2015-03-27 12:29:40 -07:00
James Kasten
8fa2204afe Add disclaimer in plugins doc 2015-03-27 12:29:27 -07:00
James Kasten
61edede7a6 Merge branch 'plugins' of git://github.com/kuba/lets-encrypt-preview into kuba-plugins 2015-03-27 11:55:35 -07:00
Jakub Warmuz
3762622ee9
Tests, lint, and docs for messages2 2015-03-27 14:49:02 +00:00
Jakub Warmuz
c985a8987b
Add fields.rst docs 2015-03-27 10:15:11 +00:00
Jakub Warmuz
b12e4ba357
ImmutableMap.update: test, lint 2015-03-27 08:48:32 +00:00
Jakub Warmuz
ffff84ee55
100% coverage for acme.fields 2015-03-27 08:43:05 +00:00
James Kasten
32c33e64df cleanup of plugins 2015-03-26 17:39:08 -07:00
James Kasten
2d848994f4 Move IAuthenticators and IInstallers into plugins dir 2015-03-26 17:23:17 -07:00
Jakub Warmuz
6b78789ea3
Improve plugins.rst 2015-03-26 22:12:40 +00:00
Jakub Warmuz
8e32ae8246
Move init_auths to scripts/main.py. 2015-03-26 22:00:00 +00:00
James Kasten
1b206e495b Merge branch 'plugins' of git://github.com/kuba/lets-encrypt-preview into kuba-plugins 2015-03-26 13:48:33 -07:00
Jakub Warmuz
df70b327e9
StatusValid -> STATUS_VALID 2015-03-26 15:03:57 +00:00
Jakub Warmuz
3caa0f8453
network2: JSON_ERROR_CONTENT_TYPE 2015-03-26 15:03:12 +00:00
Jakub Warmuz
d9871b59f0
pylint: unused imports 2015-03-26 14:42:07 +00:00
Jakub Warmuz
03383c3824
Fix quotes 2015-03-26 13:59:33 +00:00
Jakub Warmuz
ff532469a5
Setuptools entry_points plugins 2015-03-26 13:55:23 +00:00
Jakub Warmuz
d304f53895
pylint network2/messages2/fields 2015-03-26 11:10:06 +00:00
Jakub Warmuz
ede635ad99
_check_response 2015-03-26 11:02:13 +00:00
Jakub Warmuz
d128e42f76
API docs for messages2/network2 2015-03-26 06:50:13 +00:00
Jakub Warmuz
1c964c865b
network2: InsecurePlatformWarning fix 2015-03-26 06:46:31 +00:00
James Kasten
78f84441ff Merge pull request #319 from letsencrypt/fix_reverter_doc
update/fix documentation of reverter
2015-03-25 18:52:56 -07:00
James Kasten
8a9bd1ee0b update/fix documentation of reverter 2015-03-25 18:38:13 -07:00
Jakub Warmuz
dd03623065
Merge remote-tracking branch 'github/letsencrypt/master' into network2 2015-03-25 18:52:34 +00:00
Jakub Warmuz
761994a5f8
ChallengeResource.uri 2015-03-25 18:37:55 +00:00
James Kasten
197125bdda git push origin masterMerge branch 'kuba-bugs/316' 2015-03-25 10:46:46 -07:00
James Kasten
7d834a0ae8 assertTrue to assertEqual 2015-03-25 10:46:22 -07:00
Jakub Warmuz
34466f745b
RFC3339DateField (pyrfc3339) 2015-03-25 16:02:41 +00:00
Jakub Warmuz
0b557a0b8c
acme-spec #88 fixed 2015-03-25 15:22:56 +00:00
Jakub Warmuz
073dea2624
Add werkzeug dependency 2015-03-25 12:50:51 +00:00
Jakub Warmuz
a204574b02
network2 is not so much stub anymore 2015-03-25 12:50:21 +00:00
Jakub Warmuz
4eef08911a
network2: priority queue polling, _retry_after 2015-03-25 12:44:37 +00:00
Jakub Warmuz
920152bb17
messages2.Challenge 2015-03-25 11:22:50 +00:00
Jakub Warmuz
e77d9026e1
Update network2 docs 2015-03-25 11:13:26 +00:00
Jakub Warmuz
23e92da0b5
Fix typo 2015-03-25 10:25:27 +00:00
Jakub Warmuz
f5a6bb389e
Fix #316 2015-03-25 10:21:01 +00:00
Jakub Warmuz
9b33c9a685
network2: revoke 2015-03-25 07:54:08 +00:00
Jakub Warmuz
eeb4f632bf
network2: _get_cert, fetch_chain 2015-03-25 07:42:36 +00:00
Jakub Warmuz
9c8a6f7b04
network2: use ImmutableMap.update() 2015-03-25 07:42:21 +00:00
Jakub Warmuz
a4704d72bd
network2: _check_content_typ 2015-03-25 07:41:36 +00:00
Jakub Warmuz
81ef0b1ce2
Merge remote-tracking branch 'github/letsencrypt/master' into network2 2015-03-25 06:12:25 +00:00
Jakub Warmuz
3786170a89
request_issuance Accept and Content-Type 2015-03-25 06:10:38 +00:00
Jakub Warmuz
66bc89f186
Boulder messaes up Content-Type 2015-03-25 06:03:16 +00:00
Jakub Warmuz
1e45edd548
Add docstring 2015-03-25 05:59:56 +00:00
Jakub Warmuz
4dfc7ea358
network2: _get, improve netwrok error handling 2015-03-25 05:59:38 +00:00
James Kasten
5e1d5fc499 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2015-03-24 22:55:23 -07:00
James Kasten
00558c3421 git push origin masterMerge branch 'kuba-chmods' 2015-03-24 22:54:41 -07:00
James Kasten
1d70018cf4 Merge branch 'chmods' of git://github.com/kuba/lets-encrypt-preview into kuba-chmods 2015-03-24 22:53:57 -07:00
James Kasten
80cdf13f6c Merge pull request #286 from letsencrypt/fix_272
Fix 272
2015-03-24 22:52:16 -07:00
James Kasten
a1fe6039d8 Fix bug with no DVSNI challenges 2015-03-24 17:49:38 -07:00
James Kasten
8e3a496b8b simplify path_satisfied test 2015-03-24 16:21:09 -07:00
James Kasten
26ec3cfcea Merge branch 'master' into fix_272 2015-03-24 16:18:36 -07:00
Jakub Warmuz
0c30bcbf3e
Fix "pool tuple" bug in restified example 2015-03-24 19:30:14 +00:00
Jakub Warmuz
f29fe21ddd
network2: retry-after stub 2015-03-24 19:12:24 +00:00
Jakub Warmuz
3676a6d87a
network2: Update poll() 2015-03-24 19:05:09 +00:00
Jakub Warmuz
3dcf81dbb6
network2: Improve error handling 2015-03-24 18:55:32 +00:00
Jakub Warmuz
d9176d4267
Improve request_issuance 2015-03-24 18:50:53 +00:00
Jakub Warmuz
c242091b4e
UnexpectedUpdate in Network.request_challenges 2015-03-24 18:49:54 +00:00
Jakub Warmuz
7e5ccddf7e
restified example: auto-accept TOS 2015-03-24 18:33:43 +00:00
Jakub Warmuz
2fb3bd8728
UnexpectedUpdate in Network.answer_challenge 2015-03-24 18:33:43 +00:00
Jakub Warmuz
9832e5c6d6
network2: update_registration 2015-03-24 18:33:43 +00:00
Jakub Warmuz
5c40daaf1c
ImmutableMap.update 2015-03-24 18:33:42 +00:00
Jakub Warmuz
b24487a14b
restified example: NEW_REG_URL only 2015-03-24 17:40:20 +00:00
Jakub Warmuz
227d947d4c
Update network2 docs 2015-03-24 17:08:37 +00:00
Jakub Warmuz
144baf64fe
client.errors.UnexpectedUpdate 2015-03-24 17:08:25 +00:00
Jakub Warmuz
2b4b86a41b
Registration: TOS and agreement 2015-03-24 17:07:52 +00:00
Jakub Warmuz
a6e1c3ed17
Current Boulder supports registrations 2015-03-24 17:02:02 +00:00
Jakub Warmuz
62cdf4a2f8
Add more stub methods to network2 2015-03-24 13:24:20 +00:00
Jakub Warmuz
c9589d33d3
Update messages2, network2 stub, example updated 2015-03-24 12:34:21 +00:00
James Kasten
57bb4513c3 Merge pull request #314 from kuba/bugs/304
InsecurePlatformWarning (fixes #304)
2015-03-23 11:50:20 -07:00
James Kasten
82440cac30 Merge pull request #312 from kuba/docs
Docs fixes
2015-03-23 11:15:52 -07:00
Jakub Warmuz
71e17df03a
InsecurePlatformWarning (fixes #304) 2015-03-23 09:24:25 +00:00
Jakub Warmuz
79a511226f
Merge remote-tracking branch 'github/letsencrypt/master' into chmods 2015-03-23 08:59:14 +00:00
Jakub Warmuz
12346b368a
Bootstrap scripts (fixes: #302) 2015-03-23 08:56:29 +00:00
Jakub Warmuz
533cfa42c7
MANIFEST: Update CONTRIBUTING extension 2015-03-23 08:35:36 +00:00
Jakub Warmuz
53bdf5e246
Fix _enable_redirect docstring 2015-03-23 08:27:57 +00:00
Jakub Warmuz
1a0af51f6f
Fix Sphinx M2Crypto.X509 import errors 2015-03-23 08:25:44 +00:00
James Kasten
08b60115e3 Merge pull request #311 from kuba/docs
Docs reorg and cleanup
2015-03-22 17:44:14 -07:00
James Kasten
e25619e23d Merge pull request #310 from kuba/bugs/309
Bring back Vagrant documentation (fixes #309).
2015-03-22 17:33:38 -07:00
Jakub Warmuz
6d38b1b09e
HTTPS ReadTheDocs link in CONTRIBUTING.md 2015-03-22 22:30:57 +00:00
Jakub Warmuz
55a80e768a
CONTRIBUTING: md file suffix 2015-03-22 22:29:24 +00:00
Jakub Warmuz
3206eb674a
rst cleanup: contributing, using 2015-03-22 22:25:50 +00:00
Jakub Warmuz
37a7ef2160
Reorg CONTRIBUTING 2015-03-22 14:07:58 +00:00
Garrett Robinson
0a981e02f4
Bring back Vagrant documentation (fixes #309). 2015-03-22 10:51:40 +00:00
James Kasten
79f6e61c37 Merge pull request #290 from kuba/jws
JWS
2015-03-21 16:25:28 -07:00
James Kasten
12287e70fc remove schemata 2015-03-21 16:20:32 -07:00
James Kasten
0b68fa4038 Merge pull request #305 from letsencrypt/fix_setup
add acme.jose and acme.schemata packages to setup.py
2015-03-21 15:41:20 -07:00
William Budington
f1081a3d68 Rename test filename as well 2015-03-21 22:24:35 +00:00
James Kasten
4e21703503 add acme.jose and acme.schemata packages to setup.py 2015-03-21 15:14:58 -07:00
pde and jdkasten
d06cd7aa39 Update dev docs
- Import and edit James's API docs into CONTRIBUTING.rst
- Linke correctly to CONTRIBUTING from the main README
2015-03-21 14:04:28 -07:00
James Kasten
ce2a6b7c5a Merge branch 'restification'
Conflicts:
	.gitignore
	CONTRIBUTING.rst
2015-03-21 14:03:36 -07:00
James Kasten
121e3c2f7f Merge pull request #292 from garrettr/vagrant
Vagrantfile and associated changes
2015-03-21 13:55:16 -07:00
Jakub Warmuz
006fcbbf46
py26 compat: with x, y -> with x: with y 2015-03-21 20:54:02 +00:00
Jakub Warmuz
71d8999e7c
Bump up minimum coverage to 86% 2015-03-21 20:50:43 +00:00
Jakub Warmuz
4ee70a9ee4
Merge remote-tracking branch 'github/letsencrypt/master' into jws 2015-03-21 20:45:11 +00:00
James Kasten
cbd8c0d1e7 Merge pull request #287 from kuba/acme.jose
acme.jose: (Typed)JSONObjectWithFields, Field, JWA.
2015-03-21 13:40:34 -07:00
Jakub Warmuz
0374ae0a74
Remove remnants of fdec2/fenc2 2015-03-21 20:17:29 +00:00
William Budington
603f891a37 Renaming ClientAuthenticator to ContinuityAuthenticator 2015-03-21 20:01:41 +00:00
Garrett Robinson
5eb007cc31 Add Vagrantfile, document use
* Adds workaround to setup.py for issue with Vagrant sync filesystem and
  hard linking (used by distutils in Python < 2.7.9). This workaround is
  only used in a Vagrant environment.
* Adds Vagrant-related files to .gitignore
2015-03-21 12:51:33 -07:00
Jakub Warmuz
615b2c789d
_JWAHS.verify constant compare warning 2015-03-21 07:31:27 +00:00
William Budington
55494fd9cf Updating docs for docker usage 2015-03-21 02:43:15 +00:00
William Budington
147f198d7c Adding a cert path for certs generated in docker 2015-03-21 02:01:40 +00:00
Peter Eckersley
c5210d9fab Merge remote-tracking branch 'letsencrypt/restification' 2015-03-20 18:57:15 -07:00
Peter Eckersley
a863a8f9ac Link to the CONTRIBUTING file 2015-03-20 18:55:35 -07:00
Peter Eckersley
9d0ef35d25 Merge branch 'master' of ssh://github.com/letsencrypt/lets-encrypt-preview 2015-03-20 18:51:00 -07:00
William Budington
95090974e9 When running standalone client with docker, do not check container cert output dir for permissions 2015-03-21 01:03:14 +00:00
James Kasten
089ffa73dc Merge branch 'restification' of github.com:letsencrypt/lets-encrypt-preview into restification 2015-03-20 17:43:22 -07:00
James Kasten
ec8f54d1ee Merge pull request #296 from cooperq/restification
Add set up instructions to CONTRIBUTING.rst
2015-03-20 17:29:43 -07:00
Jakub Warmuz
7e820b093d
Initial impl. of v02, works with Boulder 2015-03-21 00:02:22 +00:00
Jakub Warmuz
e6b07f66d5
Remove decoder2/encoder2 2015-03-20 23:17:41 +00:00
William Budington
028179de25 Adding docker-compose file for runtime configuration 2015-03-20 23:02:43 +00:00
William Budington
039a6d79e6 Adding a Dockerfile for standalone setup 2015-03-20 22:57:56 +00:00
cooperq
a7059e6818 replace text with link to docs 2015-03-20 15:13:49 -07:00
James Kasten
44eb178fd7 Merge pull request #298 from Hainish/ignore-swp
Gitignore .swp for vim
2015-03-20 17:58:10 -04:00
James Kasten
a15b02983e Merge pull request #299 from garrettr/pin-pylint-1.4.2
Pin pylint to 1.4.2 to workaround #289
2015-03-20 17:57:25 -04:00
Garrett Robinson
2a2e4d2e8d Pin pylint and astroid to workaround #289 2015-03-20 14:48:36 -07:00
William Budington
75e4e5d48b Gitignore .swp for vim 2015-03-20 20:56:44 +00:00
Ada Lovelace
dee212fc90 fixes typos in CONTRIBUTING 2015-03-20 13:23:44 -07:00
Ada Lovelace
b288bcf4a6 include setup instructions in CONTRIBUTING.rst 2015-03-20 13:15:29 -07:00
Ada Lovelace
fb12b715bd added swig to list of dependencies to install in CONTRIBUTING.rst 2015-03-20 12:39:07 -07:00
Peter Eckersley
2f2e973552 Add the standalone mode to the README 2015-03-20 11:43:16 -07:00
Jakub Warmuz
7def7df897
JWS 2015-03-19 17:35:10 +00:00
Jakub Warmuz
8208a7f4d5
MockMessage test cleanup 2015-03-18 22:08:01 +00:00
Jakub Warmuz
77eb5f7625
jose.* imports cleanup 2015-03-18 22:07:37 +00:00
Jakub Warmuz
ef72b147ae
Remove signature verification on CertificateRequest deserialization. 2015-03-18 22:00:56 +00:00
Jakub Warmuz
d74ca1bbaa
tox: PYTHONHASHSEED=0 2015-03-18 15:04:27 +00:00
Jakub Warmuz
2d9bce8a7f
Fix typo 2015-03-18 14:32:43 +00:00
Jakub Warmuz
b6203d512c
acme.jose: (Typed)JSONObjectWithFields, Field, JWA. 2015-03-18 14:10:28 +00:00
James Kasten
b47cc8eb8f fix _path_satisfied test 2015-03-16 23:00:31 -07:00
James Kasten
7d41cadc99 make _path_satisfied conform to API 2015-03-16 22:58:33 -07:00
James Kasten
afd7db3a69 Merge pull request #282 from kuba/eula
EULA using pkg_resources (fixes #278)
2015-03-11 15:00:39 -04:00
Jakub Warmuz
97165b8711
EULA using pkg_resources (fixes #278) 2015-03-11 07:51:06 +00:00
James Kasten
2f85606d08 Merge pull request #277 from pavgup/patch-1
the readme one-liner fails in practice
2015-03-10 14:09:45 -04:00
James Kasten
1c31b35d8c Merge pull request #275 from kuba/setuptools
Add trove classifiers, clean up setup.py (c.f. #198).
2015-03-10 14:08:19 -04:00
Pavan Gupta
3ee1f0b402 the readme one-liner fails in practice
It's missing the required -d before the domain name.
2015-03-09 04:13:22 -04:00
James Kasten
f69f9809a1 git push origin masterMerge branch 'kuba-acme.challenges' 2015-03-04 23:01:39 -08:00
James Kasten
528bcc286e fix typo 2015-03-04 23:01:27 -08:00
James Kasten
b9c1694dad Merge branch 'acme.challenges' of git://github.com/kuba/lets-encrypt-preview into kuba-acme.challenges 2015-03-04 22:49:17 -08:00
Jakub Warmuz
61662553b5
Add trove classifiers, clean up setup.py (c.f. #198). 2015-02-28 21:01:22 +00:00
Jakub Warmuz
2b8f2cc113
rm letsencrypt.py, chmod -x, remove sheebangs 2015-02-28 20:40:33 +00:00
Jakub Warmuz
636e05e1c4
Use DVSNIResponse in confiurator_test 2015-02-28 09:09:44 +00:00
Jakub Warmuz
d230a210f8
Add indent to auth_handler log entries 2015-02-28 09:06:51 +00:00
Jakub Warmuz
ce1e43c603
Move decoders/encoders to top-level acme.util 2015-02-28 09:03:23 +00:00
Jakub Warmuz
c83f7c2473
Remove bool() cast from Signature.verify 2015-02-28 08:34:51 +00:00
Jakub Warmuz
5720cdc8c9
Inlining 2015-02-28 08:33:22 +00:00
Jakub Warmuz
cde829fa7c
Fix typo 2015-02-28 08:29:46 +00:00
Jakub Warmuz
a4e4d98598
Improve acme docs 2015-02-28 08:29:13 +00:00
Jakub Warmuz
8f41aa69cd
import M2Crypto.X509 -> import M2Crypto 2015-02-28 07:33:54 +00:00
Seth Schoen
f7dda7fcc2 get_sans based on .split() instead of regexp 2015-02-26 15:23:24 -08:00
Seth Schoen
2e0b72f005 Merge branch 'master' into get_sans 2015-02-26 15:19:21 -08:00
James Kasten
3dac629f2b Merge pull request #270 from kuba/bugs/268
Update MANIFEST.in
2015-02-26 13:11:12 -08:00
Jakub Warmuz
0fb9fc6565
MANIFEST: add CONTRIBUTING.rst, split lines 2015-02-26 08:55:05 +00:00
Jakub Warmuz
8e8bc3dc0d
MANIFEST: remove *.sh (fixes #268) 2015-02-26 08:53:15 +00:00
James Kasten
38dc276b22 finish merge correctly 2015-02-26 00:36:08 -08:00
James Kasten
654897510d Merge branch 'master' into get_sans
Conflicts:
	letsencrypt/client/crypto_util.py
	letsencrypt/client/tests/crypto_util_test.py
2015-02-25 19:27:36 -08:00
James Kasten
1a00be0fdf Merge pull request #249 from letsencrypt/use_psutil
Use psutil instead of netstat subprocess
2015-02-25 19:02:00 -08:00
James Kasten
a9f47cda39 Merge branch 'master' into use_psutil 2015-02-25 18:07:07 -08:00
Jakub Warmuz
ca2bbc13a3
Improve letsencrypt.acme docs. 2015-02-25 13:56:48 +00:00
Jakub Warmuz
67314e9f15
Increase min-similarity-lines. 2015-02-25 13:56:47 +00:00
Jakub Warmuz
ba98b5cb22
Bump up coverage 2015-02-25 13:56:47 +00:00
Jakub Warmuz
97bf10120c
Use acme.challenges in client 2015-02-25 13:56:47 +00:00
Jakub Warmuz
9ba69f8878
Use acme.challenges in acme.messages 2015-02-25 13:56:47 +00:00
Jakub Warmuz
0b8767f990
Catch-all __slots__ linter_plugin 2015-02-25 13:56:47 +00:00
Jakub Warmuz
52257c4d6b
Add acme.challenges. 2015-02-25 13:49:29 +00:00
Jakub Warmuz
76085f0bb0
Non-schema errors for acme.messages 2015-02-25 13:49:29 +00:00
Jakub Warmuz
3d883fd77f
(Typed)ACMEObject class hierarchy, from_json vs from_valid_json. 2015-02-25 13:49:29 +00:00
James Kasten
05cdb821dc Merge pull request #250 from letsencrypt/revoker
Display/Revoker Overhaul
2015-02-24 18:18:02 -08:00
James Kasten
a358feb82e restore names 2015-02-24 18:09:50 -08:00
James Kasten
3160df3ede Remove cannot authenticate message for cancel 2015-02-24 17:33:19 -08:00
James Kasten
e1a723586c Merge pull request #263 from kuba/travis
Travis: quicker "install", libffi-dev
2015-02-24 16:41:21 -08:00
James Kasten
ba8604a3df Correctly handle None 2015-02-24 16:39:26 -08:00
James Kasten
048d253120 Merge pull request #261 from kuba/readthedocs
ReadTheDocs improvements
2015-02-24 16:18:14 -08:00
James Kasten
50ac3cb712 Merge pull request #264 from letsencrypt/issue_253-duplicate_coverage
Make coveralls dependent on the TOXENV variable being "cover"
2015-02-24 16:10:12 -08:00
J.C. Jones
34de7a1747 Make coveralls dependent on the TOXENV variable being "cover" 2015-02-24 15:53:03 -07:00
James Kasten
2920e797c9 tests/client_test.py 2015-02-24 12:43:28 -08:00
James Kasten
31fb733e05 fix spacing 2015-02-24 12:23:02 -08:00
James Kasten
8e66c58ec9 remove incorrect comment 2015-02-24 12:13:08 -08:00
James Kasten
ad1dfc4701 iteration 2015-02-24 11:53:57 -08:00
Jakub Warmuz
05ac2bde9a
travis: install libffi-dev 2015-02-24 18:12:26 +00:00
Jakub Warmuz
21d210b8ce
Travis: quicker "install" 2015-02-24 18:03:19 +00:00
Jakub Warmuz
a5551604c6
Reference docs augeas/M2Crypto hack to github bug 2015-02-24 16:55:25 +00:00
Jakub Warmuz
31f444e4ba
Mock augeas and M2Crypto in docs (fixes #262). 2015-02-24 16:51:42 +00:00
Jakub Warmuz
00af984bac
Fix docs build errors 2015-02-24 16:23:54 +00:00
Jakub Warmuz
edce44024b
Use sphinx_rtd_theme locally. 2015-02-24 16:23:54 +00:00
Jakub Warmuz
ed99d809f1
docs: letsencrypt --help path (fixes #260) 2015-02-24 16:23:54 +00:00
Jakub Warmuz
4bb9914fa3
split dev_extras into dev_extras+docs_extras 2015-02-24 16:23:52 +00:00
Jakub Warmuz
d824002f01
Add readthedocs.org badge. Update alts. Move to top. 2015-02-24 16:23:27 +00:00
Jakub Warmuz
e7bddb5a87
requirements.txt for readthedocs.org (fixes #259) 2015-02-24 16:23:23 +00:00
James Kasten
17741a0fbf remove excess whitespace 2015-02-23 23:19:28 -08:00
James Kasten
f5c30b383a second round revisions 2015-02-23 23:18:07 -08:00
James Kasten
fa0c3d2b9f revisions 2015-02-23 04:26:43 -08:00
James Kasten
53b9ec145c Merge pull request #254 from kuba/contributing.rst
Add CONTRIBUTING.rst
2015-02-20 15:55:46 -08:00
James Kasten
e278bb7c91 Merge pull request #252 from kuba/requirements.txt
Remove requirements.txt
2015-02-20 15:54:39 -08:00
James Kasten
76976493f1 Merge pull request #251 from scottjbarr/master
Added ConfArgParse and zope.component requirements.txt
2015-02-20 15:54:12 -08:00
Seth Schoen
e17b9907f8 psutil>=2.1.0; remove redundant parentheses 2015-02-20 14:37:23 -08:00
Seth Schoen
6e0049fded Some formatting cleanups 2015-02-20 13:44:36 -08:00
Seth Schoen
daf7c81e5d Formatting and small fixes 2015-02-20 13:22:20 -08:00
Jakub Warmuz
84f0685929
Add CONTRIBUTING.rst 2015-02-20 14:37:05 +00:00
Jakub Warmuz
1382c0c969
Remove requirements.txt 2015-02-20 14:11:55 +00:00
Scott Barr
b8ba6fe4e1 Added ConfArgParse and zope.component requirements.txt 2015-02-20 21:31:32 +10:00
James Kasten
5d76c0feb1 Final cleanup for revoker/display 2015-02-19 22:30:11 -08:00
James Kasten
119863d1ea determine_authenticator tests 2015-02-19 19:33:54 -08:00
Seth Schoen
fc1617531e Use psutil instead of netstat subprocess 2015-02-19 17:49:27 -08:00
James Kasten
5ab9b7c331 Merge pull request #248 from letsencrypt/coveralls
Coveralls
2015-02-19 15:18:53 -08:00
James Kasten
e14beb2fd7 back to reST 2015-02-19 15:07:06 -08:00
Seth Schoen
36d26de746 Implement get_sans via parsing Request.as_text() 2015-02-19 14:55:14 -08:00
James Kasten
1cb2ddaa26 Update for coveralls badge 2015-02-19 14:47:50 -08:00
James Kasten
6c8eb8be17 Revisions through running/testing 2015-02-19 04:13:39 -08:00
James Kasten
08fc0852d7 Correct display docs 2015-02-18 04:13:49 -08:00
James Kasten
d24c95da16 Merge branch 'master' into revoker 2015-02-18 04:04:07 -08:00
James Kasten
b4c327be38 pylint fixes 2015-02-18 04:03:32 -08:00
James Kasten
f77307c28b Finish revoker implementation and unittests 2015-02-18 04:01:49 -08:00
James Kasten
04ecf813bd Merge branch 'master' into revoker
Conflicts:
	letsencrypt/client/auth_handler.py
	letsencrypt/client/client.py
	letsencrypt/client/crypto_util.py
	letsencrypt/client/interfaces.py
	letsencrypt/client/le_util.py
	letsencrypt/client/revoker.py
	letsencrypt/client/standalone_authenticator.py
	letsencrypt/client/tests/apache/dvsni_test.py
	letsencrypt/client/tests/apache/obj_test.py
	letsencrypt/client/tests/recovery_token_test.py
	letsencrypt/client/tests/standalone_authenticator_test.py
	letsencrypt/scripts/main.py
2015-02-18 03:36:53 -08:00
Seth Schoen
8ff99300e4 This is apparently not deterministic after all 2015-02-16 00:21:08 -08:00
Seth Schoen
d4c1ceccac 100% coverage for recovery_token 2015-02-16 00:10:55 -08:00
Seth Schoen
dd3e10eb42 100% coverage for crypto_util (test for make_csr) 2015-02-16 00:10:26 -08:00
Seth Schoen
4af0c197e1 Trivial test for ClientAuthenticator get_chall_pref 2015-02-15 23:25:41 -08:00
James Kasten
9a990ccfaf revoker progress 2015-02-15 23:17:53 -08:00
James Kasten
9d090017b5 Merge pull request #233 from kuba/acme
letsencrypt.acme module
2015-02-15 23:14:50 -08:00
Jakub Warmuz
02d5775aff
Fix port-merge get_chall_msg error 2015-02-15 12:37:56 +00:00
Jakub Warmuz
c6ec49e90d
Merge remote-tracking branch 'github/letsencrypt/master' into acme 2015-02-15 12:32:26 +00:00
Jakub Warmuz
6922124927
Use ComparableX509 everywhere. 2015-02-15 12:12:29 +00:00
Jakub Warmuz
8070b917a3
Remove str() casting in Signature.from_msg 2015-02-15 11:52:02 +00:00
Jakub Warmuz
7d74125936
Add comment int linter_plugin 2015-02-15 11:49:07 +00:00
Jakub Warmuz
61e654b852
acme.messages: explicit warnings about key verification 2015-02-15 11:45:21 +00:00
Jakub Warmuz
f81e936a49
Signature: remove todo about M2Crypto 2015-02-15 11:31:40 +00:00
schoen
5f59ca6a2f Merge pull request #244 from letsencrypt/already_listening
Already listening
2015-02-13 15:24:33 -08:00
Seth Schoen
fbd9e6f0db Specify location of netstat binary as a constant 2015-02-13 15:14:01 -08:00
Seth Schoen
67627c19d7 netstat output can differ on an IPv6 system 2015-02-13 14:59:59 -08:00
Seth Schoen
827c07a046 Merge branch 'master' into already_listening 2015-02-13 14:54:57 -08:00
James Kasten
ab8a76048f Merge pull request #243 from letsencrypt/fix_bug_from_220
fix domains bug introduced in 220
2015-02-13 02:40:15 -08:00
James Kasten
a50e8593ca Merge pull request #242 from letsencrypt/interface_specification
Clarify authenticator interface
2015-02-13 02:14:07 -08:00
James Kasten
5c5313ba73 fix domains bug introduced in 220 2015-02-13 01:56:11 -08:00
Seth Schoen
2001d180af Fix typo in comment 2015-02-12 22:55:25 -08:00
Seth Schoen
42865fbc92 Add (Linux-specific) already_listening method 2015-02-12 19:27:33 -08:00
Jakub Warmuz
89ac11c309
Fix imports 2015-02-12 23:55:47 +00:00
Seth Schoen
e1cdc79bdc 100% coverage for obj.py 2015-02-12 15:21:35 -08:00
Jakub Warmuz
bde345766f
Update acme.messages docs. Fix test 2015-02-12 15:45:12 +00:00
Jakub Warmuz
a3eedc294d
RevocationRequest.certificate auto decode/encode. 2015-02-12 15:44:05 +00:00
Jakub Warmuz
77a637b7f0
Fix save_certificate (Certificate.chain is decoded already). 2015-02-12 15:03:58 +00:00
Jakub Warmuz
a2e807debf
acme.util.ComparableX509 2015-02-12 14:40:19 +00:00
James Kasten
3a79607103 Add tests to satisfy empty list assumption of authenticator interface 2015-02-12 02:42:49 -08:00
Jakub Warmuz
a57574cbba
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	letsencrypt/client/tests/acme_util.py
2015-02-12 09:56:53 +00:00
James Kasten
23ba0166fe Fix quotes 2015-02-12 01:40:40 -08:00
James Kasten
d976de4104 fixed use before assignment bug 2015-02-12 01:32:26 -08:00
James Kasten
c28a945295 Clarify authenticator interface 2015-02-12 01:06:30 -08:00
James Kasten
050c29912d Merge pull request #241 from letsencrypt/cleanup
Remove IAuthenticator from client.py
2015-02-11 22:22:17 -08:00
James Kasten
1e22b49a49 Remove IAuthenticator from client.py 2015-02-11 22:13:42 -08:00
James Kasten
b7cfed9600 Merge pull request #240 from letsencrypt/libffi
Add libffi-dev to installation
2015-02-11 22:01:23 -08:00
James Kasten
982418e9c0 Merge branch 'cleanup' 2015-02-11 21:58:47 -08:00
James Kasten
d93e586d9b cleanup from kuba cleanup 2015-02-11 21:51:25 -08:00
James Kasten
0bc5c8a162 Correct reverter display methods 2015-02-11 17:04:50 -08:00
James Kasten
e399f7927f Add libffi-dev to installation 2015-02-11 17:00:31 -08:00
James Kasten
71dc5435c9 merge standalone, plus further development 2015-02-11 15:38:59 -08:00
James Kasten
0f3d8d17a0 Add coveralls 2015-02-11 14:44:03 -08:00
James Kasten
9dc14c0bce Merge branch 'kuba-cleanup' into cleanup 2015-02-11 13:12:02 -08:00
Jakub Warmuz
75b4add949 More quotes fixes 2015-02-11 12:51:23 -08:00
Jakub Warmuz
95fb2146c4 Cleanup after #232 2015-02-11 12:51:23 -08:00
Jakub Warmuz
2484d2e192 chmod -x standalone_authenticator 2015-02-11 12:51:23 -08:00
Jakub Warmuz
a09e4c11e6 x -> value 2015-02-11 12:51:22 -08:00
Jakub Warmuz
bdabdb519f Remove type() 2015-02-11 12:51:22 -08:00
Jakub Warmuz
a773c264c0 range -> xrange et al 2015-02-11 12:51:15 -08:00
Jakub Warmuz
30486f4445 add missing unittest.main() 2015-02-11 12:47:47 -08:00
Jakub Warmuz
2912a9f99b Organize imports (move to the top) 2015-02-11 12:47:47 -08:00
Jakub Warmuz
6fbdf9c2b3 Remove unreachable client.client.csr_pem_to_der 2015-02-11 12:47:37 -08:00
Jakub Warmuz
9476e7039b
Bump coverage to 73% 2015-02-11 16:41:01 +00:00
Jakub Warmuz
03726b9956
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	letsencrypt/client/le_util.py
	letsencrypt/client/tests/challenge_util_test.py
2015-02-11 16:40:07 +00:00
Jakub Warmuz
dad799d428
acme.messages.Message.get_msg_cls 2015-02-11 16:19:49 +00:00
schoen
b80ac49a04 Merge pull request #232 from letsencrypt/standalone_authenticator
Standalone authenticator
2015-02-10 18:01:35 -08:00
Seth Schoen
9cc7b0945b raise ValueError instead of raw Exception 2015-02-10 17:51:49 -08:00
James Kasten
4d7a673887 refactor client.namedtuples to le_util
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/le_util.py
	letsencrypt/client/tests/apache/dvsni_test.py
	letsencrypt/client/tests/challenge_util_test.py
2015-02-10 17:06:25 -08:00
Jakub Warmuz
edd207fef9
Fix typos. 2015-02-10 22:04:04 +00:00
Jakub Warmuz
d9e4a5ab86
Merge remote-tracking branch 'github/letsencrypt/master' into acme 2015-02-10 15:53:29 +00:00
James Kasten
a31500eb96 finish merge from hell 2015-02-10 00:55:40 -08:00
James Kasten
c09cf0fa07 Merge branch 'master' into revoker
Conflicts:
	letsencrypt/client/apache/configurator.py
	letsencrypt/client/client.py
	letsencrypt/client/crypto_util.py
	letsencrypt/client/interfaces.py
	letsencrypt/client/reverter.py
	letsencrypt/client/revoker.py
	letsencrypt/client/tests/apache/dvsni_test.py
	letsencrypt/client/tests/apache/parser_test.py
	letsencrypt/client/tests/challenge_util_test.py
	letsencrypt/scripts/main.py
2015-02-10 00:12:23 -08:00
James Kasten
92dae39383 Merge pull request #239 from letsencrypt/name_fix
fix local name changes
2015-02-09 16:45:38 -08:00
James Kasten
523c59d329 revert to binary redirect logic 2015-02-09 16:39:08 -08:00
James Kasten
9beded8cfb fix local name changes 2015-02-09 16:04:46 -08:00
Jakub Warmuz
cadd9ec028
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	letsencrypt/client/CONFIG.py
	letsencrypt/client/auth_handler.py
	letsencrypt/client/challenge_util.py
	letsencrypt/client/client.py
	letsencrypt/client/crypto_util.py
	letsencrypt/client/revoker.py
	letsencrypt/client/tests/challenge_util_test.py
2015-02-10 00:00:54 +00:00
Seth Schoen
82617c79b2 Remove two unused import statements 2015-02-09 15:26:46 -08:00
James Kasten
627302e060 Merge branch 'master' into standalone_authenticator
Conflicts:
	letsencrypt/client/CONFIG.py
	letsencrypt/client/tests/apache/dvsni_test.py
	letsencrypt/client/tests/challenge_util_test.py
2015-02-09 15:17:43 -08:00
James Kasten
35ea205ea1 Merge branch 'kuba-config' 2015-02-09 14:59:07 -08:00
James Kasten
e04ab25642 remove whitespace 2015-02-09 14:53:51 -08:00
Seth Schoen
b4418f72ff Improve docstrings 2015-02-09 14:42:16 -08:00
James Kasten
f476a49387 Fix #220 2015-02-09 14:39:49 -08:00
James Kasten
356ede0c8e Merge branch 'config' of git://github.com/kuba/lets-encrypt-preview into kuba-config 2015-02-09 14:31:52 -08:00
James Kasten
b5f0cf9adc Remove unnecessary mock 2015-02-09 14:31:25 -08:00
Seth Schoen
2591abd535 Change assertion to a ValueError in signal handler 2015-02-09 14:25:18 -08:00
Seth Schoen
d8fd3e4e61 Reorganizing import statements 2015-02-09 14:23:09 -08:00
Seth Schoen
93bc90203a Use hanging indent style in several places 2015-02-09 12:22:47 -08:00
Seth Schoen
314dea3f5a Change Key to le_util.Key 2015-02-09 12:05:59 -08:00
Seth Schoen
a543925e64 Some formatting fixes 2015-02-09 11:57:50 -08:00
Seth Schoen
3f250084b0 Sorting import statements 2015-02-09 10:06:24 -08:00
Seth Schoen
52ae977fb7 Sort imports lexicographically 2015-02-09 10:03:23 -08:00
Seth Schoen
275d3e3da5 Document and test start_listener() return value 2015-02-09 10:00:01 -08:00
Seth Schoen
470cad14ad Add missing return statements in start_listener() 2015-02-09 09:54:33 -08:00
Seth Schoen
6d0a14a0e5 Rename CONFIG.PORT to STANDALONE_CHALLENGE_PORT 2015-02-09 09:48:05 -08:00
Seth Schoen
56a60d0acf Move imports to top of unit test file 2015-02-09 09:44:49 -08:00
Seth Schoen
1c6865c329 Move abbreviated DVSNI code into a separate branch 2015-02-09 09:35:37 -08:00
James Kasten
9e46fcb219 modify test accordingly, green travis 2015-02-09 03:08:00 -08:00
James Kasten
5fb9cc4c39 pylint fixes 2015-02-09 02:47:45 -08:00
James Kasten
a311df9c2d Add tests for enhancments UI 2015-02-09 02:46:29 -08:00
James Kasten
f23b61d164 Add documentation for new display 2015-02-09 01:44:46 -08:00
James Kasten
8b184ca82c add failsafe, further revise 2015-02-09 01:43:54 -08:00
James Kasten
0cf4329936 full display integration 2015-02-09 00:12:43 -08:00
Seth Schoen
b324f9d912 Python 2.6 .format() requires parameter in format string 2015-02-08 21:15:49 -08:00
Seth Schoen
3453db0985 Eliminate use of Python 2.7-specific unittest features 2015-02-08 21:04:54 -08:00
James Kasten
72813ec1fa Merge branch 'master' into revoker 2015-02-08 21:03:16 -08:00
James Kasten
01899f387e Unittests for display_util 2015-02-08 21:02:23 -08:00
Seth Schoen
58ba7095f3 Merge branch 'master' into standalone_authenticator 2015-02-08 17:26:54 -08:00
Seth Schoen
f6e192bfaf Remove redundant import of client 2015-02-08 17:26:18 -08:00
Jakub Warmuz
0849e2d21e
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	tox.ini
2015-02-08 22:54:40 +00:00
James Kasten
bbd5c8ef2a Merge pull request #237 from kuba/bugs/235
Fix for #235
2015-02-08 14:16:53 -08:00
Seth Schoen
30c11920d9 Tests to follow new convention for subproc_state 2015-02-08 11:09:02 -08:00
Seth Schoen
76fb3b54e2 Satisfy pylint on various points 2015-02-08 11:06:04 -08:00
Seth Schoen
304ffab112 Merge branch 'master' into standalone_authenticator 2015-02-08 09:29:45 -08:00
Jakub Warmuz
74c02363e7
tox: PYTHONPATH that includes linter_plugin 2015-02-08 12:59:47 +00:00
Jakub Warmuz
7be419a2ca
Add linter_plugin.py to MANIFEST.in 2015-02-08 12:47:21 +00:00
Jakub Warmuz
bcb9224301
Fix "lint" and "providedBy" build errors 2015-02-08 12:22:16 +00:00
Jakub Warmuz
b1552052bb
tox: pip install -e (fixes #235) 2015-02-08 11:54:25 +00:00
Jakub Warmuz
9d44b10aff
Quickfix for #235 2015-02-08 10:01:36 +00:00
James Kasten
168a70c273 refactor and enhance display, update revoker 2015-02-08 00:46:16 -08:00
Seth Schoen
33ff8225d7 Merge branch 'master' into standalone_authenticator 2015-02-08 00:35:07 -08:00
Jakub Warmuz
a0c184f292
Update docs references for constants.ENHANCEMENTS 2015-02-07 22:36:44 +00:00
Jakub Warmuz
4b6baae8b8
Merge remote-tracking branch 'github/letsencrypt/master' into config 2015-02-07 22:32:38 +00:00
Jakub Warmuz
ec3bb4cf13
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	tox.ini
2015-02-07 22:30:24 +00:00
James Kasten
12d3493812 Merge pull request #234 from kuba/travis
No pylint in py2.6 tox
2015-02-07 14:09:38 -08:00
Jakub Warmuz
9d52cb6adc
ImmutableMap: repr recursively 2015-02-07 07:45:33 +00:00
Seth Schoen
50a6a28e73 Exclude tls_parse_client_hello from coverage
(As an alternative to commenting out this function, which is currently
totally unused.  The function does have tests; they just don't cover
the failure cases.)
2015-02-06 19:02:17 -08:00
Seth Schoen
4b8eae1084 Small changes to try to make pylint happier 2015-02-06 16:12:37 -08:00
Seth Schoen
52d1989850 Complete move of Key into le_util 2015-02-06 15:54:08 -08:00
Jakub Warmuz
337974e675
No pylint in py2.6 tox 2015-02-06 23:41:28 +00:00
Seth Schoen
31b1369752 Improve coverage for perform() error cases 2015-02-06 15:28:27 -08:00
Seth Schoen
5a9e394827 Test trying to perform challenges with others pending 2015-02-06 15:25:12 -08:00
Seth Schoen
65de5fa71e Further improvements to test coverage 2015-02-06 15:21:00 -08:00
Seth Schoen
b61708c47f Improving test coverage 2015-02-06 15:08:48 -08:00
Jakub Warmuz
13128464aa
ACME: pylint lint plugin 2015-02-06 22:54:28 +00:00
Seth Schoen
ee8e4343bc Reformat continuation indentations 2015-02-06 14:46:45 -08:00
Jakub Warmuz
ec4dc6905f
Merge remote-tracking branch 'github/letsencrypt/master' into acme
Conflicts:
	letsencrypt/client/tests/auth_handler_test.py
2015-02-06 21:33:53 +00:00
Jakub Warmuz
900b50642a
ACME tests: Message.to_json, test_json_without_optionals. 2015-02-06 21:26:43 +00:00
Jakub Warmuz
a990b0ff77
100% coverage for acme 2015-02-06 17:55:33 +00:00
Jakub Warmuz
753b9ca15c
Use new framework for ACME messages 2015-02-06 16:38:35 +00:00
Jakub Warmuz
fe98a4ca48
JSONDeSerializable; ImmutableMap: Signature and JWK 2015-02-06 16:05:43 +00:00
Seth Schoen
53f3f19f6d Check invalid cases for pack and unpack functions 2015-02-05 15:57:10 -08:00
Seth Schoen
8997248abb Use caller-specified TCP port no. in UI messages 2015-02-05 13:14:03 -08:00
Seth Schoen
db3c98b45a Adding some docstrings 2015-02-05 13:03:20 -08:00
Seth Schoen
7c39e36c5a Merge branch 'master' into standalone_authenticator 2015-02-05 12:53:55 -08:00
Jakub Warmuz
79af38cd1b
ACME Signature: JWK with pubkey only 2015-02-05 09:40:52 +00:00
Seth Schoen
ef34c06c8f Convert two assertions to exceptions 2015-02-04 23:26:10 -08:00
Seth Schoen
41284ffc0a Let tests specify how long parent waits for child process 2015-02-04 22:36:24 -08:00
Seth Schoen
ff3c0c6689 Comment out debug print statement 2015-02-04 22:33:40 -08:00
Seth Schoen
3d2f564478 Replace some call_counts with more specific assertions 2015-02-04 22:32:27 -08:00
Seth Schoen
f9b0d8d0bf Add unit tests for listener child process 2015-02-04 22:17:11 -08:00
Jakub Warmuz
d68e4d564d
ACME Signature: proper verify, tests 2015-02-04 22:49:55 +00:00
Jakub Warmuz
a22f8b09ef
json_object -> jobj 2015-02-04 22:04:38 +00:00
Jakub Warmuz
cff337723e
jose.b64 authorizationRequest nonce 2015-02-04 22:04:11 +00:00
Jakub Warmuz
7515a9800c
Unify quotes in acme 2015-02-04 20:24:15 +00:00
Jakub Warmuz
ebd9bbed90
Move CONFIG.NONCE_SIZE to acme.other 2015-02-04 20:15:40 +00:00
Jakub Warmuz
e73e207b57
Move jose b64 to acme.jose 2015-02-04 20:12:07 +00:00
Jakub Warmuz
f910b7ee6b
Remove unused import 2015-02-04 16:40:57 +00:00
Jakub Warmuz
e9512e5a46
Add tests and docs for IConfig/NamespaceConfig 2015-02-04 16:38:13 +00:00
Seth Schoen
e9b67ff6f9 Several more unit tests for StandaloneAuthenticator 2015-02-03 17:59:35 -08:00
Seth Schoen
63bf55a748 Split out parent and child listeners into methods 2015-02-03 15:51:37 -08:00
Seth Schoen
191b0d7be4 Add unit test for tls_generate_cert_msg 2015-02-03 14:23:41 -08:00
James Kasten
dcf4b1d8f2 Merge pull request #227 from ThomasWaldmann/check-mode
add mode checking to 2 unit tests
2015-02-03 12:37:39 -08:00
Jakub Warmuz
207bd6c31c
IConfig attributes. config/args separation 2015-02-03 12:16:55 +00:00
Jakub Warmuz
43e207f9d0
API docs: Remove CONFIG, add configuration. 2015-02-03 12:16:38 +00:00
Jakub Warmuz
9fb56f31d3
NamespaceConfig 2015-02-03 11:22:04 +00:00
James Kasten
e000cfd7c6 Merge branch 'master' into revoker
Conflicts:
	letsencrypt/client/challenge_util.py
	letsencrypt/client/display.py
	letsencrypt/client/tests/challenge_util_test.py
	letsencrypt/client/tests/client_test.py
	letsencrypt/scripts/main.py
2015-02-02 19:02:38 -08:00
James Kasten
5698bc3e20 refactor client.namedtuples to le_util 2015-02-02 18:11:48 -08:00
James Kasten
4540b85ade formatting... 2015-02-02 17:28:34 -08:00
Thomas Waldmann
42cd153ac4 add mode checking to 2 unit tests 2015-02-03 02:10:11 +01:00
Seth Schoen
361478eca7 Initial set of unit tests for standalone authenticator 2015-02-02 17:03:48 -08:00
Seth Schoen
220c61f124 Small fixes to how errors are reported 2015-02-02 17:03:29 -08:00
Seth Schoen
867b719de5 Move Key namedtuple definition into le_util.py 2015-02-02 17:02:59 -08:00
Seth Schoen
c43ecf924c Declare dependency on PyOpenSSL package 2015-02-02 17:02:22 -08:00
James Kasten
21147a8163 initial display revision 2015-02-02 16:17:57 -08:00
James Kasten
a740d530c3 Merge pull request #218 from ThomasWaldmann/docs-version
use version from package init also for sphinx docs, insert toplevel dir ...
2015-02-02 15:13:31 -08:00
Jakub Warmuz
1d45b466a3
IConfig.apache_server_root without trailing slash 2015-02-02 21:42:30 +00:00
Jakub Warmuz
4357c625c4
Fix typo: rev_token_dir(s) 2015-02-02 19:17:03 +00:00
Jakub Warmuz
7828853e8c
Remove "direc" magic, use IConfig instead. f(config.x, config) -> f(config) 2015-02-02 17:41:44 +00:00
James Kasten
da9d1c0fec Merge pull request #212 from letsencrypt/patch_auth_handler_cleanup
Patch auth handler cleanup
2015-02-02 05:00:27 -08:00
James Kasten
860a9a77b0 Fix DnsChall 2015-02-02 02:02:11 -08:00
James Kasten
687668ec75 indentation pylint fixes 2015-02-02 01:52:54 -08:00
James Kasten
4d247fa6a1 formatting fixes for testing code 2015-02-02 00:47:39 -08:00
Jakub Warmuz
9580a763e1
key_size/keysize -> rsa_key_size 2015-02-02 08:20:55 +00:00
Jakub Warmuz
a6addfa55a
IJSONSerializable Message, Signature, JWK 2015-02-01 23:07:27 +00:00
Jakub Warmuz
143b002d7e
Move acme to letsencrypy.acme 2015-02-01 12:27:36 +00:00
Thomas Waldmann
c42f512c2a refactor conf.py, add comment about version syntax 2015-02-01 03:15:39 +01:00
Jakub Warmuz
787f791a4e
--apache-ctl wihout dirname 2015-01-31 14:18:01 +00:00
Jakub Warmuz
0f22318c2f
pylint ignore no-member in ConfArgParse 2015-01-31 12:32:43 +00:00
Jakub Warmuz
098f779a79
Use ConfArgParse 2015-01-31 11:37:24 +00:00
Jakub Warmuz
b6de602b5b
Move CONFIG to CLI arguments 2015-01-31 11:31:29 +00:00
Jakub Warmuz
687541505b
IConfig, constants 2015-01-31 11:31:29 +00:00
James Kasten
c59dc61cf0 Merge pull request #219 from letsencrypt/unreachable_old_code
Remove associated docs
2015-01-31 02:32:37 -08:00
James Kasten
f082aa3186 Remove associated docs 2015-01-31 02:20:15 -08:00
James Kasten
ef29b6e8dc Merge pull request #217 from letsencrypt/unreachable_old_code
Unreachable old code
2015-01-31 01:16:05 -08:00
Thomas Waldmann
44c2b38cde use version from package init also for sphinx docs, insert toplevel dir into sys.path 2015-01-31 06:01:52 +01:00
James Kasten
5431c931bb Merge pull request #216 from kuba/docs-interfaces
Autodoc interfaces. Spelling.
2015-01-30 19:31:11 -08:00
James Kasten
eadf169f41 Merge pull request #215 from kuba/interfaces-inherit-non-class
interfaces: disable inherit-non-class pylint msg
2015-01-30 18:39:52 -08:00
Jakub Warmuz
f890dabb7e
Improve interfaces docstrings 2015-01-31 01:23:42 +00:00
Jakub Warmuz
1725829477
Autodoc interfaces. Spelling. 2015-01-31 00:28:09 +00:00
Seth Schoen
2409aa6475 Initial commit for standalone authenticator branch 2015-01-30 16:08:18 -08:00
James Kasten
29531bf414 remove setup.sh 2015-01-30 15:49:31 -08:00
Jakub Warmuz
e6a88c4e27
interfaces: disable inherit-non-class pylint msg 2015-01-30 21:20:40 +00:00
James Kasten
94c3eb0533 remove old code 2015-01-30 12:03:55 -08:00
James Kasten
2cb4ab936d generator to list to conform to API 2015-01-29 20:07:29 -08:00
James Kasten
bd8b908f50 patch auth_handler cleanup function 2015-01-29 19:35:31 -08:00
James Kasten
e56737de0b Merge pull request #207 from ThomasWaldmann/version
have letsencrypt.VERSION, show it in letsencrypt --help, use it in setup.py
2015-01-29 16:21:53 -08:00
Thomas Waldmann
4c7b2d202c cosmetic fixes 2015-01-29 23:28:22 +01:00
Thomas Waldmann
47e49215c3 long_description = README.rst (+ CHANGES.rst later) 2015-01-29 15:38:20 +01:00
Thomas Waldmann
af8edbc21c put file reading into function, we'll soon need that for more stuff
like e.g. reading long_description from README.rst + CHANGES.rst.
2015-01-29 15:25:28 +01:00
Thomas Waldmann
219b25cd98 parse version number from package init, avoid package import 2015-01-29 14:58:20 +01:00
James Kasten
382386159c Merge pull request #208 from letsencrypt/dead_code_removal
sanity_check - dead code removal
2015-01-29 02:11:28 -08:00
James Kasten
f6f6b79221 fix comment spelling 2015-01-29 00:17:45 -08:00
Thomas Waldmann
9c98e1e7e5 have letsencrypt.VERSION, show it in letsencrypt --help, use it in setup.py
note: we had some discussion about potential problems importing VERSION from main package.

SO link: http://stackoverflow.com/questions/2058802/how-can-i-get-the-version-defined-in-setup-py-setuptools-in-my-package

See also my comment in __init__.py - maybe we can add that "version detection from git tags" magic later.
2015-01-29 01:20:45 +01:00
James Kasten
a5f65dcfd8 pep8 - remove extra whitespace 2015-01-28 16:20:09 -08:00
James Kasten
e9a6d6039b Remove half-implemented code... leave TODO 2015-01-28 16:18:40 -08:00
James Kasten
c5db07cf0a Merge pull request #206 from kuba/travis
Simpler (and quicker) Travis CI matrix
2015-01-28 14:05:14 -08:00
Jakub Warmuz
79bb3cc80d
pylint: upstream fixed #248 in 1.4.0 2015-01-28 13:02:14 +00:00
Jakub Warmuz
0a44bbb7a1
Simpler Travis CI matrix 2015-01-28 12:56:12 +00:00
James Kasten
5a46a1e32e Merge pull request #205 from letsencrypt/gen_dvsni_chall
gen_dvsni_chall remove filepath
2015-01-28 00:39:27 -08:00
James Kasten
6baeede302 git push origin masterMerge branch 'ThomasWaldmann-docs' 2015-01-27 17:07:28 -08:00
James Kasten
d43b1dbf92 range->xrange 2015-01-27 14:53:28 -08:00
James Kasten
624e59d381 update intro paragraph of README 2015-01-27 14:41:23 -08:00
James Kasten
8a46c39fec Merge branch 'docs' of git://github.com/ThomasWaldmann/lets-encrypt-preview into ThomasWaldmann-docs 2015-01-26 22:45:25 -08:00
James Kasten
03a5750d90 modify gen_dvsni_cert api 2015-01-26 22:25:08 -08:00
James Kasten
8c80d0bedf Merge pull request #185 from letsencrypt/reverter
Reverter
2015-01-26 17:28:46 -08:00
James Kasten
eeb58d9232 pep8 fix display 2015-01-26 14:24:45 -08:00
James Kasten
1831062529 cleanup errors.py use in client_test, small changes to display doc 2015-01-26 14:21:37 -08:00
Thomas Waldmann
fb2d8061c8 docs: markup fixes, separate section for api docs, link to demo video, improved phrasing 2015-01-26 14:58:24 +01:00
James Kasten
243cc4f9fb Fixed print, fixed logging, made display work 2015-01-26 04:49:40 -08:00
James Kasten
2db2060f85 restore logging in tearDown 2015-01-26 02:18:15 -08:00
James Kasten
73b95c4307 Fix based on comments 2015-01-26 01:27:00 -08:00
James Kasten
ae4c160654 import errors at the top 2015-01-26 01:07:50 -08:00
James Kasten
f9d968071e Document raises reverter error in rollback func 2015-01-26 00:33:58 -08:00
James Kasten
3d26cfca90 Remove unused static string from CONFIG 2015-01-25 23:14:36 -08:00
James Kasten
a281a88f49 Merge branch 'master' into reverter 2015-01-25 23:04:37 -08:00
James Kasten
343f25cc89 Remove partially implemented revoker compatibility code 2015-01-25 23:01:48 -08:00
James Kasten
7577894d2a Remove duplicate code check on imports 2015-01-25 21:46:41 -08:00
James Kasten
248407c70e Merge pull request #197 from ThomasWaldmann/help-no-sudo
check late for root, so letsencrypt --help also works without being root
2015-01-25 19:19:27 -08:00
Thomas Waldmann
7e7093e821 check late for root, so letsencrypt --help also works without being root 2015-01-26 01:21:15 +01:00
Thomas Waldmann
0a14007db2 refactor docs, please check
- README has only the most important infos that a new reader needs in his first minute
  of contact with the project (to decide whether it is interesting or not)
- CHANGES shall later be a curated change log (== important changes between releases)
- separate docs into intro, using, project
- intro docs = include README, CHANGES (avoid duplication)
2015-01-25 21:30:24 +01:00
James Kasten
71c4a07687 Merge pull request #193 from ThomasWaldmann/docs
remove out-of-sync cli help, just say how to get it
2015-01-25 05:10:04 -08:00
James Kasten
241533d123 Merge pull request #192 from letsencrypt/irc-notifications
Add IRC notifications
2015-01-25 05:08:01 -08:00
Thomas Waldmann
6fbc6b2750 remove out-of-sync cli help, just say how to get it 2015-01-25 14:02:54 +01:00
James Kasten
98c47162da Add IRC notifications 2015-01-25 04:57:36 -08:00
James Kasten
ffbccd1ff1 attempt to remove final pylint errors 2015-01-25 04:49:21 -08:00
James Kasten
4ad01f07f7 Merge branch 'master' into reverter
Conflicts:
	letsencrypt/client/augeas_configurator.py
2015-01-25 04:28:09 -08:00
James Kasten
208e7ec34b refactoring/small fixes for PR 2015-01-25 04:24:05 -08:00
James Kasten
89c7eae2c8 Merge pull request #190 from kuba/docs
Link to Read The Docs
2015-01-25 03:50:25 -08:00
Jakub Warmuz
8a7c6340e8
Link to Read The Docs 2015-01-25 11:36:55 +00:00
James Kasten
df62f36c5f Merge pull request #189 from kuba/pylint
Fix locally disabled pylint messages
2015-01-25 02:31:04 -08:00
Jakub Warmuz
3734710f1c
Fix locally disable pylint messages 2015-01-25 10:23:21 +00:00
James Kasten
8c6cfaded0 Merge branch 'master' into reverter
Conflicts:
	letsencrypt/client/apache/configurator.py
	letsencrypt/client/crypto_util.py
	letsencrypt/client/display.py
	letsencrypt/client/revoker.py
	letsencrypt/client/tests/apache/configurator_test.py
	letsencrypt/client/tests/apache/dvsni_test.py
	letsencrypt/scripts/main.py
2015-01-24 21:42:26 -08:00
James Kasten
9478d14817 Merge pull request #183 from ThomasWaldmann/pathnames
fix for issue #179
2015-01-24 20:38:43 -08:00
James Kasten
9b0b7b2e28 Merge pull request #188 from kuba/bugs/152
Magical fix for #152 (M2Crypto install dependency order)
2015-01-24 20:01:32 -08:00
James Kasten
967047cb04 git push origin masterMerge branch 'ThomasWaldmann-readme-fix' 2015-01-24 19:58:08 -08:00
James Kasten
1d570541e0 Merge branch 'readme-fix' of git://github.com/ThomasWaldmann/lets-encrypt-preview into ThomasWaldmann-readme-fix 2015-01-24 19:56:22 -08:00
James Kasten
ca0ea80192 Merge pull request #186 from kuba/pylint
Pylint 10/10
2015-01-24 19:17:57 -08:00
Thomas Waldmann
55754c6697 remove openssl package from requirements, might be only needed for some special branches 2015-01-24 21:44:52 +01:00
Jakub Warmuz
f12ef3b933
whitespace 2015-01-24 20:30:58 +00:00
Jakub Warmuz
6a4164a293
Magical fix for #152 (M2Crypto install dependency order) 2015-01-24 19:50:51 +00:00
Jakub Warmuz
982fa19628
Bump cover-min-percentage to 61 2015-01-24 14:33:19 +00:00
Jakub Warmuz
e9ed8c86e4
Quickfix for #187 2015-01-24 14:27:15 +00:00
Jakub Warmuz
a507293bd8
Quickfix for #147 2015-01-24 14:09:46 +00:00
Jakub Warmuz
8ef9e13e09
pylint: ignore providedBy 2015-01-24 13:59:19 +00:00
Jakub Warmuz
1107c5a971
pylint: locally disable missing-docstring 2015-01-24 13:40:35 +00:00
Jakub Warmuz
ceebb09d89
Add some docs 2015-01-24 13:40:05 +00:00
Jakub Warmuz
b9723b6cad
Fix line-too-long from master 2015-01-24 13:16:00 +00:00
Jakub Warmuz
844d83cc0e
Merge remote-tracking branch 'github/letsencrypt/master' into pylint 2015-01-24 13:13:55 +00:00
Jakub Warmuz
48f1497af6
Fix docs warnings 2015-01-24 13:13:13 +00:00
Jakub Warmuz
23cab4e694
pylint: 10/10 with missing-docstring 2015-01-24 13:12:45 +00:00
James Kasten
f30bf2ca87 pylint cleanup 2015-01-24 03:31:33 -08:00
James Kasten
2ff3c8396e Fix revocation for "None" installer 2015-01-24 03:00:01 -08:00
James Kasten
4e3f4f8f24 Merge branch 'master' into reverter
Conflicts:
	letsencrypt/client/crypto_util.py
	letsencrypt/scripts/main.py
2015-01-24 02:30:51 -08:00
James Kasten
f67db5ef43 Prepare for "None" and misconfigured Installers 2015-01-24 02:15:23 -08:00
James Kasten
5fa42b200a Merge pull request #177 from ThomasWaldmann/server-port
talk to servers on non-standard https port (like -s acme.example.org:8443)
2015-01-23 22:23:28 -08:00
Thomas Waldmann
002f98c720 add missing dependencies to README, fixes #151 2015-01-24 05:57:59 +01:00
Thomas Waldmann
ea5015be14 better name for KEY_DIR, improve unique_file, adapt tests
unique_file now generates filenames that sort correctly and also are better
viisible / differentiable in narrow file manager filename columns (like e.g. mc).
2015-01-24 01:32:39 +01:00
James Kasten
6a501c1380 Merge pull request #175 from ThomasWaldmann/keysize
support --keysize N cmdline param to give RSA key size
2015-01-23 15:24:34 -08:00
Thomas Waldmann
47e11a5824 use lazy logging 2015-01-23 23:39:02 +01:00
Thomas Waldmann
b72897677e use default value for -s argument 2015-01-23 23:29:07 +01:00
Thomas Waldmann
aadf6da7bf show default RSA_KEY_SIZE in cmdline help 2015-01-23 14:21:59 +01:00
Thomas Waldmann
e58c344649 handle invalid key sizes in a helpful way 2015-01-23 14:17:14 +01:00
Thomas Waldmann
93b247c483 misc. improvements to ACME servername:port code
changed ACME_SERVER to include the port. this is so the commandline options help will also display the port
and also to make it clear at the constant definition that giving a port is supported there.

improved docstrings
2015-01-23 13:56:57 +01:00
James Kasten
88d016b8ca Merge branch 'master' into reverter 2015-01-23 02:06:25 -08:00
James Kasten
00371ad5eb Merge pull request #176 from ThomasWaldmann/textmode-fix
fix exception when using FileDisplay (-t), was missing the redirect_by_default method
2015-01-22 22:34:59 -08:00
James Kasten
417183165e 100% unittests for reverter, code cleanup 2015-01-22 21:51:25 -08:00
Thomas Waldmann
8070b78e3d talk to servers on non-standard https port (like -s acme.example.org:8443)
note:

I removed Network.server attribute, it was not used (only .server_url is used).

I also removed the sanity-check for the ACME server (so one can use server:port syntax, which was considered invalid).
I don't think this sanity-check is needed, because in the end, we can't fully check if it is correct anyway (you can always
give valid, but non-working server names or ports).
Also, many people will use the default ACME server, which is builtin and correct.
2015-01-23 04:57:47 +01:00
Thomas Waldmann
8d32a9a4d8 fix exception when using FileDisplay (-t), class was missing the redirect_by_default method
moved common stuff to CommonDisplayMixin class.
2015-01-23 04:20:47 +01:00
Thomas Waldmann
b0becec26e support --keysize N cmdline param to give RSA key size
also: improve tests for usual key sizes
2015-01-23 03:38:10 +01:00
James Kasten
7a238bd0de Forgot to finish merge with augeas.py 2015-01-22 02:53:20 -08:00
James Kasten
cd347990c5 Merge branch 'master' into reverter
Conflicts:
	letsencrypt/client/apache/parser.py
	letsencrypt/client/augeas_configurator.py
2015-01-22 02:48:54 -08:00
James Kasten
bedbd2e315 Initial implementation of reverter, showing main.py capabilities 2015-01-22 02:36:36 -08:00
James Kasten
16e9d887ab Merge pull request #170 from letsencrypt/fix_168
Fix 168
2015-01-20 22:16:49 -08:00
James Kasten
9729450d2c Change names to meet the API 2015-01-20 21:59:10 -08:00
James Kasten
6927c1b910 Add manual httpd_transform 2015-01-20 21:13:56 -08:00
James Kasten
55e13a906d Change incl to list to conform with augeas.add_transform func 2015-01-20 19:29:00 -08:00
James Kasten
a29984f049 Remove TODO item/fix formatting. 2015-01-20 14:03:05 -08:00
James Kasten
269d49b759 Remove load of unused modules from Augeas 2015-01-20 13:51:52 -08:00
James Kasten
8a3eb4f2ba Update interface.py documentation...
Correctly list namedtuple challenge types in challenge_util.py
2015-01-19 16:01:44 -08:00
James Kasten
1dc7bfccc4 Address startup issues, attempt to identify misconfiguration issues with proper errors 2015-01-19 03:15:31 -08:00
James Kasten
66884e05cf Merge pull request #167 from letsencrypt/requests_verify
Make requests certificate verification explicit
2015-01-18 00:25:42 -08:00
James Kasten
f28bc32129 Make requests certificate verification explicit 2015-01-18 00:14:29 -08:00
James Kasten
488859d03a Merge pull request #165 from letsencrypt/installer_api
Installer API update
2015-01-18 00:07:49 -08:00
James Kasten
1b8edd7448 Removed commented functions before push to master 2015-01-18 00:00:52 -08:00
James Kasten
c9a3d8b0c2 Cleanup Installer API change 2015-01-17 02:29:29 -08:00
James Kasten
7f6837a105 merge with master 2015-01-16 16:43:59 -08:00
James Kasten
b23499de12 update interface documentation 2015-01-16 15:22:01 -08:00
James Kasten
5a31176ea6 add html to sphinx-build instructions 2015-01-16 04:18:30 -08:00
James Kasten
91e8b0cf81 Should use venv sphinx-build 2015-01-16 04:16:57 -08:00
James Kasten
d15e0f9e43 Add documentation instructions/fix documentation of an ivar 2015-01-16 04:10:23 -08:00
James Kasten
3cf59a2876 Merge pull request #129 from letsencrypt/pylintrc
Pylintrc
2015-01-16 03:28:06 -08:00
James Kasten
dfae3f6d9e remove deprecated options 2015-01-16 03:25:20 -08:00
James Kasten
a0969b1f29 updated pylintrc file to 1.3.1 version. 2015-01-16 03:14:24 -08:00
James Kasten
35aeef417e Merge pull request #163 from letsencrypt/temp_ssl_options_copy
replaced options-ssl.conf move to avoid unnecesary problems, also placed...
2015-01-15 03:36:53 -08:00
James Kasten
31cfe7cfe6 replaced options-ssl.conf move to avoid unnecesary problems, also placed the copy more appropriately within configurator 2015-01-15 03:25:39 -08:00
James Kasten
4b44befefa Initial attempt at API modification and associated changes 2015-01-15 01:43:54 -08:00
James Kasten
55694f82b1 Merge pull request #162 from letsencrypt/dvsni_cleanup
Authenticator Cleanup
2015-01-15 01:25:05 -08:00
James Kasten
d22ce3128c Return acme_auth for better api 2015-01-13 17:34:19 -08:00
James Kasten
2114f61164 better authorization logging 2015-01-13 17:31:11 -08:00
James Kasten
32f628d3d2 use path param 2015-01-13 17:10:39 -08:00
James Kasten
ce13ead0cd pep8 compliance 2015-01-13 16:48:18 -08:00
James Kasten
d0c9e4fc07 Remove assertIs - not supported in 2.6 2015-01-13 01:41:56 -08:00
James Kasten
f547d761f4 Merge branch 'dvsni_cleanup' 2015-01-13 01:24:55 -08:00
James Kasten
ddbe8e7b29 Add more index specific documentation 2015-01-13 01:22:24 -08:00
James Kasten
0dddcd1ffa cleanup branch 2015-01-13 01:03:21 -08:00
James Kasten
ed94479775 Merge pull request #161 from ThomasWaldmann/fixes
some more little issues
2015-01-12 17:36:10 -08:00
Thomas Waldmann
97a4f27af6 remove redundant parentheses 2015-01-13 02:26:39 +01:00
Thomas Waldmann
0e2a4984b1 remove superfluous assignment 2015-01-13 02:22:24 +01:00
James Kasten
c091b9ac63 Merge pull request #160 from ThomasWaldmann/fixes
fix typos
2015-01-12 17:07:59 -08:00
Thomas Waldmann
541e006ad0 fix typos 2015-01-13 01:51:03 +01:00
James Kasten
ae20f2fd7d tests for new code 2015-01-12 05:44:06 -08:00
James Kasten
be5ae7ae9a Created auth_handler and client_authenticator. Use dicts for all messages and keep client clean. 2015-01-10 05:19:22 -08:00
James Kasten
8f062ddc54 update documentation for recovery_token 2015-01-09 22:29:46 -08:00
James Kasten
ca7628caae Small changes/renaming 2015-01-09 22:25:36 -08:00
James Kasten
0bef6769ba cleanup challenge infrastructure 2015-01-09 05:30:15 -08:00
James Kasten
8eaeb1bc5e Merge branch 'dvsni_cleanup' of github.com:letsencrypt/lets-encrypt-preview into dvsni_cleanup
Conflicts:
	letsencrypt/client/apache/configurator.py
2015-01-06 02:26:52 -08:00
James Kasten
21b8e10560 Add dvsni documentation 2015-01-06 02:15:24 -08:00
James Kasten
73ec1311c0 Initial challenge refactor/allow multiple names 2015-01-06 02:11:44 -08:00
James Kasten
a2a64e9410 Turn DVSNI into module, add more appropriate challenges/api 2015-01-06 02:11:44 -08:00
James Kasten
7c23a2f2aa Use DVSNI_Chall namedtuple 2015-01-06 02:02:18 -08:00
James Kasten
f089449bf2 Initial challenge refactor/allow multiple names 2015-01-06 01:57:07 -08:00
James Kasten
759e233aaa Merge pull request #157 from letsencrypt/fix_155
Fix #155
2015-01-02 14:32:17 -08:00
James Kasten
96f288861b Fix #155 2015-01-02 14:25:17 -08:00
James Kasten
59ec8aa280 Moved options-ssl.conf into apache dir 2015-01-01 08:20:33 -08:00
James Kasten
bb6d84c255 Merge pull request #146 from ThomasWaldmann/master
fix typos, including a severe one
2014-12-24 22:57:44 -08:00
James Kasten
2f6bbe7593 Merge pull request #145 from kuba/docs
Add API docs for client.network
2014-12-24 22:38:43 -08:00
Thomas Waldmann
3cc15ee909 fix typos, including a severe one
bugfix see configurator.py:233
2014-12-24 16:45:57 +01:00
Jakub Warmuz
3efca70a56 Add API docs for client.network 2014-12-24 06:56:14 +00:00
James Kasten
22e4d2ff34 Merge pull request #144 from ThomasWaldmann/master
bug fix + some small fixes
2014-12-23 19:55:08 -08:00
Thomas Waldmann
992830f2c2 fix typos, including a severe one 2014-12-24 04:42:03 +01:00
Thomas Waldmann
5584bb4e6f fix param names in docstrings to match actual param names 2014-12-24 04:40:16 +01:00
James Kasten
52e8761dea Merge pull request #143 from kuba/docs
Update API docs
2014-12-23 17:20:02 -08:00
James Kasten
05d803ddd3 Turn DVSNI into module, add more appropriate challenges/api 2014-12-23 03:54:30 -08:00
Jakub Warmuz
2893b25db1 Update API docs 2014-12-23 10:59:33 +00:00
James Kasten
c025f25367 Remove nginx_configurator from master 2014-12-22 00:41:54 -08:00
James Kasten
eb99571a98 Use DVSNI_Chall namedtuple 2014-12-22 00:29:33 -08:00
James Kasten
684a9aae9d git push origin masterMerge branch 'kuba-zope.interface-display' 2014-12-21 21:41:45 -08:00
James Kasten
7860db63cf Make necessary fixes to pull request 2014-12-21 21:41:12 -08:00
James Kasten
1b2896aff2 Merge branch 'zope.interface-display' of git://github.com/kuba/lets-encrypt-preview into kuba-zope.interface-display 2014-12-21 21:22:31 -08:00
James Kasten
99d3ba3c16 Merge pull request #133 from kuba/zope.interface
zope.interface
2014-12-21 20:25:39 -08:00
Jakub Warmuz
5958aee5d1 Merge branch 'zope.interface' into zope.interface-display
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/scripts/main.py
2014-12-22 01:26:31 +00:00
Jakub Warmuz
ce9f4b0849 Merge branch 'master' into zope.interface
Conflicts:
	letsencrypt/client/recovery_token_challenge.py
2014-12-22 01:04:48 +00:00
Jakub Warmuz
b12614e5e1 Merge branch 'master' into zope.interface
Conflicts:
	letsencrypt/client/augeas_configurator.py
	letsencrypt/client/interfaces.py
2014-12-22 01:02:39 +00:00
James Kasten
580dd952af Merge pull request #132 from kuba/pep8
pylint fixes
2014-12-21 16:51:10 -08:00
Jakub Warmuz
a4020c76df Merge branch 'master' into pep8
Conflicts:
	letsencrypt/client/apache/configurator.py
	letsencrypt/client/client.py
	letsencrypt/client/tests/apache_configurator_test.py
2014-12-22 00:42:28 +00:00
James Kasten
4de3b6a340 Merge pull request #135 from letsencrypt/configurator_tests
Configurator and Client Refactor
2014-12-21 16:33:17 -08:00
James Kasten
e78a2884e2 Final cleanup 2014-12-21 16:32:01 -08:00
James Kasten
be64ba35cf Merge branch 'master' into configurator_tests
Conflicts:
	letsencrypt/client/apache/configurator.py
	setup.py
2014-12-21 16:13:00 -08:00
James Kasten
fe1b858dff Fix documentation 2014-12-21 04:25:48 -08:00
James Kasten
a3bee91e35 Merge branch 'master' into configurator_tests
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/crypto_util.py
2014-12-21 04:17:07 -08:00
James Kasten
bfa04c1942 Refactor for revocation 2014-12-21 03:37:29 -08:00
James Kasten
1d9f208cdd Merge pull request #138 from fmarier/make-script-executable
Fix the sh path and make script executable
2014-12-20 21:27:51 -08:00
Francois Marier
e2957797b5 Fix the sh path and make script executable 2014-12-21 14:23:14 +13:00
James Kasten
d7a3ea443c Formatting fixes 2014-12-20 03:43:39 -08:00
James Kasten
cc999d3654 Refactored/added tests 2014-12-19 15:49:29 -08:00
James Kasten
6d0d439acf Fix #137
find_directive uses the unitialized member location in set_user_config when httpd.conf is present.  I changed set_user_config_file to use the root path as a start.
2014-12-17 19:23:43 -08:00
James Kasten
083024cb86 Major refactor of client and main 2014-12-17 06:27:21 -08:00
Jakub Warmuz
37a015f983 display using ZCA 2014-12-17 12:03:41 +01:00
Jakub Warmuz
7c3abe7ba7 IDisplay 2014-12-17 11:44:34 +01:00
Jakub Warmuz
aabe83c444 Merge branch 'display-refactor' into zope.interface-display 2014-12-17 11:39:51 +01:00
Jakub Warmuz
3a459c95c9 Remove SingletonD 2014-12-17 11:35:45 +01:00
Jakub Warmuz
49914f3307 display: width and height instance variables 2014-12-17 11:35:11 +01:00
Jakub Warmuz
20efe7b533 zope.interface 2014-12-17 10:12:59 +01:00
Jakub Warmuz
1ef2167254 pylint fixes 2014-12-17 09:14:27 +01:00
James Kasten
606584cb14 Merge pull request #131 from kuba/setuptools
Add client.tests to setup packages
2014-12-16 23:21:15 -08:00
Jakub Warmuz
5e05638fef Add client.tests to setup packages 2014-12-17 08:14:35 +01:00
James Kasten
edbe0f451d Add __init__.py to apache dir 2014-12-16 21:12:29 -08:00
James Kasten
323aa350dc Remove Apache parsing from configurator 2014-12-16 21:00:14 -08:00
James Kasten
6cd67e652b Refactor to use Addr objects, and sets 2014-12-16 01:35:46 -08:00
James Kasten
7b6081ac29 Move out Apache specific Objects 2014-12-15 23:52:18 -08:00
James Kasten
d51c01e5ad Merge pull request #122 from kuba/bugs/120
virtualenv python2 (fixes #120)
2014-12-14 18:16:40 -08:00
James Kasten
986387d9a2 Fix priv function nodocs regex 2014-12-14 17:27:13 -08:00
James Kasten
097dd8180c Merge pull request #130 from letsencrypt/challenge_util
Challenge util
2014-12-14 04:34:39 -08:00
James Kasten
31c87cdbe5 git push origin masterMerge branch 'kuba-crypto_util_test' 2014-12-14 04:30:26 -08:00
James Kasten
b8902c272d Remove MakeCSRTest 2014-12-14 04:30:12 -08:00
James Kasten
24a8c3dd40 Merge branch 'crypto_util_test' of git://github.com/kuba/lets-encrypt-preview into kuba-crypto_util_test 2014-12-14 04:24:28 -08:00
James Kasten
30c0cc9479 Remove requirement for doc of all "priv" func 2014-12-14 04:09:54 -08:00
James Kasten
89ee6971eb Fix import grouping 2014-12-14 03:15:19 -08:00
James Kasten
8f45b5e78b Update single file pylint check in README 2014-12-14 02:59:29 -08:00
James Kasten
d53889b617 Fixed up remaining pylint errors 2014-12-14 02:51:17 -08:00
James Kasten
7297a6d462 Remove deprecated options 2014-12-14 02:47:18 -08:00
James Kasten
67fca03246 Changed max line length to 80 characters 2014-12-13 23:56:56 -08:00
James Kasten
a272a0a6e9 Add pylintrc, remove docstring-req from test_ functions, start rising coverage requirement 2014-12-13 23:47:57 -08:00
James Kasten
52555c2337 Integrate rising test coverage requirement 2014-12-13 22:42:47 -08:00
James Kasten
a74bc582ff Add unit test for challenge_util 2014-12-12 18:03:18 -08:00
James Kasten
00eedfbfda Merge pull request #128 from kuba/docs
docs: logger -> log
2014-12-12 12:11:14 -08:00
Jakub Warmuz
308ec688b1 docs: logger -> log 2014-12-12 11:24:33 +01:00
Jakub Warmuz
44a050a061 Add tests for make_key 2014-12-12 11:18:44 +01:00
Jakub Warmuz
ccfeef3e8e Add tests for b64_cert_to_pem 2014-12-12 11:11:02 +01:00
Jakub Warmuz
0b7121341f Remove csr_matches_names.
c.f. #127 and
https://github.com/letsencrypt/lets-encrypt-preview/pull/127#discussion-diff-21613376
2014-12-12 10:37:57 +01:00
Jakub Warmuz
40837e9d56 More tests for crypto_util 2014-12-12 10:37:34 +01:00
Jakub Warmuz
a6da3f2c89 Merge remote-tracking branch 'github/letsencrypt/master' into crypto_util_test 2014-12-11 18:50:30 +01:00
James Kasten
cc73f09745 Refactor dvsni challenge to allow other configurators to reuse code 2014-12-11 03:42:15 -08:00
James Kasten
053c6992ef Merge pull request #126 from kuba/logging
Refactor logging, "pythonic" DialogHandler
2014-12-10 18:30:51 -08:00
Jakub Warmuz
d628afab06 Merge branch 'logging' into crypto_util_test 2014-12-10 23:22:33 +01:00
Jakub Warmuz
b3062f7bc3 Merge remote-tracking branch 'github/letsencrypt/master' into logging
Conflicts:
	letsencrypt/client/tests/apache_configurator_test.py
2014-12-10 23:15:32 +01:00
James Kasten
6b9c2a5c5e Merge pull request #125 from kuba/pr/117
Clean up after #117, fixes #123
2014-12-10 13:40:03 -08:00
James Kasten
99e1ba9c89 Merge pull request #124 from kuba/fix-manifest
Fix MANIFEST.in recursive-include
2014-12-10 12:54:04 -08:00
Jakub Warmuz
417d398a13 wording 2014-12-10 18:50:54 +01:00
Jakub Warmuz
a0a81bf533 More coverage for crypto_util 2014-12-10 17:07:12 +01:00
Jakub Warmuz
2322266b98 Export RSA key to file 2014-12-10 16:00:51 +01:00
Jakub Warmuz
d102c8be12 Add tests for create_sig 2014-12-10 15:40:11 +01:00
Jakub Warmuz
3f9ddd03d2 Simpler log_test 2014-12-10 15:19:04 +01:00
Jakub Warmuz
7e0cee00b2 Refactor logging, "pythonic" DialogHandler 2014-12-10 15:12:50 +01:00
Jakub Warmuz
3a29e55bf7 Accept names in VH.__init__ 2014-12-10 14:31:43 +01:00
Jakub Warmuz
ec71dbcc0f Remove MyPopen 2014-12-10 14:21:55 +01:00
Jakub Warmuz
e9871fe88b clean apache_configurator_test, fixes #123 2014-12-10 14:00:23 +01:00
Jakub Warmuz
4c55cdc255 Remove unecessary whitespace in apache_configurator 2014-12-10 13:19:11 +01:00
Jakub Warmuz
37bda00e36 Do not disable pylint no-member in acme.
https://github.com/letsencrypt/lets-encrypt-preview/pull/117#discussion_r21537130
2014-12-10 13:18:17 +01:00
Jakub Warmuz
8171b5e9fb Fix MANIFEST.in recursive-include
reading manifest template 'MANIFEST.in'
warning: manifest_maker: MANIFEST.in, line 4: unknown action 'recursive'
2014-12-10 12:34:42 +01:00
James Kasten
cc85f22133 Small fixes 2014-12-10 01:20:14 -08:00
James Kasten
4f1f851a8d Fix exit (bad tag) error 2014-12-10 00:56:29 -08:00
James Kasten
cdc8d602a0 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-12-10 00:49:01 -08:00
James Kasten
3da7fba0bb git push origin masterMerge branch 'kuba-acme_test' 2014-12-10 00:48:25 -08:00
James Kasten
622a1c3154 Merge branch 'acme_test' of git://github.com/kuba/lets-encrypt-preview into kuba-acme_test 2014-12-10 00:45:04 -08:00
James Kasten
feee8da8b6 Merge pull request #117 from letsencrypt/configurator_tests
Configurator tests
2014-12-10 00:43:36 -08:00
James Kasten
610e1dbc7f Merge branch 'master' into configurator_tests 2014-12-10 00:34:41 -08:00
James Kasten
28f7c78bf8 Fix small errors. 2014-12-10 00:30:37 -08:00
James Kasten
707258519c consolidated tests/data, used tempfile/pkg_resource 2014-12-10 00:15:40 -08:00
James Kasten
37a2235f57 First set of revisions based on comments 2014-12-09 19:05:51 -08:00
James Kasten
23feeafe0b Reduce size of basic tests 2014-12-09 18:42:01 -08:00
James Kasten
44131f9b01 Merge pull request #119 from kuba/bugs/97
Pin pylint<1.4
2014-12-09 13:50:59 -08:00
James Kasten
b31740ac5c Merge pull request #118 from kuba/le_util_test
Test coverage for le_util
2014-12-09 13:49:02 -08:00
Jakub Warmuz
1b270451b2 virtualenv python2 (fixes #120) 2014-12-09 22:25:01 +01:00
Jakub Warmuz
8bf677ff3b Fix acme_revocation key file bug 2014-12-09 22:16:38 +01:00
Jakub Warmuz
b5a51de16b Tests for ACME message factories 2014-12-09 22:16:38 +01:00
James Kasten
36edbf379c use assertEqual in apache_configurator_test.py 2014-12-09 12:35:56 -08:00
Jakub Warmuz
e107b9259a Pin pylint<1.4 2014-12-09 19:41:56 +01:00
Jakub Warmuz
f7784f2023 Fix lexicographic order in install_requires 2014-12-09 19:24:16 +01:00
Jakub Warmuz
8ea085553a Fix coverage for le_util 2014-12-09 19:20:57 +01:00
James Kasten
7dad983959 Merged with master 2014-12-09 02:02:10 -08:00
James Kasten
2cec7ddcc2 Add get_version test 2014-12-09 01:21:56 -08:00
James Kasten
f6207cc62a Disable spurious error 2014-12-09 00:18:32 -08:00
James Kasten
14b8f427a2 Code quality/lint warnings 2014-12-09 00:15:33 -08:00
James Kasten
acc55650d4 Finish adapting configurator to unittesting/rid project errors 2014-12-08 23:14:58 -08:00
James Kasten
4fc62cb62b Fix ApacheConf init and mock correct test object 2014-12-08 15:56:57 -08:00
Jakub Warmuz
54d67e2378 Add tests for unique_file 2014-12-08 20:41:52 +01:00
James Kasten
19501d297d Mock out check_ssl_loaded 2014-12-08 01:44:59 -08:00
James Kasten
c59cadd252 Add mock to setup 2014-12-08 00:33:27 -08:00
James Kasten
7c855be3ab Allow runtime to determine directory structure 2014-12-07 22:54:45 -08:00
James Kasten
0d988c3aa9 Allow tests without first basic revision 2014-12-07 22:18:46 -08:00
James Kasten
cc6aca6aa1 Move test configs into repo and apply necessary changes. 2014-12-07 19:36:46 -08:00
James Kasten
1fdf9b39c9 unittests add_dir and make_vhost_ssl 2014-12-06 02:33:06 -08:00
James Kasten
0673a966c5 Generalize variables in configurator 2014-12-05 23:54:37 -08:00
James Kasten
779dd755ca More unit tests... better configurator 2014-12-05 17:31:36 -08:00
James Kasten
48ae79e8c4 Correct checking of get_virtual_hosts test 2014-12-04 04:16:22 -08:00
James Kasten
8a8eb0d617 Stop testing py26 until we sort out pylint issues 2014-12-04 04:02:33 -08:00
James Kasten
30641db85f Made apache_configurator work in any location/first basic tests complete successfully 2014-12-04 04:00:22 -08:00
James Kasten
231f3d5c61 Remove code without unittests from master (code remains in separate testing branch) 2014-12-03 16:45:06 -08:00
James Kasten
a234cdf90e Safety check: if Apache releases version without a decimal 2014-12-03 04:47:41 -08:00
James Kasten
a19333d84b First draft of apache configurator unit tests 2014-12-03 04:35:49 -08:00
James Kasten
afd67d10f0 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-12-03 04:33:07 -08:00
James Kasten
600dd4264e turn off email notifications for travis 2014-12-03 04:32:52 -08:00
James Kasten
2d6c9489c8 Add get_version_method (cannot currently make unittest) fix pylint errors. 2014-12-03 04:31:01 -08:00
James Kasten
fbe0b23070 Merge pull request #111 from willoller/type_checking_client_acme
Parameter checking and filling in TODOs (acme.py)
2014-12-03 03:51:09 -08:00
James Kasten
8f9c4194bc disable pylint warnings 2014-12-02 20:36:46 -08:00
James Kasten
14c6e94c56 git push origin masterMerge branch 'kuba-pep8' 2014-12-02 12:19:43 -08:00
James Kasten
bcd423aaa3 Fixed undefined variable from Kuba-PEP8 2014-12-02 12:19:15 -08:00
Jakub Warmuz
e0f91b06b2 Move redirect to authenticate 2014-12-01 22:37:15 +01:00
Jakub Warmuz
5a2143290a ca_server -> server 2014-12-01 22:33:57 +01:00
Jakub Warmuz
6886680a62 Fix redirect var flow 2014-12-01 20:13:39 +01:00
Jakub Warmuz
b54db8c447 Merge remote-tracking branch 'github/letsencrypt/master' into pep8
Conflicts:
	letsencrypt/client/client.py
2014-12-01 20:08:27 +01:00
James Kasten
795b6fcdc6 Fix doc mistakes 2014-12-01 04:29:36 -08:00
James Kasten
9474a44b12 Merge pull request #109 from kuba/docs
Docs
2014-12-01 04:21:27 -08:00
Jakub Warmuz
a62db935c3 Merge remote-tracking branch 'github/letsencrypt/master' into pep8
Conflicts:
	letsencrypt/client/client.py
2014-12-01 12:44:39 +01:00
Jakub Warmuz
6385d0fed1 Merge remote-tracking branch 'github/letsencrypt/master' into docs
Conflicts:
	letsencrypt/client/apache_configurator.py
2014-12-01 12:43:06 +01:00
James Kasten
cb9263cc4a Random small changes 2014-12-01 03:05:06 -08:00
James Kasten
cf9c861331 Merge pull request #113 from fmarier/url-typo
Use the right URL for the project
2014-11-30 23:21:23 -08:00
Francois Marier
8f445a6136 Use the right URL for the project
`lets-encrypt.org` doesn't work but `letsencrypt.org` does so use that.
2014-11-30 20:29:38 -08:00
Will Oller
e49cf1899b Remove aggro type checking 2014-11-30 16:06:58 -08:00
Will Oller
30d4aeb463 Remove some whitespace 2014-11-30 15:52:45 -08:00
Will Oller
3184dba80c PEP8: Use isinstance(name, unicode) 2014-11-30 15:52:34 -08:00
Will Oller
8885f608a2 Single quotes instead of tics 2014-11-30 15:47:59 -08:00
Will Oller
64f011c8bb Parameter checking and filling in TODOs 2014-11-30 15:27:05 -08:00
Jakub Warmuz
06bb6b344b More docs in challenge 2014-11-30 19:28:03 +01:00
Jakub Warmuz
a88e3f8fc1 Add Challenge docs, pep8/docs interactive_challenge 2014-11-30 19:22:07 +01:00
Jakub Warmuz
924531341e Fix missing whitespace in todo 2014-11-30 18:28:14 +01:00
Jakub Warmuz
49b50538ab pylint client, add docs 2014-11-30 13:56:09 +01:00
Jakub Warmuz
b354c9caf6 Fix recent commits docs bugs 2014-11-30 13:23:20 +01:00
Jakub Warmuz
72218f3dd3 script doc fix 2014-11-30 13:10:23 +01:00
Jakub Warmuz
b8b4730535 Merge remote-tracking branch 'github/letsencrypt/master' into docs
Conflicts:
	letsencrypt/client/apache_configurator.py
2014-11-30 11:55:17 +01:00
Jakub Warmuz
9f05a3ee15 Fix apache_configurator docs 2014-11-30 11:44:17 +01:00
James Kasten
8a62587a6e Added namedtuples for key, csr in client 2014-11-30 02:31:44 -08:00
James Kasten
384e01a3d8 Remove self from function call 2014-11-30 00:01:23 -08:00
James Kasten
238fdcb4c6 Finish merging fix_pylint_errors 2014-11-29 23:54:01 -08:00
James Kasten
27d9f5f374 Cleaned up augeas configurator 2014-11-29 23:11:34 -08:00
James Kasten
e3893df5ef Merge pull request #106 from kuba/travis-cover
Add cover to Travis CI build matrix
2014-11-29 21:33:20 -08:00
James Kasten
44749f09ec Merge pull request #105 from kuba/docs
Sphinx docs, various doc fixes
2014-11-29 21:32:17 -08:00
James Kasten
d47a06cfd2 Merge pull request #104 from kuba/pylint-errors
Fix some `pylint -E` errors
2014-11-29 21:15:24 -08:00
Jakub Warmuz
8f767d4a5a Add cover to Travis CI build matrix 2014-11-30 03:32:49 +01:00
Jakub Warmuz
fca214ad12 More unifying after merge with master 2014-11-30 03:07:52 +01:00
Jakub Warmuz
3952c11d66 Add newline at end of file 2014-11-30 03:00:56 +01:00
Jakub Warmuz
5b4d70c234 Docs in CONFIG 2014-11-30 02:46:21 +01:00
Jakub Warmuz
59a7559c05 Unify docs 2014-11-30 02:46:21 +01:00
Jakub Warmuz
56b75e93ce autodoc API documentation 2014-11-30 02:38:33 +01:00
Jakub Warmuz
af298b35a1 sphinx-quickstart 2014-11-30 02:38:33 +01:00
Jakub Warmuz
78c3f161d7 setup.py docs, Sphinx dep 2014-11-30 02:38:33 +01:00
Jakub Warmuz
9b1ad63077 Merge remote-tracking branch 'github/letsencrypt/master' into pylint-errors
Conflicts:
	letsencrypt/client/client.py
2014-11-30 02:36:42 +01:00
Jakub Warmuz
24769c2a8a Fix some pylint -E errors 2014-11-30 02:33:02 +01:00
James Kasten
27f0ba2633 Missed this change in one of pulls 2014-11-29 17:11:03 -08:00
James Kasten
a013b8153e Merge pull request #92 from willoller/master
acme/challenge_request handles only 1 domain name
2014-11-29 17:02:00 -08:00
James Kasten
a615130b19 Merge pull request #101 from kuba/bugs/44
Exception -> LetsEncryptClientError, doc fixes
2014-11-29 16:41:01 -08:00
Jakub Warmuz
8f6d4b4344 Merge remote-tracking branch 'github/letsencrypt/master' into bugs/44
Conflicts:
	letsencrypt/client/client.py
2014-11-30 01:32:33 +01:00
James Kasten
add0351d20 Merge pull request #102 from kuba/pep8
PEP8
2014-11-29 16:29:35 -08:00
James Kasten
fb712064d2 Merge pull request #103 from kuba/style
Policies for pull requests. Coding style.
2014-11-29 16:13:39 -08:00
James Kasten
1d6a451bb8 git push origin masterMerge branch 'w0uld-file-to-str' 2014-11-29 16:05:45 -08:00
James Kasten
d2a1c969e4 Added in filename support because configuration files still need to reference the correct place 2014-11-29 16:05:18 -08:00
Jakub Warmuz
f116a8fc8e Add missing whitespace to README 2014-11-29 22:12:20 +01:00
Jakub Warmuz
49413cb9d2 README: -following 2014-11-29 22:11:40 +01:00
Jakub Warmuz
9bc369f5a9 Fix markdown bold fail 2014-11-29 22:10:30 +01:00
Jakub Warmuz
1a25b3d7cd Policies for pull requests. Coding style. 2014-11-29 22:07:33 +01:00
Adam Woodbeck
9581c363b1 Fixed attribute reference and docstring. 2014-11-29 12:02:48 -05:00
Adam Woodbeck
9efec15d0a Merge branch 'master' into file-to-str
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/le_util.py
2014-11-29 11:35:48 -05:00
Adam Woodbeck
5854d42672 Various bug, docstring, and PEP8 fixes. 2014-11-29 11:15:56 -05:00
Will Oller
c39e85c17e Don’t write unit tests for non-optimal code paths 2014-11-29 07:24:09 -08:00
Will Oller
f3b7839d31 Don’t use explicit file encoding hint 2014-11-29 07:23:37 -08:00
Will Oller
c5bbb30e5d Removed unnecessary whitespace 2014-11-29 07:16:30 -08:00
Jakub Warmuz
99a81ba269 Add package docstrings 2014-11-29 13:42:47 +01:00
Jakub Warmuz
23cdee4fa1 More docs for recovery_token_challenge, cleanup 2014-11-29 13:40:10 +01:00
Jakub Warmuz
b6422d74ad PEP8 recovery_contact_challenge 2014-11-29 13:34:57 +01:00
Jakub Warmuz
c80f9bed97 Fix double "raise" 2014-11-29 13:05:54 +01:00
Jakub Warmuz
73eb8f8546 Exception -> LetsEncryptClientError, doc fixes 2014-11-29 13:00:28 +01:00
James Kasten
01a06be091 Fixed a few PEP8 violations 2014-11-29 01:05:06 -08:00
James Kasten
73137b404a Merge pull request #99 from daguar/fix-acme-comment-typo
Fix typos in comments for ACME authorizationRequest message
2014-11-29 00:56:07 -08:00
James Kasten
e7ee8f9d1c Fixed many pylint errors in augeas_configurator.py 2014-11-29 00:54:34 -08:00
Will Oller
ad5c2d0a4d acme/challenge_request handles only 1 domain name
No other functions inside acme/ handle lists, so
to keep the class consistent, challenge_request()
should only handle a string parameter. Iteration
should be moved to the client, which is handling
the list of domain names anyway.
2014-11-29 00:30:16 -08:00
James Kasten
0c11418caf Fixed almost all pylint errors in apache_configurator 2014-11-29 00:14:37 -08:00
Dave Guarino
aa7ca5f03d Fix typos in comments for ACME authorizationRequest message 2014-11-28 23:58:10 -08:00
James Kasten
90aab1ab7e Merge pull request #98 from kuba/fix-chmods
Fix chmods security error: 644 != 0644
2014-11-28 21:36:44 -08:00
James Kasten
c1988d6b74 Merge pull request #96 from kuba/pr/85
#85 fixup/cleanup (travis)
2014-11-28 20:48:37 -08:00
Jakub Warmuz
3cfeac6f3b PEP 3127, fixes pylint old-octal-literal.
https://www.python.org/dev/peps/pep-3127
2014-11-29 03:33:51 +01:00
Jakub Warmuz
fa94a4f57a Fix chmods security error: 644 != 0644 2014-11-29 03:13:16 +01:00
Jakub Warmuz
86f8dad264 Remove unnecessary "Build status" comment 2014-11-29 02:37:51 +01:00
Jakub Warmuz
1e235e3bde TOXENV instead of TOX_ENV
http://tox.readthedocs.org/en/latest/config.html?highlight=toxenv#confval-envlist=CSV
2014-11-29 02:01:18 +01:00
Jakub Warmuz
0d64f5a358 Fix dev[testing] typo in .travis.yml 2014-11-29 01:50:13 +01:00
Adam Woodbeck
c0c731b3e6 Updated get_key_csr_pem() to use file contents instead of file names. 2014-11-28 17:00:02 -05:00
Adam Woodbeck
87d7ed1750 Clarified --rollback command line option. 2014-11-28 16:47:37 -05:00
Jakub Warmuz
89e86606ea Travis: multiple Python environments. 2014-11-28 22:27:29 +01:00
Jakub Warmuz
016bf5d269 Travis: explicit -e $TOX_ENV not necessary 2014-11-28 21:58:16 +01:00
Jakub Warmuz
6957d1d109 Travis: fix YAML style 2014-11-28 21:57:47 +01:00
Adam Woodbeck
3ecf8659a1 Work with CSR and private key file contents in the client. 2014-11-28 15:48:04 -05:00
Adam Woodbeck
8464ce30d5 More sensible mode defaults when creating a unique file. 2014-11-28 15:47:06 -05:00
Jakub Warmuz
3de9ff387d Use travis_retry.
http://blog.travis-ci.com/2013-05-20-network-timeouts-build-retries
2014-11-28 21:45:10 +01:00
Jakub Warmuz
1d59dd1275 Travis CI: remove python version, use setup.py dev[testing] 2014-11-28 21:42:47 +01:00
Jakub Warmuz
36acd164da Merge remote-tracking branch 'github/letsencrypt/master' into pr/85
Conflicts:
	README.md
2014-11-28 21:35:20 +01:00
Adam Woodbeck
122e6b2ca1 Read in files with universal newline support. 2014-11-28 13:28:16 -05:00
Adam Woodbeck
6254038ea3 Provide more user-friendly errors when opening file handles. 2014-11-28 11:41:03 -05:00
James Kasten
e56ee5fa4e Merge pull request #90 from kuba/pr/81
README: multi-OS support with disclaimer
2014-11-27 19:00:25 -08:00
James Kasten
6e01530684 Merge pull request #89 from kuba/tests-setup
Improve tests setup, incuding coverage.
2014-11-27 18:58:05 -08:00
Jakub Warmuz
5665ea96d4 Remove build badge 2014-11-27 23:48:25 +01:00
Jakub Warmuz
58196f046e Fix email link in README 2014-11-27 23:43:13 +01:00
Jakub Warmuz
f4a8ee56ab Further fixes to README 2014-11-27 23:40:58 +01:00
Jakub Warmuz
89ea288859 Fix dnozay README as per #81 2014-11-27 23:34:15 +01:00
Jakub Warmuz
d40a7acf8a gitignore: .coverage is a file 2014-11-27 22:49:15 +01:00
Jakub Warmuz
f2c1c30ac4 Restrict nosetests to our package 2014-11-27 21:00:07 +01:00
Jakub Warmuz
93291feb58 Ignore .coverage 2014-11-27 20:57:24 +01:00
Jakub Warmuz
dfd7e142c1 Do not use xunit. coverage instead of xcoverage. 2014-11-27 20:56:35 +01:00
Jakub Warmuz
9d5ab7dbcc Test coverage 2014-11-27 20:30:56 +01:00
Jakub Warmuz
f62ab0e351 tox.ini uses setup.py test -q 2014-11-27 20:26:30 +01:00
Jakub Warmuz
c2413ba542 setuptools test_suite 2014-11-27 20:24:50 +01:00
Jakub Warmuz
3f9a708789 tox.ini using setup.py dev 2014-11-27 20:13:21 +01:00
Damien Nozay
d8fcfc9607 add travis-ci badge to readme 2014-11-27 10:06:17 -08:00
Damien Nozay
5631ed0a97 travis-ci: install extras[testing] 2014-11-27 10:06:17 -08:00
Damien Nozay
69fc4aed1d split test matrix to cover py26 and lint separately 2014-11-27 10:06:17 -08:00
Damien Nozay
e36fe2ced5 install swig/augeas et al. 2014-11-27 10:06:17 -08:00
Damien Nozay
d9c9cdd6be let travis-ci test the code :) 2014-11-27 10:06:16 -08:00
Damien Nozay
884ab38334 trivial: update readme 2014-11-27 10:05:59 -08:00
Adam Woodbeck
ddca02cb7b Fixed format string incompatibility with py2.6. 2014-11-27 09:13:01 -05:00
Adam Woodbeck
6d9edda822 Restore 'type=file' on privkey and csr command line arguments. 2014-11-27 08:49:21 -05:00
Adam Woodbeck
bcb788fe0b Cleaned up per Kuba. 2014-11-27 08:39:32 -05:00
Adam Woodbeck
da29a92729 Better error checking on private key and csr. Also, pull in file names instead of pointers. 2014-11-27 08:12:40 -05:00
James Kasten
d548d7427f Merge pull request #87 from letsencrypt/proper_documentation
Proper documentation
2014-11-27 01:32:46 -08:00
James Kasten
9e1e7b235d started fixing pylint errors 2014-11-27 01:29:56 -08:00
James Kasten
ba91c489a6 Removed __functions, minor formatting 2014-11-27 00:53:10 -08:00
James Kasten
76a1012370 Fixed common case in client.py 2014-11-26 21:45:58 -08:00
James Kasten
083a194084 git push origin masterMerge branch 'w0uld-argparse' 2014-11-26 20:49:54 -08:00
James Kasten
d47b615d5f Changed authenticate method to handle the new arguments 2014-11-26 20:49:32 -08:00
Adam Woodbeck
f5a622cdf1 Moved config object into the main function. 2014-11-26 22:04:25 -05:00
Adam Woodbeck
09df8e6125 Removed unused constant. 2014-11-26 21:37:33 -05:00
Adam Woodbeck
13b9a63e7f Removed redundant logger and config setup. 2014-11-26 21:36:32 -05:00
Adam Woodbeck
13f1286e66 Changes per pylint. 2014-11-26 21:30:42 -05:00
Adam Woodbeck
6794a57e26 Set some defaults for instance attributes used outside of __init__() to make them explicit. 2014-11-26 21:10:04 -05:00
Adam Woodbeck
259cf47169 Refactored command line code to use argparse. 2014-11-26 21:09:45 -05:00
James Kasten
716156e5a0 Add sphinx documentation for augeas_configurator 2014-11-26 16:59:13 -08:00
James Kasten
14c81099af First pass for proper documentation of apache_configurator 2014-11-26 15:28:28 -08:00
James Kasten
6b7d3d6dd4 Merge pull request #79 from w0uld/fix_print_options
Resolved issue where command line arguments were not being printed due t...
2014-11-25 22:44:49 -08:00
James Kasten
c9f9417d4b Merge pull request #71 from kuba/bugs/44
Towards better exceptions...
2014-11-25 22:24:34 -08:00
Adam Woodbeck
1288b4986d Resolved issue where command line arguments were not being printed due to a type conversion error. 2014-11-25 10:19:08 -05:00
Jakub Warmuz
d7b33656b0 Raise errors.LetsEncryptClient error in send(). 2014-11-25 13:32:02 +01:00
James Kasten
f046db1d17 Added TODOs and a warning 2014-11-25 03:05:50 -08:00
Jakub Warmuz
6eaf9f3459 Merge remote-tracking branch 'github/master' into bugs/44
Conflicts:
	letsencrypt/client/apache_configurator.py
2014-11-25 10:52:00 +01:00
James Kasten
a83781f70f Merge pull request #77 from kuba/style-1
Revert acme to 43ae01b
2014-11-25 01:46:54 -08:00
James Kasten
3aee61f427 Merge pull request #76 from kuba/style-0
Fix recent commits style
2014-11-25 01:41:01 -08:00
Jakub Warmuz
f52556becd Revert acme to 43ae01b 2014-11-25 10:01:04 +01:00
Jakub Warmuz
5ca95a55ca Fix recent commits style 2014-11-25 09:48:35 +01:00
James Kasten
5e335f9a5f PEP8 compliance main.py 2014-11-25 00:43:18 -08:00
James Kasten
9b1ab949c7 PEP compliance logger.py 2014-11-25 00:32:56 -08:00
James Kasten
8d1d9b0734 PEP8 support for acme, acme_test, apache_configurator 2014-11-25 00:15:47 -08:00
James Kasten
506c603e30 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview
Conflicts:
	letsencrypt/client/client.py
2014-11-24 19:03:29 -08:00
James Kasten
316e686ed5 git push origin masterMerge branch 'csr_utility_functions' 2014-11-24 18:59:43 -08:00
James Kasten
b245bbb10e Added name check and attempted to clean up code - is PEP8 conformant 2014-11-24 18:58:34 -08:00
Seth Schoen
4453d3fab8 Add a missing crypto_util 2014-11-24 16:02:30 -08:00
Seth Schoen
b4492042d9 For crypto.util, read crypto_util 2014-11-24 15:59:32 -08:00
Seth Schoen
c741d969de Correct PEM/DER return behavior 2014-11-24 15:52:48 -08:00
Seth Schoen
35c34ec6d4 PEP8 formatting fixes 2014-11-24 15:52:22 -08:00
Seth Schoen
2e8cdd071a Integrate CSR and private key validation steps into client 2014-11-24 14:16:29 -08:00
Seth Schoen
7e71bccf28 Add documentation for parameters of new functions 2014-11-24 14:10:35 -08:00
James Kasten
539b9b12a7 Merge pull request #72 from kuba/le_util_test
le_util cleanup, le_util_test, docs
2014-11-24 04:50:49 -08:00
James Kasten
b8ee00bddd Merge pull request #73 from kuba/acme_test_pretty_heisenbug
Remove heisenbug from acme_test
2014-11-24 04:46:53 -08:00
Jakub Warmuz
43ae01b4c8 Fix TODO encode? in acme.py 2014-11-24 13:45:15 +01:00
Jakub Warmuz
018ebd4087 Fix TODO encode? in apache_configurator.
`s = Random.get_random_bytes(CONFIG.S_SIZE)`, so `s` is `str`.
2014-11-24 13:32:55 +01:00
Jakub Warmuz
0dd530a4d1 Fix TODO encode? in create_sig
- n, e are `int`s, applied transformations make it `str`

- Crypto.Signature.PKCS1_v1_5.new [1] returns PKCS115_SigScheme, and its
  `sign()` method returns `str` [2]

- docs is OK: requires `nonce` to be `str`

- `create_sig` is not called with custom `nonce` argument anywhere in
  the code

[1] https://www.dlitz.net/software/pycrypto/api/2.6/Crypto.Signature.PKCS1_v1_5-module.html#new
[2] https://www.dlitz.net/software/pycrypto/api/2.6/Crypto.Signature.PKCS1_v1_5.PKCS115_SigScheme-class.html#sign
2014-11-24 13:30:10 +01:00
Jakub Warmuz
882170559d TODO encode? comments for jose_b64encode 2014-11-24 13:15:09 +01:00
Jakub Warmuz
aef18c4413 JOSE Base64: encode str only, decode str or ascii unicode 2014-11-24 11:00:08 +01:00
Jakub Warmuz
0e6e85cf19 Merge remote-tracking branch 'github/master' into le_util_test 2014-11-24 10:49:23 +01:00
James Kasten
ee726d2c23 Merge pull request #74 from kuba/py26-compat
py26 doesn't support set {} constructor. Use frozenset.
2014-11-23 20:16:33 -08:00
Seth Schoen
ea6bec851b Add utility functions for checking CSR and privkey 2014-11-23 18:21:56 -08:00
Jakub Warmuz
0d6a482d32 Merge remote-tracking branch 'github/master' into bugs/44
Conflicts:
	letsencrypt/client/client.py
2014-11-24 02:44:55 +01:00
Jakub Warmuz
285761465b Merge remote-tracking branch 'github/master' into le_util_test
Conflicts:
	letsencrypt/client/client.py
	letsencrypt/client/crypto_util.py
2014-11-24 02:29:09 +01:00
Jakub Warmuz
1c1d9221c8 py26 doesn't support set {} constructor. Use frozenset. 2014-11-24 02:23:27 +01:00
Jakub Warmuz
c2d0acb063 Remove heisenbug from acme_test 2014-11-24 02:16:24 +01:00
James Kasten
26e5535d21 Bring in @Kuba PEP-8 BranchMerge branch 'kuba-pep8' 2014-11-23 16:02:01 -08:00
James Kasten
83c267faa1 Fixed typos in crypto_util.py 2014-11-23 16:00:53 -08:00
Jakub Warmuz
421f541271 Allow unicode input for JOSE Base64 2014-11-23 22:27:22 +01:00
Jakub Warmuz
22bea4c975 Better tests for jose b64 padding 2014-11-23 22:18:00 +01:00
Jakub Warmuz
af4d955806 Fix JOSE encoding mess 2014-11-23 22:18:00 +01:00
Jakub Warmuz
6f32c41da3 le_util cleanup, le_util_test 2014-11-23 22:18:00 +01:00
schoen
0dc4ef1670 Merge pull request #60 from kuba/acme_test
Add acme_test. Sphinx-like doc in acme module.
2014-11-23 13:02:55 -08:00
Jakub Warmuz
c22d243728 Less generic exception catching in send() 2014-11-23 21:16:42 +01:00
Jakub Warmuz
725d05e8ac Less generic exception for subprocess.check_call.
https://docs.python.org/2/library/subprocess.html#exceptions
2014-11-23 21:04:59 +01:00
Jakub Warmuz
cdde731aa4 LetsEncryptClientError 2014-11-23 21:04:59 +01:00
James Kasten
d2f4644b4f Merge pull request #69 from kuba/libaugeas0
apt-get install libaugeas0
2014-11-23 11:53:32 -08:00
Jakub Warmuz
f7e415b368 apt-get install libaugeas0
libaugeas0 is still required, despite python-augeas from PyPI
2014-11-23 20:48:07 +01:00
James Kasten
034393c362 Merge pull request #64 from kuba/pypi-augeas
python-augeas from PyPI
2014-11-23 11:14:11 -08:00
Jakub Warmuz
628475e639 Clean crypto_util, add some doc 2014-11-23 19:55:56 +01:00
Jakub Warmuz
ab183511b7 Simple doc fix in configurator 2014-11-23 19:55:32 +01:00
Jakub Warmuz
c3897466b7 Refactor clients module.
- PEP8, pylint cleaning

- no-self methods -> functions, some of which were moved to acme or
  challenge module.

- a bit of renaming

- more Pythonic code

- Sphinx-like doc.
2014-11-23 19:52:57 +01:00
Jakub Warmuz
cd9e30ad7e python-augeas from PyPI
https://github.com/hercules-team/python-augeas/issues/2#issuecomment-64116945
2014-11-23 15:29:11 +01:00
Jakub Warmuz
891622c4f5 Merge remote-tracking branch 'github/master' into acme_test
Conflicts:
	letsencrypt/client/acme.py
2014-11-23 12:56:37 +01:00
Seth Schoen
faaf101f08 More complete list of exceptions that can be raised
The acme.acme_object_validate() function can also raise ValueError
when given something that isn't JSON.
2014-11-22 17:36:10 -08:00
James Kasten
2cf5fbab3b Merge pull request #57 from kuba/tests-setup
Tests setup
2014-11-22 17:19:50 -08:00
James Kasten
6a2e6f6828 Merge pull request #56 from kuba/clean-manifest
Clean MANIFEST.in
2014-11-22 14:27:09 -08:00
Jakub Warmuz
560d436536 Add acme_test. Sphinx-like doc in acme module. 2014-11-22 23:23:46 +01:00
Jakub Warmuz
06a6969a24 Merge remote-tracking branch 'github/master' into tests-setup 2014-11-22 22:51:00 +01:00
James Kasten
7b7f1c5d9c Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-22 12:35:36 -08:00
James Kasten
c8fc86a404 Added IRC channel to README 2014-11-22 12:35:24 -08:00
Jakub Warmuz
e432d00e0a pylint dep for tox 2014-11-22 20:28:29 +01:00
Jakub Warmuz
b901a84f2b Fix README.md ordered list 2014-11-22 15:15:33 +01:00
Jakub Warmuz
10707868db Add missing newline at end of file 2014-11-22 15:14:14 +01:00
Jakub Warmuz
1f844153fe Use tox for lint. 2014-11-22 15:12:33 +01:00
Jakub Warmuz
569f004f09 Clean MANIFEST.in 2014-11-22 15:05:44 +01:00
Jakub Warmuz
300a4f0b45 Use tox for tests 2014-11-22 15:04:36 +01:00
Jakub Warmuz
1c643d483e Basic dev/testing setup 2014-11-22 15:04:02 +01:00
Seth Schoen
8b75058783 Spelling correction 2014-11-22 00:22:52 -08:00
James Kasten
0ab8e4e6d4 Merge pull request #54 from kuba/setuptools-augeas
Setuptools augeas
2014-11-22 00:09:19 -08:00
James Kasten
385115cc4e Fixed #55 2014-11-21 23:46:18 -08:00
James Kasten
9896274478 Update to new CONFIG style 2014-11-21 23:39:05 -08:00
James Kasten
976560b0e9 Merged @kuba changes 2014-11-21 23:32:19 -08:00
James Kasten
c260232f61 Merge pull request #52 from kuba/pep8
PEP-8, code base cleanup, bug fixes
2014-11-21 22:48:55 -08:00
Jakub Warmuz
1343cfa465 Add missing line of code (merge/rebase effect). 2014-11-22 03:10:22 +01:00
Jakub Warmuz
d2ef9ea552 virtualenv --no-site-packages
Now, that all packages are installable using setuptools,
`--system-site-packages` is no longer necessary, and it's better to use
saner `--no-site-packages`.
2014-11-22 02:53:48 +01:00
Jakub Warmuz
731727cb38 Install Augeas using setuptools 2014-11-22 02:52:03 +01:00
James Kasten
c7ac555a43 Fixed merge error 2014-11-21 17:15:03 -08:00
James Kasten
4c771ae32f Merged README to have both virtualenv and libssl-dev 2014-11-21 17:08:56 -08:00
James Kasten
032eb5ec1d fix dependency - Merge branch 'kuba-bugs/37' 2014-11-21 17:01:24 -08:00
James Kasten
45ef583a3c Merge pull request #51 from marcoscaceres/patch-1
Fixed typo
2014-11-21 16:21:37 -08:00
Jakub Warmuz
6fa555788e Clean augeas_configurator module. 2014-11-22 00:22:10 +01:00
Jakub Warmuz
3dbfa9b4cd More display module cleanups. 2014-11-22 00:22:10 +01:00
Jakub Warmuz
bc294cbe2c cert_info_{frame,string} method -> function (no-self-use) 2014-11-22 00:22:10 +01:00
Jakub Warmuz
233804aec2 gen_https method -> function (no-self-use) 2014-11-22 00:22:10 +01:00
Jakub Warmuz
6032eb6393 setDisplay -> set_display 2014-11-22 00:22:09 +01:00
Jakub Warmuz
9ff88c1c98 Clean display module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
6b7d0eaa9e Clean nginx_configurator module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
a449a8917e Clean crypto_util module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
e70c6cc65c Configurator restart quiet 2014-11-22 00:22:09 +01:00
Jakub Warmuz
bc6f0b4b6b Clean configurator module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
30c6c16fa8 Clean CONFIG module, use os.path. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
b5462f8f88 Clean acme module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
0fcb2a056f Clean recovery_token_challenge module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
de555cec87 Clean challenge module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
123e64ff03 Clean validator module. 2014-11-22 00:22:09 +01:00
Jakub Warmuz
9cdb7dbae2 Fix pylint errors or comment unused invalid code.
augeas_configurator.py: E:220,12: Too many positional arguments for
function call (too-many-function-args)

client.py: E:628,18: Instance of 'Client' has no 'get_cas' member
(no-member)

interactive_challenge.py: E: 35,29: Instance of 'Interactive_Challenge'
has no 'reason' member (no-member)
2014-11-22 00:22:09 +01:00
Jakub Warmuz
a62c02a9cf Fix no-self-argument 2014-11-22 00:22:09 +01:00
Jakub Warmuz
d88eb92fbe Fix undefined-variable 2014-11-22 00:22:09 +01:00
Jakub Warmuz
de53f8e940 Clean up imports 2014-11-22 00:22:09 +01:00
Jakub Warmuz
aafcaa4ef8 Remove unnecessary executable bits 2014-11-22 00:21:17 +01:00
Jakub Warmuz
e4102e985b Get rid of abc 2014-11-22 00:21:17 +01:00
Marcos Caceres
2c818c6597 Fixed typo 2014-11-21 14:27:00 -08:00
James Kasten
cc693fbc20 Merge pull request #48 from kuba/virtualenv-setup
Virtualenv setup. Fixes #41.
2014-11-21 13:31:21 -08:00
James Kasten
d5b9ea2ce2 Merge pull request #46 from kuba/remove-gitmodules
Remove unnecessary gitmodules
2014-11-21 13:30:28 -08:00
James Kasten
14a01ad168 Merge pull request #50 from kuba/remove-sni_challenge-import
Remove sni_challenge import.
2014-11-21 10:40:22 -08:00
Jakub Warmuz
124af9c22b Remove sni_challenge import. 2014-11-21 18:39:14 +01:00
Jakub Warmuz
2a5364b3ce libssl-dev dep. Fixes #37. 2014-11-21 18:07:55 +01:00
Jakub Warmuz
3ce1679717 Virtualenv setup. Fixes $41. 2014-11-21 17:45:13 +01:00
Jakub Warmuz
3cfb34af16 Remove unnecessary gitmodules 2014-11-21 15:00:38 +01:00
James Kasten
ecff61a193 It is way too late... syntax fix... I am going to bed 2014-11-21 03:24:21 -08:00
James Kasten
971441fe2f Better fix for options-ssl.conf requirement 2014-11-21 03:18:37 -08:00
James Kasten
ad2078df37 Forgot to make options-ssl.conf part of the required setup... added code to check and copy it down to the appropriate place to aid developers working on the project, this will eventually go in the setup files 2014-11-21 01:36:42 -08:00
James Kasten
3853fa28c7 Added reference to Wiki documentation in README 2014-11-21 00:16:56 -08:00
James Kasten
5026e92142 Remove reference to old file in CONFIG 2014-11-21 00:10:53 -08:00
James Kasten
c1c6d24c32 Remove more legacy files, code references 2014-11-21 00:10:21 -08:00
James Kasten
c1f3a55d04 Add development mailing list to README 2014-11-20 18:56:19 -08:00
James Kasten
36027477b1 Push error message for send() function. This seems to be giving many people trouble 2014-11-20 16:48:39 -08:00
James Kasten
b779bb0461 specified ApacheConfigurator to enable rollback, --view-checkpoints 2014-11-20 16:02:57 -08:00
James Kasten
f64570c5db Merge branch 'configurator_refactor', Enable modular configuration
editing to support development for other webservers.
2014-11-20 15:43:31 -08:00
James Kasten
95fbf69206 Caught bug in display... referencing 0 instead of OK variable 2014-11-20 15:42:13 -08:00
James Kasten
e79b379e67 Enabled authentication under new system 2014-11-20 15:29:40 -08:00
James Kasten
bf393f78ab Changed client to use new form of challenges 2014-11-20 12:23:44 -08:00
James Kasten
d57dd9faee Move SNIChallenge into apache_configurator.py as the dvsni challenge should be a feature for the configurator 2014-11-20 03:47:35 -08:00
James Kasten
3b26f6c526 Add stub of nginx_configurator.py for another developer to work on if they desire 2014-11-20 02:39:11 -08:00
James Kasten
c238a6a36f Add AugeasConfigurator class... move all appropriate functions to it 2014-11-20 02:21:56 -08:00
James Kasten
0093684f93 Initial attempt to extract essentials out of Configurator - still need to fix SNIChall 2014-11-20 01:30:53 -08:00
James Kasten
a480107a4f Merge pull request #33 from willnewby/master
Adding dependencies to ubuntu setup in README
2014-11-19 22:30:32 -08:00
James Kasten
35ef6b7048 Remove legacy code from project 2014-11-19 20:46:34 -08:00
Will Newby
6a6e5088c1 Merge branch 'master' of github.com:willnewby/lets-encrypt-preview 2014-11-20 03:19:59 +00:00
Will Newby
091ce0029b Adding dependencies of swig + dialog, for minimal ubuntu installs 2014-11-20 03:18:04 +00:00
Will Newby
a802651513 Adding dependencies of swig + dialog, for minimal ubuntu installs 2014-11-20 03:15:36 +00:00
James Kasten
d01a959ea8 Merge pull request #29 from mpapis/patch-2
wrap command line usage to avoid scrolling
2014-11-19 18:47:38 -08:00
James Kasten
6b2841793c Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-19 17:01:57 -08:00
James Kasten
5dfa3d5715 Changed EULA to Yes/No to Agree/Disagree 2014-11-19 17:01:30 -08:00
James Kasten
c7f29e77b8 Merge pull request #30 from jsoref/spelling
Spelling
2014-11-19 15:46:08 -08:00
James Kasten
7af9263e4a Remove jose dependency, convert display code to use dialog from setup 2014-11-19 15:34:54 -08:00
Josh Soref
9e906d39a3 Spelling: organization 2014-11-19 17:43:42 -05:00
Josh Soref
61543ad19b Spelling: directive 2014-11-19 17:43:08 -05:00
Josh Soref
ad3500eae6 Spelling: challenge 2014-11-19 17:42:00 -05:00
Michal Papis
025f8fc66d wrap command line usage to avoid scrolling 2014-11-19 21:40:20 +01:00
James Kasten
74994a36c4 Merge pull request #22 from kuba/setuptools-script
Setuptools magic
2014-11-19 11:39:33 -08:00
Jakub Warmuz
2669b5c4ae Remove trailing whitespace in README 2014-11-19 20:37:39 +01:00
Jakub Warmuz
d5d1ec8b1c Merge remote-tracking branch 'github/master' into setuptools-script
Conflicts:
	README.md
	letsencrypt.py
2014-11-19 20:36:14 +01:00
James Kasten
1c1e63e322 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-19 09:55:25 -08:00
James Kasten
5d785cb862 Fixed issue #27 2014-11-19 09:55:08 -08:00
James Kasten
7c237ea444 Merge pull request #14 from frewsxcv/patch-1
Make the 'command line usage' section slightly more readable
2014-11-19 09:51:28 -08:00
James Kasten
99036a8aaf Merge pull request #18 from martijnbastiaan/master
Fixed small typo in usage()
2014-11-19 09:24:44 -08:00
James Kasten
db0643676a Merge pull request #23 from skorokithakis/master
Correct dependency.
2014-11-19 09:18:04 -08:00
Stavros Korokithakis
319e985616 Correct dependency. 2014-11-19 15:29:21 +02:00
Jakub Warmuz
69ebee9bf2 Update README for setuptools magic. 2014-11-19 13:36:39 +01:00
Jakub Warmuz
139ad7c3ee Fix setuptools package resources
https://pythonhosted.org/setuptools/setuptools.html#including-data-files
2014-11-19 13:33:53 +01:00
Jakub Warmuz
8dc8ceca2d gitignore setuptools artifacts 2014-11-19 13:29:02 +01:00
Jakub Warmuz
35c4cbd438 letsencrypt.py as setuptools console script 2014-11-19 13:29:02 +01:00
Martijn Bastiaan
c6d58640b2 Fixed small typo in usage() 2014-11-19 09:39:11 +01:00
James Kasten
d56ec3c1da Adding last of @kuba fixes from PR#9 Thanks! 2014-11-18 20:15:22 -08:00
James Kasten
dcdbd13674 All things in the attic are now in the legacy_protocol branch 2014-11-18 18:45:58 -08:00
James Kasten
02bd284390 Merge pull request #13 from skorokithakis/master
Add requirements.txt
2014-11-18 18:44:53 -08:00
James Kasten
d3708e1e79 Remove trustify/attic related code from setup.py
Conflicts:
	.gitignore
	setup.py
2014-11-18 18:39:39 -08:00
James Kasten
ea3804efb7 Sort setup install_requires
Conflicts:
	setup.py
2014-11-18 18:36:15 -08:00
Jakub Warmuz
c843a9681f Fix SyntaxError 2014-11-18 18:36:15 -08:00
James Kasten
229f88dfcd setup install_requires python2-pythondialog, jose, jsonschema
Conflicts:
	setup.py
2014-11-18 18:36:15 -08:00
Jakub Warmuz
3486a53132 python-augeas missing from PyPI 2014-11-18 18:36:15 -08:00
Corey Farwell
9fb1e2a028 Make the 'command line usage' section slightly more readable 2014-11-18 21:05:48 -05:00
Stavros Korokithakis
0704dd7b80 Merge branch 'master' of https://github.com/letsencrypt/lets-encrypt-preview 2014-11-19 04:01:57 +02:00
Stavros Korokithakis
eef9e3e948 Add requirements.txt. 2014-11-19 04:01:29 +02:00
James Kasten
badd23b717 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-18 18:00:41 -08:00
James Kasten
08e07e67a6 small cleanup 2014-11-18 18:00:30 -08:00
James Kasten
41093304ae Merge pull request #12 from alex/flake8-w
Replaced tabs with spaces and removed usage of several deprecated features:
2014-11-18 17:12:46 -08:00
Alex Gaynor
6011453a14 Replaced tabs with spaces and removed usage of several deprecated features:
* dict.has_key -> `in` operator
* backticks -> repr

Also removed trailing newlines from a few files.

flake8 --select='W' no longer reports any issues
2014-11-18 17:01:31 -08:00
James Kasten
a5f985c9cc Merge pull request #11 from alex/trailing-whitespace-unused-imports
Removed trailing whitespace as well as unused imports
2014-11-18 16:56:11 -08:00
Alex Gaynor
0b75c5194f Removed trailing whitespace as well as unused imports 2014-11-18 16:41:13 -08:00
Seth Schoen
b8170a38ec docstrings and add JSON pretty-printing code 2014-11-18 16:39:38 -08:00
James Kasten
3ff20fbeb4 Merge pull request #2 from alex/use-requests
Switched from using urllib2 to requests.
2014-11-18 16:30:09 -08:00
James Kasten
786b0c7b6b Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-18 16:27:25 -08:00
James Kasten
aa022adecb Change CONFIG to point to an ACME server with valid trusted certificate 2014-11-18 16:27:10 -08:00
James Kasten
ce9a3a3f27 Merge pull request #7 from bkerensa/patch-1
Remove Unneccesary How To
2014-11-18 13:39:50 -08:00
Benjamin Kerensa
373f514de6 Remove Unneccesary How To
The how to portion is not necessary in the license file and only serves as instructions.
2014-11-18 13:08:09 -08:00
James Kasten
073e9758f1 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-18 12:34:10 -08:00
James Kasten
91859d8a99 moved old protocol directory to attic to avoid confusion 2014-11-18 12:33:48 -08:00
James Kasten
2faacc1b43 update options-ssl.conf 2014-11-18 12:00:14 -08:00
Peter Eckersley
d5d39da999 Merge pull request #4 from zuehlaa/typos
fixed a few typos... though please note that https://github.com/letsencrypt/acme-spec is the current plan for an issuance protocol!
2014-11-18 10:39:12 -08:00
Aaron Zuehlke
3f02247791 fixed a few typos 2014-11-18 11:24:43 -06:00
Alex Gaynor
bcda03d948 Set the content-type header and use POST, which the ACME spec requires 2014-11-18 09:15:36 -08:00
Alex Gaynor
a9e0028007 Use the older requests API 2014-11-18 09:09:56 -08:00
James Kasten
7bb2a6ccf0 Merge pull request #1 from alex/fix-typos
Fixed several typos
2014-11-18 08:56:37 -08:00
Alex Gaynor
efaec60e6b Switched from using urllib2 to requests.
urllib2 is a security hazzard, it does not perform certificate checks against a trust root by default, nor does it perform service_identity checks.

Also, requests has a prettier API.
2014-11-18 08:13:06 -08:00
Alex Gaynor
00aae545ac Fixed several typos 2014-11-18 07:55:39 -08:00
James Kasten
ec92f6d935 Added more notes about testing of configurator 2014-11-18 03:38:00 -08:00
James Kasten
f59a581c35 Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-18 03:20:54 -08:00
James Kasten
abf865beca Add --help option 2014-11-18 03:20:42 -08:00
Peter Eckersley
c18e0838d6 README formatting 2014-11-18 03:14:49 -08:00
Peter Eckersley
08cb7f9dff Merge remote-tracking branch 'letsencrypt/master' 2014-11-18 03:01:16 -08:00
Peter Eckersley
c9009b1e54 Cleanup README 2014-11-18 03:00:34 -08:00
James Kasten
77e7ddda7e Merge branch 'master' of github.com:letsencrypt/lets-encrypt-preview 2014-11-18 02:59:39 -08:00
James Kasten
8d55ffdd87 Formatted output of filter_names selection for --text mode 2014-11-18 02:59:28 -08:00
Peter Eckersley
6b69f6371c Add usage 2014-11-18 02:46:59 -08:00
Peter Eckersley
0e068b4670 Add usage 2014-11-18 02:46:36 -08:00
Peter Eckersley
3c89b6838c formatting 2014-11-18 02:44:58 -08:00
Peter Eckersley
614cf89d55 More documentation 2014-11-18 02:42:56 -08:00
Peter Eckersley
396f6b0b50 Attempt to document dependencies 2014-11-18 02:31:55 -08:00
James Kasten
1dca1441ee Added notes about the status of configurator 2014-11-18 01:56:26 -08:00
James Kasten
aa3d7986d8 Fixed --text bug when selecting whether to redirect host 2014-11-18 01:55:36 -08:00
Peter Eckersley
bd19df886b README should be markdown. 2014-11-17 15:34:19 -08:00
Peter Eckersley
3fb1ab94d7 New README for Let's Encrypt 2014-11-17 15:34:00 -08:00
Peter Eckersley
9a298364f1 Add a license for launch: Apache 2.0 2014-11-17 15:28:27 -08:00
Seth Schoen
0bbec49880 consistent naming of PEM files in CONFIG.py 2014-11-17 15:00:36 -08:00
Seth Schoen
16af948b36 use JSON Schema schemata for ACME protocol validation 2014-11-17 14:50:01 -08:00
James Kasten
ac28e9f880 Clean code... add OCSP staple check 2014-11-17 03:51:00 -08:00
Peter Eckersley
b4170b539d README for the attic 2014-11-14 18:19:02 -08:00
Peter Eckersley
59f60e1fe2 Move files that are specific to the trustify protocol into the attic 2014-11-14 18:16:40 -08:00
Peter Eckersley
8f660978e4 rm intermediate-state protocol docs 2014-11-14 18:00:10 -08:00
James Kasten
6b69241e17 Cleaned up --text option by wrapping all lines at 80 chars 2014-11-13 13:05:03 -08:00
James Kasten
c1a482a8da Formatting changes before demo 2014-11-13 01:49:32 -08:00
James Kasten
69b8f58735 forgot files in top directory 2014-11-11 23:22:59 -08:00
James Kasten
29b21ebb7a Renamed client strings/file names plus a few small changes 2014-11-11 23:21:36 -08:00
James Kasten
f72836ba14 Add validator class to be used after configuration to guarantee correctness 2014-11-11 11:52:53 -08:00
James Kasten
4af311894b Finished refactoring client.py and also reduced column size to 80 through display and client 2014-11-11 01:42:46 -08:00
James Kasten
66c37a2d40 Pulled out all display code out of client and arranged code into a curses and stdout Singleton 2014-11-10 22:55:01 -08:00
James Kasten
66b5b7a0c5 Changed display to a Singleton inherited class that can inherit multiple display types 2014-11-10 16:19:24 -08:00
James Kasten
1712a024e5 Started refactoring code - roughly demo ready 2014-11-10 07:30:36 -05:00
James Kasten
cbec87e181 Fully support Revocation with menus 2014-11-09 07:30:40 -05:00
James Kasten
19bc2fa084 Certificate Issuance/Deployment/Redirection; recovery tokens/contact 2014-11-08 06:11:29 -05:00
James Kasten
412b28b219 Protocol overhaul - initial commit down to certificate issuance 2014-11-07 06:38:48 -05:00
James Kasten
80799e28a0 Initial ACME compliant DVSNI commit 2014-11-06 05:37:22 -05:00
396 changed files with 31017 additions and 29445 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
# this file uses slightly different syntax than .gitignore,
# e.g. ".tox/" will not ignore .tox directory
# well, official docker build should be done on clean git checkout
# anyway, so .tox should be empty... But I'm sure people will try to
# test docker on their git working directories.
.git
.tox
venv
venv3
docs

25
.gitignore vendored
View file

@ -1,3 +1,24 @@
*.pyc
trustify/protocol/chocolate_pb2.py
m3
*.egg-info/
.eggs/
build/
dist/
/venv/
/venv3/
/.tox/
letsencrypt.log
# coverage
.coverage
/htmlcov/
/.vagrant
# editor temporary files
*~
*.swp
\#*#
.idea
# auth --cert-path --chain-path
/*.pem

6
.gitmodules vendored
View file

@ -1,6 +0,0 @@
[submodule "m3crypto"]
path = m3crypto
url = git@github.com:research/m3crypto.git
[submodule "pygeoip"]
path = pygeoip
url = https://github.com/appliedsec/pygeoip.git

333
.pylintrc Normal file
View file

@ -0,0 +1,333 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=linter_plugin
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,locally-disabled,abstract-class-not-used
# abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1)
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input,file
# Good variable names which should always be accepted, separated by a comma
good-names=f,i,j,k,ex,Run,_,fd,logger
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,40}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,40}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__)|(test_[A-Za-z0-9_]*)|(_.*)|(.*Test$)
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,logger
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=(unused)?_.*|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=6
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=yes
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1250
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=pkg_resources,confargparse,argparse,six.moves,six.moves.urllib
# import errors ignored only in 1.4.4
# https://bitbucket.org/logilab/pylint/commits/cd000904c9e2
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=yes
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defined in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,implementedBy,providedBy
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
max-args=6
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=12
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

39
.travis.yml Normal file
View file

@ -0,0 +1,39 @@
language: python
services:
- rabbitmq
# http://docs.travis-ci.com/user/ci-environment/#CI-environment-OS
before_install:
- travis_retry sudo ./bootstrap/ubuntu.sh
- travis_retry sudo apt-get install --no-install-recommends nginx-light openssl
# using separate envs with different TOXENVs creates 4x1 Travis build
# matrix, which allows us to clearly distinguish which component under
# test has failed
env:
global:
- GOPATH=/tmp/go
matrix:
- TOXENV=py26 BOULDER_INTEGRATION=1
- TOXENV=py27 BOULDER_INTEGRATION=1
- TOXENV=py33
- TOXENV=py34
- TOXENV=lint
- TOXENV=cover
install: "travis_retry pip install tox coveralls"
before_script: '[ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/boulder-start.sh amqp'
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || (source .tox/$TOXENV/bin/activate && ./tests/boulder-integration.sh))'
after_success: '[ "$TOXENV" == "cover" ] && coveralls'
notifications:
email: false
irc:
channels:
- "chat.freenode.net#letsencrypt"
on_success: never
on_failure: always
use_notice: true
skip_join: true

27
CHANGES.rst Normal file
View file

@ -0,0 +1,27 @@
ChangeLog
=========
Please note:
the change log will only get updated after first release - for now please use the
`commit log <https://github.com/letsencrypt/letsencrypt/commits/master>`_.
Release 0.1.0 (not released yet)
--------------------------------
New Features:
* ...
Fixes:
* ...
Other changes:
* ...
Release 0.0.0 (not released yet)
--------------------------------
Initial release.

18
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,18 @@
<!---
This file serves as an entry point for GitHub's Contributing
Guidelines [1] only.
GitHub doesn't render rST very well, especially in respect to internal
hyperlink targets and cross-references [2]. People also tend to
confuse rST and Markdown syntax. Therefore, instead of keeping the
contents here (and including from rST documentation under doc/), link
to the Sphinx generated docs is provided below.
[1] https://github.com/blog/1184-contributing-guidelines
[2] http://docutils.sourceforge.net/docs/user/rst/quickref.html#hyperlink-targets
-->
https://letsencrypt.readthedocs.org/en/latest/contributing.html

66
Dockerfile Normal file
View file

@ -0,0 +1,66 @@
# https://github.com/letsencrypt/letsencrypt/pull/431#issuecomment-103659297
# it is more likely developers will already have ubuntu:trusty rather
# than e.g. debian:jessie and image size differences are negligible
FROM ubuntu:trusty
MAINTAINER Jakub Warmuz <jakub@warmuz.org>
MAINTAINER William Budington <bill@eff.org>
# Note: this only exposes the port to other docker containers. You
# still have to bind to 443@host at runtime, as per the ACME spec.
EXPOSE 443
# TODO: make sure --config-dir and --work-dir cannot be changed
# through the CLI (letsencrypt-docker wrapper that uses standalone
# authenticator and text mode only?)
VOLUME /etc/letsencrypt /var/lib/letsencrypt
WORKDIR /opt/letsencrypt
# no need to mkdir anything:
# https://docs.docker.com/reference/builder/#copy
# If <dest> doesn't exist, it is created along with all missing
# directories in its path.
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/
RUN /opt/letsencrypt/src/ubuntu.sh && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* \
/tmp/* \
/var/tmp/*
# the above is not likely to change, so by putting it further up the
# Dockerfile we make sure we cache as much as possible
COPY setup.py README.rst CHANGES.rst MANIFEST.in /opt/letsencrypt/src/
# all above files are necessary for setup.py, however, package source
# code directory has to be copied separately to a subdirectory...
# https://docs.docker.com/reference/builder/#copy: "If <src> is a
# directory, the entire contents of the directory are copied,
# including filesystem metadata. Note: The directory itself is not
# copied, just its contents." Order again matters, three files are far
# more likely to be cached than the whole project directory
COPY letsencrypt /opt/letsencrypt/src/letsencrypt/
COPY acme /opt/letsencrypt/src/acme/
COPY letsencrypt-apache /opt/letsencrypt/src/letsencrypt-apache/
COPY letsencrypt-nginx /opt/letsencrypt/src/letsencrypt-nginx/
# requirements.txt not installed!
RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
/opt/letsencrypt/venv/bin/pip install \
-e /opt/letsencrypt/src/acme \
-e /opt/letsencrypt/src \
-e /opt/letsencrypt/src/letsencrypt-apache \
-e /opt/letsencrypt/src/letsencrypt-nginx
# install in editable mode (-e) to save space: it's not possible to
# "rm -rf /opt/letsencrypt/src" (it's stays in the underlaying image);
# this might also help in debugging: you can "docker run --entrypoint
# bash" and investigate, apply patches, etc.
ENV PATH /opt/letsencrypt/venv/bin:$PATH
# TODO: is --text really necessary?
ENTRYPOINT [ "letsencrypt", "--text" ]

1
EULA Symbolic link
View file

@ -0,0 +1 @@
letsencrypt/EULA

205
LICENSE.txt Normal file
View file

@ -0,0 +1,205 @@
Let's Encrypt:
Copyright (c) Internet Security Research Group
Licensed Apache Version 2.0
Incorporating code from nginxparser
Copyright (c) 2014 Fatih Erikli
Licensed MIT
Text of Apache License
======================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
Text of MIT License
===================
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1 +1,7 @@
recursive-include trustify *
include requirements.txt
include README.rst
include CHANGES.rst
include CONTRIBUTING.md
include linter_plugin.py
include letsencrypt/EULA
recursive-include letsencrypt/tests/testdata *

24
README
View file

@ -1,24 +0,0 @@
The Chocolate project to implement sweet automatic encryption for webservers.
There are two portions to the Chocolate protocol.
trustify/ contains code that can be run on any webserver (eventually,
email, XMPP and other SSL-securable servers too); it is used to automatically
request and install a CA-signed certificate for that server's public names.
server-ca/ contains a reference implementation for CAs to receive requests for
certs, set challenges for the requesting servers to prove that they really
control the names, and issue certificates.
Debian dependencies:
build deps:
swig
protobuf-compiler
python-dev
others:
gnutls-bin # for make cert requests
python-protobuf
python-dialog
hashcash

120
README.rst Normal file
View file

@ -0,0 +1,120 @@
.. notice for github users
Official **documentation**, including `installation instructions`_, is
available at https://letsencrypt.readthedocs.org.
Generic information about Let's Encrypt project can be found at
https://letsencrypt.org. Please read `Frequently Asked Questions (FAQ)
<https://letsencrypt.org/faq/>`_.
About the Let's Encrypt Client
==============================
|build-status| |coverage| |docs| |container|
In short: getting and installing SSL/TLS certificates made easy (`watch demo video`_).
The Let's Encrypt Client is a tool to automatically receive and install
X.509 certificates to enable TLS on servers. The client will
interoperate with the Let's Encrypt CA which will be issuing browser-trusted
certificates for free beginning the summer of 2015.
It's all automated:
* The tool will prove domain control to the CA and submit a CSR (Certificate
Signing Request).
* If domain control has been proven, a certificate will get issued and the tool
will automatically install it.
All you need to do to sign a single domain is::
user@www:~$ sudo letsencrypt -d www.example.org auth
For multiple domains (SAN) use::
user@www:~$ sudo letsencrypt -d www.example.org -d example.org auth
and if you have a compatible web server (Apache or Nginx), Let's Encrypt can
not only get a new certificate, but also deploy it and configure your
server automatically!::
user@www:~$ sudo letsencrypt -d www.example.org run
**Encrypt ALL the things!**
.. |build-status| image:: https://travis-ci.org/letsencrypt/letsencrypt.svg?branch=master
:target: https://travis-ci.org/letsencrypt/letsencrypt
:alt: Travis CI status
.. |coverage| image:: https://coveralls.io/repos/letsencrypt/letsencrypt/badge.svg?branch=master
:target: https://coveralls.io/r/letsencrypt/letsencrypt
:alt: Coverage status
.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/
:target: https://readthedocs.org/projects/letsencrypt/
:alt: Documentation status
.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status
:target: https://quay.io/repository/letsencrypt/letsencrypt
:alt: Docker Repository on Quay.io
.. _`installation instructions`:
https://letsencrypt.readthedocs.org/en/latest/using.html
.. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU
Disclaimer
----------
This is a **DEVELOPER PREVIEW** intended for developers and testers only.
**DO NOT RUN THIS CODE ON A PRODUCTION SERVER. IT WILL INSTALL CERTIFICATES
SIGNED BY A TEST CA, AND WILL CAUSE CERT WARNINGS FOR USERS.**
Current Features
----------------
* web servers supported:
- apache/2.x (tested and working on Ubuntu Linux)
- nginx/0.8.48+ (tested and mostly working on Ubuntu Linux)
- standalone (runs its own webserver to prove you control the domain)
* the private key is generated locally on your system
* can talk to the Let's Encrypt (demo) CA or optionally to other ACME
compliant services
* can get domain-validated (DV) certificates
* can revoke certificates
* adjustable RSA key bitlength (2048 (default), 4096, ...)
* optionally can install a http->https redirect, so your site effectively
runs https only (Apache only)
* fully automated
* configuration changes are logged and can be reverted using the CLI
* text and ncurses UI
* Free and Open Source Software, made with Python.
Links
-----
Documentation: https://letsencrypt.readthedocs.org
Software project: https://github.com/letsencrypt/letsencrypt
Notes for developers: CONTRIBUTING.md_
Main Website: https://letsencrypt.org/
IRC Channel: #letsencrypt on `Freenode`_
Mailing list: `client-dev`_ (to subscribe without a Google account, send an
email to client-dev+subscribe@letsencrypt.org)
.. _Freenode: https://freenode.net
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
.. _CONTRIBUTING.md: https://github.com/letsencrypt/letsencrypt/blob/master/CONTRIBUTING.md

30
Vagrantfile vendored Normal file
View file

@ -0,0 +1,30 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
# Setup instructions from docs/using.rst
$ubuntu_setup_script = <<SETUP_SCRIPT
cd /vagrant
sudo ./bootstrap/ubuntu.sh
if [ ! -d "venv" ]; then
virtualenv --no-site-packages -p python2 venv
./venv/bin/pip install -r requirements.txt -e acme -e .[dev,docs,testing] -e letsencrypt-apache -e letsencrypt-nginx
fi
SETUP_SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "ubuntu-trusty", primary: true do |ubuntu_trusty|
ubuntu_trusty.vm.box = "ubuntu/trusty64"
ubuntu_trusty.vm.provision "shell", inline: $ubuntu_setup_script
ubuntu_trusty.vm.provider "virtualbox" do |v|
# VM needs more memory to run test suite, got "OSError: [Errno 12]
# Cannot allocate memory" when running
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
v.memory = 1024
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,869 +0,0 @@
<?xml version="1.0" encoding="US-ASCII"?>
<!DOCTYPE rfc SYSTEM "rfc2629.dtd" [
<!ENTITY RFC2119 SYSTEM "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2119.xml">
<!ENTITY RFC2314 SYSTEM "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2314.xml">
<!ENTITY RFC2818 SYSTEM "http://xml.resource.org/public/rfc/bibxml/reference.RFC.2818.xml">
<!ENTITY RFC5226 SYSTEM "http://xml.resource.org/public/rfc/bibxml/reference.RFC.5226.xml">
<!ENTITY RFC5246 SYSTEM "http://xml.resource.org/public/rfc/bibxml/reference.RFC.5246.xml">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<?rfc toc="yes" ?>
<?rfc symrefs="yes" ?>
<?rfc strict="yes" ?>
<?rfc compact="yes" ?>
<?rfc sortrefs="yes" ?>
<?rfc colonspace="yes" ?>
<?rfc rfcedstyle="no" ?>
<!-- Don't change this. It breaks stuff -->
<?rfc tocdepth="4"?>
<rfc category="std" docName="draft-rescorla-stir-fallback-00"
ipr="pre5378Trust200902">
<front>
<title abbrev="ACIP">Automatic Certificate Issuance Protocol (ACIP)</title>
<author fullname="Eric Rescorla" initials="E.K." surname="Rescorla">
<organization>Mozilla</organization>
<address>
<postal>
<street>2064 Edgewood Drive</street>
<city>Palo Alto</city>
<region>CA</region>
<code>94303</code>
<country>USA</country>
</postal>
<phone>+1 650 678 2350</phone>
<email>ekr@rtfm.com</email>
<!-- Insert names of other people here -->
</address>
</author>
<date day="11" month="December" year="2013" />
<area>SEC</area>
<abstract>
<t>
[TODO]
</t>
</abstract>
</front>
<middle>
<section anchor="sec-term" title="Terminology">
<t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in <xref
target="RFC2119">RFC 2119</xref>.</t>
</section>
<section title="Introduction" anchor="sec.intro">
<t>
Existing Web PKI certificate authorities tend to run on
a set of ad hoc protocols for certificate issuance and
identity verification. A typical user experience is something
like:
</t>
<t>
<list style="symbols">
<t>Generate a PKCS#10 <xref target="RFC2314"/> Certificate Signing Request (CSR).</t>
<t>Cut-and-paste the CSR into a CA web page.</t>
<t>Prove ownership of the domain by one of the following methods:
<list style="symbols">
<t>Put a CA-provided challenge at a specific place on the web server</t>
<t>Put a CA-challenge at a DNS location corresponding to the target domain.</t>
<t>Receive CA challenge at an (allegedly) administrator controlled e-mail address corresponding to the domain and then respond to it on the CA's web page.</t>
</list>
</t>
<t>Download the issued certificate and install it on their Web Server.</t>
</list>
</t>
<t>
With the exception of the CSR itself and the certificates that are
issued, these are all completely ad hoc procedures and are accomplished
by getting the user to follow instructions from the CA rather than by
published protocols. In many cases, the instructions are difficult to
follow and cause significant confusion. Even in the best case, the
lack of published, standardized mechanisms presents an obstacle to
the wide deployment of certificate-using systems.
</t>
<t>
This document describes a framework for automating the issuance
and domain validation procedure, thus enabling the construction of
certificate-holding entities which can obtain certificates without
user interaction. It is hoped that this will radically increase
the level of deployment of certificate-using systems and specifically
of HTTPS.
</t>
</section>
<section title="Deployment Model and Operator Experience">
<t>
It's easiest to understand ACIP in the context of certificates
for Web sites (HTTPS <xref target="RFC2818"/>). In that case, the
server is intended to speak for one or more domains and the process of
certificate issuance is intended to verify that the server actually
speaks for the domain. In the case of "Domain Validation" (DV) certificates,
the server validation process merely verifies that the requester
has effective control of the domain but does not really attempt to
verify their real-world identity. (This is as opposed to "Extended
Validation" (EV) certificates where the process is intended to also verify
the real-world identity of the requester.)
</t>
<t>
When an operator deploys a current HTTPS server, it generally prompts
him to generate a self-signed certificate.
When an operator deploys an ACIP-compatible server, the experience would
be something like this:
</t>
<t>
<list style="symbols">
<t>The server prompts the operator for the intended domain name(s) that the
server is to stand for.</t>
<t>The server presents the operator with a list of CAs which it
could get a certificate from. The server might prompt the operator
for payment information at this point.</t>
<t>Once the operator has selected a CA, tells the operator that he will have a certificate shortly.</t>
<t>In the background, the server contacts the CA, sends a CSR, and
engages in the validation procedure.</t>
<t>Once the CA is satisfied, the certificate is issued and the
server automatically downloads and installs it, potentially notifying
the operator via e-mail, SMS, etc.</t>
<t>The server periodically contacts the CA to get updated certificates,
stapled OCSP responses, or whatever else would be required to keep
the server functional and its credentials up-to-date.</t>
</list>
</t>
<t>
The overall idea is that it's nearly as easy to deploy with a valid
certificate as a self-signed certificate and that once the operator
has done so, the process is self-sustaining with minimal manual
intervention.
</t>
<section title="Other Use Cases">
<t>
While ACIP is explicitly designed for HTTPS server certificates,
it is in general usable in any situation where the certificate
subject is automatically verifiable. In principle, then, it
could be used with personal addresses (XMPP or e-mail) if there
is some straightforward way of responding to the validation
procedure, e.g., by having the client automatically
detect and respond to validation answerback queries.
</t>
</section>
</section>
<section title="System Architecture Overview" anchor="sec.overview">
<t>
We assume that that an ACIP-compatible agent which wishes
to get a certificate (currently, the "Certificate Holder" (CH))
starts with a list of ACIP-compliant CAs represented as URLs.
From there, the protocol proceeds as shown below. Note
that we have taken some liberties with messages from the
CA to the CH: everything is done over HTTPS, so messages
from the CA likely require either polling by the CH or
that the CH have some accessible endpoint for the CA to
talk to.
</t>
<section title="Initial Issuance" anchor="sec.initial-issuance">
<figure>
<artwork align="left" alt="" height="" name="Initial Issuance" type="" width=""
xml:space="preserve"><![CDATA[
CH CA
Policy
<-------- Validation Methods
Desired identity
CSR
Validation Method -------->
<-------- Challenge
Response -------->
<-------- Certificate
]]></artwork>
</figure>
<t>
Initially, the CH contacts the CA and retrieves the CA's
capabilities. This likely includes information like
the validation types (e-mail, DNS, Web, etc.) that the
CA supports, whether it charges, the types of keys it will
issue for, how often the certificates
need to be reissued, etc. The CH might contact multiple
CAs to find one with the best policies before it actually
requests a certificate.
</t>
<t>
Once the CH (or more likely the operator) has selected a CA,
the CH generates a key pair and contacts the CA with
signing request. He will also indicate (probably separately),
the identity he wants the certificate issued for and which
validation method he wants the CA to use. He might
also provide a URL which the CA can use to indicate that
the certificate is ready.
</t>
<t>
Depending on the validation method, the CA will then supply
a challenge to the CH. This might happen immediately, as
in the case of HTTPS validation (section XXX) or might
happen later, as in the case of e-mail validation (section YYY).
The CH responds to the challenge, e.g., by putting it somewhere
in /.well-known on their server.
</t>
<t>
Once the CA has validated the CH's identity, it issues the
certificate. The CH might either poll the CA for readiness
or the CH can provide a URL for the CA to contact when
the certificate is ready. In either case, once the CH
has the certificate, it installs it.
</t>
</section>
<section title="Certificate Refresh" anchor="sec.cert-refresh">
<figure>
<artwork align="left" alt="" height="" name="Re-Issuance" type="" width=""
xml:space="preserve"><![CDATA[
CH CA
Retrieve Certificate -------->
<-------- Certificate
]]></artwork>
</figure>
<t>
Because certificates eventually expire, there must be some
for the CH to get a new certificate. This need not involve
a new authentication transaction--and if short-lived
certificates are used, then it generally will not--but
does require getting new bits to the CH.
In general, the CA will just generate new certificates
periodically and publish them at some deterministic
URL that the CH can retrieve them at. Where a new
authentication transaction is required, we just repeat
the flow of <xref target="sec.initial-issuance"/>
</t>
</section>
</section>
<section title="Protocol Description">
<t>
This section describes the relevant protocol elements.
For explanatory reasons, we have chosen to describe
these as if they were JSON structures. Eventually
we may decide on some more fixed binary encoding,
but it's easier to explain with JSON and we may
eventually just use JSON in any cse.
</t>
<section title="CA Policies" anchor="sec.ca-policies">
<t>
Every ACIP-compliant CA will post a policy document in
some public location. This document MUST be hosted over
HTTPS <xref target="RFC2818"/> so that it can be validated.
The URL is assumed to be known to the CH.
</t>
<t>
The policy document contains the following values:
</t>
<t>
<list style="hanging">
<t></t><t hangText="issuance_validation_methods (mandatory):"></t>
<t>A list of validation methods for issuance that the CA supports.</t>
<t></t><t hangText="revocation_validation_methods (mandatory):"></t>
<t>A list of validation methods for revocation that the CA supports.</t>
<t></t><t hangText="signature_algorithms (mandatory):"></t>
<t>A list of signature algorithms that the CA supports. [TODO: which registry]</t>
<t></t><t hangText="key_types (mandatory):"></t>
<t>A list of the key types that the CA allows the CH to have, structured
as an algorithm and an (optional min/max} [TODO: which registry.</t>
<t></t><t hangText="status_mechanisms (mandatory)"></t>
<t>A list of the status mechanisms that the CA supports (OCSP, etc.)</t>
<t></t><t hangText="signing_certificate (optional):"></t>
<t>The base64 [REF] encoding of the certificate which will
be used to authorize the CH's key. If the CA has multiple
certificates signed by some intermediate, it will include
the intermediate. The idea here is to provide some idea
of what the final certificate will look like.</t>
<t></t><t hangText="min_lifetime (optional):"></t>
<t>The minimum lifetime of certificates issued by the CA in seconds
so that the CH knows how often it is likely to have to refresh.</t>
</list>
</t>
<t>
An example policy document might look like the one below.
</t>
<figure>
<artwork><![CDATA[
{
"issuance_issuance_validation_methods" :
["https", "https-sni"],
"revocation_validation_methods" :
["https", "shared-secret"],
"signature_algorithms" : [ "RSA", "ECDSA"],
"key_types" : [
{
"algorithm" : "RSA",
"min" : 2048,
"max" : 4096
},
{
"algorithm" : "ECDSA",
"min" : 192,
"max" : 512
},
"min_lifetime" : 604800,
"status_mechanisms" : ["OCSP",
"short-lived",
"OCSP-stapled"],
]
}
]]></artwork>
</figure>
<t>
Note that one important consideration is that it be possible for
add new policy values. Since these are just information to the
CH, if they are unknown they can be ignored, but may of course
cause issuance failure. [TODO: Add some support for payment.]
[TODO: Add other types.]
</t>
</section>
<section title="Protocol Messages" anchor="sec.messages">
<t>
All ACIP protocol messages other than the policy document are
carried in a generic wrapper.
</t>
<t>
<list style="hanging">
<t></t><t hangText="type (mandatory):"></t>
<t>The message type, either "error" or one of the messages
described below.</t>
<t></t><t hangText="message (mandatory):"></t>
<t>The message itself. In the JSON encoding here, just the
JSON structure shown below.</t>
</list>
</t>
<t>
An example message is shown below.
</t>
<figure>
<artwork><![CDATA[
{
"type": "issuance_request",
"message":
{
"validation_method" : "https",
"identities" : [
{
"type" : "dnsNAme",
"www1.example.com"
},
{
"type" : "dnsNAme",
"www2.example.com"
}
],
"csr" : "<base-64-encoded CSR>",
"notification_endpoint" :
"http://status.example.com/acip-notification"
}
}
]]></artwork>
</figure>
<section title="Issuance Requests" anchor="sec.issuance-requests">
<t>
The issuance request (message type "issuance_request") is relatively simple:
</t>
<t>
<list style="hanging">
<t></t><t hangText="validation_method (mandatory):"></t>
<t>The validation method the CH has selected.</t>
<t></t><t hangText="identities (mandatory):"></t>
<t>A list of the identities that the CH wants issuance for.
Each identity is a pair of PKIX type and a string indicating
the identity and may also contain a "validation_info"
value for the selected validation method.</t>
<t></t><t hangText="csr (mandatory):"></t>
<t>The PKCS#10 CSR.</t>
<t></t><t hangText="notification_endpoint (optional):"></t>
<t>A URL that the CA can contact with status notifications"</t>
</list>
</t>
<t>
An example issuance request is shown below:
</t>
<figure>
<artwork><![CDATA[
{
"validation_method" : "https",
"identities" : [
{
"type" : "dnsNAme",
"www1.example.com"
},
{
"type" : "dnsNAme",
"www2.example.com"
}
],
"csr" : "<base-64-encoded CSR>",
"notification_endpoint" :
"http://status.example.com/acip-notification"
}
]]></artwork>
</figure>
<t>
The CA responds to a successful issuance request with a message
indicating acknowledgement (type "issuance_response"),
and containing the following contents.
</t>
<t>
<list style="hanging">
<t></t><t hangText="challenges (optional)"></t>
<t>If the selected validation method requires a challenge, then
the CA shall include the challenge data in the challenges
value. The data is a list of values containing an identity
field from the request. More than one challenge may be
provided for a given identity, which means that the CH
must comply with all of them. [TODO: Do we want to allow
multiple simultaneous types of challenge? If so, we may
need to tweak the negotiation to make the client offer
and then add a type in this field.</t>
<t></t><t hangText="status_url (mandatory)"></t>
<t>A URL which the CH can contact to find the status of the
request. This MUST contain a large enough random component
to avoid guessing (minumum 80 bits of entropy).</t>
<t></t><t hangText="certificate_url (mandatory)"></t>
<t>A URL where the eventual certificate will live. At any
point this MUST contain the most recent certificate, thus
enabling short-lived certificates. This need not be secret.</t>
</list>
</t>
<t>
An example issuance response is shown below:
</t>
<figure>
<artwork><![CDATA[
{
"challenges" : [
{
"identity" : {
"type" : "dnsNAme",
"www1.example.com"
},
"value" : {
"path" : "<random1>",
"value" : "<random2>",
},
},
{
"identity" : {
"type" : "dnsNAme",
"www2.example.com"
},
"value" : {
"path" : "<random3>",
"value" : "<random4>",
}
}
],
"status_url":"https://ca.example.com/status/<random>",
"certificate_url:"https://ca.example.com/cert/1234"
]]></artwork>
</figure>
</section>
<section title="HTTPS with SNI" anchor="sec.https-with-sni">
<t>[TODO]</t>
</section>
<section title="DNS" anchor="sec.dns"/>
<section title="DANE" anchor="sec.dane"/>
</section>
<section title="Certificate Issuance" anchor="sec.issuance">
<t>
Once the certificate is issued, the CA places it at the
location indicated by the "certificate_url" it previously
provided in its request to the issuance response. A
HTTP GET to that location produces a JSON structure
of the following form.
</t>
<t>
<list style="hanging">
<t></t><t hangText="certificate chain (mandatory):"></t>
<t>A list of certificates in the order specified by
TLS <xref target="RFC5246"/>
(i.e., with each certificate certifying the next one and the
end-entity certificate as the last one.). The
CA SHOULD include the intermediate certificates
that it believes a relying party would use,but
ultimately it is the CH's job to sort this out.</t>
<t></t><t hangText="next_issuance (mandatory):"></t>
<t>The time that the CA expects to automatically
issue a replacement for the certificate, to be used
for the CH to determine when to refresh.</t>
</list>
</t>
</section>
<section title="Requesting Revocation" anchor="sec.revocation-request">
<t>
[TODO(ekr@rtfm.com): Should we refactor this and issuance to
have the common elements in one place.]
Under some circumstances, ACIP-issued certificates may need to
be revoked. While conventional mechanisms (OCSP, CRLs, etc.)
can be used for disseminating status information, an automatic
mechanism is needed for informing the CA that the certificate
should be revoked. As with certificate issuance, ACIP provides
a generic framework for this request, which then must be validated
by one or more of several validation mechanisms.
</t>
<t>
The general form of the revocation request is:
</t>
<t>
<list style="hanging">
<t></t><t hangText="validation_method (mandatory):"></t>
<t>The revocation validation method the CH has selected.</t>
<t></t><t hangText="certificates (mandatory):"></t>
<t>A list of the certificates to revoke.
Each entry in the list has a "certificate" field and
may also have a "validation_info" field if required
by the validation method.
If multiple certificates
have been provided, they MUST all have the same identities.
</t>
</list>
</t>
<t>
For example:
</t>
<figure>
<artwork><![CDATA[
{
"validation_method" : "shared_secret",
"certificates" : [
"certificate" : "<base-64-encoded cert>",
"validation_info" : "<revocation-secret>"
]
}
]]></artwork>
</figure>
<t>
The CA responds to a revocation request either with an error
or with an indication of what the CH needs to do in order to
have the certificate revoked (type "revocation_response"),
containing the following items:
</t>
<t>
<list style="hanging">
<t></t><t hangText="challenges (optional)"></t>
<t>If the selected validation method requires a challenge, then
the CA shall include the challenge data in the challenges
value. The data is a list of values containing an identity
field from the request. More than one challenge may be
provided for a given identity, which means that the CH
must comply with all of them.</t>
<t></t><t hangText="status_url (mandatory)"></t>
<t>A URL which the CH can contact to find the status of the
request. This MUST contain a large enough random component
to avoid guessing (minumum 80 bits of entropy).</t>
</list>
</t>
</section>
<section title="Validation Methods" anchor="sec.validation-types">
<t>
This section describes mechanisms for validating requests (both
issuance and revocation). In practice, some of these may only
be useful for validating issuance or revocation (noted below
where known) but because there is significant overlap,
and this is somewhat subject to CA policy, we simply describe
validation requests.
</t>
<section title="Simple HTTPS" anchor="sec.simple-https">
<t>
The "Simple HTTPS" validation method consists simply of verifying
that the operator of the server has authority to insert a given
data value at a specific location in the /.well-known directory
of the target Web server, specifically under "/.well-known/acip-verify".
[TODO: Registration needed for this?]. The challenge information is a JSON
[TODO: What if the server for some reason has an existing cert?
SNI is a clumsy discrimination mechanism here...]
dictionary with two values:
</t>
<t>
<list style="hanging">
<t></t><t hangText="path (mandatory):"></t>
<t>The location in /.well-known/acip-verify to write the challenge value.</t>
<t></t><t hangText="value (mandatory):"></t>
<t>The value to write.</t>
<t></t><t hangText="expected_certificate (mandatory):"></t>
<t>The certificate which MUST be used by the CH server to authenticate the connection.</t>
</list>
</t>
<t>
For instance:
</t>
<figure>
<artwork><![CDATA[
{
"path" : "abcdef",
"value" : "12345",
"expected_key" : "<base64-encoded key>"
}
]]></artwork>
</figure>
<t>
In this example, the CH MUST write the string "12345" to the file
/well-known/acip-verify/12345.
</t>
<t>
The CH server MUST use the a certificate which matches the
"expected_key" provided by the client. If the server has
no such certificate, it MUST signal an error to the operator.
In general, the expected_key will be chosen to match one of two
values:
</t>
<t>
<list style="symbols">
<t>The key in the CSR.</t>
<t>Some previous key that the operator knows the CH previously used.</t>
</list>
</t>
<t>
[TODO: Do we want to force both of these? if so we will need to discriminate
via SNI. We could also require that the challenge be signed for POP...]
</t>
</section>
<section title="Digital Signature" anchor="sec.digital-signature">
<t>
The digital signature validation request involves the CH
signing a message requesting a service from a CA. This
message MAY involve a challenge from the CA, but need not
(for instance, a signed request for revocation need not
involve a challenge, as the requester clearly had
control of the key at some point and therefore a request
for revocation seems in order.
</t>
<t>
[TODO: RLB, please provide the JWT text here.]
</t>
</section>
<section title="Secret Token" anchor="sec.secret-token">
<t>
In some cases, the CA and the CH may wish to establish a
shared secret which can be used non-cryptographically to
authenticate the CH to the CA. The most obvious example is
that the CA may want to issue the CH a "revocation token"
which it can use to authenticate revocation requests.
This value could be comparatively low entropy if it is
used solely to revoke the certificate.
</t>
</section>
</section>
<section title="Errors" anchor="sec.errors">
<t>
Any request from the CH to the CA can potentially generate an error
(message type = "error"). The error message contents are:
</t>
<t>
<list style="hanging">
<t></t><t hangText="error_code (mandatory):"></t>
<t>An IANA-defined error code.</t>
<t></t><t hangText="error_reason (optional):"></t>
<t>A free-form text string providing further information. The
error_reason field SHOULD be populated.</t>
</list>
</t>
<t>
For example:
</t>
<figure>
<artwork><![CDATA[
{
"error_code" : "invalid_key",
"error_reason" : "RSA key less than 2048 bits provided."
}
]]>
</artwork>
</figure>
<t>
The following error codes are defined.
</t>
<t>
<list style="hanging">
<t></t><t hangText="invalid_key:"></t>
<t>The provided key was invalid for some reason.</t>
<t></t><t hangText="invalid_csr:"></t>
<t>The provided CSR was invalid for some reason other than the key,
e.g., because the signature is broken.</t>
<t></t><t hangText="invalid_validation_method:"></t>
<t>The authentication method specified is unknown or unacceptable.</t>
<t></t><t hangText="invalid_identity:"></t>
<t>The requested identity is invalid for some reason, e.g., because
it is improperly formatted.</t>
<t></t><t hangText="other_error:"></t>
<t>There is some other error not covered by the above. The
"error_reason" field MUST be populated for this error, as
it is the only way for the CH to get information about what
happened.</t>
</list>
</t>
</section>
<section title="Status Publication" anchor="sec.status">
<t>
CAs are encouraged to provide a status URL so that CHs can determine
the status of a given request. A GET to the status URL returns a
status document consisting of:
</t>
<t>
<list style="hanging">
<t></t><t hangText="request_status (mandatory):"></t>
<t>One of the following values:
<list style="symbols">
<t>"pending": the request is pending.</t>
<t>"complete": the request has successfully completed.</t>
<t>"failed": the request has failed</t>
</list>
</t>
<t></t><t hangText="error (optional):"></t>
<t>If the request failed, the CA SHOULD supply the error information
that would have been published in an error.</t>
<t></t><t hangText="log (optional):"></t>
<t>A free-text field consisting of logging information that might be
helpful to the CH.</t>
</list>
</t>
</section>
<section title="HTTPS Binding" anchor="sec.http-binding">
<t>
While the protocol defined in this document could in principle be
carried over any transport, this document defines a single transport
over HTTPS <xref target="RFC2818"/>.
</t>
<t>
An CA has a single URL which it uses as its main
ACIP endpoint. As discussed in <xref target="sec.overview"/>,
the CH knows this URL via some out-of-band mechanism. The
CA responds to both GET and POST requests at this URL
as described below. All messages have the type "application/json" [REF].
</t>
<t>
The response to any GET request is the policy document <xref target="sec.ca-policies"/>. The GET request SHOULD not contain any HTTP parameters or body and
in any case these MUST be ignored.
</t>
<t>
The CH issues instructions to the CA using HTTP POST. The
body of the POST message contains an ACIP message as
specified in <xref target="sec.messages"/>. Again, there
SHOULD be no query arguments. If the message is not well-formed,
than an HTTP 400 "bad request" error SHOULD be generated. If the
message is well-formed, then the CA SHOULD attempt to process
it and return any errors in the response body with an HTTP 200
success code, rather than as an HTTP error.
</t>
<t>
In addition to the above, the CA is expected to maintain a number
of informational URLs for a given certificate or certificate request
(e.g., "status_url" and "certificate_url") however these are
orthogonal to the protocol used for interacting with the CA
(since the client just does GETs to them) and therefore could
in principle be used even if some other protocol were used
to interact with the CA.
</t>
</section>
</section>
<section title="Security Considerations" anchor="sec.sec-cons">
<t>
<list style="symbols">
<t>CAs should bind the challenge to the key to prevent referrals...</t>
</list>
</t>
</section>
<section title="IANA Considerations" anchor="sec.iana-cons">
<t>
IANA [SHALL create/has created] an ACIP Validation Protocol specification
registry. The values are ASCII strings [REF: 8859-1?] and the registry
is initially populated with values as below. The registration policy
is Specification Required <xref target="RFC5226"/>.
</t>
<texttable>
<ttcol>Code Point</ttcol>
<ttcol>Description</ttcol>
<c>simple-https</c>
<c><xref target="sec.simple-https"/></c>
<c>https-sni</c>
<c><xref target="sec.https-with-sni"/></c>
<c>digital-signature</c>
<c><xref target="sec.digital-signature"/></c>
<c>secret-token</c>
<c><xref target="sec.secret-token"/></c>
</texttable>
<t>
IANA [SHALL create/has created] an ACIP Error registry.
The values are ASCII strings [REF: 8859-1?] and the registry
is initially populated with values from <xref target="sec.errors"/>.
The registration policy
is Standards Action <xref target="RFC5226"/>. The
</t>
</section>
<section title="Acknowledgements" anchor="sec.acknowledgments">
</section>
</middle>
<back>
<references title="Normative References">
&RFC2119;
&RFC2314;
&RFC2818;
&RFC5226;
&RFC5246;
</references>
<!--
<references title="Informative References">
</references>-->
</back>
</rfc>

1
acme/MANIFEST.in Normal file
View file

@ -0,0 +1 @@
recursive-include acme/testdata *

12
acme/acme/__init__.py Normal file
View file

@ -0,0 +1,12 @@
"""ACME protocol implementation.
This module is an implementation of the `ACME protocol`_. Latest
supported version: `v02`_.
.. _`ACME protocol`: https://github.com/letsencrypt/acme-spec
.. _`v02`:
https://github.com/letsencrypt/acme-spec/commit/d328fea2d507deb9822793c512830d827a4150c4
"""

454
acme/acme/challenges.py Normal file
View file

@ -0,0 +1,454 @@
"""ACME Identifier Validation Challenges."""
import binascii
import functools
import hashlib
import logging
import os
import socket
from cryptography.hazmat.backends import default_backend
from cryptography import x509
import OpenSSL
import requests
from acme import errors
from acme import crypto_util
from acme import fields
from acme import jose
from acme import other
logger = logging.getLogger(__name__)
# pylint: disable=too-few-public-methods
class Challenge(jose.TypedJSONObjectWithFields):
# _fields_to_partial_json | pylint: disable=abstract-method
"""ACME challenge."""
TYPES = {}
class ContinuityChallenge(Challenge): # pylint: disable=abstract-method
"""Client validation challenges."""
class DVChallenge(Challenge): # pylint: disable=abstract-method
"""Domain validation challenges."""
class ChallengeResponse(jose.TypedJSONObjectWithFields):
# _fields_to_partial_json | pylint: disable=abstract-method
"""ACME challenge response."""
TYPES = {}
resource_type = 'challenge'
resource = fields.Resource(resource_type)
@Challenge.register
class SimpleHTTP(DVChallenge):
"""ACME "simpleHttp" challenge.
:ivar unicode token:
"""
typ = "simpleHttp"
token = jose.Field("token")
@ChallengeResponse.register
class SimpleHTTPResponse(ChallengeResponse):
"""ACME "simpleHttp" challenge response.
:ivar unicode path:
:ivar unicode tls:
"""
typ = "simpleHttp"
path = jose.Field("path")
tls = jose.Field("tls", default=True, omitempty=True)
URI_ROOT_PATH = ".well-known/acme-challenge"
"""URI root path for the server provisioned resource."""
_URI_TEMPLATE = "{scheme}://{domain}/" + URI_ROOT_PATH + "/{path}"
MAX_PATH_LEN = 25
"""Maximum allowed `path` length."""
CONTENT_TYPE = "text/plain"
@property
def good_path(self):
"""Is `path` good?
.. todo:: acme-spec: "The value MUST be comprised entirely of
characters from the URL-safe alphabet for Base64 encoding
[RFC4648]", base64.b64decode ignores those characters
"""
# TODO: check that path combined with uri does not go above
# URI_ROOT_PATH!
return len(self.path) <= 25
@property
def scheme(self):
"""URL scheme for the provisioned resource."""
return "https" if self.tls else "http"
@property
def port(self):
"""Port that the ACME client should be listening for validation."""
return 443 if self.tls else 80
def uri(self, domain):
"""Create an URI to the provisioned resource.
Forms an URI to the HTTPS server provisioned resource
(containing :attr:`~SimpleHTTP.token`).
:param unicode domain: Domain name being verified.
"""
return self._URI_TEMPLATE.format(
scheme=self.scheme, domain=domain, path=self.path)
def simple_verify(self, chall, domain, port=None):
"""Simple verify.
According to the ACME specification, "the ACME server MUST
ignore the certificate provided by the HTTPS server", so
``requests.get`` is called with ``verify=False``.
:param .SimpleHTTP chall: Corresponding challenge.
:param unicode domain: Domain name being verified.
:param int port: Port used in the validation.
:returns: ``True`` iff validation is successful, ``False``
otherwise.
:rtype: bool
"""
# TODO: ACME specification defines URI template that doesn't
# allow to use a custom port... Make sure port is not in the
# request URI, if it's standard.
if port is not None and port != self.port:
logger.warn(
"Using non-standard port for SimpleHTTP verification: %s", port)
domain += ":{0}".format(port)
uri = self.uri(domain)
logger.debug("Verifying %s at %s...", chall.typ, uri)
try:
http_response = requests.get(uri, verify=False)
except requests.exceptions.RequestException as error:
logger.error("Unable to reach %s: %s", uri, error)
return False
logger.debug(
"Received %s. Headers: %s", http_response, http_response.headers)
good_token = http_response.text == chall.token
if not good_token:
logger.error(
"Unable to verify %s! Expected: %r, returned: %r.",
uri, chall.token, http_response.text)
# TODO: spec contradicts itself, c.f.
# https://github.com/letsencrypt/acme-spec/pull/156/files#r33136438
good_ct = self.CONTENT_TYPE == http_response.headers.get(
"Content-Type", self.CONTENT_TYPE)
return self.good_path and good_ct and good_token
@Challenge.register
class DVSNI(DVChallenge):
"""ACME "dvsni" challenge.
:ivar bytes r: Random data, **not** base64-encoded.
:ivar bytes nonce: Random data, **not** hex-encoded.
"""
typ = "dvsni"
DOMAIN_SUFFIX = b".acme.invalid"
"""Domain name suffix."""
R_SIZE = 32
"""Required size of the :attr:`r` in bytes."""
NONCE_SIZE = 16
"""Required size of the :attr:`nonce` in bytes."""
PORT = 443
"""Port to perform DVSNI challenge."""
r = jose.Field("r", encoder=jose.encode_b64jose, # pylint: disable=invalid-name
decoder=functools.partial(jose.decode_b64jose, size=R_SIZE))
nonce = jose.Field("nonce", encoder=jose.encode_hex16,
decoder=functools.partial(functools.partial(
jose.decode_hex16, size=NONCE_SIZE)))
@property
def nonce_domain(self):
"""Domain name used in SNI.
:rtype: bytes
"""
return binascii.hexlify(self.nonce) + self.DOMAIN_SUFFIX
def probe_cert(self, domain, **kwargs):
"""Probe DVSNI challenge certificate."""
host = socket.gethostbyname(domain)
logging.debug('%s resolved to %s', domain, host)
kwargs.setdefault("host", host)
kwargs.setdefault("port", self.PORT)
kwargs["name"] = self.nonce_domain
# TODO: try different methods?
# pylint: disable=protected-access
return crypto_util._probe_sni(**kwargs)
@ChallengeResponse.register
class DVSNIResponse(ChallengeResponse):
"""ACME "dvsni" challenge response.
:param bytes s: Random data, **not** base64-encoded.
"""
typ = "dvsni"
DOMAIN_SUFFIX = DVSNI.DOMAIN_SUFFIX
"""Domain name suffix."""
S_SIZE = 32
"""Required size of the :attr:`s` in bytes."""
s = jose.Field("s", encoder=jose.encode_b64jose, # pylint: disable=invalid-name
decoder=functools.partial(jose.decode_b64jose, size=S_SIZE))
def __init__(self, s=None, *args, **kwargs):
s = os.urandom(self.S_SIZE) if s is None else s
super(DVSNIResponse, self).__init__(s=s, *args, **kwargs)
def z(self, chall): # pylint: disable=invalid-name
"""Compute the parameter ``z``.
:param challenge: Corresponding challenge.
:type challenge: :class:`DVSNI`
:rtype: bytes
"""
z = hashlib.new("sha256") # pylint: disable=invalid-name
z.update(chall.r)
z.update(self.s)
return z.hexdigest().encode()
def z_domain(self, chall):
"""Domain name for certificate subjectAltName.
:rtype bytes:
"""
return self.z(chall) + self.DOMAIN_SUFFIX
def gen_cert(self, chall, domain, key):
"""Generate DVSNI certificate.
:param .DVSNI chall: Corresponding challenge.
:param unicode domain:
:param OpenSSL.crypto.PKey
"""
return crypto_util.gen_ss_cert(key, [
domain, chall.nonce_domain.decode(), self.z_domain(chall).decode()])
def simple_verify(self, chall, domain, public_key, **kwargs):
"""Simple verify.
Probes DVSNI certificate and checks it using `verify_cert`;
hence all arguments documented in `verify_cert`.
"""
try:
cert = chall.probe_cert(domain=domain, **kwargs)
except errors.Error as error:
logger.debug(error, exc_info=True)
return False
return self.verify_cert(chall, domain, public_key, cert)
def verify_cert(self, chall, domain, public_key, cert):
"""Verify DVSNI certificate.
:param .challenges.DVSNI chall: Corresponding challenge.
:param str domain: Domain name being validated.
:param public_key: Public key for the key pair
being authorized. If ``None`` key verification is not
performed!
:type public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
or
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
wrapped in `.ComparableKey
:param OpenSSL.crypto.X509 cert:
:returns: ``True`` iff client's control of the domain has been
verified, ``False`` otherwise.
:rtype: bool
"""
# TODO: check "It is a valid self-signed certificate" and
# return False if not
# pylint: disable=protected-access
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
cert = x509.load_der_x509_certificate(
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert),
default_backend())
if public_key is None:
logging.warn('No key verification is performed')
elif public_key != jose.ComparableKey(cert.public_key()):
return False
return domain in sans and self.z_domain(chall).decode() in sans
@Challenge.register
class RecoveryContact(ContinuityChallenge):
"""ACME "recoveryContact" challenge.
:ivar unicode activation_url:
:ivar unicode success_url:
:ivar unicode contact:
"""
typ = "recoveryContact"
activation_url = jose.Field("activationURL", omitempty=True)
success_url = jose.Field("successURL", omitempty=True)
contact = jose.Field("contact", omitempty=True)
@ChallengeResponse.register
class RecoveryContactResponse(ChallengeResponse):
"""ACME "recoveryContact" challenge response.
:ivar unicode token:
"""
typ = "recoveryContact"
token = jose.Field("token", omitempty=True)
@Challenge.register
class RecoveryToken(ContinuityChallenge):
"""ACME "recoveryToken" challenge."""
typ = "recoveryToken"
@ChallengeResponse.register
class RecoveryTokenResponse(ChallengeResponse):
"""ACME "recoveryToken" challenge response.
:ivar unicode token:
"""
typ = "recoveryToken"
token = jose.Field("token", omitempty=True)
@Challenge.register
class ProofOfPossession(ContinuityChallenge):
"""ACME "proofOfPossession" challenge.
:ivar .JWAAlgorithm alg:
:ivar bytes nonce: Random data, **not** base64-encoded.
:ivar hints: Various clues for the client (:class:`Hints`).
"""
typ = "proofOfPossession"
NONCE_SIZE = 16
class Hints(jose.JSONObjectWithFields):
"""Hints for "proofOfPossession" challenge.
:ivar jwk: JSON Web Key (:class:`acme.jose.JWK`)
:ivar tuple cert_fingerprints: `tuple` of `unicode`
:ivar tuple certs: Sequence of :class:`acme.jose.ComparableX509`
certificates.
:ivar tuple subject_key_identifiers: `tuple` of `unicode`
:ivar tuple issuers: `tuple` of `unicode`
:ivar tuple authorized_for: `tuple` of `unicode`
"""
jwk = jose.Field("jwk", decoder=jose.JWK.from_json)
cert_fingerprints = jose.Field(
"certFingerprints", omitempty=True, default=())
certs = jose.Field("certs", omitempty=True, default=())
subject_key_identifiers = jose.Field(
"subjectKeyIdentifiers", omitempty=True, default=())
serial_numbers = jose.Field("serialNumbers", omitempty=True, default=())
issuers = jose.Field("issuers", omitempty=True, default=())
authorized_for = jose.Field("authorizedFor", omitempty=True, default=())
@certs.encoder
def certs(value): # pylint: disable=missing-docstring,no-self-argument
return tuple(jose.encode_cert(cert) for cert in value)
@certs.decoder
def certs(value): # pylint: disable=missing-docstring,no-self-argument
return tuple(jose.decode_cert(cert) for cert in value)
alg = jose.Field("alg", decoder=jose.JWASignature.from_json)
nonce = jose.Field(
"nonce", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=NONCE_SIZE))
hints = jose.Field("hints", decoder=Hints.from_json)
@ChallengeResponse.register
class ProofOfPossessionResponse(ChallengeResponse):
"""ACME "proofOfPossession" challenge response.
:ivar bytes nonce: Random data, **not** base64-encoded.
:ivar acme.other.Signature signature: Sugnature of this message.
"""
typ = "proofOfPossession"
NONCE_SIZE = ProofOfPossession.NONCE_SIZE
nonce = jose.Field(
"nonce", encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=NONCE_SIZE))
signature = jose.Field("signature", decoder=other.Signature.from_json)
def verify(self):
"""Verify the challenge."""
# self.signature is not Field | pylint: disable=no-member
return self.signature.verify(self.nonce)
@Challenge.register
class DNS(DVChallenge):
"""ACME "dns" challenge.
:ivar unicode token:
"""
typ = "dns"
token = jose.Field("token")
@ChallengeResponse.register
class DNSResponse(ChallengeResponse):
"""ACME "dns" challenge response."""
typ = "dns"

View file

@ -0,0 +1,611 @@
"""Tests for acme.challenges."""
import unittest
import mock
import OpenSSL
import requests
from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error
from acme import errors
from acme import jose
from acme import other
from acme import test_util
CERT = test_util.load_cert('cert.pem')
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class SimpleHTTPTest(unittest.TestCase):
def setUp(self):
from acme.challenges import SimpleHTTP
self.msg = SimpleHTTP(
token='evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')
self.jmsg = {
'type': 'simpleHttp',
'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA',
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import SimpleHTTP
self.assertEqual(self.msg, SimpleHTTP.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import SimpleHTTP
hash(SimpleHTTP.from_json(self.jmsg))
class SimpleHTTPResponseTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import SimpleHTTPResponse
self.msg_http = SimpleHTTPResponse(
path='6tbIMBC5Anhl5bOlWT5ZFA', tls=False)
self.msg_https = SimpleHTTPResponse(path='6tbIMBC5Anhl5bOlWT5ZFA')
self.jmsg_http = {
'resource': 'challenge',
'type': 'simpleHttp',
'path': '6tbIMBC5Anhl5bOlWT5ZFA',
'tls': False,
}
self.jmsg_https = {
'resource': 'challenge',
'type': 'simpleHttp',
'path': '6tbIMBC5Anhl5bOlWT5ZFA',
'tls': True,
}
from acme.challenges import SimpleHTTP
self.chall = SimpleHTTP(token="foo")
self.resp_http = SimpleHTTPResponse(path="bar", tls=False)
self.resp_https = SimpleHTTPResponse(path="bar", tls=True)
self.good_headers = {'Content-Type': SimpleHTTPResponse.CONTENT_TYPE}
def test_good_path(self):
self.assertTrue(self.msg_http.good_path)
self.assertTrue(self.msg_https.good_path)
self.assertFalse(
self.msg_http.update(path=(self.msg_http.path * 10)).good_path)
def test_scheme(self):
self.assertEqual('http', self.msg_http.scheme)
self.assertEqual('https', self.msg_https.scheme)
def test_port(self):
self.assertEqual(80, self.msg_http.port)
self.assertEqual(443, self.msg_https.port)
def test_uri(self):
self.assertEqual(
'http://example.com/.well-known/acme-challenge/'
'6tbIMBC5Anhl5bOlWT5ZFA', self.msg_http.uri('example.com'))
self.assertEqual(
'https://example.com/.well-known/acme-challenge/'
'6tbIMBC5Anhl5bOlWT5ZFA', self.msg_https.uri('example.com'))
def test_to_partial_json(self):
self.assertEqual(self.jmsg_http, self.msg_http.to_partial_json())
self.assertEqual(self.jmsg_https, self.msg_https.to_partial_json())
def test_from_json(self):
from acme.challenges import SimpleHTTPResponse
self.assertEqual(
self.msg_http, SimpleHTTPResponse.from_json(self.jmsg_http))
self.assertEqual(
self.msg_https, SimpleHTTPResponse.from_json(self.jmsg_https))
def test_from_json_hashable(self):
from acme.challenges import SimpleHTTPResponse
hash(SimpleHTTPResponse.from_json(self.jmsg_http))
hash(SimpleHTTPResponse.from_json(self.jmsg_https))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_good_token(self, mock_get):
for resp in self.resp_http, self.resp_https:
mock_get.reset_mock()
mock_get.return_value = mock.MagicMock(
text=self.chall.token, headers=self.good_headers)
self.assertTrue(resp.simple_verify(self.chall, "local"))
mock_get.assert_called_once_with(resp.uri("local"), verify=False)
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_bad_token(self, mock_get):
mock_get.return_value = mock.MagicMock(
text=self.chall.token + "!", headers=self.good_headers)
self.assertFalse(self.resp_http.simple_verify(self.chall, "local"))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_bad_content_type(self, mock_get):
mock_get().text = self.chall.token
self.assertFalse(self.resp_http.simple_verify(self.chall, "local"))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_connection_error(self, mock_get):
mock_get.side_effect = requests.exceptions.RequestException
self.assertFalse(self.resp_http.simple_verify(self.chall, "local"))
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_port(self, mock_get):
self.resp_http.simple_verify(self.chall, "local", 4430)
self.assertEqual("local:4430", urllib_parse.urlparse(
mock_get.mock_calls[0][1][0]).netloc)
class DVSNITest(unittest.TestCase):
def setUp(self):
from acme.challenges import DVSNI
self.msg = DVSNI(
r=b"O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6"
b"\xbf'\xb3\xed\x9a9nX\x0f'\\m\xe7\x12",
nonce=b'\xa8-_\xf8\xeft\r\x12\x88\x1fm<"w\xab.')
self.jmsg = {
'type': 'dvsni',
'r': 'Tyq0La3slT7tqQ0wlOiXnCY2vyez7Zo5blgPJ1xt5xI',
'nonce': 'a82d5ff8ef740d12881f6d3c2277ab2e',
}
def test_nonce_domain(self):
self.assertEqual(b'a82d5ff8ef740d12881f6d3c2277ab2e.acme.invalid',
self.msg.nonce_domain)
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import DVSNI
self.assertEqual(self.msg, DVSNI.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import DVSNI
hash(DVSNI.from_json(self.jmsg))
def test_from_json_invalid_r_length(self):
from acme.challenges import DVSNI
self.jmsg['r'] = 'abcd'
self.assertRaises(
jose.DeserializationError, DVSNI.from_json, self.jmsg)
def test_from_json_invalid_nonce_length(self):
from acme.challenges import DVSNI
self.jmsg['nonce'] = 'abcd'
self.assertRaises(
jose.DeserializationError, DVSNI.from_json, self.jmsg)
@mock.patch('acme.challenges.socket.gethostbyname')
@mock.patch('acme.challenges.crypto_util._probe_sni')
def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
mock_gethostbyname.return_value = '127.0.0.1'
self.msg.probe_cert('foo.com')
mock_gethostbyname.assert_called_once_with('foo.com')
mock_probe_sni.assert_called_once_with(
host='127.0.0.1', port=self.msg.PORT,
name=b'a82d5ff8ef740d12881f6d3c2277ab2e.acme.invalid')
self.msg.probe_cert('foo.com', host='8.8.8.8')
mock_probe_sni.assert_called_with(
host='8.8.8.8', port=mock.ANY, name=mock.ANY)
self.msg.probe_cert('foo.com', port=1234)
mock_probe_sni.assert_called_with(
host=mock.ANY, port=1234, name=mock.ANY)
self.msg.probe_cert('foo.com', bar='baz')
mock_probe_sni.assert_called_with(
host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz')
self.msg.probe_cert('foo.com', name=b'xxx')
mock_probe_sni.assert_called_with(
host=mock.ANY, port=mock.ANY,
name=b'a82d5ff8ef740d12881f6d3c2277ab2e.acme.invalid')
class DVSNIResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import DVSNIResponse
# pylint: disable=invalid-name
s = '9dbjsl3gTAtOnEtKFEmhS6Mj-ajNjDcOmRkp3Lfzm3c'
self.msg = DVSNIResponse(s=jose.decode_b64jose(s))
self.jmsg = {
'resource': 'challenge',
'type': 'dvsni',
's': s,
}
from acme.challenges import DVSNI
self.chall = DVSNI(
r=jose.decode_b64jose('Tyq0La3slT7tqQ0wlOiXnCY2vyez7Zo5blgPJ1xt5xI'),
nonce=jose.decode_b64jose('a82d5ff8ef740d12881f6d3c2277ab2e'))
self.z = (b'38e612b0397cc2624a07d351d7ef50e4'
b'6134c0213d9ed52f7d7c611acaeed41b')
self.domain = 'foo.com'
self.key = test_util.load_pyopenssl_private_key('rsa512_key.pem')
self.public_key = test_util.load_rsa_private_key(
'rsa512_key.pem').public_key()
def test_z_and_domain(self):
# pylint: disable=invalid-name
self.assertEqual(self.z, self.msg.z(self.chall))
self.assertEqual(
self.z + b'.acme.invalid', self.msg.z_domain(self.chall))
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import DVSNIResponse
self.assertEqual(self.msg, DVSNIResponse.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import DVSNIResponse
hash(DVSNIResponse.from_json(self.jmsg))
@mock.patch('acme.challenges.DVSNIResponse.verify_cert')
def test_simple_verify(self, mock_verify_cert):
chall = mock.Mock()
chall.probe_cert.return_value = mock.sentinel.cert
mock_verify_cert.return_value = 'x'
self.assertEqual('x', self.msg.simple_verify(
chall, mock.sentinel.domain, mock.sentinel.key))
chall.probe_cert.assert_called_once_with(domain=mock.sentinel.domain)
self.msg.verify_cert.assert_called_once_with(
chall, mock.sentinel.domain, mock.sentinel.key,
mock.sentinel.cert)
def test_simple_verify_false_on_probe_error(self):
chall = mock.Mock()
chall.probe_cert.side_effect = errors.Error
self.assertFalse(self.msg.simple_verify(
chall=chall, domain=None, public_key=None))
def test_gen_verify_cert_postive_no_key(self):
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
self.assertTrue(self.msg.verify_cert(
self.chall, self.domain, public_key=None, cert=cert))
def test_gen_verify_cert_postive_with_key(self):
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
self.assertTrue(self.msg.verify_cert(
self.chall, self.domain, public_key=self.public_key, cert=cert))
def test_gen_verify_cert_negative_with_wrong_key(self):
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
key = test_util.load_rsa_private_key('rsa256_key.pem').public_key()
self.assertFalse(self.msg.verify_cert(
self.chall, self.domain, public_key=key, cert=cert))
def test_gen_verify_cert_negative(self):
cert = self.msg.gen_cert(self.chall, self.domain + 'x', self.key)
self.assertFalse(self.msg.verify_cert(
self.chall, self.domain, public_key=None, cert=cert))
class RecoveryContactTest(unittest.TestCase):
def setUp(self):
from acme.challenges import RecoveryContact
self.msg = RecoveryContact(
activation_url='https://example.ca/sendrecovery/a5bd99383fb0',
success_url='https://example.ca/confirmrecovery/bb1b9928932',
contact='c********n@example.com')
self.jmsg = {
'type': 'recoveryContact',
'activationURL' : 'https://example.ca/sendrecovery/a5bd99383fb0',
'successURL' : 'https://example.ca/confirmrecovery/bb1b9928932',
'contact' : 'c********n@example.com',
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import RecoveryContact
self.assertEqual(self.msg, RecoveryContact.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import RecoveryContact
hash(RecoveryContact.from_json(self.jmsg))
def test_json_without_optionals(self):
del self.jmsg['activationURL']
del self.jmsg['successURL']
del self.jmsg['contact']
from acme.challenges import RecoveryContact
msg = RecoveryContact.from_json(self.jmsg)
self.assertTrue(msg.activation_url is None)
self.assertTrue(msg.success_url is None)
self.assertTrue(msg.contact is None)
self.assertEqual(self.jmsg, msg.to_partial_json())
class RecoveryContactResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import RecoveryContactResponse
self.msg = RecoveryContactResponse(token='23029d88d9e123e')
self.jmsg = {
'resource': 'challenge',
'type': 'recoveryContact',
'token': '23029d88d9e123e',
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import RecoveryContactResponse
self.assertEqual(
self.msg, RecoveryContactResponse.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import RecoveryContactResponse
hash(RecoveryContactResponse.from_json(self.jmsg))
def test_json_without_optionals(self):
del self.jmsg['token']
from acme.challenges import RecoveryContactResponse
msg = RecoveryContactResponse.from_json(self.jmsg)
self.assertTrue(msg.token is None)
self.assertEqual(self.jmsg, msg.to_partial_json())
class RecoveryTokenTest(unittest.TestCase):
def setUp(self):
from acme.challenges import RecoveryToken
self.msg = RecoveryToken()
self.jmsg = {'type': 'recoveryToken'}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import RecoveryToken
self.assertEqual(self.msg, RecoveryToken.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import RecoveryToken
hash(RecoveryToken.from_json(self.jmsg))
class RecoveryTokenResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import RecoveryTokenResponse
self.msg = RecoveryTokenResponse(token='23029d88d9e123e')
self.jmsg = {
'resource': 'challenge',
'type': 'recoveryToken',
'token': '23029d88d9e123e'
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import RecoveryTokenResponse
self.assertEqual(
self.msg, RecoveryTokenResponse.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import RecoveryTokenResponse
hash(RecoveryTokenResponse.from_json(self.jmsg))
def test_json_without_optionals(self):
del self.jmsg['token']
from acme.challenges import RecoveryTokenResponse
msg = RecoveryTokenResponse.from_json(self.jmsg)
self.assertTrue(msg.token is None)
self.assertEqual(self.jmsg, msg.to_partial_json())
class ProofOfPossessionHintsTest(unittest.TestCase):
def setUp(self):
jwk = jose.JWKRSA(key=KEY.public_key())
issuers = (
'C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA',
'O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure',
)
cert_fingerprints = (
'93416768eb85e33adc4277f4c9acd63e7418fcfe',
'16d95b7b63f1972b980b14c20291f3c0d1855d95',
'48b46570d9fc6358108af43ad1649484def0debf',
)
subject_key_identifiers = ('d0083162dcc4c8a23ecb8aecbd86120e56fd24e5')
authorized_for = ('www.example.com', 'example.net')
serial_numbers = (34234239832, 23993939911, 17)
from acme.challenges import ProofOfPossession
self.msg = ProofOfPossession.Hints(
jwk=jwk, issuers=issuers, cert_fingerprints=cert_fingerprints,
certs=(CERT,), subject_key_identifiers=subject_key_identifiers,
authorized_for=authorized_for, serial_numbers=serial_numbers)
self.jmsg_to = {
'jwk': jwk,
'certFingerprints': cert_fingerprints,
'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT)),),
'subjectKeyIdentifiers': subject_key_identifiers,
'serialNumbers': serial_numbers,
'issuers': issuers,
'authorizedFor': authorized_for,
}
self.jmsg_from = self.jmsg_to.copy()
self.jmsg_from.update({'jwk': jwk.to_json()})
def test_to_partial_json(self):
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import ProofOfPossession
self.assertEqual(
self.msg, ProofOfPossession.Hints.from_json(self.jmsg_from))
def test_from_json_hashable(self):
from acme.challenges import ProofOfPossession
hash(ProofOfPossession.Hints.from_json(self.jmsg_from))
def test_json_without_optionals(self):
for optional in ['certFingerprints', 'certs', 'subjectKeyIdentifiers',
'serialNumbers', 'issuers', 'authorizedFor']:
del self.jmsg_from[optional]
del self.jmsg_to[optional]
from acme.challenges import ProofOfPossession
msg = ProofOfPossession.Hints.from_json(self.jmsg_from)
self.assertEqual(msg.cert_fingerprints, ())
self.assertEqual(msg.certs, ())
self.assertEqual(msg.subject_key_identifiers, ())
self.assertEqual(msg.serial_numbers, ())
self.assertEqual(msg.issuers, ())
self.assertEqual(msg.authorized_for, ())
self.assertEqual(self.jmsg_to, msg.to_partial_json())
class ProofOfPossessionTest(unittest.TestCase):
def setUp(self):
from acme.challenges import ProofOfPossession
hints = ProofOfPossession.Hints(
jwk=jose.JWKRSA(key=KEY.public_key()), cert_fingerprints=(),
certs=(), serial_numbers=(), subject_key_identifiers=(),
issuers=(), authorized_for=())
self.msg = ProofOfPossession(
alg=jose.RS256, hints=hints,
nonce=b'xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ')
self.jmsg_to = {
'type': 'proofOfPossession',
'alg': jose.RS256,
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
'hints': hints,
}
self.jmsg_from = {
'type': 'proofOfPossession',
'alg': jose.RS256.to_json(),
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
'hints': hints.to_json(),
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import ProofOfPossession
self.assertEqual(
self.msg, ProofOfPossession.from_json(self.jmsg_from))
def test_from_json_hashable(self):
from acme.challenges import ProofOfPossession
hash(ProofOfPossession.from_json(self.jmsg_from))
class ProofOfPossessionResponseTest(unittest.TestCase):
def setUp(self):
# acme-spec uses a confusing example in which both signature
# nonce and challenge nonce are the same, don't make the same
# mistake here...
signature = other.Signature(
alg=jose.RS256, jwk=jose.JWKRSA(key=KEY.public_key()),
sig=b'\xa7\xc1\xe7\xe82o\xbc\xcd\xd0\x1e\x010#Z|\xaf\x15\x83'
b'\x94\x8f#\x9b\nQo(\x80\x15,\x08\xfcz\x1d\xfd\xfd.\xaap'
b'\xfa\x06\xd1\xa2f\x8d8X2>%d\xbd%\xe1T\xdd\xaa0\x18\xde'
b'\x99\x08\xf0\x0e{',
nonce=b'\x99\xc7Q\xb3f2\xbc\xdci\xfe\xd6\x98k\xc67\xdf',
)
from acme.challenges import ProofOfPossessionResponse
self.msg = ProofOfPossessionResponse(
nonce=b'xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ',
signature=signature)
self.jmsg_to = {
'resource': 'challenge',
'type': 'proofOfPossession',
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
'signature': signature,
}
self.jmsg_from = {
'resource': 'challenge',
'type': 'proofOfPossession',
'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
'signature': signature.to_json(),
}
def test_verify(self):
self.assertTrue(self.msg.verify())
def test_to_partial_json(self):
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import ProofOfPossessionResponse
self.assertEqual(
self.msg, ProofOfPossessionResponse.from_json(self.jmsg_from))
def test_from_json_hashable(self):
from acme.challenges import ProofOfPossessionResponse
hash(ProofOfPossessionResponse.from_json(self.jmsg_from))
class DNSTest(unittest.TestCase):
def setUp(self):
from acme.challenges import DNS
self.msg = DNS(token='17817c66b60ce2e4012dfad92657527a')
self.jmsg = {'type': 'dns', 'token': '17817c66b60ce2e4012dfad92657527a'}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import DNS
self.assertEqual(self.msg, DNS.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import DNS
hash(DNS.from_json(self.jmsg))
class DNSResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import DNSResponse
self.msg = DNSResponse()
self.jmsg = {
'resource': 'challenge',
'type': 'dns',
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import DNSResponse
self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import DNSResponse
hash(DNSResponse.from_json(self.jmsg))
if __name__ == '__main__':
unittest.main() # pragma: no cover

582
acme/acme/client.py Normal file
View file

@ -0,0 +1,582 @@
"""ACME client API."""
import datetime
import heapq
import logging
import time
from six.moves import http_client # pylint: disable=import-error
import OpenSSL
import requests
import six
import werkzeug
from acme import errors
from acme import jose
from acme import jws
from acme import messages
logger = logging.getLogger(__name__)
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
if six.PY2:
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()
class Client(object): # pylint: disable=too-many-instance-attributes
"""ACME client.
.. todo::
Clean up raised error types hierarchy, document, and handle (wrap)
instances of `.DeserializationError` raised in `from_json()`.
:ivar str new_reg_uri: Location of new-reg
:ivar key: `.JWK` (private)
:ivar alg: `.JWASignature`
:ivar bool verify_ssl: Verify SSL certificates?
:ivar .ClientNetwork net: Client network. Useful for testing. If not
supplied, it will be initialized using `key`, `alg` and
`verify_ssl`.
"""
DER_CONTENT_TYPE = 'application/pkix-cert'
def __init__(self, new_reg_uri, key, alg=jose.RS256,
verify_ssl=True, net=None):
self.new_reg_uri = new_reg_uri
self.key = key
self.net = ClientNetwork(key, alg, verify_ssl) if net is None else net
@classmethod
def _regr_from_response(cls, response, uri=None, new_authzr_uri=None,
terms_of_service=None):
terms_of_service = (
response.links['terms-of-service']['url']
if 'terms-of-service' in response.links else terms_of_service)
if new_authzr_uri is None:
try:
new_authzr_uri = response.links['next']['url']
except KeyError:
raise errors.ClientError('"next" link missing')
return messages.RegistrationResource(
body=messages.Registration.from_json(response.json()),
uri=response.headers.get('Location', uri),
new_authzr_uri=new_authzr_uri,
terms_of_service=terms_of_service)
def register(self, new_reg=None):
"""Register.
:param .NewRegistration new_reg:
:returns: Registration Resource.
:rtype: `.RegistrationResource`
:raises .UnexpectedUpdate:
"""
new_reg = messages.NewRegistration() if new_reg is None else new_reg
assert isinstance(new_reg, messages.NewRegistration)
response = self.net.post(self.new_reg_uri, new_reg)
# TODO: handle errors
assert response.status_code == http_client.CREATED
# "Instance of 'Field' has no key/contact member" bug:
# pylint: disable=no-member
regr = self._regr_from_response(response)
if (regr.body.key != self.key.public_key() or
regr.body.contact != new_reg.contact):
raise errors.UnexpectedUpdate(regr)
return regr
def update_registration(self, regr):
"""Update registration.
:pram regr: Registration Resource.
:type regr: `.RegistrationResource`
:returns: Updated Registration Resource.
:rtype: `.RegistrationResource`
"""
response = self.net.post(
regr.uri, messages.UpdateRegistration(**dict(regr.body)))
# TODO: Boulder returns httplib.ACCEPTED
#assert response.status_code == httplib.OK
# TODO: Boulder does not set Location or Link on update
# (c.f. acme-spec #94)
updated_regr = self._regr_from_response(
response, uri=regr.uri, new_authzr_uri=regr.new_authzr_uri,
terms_of_service=regr.terms_of_service)
if updated_regr != regr:
raise errors.UnexpectedUpdate(regr)
return updated_regr
def agree_to_tos(self, regr):
"""Agree to the terms-of-service.
Agree to the terms-of-service in a Registration Resource.
:param regr: Registration Resource.
:type regr: `.RegistrationResource`
:returns: Updated Registration Resource.
:rtype: `.RegistrationResource`
"""
return self.update_registration(
regr.update(body=regr.body.update(agreement=regr.terms_of_service)))
def _authzr_from_response(self, response, identifier,
uri=None, new_cert_uri=None):
# pylint: disable=no-self-use
if new_cert_uri is None:
try:
new_cert_uri = response.links['next']['url']
except KeyError:
raise errors.ClientError('"next" link missing')
authzr = messages.AuthorizationResource(
body=messages.Authorization.from_json(response.json()),
uri=response.headers.get('Location', uri),
new_cert_uri=new_cert_uri)
if authzr.body.identifier != identifier:
raise errors.UnexpectedUpdate(authzr)
return authzr
def request_challenges(self, identifier, new_authzr_uri):
"""Request challenges.
:param identifier: Identifier to be challenged.
:type identifier: `.messages.Identifier`
:param str new_authzr_uri: new-authorization URI
:returns: Authorization Resource.
:rtype: `.AuthorizationResource`
"""
new_authz = messages.NewAuthorization(identifier=identifier)
response = self.net.post(new_authzr_uri, new_authz)
# TODO: handle errors
assert response.status_code == http_client.CREATED
return self._authzr_from_response(response, identifier)
def request_domain_challenges(self, domain, new_authz_uri):
"""Request challenges for domain names.
This is simply a convenience function that wraps around
`request_challenges`, but works with domain names instead of
generic identifiers.
:param str domain: Domain name to be challenged.
:param str new_authzr_uri: new-authorization URI
:returns: Authorization Resource.
:rtype: `.AuthorizationResource`
"""
return self.request_challenges(messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value=domain), new_authz_uri)
def answer_challenge(self, challb, response):
"""Answer challenge.
:param challb: Challenge Resource body.
:type challb: `.ChallengeBody`
:param response: Corresponding Challenge response
:type response: `.challenges.ChallengeResponse`
:returns: Challenge Resource with updated body.
:rtype: `.ChallengeResource`
:raises .UnexpectedUpdate:
"""
response = self.net.post(challb.uri, response)
try:
authzr_uri = response.links['up']['url']
except KeyError:
raise errors.ClientError('"up" Link header missing')
challr = messages.ChallengeResource(
authzr_uri=authzr_uri,
body=messages.ChallengeBody.from_json(response.json()))
# TODO: check that challr.uri == response.headers['Location']?
if challr.uri != challb.uri:
raise errors.UnexpectedUpdate(challr.uri)
return challr
@classmethod
def retry_after(cls, response, default):
"""Compute next `poll` time based on response ``Retry-After`` header.
:param response: Response from `poll`.
:type response: `requests.Response`
:param int default: Default value (in seconds), used when
``Retry-After`` header is not present or invalid.
:returns: Time point when next `poll` should be performed.
:rtype: `datetime.datetime`
"""
retry_after = response.headers.get('Retry-After', str(default))
try:
seconds = int(retry_after)
except ValueError:
# pylint: disable=no-member
decoded = werkzeug.parse_date(retry_after) # RFC1123
if decoded is None:
seconds = default
else:
return decoded
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri, authzr.new_cert_uri)
# TODO: check and raise UnexpectedUpdate
return updated_authzr, response
def request_issuance(self, csr, authzrs):
"""Request issuance.
:param csr: CSR
:type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:param authzrs: `list` of `.AuthorizationResource`
:returns: Issued certificate
:rtype: `.messages.CertificateResource`
"""
assert authzrs, "Authorizations list is empty"
logger.debug("Requesting issuance...")
# TODO: assert len(authzrs) == number of SANs
req = messages.CertificateRequest(
csr=csr, authorizations=tuple(authzr.uri for authzr in authzrs))
content_type = self.DER_CONTENT_TYPE # TODO: add 'cert_type 'argument
response = self.net.post(
authzrs[0].new_cert_uri, # TODO: acme-spec #90
req,
content_type=content_type,
headers={'Accept': content_type})
cert_chain_uri = response.links.get('up', {}).get('url')
try:
uri = response.headers['Location']
except KeyError:
raise errors.ClientError('"Location" Header missing')
return messages.CertificateResource(
uri=uri, authzrs=authzrs, cert_chain_uri=cert_chain_uri,
body=jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def poll_and_request_issuance(self, csr, authzrs, mintime=5):
"""Poll and request issuance.
This function polls all provided Authorization Resource URIs
until all challenges are valid, respecting ``Retry-After`` HTTP
headers, and then calls `request_issuance`.
.. todo:: add `max_attempts` or `timeout`
:param csr: CSR.
:type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:param authzrs: `list` of `.AuthorizationResource`
:param int mintime: Minimum time before next attempt, used if
``Retry-After`` is not present in the response.
:returns: ``(cert, updated_authzrs)`` `tuple` where ``cert`` is
the issued certificate (`.messages.CertificateResource.),
and ``updated_authzrs`` is a `tuple` consisting of updated
Authorization Resources (`.AuthorizationResource`) as
present in the responses from server, and in the same order
as the input ``authzrs``.
:rtype: `tuple`
"""
# priority queue with datetime (based on Retry-After) as key,
# and original Authorization Resource as value
waiting = [(datetime.datetime.now(), authzr) for authzr in authzrs]
# mapping between original Authorization Resource and the most
# recently updated one
updated = dict((authzr, authzr) for authzr in authzrs)
while waiting:
# find the smallest Retry-After, and sleep if necessary
when, authzr = heapq.heappop(waiting)
now = datetime.datetime.now()
if when > now:
seconds = (when - now).seconds
logger.debug('Sleeping for %d seconds', seconds)
time.sleep(seconds)
# Note that we poll with the latest updated Authorization
# URI, which might have a different URI than initial one
updated_authzr, response = self.poll(updated[authzr])
updated[authzr] = updated_authzr
# pylint: disable=no-member
if updated_authzr.body.status != messages.STATUS_VALID:
# push back to the priority queue, with updated retry_after
heapq.heappush(waiting, (self.retry_after(
response, default=mintime), authzr))
updated_authzrs = tuple(updated[authzr] for authzr in authzrs)
return self.request_issuance(csr, updated_authzrs), updated_authzrs
def _get_cert(self, uri):
"""Returns certificate from URI.
:param str uri: URI of certificate
:returns: tuple of the form
(response, :class:`acme.jose.ComparableX509`)
:rtype: tuple
"""
content_type = self.DER_CONTENT_TYPE # TODO: make it a param
response = self.net.get(uri, headers={'Accept': content_type},
content_type=content_type)
return response, jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content))
def check_cert(self, certr):
"""Check for new cert.
:param certr: Certificate Resource
:type certr: `.CertificateResource`
:returns: Updated Certificate Resource.
:rtype: `.CertificateResource`
"""
# TODO: acme-spec 5.1 table action should be renamed to
# "refresh cert", and this method integrated with self.refresh
response, cert = self._get_cert(certr.uri)
if 'Location' not in response.headers:
raise errors.ClientError('Location header missing')
if response.headers['Location'] != certr.uri:
raise errors.UnexpectedUpdate(response.text)
return certr.update(body=cert)
def refresh(self, certr):
"""Refresh certificate.
:param certr: Certificate Resource
:type certr: `.CertificateResource`
:returns: Updated Certificate Resource.
:rtype: `.CertificateResource`
"""
# TODO: If a client sends a refresh request and the server is
# not willing to refresh the certificate, the server MUST
# respond with status code 403 (Forbidden)
return self.check_cert(certr)
def fetch_chain(self, certr):
"""Fetch chain for certificate.
:param certr: Certificate Resource
:type certr: `.CertificateResource`
:returns: Certificate chain, or `None` if no "up" Link was provided.
:rtype: `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
if certr.cert_chain_uri is not None:
return self._get_cert(certr.cert_chain_uri)[1]
else:
return None
def revoke(self, cert):
"""Revoke certificate.
:param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
:raises .ClientError: If revocation is unsuccessful.
"""
response = self.net.post(messages.Revocation.url(self.new_reg_uri),
messages.Revocation(certificate=cert))
if response.status_code != http_client.OK:
raise errors.ClientError(
'Successful revocation must return HTTP OK status')
class ClientNetwork(object):
"""Client network."""
JSON_CONTENT_TYPE = 'application/json'
JSON_ERROR_CONTENT_TYPE = 'application/problem+json'
REPLAY_NONCE_HEADER = 'Replay-Nonce'
def __init__(self, key, alg=jose.RS256, verify_ssl=True):
self.key = key
self.alg = alg
self.verify_ssl = verify_ssl
self._nonces = set()
def _wrap_in_jws(self, obj, nonce):
"""Wrap `JSONDeSerializable` object in JWS.
.. todo:: Implement ``acmePath``.
:param .JSONDeSerializable obj:
:param bytes nonce:
:rtype: `.JWS`
"""
jobj = obj.json_dumps().encode()
logger.debug('Serialized JSON: %s', jobj)
return jws.JWS.sign(
payload=jobj, key=self.key, alg=self.alg, nonce=nonce).json_dumps()
@classmethod
def _check_response(cls, response, content_type=None):
"""Check response content and its type.
.. note::
Checking is not strict: wrong server response ``Content-Type``
HTTP header is ignored if response is an expected JSON object
(c.f. Boulder #56).
:param str content_type: Expected Content-Type response header.
If JSON is expected and not present in server response, this
function will raise an error. Otherwise, wrong Content-Type
is ignored, but logged.
:raises .messages.Error: If server response body
carries HTTP Problem (draft-ietf-appsawg-http-problem-00).
:raises .ClientError: In case of other networking errors.
"""
logger.debug('Received response %s (headers: %s): %r',
response, response.headers, response.content)
response_ct = response.headers.get('Content-Type')
try:
# TODO: response.json() is called twice, once here, and
# once in _get and _post clients
jobj = response.json()
except ValueError as error:
jobj = None
if not response.ok:
if jobj is not None:
if response_ct != cls.JSON_ERROR_CONTENT_TYPE:
logger.debug(
'Ignoring wrong Content-Type (%r) for JSON Error',
response_ct)
try:
raise messages.Error.from_json(jobj)
except jose.DeserializationError as error:
# Couldn't deserialize JSON object
raise errors.ClientError((response, error))
else:
# response is not JSON object
raise errors.ClientError(response)
else:
if jobj is not None and response_ct != cls.JSON_CONTENT_TYPE:
logger.debug(
'Ignoring wrong Content-Type (%r) for JSON decodable '
'response', response_ct)
if content_type == cls.JSON_CONTENT_TYPE and jobj is None:
raise errors.ClientError(
'Unexpected response Content-Type: {0}'.format(response_ct))
return response
def _send_request(self, method, url, *args, **kwargs):
"""Send HTTP request.
Makes sure that `verify_ssl` is respected. Logs request and
response (with headers). For allowed parameters please see
`requests.request`.
:param str method: method for the new `requests.Request` object
:param str url: URL for the new `requests.Request` object
:raises requests.exceptions.RequestException: in case of any problems
:returns: HTTP Response
:rtype: `requests.Response`
"""
logging.debug('Sending %s request to %s', method, url)
kwargs['verify'] = self.verify_ssl
response = requests.request(method, url, *args, **kwargs)
logging.debug('Received %s. Headers: %s. Content: %r',
response, response.headers, response.content)
return response
def head(self, *args, **kwargs):
"""Send HEAD request without checking the response.
Note, that `_check_response` is not called, as it is expected
that status code other than successfuly 2xx will be returned, or
messages2.Error will be raised by the server.
"""
return self._send_request('HEAD', *args, **kwargs)
def get(self, url, content_type=JSON_CONTENT_TYPE, **kwargs):
"""Send GET request and check response."""
return self._check_response(
self._send_request('GET', url, **kwargs), content_type=content_type)
def _add_nonce(self, response):
if self.REPLAY_NONCE_HEADER in response.headers:
nonce = response.headers[self.REPLAY_NONCE_HEADER]
try:
decoded_nonce = jws.Header._fields['nonce'].decode(nonce)
except jose.DeserializationError as error:
raise errors.BadNonce(nonce, error)
logger.debug('Storing nonce: %r', decoded_nonce)
self._nonces.add(decoded_nonce)
else:
raise errors.MissingNonce(response)
def _get_nonce(self, url):
if not self._nonces:
logging.debug('Requesting fresh nonce')
self._add_nonce(self.head(url))
return self._nonces.pop()
def post(self, url, obj, content_type=JSON_CONTENT_TYPE, **kwargs):
"""POST object wrapped in `.JWS` and check response."""
data = self._wrap_in_jws(obj, self._get_nonce(url))
response = self._send_request('POST', url, data=data, **kwargs)
self._add_nonce(response)
return self._check_response(response, content_type=content_type)

551
acme/acme/client_test.py Normal file
View file

@ -0,0 +1,551 @@
"""Tests for acme.client."""
import datetime
import json
import unittest
from six.moves import http_client # pylint: disable=import-error
import mock
import requests
from acme import challenges
from acme import errors
from acme import jose
from acme import jws as acme_jws
from acme import messages
from acme import messages_test
from acme import test_util
CERT_DER = test_util.load_vector('cert.der')
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
class ClientTest(unittest.TestCase):
"""Tests for acme.client.Client."""
# pylint: disable=too-many-instance-attributes,too-many-public-methods
def setUp(self):
self.response = mock.MagicMock(
ok=True, status_code=http_client.OK, headers={}, links={})
self.net = mock.MagicMock()
self.net.post.return_value = self.response
self.net.get.return_value = self.response
from acme.client import Client
self.client = Client(
new_reg_uri='https://www.letsencrypt-demo.org/acme/new-reg',
key=KEY, alg=jose.RS256, net=self.net)
self.identifier = messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value='example.com')
# Registration
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key(), recovery_token='t')
self.new_reg = messages.NewRegistration(**dict(reg))
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1',
new_authzr_uri='https://www.letsencrypt-demo.org/acme/new-reg',
terms_of_service='https://www.letsencrypt-demo.org/tos')
# Authorization
authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1'
challb = messages.ChallengeBody(
uri=(authzr_uri + '/1'), status=messages.STATUS_VALID,
chall=challenges.DNS(token='foo'))
self.challr = messages.ChallengeResource(
body=challb, authzr_uri=authzr_uri)
self.authz = messages.Authorization(
identifier=messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value='example.com'),
challenges=(challb,), combinations=None)
self.authzr = messages.AuthorizationResource(
body=self.authz, uri=authzr_uri,
new_cert_uri='https://www.letsencrypt-demo.org/acme/new-cert')
# Request issuance
self.certr = messages.CertificateResource(
body=messages_test.CERT, authzrs=(self.authzr,),
uri='https://www.letsencrypt-demo.org/acme/cert/1',
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
def test_register(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
self.response.status_code = http_client.CREATED
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
self.response.links.update({
'next': {'url': self.regr.new_authzr_uri},
'terms-of-service': {'url': self.regr.terms_of_service},
})
self.assertEqual(self.regr, self.client.register(self.new_reg))
# TODO: test POST call arguments
# TODO: split here and separate test
reg_wrong_key = self.regr.body.update(key=KEY2.public_key())
self.response.json.return_value = reg_wrong_key.to_json()
self.assertRaises(
errors.UnexpectedUpdate, self.client.register, self.new_reg)
def test_register_missing_next(self):
self.response.status_code = http_client.CREATED
self.assertRaises(
errors.ClientError, self.client.register, self.new_reg)
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
# TODO: test POST call arguments
# TODO: split here and separate test
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
self.assertRaises(
errors.UnexpectedUpdate, self.client.update_registration, self.regr)
def test_agree_to_tos(self):
self.client.update_registration = mock.Mock()
self.client.agree_to_tos(self.regr)
regr = self.client.update_registration.call_args[0][0]
self.assertEqual(self.regr.terms_of_service, regr.body.agreement)
def test_request_challenges(self):
self.response.status_code = http_client.CREATED
self.response.headers['Location'] = self.authzr.uri
self.response.json.return_value = self.authz.to_json()
self.response.links = {
'next': {'url': self.authzr.new_cert_uri},
}
self.client.request_challenges(self.identifier, self.authzr.uri)
# TODO: test POST call arguments
# TODO: split here and separate test
self.response.json.return_value = self.authz.update(
identifier=self.identifier.update(value='foo')).to_json()
self.assertRaises(
errors.UnexpectedUpdate, self.client.request_challenges,
self.identifier, self.authzr.uri)
def test_request_challenges_missing_next(self):
self.response.status_code = http_client.CREATED
self.assertRaises(
errors.ClientError, self.client.request_challenges,
self.identifier, self.regr)
def test_request_domain_challenges(self):
self.client.request_challenges = mock.MagicMock()
self.assertEqual(
self.client.request_challenges(self.identifier),
self.client.request_domain_challenges('example.com', self.regr))
def test_answer_challenge(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}
self.response.json.return_value = self.challr.body.to_json()
chall_response = challenges.DNSResponse()
self.client.answer_challenge(self.challr.body, chall_response)
# TODO: split here and separate test
self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge,
self.challr.body.update(uri='foo'), chall_response)
def test_answer_challenge_missing_next(self):
self.assertRaises(errors.ClientError, self.client.answer_challenge,
self.challr.body, challenges.DNSResponse())
def test_retry_after_date(self):
self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT'
self.assertEqual(
datetime.datetime(1999, 12, 31, 23, 59, 59),
self.client.retry_after(response=self.response, default=10))
@mock.patch('acme.client.datetime')
def test_retry_after_invalid(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = 'foooo'
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
@mock.patch('acme.client.datetime')
def test_retry_after_seconds(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = '50'
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 50),
self.client.retry_after(response=self.response, default=10))
@mock.patch('acme.client.datetime')
def test_retry_after_missing(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
def test_poll(self):
self.response.json.return_value = self.authzr.body.to_json()
self.assertEqual((self.authzr, self.response),
self.client.poll(self.authzr))
# TODO: split here and separate test
self.response.json.return_value = self.authz.update(
identifier=self.identifier.update(value='foo')).to_json()
self.assertRaises(
errors.UnexpectedUpdate, self.client.poll, self.authzr)
def test_request_issuance(self):
self.response.content = CERT_DER
self.response.headers['Location'] = self.certr.uri
self.response.links['up'] = {'url': self.certr.cert_chain_uri}
self.assertEqual(self.certr, self.client.request_issuance(
messages_test.CSR, (self.authzr,)))
# TODO: check POST args
def test_request_issuance_missing_up(self):
self.response.content = CERT_DER
self.response.headers['Location'] = self.certr.uri
self.assertEqual(
self.certr.update(cert_chain_uri=None),
self.client.request_issuance(messages_test.CSR, (self.authzr,)))
def test_request_issuance_missing_location(self):
self.assertRaises(
errors.ClientError, self.client.request_issuance,
messages_test.CSR, (self.authzr,))
@mock.patch('acme.client.datetime')
@mock.patch('acme.client.time')
def test_poll_and_request_issuance(self, time_mock, dt_mock):
# clock.dt | pylint: disable=no-member
clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27))
def sleep(seconds):
"""increment clock"""
clock.dt += datetime.timedelta(seconds=seconds)
time_mock.sleep.side_effect = sleep
def now():
"""return current clock value"""
return clock.dt
dt_mock.datetime.now.side_effect = now
dt_mock.timedelta = datetime.timedelta
def poll(authzr): # pylint: disable=missing-docstring
# record poll start time based on the current clock value
authzr.times.append(clock.dt)
# suppose it takes 2 seconds for server to produce the
# result, increment clock
clock.dt += datetime.timedelta(seconds=2)
if not authzr.retries: # no more retries
done = mock.MagicMock(uri=authzr.uri, times=authzr.times)
done.body.status = messages.STATUS_VALID
return done, []
# response (2nd result tuple element) is reduced to only
# Retry-After header contents represented as integer
# seconds; authzr.retries is a list of Retry-After
# headers, head(retries) is peeled of as a current
# Retry-After header, and tail(retries) is persisted for
# later poll() calls
return (mock.MagicMock(retries=authzr.retries[1:],
uri=authzr.uri + '.', times=authzr.times),
authzr.retries[0])
self.client.poll = mock.MagicMock(side_effect=poll)
mintime = 7
def retry_after(response, default): # pylint: disable=missing-docstring
# check that poll_and_request_issuance correctly passes mintime
self.assertEqual(default, mintime)
return clock.dt + datetime.timedelta(seconds=response)
self.client.retry_after = mock.MagicMock(side_effect=retry_after)
def request_issuance(csr, authzrs): # pylint: disable=missing-docstring
return csr, authzrs
self.client.request_issuance = mock.MagicMock(
side_effect=request_issuance)
csr = mock.MagicMock()
authzrs = (
mock.MagicMock(uri='a', times=[], retries=(8, 20, 30)),
mock.MagicMock(uri='b', times=[], retries=(5,)),
)
cert, updated_authzrs = self.client.poll_and_request_issuance(
csr, authzrs, mintime=mintime)
self.assertTrue(cert[0] is csr)
self.assertTrue(cert[1] is updated_authzrs)
self.assertEqual(updated_authzrs[0].uri, 'a...')
self.assertEqual(updated_authzrs[1].uri, 'b.')
self.assertEqual(updated_authzrs[0].times, [
datetime.datetime(2015, 3, 27),
# a is scheduled for 10, but b is polling [9..11), so it
# will be picked up as soon as b is finished, without
# additional sleeping
datetime.datetime(2015, 3, 27, 0, 0, 11),
datetime.datetime(2015, 3, 27, 0, 0, 33),
datetime.datetime(2015, 3, 27, 0, 1, 5),
])
self.assertEqual(updated_authzrs[1].times, [
datetime.datetime(2015, 3, 27, 0, 0, 2),
datetime.datetime(2015, 3, 27, 0, 0, 9),
])
self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7))
def test_check_cert(self):
self.response.headers['Location'] = self.certr.uri
self.response.content = CERT_DER
self.assertEqual(self.certr.update(body=messages_test.CERT),
self.client.check_cert(self.certr))
# TODO: split here and separate test
self.response.headers['Location'] = 'foo'
self.assertRaises(
errors.UnexpectedUpdate, self.client.check_cert, self.certr)
def test_check_cert_missing_location(self):
self.response.content = CERT_DER
self.assertRaises(
errors.ClientError, self.client.check_cert, self.certr)
def test_refresh(self):
self.client.check_cert = mock.MagicMock()
self.assertEqual(
self.client.check_cert(self.certr), self.client.refresh(self.certr))
def test_fetch_chain(self):
# pylint: disable=protected-access
self.client._get_cert = mock.MagicMock()
self.client._get_cert.return_value = ("response", "certificate")
self.assertEqual(self.client._get_cert(self.certr.cert_chain_uri)[1],
self.client.fetch_chain(self.certr))
def test_fetch_chain_no_up_link(self):
self.assertTrue(self.client.fetch_chain(self.certr.update(
cert_chain_uri=None)) is None)
def test_revoke(self):
self.client.revoke(self.certr.body)
self.net.post.assert_called_once_with(messages.Revocation.url(
self.client.new_reg_uri), mock.ANY)
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
self.assertRaises(errors.ClientError, self.client.revoke, self.certr)
class ClientNetworkTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork."""
def setUp(self):
self.verify_ssl = mock.MagicMock()
self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped)
from acme.client import ClientNetwork
self.net = ClientNetwork(
key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl)
self.response = mock.MagicMock(ok=True, status_code=http_client.OK)
self.response.headers = {}
self.response.links = {}
def test_init(self):
self.assertTrue(self.net.verify_ssl is self.verify_ssl)
def test_wrap_in_jws(self):
class MockJSONDeSerializable(jose.JSONDeSerializable):
# pylint: disable=missing-docstring
def __init__(self, value):
self.value = value
def to_partial_json(self):
return {'foo': self.value}
@classmethod
def from_json(cls, value):
pass # pragma: no cover
# pylint: disable=protected-access
jws_dump = self.net._wrap_in_jws(
MockJSONDeSerializable('foo'), nonce=b'Tg')
jws = acme_jws.JWS.json_loads(jws_dump)
self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'})
self.assertEqual(jws.signature.combined.nonce, b'Tg')
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
self.response.json.return_value = {}
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
def test_check_response_not_ok_jobj_error(self):
self.response.ok = False
self.response.json.return_value = messages.Error(
detail='foo', typ='serverInternal', title='some title').to_json()
# pylint: disable=protected-access
self.assertRaises(
messages.Error, self.net._check_response, self.response)
def test_check_response_not_ok_no_jobj(self):
self.response.ok = False
self.response.json.side_effect = ValueError
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
def test_check_response_ok_no_jobj_ct_required(self):
self.response.json.side_effect = ValueError
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response,
content_type=self.net.JSON_CONTENT_TYPE)
def test_check_response_ok_no_jobj_no_ct(self):
self.response.json.side_effect = ValueError
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access,no-value-for-parameter
self.assertEqual(
self.response, self.net._check_response(self.response))
def test_check_response_jobj(self):
self.response.json.return_value = {}
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access,no-value-for-parameter
self.assertEqual(
self.response, self.net._check_response(self.response))
@mock.patch('acme.client.requests')
def test_send_request(self, mock_requests):
mock_requests.request.return_value = self.response
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._send_request(
'HEAD', 'url', 'foo', bar='baz'))
mock_requests.request.assert_called_once_with(
'HEAD', 'url', 'foo', verify=mock.ANY, bar='baz')
@mock.patch('acme.client.requests')
def test_send_request_verify_ssl(self, mock_requests):
# pylint: disable=protected-access
for verify in True, False:
mock_requests.request.reset_mock()
mock_requests.request.return_value = self.response
self.net.verify_ssl = verify
# pylint: disable=protected-access
self.assertEqual(
self.response, self.net._send_request('GET', 'url'))
mock_requests.request.assert_called_once_with(
'GET', 'url', verify=verify)
@mock.patch('acme.client.requests')
def test_requests_error_passthrough(self, mock_requests):
mock_requests.exceptions = requests.exceptions
mock_requests.request.side_effect = requests.exceptions.RequestException
# pylint: disable=protected-access
self.assertRaises(requests.exceptions.RequestException,
self.net._send_request, 'GET', 'uri')
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork which mock out response."""
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.client import ClientNetwork
self.net = ClientNetwork(key=None, alg=None)
self.response = mock.MagicMock(ok=True, status_code=http_client.OK)
self.response.headers = {}
self.response.links = {}
self.checked_response = mock.MagicMock()
self.obj = mock.MagicMock()
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type
self.all_nonces = [jose.b64encode(b'Nonce'), jose.b64encode(b'Nonce2')]
self.available_nonces = self.all_nonces[:]
def send_request(*args, **kwargs):
# pylint: disable=unused-argument,missing-docstring
if self.available_nonces:
self.response.headers = {
self.net.REPLAY_NONCE_HEADER:
self.available_nonces.pop().decode()}
else:
self.response.headers = {}
return self.response
# pylint: disable=protected-access
self.net._send_request = self.send_request = mock.MagicMock(
side_effect=send_request)
self.net._check_response = self.check_response
self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj)
def check_response(self, response, content_type):
# pylint: disable=missing-docstring
self.assertEqual(self.response, response)
self.assertEqual(self.content_type, content_type)
return self.checked_response
def test_head(self):
self.assertEqual(self.response, self.net.head('url', 'foo', bar='baz'))
self.send_request.assert_called_once_with(
'HEAD', 'url', 'foo', bar='baz')
def test_get(self):
self.assertEqual(self.checked_response, self.net.get(
'url', content_type=self.content_type, bar='baz'))
self.send_request.assert_called_once_with('GET', 'url', bar='baz')
def test_post(self):
# pylint: disable=protected-access
self.assertEqual(self.checked_response, self.net.post(
'uri', self.obj, content_type=self.content_type))
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()))
assert not self.available_nonces
self.assertRaises(errors.MissingNonce, self.net.post,
'uri', self.obj, content_type=self.content_type)
self.net._wrap_in_jws.assert_called_with(
self.obj, jose.b64decode(self.all_nonces.pop()))
def test_post_wrong_initial_nonce(self): # HEAD
self.available_nonces = [b'f', jose.b64encode(b'good')]
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_wrong_post_response_nonce(self):
self.available_nonces = [jose.b64encode(b'good'), b'f']
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_head_get_post_error_passthrough(self):
self.send_request.side_effect = requests.exceptions.RequestException
for method in self.net.head, self.net.get:
self.assertRaises(
requests.exceptions.RequestException, method, 'GET', 'uri')
self.assertRaises(requests.exceptions.RequestException,
self.net.post, 'uri', obj=self.obj)
if __name__ == '__main__':
unittest.main() # pragma: no cover

195
acme/acme/crypto_util.py Normal file
View file

@ -0,0 +1,195 @@
"""Crypto utilities."""
import contextlib
import logging
import socket
import sys
from six.moves import range # pylint: disable=import-error,redefined-builtin
import OpenSSL
from acme import errors
logger = logging.getLogger(__name__)
# DVSNI certificate serving and probing is not affected by SSL
# vulnerabilities: prober needs to check certificate for expected
# contents anyway. Working SNI is the only thing that's necessary for
# the challenge and thus scoping down SSL/TLS method (version) would
# cause interoperability issues: TLSv1_METHOD is only compatible with
# TLSv1_METHOD, while SSLv23_METHOD is compatible with all other
# methods, including TLSv2_METHOD (read more at
# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
# should be changed to use "set_options" to disable SSLv2 and SSLv3,
# in case it's used for things other than probing/serving!
_DEFAULT_DVSNI_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD
def _serve_sni(certs, sock, reuseaddr=True, method=_DEFAULT_DVSNI_SSL_METHOD,
accept=None):
"""Start SNI-enabled server, that drops connection after handshake.
:param certs: Mapping from SNI name to ``(key, cert)`` `tuple`.
:param sock: Already bound socket.
:param bool reuseaddr: Should `socket.SO_REUSEADDR` be set?
:param method: See `OpenSSL.SSL.Context` for allowed values.
:param accept: Callable that doesn't take any arguments and
returns ``True`` if more connections should be served.
"""
def _pick_certificate(connection):
try:
key, cert = certs[connection.get_servername()]
except KeyError:
return
new_context = OpenSSL.SSL.Context(method)
new_context.use_privatekey(key)
new_context.use_certificate(cert)
connection.set_context(new_context)
if reuseaddr:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.listen(1) # TODO: add func arg?
while accept is None or accept():
server, addr = sock.accept()
logger.debug('Received connection from %s', addr)
with contextlib.closing(server):
context = OpenSSL.SSL.Context(method)
context.set_tlsext_servername_callback(_pick_certificate)
server_ssl = OpenSSL.SSL.Connection(context, server)
server_ssl.set_accept_state()
try:
server_ssl.do_handshake()
server_ssl.shutdown()
except OpenSSL.SSL.Error as error:
raise errors.Error(error)
def _probe_sni(name, host, port=443, timeout=300,
method=_DEFAULT_DVSNI_SSL_METHOD, source_address=('0', 0)):
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
client hello message.
:param bytes host: Host to connect to.
:param int port: Port to connect to.
:param int timeout: Timeout in seconds.
:param method: See `OpenSSL.SSL.Context` for allowed values.
:param tuple source_address: Enables multi-path probing (selection
of source interface). See `socket.creation_connection` for more
info. Available only in Python 2.7+.
:raises acme.errors.Error: In case of any problems.
:returns: SSL certificate presented by the server.
:rtype: OpenSSL.crypto.X509
"""
context = OpenSSL.SSL.Context(method)
context.set_timeout(timeout)
socket_kwargs = {} if sys.version_info < (2, 7) else {
'source_address': source_address}
try:
# pylint: disable=star-args
sock = socket.create_connection((host, port), **socket_kwargs)
except socket.error as error:
raise errors.Error(error)
with contextlib.closing(sock) as client:
client_ssl = OpenSSL.SSL.Connection(context, client)
client_ssl.set_connect_state()
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
try:
client_ssl.do_handshake()
client_ssl.shutdown()
except OpenSSL.SSL.Error as error:
raise errors.Error(error)
return client_ssl.get_peer_certificate()
def _pyopenssl_cert_or_req_san(cert_or_req):
"""Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
.. todo:: Implement directly in PyOpenSSL!
.. note:: Although this is `acme` internal API, it is used by
`letsencrypt`.
:param cert_or_req: Certificate or CSR.
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
:returns: A list of Subject Alternative Names.
:rtype: `list` of `unicode`
"""
# constants based on implementation of
# OpenSSL.crypto.X509Error._subjectAltNameString
parts_separator = ", "
part_separator = ":"
extension_short_name = b"subjectAltName"
if hasattr(cert_or_req, 'get_extensions'): # X509Req
extensions = cert_or_req.get_extensions()
else: # X509
extensions = [cert_or_req.get_extension(i)
for i in range(cert_or_req.get_extension_count())]
# pylint: disable=protected-access,no-member
label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS]
assert parts_separator not in label
prefix = label + part_separator
san_extensions = [
ext._subjectAltNameString().split(parts_separator)
for ext in extensions if ext.get_short_name() == extension_short_name]
# WARNING: this function assumes that no SAN can include
# parts_separator, hence the split!
return [part.split(part_separator)[1] for parts in san_extensions
for part in parts if part.startswith(prefix)]
def gen_ss_cert(key, domains, not_before=None, validity=(7 * 24 * 60 * 60)):
"""Generate new self-signed certificate.
:type domains: `list` of `unicode`
:param OpenSSL.crypto.PKey key:
Uses key and contains all domains.
"""
assert domains, "Must provide one or more hostnames for the cert."
cert = OpenSSL.crypto.X509()
cert.set_serial_number(1337)
cert.set_version(2)
extensions = [
OpenSSL.crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
]
cert.get_subject().CN = domains[0]
# TODO: what to put into cert.get_subject()?
cert.set_issuer(cert.get_subject())
if len(domains) > 1:
extensions.append(OpenSSL.crypto.X509Extension(
b"subjectAltName",
critical=False,
value=b", ".join(b"DNS:" + d.encode() for d in domains)
))
cert.add_extensions(extensions)
cert.gmtime_adj_notBefore(0 if not_before is None else not_before)
cert.gmtime_adj_notAfter(validity)
cert.set_pubkey(key)
cert.sign(key, "sha256")
return cert

View file

@ -0,0 +1,104 @@
"""Tests for acme.crypto_util."""
import socket
import threading
import time
import unittest
import mock
import OpenSSL
from acme import errors
from acme import jose
from acme import test_util
class ServeProbeSNITest(unittest.TestCase):
"""Tests for acme.crypto_util._serve_sni/_probe_sni."""
def setUp(self):
self.cert = test_util.load_cert('cert.pem')
key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM,
test_util.load_vector('rsa512_key.pem'))
# pylint: disable=protected-access
certs = {b'foo': (key, self.cert._wrapped)}
sock = socket.socket()
sock.bind(('', 0)) # pick random port
self.port = sock.getsockname()[1]
self.server = threading.Thread(target=self._run_server, args=(certs, sock))
self.server.start()
time.sleep(1) # TODO: avoid race conditions in other way
@classmethod
def _run_server(cls, certs, sock):
from acme.crypto_util import _serve_sni
# TODO: improve testing of server errors and their conditions
try:
return _serve_sni(
certs, sock, accept=mock.Mock(side_effect=[True, False]))
except errors.Error:
pass
def tearDown(self):
self.server.join()
def _probe(self, name):
from acme.crypto_util import _probe_sni
return jose.ComparableX509(_probe_sni(
name, host='127.0.0.1', port=self.port))
def test_probe_ok(self):
self.assertEqual(self.cert, self._probe(b'foo'))
def test_probe_not_recognized_name(self):
self.assertRaises(errors.Error, self._probe, b'bar')
def test_probe_connection_error(self):
self._probe(b'foo')
time.sleep(1) # TODO: avoid race conditions in other way
self.assertRaises(errors.Error, self._probe, b'bar')
class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
"""Test for acme.crypto_util._pyopenssl_cert_or_req_san."""
@classmethod
def _call(cls, loader, name):
# pylint: disable=protected-access
from acme.crypto_util import _pyopenssl_cert_or_req_san
return _pyopenssl_cert_or_req_san(loader(name))
def _call_cert(self, name):
return self._call(test_util.load_cert, name)
def _call_csr(self, name):
return self._call(test_util.load_csr, name)
def test_cert_no_sans(self):
self.assertEqual(self._call_cert('cert.pem'), [])
def test_cert_two_sans(self):
self.assertEqual(self._call_cert('cert-san.pem'),
['example.com', 'www.example.com'])
def test_csr_no_sans(self):
self.assertEqual(self._call_csr('csr-nosans.pem'), [])
def test_csr_one_san(self):
self.assertEqual(self._call_csr('csr.pem'), ['example.com'])
def test_csr_two_sans(self):
self.assertEqual(self._call_csr('csr-san.pem'),
['example.com', 'www.example.com'])
def test_csr_six_sans(self):
self.assertEqual(self._call_csr('csr-6sans.pem'),
["example.com", "example.org", "example.net",
"example.info", "subdomain.example.com",
"other.subdomain.example.com"])
if __name__ == "__main__":
unittest.main() # pragma: no cover

53
acme/acme/errors.py Normal file
View file

@ -0,0 +1,53 @@
"""ACME errors."""
from acme.jose import errors as jose_errors
class Error(Exception):
"""Generic ACME error."""
class SchemaValidationError(jose_errors.DeserializationError):
"""JSON schema ACME object validation error."""
class ClientError(Error):
"""Network error."""
class UnexpectedUpdate(ClientError):
"""Unexpected update error."""
class NonceError(ClientError):
"""Server response nonce error."""
class BadNonce(NonceError):
"""Bad nonce error."""
def __init__(self, nonce, error, *args, **kwargs):
super(BadNonce, self).__init__(*args, **kwargs)
self.nonce = nonce
self.error = error
def __str__(self):
return 'Invalid nonce ({0!r}): {1}'.format(self.nonce, self.error)
class MissingNonce(NonceError):
"""Missing nonce error.
According to the specification an "ACME server MUST include an
Replay-Nonce header field in each successful response to a POST it
provides to a client (...)".
:ivar requests.Response response: HTTP Response
"""
def __init__(self, response, *args, **kwargs):
super(MissingNonce, self).__init__(*args, **kwargs)
self.response = response
def __str__(self):
return ('Server {0} response did not include a replay '
'nonce, headers: {1}'.format(
self.response.request.method, self.response.headers))

33
acme/acme/errors_test.py Normal file
View file

@ -0,0 +1,33 @@
"""Tests for acme.errors."""
import unittest
import mock
class BadNonceTest(unittest.TestCase):
"""Tests for acme.errors.BadNonce."""
def setUp(self):
from acme.errors import BadNonce
self.error = BadNonce(nonce="xxx", error="error")
def test_str(self):
self.assertEqual("Invalid nonce ('xxx'): error", str(self.error))
class MissingNonceTest(unittest.TestCase):
"""Tests for acme.errors.MissingNonce."""
def setUp(self):
from acme.errors import MissingNonce
self.response = mock.MagicMock(headers={})
self.response.request.method = 'FOO'
self.error = MissingNonce(self.response)
def test_str(self):
self.assertTrue("FOO" in str(self.error))
self.assertTrue("{}" in str(self.error))
if __name__ == "__main__":
unittest.main() # pragma: no cover

43
acme/acme/fields.py Normal file
View file

@ -0,0 +1,43 @@
"""ACME JSON fields."""
import pyrfc3339
from acme import jose
class RFC3339Field(jose.Field):
"""RFC3339 field encoder/decoder.
Handles decoding/encoding between RFC3339 strings and aware (not
naive) `datetime.datetime` objects
(e.g. ``datetime.datetime.now(pytz.utc)``).
"""
@classmethod
def default_encoder(cls, value):
return pyrfc3339.generate(value)
@classmethod
def default_decoder(cls, value):
try:
return pyrfc3339.parse(value)
except ValueError as error:
raise jose.DeserializationError(error)
class Resource(jose.Field):
"""Resource MITM field."""
def __init__(self, resource_type, *args, **kwargs):
self.resource_type = resource_type
super(Resource, self).__init__(
# TODO: omitempty used only to trick
# JSONObjectWithFieldsMeta._defaults..., server implementation
'resource', default=resource_type, *args, **kwargs)
def decode(self, value):
if value != self.resource_type:
raise jose.DeserializationError(
'Wrong resource type: {0} instead of {1}'.format(
value, self.resource_type))
return value

53
acme/acme/fields_test.py Normal file
View file

@ -0,0 +1,53 @@
"""Tests for acme.fields."""
import datetime
import unittest
import pytz
from acme import jose
class RFC3339FieldTest(unittest.TestCase):
"""Tests for acme.fields.RFC3339Field."""
def setUp(self):
self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc)
self.encoded = '2015-03-27T00:00:00Z'
def test_default_encoder(self):
from acme.fields import RFC3339Field
self.assertEqual(
self.encoded, RFC3339Field.default_encoder(self.decoded))
def test_default_encoder_naive_fails(self):
from acme.fields import RFC3339Field
self.assertRaises(
ValueError, RFC3339Field.default_encoder, datetime.datetime.now())
def test_default_decoder(self):
from acme.fields import RFC3339Field
self.assertEqual(
self.decoded, RFC3339Field.default_decoder(self.encoded))
def test_default_decoder_raises_deserialization_error(self):
from acme.fields import RFC3339Field
self.assertRaises(
jose.DeserializationError, RFC3339Field.default_decoder, '')
class ResourceTest(unittest.TestCase):
"""Tests for acme.fields.Resource."""
def setUp(self):
from acme.fields import Resource
self.field = Resource('x')
def test_decode_good(self):
self.assertEqual('x', self.field.decode('x'))
def test_decode_wrong(self):
self.assertRaises(jose.DeserializationError, self.field.decode, 'y')
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -0,0 +1,82 @@
"""Javascript Object Signing and Encryption (jose).
This package is a Python implementation of the stadards developed by
IETF `Javascript Object Signing and Encryption (Active WG)`_, in
particular the following RFCs:
- `JSON Web Algorithms (JWA)`_
- `JSON Web Key (JWK)`_
- `JSON Web Signature (JWS)`_
.. _`Javascript Object Signing and Encryption (Active WG)`:
https://tools.ietf.org/wg/jose/
.. _`JSON Web Algorithms (JWA)`:
https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-algorithms/
.. _`JSON Web Key (JWK)`:
https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-key/
.. _`JSON Web Signature (JWS)`:
https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-signature/
"""
from acme.jose.b64 import (
b64decode,
b64encode,
)
from acme.jose.errors import (
DeserializationError,
SerializationError,
Error,
UnrecognizedTypeError,
)
from acme.jose.interfaces import JSONDeSerializable
from acme.jose.json_util import (
Field,
JSONObjectWithFields,
TypedJSONObjectWithFields,
decode_b64jose,
decode_cert,
decode_csr,
decode_hex16,
encode_b64jose,
encode_cert,
encode_csr,
encode_hex16,
)
from acme.jose.jwa import (
HS256,
HS384,
HS512,
JWASignature,
PS256,
PS384,
PS512,
RS256,
RS384,
RS512,
)
from acme.jose.jwk import (
JWK,
JWKRSA,
)
from acme.jose.jws import (
Header,
JWS,
Signature,
)
from acme.jose.util import (
ComparableX509,
ComparableKey,
ComparableRSAKey,
ImmutableMap,
)

61
acme/acme/jose/b64.py Normal file
View file

@ -0,0 +1,61 @@
"""JOSE Base64.
`JOSE Base64`_ is defined as:
- URL-safe Base64
- padding stripped
.. _`JOSE Base64`:
https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-37#appendix-C
.. Do NOT try to call this module "base64", as it will "shadow" the
standard library.
"""
import base64
import six
def b64encode(data):
"""JOSE Base64 encode.
:param data: Data to be encoded.
:type data: `bytes`
:returns: JOSE Base64 string.
:rtype: bytes
:raises TypeError: if `data` is of incorrect type
"""
if not isinstance(data, six.binary_type):
raise TypeError('argument should be {0}'.format(six.binary_type))
return base64.urlsafe_b64encode(data).rstrip(b'=')
def b64decode(data):
"""JOSE Base64 decode.
:param data: Base64 string to be decoded. If it's unicode, then
only ASCII characters are allowed.
:type data: `bytes` or `unicode`
:returns: Decoded data.
:rtype: bytes
:raises TypeError: if input is of incorrect type
:raises ValueError: if input is unicode with non-ASCII characters
"""
if isinstance(data, six.string_types):
try:
data = data.encode('ascii')
except UnicodeEncodeError:
raise ValueError(
'unicode argument should contain only ASCII characters')
elif not isinstance(data, six.binary_type):
raise TypeError('argument should be a str or unicode')
return base64.urlsafe_b64decode(data + b'=' * (4 - (len(data) % 4)))

View file

@ -0,0 +1,77 @@
"""Tests for acme.jose.b64."""
import unittest
import six
# https://en.wikipedia.org/wiki/Base64#Examples
B64_PADDING_EXAMPLES = {
b'any carnal pleasure.': (b'YW55IGNhcm5hbCBwbGVhc3VyZS4', b'='),
b'any carnal pleasure': (b'YW55IGNhcm5hbCBwbGVhc3VyZQ', b'=='),
b'any carnal pleasur': (b'YW55IGNhcm5hbCBwbGVhc3Vy', b''),
b'any carnal pleasu': (b'YW55IGNhcm5hbCBwbGVhc3U', b'='),
b'any carnal pleas': (b'YW55IGNhcm5hbCBwbGVhcw', b'=='),
}
B64_URL_UNSAFE_EXAMPLES = {
six.int2byte(251) + six.int2byte(239): b'--8',
six.int2byte(255) * 2: b'__8',
}
class B64EncodeTest(unittest.TestCase):
"""Tests for acme.jose.b64.b64encode."""
@classmethod
def _call(cls, data):
from acme.jose.b64 import b64encode
return b64encode(data)
def test_empty(self):
self.assertEqual(self._call(b''), b'')
def test_unsafe_url(self):
for text, b64 in six.iteritems(B64_URL_UNSAFE_EXAMPLES):
self.assertEqual(self._call(text), b64)
def test_different_paddings(self):
for text, (b64, _) in six.iteritems(B64_PADDING_EXAMPLES):
self.assertEqual(self._call(text), b64)
def test_unicode_fails_with_type_error(self):
self.assertRaises(TypeError, self._call, u'some unicode')
class B64DecodeTest(unittest.TestCase):
"""Tests for acme.jose.b64.b64decode."""
@classmethod
def _call(cls, data):
from acme.jose.b64 import b64decode
return b64decode(data)
def test_unsafe_url(self):
for text, b64 in six.iteritems(B64_URL_UNSAFE_EXAMPLES):
self.assertEqual(self._call(b64), text)
def test_input_without_padding(self):
for text, (b64, _) in six.iteritems(B64_PADDING_EXAMPLES):
self.assertEqual(self._call(b64), text)
def test_input_with_padding(self):
for text, (b64, pad) in six.iteritems(B64_PADDING_EXAMPLES):
self.assertEqual(self._call(b64 + pad), text)
def test_unicode_with_ascii(self):
self.assertEqual(self._call(u'YQ'), b'a')
def test_non_ascii_unicode_fails(self):
self.assertRaises(ValueError, self._call, u'\u0105')
def test_type_error_no_unicode_or_bytes(self):
self.assertRaises(TypeError, self._call, object())
if __name__ == '__main__':
unittest.main() # pragma: no cover

31
acme/acme/jose/errors.py Normal file
View file

@ -0,0 +1,31 @@
"""JOSE errors."""
class Error(Exception):
"""Generic JOSE Error."""
class DeserializationError(Error):
"""JSON deserialization error."""
class SerializationError(Error):
"""JSON serialization error."""
class UnrecognizedTypeError(DeserializationError):
"""Unrecognized type error.
:ivar str typ: The unrecognized type of the JSON object.
:ivar jobj: Full JSON object.
"""
def __init__(self, typ, jobj):
self.typ = typ
self.jobj = jobj
super(UnrecognizedTypeError, self).__init__(str(self))
def __str__(self):
return '{0} was not recognized, full message: {1}'.format(
self.typ, self.jobj)

View file

@ -0,0 +1,17 @@
"""Tests for acme.jose.errors."""
import unittest
class UnrecognizedTypeErrorTest(unittest.TestCase):
def setUp(self):
from acme.jose.errors import UnrecognizedTypeError
self.error = UnrecognizedTypeError('foo', {'type': 'foo'})
def test_str(self):
self.assertEqual(
"foo was not recognized, full message: {'type': 'foo'}",
str(self.error))
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -0,0 +1,211 @@
"""JOSE interfaces."""
import abc
import collections
import json
import six
from acme.jose import util
# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class
# pylint: disable=too-few-public-methods
@six.add_metaclass(abc.ABCMeta)
class JSONDeSerializable(object):
# pylint: disable=too-few-public-methods
"""Interface for (de)serializable JSON objects.
Please recall, that standard Python library implements
:class:`json.JSONEncoder` and :class:`json.JSONDecoder` that perform
translations based on respective :ref:`conversion tables
<conversion-table>` that look pretty much like the one below (for
complete tables see relevant Python documentation):
.. _conversion-table:
====== ======
JSON Python
====== ======
object dict
... ...
====== ======
While the above **conversion table** is about translation of JSON
documents to/from the basic Python types only,
:class:`JSONDeSerializable` introduces the following two concepts:
serialization
Turning an arbitrary Python object into Python object that can
be encoded into a JSON document. **Full serialization** produces
a Python object composed of only basic types as required by the
:ref:`conversion table <conversion-table>`. **Partial
serialization** (acomplished by :meth:`to_partial_json`)
produces a Python object that might also be built from other
:class:`JSONDeSerializable` objects.
deserialization
Turning a decoded Python object (necessarily one of the basic
types as required by the :ref:`conversion table
<conversion-table>`) into an arbitrary Python object.
Serialization produces **serialized object** ("partially serialized
object" or "fully serialized object" for partial and full
serialization respectively) and deserialization produces
**deserialized object**, both usually denoted in the source code as
``jobj``.
Wording in the official Python documentation might be confusing
after reading the above, but in the light of those definitions, one
can view :meth:`json.JSONDecoder.decode` as decoder and
deserializer of basic types, :meth:`json.JSONEncoder.default` as
serializer of basic types, :meth:`json.JSONEncoder.encode` as
serializer and encoder of basic types.
One could extend :mod:`json` to support arbitrary object
(de)serialization either by:
- overriding :meth:`json.JSONDecoder.decode` and
:meth:`json.JSONEncoder.default` in subclasses
- or passing ``object_hook`` argument (or ``object_hook_pairs``)
to :func:`json.load`/:func:`json.loads` or ``default`` argument
for :func:`json.dump`/:func:`json.dumps`.
Interestingly, ``default`` is required to perform only partial
serialization, as :func:`json.dumps` applies ``default``
recursively. This is the idea behind making :meth:`to_partial_json`
produce only partial serialization, while providing custom
:meth:`json_dumps` that dumps with ``default`` set to
:meth:`json_dump_default`.
To make further documentation a bit more concrete, please, consider
the following imaginatory implementation example::
class Foo(JSONDeSerializable):
def to_partial_json(self):
return 'foo'
@classmethod
def from_json(cls, jobj):
return Foo()
class Bar(JSONDeSerializable):
def to_partial_json(self):
return [Foo(), Foo()]
@classmethod
def from_json(cls, jobj):
return Bar()
"""
@abc.abstractmethod
def to_partial_json(self): # pragma: no cover
"""Partially serialize.
Following the example, **partial serialization** means the following::
assert isinstance(Bar().to_partial_json()[0], Foo)
assert isinstance(Bar().to_partial_json()[1], Foo)
# in particular...
assert Bar().to_partial_json() != ['foo', 'foo']
:raises acme.jose.errors.SerializationError:
in case of any serialization error.
:returns: Partially serializable object.
"""
raise NotImplementedError()
def to_json(self):
"""Fully serialize.
Again, following the example from before, **full serialization**
means the following::
assert Bar().to_json() == ['foo', 'foo']
:raises acme.jose.errors.SerializationError:
in case of any serialization error.
:returns: Fully serialized object.
"""
def _serialize(obj):
if isinstance(obj, JSONDeSerializable):
return _serialize(obj.to_partial_json())
if isinstance(obj, six.string_types): # strings are Sequence
return obj
elif isinstance(obj, list):
return [_serialize(subobj) for subobj in obj]
elif isinstance(obj, collections.Sequence):
# default to tuple, otherwise Mapping could get
# unhashable list
return tuple(_serialize(subobj) for subobj in obj)
elif isinstance(obj, collections.Mapping):
return dict((_serialize(key), _serialize(value))
for key, value in six.iteritems(obj))
else:
return obj
return _serialize(self)
@util.abstractclassmethod
def from_json(cls, jobj): # pylint: disable=unused-argument
"""Deserialize a decoded JSON document.
:param jobj: Python object, composed of only other basic data
types, as decoded from JSON document. Not necessarily
:class:`dict` (as decoded from "JSON object" document).
:raises acme.jose.errors.DeserializationError:
if decoding was unsuccessful, e.g. in case of unparseable
X509 certificate, or wrong padding in JOSE base64 encoded
string, etc.
"""
# TypeError: Can't instantiate abstract class <cls> with
# abstract methods from_json, to_partial_json
return cls() # pylint: disable=abstract-class-instantiated
@classmethod
def json_loads(cls, json_string):
"""Deserialize from JSON document string."""
return cls.from_json(json.loads(json_string))
def json_dumps(self, **kwargs):
"""Dump to JSON string using proper serializer.
:returns: JSON document string.
:rtype: str
"""
return json.dumps(self, default=self.json_dump_default, **kwargs)
def json_dumps_pretty(self):
"""Dump the object to pretty JSON document string.
:rtype: str
"""
return self.json_dumps(sort_keys=True, indent=4, separators=(',', ': '))
@classmethod
def json_dump_default(cls, python_object):
"""Serialize Python object.
This function is meant to be passed as ``default`` to
:func:`json.dump` or :func:`json.dumps`. They call
``default(python_object)`` only for non-basic Python types, so
this function necessarily raises :class:`TypeError` if
``python_object`` is not an instance of
:class:`IJSONSerializable`.
Please read the class docstring for more information.
"""
if isinstance(python_object, JSONDeSerializable):
return python_object.to_partial_json()
else: # this branch is necessary, cannot just "return"
raise TypeError(repr(python_object) + ' is not JSON serializable')

View file

@ -0,0 +1,114 @@
"""Tests for acme.jose.interfaces."""
import unittest
class JSONDeSerializableTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.jose.interfaces import JSONDeSerializable
# pylint: disable=missing-docstring,invalid-name
class Basic(JSONDeSerializable):
def __init__(self, v):
self.v = v
def to_partial_json(self):
return self.v
@classmethod
def from_json(cls, jobj):
return cls(jobj)
class Sequence(JSONDeSerializable):
def __init__(self, x, y):
self.x = x
self.y = y
def to_partial_json(self):
return [self.x, self.y]
@classmethod
def from_json(cls, jobj):
return cls(
Basic.from_json(jobj[0]), Basic.from_json(jobj[1]))
class Mapping(JSONDeSerializable):
def __init__(self, x, y):
self.x = x
self.y = y
def to_partial_json(self):
return {self.x: self.y}
@classmethod
def from_json(cls, jobj):
pass # pragma: no cover
self.basic1 = Basic('foo1')
self.basic2 = Basic('foo2')
self.seq = Sequence(self.basic1, self.basic2)
self.mapping = Mapping(self.basic1, self.basic2)
self.nested = Basic([[self.basic1]])
self.tuple = Basic(('foo',))
# pylint: disable=invalid-name
self.Basic = Basic
self.Sequence = Sequence
self.Mapping = Mapping
def test_to_json_sequence(self):
self.assertEqual(self.seq.to_json(), ['foo1', 'foo2'])
def test_to_json_mapping(self):
self.assertEqual(self.mapping.to_json(), {'foo1': 'foo2'})
def test_to_json_other(self):
mock_value = object()
self.assertTrue(self.Basic(mock_value).to_json() is mock_value)
def test_to_json_nested(self):
self.assertEqual(self.nested.to_json(), [['foo1']])
def test_to_json(self):
self.assertEqual(self.tuple.to_json(), (('foo', )))
def test_from_json_not_implemented(self):
from acme.jose.interfaces import JSONDeSerializable
self.assertRaises(TypeError, JSONDeSerializable.from_json, 'xxx')
def test_json_loads(self):
seq = self.Sequence.json_loads('["foo1", "foo2"]')
self.assertTrue(isinstance(seq, self.Sequence))
self.assertTrue(isinstance(seq.x, self.Basic))
self.assertTrue(isinstance(seq.y, self.Basic))
self.assertEqual(seq.x.v, 'foo1')
self.assertEqual(seq.y.v, 'foo2')
def test_json_dumps(self):
self.assertEqual('["foo1", "foo2"]', self.seq.json_dumps())
def test_json_dumps_pretty(self):
self.assertEqual(
self.seq.json_dumps_pretty(), '[\n "foo1",\n "foo2"\n]')
def test_json_dump_default(self):
from acme.jose.interfaces import JSONDeSerializable
self.assertEqual(
'foo1', JSONDeSerializable.json_dump_default(self.basic1))
jobj = JSONDeSerializable.json_dump_default(self.seq)
self.assertEqual(len(jobj), 2)
self.assertTrue(jobj[0] is self.basic1)
self.assertTrue(jobj[1] is self.basic2)
def test_json_dump_default_type_error(self):
from acme.jose.interfaces import JSONDeSerializable
self.assertRaises(
TypeError, JSONDeSerializable.json_dump_default, object())
if __name__ == '__main__':
unittest.main() # pragma: no cover

463
acme/acme/jose/json_util.py Normal file
View file

@ -0,0 +1,463 @@
"""JSON (de)serialization framework.
The framework presented here is somewhat based on `Go's "json" package`_
(especially the ``omitempty`` functionality).
.. _`Go's "json" package`: http://golang.org/pkg/encoding/json/
"""
import abc
import binascii
import logging
import OpenSSL
import six
from acme.jose import b64
from acme.jose import errors
from acme.jose import interfaces
from acme.jose import util
logger = logging.getLogger(__name__)
class Field(object):
"""JSON object field.
:class:`Field` is meant to be used together with
:class:`JSONObjectWithFields`.
``encoder`` (``decoder``) is a callable that accepts a single
parameter, i.e. a value to be encoded (decoded), and returns the
serialized (deserialized) value. In case of errors it should raise
:class:`~acme.jose.errors.SerializationError`
(:class:`~acme.jose.errors.DeserializationError`).
Note, that ``decoder`` should perform partial serialization only.
:ivar str json_name: Name of the field when encoded to JSON.
:ivar default: Default value (used when not present in JSON object).
:ivar bool omitempty: If ``True`` and the field value is empty, then
it will not be included in the serialized JSON object, and
``default`` will be used for deserialization. Otherwise, if ``False``,
field is considered as required, value will always be included in the
serialized JSON objected, and it must also be present when
deserializing.
"""
__slots__ = ('json_name', 'default', 'omitempty', 'fdec', 'fenc')
def __init__(self, json_name, default=None, omitempty=False,
decoder=None, encoder=None):
# pylint: disable=too-many-arguments
self.json_name = json_name
self.default = default
self.omitempty = omitempty
self.fdec = self.default_decoder if decoder is None else decoder
self.fenc = self.default_encoder if encoder is None else encoder
@classmethod
def _empty(cls, value):
"""Is the provided value cosidered "empty" for this field?
This is useful for subclasses that might want to override the
definition of being empty, e.g. for some more exotic data types.
"""
return not isinstance(value, bool) and not value
def omit(self, value):
"""Omit the value in output?"""
return self._empty(value) and self.omitempty
def _update_params(self, **kwargs):
current = dict(json_name=self.json_name, default=self.default,
omitempty=self.omitempty,
decoder=self.fdec, encoder=self.fenc)
current.update(kwargs)
return type(self)(**current) # pylint: disable=star-args
def decoder(self, fdec):
"""Descriptor to change the decoder on JSON object field."""
return self._update_params(decoder=fdec)
def encoder(self, fenc):
"""Descriptor to change the encoder on JSON object field."""
return self._update_params(encoder=fenc)
def decode(self, value):
"""Decode a value, optionally with context JSON object."""
return self.fdec(value)
def encode(self, value):
"""Encode a value, optionally with context JSON object."""
return self.fenc(value)
@classmethod
def default_decoder(cls, value):
"""Default decoder.
Recursively deserialize into immutable types (
:class:`acme.jose.util.frozendict` instead of
:func:`dict`, :func:`tuple` instead of :func:`list`).
"""
# bases cases for different types returned by json.loads
if isinstance(value, list):
return tuple(cls.default_decoder(subvalue) for subvalue in value)
elif isinstance(value, dict):
return util.frozendict(
dict((cls.default_decoder(key), cls.default_decoder(value))
for key, value in six.iteritems(value)))
else: # integer or string
return value
@classmethod
def default_encoder(cls, value):
"""Default (passthrough) encoder."""
# field.to_partial_json() is no good as encoder has to do partial
# serialization only
return value
class JSONObjectWithFieldsMeta(abc.ABCMeta):
"""Metaclass for :class:`JSONObjectWithFields` and its subclasses.
It makes sure that, for any class ``cls`` with ``__metaclass__``
set to ``JSONObjectWithFieldsMeta``:
1. All fields (attributes of type :class:`Field`) in the class
definition are moved to the ``cls._fields`` dictionary, where
keys are field attribute names and values are fields themselves.
2. ``cls.__slots__`` is extended by all field attribute names
(i.e. not :attr:`Field.json_name`). Original ``cls.__slots__``
are stored in ``cls._orig_slots``.
In a consequence, for a field attribute name ``some_field``,
``cls.some_field`` will be a slot descriptor and not an instance
of :class:`Field`. For example::
some_field = Field('someField', default=())
class Foo(object):
__metaclass__ = JSONObjectWithFieldsMeta
__slots__ = ('baz',)
some_field = some_field
assert Foo.__slots__ == ('some_field', 'baz')
assert Foo._orig_slots == ()
assert Foo.some_field is not Field
assert Foo._fields.keys() == ['some_field']
assert Foo._fields['some_field'] is some_field
As an implementation note, this metaclass inherits from
:class:`abc.ABCMeta` (and not the usual :class:`type`) to mitigate
the metaclass conflict (:class:`ImmutableMap` and
:class:`JSONDeSerializable`, parents of :class:`JSONObjectWithFields`,
use :class:`abc.ABCMeta` as its metaclass).
"""
def __new__(mcs, name, bases, dikt):
fields = {}
for base in bases:
fields.update(getattr(base, '_fields', {}))
# Do not reorder, this class might override fields from base classes!
for key, value in tuple(six.iteritems(dikt)):
# not six.iterkeys() (in-place edit!)
if isinstance(value, Field):
fields[key] = dikt.pop(key)
dikt['_orig_slots'] = dikt.get('__slots__', ())
dikt['__slots__'] = tuple(
list(dikt['_orig_slots']) + list(six.iterkeys(fields)))
dikt['_fields'] = fields
return abc.ABCMeta.__new__(mcs, name, bases, dikt)
@six.add_metaclass(JSONObjectWithFieldsMeta)
class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable):
# pylint: disable=too-few-public-methods
"""JSON object with fields.
Example::
class Foo(JSONObjectWithFields):
bar = Field('Bar')
empty = Field('Empty', omitempty=True)
@bar.encoder
def bar(value):
return value + 'bar'
@bar.decoder
def bar(value):
if not value.endswith('bar'):
raise errors.DeserializationError('No bar suffix!')
return value[:-3]
assert Foo(bar='baz').to_partial_json() == {'Bar': 'bazbar'}
assert Foo.from_json({'Bar': 'bazbar'}) == Foo(bar='baz')
assert (Foo.from_json({'Bar': 'bazbar', 'Empty': '!'})
== Foo(bar='baz', empty='!'))
assert Foo(bar='baz').bar == 'baz'
"""
@classmethod
def _defaults(cls):
"""Get default fields values."""
return dict([(slot, field.default) for slot, field
in six.iteritems(cls._fields)])
def __init__(self, **kwargs):
# pylint: disable=star-args
super(JSONObjectWithFields, self).__init__(
**(dict(self._defaults(), **kwargs)))
def fields_to_partial_json(self):
"""Serialize fields to JSON."""
jobj = {}
omitted = set()
for slot, field in six.iteritems(self._fields):
value = getattr(self, slot)
if field.omit(value):
omitted.add((slot, value))
else:
try:
jobj[field.json_name] = field.encode(value)
except errors.SerializationError as error:
raise errors.SerializationError(
'Could not encode {0} ({1}): {2}'.format(
slot, value, error))
if omitted:
# pylint: disable=star-args
logger.debug('Omitted empty fields: %s', ', '.join(
'{0!s}={1!r}'.format(*field) for field in omitted))
return jobj
def to_partial_json(self):
return self.fields_to_partial_json()
@classmethod
def _check_required(cls, jobj):
missing = set()
for _, field in six.iteritems(cls._fields):
if not field.omitempty and field.json_name not in jobj:
missing.add(field.json_name)
if missing:
raise errors.DeserializationError(
'The following field are required: {0}'.format(
','.join(missing)))
@classmethod
def fields_from_json(cls, jobj):
"""Deserialize fields from JSON."""
cls._check_required(jobj)
fields = {}
for slot, field in six.iteritems(cls._fields):
if field.json_name not in jobj and field.omitempty:
fields[slot] = field.default
else:
value = jobj[field.json_name]
try:
fields[slot] = field.decode(value)
except errors.DeserializationError as error:
raise errors.DeserializationError(
'Could not decode {0!r} ({1!r}): {2}'.format(
slot, value, error))
return fields
@classmethod
def from_json(cls, jobj):
return cls(**cls.fields_from_json(jobj))
def encode_b64jose(data):
"""Encode JOSE Base-64 field.
:param bytes data:
:rtype: `unicode`
"""
# b64encode produces ASCII characters only
return b64.b64encode(data).decode('ascii')
def decode_b64jose(data, size=None, minimum=False):
"""Decode JOSE Base-64 field.
:param unicode data:
:param int size: Required length (after decoding).
:param bool minimum: If ``True``, then `size` will be treated as
minimum required length, as opposed to exact equality.
:rtype: bytes
"""
error_cls = TypeError if six.PY2 else binascii.Error
try:
decoded = b64.b64decode(data.encode())
except error_cls as error:
raise errors.DeserializationError(error)
if size is not None and ((not minimum and len(decoded) != size)
or (minimum and len(decoded) < size)):
raise errors.DeserializationError()
return decoded
def encode_hex16(value):
"""Hexlify.
:param bytes value:
:rtype: unicode
"""
return binascii.hexlify(value).decode()
def decode_hex16(value, size=None, minimum=False):
"""Decode hexlified field.
:param unicode value:
:param int size: Required length (after decoding).
:param bool minimum: If ``True``, then `size` will be treated as
minimum required length, as opposed to exact equality.
:rtype: bytes
"""
value = value.encode()
if size is not None and ((not minimum and len(value) != size * 2)
or (minimum and len(value) < size * 2)):
raise errors.DeserializationError()
error_cls = TypeError if six.PY2 else binascii.Error
try:
return binascii.unhexlify(value)
except error_cls as error:
raise errors.DeserializationError(error)
def encode_cert(cert):
"""Encode certificate as JOSE Base-64 DER.
:type cert: `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
:rtype: unicode
"""
return encode_b64jose(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert))
def decode_cert(b64der):
"""Decode JOSE Base-64 DER-encoded certificate.
:param unicode b64der:
:rtype: `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
try:
return util.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, decode_b64jose(b64der)))
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
def encode_csr(csr):
"""Encode CSR as JOSE Base-64 DER.
:type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:rtype: unicode
"""
return encode_b64jose(OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr))
def decode_csr(b64der):
"""Decode JOSE Base-64 DER-encoded CSR.
:param unicode b64der:
:rtype: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
"""
try:
return util.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, decode_b64jose(b64der)))
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
class TypedJSONObjectWithFields(JSONObjectWithFields):
"""JSON object with type."""
typ = NotImplemented
"""Type of the object. Subclasses must override."""
type_field_name = "type"
"""Field name used to distinguish different object types.
Subclasses will probably have to override this.
"""
TYPES = NotImplemented
"""Types registered for JSON deserialization"""
@classmethod
def register(cls, type_cls, typ=None):
"""Register class for JSON deserialization."""
typ = type_cls.typ if typ is None else typ
cls.TYPES[typ] = type_cls
return type_cls
@classmethod
def get_type_cls(cls, jobj):
"""Get the registered class for ``jobj``."""
if cls in six.itervalues(cls.TYPES):
assert jobj[cls.type_field_name]
# cls is already registered type_cls, force to use it
# so that, e.g Revocation.from_json(jobj) fails if
# jobj["type"] != "revocation".
return cls
if not isinstance(jobj, dict):
raise errors.DeserializationError(
"{0} is not a dictionary object".format(jobj))
try:
typ = jobj[cls.type_field_name]
except KeyError:
raise errors.DeserializationError("missing type field")
try:
return cls.TYPES[typ]
except KeyError:
raise errors.UnrecognizedTypeError(typ, jobj)
def to_partial_json(self):
"""Get JSON serializable object.
:returns: Serializable JSON object representing ACME typed object.
:meth:`validate` will almost certainly not work, due to reasons
explained in :class:`acme.interfaces.IJSONSerializable`.
:rtype: dict
"""
jobj = self.fields_to_partial_json()
jobj[self.type_field_name] = self.typ
return jobj
@classmethod
def from_json(cls, jobj):
"""Deserialize ACME object from valid JSON object.
:raises acme.errors.UnrecognizedTypeError: if type
of the ACME object has not been registered.
"""
# make sure subclasses don't cause infinite recursive from_json calls
type_cls = cls.get_type_cls(jobj)
return type_cls(**type_cls.fields_from_json(jobj))

View file

@ -0,0 +1,364 @@
"""Tests for acme.jose.json_util."""
import itertools
import unittest
import mock
import six
from acme import test_util
from acme.jose import errors
from acme.jose import interfaces
from acme.jose import util
CERT = test_util.load_cert('cert.pem')
CSR = test_util.load_csr('csr.pem')
class FieldTest(unittest.TestCase):
"""Tests for acme.jose.json_util.Field."""
def test_no_omit_boolean(self):
from acme.jose.json_util import Field
for default, omitempty, value in itertools.product(
[True, False], [True, False], [True, False]):
self.assertFalse(
Field("foo", default=default, omitempty=omitempty).omit(value))
def test_descriptors(self):
mock_value = mock.MagicMock()
# pylint: disable=missing-docstring
def decoder(unused_value):
return 'd'
def encoder(unused_value):
return 'e'
from acme.jose.json_util import Field
field = Field('foo')
field = field.encoder(encoder)
self.assertEqual('e', field.encode(mock_value))
field = field.decoder(decoder)
self.assertEqual('e', field.encode(mock_value))
self.assertEqual('d', field.decode(mock_value))
def test_default_encoder_is_partial(self):
class MockField(interfaces.JSONDeSerializable):
# pylint: disable=missing-docstring
def to_partial_json(self):
return 'foo' # pragma: no cover
@classmethod
def from_json(cls, jobj):
pass # pragma: no cover
mock_field = MockField()
from acme.jose.json_util import Field
self.assertTrue(Field.default_encoder(mock_field) is mock_field)
# in particular...
self.assertNotEqual('foo', Field.default_encoder(mock_field))
def test_default_encoder_passthrough(self):
mock_value = mock.MagicMock()
from acme.jose.json_util import Field
self.assertTrue(Field.default_encoder(mock_value) is mock_value)
def test_default_decoder_list_to_tuple(self):
from acme.jose.json_util import Field
self.assertEqual((1, 2, 3), Field.default_decoder([1, 2, 3]))
def test_default_decoder_dict_to_frozendict(self):
from acme.jose.json_util import Field
obj = Field.default_decoder({'x': 2})
self.assertTrue(isinstance(obj, util.frozendict))
self.assertEqual(obj, util.frozendict(x=2))
def test_default_decoder_passthrough(self):
mock_value = mock.MagicMock()
from acme.jose.json_util import Field
self.assertTrue(Field.default_decoder(mock_value) is mock_value)
class JSONObjectWithFieldsMetaTest(unittest.TestCase):
"""Tests for acme.jose.json_util.JSONObjectWithFieldsMeta."""
def setUp(self):
from acme.jose.json_util import Field
from acme.jose.json_util import JSONObjectWithFieldsMeta
self.field = Field('Baz')
self.field2 = Field('Baz2')
# pylint: disable=invalid-name,missing-docstring,too-few-public-methods
# pylint: disable=blacklisted-name
@six.add_metaclass(JSONObjectWithFieldsMeta)
class A(object):
__slots__ = ('bar',)
baz = self.field
class B(A):
pass
class C(A):
baz = self.field2
self.a_cls = A
self.b_cls = B
self.c_cls = C
def test_fields(self):
# pylint: disable=protected-access,no-member
self.assertEqual({'baz': self.field}, self.a_cls._fields)
self.assertEqual({'baz': self.field}, self.b_cls._fields)
def test_fields_inheritance(self):
# pylint: disable=protected-access,no-member
self.assertEqual({'baz': self.field2}, self.c_cls._fields)
def test_slots(self):
self.assertEqual(('bar', 'baz'), self.a_cls.__slots__)
self.assertEqual(('baz',), self.b_cls.__slots__)
def test_orig_slots(self):
# pylint: disable=protected-access,no-member
self.assertEqual(('bar',), self.a_cls._orig_slots)
self.assertEqual((), self.b_cls._orig_slots)
class JSONObjectWithFieldsTest(unittest.TestCase):
"""Tests for acme.jose.json_util.JSONObjectWithFields."""
# pylint: disable=protected-access
def setUp(self):
from acme.jose.json_util import JSONObjectWithFields
from acme.jose.json_util import Field
class MockJSONObjectWithFields(JSONObjectWithFields):
# pylint: disable=invalid-name,missing-docstring,no-self-argument
# pylint: disable=too-few-public-methods
x = Field('x', omitempty=True,
encoder=(lambda x: x * 2),
decoder=(lambda x: x / 2))
y = Field('y')
z = Field('Z') # on purpose uppercase
@y.encoder
def y(value):
if value == 500:
raise errors.SerializationError()
return value
@y.decoder
def y(value):
if value == 500:
raise errors.DeserializationError()
return value
# pylint: disable=invalid-name
self.MockJSONObjectWithFields = MockJSONObjectWithFields
self.mock = MockJSONObjectWithFields(x=None, y=2, z=3)
def test_init_defaults(self):
self.assertEqual(self.mock, self.MockJSONObjectWithFields(y=2, z=3))
def test_fields_to_partial_json_omits_empty(self):
self.assertEqual(self.mock.fields_to_partial_json(), {'y': 2, 'Z': 3})
def test_fields_from_json_fills_default_for_empty(self):
self.assertEqual(
{'x': None, 'y': 2, 'z': 3},
self.MockJSONObjectWithFields.fields_from_json({'y': 2, 'Z': 3}))
def test_fields_from_json_fails_on_missing(self):
self.assertRaises(
errors.DeserializationError,
self.MockJSONObjectWithFields.fields_from_json, {'y': 0})
self.assertRaises(
errors.DeserializationError,
self.MockJSONObjectWithFields.fields_from_json, {'Z': 0})
self.assertRaises(
errors.DeserializationError,
self.MockJSONObjectWithFields.fields_from_json, {'x': 0, 'y': 0})
self.assertRaises(
errors.DeserializationError,
self.MockJSONObjectWithFields.fields_from_json, {'x': 0, 'Z': 0})
def test_fields_to_partial_json_encoder(self):
self.assertEqual(
self.MockJSONObjectWithFields(x=1, y=2, z=3).to_partial_json(),
{'x': 2, 'y': 2, 'Z': 3})
def test_fields_from_json_decoder(self):
self.assertEqual(
{'x': 2, 'y': 2, 'z': 3},
self.MockJSONObjectWithFields.fields_from_json(
{'x': 4, 'y': 2, 'Z': 3}))
def test_fields_to_partial_json_error_passthrough(self):
self.assertRaises(
errors.SerializationError, self.MockJSONObjectWithFields(
x=1, y=500, z=3).to_partial_json)
def test_fields_from_json_error_passthrough(self):
self.assertRaises(
errors.DeserializationError,
self.MockJSONObjectWithFields.from_json,
{'x': 4, 'y': 500, 'Z': 3})
class DeEncodersTest(unittest.TestCase):
def setUp(self):
self.b64_cert = (
u'MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhM'
u'CVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKz'
u'ApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxF'
u'DASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIx'
u'ODIyMzQ0NVowdzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRI'
u'wEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTW'
u'ljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4YW1wbGUuY29tMFwwD'
u'QYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR7R_drnBSQ_zfx1vQLHUbFLh1'
u'AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c-pVE6K-EdE_twuUCAwE'
u'AATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksllvr6zJepBH5fMnd'
u'fk3XJp10jT6VE-14KNtjh02a56GoraAvJAT5_H67E8GvJ_ocNnB_o'
)
self.b64_csr = (
u'MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2F'
u'uMRIwEAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECw'
u'wWVW5pdmVyc2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb'
u'20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD_N_HW9As'
u'dRsUuHUBBBDlHwNlRd3fp580rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3'
u'C5QIDAQABoCkwJwYJKoZIhvcNAQkOMRowGDAWBgNVHREEDzANggtleGFtcG'
u'xlLmNvbTANBgkqhkiG9w0BAQsFAANBAHJH_O6BtC9aGzEVCMGOZ7z9iIRHW'
u'Szr9x_bOzn7hLwsbXPAgO1QxEwL-X-4g20Gn9XBE1N9W6HCIEut2d8wACg'
)
def test_encode_b64jose(self):
from acme.jose.json_util import encode_b64jose
encoded = encode_b64jose(b'x')
self.assertTrue(isinstance(encoded, six.string_types))
self.assertEqual(u'eA', encoded)
def test_decode_b64jose(self):
from acme.jose.json_util import decode_b64jose
decoded = decode_b64jose(u'eA')
self.assertTrue(isinstance(decoded, six.binary_type))
self.assertEqual(b'x', decoded)
def test_decode_b64jose_padding_error(self):
from acme.jose.json_util import decode_b64jose
self.assertRaises(errors.DeserializationError, decode_b64jose, u'x')
def test_decode_b64jose_size(self):
from acme.jose.json_util import decode_b64jose
self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=3))
self.assertRaises(
errors.DeserializationError, decode_b64jose, u'Zm9v', size=2)
self.assertRaises(
errors.DeserializationError, decode_b64jose, u'Zm9v', size=4)
def test_decode_b64jose_minimum_size(self):
from acme.jose.json_util import decode_b64jose
self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=3, minimum=True))
self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=2, minimum=True))
self.assertRaises(errors.DeserializationError, decode_b64jose,
u'Zm9v', size=4, minimum=True)
def test_encode_hex16(self):
from acme.jose.json_util import encode_hex16
encoded = encode_hex16(b'foo')
self.assertEqual(u'666f6f', encoded)
self.assertTrue(isinstance(encoded, six.string_types))
def test_decode_hex16(self):
from acme.jose.json_util import decode_hex16
decoded = decode_hex16(u'666f6f')
self.assertEqual(b'foo', decoded)
self.assertTrue(isinstance(decoded, six.binary_type))
def test_decode_hex16_minimum_size(self):
from acme.jose.json_util import decode_hex16
self.assertEqual(b'foo', decode_hex16(u'666f6f', size=3, minimum=True))
self.assertEqual(b'foo', decode_hex16(u'666f6f', size=2, minimum=True))
self.assertRaises(errors.DeserializationError, decode_hex16,
u'666f6f', size=4, minimum=True)
def test_decode_hex16_odd_length(self):
from acme.jose.json_util import decode_hex16
self.assertRaises(errors.DeserializationError, decode_hex16, u'x')
def test_encode_cert(self):
from acme.jose.json_util import encode_cert
self.assertEqual(self.b64_cert, encode_cert(CERT))
def test_decode_cert(self):
from acme.jose.json_util import decode_cert
cert = decode_cert(self.b64_cert)
self.assertTrue(isinstance(cert, util.ComparableX509))
self.assertEqual(cert, CERT)
self.assertRaises(errors.DeserializationError, decode_cert, u'')
def test_encode_csr(self):
from acme.jose.json_util import encode_csr
self.assertEqual(self.b64_csr, encode_csr(CSR))
def test_decode_csr(self):
from acme.jose.json_util import decode_csr
csr = decode_csr(self.b64_csr)
self.assertTrue(isinstance(csr, util.ComparableX509))
self.assertEqual(csr, CSR)
self.assertRaises(errors.DeserializationError, decode_csr, u'')
class TypedJSONObjectWithFieldsTest(unittest.TestCase):
def setUp(self):
from acme.jose.json_util import TypedJSONObjectWithFields
# pylint: disable=missing-docstring,abstract-method
# pylint: disable=too-few-public-methods
class MockParentTypedJSONObjectWithFields(TypedJSONObjectWithFields):
TYPES = {}
type_field_name = 'type'
@MockParentTypedJSONObjectWithFields.register
class MockTypedJSONObjectWithFields(
MockParentTypedJSONObjectWithFields):
typ = 'test'
__slots__ = ('foo',)
@classmethod
def fields_from_json(cls, jobj):
return {'foo': jobj['foo']}
def fields_to_partial_json(self):
return {'foo': self.foo}
self.parent_cls = MockParentTypedJSONObjectWithFields
self.msg = MockTypedJSONObjectWithFields(foo='bar')
def test_to_partial_json(self):
self.assertEqual(self.msg.to_partial_json(), {
'type': 'test',
'foo': 'bar',
})
def test_from_json_non_dict_fails(self):
for value in [[], (), 5, "asd"]: # all possible input types
self.assertRaises(
errors.DeserializationError, self.parent_cls.from_json, value)
def test_from_json_dict_no_type_fails(self):
self.assertRaises(
errors.DeserializationError, self.parent_cls.from_json, {})
def test_from_json_unknown_type_fails(self):
self.assertRaises(errors.UnrecognizedTypeError,
self.parent_cls.from_json, {'type': 'bar'})
def test_from_json_returns_obj(self):
self.assertEqual({'foo': 'bar'}, self.parent_cls.from_json(
{'type': 'test', 'foo': 'bar'}))
if __name__ == '__main__':
unittest.main() # pragma: no cover

180
acme/acme/jose/jwa.py Normal file
View file

@ -0,0 +1,180 @@
"""JSON Web Algorithm.
https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
"""
import abc
import collections
import logging
import cryptography.exceptions
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import hmac
from cryptography.hazmat.primitives.asymmetric import padding
from acme.jose import errors
from acme.jose import interfaces
from acme.jose import jwk
logger = logging.getLogger(__name__)
class JWA(interfaces.JSONDeSerializable): # pylint: disable=abstract-method
# pylint: disable=too-few-public-methods
# for some reason disable=abstract-method has to be on the line
# above...
"""JSON Web Algorithm."""
class JWASignature(JWA, collections.Hashable):
"""JSON Web Signature Algorithm."""
SIGNATURES = {}
def __init__(self, name):
self.name = name
def __eq__(self, other):
if not isinstance(other, JWASignature):
return NotImplemented
return self.name == other.name
def __hash__(self):
return hash((self.__class__, self.name))
def __ne__(self, other):
return not self == other
@classmethod
def register(cls, signature_cls):
"""Register class for JSON deserialization."""
cls.SIGNATURES[signature_cls.name] = signature_cls
return signature_cls
def to_partial_json(self):
return self.name
@classmethod
def from_json(cls, jobj):
return cls.SIGNATURES[jobj]
@abc.abstractmethod
def sign(self, key, msg): # pragma: no cover
"""Sign the ``msg`` using ``key``."""
raise NotImplementedError()
@abc.abstractmethod
def verify(self, key, msg, sig): # pragma: no cover
"""Verify the ``msg` and ``sig`` using ``key``."""
raise NotImplementedError()
def __repr__(self):
return self.name
class _JWAHS(JWASignature):
kty = jwk.JWKOct
def __init__(self, name, hash_):
super(_JWAHS, self).__init__(name)
self.hash = hash_()
def sign(self, key, msg):
signer = hmac.HMAC(key, self.hash, backend=default_backend())
signer.update(msg)
return signer.finalize()
def verify(self, key, msg, sig):
verifier = hmac.HMAC(key, self.hash, backend=default_backend())
verifier.update(msg)
try:
verifier.verify(sig)
except cryptography.exceptions.InvalidSignature as error:
logger.debug(error, exc_info=True)
return False
else:
return True
class _JWARSA(object):
kty = jwk.JWKRSA
padding = NotImplemented
hash = NotImplemented
def sign(self, key, msg):
"""Sign the ``msg`` using ``key``."""
try:
signer = key.signer(self.padding, self.hash)
except AttributeError as error:
logger.debug(error, exc_info=True)
raise errors.Error("Public key cannot be used for signing")
except ValueError as error: # digest too large
logger.debug(error, exc_info=True)
raise errors.Error(str(error))
signer.update(msg)
try:
return signer.finalize()
except ValueError as error:
logger.debug(error, exc_info=True)
raise errors.Error(str(error))
def verify(self, key, msg, sig):
"""Verify the ``msg` and ``sig`` using ``key``."""
verifier = key.verifier(sig, self.padding, self.hash)
verifier.update(msg)
try:
verifier.verify()
except cryptography.exceptions.InvalidSignature as error:
logger.debug(error, exc_info=True)
return False
else:
return True
class _JWARS(_JWARSA, JWASignature):
def __init__(self, name, hash_):
super(_JWARS, self).__init__(name)
self.padding = padding.PKCS1v15()
self.hash = hash_()
class _JWAPS(_JWARSA, JWASignature):
def __init__(self, name, hash_):
super(_JWAPS, self).__init__(name)
self.padding = padding.PSS(
mgf=padding.MGF1(hash_()),
salt_length=padding.PSS.MAX_LENGTH)
self.hash = hash_()
class _JWAES(JWASignature): # pylint: disable=abstract-class-not-used
# TODO: implement ES signatures
def sign(self, key, msg): # pragma: no cover
raise NotImplementedError()
def verify(self, key, msg, sig): # pragma: no cover
raise NotImplementedError()
HS256 = JWASignature.register(_JWAHS('HS256', hashes.SHA256))
HS384 = JWASignature.register(_JWAHS('HS384', hashes.SHA384))
HS512 = JWASignature.register(_JWAHS('HS512', hashes.SHA512))
RS256 = JWASignature.register(_JWARS('RS256', hashes.SHA256))
RS384 = JWASignature.register(_JWARS('RS384', hashes.SHA384))
RS512 = JWASignature.register(_JWARS('RS512', hashes.SHA512))
PS256 = JWASignature.register(_JWAPS('PS256', hashes.SHA256))
PS384 = JWASignature.register(_JWAPS('PS384', hashes.SHA384))
PS512 = JWASignature.register(_JWAPS('PS512', hashes.SHA512))
ES256 = JWASignature.register(_JWAES('ES256'))
ES256 = JWASignature.register(_JWAES('ES384'))
ES256 = JWASignature.register(_JWAES('ES512'))

104
acme/acme/jose/jwa_test.py Normal file
View file

@ -0,0 +1,104 @@
"""Tests for acme.jose.jwa."""
import unittest
from acme import test_util
from acme.jose import errors
RSA256_KEY = test_util.load_rsa_private_key('rsa256_key.pem')
RSA512_KEY = test_util.load_rsa_private_key('rsa512_key.pem')
RSA1024_KEY = test_util.load_rsa_private_key('rsa1024_key.pem')
class JWASignatureTest(unittest.TestCase):
"""Tests for acme.jose.jwa.JWASignature."""
def setUp(self):
from acme.jose.jwa import JWASignature
class MockSig(JWASignature):
# pylint: disable=missing-docstring,too-few-public-methods
# pylint: disable=abstract-class-not-used
def sign(self, key, msg):
raise NotImplementedError() # pragma: no cover
def verify(self, key, msg, sig):
raise NotImplementedError() # pragma: no cover
# pylint: disable=invalid-name
self.Sig1 = MockSig('Sig1')
self.Sig2 = MockSig('Sig2')
def test_eq(self):
self.assertEqual(self.Sig1, self.Sig1)
def test_ne(self):
self.assertNotEqual(self.Sig1, self.Sig2)
def test_ne_other_type(self):
self.assertNotEqual(self.Sig1, 5)
def test_repr(self):
self.assertEqual('Sig1', repr(self.Sig1))
self.assertEqual('Sig2', repr(self.Sig2))
def test_to_partial_json(self):
self.assertEqual(self.Sig1.to_partial_json(), 'Sig1')
self.assertEqual(self.Sig2.to_partial_json(), 'Sig2')
def test_from_json(self):
from acme.jose.jwa import JWASignature
from acme.jose.jwa import RS256
self.assertTrue(JWASignature.from_json('RS256') is RS256)
class JWAHSTest(unittest.TestCase): # pylint: disable=too-few-public-methods
def test_it(self):
from acme.jose.jwa import HS256
sig = (
b"\xceR\xea\xcd\x94\xab\xcf\xfb\xe0\xacA.:\x1a'\x08i\xe2\xc4"
b"\r\x85+\x0e\x85\xaeUZ\xd4\xb3\x97zO"
)
self.assertEqual(HS256.sign(b'some key', b'foo'), sig)
self.assertTrue(HS256.verify(b'some key', b'foo', sig) is True)
self.assertTrue(HS256.verify(b'some key', b'foo', sig + b'!') is False)
class JWARSTest(unittest.TestCase):
def test_sign_no_private_part(self):
from acme.jose.jwa import RS256
self.assertRaises(
errors.Error, RS256.sign, RSA512_KEY.public_key(), b'foo')
def test_sign_key_too_small(self):
from acme.jose.jwa import RS256
from acme.jose.jwa import PS256
self.assertRaises(errors.Error, RS256.sign, RSA256_KEY, b'foo')
self.assertRaises(errors.Error, PS256.sign, RSA256_KEY, b'foo')
def test_rs(self):
from acme.jose.jwa import RS256
sig = (
b'|\xc6\xb2\xa4\xab(\x87\x99\xfa*:\xea\xf8\xa0N&}\x9f\x0f\xc0O'
b'\xc6t\xa3\xe6\xfa\xbb"\x15Y\x80Y\xe0\x81\xb8\x88)\xba\x0c\x9c'
b'\xa4\x99\x1e\x19&\xd8\xc7\x99S\x97\xfc\x85\x0cOV\xe6\x07\x99'
b'\xd2\xb9.>}\xfd'
)
self.assertEqual(RS256.sign(RSA512_KEY, b'foo'), sig)
self.assertTrue(RS256.verify(RSA512_KEY.public_key(), b'foo', sig))
self.assertFalse(RS256.verify(
RSA512_KEY.public_key(), b'foo', sig + b'!'))
def test_ps(self):
from acme.jose.jwa import PS256
sig = PS256.sign(RSA1024_KEY, b'foo')
self.assertTrue(PS256.verify(RSA1024_KEY.public_key(), b'foo', sig))
self.assertFalse(PS256.verify(
RSA1024_KEY.public_key(), b'foo', sig + b'!'))
if __name__ == '__main__':
unittest.main() # pragma: no cover

248
acme/acme/jose/jwk.py Normal file
View file

@ -0,0 +1,248 @@
"""JSON Web Key."""
import abc
import binascii
import logging
import cryptography.exceptions
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
import six
from acme.jose import errors
from acme.jose import json_util
from acme.jose import util
logger = logging.getLogger(__name__)
class JWK(json_util.TypedJSONObjectWithFields):
# pylint: disable=too-few-public-methods
"""JSON Web Key."""
type_field_name = 'kty'
TYPES = {}
cryptography_key_types = ()
"""Subclasses should override."""
@abc.abstractmethod
def public_key(self): # pragma: no cover
"""Generate JWK with public key.
For symmetric cryptosystems, this would return ``self``.
"""
raise NotImplementedError()
@classmethod
def _load_cryptography_key(cls, data, password=None, backend=None):
backend = default_backend() if backend is None else backend
exceptions = {}
# private key?
for loader in (serialization.load_pem_private_key,
serialization.load_der_private_key):
try:
return loader(data, password, backend)
except (ValueError, TypeError,
cryptography.exceptions.UnsupportedAlgorithm) as error:
exceptions[loader] = error
# public key?
for loader in (serialization.load_pem_public_key,
serialization.load_der_public_key):
try:
return loader(data, backend)
except (ValueError,
cryptography.exceptions.UnsupportedAlgorithm) as error:
exceptions[loader] = error
# no luck
raise errors.Error("Unable to deserialize key: {0}".format(exceptions))
@classmethod
def load(cls, data, password=None, backend=None):
"""Load serialized key as JWK.
:param str data: Public or private key serialized as PEM or DER.
:param str password: Optional password.
:param backend: A `.PEMSerializationBackend` and
`.DERSerializationBackend` provider.
:raises errors.Error: if unable to deserialize, or unsupported
JWK algorithm
:returns: JWK of an appropriate type.
:rtype: `JWK`
"""
try:
key = cls._load_cryptography_key(data, password, backend)
except errors.Error as error:
logger.debug("Loading symmetric key, assymentric failed: %s", error)
return JWKOct(key=data)
if cls.typ is not NotImplemented and not isinstance(
key, cls.cryptography_key_types):
raise errors.Error("Unable to deserialize {0} into {1}".format(
key.__class__, cls.__class__))
for jwk_cls in six.itervalues(cls.TYPES):
if isinstance(key, jwk_cls.cryptography_key_types):
return jwk_cls(key=key)
raise errors.Error("Unsupported algorithm: {0}".format(key.__class__))
@JWK.register
class JWKES(JWK): # pragma: no cover
# pylint: disable=abstract-class-not-used
"""ES JWK.
.. warning:: This is not yet implemented!
"""
typ = 'ES'
cryptography_key_types = (
ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)
def fields_to_partial_json(self):
raise NotImplementedError()
@classmethod
def fields_from_json(cls, jobj):
raise NotImplementedError()
def public_key(self):
raise NotImplementedError()
@JWK.register
class JWKOct(JWK):
"""Symmetric JWK."""
typ = 'oct'
__slots__ = ('key',)
def fields_to_partial_json(self):
# TODO: An "alg" member SHOULD also be present to identify the
# algorithm intended to be used with the key, unless the
# application uses another means or convention to determine
# the algorithm used.
return {'k': json_util.encode_b64jose(self.key)}
@classmethod
def fields_from_json(cls, jobj):
return cls(key=json_util.decode_b64jose(jobj['k']))
def public_key(self):
return self
@JWK.register
class JWKRSA(JWK):
"""RSA JWK.
:ivar key: `cryptography.hazmat.primitives.rsa.RSAPrivateKey`
or `cryptography.hazmat.primitives.rsa.RSAPublicKey` wrapped
in `.ComparableRSAKey`
"""
typ = 'RSA'
cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey)
__slots__ = ('key',)
def __init__(self, *args, **kwargs):
if 'key' in kwargs and not isinstance(
kwargs['key'], util.ComparableRSAKey):
kwargs['key'] = util.ComparableRSAKey(kwargs['key'])
super(JWKRSA, self).__init__(*args, **kwargs)
@classmethod
def _encode_param(cls, data):
"""Encode Base64urlUInt.
:type data: long
:rtype: unicode
"""
def _leading_zeros(arg):
if len(arg) % 2:
return '0' + arg
return arg
return json_util.encode_b64jose(binascii.unhexlify(
_leading_zeros(hex(data)[2:].rstrip('L'))))
@classmethod
def _decode_param(cls, data):
"""Decode Base64urlUInt."""
try:
return int(binascii.hexlify(json_util.decode_b64jose(data)), 16)
except ValueError: # invalid literal for long() with base 16
raise errors.DeserializationError()
def public_key(self):
return type(self)(key=self.key.public_key())
@classmethod
def fields_from_json(cls, jobj):
# pylint: disable=invalid-name
n, e = (cls._decode_param(jobj[x]) for x in ('n', 'e'))
public_numbers = rsa.RSAPublicNumbers(e=e, n=n)
if 'd' not in jobj: # public key
key = public_numbers.public_key(default_backend())
else: # private key
d = cls._decode_param(jobj['d'])
if ('p' in jobj or 'q' in jobj or 'dp' in jobj or
'dq' in jobj or 'qi' in jobj or 'oth' in jobj):
# "If the producer includes any of the other private
# key parameters, then all of the others MUST be
# present, with the exception of "oth", which MUST
# only be present when more than two prime factors
# were used."
p, q, dp, dq, qi, = all_params = tuple(
jobj.get(x) for x in ('p', 'q', 'dp', 'dq', 'qi'))
if tuple(param for param in all_params if param is None):
raise errors.Error(
"Some private parameters are missing: {0}".format(
all_params))
p, q, dp, dq, qi = tuple(
cls._decode_param(x) for x in all_params)
# TODO: check for oth
else:
# cryptography>=0.8
p, q = rsa.rsa_recover_prime_factors(n, e, d)
dp = rsa.rsa_crt_dmp1(d, p)
dq = rsa.rsa_crt_dmq1(d, q)
qi = rsa.rsa_crt_iqmp(p, q)
key = rsa.RSAPrivateNumbers(
p, q, d, dp, dq, qi, public_numbers).private_key(
default_backend())
return cls(key=key)
def fields_to_partial_json(self):
# pylint: disable=protected-access
if isinstance(self.key._wrapped, rsa.RSAPublicKey):
numbers = self.key.public_numbers()
params = {
'n': numbers.n,
'e': numbers.e,
}
else: # rsa.RSAPrivateKey
private = self.key.private_numbers()
public = self.key.public_key().public_numbers()
params = {
'n': public.n,
'e': public.e,
'd': private.d,
'p': private.p,
'q': private.q,
'dp': private.dmp1,
'dq': private.dmq1,
'qi': private.iqmp,
}
return dict((key, self._encode_param(value))
for key, value in six.iteritems(params))

154
acme/acme/jose/jwk_test.py Normal file
View file

@ -0,0 +1,154 @@
"""Tests for acme.jose.jwk."""
import unittest
from acme import test_util
from acme.jose import errors
from acme.jose import json_util
from acme.jose import util
DSA_PEM = test_util.load_vector('dsa512_key.pem')
RSA256_KEY = test_util.load_rsa_private_key('rsa256_key.pem')
RSA512_KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class JWKTest(unittest.TestCase):
"""Tests for acme.jose.jwk.JWK."""
def test_load(self):
from acme.jose.jwk import JWK
self.assertRaises(errors.Error, JWK.load, DSA_PEM)
def test_load_subclass_wrong_type(self):
from acme.jose.jwk import JWKRSA
self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM)
class JWKOctTest(unittest.TestCase):
"""Tests for acme.jose.jwk.JWKOct."""
def setUp(self):
from acme.jose.jwk import JWKOct
self.jwk = JWKOct(key=b'foo')
self.jobj = {'kty': 'oct', 'k': json_util.encode_b64jose(b'foo')}
def test_to_partial_json(self):
self.assertEqual(self.jwk.to_partial_json(), self.jobj)
def test_from_json(self):
from acme.jose.jwk import JWKOct
self.assertEqual(self.jwk, JWKOct.from_json(self.jobj))
def test_from_json_hashable(self):
from acme.jose.jwk import JWKOct
hash(JWKOct.from_json(self.jobj))
def test_load(self):
from acme.jose.jwk import JWKOct
self.assertEqual(self.jwk, JWKOct.load(b'foo'))
def test_public_key(self):
self.assertTrue(self.jwk.public_key() is self.jwk)
class JWKRSATest(unittest.TestCase):
"""Tests for acme.jose.jwk.JWKRSA."""
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.jose.jwk import JWKRSA
self.jwk256 = JWKRSA(key=RSA256_KEY.public_key())
self.jwk256json = {
'kty': 'RSA',
'e': 'AQAB',
'n': 'm2Fylv-Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEk',
}
# pylint: disable=protected-access
self.jwk256_not_comparable = JWKRSA(
key=RSA256_KEY.public_key()._wrapped)
self.jwk512 = JWKRSA(key=RSA512_KEY.public_key())
self.jwk512json = {
'kty': 'RSA',
'e': 'AQAB',
'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5'
'80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q',
}
self.private = JWKRSA(key=RSA256_KEY)
self.private_json_small = self.jwk256json.copy()
self.private_json_small['d'] = (
'lPQED_EPTV0UIBfNI3KP2d9Jlrc2mrMllmf946bu-CE')
self.private_json = self.jwk256json.copy()
self.private_json.update({
'd': 'lPQED_EPTV0UIBfNI3KP2d9Jlrc2mrMllmf946bu-CE',
'p': 'zUVNZn4lLLBD1R6NE8TKNQ',
'q': 'wcfKfc7kl5jfqXArCRSURQ',
'dp': 'CWJFq43QvT5Bm5iN8n1okQ',
'dq': 'bHh2u7etM8LKKCF2pY2UdQ',
'qi': 'oi45cEkbVoJjAbnQpFY87Q',
})
def test_init_auto_comparable(self):
self.assertTrue(isinstance(
self.jwk256_not_comparable.key, util.ComparableRSAKey))
self.assertEqual(self.jwk256, self.jwk256_not_comparable)
def test_encode_param_zero(self):
from acme.jose.jwk import JWKRSA
# pylint: disable=protected-access
# TODO: move encode/decode _param to separate class
self.assertEqual('AA', JWKRSA._encode_param(0))
def test_equals(self):
self.assertEqual(self.jwk256, self.jwk256)
self.assertEqual(self.jwk512, self.jwk512)
def test_not_equals(self):
self.assertNotEqual(self.jwk256, self.jwk512)
self.assertNotEqual(self.jwk512, self.jwk256)
def test_load(self):
from acme.jose.jwk import JWKRSA
self.assertEqual(self.private, JWKRSA.load(
test_util.load_vector('rsa256_key.pem')))
def test_public_key(self):
self.assertEqual(self.jwk256, self.private.public_key())
def test_to_partial_json(self):
self.assertEqual(self.jwk256.to_partial_json(), self.jwk256json)
self.assertEqual(self.jwk512.to_partial_json(), self.jwk512json)
self.assertEqual(self.private.to_partial_json(), self.private_json)
def test_from_json(self):
from acme.jose.jwk import JWK
self.assertEqual(
self.jwk256, JWK.from_json(self.jwk256json))
self.assertEqual(
self.jwk512, JWK.from_json(self.jwk512json))
self.assertEqual(self.private, JWK.from_json(self.private_json))
def test_from_json_private_small(self):
from acme.jose.jwk import JWK
self.assertEqual(self.private, JWK.from_json(self.private_json_small))
def test_from_json_missing_one_additional(self):
from acme.jose.jwk import JWK
del self.private_json['q']
self.assertRaises(errors.Error, JWK.from_json, self.private_json)
def test_from_json_hashable(self):
from acme.jose.jwk import JWK
hash(JWK.from_json(self.jwk256json))
def test_from_json_non_schema_errors(self):
# valid against schema, but still failing
from acme.jose.jwk import JWK
self.assertRaises(errors.DeserializationError, JWK.from_json,
{'kty': 'RSA', 'e': 'AQAB', 'n': ''})
self.assertRaises(errors.DeserializationError, JWK.from_json,
{'kty': 'RSA', 'e': 'AQAB', 'n': '1'})
if __name__ == '__main__':
unittest.main() # pragma: no cover

433
acme/acme/jose/jws.py Normal file
View file

@ -0,0 +1,433 @@
"""JOSE Web Signature."""
import argparse
import base64
import sys
import OpenSSL
import six
from acme.jose import b64
from acme.jose import errors
from acme.jose import json_util
from acme.jose import jwa
from acme.jose import jwk
from acme.jose import util
class MediaType(object):
"""MediaType field encoder/decoder."""
PREFIX = 'application/'
"""MIME Media Type and Content Type prefix."""
@classmethod
def decode(cls, value):
"""Decoder."""
# 4.1.10
if '/' not in value:
if ';' in value:
raise errors.DeserializationError('Unexpected semi-colon')
return cls.PREFIX + value
return value
@classmethod
def encode(cls, value):
"""Encoder."""
# 4.1.10
if ';' not in value:
assert value.startswith(cls.PREFIX)
return value[len(cls.PREFIX):]
return value
class Header(json_util.JSONObjectWithFields):
"""JOSE Header.
.. warning:: This class supports **only** Registered Header
Parameter Names (as defined in section 4.1 of the
protocol). If you need Public Header Parameter Names (4.2)
or Private Header Parameter Names (4.3), you must subclass
and override :meth:`from_json` and :meth:`to_partial_json`
appropriately.
.. warning:: This class does not support any extensions through
the "crit" (Critical) Header Parameter (4.1.11) and as a
conforming implementation, :meth:`from_json` treats its
occurence as an error. Please subclass if you seek for
a diferent behaviour.
:ivar x5tS256: "x5t#S256"
:ivar str typ: MIME Media Type, inc. :const:`MediaType.PREFIX`.
:ivar str cty: Content-Type, inc. :const:`MediaType.PREFIX`.
"""
alg = json_util.Field(
'alg', decoder=jwa.JWASignature.from_json, omitempty=True)
jku = json_util.Field('jku', omitempty=True)
jwk = json_util.Field('jwk', decoder=jwk.JWK.from_json, omitempty=True)
kid = json_util.Field('kid', omitempty=True)
x5u = json_util.Field('x5u', omitempty=True)
x5c = json_util.Field('x5c', omitempty=True, default=())
x5t = json_util.Field(
'x5t', decoder=json_util.decode_b64jose, omitempty=True)
x5tS256 = json_util.Field(
'x5t#S256', decoder=json_util.decode_b64jose, omitempty=True)
typ = json_util.Field('typ', encoder=MediaType.encode,
decoder=MediaType.decode, omitempty=True)
cty = json_util.Field('cty', encoder=MediaType.encode,
decoder=MediaType.decode, omitempty=True)
crit = json_util.Field('crit', omitempty=True, default=())
def not_omitted(self):
"""Fields that would not be omitted in the JSON object."""
return dict((name, getattr(self, name))
for name, field in six.iteritems(self._fields)
if not field.omit(getattr(self, name)))
def __add__(self, other):
if not isinstance(other, type(self)):
raise TypeError('Header cannot be added to: {0}'.format(
type(other)))
not_omitted_self = self.not_omitted()
not_omitted_other = other.not_omitted()
if set(not_omitted_self).intersection(not_omitted_other):
raise TypeError('Addition of overlapping headers not defined')
not_omitted_self.update(not_omitted_other)
return type(self)(**not_omitted_self) # pylint: disable=star-args
def find_key(self):
"""Find key based on header.
.. todo:: Supports only "jwk" header parameter lookup.
:returns: (Public) key found in the header.
:rtype: :class:`acme.jose.jwk.JWK`
:raises acme.jose.errors.Error: if key could not be found
"""
if self.jwk is None:
raise errors.Error('No key found')
return self.jwk
@crit.decoder
def crit(unused_value):
# pylint: disable=missing-docstring,no-self-argument,no-self-use
raise errors.DeserializationError(
'"crit" is not supported, please subclass')
# x5c does NOT use JOSE Base64 (4.1.6)
@x5c.encoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
return [base64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert)) for cert in value]
@x5c.decoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
try:
return tuple(util.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1,
base64.b64decode(cert))) for cert in value)
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
class Signature(json_util.JSONObjectWithFields):
"""JWS Signature.
:ivar combined: Combined Header (protected and unprotected,
:class:`Header`).
:ivar unicode protected: JWS protected header (Jose Base-64 decoded).
:ivar header: JWS Unprotected Header (:class:`Header`).
:ivar str signature: The signature.
"""
header_cls = Header
__slots__ = ('combined',)
protected = json_util.Field('protected', omitempty=True, default='')
header = json_util.Field(
'header', omitempty=True, default=header_cls(),
decoder=header_cls.from_json)
signature = json_util.Field(
'signature', decoder=json_util.decode_b64jose,
encoder=json_util.encode_b64jose)
@protected.encoder
def protected(value): # pylint: disable=missing-docstring,no-self-argument
# wrong type guess (Signature, not bytes) | pylint: disable=no-member
return json_util.encode_b64jose(value.encode('utf-8'))
@protected.decoder
def protected(value): # pylint: disable=missing-docstring,no-self-argument
return json_util.decode_b64jose(value).decode('utf-8')
def __init__(self, **kwargs):
if 'combined' not in kwargs:
kwargs = self._with_combined(kwargs)
super(Signature, self).__init__(**kwargs)
assert self.combined.alg is not None
@classmethod
def _with_combined(cls, kwargs):
assert 'combined' not in kwargs
header = kwargs.get('header', cls._fields['header'].default)
protected = kwargs.get('protected', cls._fields['protected'].default)
if protected:
combined = header + cls.header_cls.json_loads(protected)
else:
combined = header
kwargs['combined'] = combined
return kwargs
@classmethod
def _msg(cls, protected, payload):
return (b64.b64encode(protected.encode('utf-8')) + b'.' +
b64.b64encode(payload))
def verify(self, payload, key=None):
"""Verify.
:param key: Key used for verification.
:type key: :class:`acme.jose.jwk.JWK`
"""
key = self.combined.find_key() if key is None else key
return self.combined.alg.verify(
key=key.key, sig=self.signature,
msg=self._msg(self.protected, payload))
@classmethod
def sign(cls, payload, key, alg, include_jwk=True,
protect=frozenset(), **kwargs):
"""Sign.
:param key: Key for signature.
:type key: :class:`acme.jose.jwk.JWK`
"""
assert isinstance(key, alg.kty)
header_params = kwargs
header_params['alg'] = alg
if include_jwk:
header_params['jwk'] = key.public_key()
assert set(header_params).issubset(cls.header_cls._fields)
assert protect.issubset(cls.header_cls._fields)
protected_params = {}
for header in protect:
protected_params[header] = header_params.pop(header)
if protected_params:
# pylint: disable=star-args
protected = cls.header_cls(**protected_params).json_dumps()
else:
protected = ''
header = cls.header_cls(**header_params) # pylint: disable=star-args
signature = alg.sign(key.key, cls._msg(protected, payload))
return cls(protected=protected, header=header, signature=signature)
def fields_to_partial_json(self):
fields = super(Signature, self).fields_to_partial_json()
if not fields['header'].not_omitted():
del fields['header']
return fields
@classmethod
def fields_from_json(cls, jobj):
fields = super(Signature, cls).fields_from_json(jobj)
fields_with_combined = cls._with_combined(fields)
if 'alg' not in fields_with_combined['combined'].not_omitted():
raise errors.DeserializationError('alg not present')
return fields_with_combined
class JWS(json_util.JSONObjectWithFields):
"""JSON Web Signature.
:ivar str payload: JWS Payload.
:ivar str signature: JWS Signatures.
"""
__slots__ = ('payload', 'signatures')
signature_cls = Signature
def verify(self, key=None):
"""Verify."""
return all(sig.verify(self.payload, key) for sig in self.signatures)
@classmethod
def sign(cls, payload, **kwargs):
"""Sign."""
return cls(payload=payload, signatures=(
cls.signature_cls.sign(payload=payload, **kwargs),))
@property
def signature(self):
"""Get a singleton signature.
:rtype: `signature_cls`
"""
assert len(self.signatures) == 1
return self.signatures[0]
def to_compact(self):
"""Compact serialization.
:rtype: bytes
"""
assert len(self.signatures) == 1
assert 'alg' not in self.signature.header.not_omitted()
# ... it must be in protected
return (
b64.b64encode(self.signature.protected.encode('utf-8'))
+ b'.' +
b64.b64encode(self.payload)
+ b'.' +
b64.b64encode(self.signature.signature))
@classmethod
def from_compact(cls, compact):
"""Compact deserialization.
:param bytes compact:
"""
try:
protected, payload, signature = compact.split(b'.')
except ValueError:
raise errors.DeserializationError(
'Compact JWS serialization should comprise of exactly'
' 3 dot-separated components')
sig = cls.signature_cls(
protected=b64.b64decode(protected).decode('utf-8'),
signature=b64.b64decode(signature))
return cls(payload=b64.b64decode(payload), signatures=(sig,))
def to_partial_json(self, flat=True): # pylint: disable=arguments-differ
assert self.signatures
payload = json_util.encode_b64jose(self.payload)
if flat and len(self.signatures) == 1:
ret = self.signatures[0].to_partial_json()
ret['payload'] = payload
return ret
else:
return {
'payload': payload,
'signatures': self.signatures,
}
@classmethod
def from_json(cls, jobj):
if 'signature' in jobj and 'signatures' in jobj:
raise errors.DeserializationError('Flat mixed with non-flat')
elif 'signature' in jobj: # flat
return cls(payload=json_util.decode_b64jose(jobj.pop('payload')),
signatures=(cls.signature_cls.from_json(jobj),))
else:
return cls(payload=json_util.decode_b64jose(jobj['payload']),
signatures=tuple(cls.signature_cls.from_json(sig)
for sig in jobj['signatures']))
class CLI(object):
"""JWS CLI."""
@classmethod
def sign(cls, args):
"""Sign."""
key = args.alg.kty.load(args.key.read())
args.key.close()
if args.protect is None:
args.protect = []
if args.compact:
args.protect.append('alg')
sig = JWS.sign(payload=sys.stdin.read().encode(), key=key, alg=args.alg,
protect=set(args.protect))
if args.compact:
six.print_(sig.to_compact().decode('utf-8'))
else: # JSON
six.print_(sig.json_dumps_pretty())
@classmethod
def verify(cls, args):
"""Verify."""
if args.compact:
sig = JWS.from_compact(sys.stdin.read().encode())
else: # JSON
try:
sig = JWS.json_loads(sys.stdin.read())
except errors.Error as error:
six.print_(error)
return -1
if args.key is not None:
assert args.kty is not None
key = args.kty.load(args.key.read()).public_key()
args.key.close()
else:
key = None
sys.stdout.write(sig.payload)
return not sig.verify(key=key)
@classmethod
def _alg_type(cls, arg):
return jwa.JWASignature.from_json(arg)
@classmethod
def _header_type(cls, arg):
assert arg in Signature.header_cls._fields
return arg
@classmethod
def _kty_type(cls, arg):
assert arg in jwk.JWK.TYPES
return jwk.JWK.TYPES[arg]
@classmethod
def run(cls, args=sys.argv[1:]):
"""Parse arguments and sign/verify."""
parser = argparse.ArgumentParser()
parser.add_argument('--compact', action='store_true')
subparsers = parser.add_subparsers()
parser_sign = subparsers.add_parser('sign')
parser_sign.set_defaults(func=cls.sign)
parser_sign.add_argument(
'-k', '--key', type=argparse.FileType('rb'), required=True)
parser_sign.add_argument(
'-a', '--alg', type=cls._alg_type, default=jwa.RS256)
parser_sign.add_argument(
'-p', '--protect', action='append', type=cls._header_type)
parser_verify = subparsers.add_parser('verify')
parser_verify.set_defaults(func=cls.verify)
parser_verify.add_argument(
'-k', '--key', type=argparse.FileType('rb'), required=False)
parser_verify.add_argument(
'--kty', type=cls._kty_type, required=False)
parsed = parser.parse_args(args)
return parsed.func(parsed)
if __name__ == '__main__':
exit(CLI.run()) # pragma: no cover

240
acme/acme/jose/jws_test.py Normal file
View file

@ -0,0 +1,240 @@
"""Tests for acme.jose.jws."""
import base64
import unittest
import mock
import OpenSSL
from acme import test_util
from acme.jose import errors
from acme.jose import json_util
from acme.jose import jwa
from acme.jose import jwk
CERT = test_util.load_cert('cert.pem')
KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
class MediaTypeTest(unittest.TestCase):
"""Tests for acme.jose.jws.MediaType."""
def test_decode(self):
from acme.jose.jws import MediaType
self.assertEqual('application/app', MediaType.decode('application/app'))
self.assertEqual('application/app', MediaType.decode('app'))
self.assertRaises(
errors.DeserializationError, MediaType.decode, 'app;foo')
def test_encode(self):
from acme.jose.jws import MediaType
self.assertEqual('app', MediaType.encode('application/app'))
self.assertEqual('application/app;foo',
MediaType.encode('application/app;foo'))
class HeaderTest(unittest.TestCase):
"""Tests for acme.jose.jws.Header."""
def setUp(self):
from acme.jose.jws import Header
self.header1 = Header(jwk='foo')
self.header2 = Header(jwk='bar')
self.crit = Header(crit=('a', 'b'))
self.empty = Header()
def test_add_non_empty(self):
from acme.jose.jws import Header
self.assertEqual(Header(jwk='foo', crit=('a', 'b')),
self.header1 + self.crit)
def test_add_empty(self):
self.assertEqual(self.header1, self.header1 + self.empty)
self.assertEqual(self.header1, self.empty + self.header1)
def test_add_overlapping_error(self):
self.assertRaises(TypeError, self.header1.__add__, self.header2)
def test_add_wrong_type_error(self):
self.assertRaises(TypeError, self.header1.__add__, 'xxx')
def test_crit_decode_always_errors(self):
from acme.jose.jws import Header
self.assertRaises(errors.DeserializationError, Header.from_json,
{'crit': ['a', 'b']})
def test_x5c_decoding(self):
from acme.jose.jws import Header
header = Header(x5c=(CERT, CERT))
jobj = header.to_partial_json()
cert_b64 = base64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT))
self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
self.assertEqual(header, Header.from_json(jobj))
jobj['x5c'][0] = base64.b64encode(
b'xxx' + OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT))
self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
def test_find_key(self):
self.assertEqual('foo', self.header1.find_key())
self.assertEqual('bar', self.header2.find_key())
self.assertRaises(errors.Error, self.crit.find_key)
class SignatureTest(unittest.TestCase):
"""Tests for acme.jose.jws.Signature."""
def test_from_json(self):
from acme.jose.jws import Header
from acme.jose.jws import Signature
self.assertEqual(
Signature(signature=b'foo', header=Header(alg=jwa.RS256)),
Signature.from_json(
{'signature': 'Zm9v', 'header': {'alg': 'RS256'}}))
def test_from_json_no_alg_error(self):
from acme.jose.jws import Signature
self.assertRaises(errors.DeserializationError,
Signature.from_json, {'signature': 'foo'})
class JWSTest(unittest.TestCase):
"""Tests for acme.jose.jws.JWS."""
def setUp(self):
self.privkey = KEY
self.pubkey = self.privkey.public_key()
from acme.jose.jws import JWS
self.unprotected = JWS.sign(
payload=b'foo', key=self.privkey, alg=jwa.RS256)
self.protected = JWS.sign(
payload=b'foo', key=self.privkey, alg=jwa.RS256,
protect=frozenset(['jwk', 'alg']))
self.mixed = JWS.sign(
payload=b'foo', key=self.privkey, alg=jwa.RS256,
protect=frozenset(['alg']))
def test_pubkey_jwk(self):
self.assertEqual(self.unprotected.signature.combined.jwk, self.pubkey)
self.assertEqual(self.protected.signature.combined.jwk, self.pubkey)
self.assertEqual(self.mixed.signature.combined.jwk, self.pubkey)
def test_sign_unprotected(self):
self.assertTrue(self.unprotected.verify())
def test_sign_protected(self):
self.assertTrue(self.protected.verify())
def test_sign_mixed(self):
self.assertTrue(self.mixed.verify())
def test_compact_lost_unprotected(self):
compact = self.mixed.to_compact()
self.assertEqual(
b'eyJhbGciOiAiUlMyNTYifQ.Zm9v.OHdxFVj73l5LpxbFp1AmYX4yJM0Pyb'
b'_893n1zQjpim_eLS5J1F61lkvrCrCDErTEJnBGOGesJ72M7b6Ve1cAJA',
compact)
from acme.jose.jws import JWS
mixed = JWS.from_compact(compact)
self.assertNotEqual(self.mixed, mixed)
self.assertEqual(
set(['alg']), set(mixed.signature.combined.not_omitted()))
def test_from_compact_missing_components(self):
from acme.jose.jws import JWS
self.assertRaises(errors.DeserializationError, JWS.from_compact, b'.')
def test_json_omitempty(self):
protected_jobj = self.protected.to_partial_json(flat=True)
unprotected_jobj = self.unprotected.to_partial_json(flat=True)
self.assertTrue('protected' not in unprotected_jobj)
self.assertTrue('header' not in protected_jobj)
unprotected_jobj['header'] = unprotected_jobj['header'].to_json()
from acme.jose.jws import JWS
self.assertEqual(JWS.from_json(protected_jobj), self.protected)
self.assertEqual(JWS.from_json(unprotected_jobj), self.unprotected)
def test_json_flat(self):
jobj_to = {
'signature': json_util.encode_b64jose(
self.mixed.signature.signature),
'payload': json_util.encode_b64jose(b'foo'),
'header': self.mixed.signature.header,
'protected': json_util.encode_b64jose(
self.mixed.signature.protected.encode('utf-8')),
}
jobj_from = jobj_to.copy()
jobj_from['header'] = jobj_from['header'].to_json()
self.assertEqual(self.mixed.to_partial_json(flat=True), jobj_to)
from acme.jose.jws import JWS
self.assertEqual(self.mixed, JWS.from_json(jobj_from))
def test_json_not_flat(self):
jobj_to = {
'signatures': (self.mixed.signature,),
'payload': json_util.encode_b64jose(b'foo'),
}
jobj_from = jobj_to.copy()
jobj_from['signatures'] = [jobj_to['signatures'][0].to_json()]
self.assertEqual(self.mixed.to_partial_json(flat=False), jobj_to)
from acme.jose.jws import JWS
self.assertEqual(self.mixed, JWS.from_json(jobj_from))
def test_from_json_mixed_flat(self):
from acme.jose.jws import JWS
self.assertRaises(errors.DeserializationError, JWS.from_json,
{'signatures': (), 'signature': 'foo'})
def test_from_json_hashable(self):
from acme.jose.jws import JWS
hash(JWS.from_json(self.mixed.to_json()))
class CLITest(unittest.TestCase):
def setUp(self):
self.key_path = test_util.vector_path('rsa512_key.pem')
def test_unverified(self):
from acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin:
sin.read.return_value = '{"payload": "foo", "signature": "xxx"}'
with mock.patch('sys.stdout'):
self.assertEqual(-1, CLI.run(['verify']))
def test_json(self):
from acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin:
sin.read.return_value = 'foo'
with mock.patch('sys.stdout') as sout:
CLI.run(['sign', '-k', self.key_path, '-a', 'RS256',
'-p', 'jwk'])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run(['verify']))
def test_compact(self):
from acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin:
sin.read.return_value = 'foo'
with mock.patch('sys.stdout') as sout:
CLI.run(['--compact', 'sign', '-k', self.key_path])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run([
'--compact', 'verify', '--kty', 'RSA',
'-k', self.key_path]))
if __name__ == '__main__':
unittest.main() # pragma: no cover

217
acme/acme/jose/util.py Normal file
View file

@ -0,0 +1,217 @@
"""JOSE utilities."""
import collections
from cryptography.hazmat.primitives.asymmetric import rsa
import OpenSSL
import six
class abstractclassmethod(classmethod):
# pylint: disable=invalid-name,too-few-public-methods
"""Descriptor for an abstract classmethod.
It augments the :mod:`abc` framework with an abstract
classmethod. This is implemented as :class:`abc.abstractclassmethod`
in the standard Python library starting with version 3.2.
This particular implementation, allegedly based on Python 3.3 source
code, is stolen from
http://stackoverflow.com/questions/11217878/python-2-7-combine-abc-abstractmethod-and-classmethod.
"""
__isabstractmethod__ = True
def __init__(self, target):
target.__isabstractmethod__ = True
super(abstractclassmethod, self).__init__(target)
class ComparableX509(object): # pylint: disable=too-few-public-methods
"""Wrapper for OpenSSL.crypto.X509** objects that supports __eq__.
Wraps around:
- :class:`OpenSSL.crypto.X509`
- :class:`OpenSSL.crypto.X509Req`
"""
def __init__(self, wrapped):
assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance(
wrapped, OpenSSL.crypto.X509Req)
self._wrapped = wrapped
def __getattr__(self, name):
return getattr(self._wrapped, name)
def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1):
# pylint: disable=missing-docstring,protected-access
if isinstance(self._wrapped, OpenSSL.crypto.X509):
func = OpenSSL.crypto.dump_certificate
else: # assert in __init__ makes sure this is X509Req
func = OpenSSL.crypto.dump_certificate_request
return func(filetype, self._wrapped)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self._dump() == other._dump() # pylint: disable=protected-access
def __hash__(self):
return hash((self.__class__, self._dump()))
def __ne__(self, other):
return not self == other
def __repr__(self):
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
class ComparableKey(object): # pylint: disable=too-few-public-methods
"""Comparable wrapper for `cryptography` keys.
See https://github.com/pyca/cryptography/issues/2122.
"""
__hash__ = NotImplemented
def __init__(self, wrapped):
self._wrapped = wrapped
def __getattr__(self, name):
return getattr(self._wrapped, name)
def __eq__(self, other):
# pylint: disable=protected-access
if (not isinstance(other, self.__class__) or
self._wrapped.__class__ is not other._wrapped.__class__):
return NotImplemented
elif hasattr(self._wrapped, 'private_numbers'):
return self.private_numbers() == other.private_numbers()
elif hasattr(self._wrapped, 'public_numbers'):
return self.public_numbers() == other.public_numbers()
else:
return NotImplemented
def __ne__(self, other):
return not self == other
def __repr__(self):
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
def public_key(self):
"""Get wrapped public key."""
return self.__class__(self._wrapped.public_key())
class ComparableRSAKey(ComparableKey): # pylint: disable=too-few-public-methods
"""Wrapper for `cryptography` RSA keys.
Wraps around:
- `cryptography.hazmat.primitives.assymetric.RSAPrivateKey`
- `cryptography.hazmat.primitives.assymetric.RSAPublicKey`
"""
def __hash__(self):
# public_numbers() hasn't got stable hash!
# https://github.com/pyca/cryptography/issues/2143
if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization):
priv = self.private_numbers()
pub = priv.public_numbers
return hash((self.__class__, priv.p, priv.q, priv.dmp1,
priv.dmq1, priv.iqmp, pub.n, pub.e))
elif isinstance(self._wrapped, rsa.RSAPublicKeyWithSerialization):
pub = self.public_numbers()
return hash((self.__class__, pub.n, pub.e))
class ImmutableMap(collections.Mapping, collections.Hashable):
# pylint: disable=too-few-public-methods
"""Immutable key to value mapping with attribute access."""
__slots__ = ()
"""Must be overriden in subclasses."""
def __init__(self, **kwargs):
if set(kwargs) != set(self.__slots__):
raise TypeError(
'__init__() takes exactly the following arguments: {0} '
'({1} given)'.format(', '.join(self.__slots__),
', '.join(kwargs) if kwargs else 'none'))
for slot in self.__slots__:
object.__setattr__(self, slot, kwargs.pop(slot))
def update(self, **kwargs):
"""Return updated map."""
items = dict(self)
items.update(kwargs)
return type(self)(**items) # pylint: disable=star-args
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
def __iter__(self):
return iter(self.__slots__)
def __len__(self):
return len(self.__slots__)
def __hash__(self):
return hash(tuple(getattr(self, slot) for slot in self.__slots__))
def __setattr__(self, name, value):
raise AttributeError("can't set attribute")
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, ', '.join(
'{0}={1!r}'.format(key, value)
for key, value in six.iteritems(self)))
class frozendict(collections.Mapping, collections.Hashable):
# pylint: disable=invalid-name,too-few-public-methods
"""Frozen dictionary."""
__slots__ = ('_items', '_keys')
def __init__(self, *args, **kwargs):
if kwargs and not args:
items = dict(kwargs)
elif len(args) == 1 and isinstance(args[0], collections.Mapping):
items = args[0]
else:
raise TypeError()
# TODO: support generators/iterators
object.__setattr__(self, '_items', items)
object.__setattr__(self, '_keys', tuple(sorted(six.iterkeys(items))))
def __getitem__(self, key):
return self._items[key]
def __iter__(self):
return iter(self._keys)
def __len__(self):
return len(self._items)
def _sorted_items(self):
return tuple((key, self[key]) for key in self._keys)
def __hash__(self):
return hash(self._sorted_items())
def __getattr__(self, name):
try:
return self._items[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
raise AttributeError("can't set attribute")
def __repr__(self):
return 'frozendict({0})'.format(', '.join('{0}={1!r}'.format(
key, value) for key, value in self._sorted_items()))

196
acme/acme/jose/util_test.py Normal file
View file

@ -0,0 +1,196 @@
"""Tests for acme.jose.util."""
import functools
import unittest
import six
from acme import test_util
class ComparableX509Test(unittest.TestCase):
"""Tests for acme.jose.util.ComparableX509."""
def setUp(self):
# test_util.load_{csr,cert} return ComparableX509
self.req1 = test_util.load_csr('csr.pem')
self.req2 = test_util.load_csr('csr.pem')
self.req_other = test_util.load_csr('csr-san.pem')
self.cert1 = test_util.load_cert('cert.pem')
self.cert2 = test_util.load_cert('cert.pem')
self.cert_other = test_util.load_cert('cert-san.pem')
def test_eq(self):
self.assertEqual(self.req1, self.req2)
self.assertEqual(self.cert1, self.cert2)
def test_ne(self):
self.assertNotEqual(self.req1, self.req_other)
self.assertNotEqual(self.cert1, self.cert_other)
def test_ne_wrong_types(self):
self.assertNotEqual(self.req1, 5)
self.assertNotEqual(self.cert1, 5)
def test_hash(self):
self.assertEqual(hash(self.req1), hash(self.req2))
self.assertNotEqual(hash(self.req1), hash(self.req_other))
self.assertEqual(hash(self.cert1), hash(self.cert2))
self.assertNotEqual(hash(self.cert1), hash(self.cert_other))
def test_repr(self):
for x509 in self.req1, self.cert1:
self.assertTrue(repr(x509).startswith(
'<ComparableX509(<OpenSSL.crypto.X509'))
class ComparableRSAKeyTest(unittest.TestCase):
"""Tests for acme.jose.util.ComparableRSAKey."""
def setUp(self):
# test_utl.load_rsa_private_key return ComparableRSAKey
self.key = test_util.load_rsa_private_key('rsa256_key.pem')
self.key_same = test_util.load_rsa_private_key('rsa256_key.pem')
self.key2 = test_util.load_rsa_private_key('rsa512_key.pem')
def test_getattr_proxy(self):
self.assertEqual(256, self.key.key_size)
def test_eq(self):
self.assertEqual(self.key, self.key_same)
def test_ne(self):
self.assertNotEqual(self.key, self.key2)
def test_ne_different_types(self):
self.assertNotEqual(self.key, 5)
def test_ne_not_wrapped(self):
# pylint: disable=protected-access
self.assertNotEqual(self.key, self.key_same._wrapped)
def test_ne_no_serialization(self):
from acme.jose.util import ComparableRSAKey
self.assertNotEqual(ComparableRSAKey(5), ComparableRSAKey(5))
def test_hash(self):
self.assertTrue(isinstance(hash(self.key), int))
self.assertEqual(hash(self.key), hash(self.key_same))
self.assertNotEqual(hash(self.key), hash(self.key2))
def test_repr(self):
self.assertTrue(repr(self.key).startswith(
'<ComparableRSAKey(<cryptography.hazmat.'))
def test_public_key(self):
from acme.jose.util import ComparableRSAKey
self.assertTrue(isinstance(self.key.public_key(), ComparableRSAKey))
class ImmutableMapTest(unittest.TestCase):
"""Tests for acme.jose.util.ImmutableMap."""
def setUp(self):
# pylint: disable=invalid-name,too-few-public-methods
# pylint: disable=missing-docstring
from acme.jose.util import ImmutableMap
class A(ImmutableMap):
__slots__ = ('x', 'y')
class B(ImmutableMap):
__slots__ = ('x', 'y')
self.A = A
self.B = B
self.a1 = self.A(x=1, y=2)
self.a1_swap = self.A(y=2, x=1)
self.a2 = self.A(x=3, y=4)
self.b = self.B(x=1, y=2)
def test_update(self):
self.assertEqual(self.A(x=2, y=2), self.a1.update(x=2))
self.assertEqual(self.a2, self.a1.update(x=3, y=4))
def test_get_missing_item_raises_key_error(self):
self.assertRaises(KeyError, self.a1.__getitem__, 'z')
def test_order_of_args_does_not_matter(self):
self.assertEqual(self.a1, self.a1_swap)
def test_type_error_on_missing(self):
self.assertRaises(TypeError, self.A, x=1)
self.assertRaises(TypeError, self.A, y=2)
def test_type_error_on_unrecognized(self):
self.assertRaises(TypeError, self.A, x=1, z=2)
self.assertRaises(TypeError, self.A, x=1, y=2, z=3)
def test_get_attr(self):
self.assertEqual(1, self.a1.x)
self.assertEqual(2, self.a1.y)
self.assertEqual(1, self.a1_swap.x)
self.assertEqual(2, self.a1_swap.y)
def test_set_attr_raises_attribute_error(self):
self.assertRaises(
AttributeError, functools.partial(self.a1.__setattr__, 'x'), 10)
def test_equal(self):
self.assertEqual(self.a1, self.a1)
self.assertEqual(self.a2, self.a2)
self.assertNotEqual(self.a1, self.a2)
def test_hash(self):
self.assertEqual(hash((1, 2)), hash(self.a1))
def test_unhashable(self):
self.assertRaises(TypeError, self.A(x=1, y={}).__hash__)
def test_repr(self):
self.assertEqual('A(x=1, y=2)', repr(self.a1))
self.assertEqual('A(x=1, y=2)', repr(self.a1_swap))
self.assertEqual('B(x=1, y=2)', repr(self.b))
self.assertEqual("B(x='foo', y='bar')", repr(self.B(x='foo', y='bar')))
class frozendictTest(unittest.TestCase): # pylint: disable=invalid-name
"""Tests for acme.jose.util.frozendict."""
def setUp(self):
from acme.jose.util import frozendict
self.fdict = frozendict(x=1, y='2')
def test_init_dict(self):
from acme.jose.util import frozendict
self.assertEqual(self.fdict, frozendict({'x': 1, 'y': '2'}))
def test_init_other_raises_type_error(self):
from acme.jose.util import frozendict
# specifically fail for generators...
self.assertRaises(TypeError, frozendict, six.iteritems({'a': 'b'}))
def test_len(self):
self.assertEqual(2, len(self.fdict))
def test_hash(self):
self.assertTrue(isinstance(hash(self.fdict), int))
def test_getattr_proxy(self):
self.assertEqual(1, self.fdict.x)
self.assertEqual('2', self.fdict.y)
def test_getattr_raises_attribute_error(self):
self.assertRaises(AttributeError, self.fdict.__getattr__, 'z')
def test_setattr_immutable(self):
self.assertRaises(AttributeError, self.fdict.__setattr__, 'z', 3)
def test_repr(self):
self.assertEqual("frozendict(x=1, y='2')", repr(self.fdict))
if __name__ == '__main__':
unittest.main() # pragma: no cover

44
acme/acme/jws.py Normal file
View file

@ -0,0 +1,44 @@
"""ACME JOSE JWS."""
from acme import jose
class Header(jose.Header):
"""ACME JOSE Header.
.. todo:: Implement ``acmePath``.
"""
nonce = jose.Field('nonce', omitempty=True, encoder=jose.encode_b64jose)
@nonce.decoder
def nonce(value): # pylint: disable=missing-docstring,no-self-argument
try:
return jose.decode_b64jose(value)
except jose.DeserializationError as error:
# TODO: custom error
raise jose.DeserializationError("Invalid nonce: {0}".format(error))
class Signature(jose.Signature):
"""ACME Signature."""
__slots__ = jose.Signature._orig_slots # pylint: disable=no-member
# TODO: decoder/encoder should accept cls? Otherwise, subclassing
# JSONObjectWithFields is tricky...
header_cls = Header
header = jose.Field(
'header', omitempty=True, default=header_cls(),
decoder=header_cls.from_json)
# TODO: decoder should check that nonce is in the protected header
class JWS(jose.JWS):
"""ACME JWS."""
signature_cls = Signature
__slots__ = jose.JWS._orig_slots # pylint: disable=no-member
@classmethod
def sign(cls, payload, key, alg, nonce): # pylint: disable=arguments-differ
return super(JWS, cls).sign(payload, key=key, alg=alg,
protect=frozenset(['nonce']), nonce=nonce)

52
acme/acme/jws_test.py Normal file
View file

@ -0,0 +1,52 @@
"""Tests for acme.jws."""
import unittest
from acme import jose
from acme import test_util
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
class HeaderTest(unittest.TestCase):
"""Tests for acme.jws.Header."""
good_nonce = jose.encode_b64jose(b'foo')
wrong_nonce = u'F'
# Following just makes sure wrong_nonce is wrong
try:
jose.b64decode(wrong_nonce)
except (ValueError, TypeError):
assert True
else:
assert False # pragma: no cover
def test_nonce_decoder(self):
from acme.jws import Header
nonce_field = Header._fields['nonce']
self.assertRaises(
jose.DeserializationError, nonce_field.decode, self.wrong_nonce)
self.assertEqual(b'foo', nonce_field.decode(self.good_nonce))
class JWSTest(unittest.TestCase):
"""Tests for acme.jws.JWS."""
def setUp(self):
self.privkey = KEY
self.pubkey = self.privkey.public_key()
self.nonce = jose.b64encode(b'Nonce')
def test_it(self):
from acme.jws import JWS
jws = JWS.sign(payload=b'foo', key=self.privkey,
alg=jose.RS256, nonce=self.nonce)
self.assertEqual(jws.signature.combined.nonce, self.nonce)
# TODO: check that nonce is in protected header
self.assertEqual(jws, JWS.from_json(jws.to_json()))
if __name__ == '__main__':
unittest.main() # pragma: no cover

377
acme/acme/messages.py Normal file
View file

@ -0,0 +1,377 @@
"""ACME protocol messages."""
import collections
from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error
from acme import challenges
from acme import fields
from acme import jose
class Error(jose.JSONObjectWithFields, Exception):
"""ACME error.
https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
:ivar unicode typ:
:ivar unicode title:
:ivar unicode detail:
"""
ERROR_TYPE_NAMESPACE = 'urn:acme:error:'
ERROR_TYPE_DESCRIPTIONS = {
'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',
'badNonce': 'The client sent an unacceptable anti-replay nonce',
'connection': 'The server could not connect to the client for DV',
'dnssec': 'The server could not validate a DNSSEC signed domain',
'malformed': 'The request message was malformed',
'serverInternal': 'The server experienced an internal error',
'tls': 'The server experienced a TLS error during DV',
'unauthorized': 'The client lacks sufficient authorization',
'unknownHost': 'The server could not resolve a domain name',
}
typ = jose.Field('type')
title = jose.Field('title', omitempty=True)
detail = jose.Field('detail')
@typ.encoder
def typ(value): # pylint: disable=missing-docstring,no-self-argument
return Error.ERROR_TYPE_NAMESPACE + value
@typ.decoder
def typ(value): # pylint: disable=missing-docstring,no-self-argument
# pylint thinks isinstance(value, Error), so startswith is not found
# pylint: disable=no-member
if not value.startswith(Error.ERROR_TYPE_NAMESPACE):
raise jose.DeserializationError('Missing error type prefix')
without_prefix = value[len(Error.ERROR_TYPE_NAMESPACE):]
if without_prefix not in Error.ERROR_TYPE_DESCRIPTIONS:
raise jose.DeserializationError('Error type not recognized')
return without_prefix
@property
def description(self):
"""Hardcoded error description based on its type.
:rtype: unicode
"""
return self.ERROR_TYPE_DESCRIPTIONS[self.typ]
def __str__(self):
if self.typ is not None:
return ' :: '.join([self.typ, self.description, self.detail])
else:
return str(self.detail)
class _Constant(jose.JSONDeSerializable, collections.Hashable):
"""ACME constant."""
__slots__ = ('name',)
POSSIBLE_NAMES = NotImplemented
def __init__(self, name):
self.POSSIBLE_NAMES[name] = self
self.name = name
def to_partial_json(self):
return self.name
@classmethod
def from_json(cls, value):
if value not in cls.POSSIBLE_NAMES:
raise jose.DeserializationError(
'{0} not recognized'.format(cls.__name__))
return cls.POSSIBLE_NAMES[value]
def __repr__(self):
return '{0}({1})'.format(self.__class__.__name__, self.name)
def __eq__(self, other):
return isinstance(other, type(self)) and other.name == self.name
def __hash__(self):
return hash((self.__class__, self.name))
def __ne__(self, other):
return not self == other
class Status(_Constant):
"""ACME "status" field."""
POSSIBLE_NAMES = {}
STATUS_UNKNOWN = Status('unknown')
STATUS_PENDING = Status('pending')
STATUS_PROCESSING = Status('processing')
STATUS_VALID = Status('valid')
STATUS_INVALID = Status('invalid')
STATUS_REVOKED = Status('revoked')
class IdentifierType(_Constant):
"""ACME identifier type."""
POSSIBLE_NAMES = {}
IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder
class Identifier(jose.JSONObjectWithFields):
"""ACME identifier.
:ivar IdentifierType typ:
:ivar unicode value:
"""
typ = jose.Field('type', decoder=IdentifierType.from_json)
value = jose.Field('value')
class Resource(jose.JSONObjectWithFields):
"""ACME Resource.
:ivar acme.messages.ResourceBody body: Resource body.
"""
body = jose.Field('body')
class ResourceWithURI(Resource):
"""ACME Resource with URI.
:ivar unicode uri: Location of the resource.
"""
uri = jose.Field('uri') # no ChallengeResource.uri
class ResourceBody(jose.JSONObjectWithFields):
"""ACME Resource Body."""
class Registration(ResourceBody):
"""Registration Resource Body.
:ivar acme.jose.jwk.JWK key: Public key.
:ivar tuple contact: Contact information following ACME spec,
`tuple` of `unicode`.
:ivar unicode recovery_token:
:ivar unicode agreement:
"""
# on new-reg key server ignores 'key' and populates it based on
# JWS.signature.combined.jwk
key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
contact = jose.Field('contact', omitempty=True, default=())
recovery_token = jose.Field('recoveryToken', omitempty=True)
agreement = jose.Field('agreement', omitempty=True)
phone_prefix = 'tel:'
email_prefix = 'mailto:'
@classmethod
def from_data(cls, phone=None, email=None, **kwargs):
"""Create registration resource from contact details."""
details = list(kwargs.pop('contact', ()))
if phone is not None:
details.append(cls.phone_prefix + phone)
if email is not None:
details.append(cls.email_prefix + email)
kwargs['contact'] = tuple(details)
return cls(**kwargs)
def _filter_contact(self, prefix):
return tuple(
detail[len(prefix):] for detail in self.contact
if detail.startswith(prefix))
@property
def phones(self):
"""All phones found in the ``contact`` field."""
return self._filter_contact(self.phone_prefix)
@property
def emails(self):
"""All emails found in the ``contact`` field."""
return self._filter_contact(self.email_prefix)
class NewRegistration(Registration):
"""New registration."""
resource_type = 'new-reg'
resource = fields.Resource(resource_type)
class UpdateRegistration(Registration):
"""Update registration."""
resource_type = 'reg'
resource = fields.Resource(resource_type)
class RegistrationResource(ResourceWithURI):
"""Registration Resource.
:ivar acme.messages.Registration body:
:ivar unicode new_authzr_uri: URI found in the 'next' ``Link`` header
:ivar unicode terms_of_service: URL for the CA TOS.
"""
body = jose.Field('body', decoder=Registration.from_json)
new_authzr_uri = jose.Field('new_authzr_uri')
terms_of_service = jose.Field('terms_of_service', omitempty=True)
class ChallengeBody(ResourceBody):
"""Challenge Resource Body.
.. todo::
Confusingly, this has a similar name to `.challenges.Challenge`,
as well as `.achallenges.AnnotatedChallenge`. Please use names
such as ``challb`` to distinguish instances of this class from
``achall``.
:ivar acme.challenges.Challenge: Wrapped challenge.
Conveniently, all challenge fields are proxied, i.e. you can
call ``challb.x`` to get ``challb.chall.x`` contents.
:ivar acme.messages.Status status:
:ivar datetime.datetime validated:
:ivar Error error:
"""
__slots__ = ('chall',)
uri = jose.Field('uri')
status = jose.Field('status', decoder=Status.from_json,
omitempty=True, default=STATUS_PENDING)
validated = fields.RFC3339Field('validated', omitempty=True)
error = jose.Field('error', decoder=Error.from_json,
omitempty=True, default=None)
def to_partial_json(self):
jobj = super(ChallengeBody, self).to_partial_json()
jobj.update(self.chall.to_partial_json())
return jobj
@classmethod
def fields_from_json(cls, jobj):
jobj_fields = super(ChallengeBody, cls).fields_from_json(jobj)
jobj_fields['chall'] = challenges.Challenge.from_json(jobj)
return jobj_fields
def __getattr__(self, name):
return getattr(self.chall, name)
class ChallengeResource(Resource):
"""Challenge Resource.
:ivar acme.messages.ChallengeBody body:
:ivar unicode authzr_uri: URI found in the 'up' ``Link`` header.
"""
body = jose.Field('body', decoder=ChallengeBody.from_json)
authzr_uri = jose.Field('authzr_uri')
@property
def uri(self): # pylint: disable=missing-docstring,no-self-argument
# bug? 'method already defined line None'
# pylint: disable=function-redefined
return self.body.uri # pylint: disable=no-member
class Authorization(ResourceBody):
"""Authorization Resource Body.
:ivar acme.messages.Identifier identifier:
:ivar list challenges: `list` of `.ChallengeBody`
:ivar tuple combinations: Challenge combinations (`tuple` of `tuple`
of `int`, as opposed to `list` of `list` from the spec).
:ivar acme.messages.Status status:
:ivar datetime.datetime expires:
"""
identifier = jose.Field('identifier', decoder=Identifier.from_json)
challenges = jose.Field('challenges', omitempty=True)
combinations = jose.Field('combinations', omitempty=True)
status = jose.Field('status', omitempty=True, decoder=Status.from_json)
# TODO: 'expires' is allowed for Authorization Resources in
# general, but for Key Authorization '[t]he "expires" field MUST
# be absent'... then acme-spec gives example with 'expires'
# present... That's confusing!
expires = fields.RFC3339Field('expires', omitempty=True)
@challenges.decoder
def challenges(value): # pylint: disable=missing-docstring,no-self-argument
return tuple(ChallengeBody.from_json(chall) for chall in value)
@property
def resolved_combinations(self):
"""Combinations with challenges instead of indices."""
return tuple(tuple(self.challenges[idx] for idx in combo)
for combo in self.combinations)
class NewAuthorization(Authorization):
"""New authorization."""
resource_type = 'new-authz'
resource = fields.Resource(resource_type)
class AuthorizationResource(ResourceWithURI):
"""Authorization Resource.
:ivar acme.messages.Authorization body:
:ivar unicode new_cert_uri: URI found in the 'next' ``Link`` header
"""
body = jose.Field('body', decoder=Authorization.from_json)
new_cert_uri = jose.Field('new_cert_uri')
class CertificateRequest(jose.JSONObjectWithFields):
"""ACME new-cert request.
:ivar acme.jose.util.ComparableX509 csr:
`OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:ivar tuple authorizations: `tuple` of URIs (`str`)
"""
resource_type = 'new-cert'
resource = fields.Resource(resource_type)
csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
authorizations = jose.Field('authorizations', decoder=tuple)
class CertificateResource(ResourceWithURI):
"""Certificate Resource.
:ivar acme.jose.util.ComparableX509 body:
`OpenSSL.crypto.X509` wrapped in `.ComparableX509`
:ivar unicode cert_chain_uri: URI found in the 'up' ``Link`` header
:ivar tuple authzrs: `tuple` of `AuthorizationResource`.
"""
cert_chain_uri = jose.Field('cert_chain_uri')
authzrs = jose.Field('authzrs')
class Revocation(jose.JSONObjectWithFields):
"""Revocation message.
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
"""
resource_type = 'revoke-cert'
resource = fields.Resource(resource_type)
certificate = jose.Field(
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
# TODO: acme-spec#138, this allows only one ACME server instance per domain
PATH = '/acme/revoke-cert'
"""Path to revocation URL, see `url`"""
@classmethod
def url(cls, base):
"""Get revocation URL.
:param str base: New Registration Resource or server (root) URL.
"""
return urllib_parse.urljoin(base, cls.PATH)

331
acme/acme/messages_test.py Normal file
View file

@ -0,0 +1,331 @@
"""Tests for acme.messages."""
import unittest
import mock
from acme import challenges
from acme import jose
from acme import test_util
CERT = test_util.load_cert('cert.der')
CSR = test_util.load_csr('csr.der')
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class ErrorTest(unittest.TestCase):
"""Tests for acme.messages.Error."""
def setUp(self):
from acme.messages import Error
self.error = Error(detail='foo', typ='malformed', title='title')
self.jobj = {'detail': 'foo', 'title': 'some title'}
def test_typ_prefix(self):
self.assertEqual('malformed', self.error.typ)
self.assertEqual(
'urn:acme:error:malformed', self.error.to_partial_json()['type'])
self.assertEqual(
'malformed', self.error.from_json(self.error.to_partial_json()).typ)
def test_typ_decoder_missing_prefix(self):
from acme.messages import Error
self.jobj['type'] = 'malformed'
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
self.jobj['type'] = 'not valid bare type'
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
def test_typ_decoder_not_recognized(self):
from acme.messages import Error
self.jobj['type'] = 'urn:acme:error:baz'
self.assertRaises(jose.DeserializationError, Error.from_json, self.jobj)
def test_description(self):
self.assertEqual(
'The request message was malformed', self.error.description)
def test_from_json_hashable(self):
from acme.messages import Error
hash(Error.from_json(self.error.to_json()))
def test_str(self):
self.assertEqual(
'malformed :: The request message was malformed :: foo',
str(self.error))
self.assertEqual('foo', str(self.error.update(typ=None)))
class ConstantTest(unittest.TestCase):
"""Tests for acme.messages._Constant."""
def setUp(self):
from acme.messages import _Constant
class MockConstant(_Constant): # pylint: disable=missing-docstring
POSSIBLE_NAMES = {}
self.MockConstant = MockConstant # pylint: disable=invalid-name
self.const_a = MockConstant('a')
self.const_b = MockConstant('b')
def test_to_partial_json(self):
self.assertEqual('a', self.const_a.to_partial_json())
self.assertEqual('b', self.const_b.to_partial_json())
def test_from_json(self):
self.assertEqual(self.const_a, self.MockConstant.from_json('a'))
self.assertRaises(
jose.DeserializationError, self.MockConstant.from_json, 'c')
def test_from_json_hashable(self):
hash(self.MockConstant.from_json('a'))
def test_repr(self):
self.assertEqual('MockConstant(a)', repr(self.const_a))
self.assertEqual('MockConstant(b)', repr(self.const_b))
def test_equality(self):
const_a_prime = self.MockConstant('a')
self.assertFalse(self.const_a == self.const_b)
self.assertTrue(self.const_a == const_a_prime)
self.assertTrue(self.const_a != self.const_b)
self.assertFalse(self.const_a != const_a_prime)
class RegistrationTest(unittest.TestCase):
"""Tests for acme.messages.Registration."""
def setUp(self):
key = jose.jwk.JWKRSA(key=KEY.public_key())
contact = (
'mailto:admin@foo.com',
'tel:1234',
)
recovery_token = 'XYZ'
agreement = 'https://letsencrypt.org/terms'
from acme.messages import Registration
self.reg = Registration(
key=key, contact=contact, recovery_token=recovery_token,
agreement=agreement)
self.reg_none = Registration()
self.jobj_to = {
'contact': contact,
'recoveryToken': recovery_token,
'agreement': agreement,
'key': key,
}
self.jobj_from = self.jobj_to.copy()
self.jobj_from['key'] = key.to_json()
def test_from_data(self):
from acme.messages import Registration
reg = Registration.from_data(phone='1234', email='admin@foo.com')
self.assertEqual(reg.contact, (
'tel:1234',
'mailto:admin@foo.com',
))
def test_phones(self):
self.assertEqual(('1234',), self.reg.phones)
def test_emails(self):
self.assertEqual(('admin@foo.com',), self.reg.emails)
def test_to_partial_json(self):
self.assertEqual(self.jobj_to, self.reg.to_partial_json())
def test_from_json(self):
from acme.messages import Registration
self.assertEqual(self.reg, Registration.from_json(self.jobj_from))
def test_from_json_hashable(self):
from acme.messages import Registration
hash(Registration.from_json(self.jobj_from))
class RegistrationResourceTest(unittest.TestCase):
"""Tests for acme.messages.RegistrationResource."""
def setUp(self):
from acme.messages import RegistrationResource
self.regr = RegistrationResource(
body=mock.sentinel.body, uri=mock.sentinel.uri,
new_authzr_uri=mock.sentinel.new_authzr_uri,
terms_of_service=mock.sentinel.terms_of_service)
def test_to_partial_json(self):
self.assertEqual(self.regr.to_json(), {
'body': mock.sentinel.body,
'uri': mock.sentinel.uri,
'new_authzr_uri': mock.sentinel.new_authzr_uri,
'terms_of_service': mock.sentinel.terms_of_service,
})
class ChallengeResourceTest(unittest.TestCase):
"""Tests for acme.messages.ChallengeResource."""
def test_uri(self):
from acme.messages import ChallengeResource
self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock(
uri='http://challb'), authzr_uri='http://authz').uri)
class ChallengeBodyTest(unittest.TestCase):
"""Tests for acme.messages.ChallengeBody."""
def setUp(self):
self.chall = challenges.DNS(token='foo')
from acme.messages import ChallengeBody
from acme.messages import Error
from acme.messages import STATUS_INVALID
self.status = STATUS_INVALID
error = Error(typ='serverInternal',
detail='Unable to communicate with DNS server')
self.challb = ChallengeBody(
uri='http://challb', chall=self.chall, status=self.status,
error=error)
self.jobj_to = {
'uri': 'http://challb',
'status': self.status,
'type': 'dns',
'token': 'foo',
'error': error,
}
self.jobj_from = self.jobj_to.copy()
self.jobj_from['status'] = 'invalid'
self.jobj_from['error'] = {
'type': 'urn:acme:error:serverInternal',
'detail': 'Unable to communicate with DNS server',
}
def test_to_partial_json(self):
self.assertEqual(self.jobj_to, self.challb.to_partial_json())
def test_from_json(self):
from acme.messages import ChallengeBody
self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from))
def test_from_json_hashable(self):
from acme.messages import ChallengeBody
hash(ChallengeBody.from_json(self.jobj_from))
def test_proxy(self):
self.assertEqual('foo', self.challb.token)
class AuthorizationTest(unittest.TestCase):
"""Tests for acme.messages.Authorization."""
def setUp(self):
from acme.messages import ChallengeBody
from acme.messages import STATUS_VALID
self.challbs = (
ChallengeBody(
uri='http://challb1', status=STATUS_VALID,
chall=challenges.SimpleHTTP(token='IlirfxKKXAsHtmzK29Pj8A')),
ChallengeBody(uri='http://challb2', status=STATUS_VALID,
chall=challenges.DNS(token='DGyRejmCefe7v4NfDGDKfA')),
ChallengeBody(uri='http://challb3', status=STATUS_VALID,
chall=challenges.RecoveryToken()),
)
combinations = ((0, 2), (1, 2))
from acme.messages import Authorization
from acme.messages import Identifier
from acme.messages import IDENTIFIER_FQDN
identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com')
self.authz = Authorization(
identifier=identifier, combinations=combinations,
challenges=self.challbs)
self.jobj_from = {
'identifier': identifier.to_json(),
'challenges': [challb.to_json() for challb in self.challbs],
'combinations': combinations,
}
def test_from_json(self):
from acme.messages import Authorization
Authorization.from_json(self.jobj_from)
def test_from_json_hashable(self):
from acme.messages import Authorization
hash(Authorization.from_json(self.jobj_from))
def test_resolved_combinations(self):
self.assertEqual(self.authz.resolved_combinations, (
(self.challbs[0], self.challbs[2]),
(self.challbs[1], self.challbs[2]),
))
class AuthorizationResourceTest(unittest.TestCase):
"""Tests for acme.messages.AuthorizationResource."""
def test_json_de_serializable(self):
from acme.messages import AuthorizationResource
authzr = AuthorizationResource(
uri=mock.sentinel.uri,
body=mock.sentinel.body,
new_cert_uri=mock.sentinel.new_cert_uri,
)
self.assertTrue(isinstance(authzr, jose.JSONDeSerializable))
class CertificateRequestTest(unittest.TestCase):
"""Tests for acme.messages.CertificateRequest."""
def setUp(self):
from acme.messages import CertificateRequest
self.req = CertificateRequest(csr=CSR, authorizations=('foo',))
def test_json_de_serializable(self):
self.assertTrue(isinstance(self.req, jose.JSONDeSerializable))
from acme.messages import CertificateRequest
self.assertEqual(
self.req, CertificateRequest.from_json(self.req.to_json()))
class CertificateResourceTest(unittest.TestCase):
"""Tests for acme.messages.CertificateResourceTest."""
def setUp(self):
from acme.messages import CertificateResource
self.certr = CertificateResource(
body=CERT, uri=mock.sentinel.uri, authzrs=(),
cert_chain_uri=mock.sentinel.cert_chain_uri)
def test_json_de_serializable(self):
self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable))
from acme.messages import CertificateResource
self.assertEqual(
self.certr, CertificateResource.from_json(self.certr.to_json()))
class RevocationTest(unittest.TestCase):
"""Tests for acme.messages.RevocationTest."""
def test_url(self):
from acme.messages import Revocation
url = 'https://letsencrypt-demo.org/acme/revoke-cert'
self.assertEqual(url, Revocation.url('https://letsencrypt-demo.org'))
self.assertEqual(
url, Revocation.url('https://letsencrypt-demo.org/acme/new-reg'))
def setUp(self):
from acme.messages import Revocation
self.rev = Revocation(certificate=CERT)
def test_from_json_hashable(self):
from acme.messages import Revocation
hash(Revocation.from_json(self.rev.to_json()))
if __name__ == '__main__':
unittest.main() # pragma: no cover

67
acme/acme/other.py Normal file
View file

@ -0,0 +1,67 @@
"""Other ACME objects."""
import functools
import logging
import os
from acme import jose
logger = logging.getLogger(__name__)
class Signature(jose.JSONObjectWithFields):
"""ACME signature.
:ivar .JWASignature alg: Signature algorithm.
:ivar bytes sig: Signature.
:ivar bytes nonce: Nonce.
:ivar .JWK jwk: JWK.
"""
NONCE_SIZE = 16
"""Minimum size of nonce in bytes."""
alg = jose.Field('alg', decoder=jose.JWASignature.from_json)
sig = jose.Field('sig', encoder=jose.encode_b64jose,
decoder=jose.decode_b64jose)
nonce = jose.Field(
'nonce', encoder=jose.encode_b64jose, decoder=functools.partial(
jose.decode_b64jose, size=NONCE_SIZE, minimum=True))
jwk = jose.Field('jwk', decoder=jose.JWK.from_json)
@classmethod
def from_msg(cls, msg, key, nonce=None, nonce_size=None, alg=jose.RS256):
"""Create signature with nonce prepended to the message.
:param bytes msg: Message to be signed.
:param key: Key used for signing.
:type key: `cryptography.hazmat.primitives.assymetric.rsa.RSAPrivateKey`
(optionally wrapped in `.ComparableRSAKey`).
:param bytes nonce: Nonce to be used. If None, nonce of
``nonce_size`` will be randomly generated.
:param int nonce_size: Size of the automatically generated nonce.
Defaults to :const:`NONCE_SIZE`.
:param .JWASignature alg:
"""
nonce_size = cls.NONCE_SIZE if nonce_size is None else nonce_size
nonce = os.urandom(nonce_size) if nonce is None else nonce
msg_with_nonce = nonce + msg
sig = alg.sign(key, nonce + msg)
logger.debug('%r signed as %r', msg_with_nonce, sig)
return cls(alg=alg, sig=sig, nonce=nonce,
jwk=alg.kty(key=key.public_key()))
def verify(self, msg):
"""Verify the signature.
:param bytes msg: Message that was used in signing.
"""
# self.alg is not Field, but JWA | pylint: disable=no-member
return self.alg.verify(self.jwk.key, self.nonce + msg, self.sig)

94
acme/acme/other_test.py Normal file
View file

@ -0,0 +1,94 @@
"""Tests for acme.sig."""
import unittest
from acme import jose
from acme import test_util
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class SignatureTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
"""Tests for acme.sig.Signature."""
def setUp(self):
self.msg = b'message'
self.sig = (b'IC\xd8*\xe7\x14\x9e\x19S\xb7\xcf\xec3\x12\xe2\x8a\x03'
b'\x98u\xff\xf0\x94\xe2\xd7<\x8f\xa8\xed\xa4KN\xc3\xaa'
b'\xb9X\xc3w\xaa\xc0_\xd0\x05$y>l#\x10<\x96\xd2\xcdr\xa3'
b'\x1b\xa1\xf5!f\xef\xc64\xb6\x13')
self.nonce = b'\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9'
self.alg = jose.RS256
self.jwk = jose.JWKRSA(key=KEY.public_key())
b64sig = ('SUPYKucUnhlTt8_sMxLiigOYdf_wlOLXPI-o7aRLTsOquVjDd6r'
'AX9AFJHk-bCMQPJbSzXKjG6H1IWbvxjS2Ew')
b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ'
self.jsig_to = {
'nonce': b64nonce,
'alg': self.alg,
'jwk': self.jwk,
'sig': b64sig,
}
self.jsig_from = {
'nonce': b64nonce,
'alg': self.alg.to_partial_json(),
'jwk': self.jwk.to_partial_json(),
'sig': b64sig,
}
from acme.other import Signature
self.signature = Signature(
alg=self.alg, sig=self.sig, nonce=self.nonce, jwk=self.jwk)
def test_attributes(self):
self.assertEqual(self.signature.nonce, self.nonce)
self.assertEqual(self.signature.alg, self.alg)
self.assertEqual(self.signature.sig, self.sig)
self.assertEqual(self.signature.jwk, self.jwk)
def test_verify_good_succeeds(self):
self.assertTrue(self.signature.verify(self.msg))
def test_verify_bad_fails(self):
self.assertFalse(self.signature.verify(self.msg + b'x'))
@classmethod
def _from_msg(cls, *args, **kwargs):
from acme.other import Signature
return Signature.from_msg(*args, **kwargs)
def test_create_from_msg(self):
signature = self._from_msg(self.msg, KEY, self.nonce)
self.assertEqual(self.signature, signature)
def test_create_from_msg_random_nonce(self):
signature = self._from_msg(self.msg, KEY)
self.assertEqual(signature.alg, self.alg)
self.assertEqual(signature.jwk, self.jwk)
self.assertTrue(signature.verify(self.msg))
def test_to_partial_json(self):
self.assertEqual(self.signature.to_partial_json(), self.jsig_to)
def test_from_json(self):
from acme.other import Signature
self.assertEqual(
self.signature, Signature.from_json(self.jsig_from))
def test_from_json_non_schema_errors(self):
from acme.other import Signature
jwk = self.jwk.to_partial_json()
self.assertRaises(
jose.DeserializationError, Signature.from_json, {
'alg': 'RS256', 'sig': 'x', 'nonce': '', 'jwk': jwk})
self.assertRaises(
jose.DeserializationError, Signature.from_json, {
'alg': 'RS256', 'sig': '', 'nonce': 'x', 'jwk': jwk})
if __name__ == '__main__':
unittest.main() # pragma: no cover

63
acme/acme/test_util.py Normal file
View file

@ -0,0 +1,63 @@
# Symlinked in letsencrypt/tests/test_util.py, casues duplicate-code
# warning that cannot be disabled locally.
"""Test utilities.
.. warning:: This module is not part of the public API.
"""
import os
import pkg_resources
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import OpenSSL
from acme import jose
def vector_path(*names):
"""Path to a test vector."""
return pkg_resources.resource_filename(
__name__, os.path.join('testdata', *names))
def load_vector(*names):
"""Load contents of a test vector."""
# luckily, resource_string opens file in binary mode
return pkg_resources.resource_string(
__name__, os.path.join('testdata', *names))
def _guess_loader(filename, loader_pem, loader_der):
_, ext = os.path.splitext(filename)
if ext.lower() == '.pem':
return loader_pem
elif ext.lower() == '.der':
return loader_der
else: # pragma: no cover
raise ValueError("Loader could not be recognized based on extension")
def load_cert(*names):
"""Load certificate."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate(
loader, load_vector(*names)))
def load_csr(*names):
"""Load certificate request."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
loader, load_vector(*names)))
def load_rsa_private_key(*names):
"""Load RSA private key."""
loader = _guess_loader(names[-1], serialization.load_pem_private_key,
serialization.load_der_private_key)
return jose.ComparableRSAKey(loader(
load_vector(*names), password=None, backend=default_backend()))
def load_pyopenssl_private_key(*names):
"""Load pyOpenSSL private key."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))

15
acme/acme/testdata/README vendored Normal file
View file

@ -0,0 +1,15 @@
In order for acme.test_util._guess_loader to work properly, make sure
to use appropriate extension for vector filenames: .pem for PEM and
.der for DER.
The following command has been used to generate test keys:
for x in 256 512 1024; do openssl genrsa -out rsa${k}_key.pem $k; done
and for the CSR:
openssl req -key rsa512_key.pem -new -subj '/CN=example.com' -outform DER > csr.der
and for the certificate:
openssl req -key rsa512_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der

14
acme/acme/testdata/cert-san.pem vendored Normal file
View file

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx
ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM
IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4
YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG
A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix
KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS
BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR
7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c
+pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt
cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF
nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7
RDjyGMKy5ZgM2w==
-----END CERTIFICATE-----

BIN
acme/acme/testdata/cert.der vendored Normal file

Binary file not shown.

13
acme/acme/testdata/cert.pem vendored Normal file
View file

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx
ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM
IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4
YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG
A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix
KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS
BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR
7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c
+pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll
vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn
B/o=
-----END CERTIFICATE-----

12
acme/acme/testdata/csr-6sans.pem vendored Normal file
View file

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw
EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG
9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0
9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG
9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL
ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t
ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd
k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv
IvzVBz/nD11drfz/RNuX
-----END CERTIFICATE REQUEST-----

8
acme/acme/testdata/csr-nosans.pem vendored Normal file
View file

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt
cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn
BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz
AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo
wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA=
-----END CERTIFICATE REQUEST-----

10
acme/acme/testdata/csr-san.pem vendored Normal file
View file

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw
EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG
9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f
p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN
AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t
MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy
tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A==
-----END CERTIFICATE REQUEST-----

BIN
acme/acme/testdata/csr.der vendored Normal file

Binary file not shown.

10
acme/acme/testdata/csr.pem vendored Normal file
View file

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw
EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG
9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f
p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN
AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB
AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G
n9XBE1N9W6HCIEut2d8wACg=
-----END CERTIFICATE REQUEST-----

14
acme/acme/testdata/dsa512_key.pem vendored Normal file
View file

@ -0,0 +1,14 @@
-----BEGIN DSA PARAMETERS-----
MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC
OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA
qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl
41pgNJpgu99YOYqPpS0g7A==
-----END DSA PARAMETERS-----
-----BEGIN DSA PRIVATE KEY-----
MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf
n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP
AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm
rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth
zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE
mNv063So6E+eYaIN
-----END DSA PRIVATE KEY-----

15
acme/acme/testdata/rsa1024_key.pem vendored Normal file
View file

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi
4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/
w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB
AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB
Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc
TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB
CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X
UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak
Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt
73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa
HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU
6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ
c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc=
-----END RSA PRIVATE KEY-----

6
acme/acme/testdata/rsa256_key.pem vendored Normal file
View file

@ -0,0 +1,6 @@
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh
AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N
E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3
rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt
-----END RSA PRIVATE KEY-----

9
acme/acme/testdata/rsa512_key.pem vendored Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79
vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn
elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc
mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp
Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj
8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq
6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/
-----END RSA PRIVATE KEY-----

48
acme/setup.py Normal file
View file

@ -0,0 +1,48 @@
import sys
from setuptools import setup
from setuptools import find_packages
install_requires = [
'argparse',
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
'cryptography>=0.8',
'mock<1.1.0', # py26
'pyrfc3339',
'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304)
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
# Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15)
'PyOpenSSL>=0.15',
'pytz',
'requests',
'six',
'werkzeug',
]
# env markers in extras_require cause problems with older pip: #517
if sys.version_info < (2, 7):
# only some distros recognize stdlib argparse as already satisfying
install_requires.append('argparse')
testing_extras = [
'nose',
'tox',
]
setup(
name='acme',
packages=find_packages(),
install_requires=install_requires,
extras_require={
'testing': testing_extras,
},
entry_points={
'console_scripts': [
'jws = acme.jose.jws:CLI.run',
],
},
test_suite='acme',
)

7
bootstrap/README Normal file
View file

@ -0,0 +1,7 @@
This directory contains scripts that install necessary OS-specific
prerequisite dependencies (see docs/using.rst).
General dependencies:
- git-core: requirements.txt git+https://*
- ca-certificates: communication with demo ACMO server at
https://www.letsencrypt-demo.org, requirements.txt git+https://*

57
bootstrap/_deb_common.sh Executable file
View file

@ -0,0 +1,57 @@
#!/bin/sh
# Tested with:
# - Ubuntu:
# - 12.04 (x64, Travis)
# - 14.04 (x64, Vagrant)
# - 14.10 (x64)
# - Debian:
# - 6.0.10 "squeeze" (x64)
# - 7.8 "wheezy" (x64)
# - 8.0 "jessie" (x64)
# - Raspbian:
# - 7.8 (armhf)
# virtualenv binary can be found in different packages depending on
# distro version (#346)
newer () {
apt-get install -y lsb-release --no-install-recommends
distro=$(lsb_release -si)
# 6.0.10 => 60, 14.04 => 1404
# TODO: in sid version==unstable
version=$(lsb_release -sr | awk -F '.' '{print $1 $2}')
if [ "$distro" = "Ubuntu" -a "$version" -ge 1410 ]
then
return 0;
elif [ "$distro" = "Debian" -a "$version" -ge 80 ]
then
return 0;
else
return 1;
fi
}
apt-get update
# you can force newer if lsb_release is not available (e.g. Docker
# debian:jessie base image)
if [ "$1" = "newer" ] || newer
then
virtualenv="virtualenv"
else
virtualenv="python-virtualenv"
fi
apt-get install -y --no-install-recommends \
git-core \
python \
python-dev \
"$virtualenv" \
gcc \
dialog \
libaugeas0 \
libssl-dev \
libffi-dev \
ca-certificates \

19
bootstrap/_rpm_common.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/sh
# Tested with:
# - Fedora 22 (x64)
# - Centos 7 (x64: on AWS EC2 t2.micro, DigitalOcean droplet)
# "git-core" seems to be an alias for "git" in CentOS 7 (yum search fails)
yum install -y \
git-core \
python \
python-devel \
python-virtualenv \
python-devel \
gcc \
dialog \
augeas-libs \
openssl-devel \
libffi-devel \
ca-certificates \

1
bootstrap/centos.sh Symbolic link
View file

@ -0,0 +1 @@
_rpm_common.sh

1
bootstrap/debian.sh Symbolic link
View file

@ -0,0 +1 @@
_deb_common.sh

1
bootstrap/fedora.sh Symbolic link
View file

@ -0,0 +1 @@
_rpm_common.sh

2
bootstrap/mac.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
brew install augeas

1
bootstrap/ubuntu.sh Symbolic link
View file

@ -0,0 +1 @@
_deb_common.sh

View file

@ -1 +0,0 @@
Protocol docs are currently authored at https://choc.pad.jhalderm.com/6, and exported here. This should probably change soon.

View file

@ -1,266 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>/26$6</title>
</head>
<body><b>ABSTRACT</b><br
/><br
/>This protocol exists to facilitate the activation of secure TLS encryption on all Internet servers to which it is applicable, in a manner compatible with existing deployed client bases, but without the need for the expert human configuration that has limited the use of TLS to date.&nbsp; The protocol provides transmission and validation of certification requests, and the issuance of digital certificates, with as little human intervention as possible.<br
/><br
/><br
/><b>INTRODUCTION</b><br
/><br
/>The Trustify (aka Chococlate) protocol is used between a <b>client</b>, which is a software package on an Internet-connected host that is or will be used to operate a Web server or other TLS service, and a <b>server</b>, which is a certification service provided by a certificate authority.<br
/><br
/>The protocol is implemented as a series of Protocol Buffers messages which are serialized and transported as HTTP POST request and response bodies relative to an HTTPS URI published by the server. Currently, each client to server message is a new HTTP POST and each server reply is a new response body.&nbsp; Hence, the client and server alternate sending messages; asynchronous notification of changes in server state do not occur, but the client can be instructed to poll periodically while the server is working.&nbsp; Full specification of the Trustify message format is in the Messages section below.<br
/><br
/>The client must verify the server's own certificate when making each HTTPS connection.&nbsp; If the client cannot establish a secure connection to the server, the client SHOULD abort the session without any further communication.<br
/><br
/>Because avoiding misissuance of certificates is a fundamental priority, the server is able to abort sessions at any time for a wide variety of reasons, declining any further communication about an abandoned session.<br
/><br
/><b>IDENTIFYING AVAILABLE TRUSTIFY SERVERS</b><br
/><br
/>The first step for a Trustify client is to identify the currently operating and available Trustify servers that correspond to CAs issuing certificates through the protocol.&nbsp; Clients contain an HTTPS URL that always referrs to a fresh client list.&nbsp; For reliability purposes, if the URL cannot be fetched, clients may refer to a previously cached copy of the list.<br
/><br
/>The Trustify Server List will contain the following data:<br
/><br
/>[Server name string, server URL, &lt;optional&gt; [policy string1, policy string 2,..]]<br
/><br
/>The Server name string is a UTF-8 string containing an English readable name (possibly followed by a native name in another alphabet) of the CA.&nbsp; The server URL is an HTTPS endpoint that responds to the Trustify protocol as documented below.&nbsp; The policy strings are an optional list of parameters that may at some point express relevant comparison points between multiple CAs.&nbsp; The semantics of policy fields will be specified by the Trustify governance foundation.<br
/><br
/><b>SESSIONS</b><br
/><br
/>Trustify sessions are distinguished by a random session identifier which is issued by the server in its very first response to a new client connection. The session identifier must then be mentioned in all communications in either direction in order to associate requests and responses with a particular transaction. Omitting the session identifier is treated as a request to begin a new session, while messages containing an unrecognized or stale session identifier are rejected.<br
/><br
/>The session identifier is an ephemeral authenticator private to the client and server.&nbsp; An entity that knows the session identifier is able to make requests to the server related to the session, including erroneous requests that will cause the session to be terminated. Session identifiers from old, inactive sessions are explicitly marked invalid and no further use can be made of them.<br
/><br
/>The goal of a session is the issuance of a new digital certificate. The client must already possess a subject public key and a list of requested subject host names.&nbsp; The client begins the protocol with the goal of obtaining a certificate issued by the server, associating the public key with each subject name.<br
/><br
/><b>MESSAGE</b><br
/><br
/>The Trusitfy message object contains a version ID and a session ID.&nbsp; It also contains exactly one of the following specific message objects (except for challenge and completedchallenge):<br
/><br
/>request [type SigningRequest]: sent exactly once by the client at the beginning of a session in order to request a certificate.&nbsp; The contents indicate the details of the certificate that is being requested in the current session.<br
/><br
/>failure [type Failure]: sent by the client or the server to indicate a reason why the session cannot continue.&nbsp; All failure messages are fatal and result in the server marking the session as expired, so that no further activity may occur.&nbsp; (If a client sends a message related to an expired session, the server will respond with a failure message indicating that the session is expired.)<br
/><br
/>proceed [type Proceed]: sent by the server to request the client to poll again on the current session after a specified time delay.&nbsp; Normally used when the server believes that it is in the process of testing whether challenges have been satisfied or in the process of issuing the requested certificate.<br
/>Note: Protocol also specifies the client can send this message after a polling period.<br
/><br
/>challenge [type Challenge]: sent by the server to announce challenges for the first time or to tell the client whether the server believes that the client has successfully completed a subset of the previously issued challenges.<br
/><br
/>completedchallenge [type Challenge]: sent by the client to announce that the client believes it has successfully completed a challenge.<br
/><br
/>success [type Success]: sent by the server to issue the requested certificate.<br
/><br
/><b>SEQUENCE OF COMMUNICATIONS</b><br
/><br
/><b><i>TODO</i></b>: Document difference between challenge types that call for proceed message and challenge types that call for completedchallenge message.<br
/><br
/>Initial request<br
/>At the start of communications:<br
/><ul><li>1. The client sends a <b>request</b> message, which includes a signing request.</li
><li>2. The server issues a new session ID, which is included with all subsequent messages sent in either direction.</li
><li>3. The server validates the request.&nbsp; If it is known to be invalid or unacceptable for any reason, the server sends a failure message and aborts the protocol.<br/><br
/></li></ul
>Server challenge or certificate issuance<br
/>In response to the <b>request</b> message:<br
/><ul><li>4. Otherwise, in accordance with its policy, the server sends a <b>challenge</b> or <b>success</b> message.&nbsp; (The normal case is a <b>challenge</b> message because the server typically requires proof from the client that the client is authorized to obtain the requested certificate.)</li
><li>5. If the client receives a <b>challenge</b> message, it notes the challenges that it is expected to complete.&nbsp; The client then attempts to complete each specified challenge.</li
><li>6. When the client believes that it has completed a challenge, it can send a <b>completedchallenge</b> message updating the server on its progress. If the client believes that it is unable to complete a challenge, it can send a failure message admitting failure, which will result in the termination of the session.<br/><br
/></li></ul
>Server response to challenge progress<br
/>In response to the client-side <b>completedchallenge</b> or <b>proceed</b> message:<br
/><ul><li>7. If the server observes or agrees that all challenges issued in the session have been completed, it may issue the certificate and send a <b>success</b> message. This terminates the session.</li
><li>8. If the server believes that more time is required for certificate issuance or for challenge verification, it may send a <b>proceed</b> message asking the client to poll again after a specified time interval. The client should poll after this interval by sending completedchallenge (if it has new successes to claim) or proceed (if not). [Return to step 7.]</li
><li>9. If the server believes that one or more challenges have still not been completed, it may send a <b>challenge</b> message indicating the status of the challenges whose completeness has or has not been verified. [Return to step 5.]</li
><li>10. If the server believes that more challenges should be completed, it may send a new <b>challenge</b> message presenting the additional challenges. [Return to step 5.]</li
><li>11. If the server believes that too much time has elapsed or that some challenge was abandoned or has become impossible to satisfy, it can send a <b>failure</b> message. This terminates the session.<br/><br
/></li></ul
><b>VERIFICATION CHALLENGES</b><br
/><br
/>During the course of a session, the Trustify server presents the client with one or more <b>challenges</b>,&nbsp; which are messages specifying tasks that must be completed to verify&nbsp; the preconditions of issuance for the certificate(s) that the client has requested.&nbsp; Challenges are an abstraction layer to allow the Trustify protocol to be enhanced and expanded over time.<br
/><br
/>Several challenges may be presented at once, but further or additional&nbsp; challenges may be presented after previous sets, possibly as a result of&nbsp; information that the server obtained while verfiying the earlier&nbsp; challenges.<br
/><br
/>Clients may decide to meet all of the outstanding challenges at once, or may&nbsp; decide to send responses to the challenges one at a time. The semantic&nbsp; relationship between the outstanding challenges may be conjunctive (all challenges must eventually be met before issuance), disjunctive (only one or a subset of challenges must be met), or variable (the set of challenges that are required depends on the manner in which the earlier ones are completed)<br
/><br
/>As&nbsp; a matter of protocol synchronization, there are two subtypes of&nbsp; challenges: those where completion of the challenge is signalled by the client and then verified by the server (such as the DVSNI challenge&nbsp; described below, which requires the client to configure a TLS server in a particular way) and those where completion of the challenge is identified and verified solely by the server (such as a challenge involving a payment or other organizational validation for a high-value domain).<br
/><br
/><b>DVSNI Challenges:</b><br
/><br
/>DVSNI challenges are the fundamental method employed by the Trustify protocol to ensure that clients control&nbsp; the DNS names for which they are requesting certificates.&nbsp; It is intended to be more automatable (and in some cases, more secure) than&nbsp; the email receipt verification that is commonly used by DV CAs, and&nbsp; categorically more secure than the HTTP nonce deployment verification used by some DV CAs.<br
/><br
/>DVSNI requires the client to demonstrate significant administrative control&nbsp; of the domain by not only changing responses from an HTTP server, but by&nbsp; altering the TLS configuration of an HTTPS server to answer specially crafted SNI (Server Name Indication) requests. This shows that the client at least has adminitrative control of the DNS name's web server&nbsp; software, and probably has full adminstrative control of the servers that the DNS name points to.<br
/><br
/><b>DVSNI implementation specifics:</b><br
/><br
/><i>&nbsp;&nbsp;&nbsp; Shared parameters (chosen and sent by CA in the Challenge message):</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${nonce}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A randomly chosen nonce hex output of digest of (random 32-bytes)<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${y} [:= E(r)]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r is a random secret (32 bytes, raw binary)<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${ext}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x.509 extension (format? e.g: 1.3.3.7)<br
/><br
/><i>&nbsp;&nbsp;&nbsp; Client setup:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Setup SNI for <i>${nonce}</i>.chocolate<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Serve a self-signed certificate with (critical(?)) X.509 extension <i>${ext}</i>,<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; with value z = HMAC_r(s) || s (random s, r = D(${y}) (private key decrypt))<br
/><br
/><i>&nbsp;&nbsp;&nbsp; Chocolate Server (CA) verification steps:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Connect to domain.com, with TLS SNI ${nonce}.chocolate<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Check certificate for X.509 extension ${ext}. Verify that value z is formed as expected.<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i>&nbsp;&nbsp;&nbsp; Purpose:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Proves that whoever has access to the corresponding private key also has the ability<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; to serve (change configuration for) an arbitrary certificate for an arbitrary<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; subdomain under the desired domain name.<br
/><br
/>&nbsp;&nbsp;&nbsp; <i>Explanation:</i><br
/>Using ${nonce}.chocolate&nbsp; (instead of domain.com) allows a currently running web server to&nbsp; continue serving domain.com without interruption. (Note we still use the&nbsp; IP for domain.com when we connect to the server)&nbsp;&nbsp; It is of slight importance that the .chocolate TLD is not real; it may&nbsp; protect against elaborate attacks against domains like dyndns.com which&nbsp; would allow attackers to control nonce.dyndns.com.&nbsp; If such a domain&nbsp; were also hosted on a virtual hosting service/CDN with the same IP as&nbsp; the attacker, and which really did SNI, it would be important that&nbsp; .chocolate not be real.<br
/>We&nbsp; require the certificate to be modified to ensure that it is freshly&nbsp; generated for this purpose, and not just being served a wildcard&nbsp; certificate. We could do this several ways, but it is neccessary to tie&nbsp; this modification to control of the private key (hence the need for z)&nbsp; and to the current Chocolate request (hence the need for ${ext}).&nbsp; Otherwise, a "rogue CA" could receive a request from domain.com, forward&nbsp; it to a "real CA" with a different public key, and have the real&nbsp; domain.com carry out this challenge (which real CA would verify, and&nbsp; give rogue CA a signed cert for domain.com).<br
/><br
/>&nbsp;&nbsp;&nbsp; <i>Deviations from model:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp; SNI only allows multiplexing over the domain.&nbsp; Apache always has a&nbsp; default SNI page... even if you provide a completely wrong header.&nbsp; The&nbsp; real challenge happens with y and z.<br
/>&nbsp;&nbsp;&nbsp;&nbsp; Apache uses the first virtual server specifed on the ip/port to act as a&nbsp; default server if the SNI extension is specified by the client but does&nbsp; not match any of the virtual servers.<br
/><br
/><b>DVSNI Security Considerations:</b><br
/><br
/>Shared hosting environments with multiple DNS names pointed a single host and IP are a common situation on the modern Internet.&nbsp; We expect that the hosting provider managing these guest domains should take its own steps&nbsp; to prevent one from listening on port 443 in an unconstrained manner for the other guests' domains.&nbsp; If it does not, the DVSNI verification step&nbsp; will be unable to distinguish the owner of the domain from others who can listen on the domain's privileged ports, and, depending on what&nbsp; other verification steps are applicable, may issue certificates to these other parties.<br
/><br
/>Verification&nbsp; of control on TLS ports other than 443 (such as the TLS email serivces&nbsp; on ports 465, 993 and 995) in the presence of virtual hosting presents similar but possibly more serious challenges, since use of these ports on virtual private servers may be rarer, and policies about them more&nbsp; varied.<br
/><br
/><b>Proof-of-posession challenges:</b><br
/><br
/>The&nbsp; server MUST check whether there are pre-existing valid certificates for the requested DNS names by consulting Certificate Transparency logs and/or the EFF SSL Observatory.&nbsp; If such certificates exist, the server SHOULD NOT proceed unless it can ensure that issuance will not reduce the security of TLS services deployed with those existing certificates.<br
/><br
/>Traditional OV verification processes may be one way of achieving this, but it is recommended that a Proof of Possession challenge be offered as an alternative.&nbsp; This challenge type requires&nbsp; the client to sign a challenge nonce using the private key from one of the existing valid certificates for the DNS name in question.<br
/><br
/><br
/><b>Payment challenges:</b><br
/><br
/>In&nbsp; limited situations, it may be determined that traditional OV or EV processes are required for the issuance of ceriticates for high-value, high-traffic DNS names.&nbsp; In such cases a payment challenge may be used to facilitate the transition to the OV or EV process at the client end. The Trustify governance foundation may or may not decide to allow such challenges, but if it does they will&nbsp; be in limited circumstances, where the OV or EV process significant enhances the security of the domain in question, and not a common case for Trustify protocol execution.<br
/><br
/>If Payment challenges exist, they will simply contain a URL for a web page that can specify and accept the payment required.<br
/><br
/><br
/><b>FAILURE REASONS</b><br
/><br
/>Currently, the following reasons for the failure of a session are defined:<br
/><br
/><b>UnsupportedVersion</b>: the requested protocol version is not available.<br
/><b>AbandonedRequest</b>: the client has abandoned the session and is no longer requesting issuance of the certificate.<br
/><b>ServerOutage</b>: the service is temporarily unavailable.<br
/><b>ServerGone</b>: the service is permanently unavailable.<br
/><b>StaleRequest</b>: the request is expired as a result of its age, excessive delay in the client-server interaction, or because the request has previously failed for another reason.<br
/><b>BadSignature</b>: the digital signature used by the client to prove its possession of the private key corresponding to the subject public key is invalid.<br
/><b>BadCSR</b>: the certificate signing request sent by the client is invalid in some way.<br
/><b>BadRequest</b>: the subject public key, one or more subject host names, or some other aspect of the signing request is invalid.<br
/><b>NeedClientPuzzle</b>: as a denial-of-service mitigation measure, the server cannot accept the request without additional proof of work by the client.<br
/><b>CannotIssueThatName</b>: the issuance of a certificate for one or more of the subject host names is administratively prohibited.<br
/><b>ExistingCertificate</b>: a previous certificate for one or more of the subject host names is known to exist and the CA policy does not permit the automated issuance of a new one in response to the current request.<br
/><b>UnsafeKey</b>: the subject public key violates a CA policy or is known to be insecure.<br
/><b>ChallengeFailed</b>: the client's attempt to comply with a challenge was unsuccessful.<br
/><b>ChallengeTimeout</b>: the client did not appear to comply with a challenge within the required period of time.<br
/><br
/>A message of type Failure must contain one of these reasons, and may contain a URI with additional human-readable information about the reason for the failure of the request.<br
/><br
/><b>SECURITY CONSIDERATIONS</b><br
/><br
/>Because the intended application of this protocol causes valuable digital certificates to be issued automatically with no time delay and without human intervention, attackers are likely to be interested in trying to use this protocol to request certificates fraudulently.&nbsp; It should be possible to implement the protocol and verification steps in such a way that the system as a whole is more secure than some existing certificate authority verification processes.<br
/><br
/>Before issuing a certificate, the server needs to perform a large number of validation and policy enforcement steps which are outside the scope of this protocol.&nbsp; For example, the server SHOULD check that the RSA modulus of the submitted subject public key is &gt;=2048 bits and that it is not on any weak RSA modulus blacklist.&nbsp; The server SHOULD check that no CAA records forbid it from issuing a certificate for this domain and that the subject domain is not a high-value domain for which automated certificate issuance should be prevented.&nbsp; The server SHOULD check whether there is any known existing valid certificate for the requested domain and determine under what conditions server policy permits the issuance of a new certificate with concurrent validity.<br
/><br
/>When using DVSNI validation, the server SHOULD perform the probe connection from multiple locations on the Internet to achieve geographic and network topology diversity to reduce the risk that an attacker performs a DNS or BGP attack to appear to control a particular web server.<br
/><br
/>The server SHOULD avoid passing unvalidated or unsanitized data from the client to any server code implemented in a non-bounds-checked language.<br
/><br
/>The server SHOULD use physical controls to isolate a machine capable of directly causing certificate issuance from the public Internet.<br
/><br
/>The server SHOULD attempt to detect fraudulent attempts to issue certificates.<br
/><br
/>The server SHOULD attempt to notify the owner of a site when a certificate was issued, and SHOULD memorialize the issuance publicly using a system such as Certificate Transparency.<br
/><br
/>The client SHOULD be encouraged to use best practices to increase the security of its TLS deployment using the new certificate, such as HSTS and DANE.<br
/><br
/>The server MUST securely generate random session IDs to prevent a malicious client from guessing a valid session ID.&nbsp; The server MUST cause sessions to terminate after a specified period of inactivity or after a fatal error, and prevent any further activity from occurring on a terminated session.&nbsp; After sending a single error message indicating why a session was terminated, the server SHOULD not convey any information to clients about why a particular session is nonexistent or inactive, including whether or not the specified session ever existed.<br
/><br
/>The above list of verification steps may not be exhaustive.&nbsp; Full policy guidelines on these questions will be maintained by the Trustify governance foundation.<br
/><br
/><b>MESSAGE DATA TYPES</b><br
/><br
/>SigningRequest:<br
/><br
/>&nbsp;&nbsp;&nbsp; message SigningRequest {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required int64 timestamp = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string recipient = 3;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string csr = 4;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required bytes sig = 5;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string clientpuzzle = 6;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>timestamp is the Unix timestamp when the request was made.&nbsp; (The server SHOULD verify that this is not significantly in the past or future relative to the time that the server received it.)<br
/><br
/>recipient is the URI of the service to which the client intended to submit the request.<br
/><br
/>csr is a PEM-encoded certificate signing request containing the subject public key and all subject names to which the request relates (using "\n" rather than "\r\n" as newline delimiter).<br
/><br
/>sig is an RSA signature over the preceding values using the private key that corresponds to the subject public key.&nbsp; <b><i>TODO</i></b>: describe how the signature is calculated.<br
/><br
/>clientpuzzle is a hashcash string that refers to the hostname of the server.<br
/><br
/><br
/>Failure:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Failure {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required FailureReason cause = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string URI = 2;&nbsp; /* for more human-readable information */<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>cause is the FailureReason enumerated type reason why the session or request failed or is being abandoned.<br
/><br
/>URI is an optional refernce to a URI where more human-readable information about the failure can be obtained. This can be used to clarify to the human user whether there is an action that could be taken to correct the problem.<br
/><br
/><br
/>Proceed:&nbsp;<br
/><br
/>&nbsp;&nbsp;&nbsp; message Proceed {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required int64 timestamp = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional int32 polldelay = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br
/><br
/>timestamp is the Unix time when the message was issued.&nbsp; When sent by the server, polldelay is a suggested number of seconds to wait before contacting the server again.<br
/><br
/>Challenge:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Challenge {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required ChallengeType type = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string name = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeated bytes data = 3;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string URI = 4;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional bool succeeded = 5;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>type is the type of the challenge (as defined in the enumerated type ChallengeType).<br
/><br
/>name is the name or identifier the server assigned to this particular challenge instance.<br
/><br
/>data is an array of arbitrary byte values used by a particular challenge, whose semantics are defined by the corresponding challenge type.<br
/><br
/>URI is a URI associated with the challenge, whose semantics are defined by the corresponding challenge type.<br
/><br
/>succeeded can be used by the client or the server to indicate whether the party mentioning the challenge believes that the challenge has already been satisfied.<br
/><br
/>Success:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Success {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string certificate = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string chain = 2;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>certificate is the PEM-encoded certificate that was successfully issued.<br
/><br
/>chain is an optional PEM-encoded certificate chain that chains up from the intermediate certificate authority that issued this certificate to a root certificate authority, in order to allow a verifier to validate this certificate.<br
/><br
/></body>
</html>

14
docker-compose.yml Normal file
View file

@ -0,0 +1,14 @@
production:
build: .
ports:
- "443:443"
# For development, mount git root to /opt/letsencrypt/src in order to
# make the dev workflow more vagrant-like.
development:
build: .
ports:
- "443:443"
volumes:
- .:/opt/letsencrypt/src
- /opt/letsencrypt/venv

1
docs/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_build/

183
docs/Makefile Normal file
View file

@ -0,0 +1,183 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LetsEncrypt.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LetsEncrypt.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/LetsEncrypt"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LetsEncrypt"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

8
docs/api.rst Normal file
View file

@ -0,0 +1,8 @@
=================
API Documentation
=================
.. toctree::
:glob:
api/**

5
docs/api/account.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.account`
--------------------------
.. automodule:: letsencrypt.account
:members:

5
docs/api/achallenges.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.achallenges`
------------------------------
.. automodule:: letsencrypt.achallenges
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.auth_handler`
-------------------------------
.. automodule:: letsencrypt.auth_handler
:members:

5
docs/api/client.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.client`
-------------------------
.. automodule:: letsencrypt.client
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.configuration`
--------------------------------
.. automodule:: letsencrypt.configuration
:members:

5
docs/api/constants.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.constants`
-----------------------------------
.. automodule:: letsencrypt.constants
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.continuity_auth`
----------------------------------
.. automodule:: letsencrypt.continuity_auth
:members:

5
docs/api/crypto_util.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.crypto_util`
------------------------------
.. automodule:: letsencrypt.crypto_util
:members:

29
docs/api/display.rst Normal file
View file

@ -0,0 +1,29 @@
:mod:`letsencrypt.display`
--------------------------
.. automodule:: letsencrypt.display
:members:
:mod:`letsencrypt.display.util`
===============================
.. automodule:: letsencrypt.display.util
:members:
:mod:`letsencrypt.display.ops`
==============================
.. automodule:: letsencrypt.display.ops
:members:
:mod:`letsencrypt.display.enhancements`
=======================================
.. automodule:: letsencrypt.display.enhancements
:members:
:mod:`letsencrypt.display.revocation`
=====================================
.. automodule:: letsencrypt.display.revocation
:members:

5
docs/api/errors.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.errors`
-------------------------
.. automodule:: letsencrypt.errors
:members:

5
docs/api/index.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt`
------------------
.. automodule:: letsencrypt
:members:

5
docs/api/interfaces.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.interfaces`
-----------------------------
.. automodule:: letsencrypt.interfaces
:members:

5
docs/api/le_util.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.le_util`
--------------------------
.. automodule:: letsencrypt.le_util
:members:

5
docs/api/log.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.log`
----------------------
.. automodule:: letsencrypt.log
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.plugins.common`
---------------------------------
.. automodule:: letsencrypt.plugins.common
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.plugins.disco`
--------------------------------
.. automodule:: letsencrypt.plugins.disco
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.plugins.manual`
---------------------------------
.. automodule:: letsencrypt.plugins.manual
:members:

View file

@ -0,0 +1,11 @@
:mod:`letsencrypt.plugins.standalone`
-------------------------------------
.. automodule:: letsencrypt.plugins.standalone
:members:
:mod:`letsencrypt.plugins.standalone.authenticator`
===================================================
.. automodule:: letsencrypt.plugins.standalone.authenticator
:members:

View file

@ -0,0 +1,5 @@
:mod:`letsencrypt.proof_of_possession`
--------------------------------------
.. automodule:: letsencrypt.proof_of_possession
:members:

Some files were not shown because too many files have changed in this diff Show more