mirror of
https://github.com/certbot/certbot.git
synced 2026-06-08 08:12:15 -04:00
Merge branch 'master' into mageia
This commit is contained in:
commit
079e6ee139
573 changed files with 21003 additions and 7780 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -6,7 +6,10 @@ dist*/
|
|||
/venv*/
|
||||
/kgs/
|
||||
/.tox/
|
||||
/releases/
|
||||
letsencrypt.log
|
||||
certbot.log
|
||||
letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64
|
||||
|
||||
# coverage
|
||||
.coverage
|
||||
|
|
@ -22,3 +25,8 @@ letsencrypt.log
|
|||
|
||||
# auth --cert-path --chain-path
|
||||
/*.pem
|
||||
|
||||
# letstest
|
||||
tests/letstest/letest-*/
|
||||
tests/letstest/*.pem
|
||||
tests/letstest/venv/
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ load-plugins=linter_plugin
|
|||
# --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-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes
|
||||
disable=fixme,locally-disabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import
|
||||
# abstract-class-not-used cannot be disabled locally (at least in
|
||||
# pylint 1.4.1), same for abstract-class-little-used
|
||||
|
||||
|
|
|
|||
87
.travis.yml
87
.travis.yml
|
|
@ -1,28 +1,66 @@
|
|||
language: python
|
||||
|
||||
services:
|
||||
- rabbitmq
|
||||
- mariadb
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
# This makes sure we get a host with docker-compose present.
|
||||
dist: trusty
|
||||
|
||||
# http://docs.travis-ci.com/user/ci-environment/#CI-environment-OS
|
||||
# gimme has to be kept in sync with Boulder's Go version setting in .travis.yml
|
||||
before_install:
|
||||
- 'dpkg -s libaugeas0'
|
||||
- '[ "xxx$BOULDER_INTEGRATION" = "xxx" ] || eval "$(gimme 1.5.1)"'
|
||||
|
||||
# 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
|
||||
- PATH=$GOPATH/bin:$PATH
|
||||
matrix:
|
||||
- TOXENV=py26 BOULDER_INTEGRATION=1
|
||||
- TOXENV=py27 BOULDER_INTEGRATION=1
|
||||
- TOXENV=lint
|
||||
- TOXENV=cover
|
||||
- BOULDERPATH=$PWD/boulder/
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- python: "2.6"
|
||||
env: TOXENV=py26 BOULDER_INTEGRATION=1
|
||||
sudo: true
|
||||
after_failure:
|
||||
- sudo cat /var/log/mysql/error.log
|
||||
- ps aux | grep mysql
|
||||
- python: "2.6"
|
||||
env: TOXENV=py26-oldest BOULDER_INTEGRATION=1
|
||||
sudo: true
|
||||
after_failure:
|
||||
- sudo cat /var/log/mysql/error.log
|
||||
- ps aux | grep mysql
|
||||
- python: "2.7"
|
||||
env: TOXENV=apacheconftest
|
||||
sudo: required
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27 BOULDER_INTEGRATION=1
|
||||
sudo: true
|
||||
after_failure:
|
||||
- sudo cat /var/log/mysql/error.log
|
||||
- ps aux | grep mysql
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-oldest BOULDER_INTEGRATION=1
|
||||
sudo: true
|
||||
after_failure:
|
||||
- sudo cat /var/log/mysql/error.log
|
||||
- ps aux | grep mysql
|
||||
- python: "2.7"
|
||||
env: TOXENV=lint
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto
|
||||
services: docker
|
||||
before_install:
|
||||
addons:
|
||||
- python: "2.7"
|
||||
env: TOXENV=cover
|
||||
- python: "3.3"
|
||||
env: TOXENV=py33
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35
|
||||
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
|
||||
|
|
@ -36,15 +74,21 @@ branches:
|
|||
sudo: false
|
||||
|
||||
addons:
|
||||
# make sure simplehttp simple verification works (custom /etc/hosts)
|
||||
# Custom /etc/hosts required for simple verification of http-01
|
||||
# and tls-sni-01, and for certbot_test_nginx
|
||||
hosts:
|
||||
- le.wtf
|
||||
mariadb: "10.0"
|
||||
- le1.wtf
|
||||
- le2.wtf
|
||||
- le3.wtf
|
||||
- nginx.wtf
|
||||
- boulder
|
||||
- boulder-mysql
|
||||
- boulder-rabbitmq
|
||||
apt:
|
||||
sources:
|
||||
- augeas
|
||||
packages: # keep in sync with bootstrap/ubuntu.sh and Boulder
|
||||
- python
|
||||
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
|
||||
- python-dev
|
||||
- python-virtualenv
|
||||
- gcc
|
||||
|
|
@ -53,11 +97,14 @@ addons:
|
|||
- libssl-dev
|
||||
- libffi-dev
|
||||
- ca-certificates
|
||||
# For letsencrypt-nginx integration testing
|
||||
# For certbot-nginx integration testing
|
||||
- nginx-light
|
||||
- openssl
|
||||
# For Boulder integration testing
|
||||
- rsyslog
|
||||
# for apacheconftest
|
||||
- apache2
|
||||
- libapache2-mod-wsgi
|
||||
- libapache2-mod-macro
|
||||
- sudo
|
||||
|
||||
install: "travis_retry pip install tox coveralls"
|
||||
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/travis-integration.sh)'
|
||||
|
|
|
|||
24
CHANGES.rst
24
CHANGES.rst
|
|
@ -3,25 +3,9 @@ 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>`_.
|
||||
`commit log <https://github.com/certbot/certbot/commits/master>`_.
|
||||
|
||||
To see the changes in a given release, inspect the github milestone for the
|
||||
release. For instance:
|
||||
|
||||
Release 0.1.0 (not released yet)
|
||||
--------------------------------
|
||||
|
||||
New Features:
|
||||
|
||||
* ...
|
||||
|
||||
Fixes:
|
||||
|
||||
* ...
|
||||
|
||||
Other changes:
|
||||
|
||||
* ...
|
||||
|
||||
Release 0.0.0 (not released yet)
|
||||
--------------------------------
|
||||
|
||||
Initial release.
|
||||
https://github.com/certbot/certbot/issues?utf8=%E2%9C%93&q=milestone%3A0.3.0
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ to the Sphinx generated docs is provided below.
|
|||
|
||||
-->
|
||||
|
||||
https://letsencrypt.readthedocs.org/en/latest/contributing.html
|
||||
https://certbot.eff.org/docs/contributing.html
|
||||
|
|
|
|||
47
Dockerfile
47
Dockerfile
|
|
@ -10,20 +10,21 @@ MAINTAINER William Budington <bill@eff.org>
|
|||
EXPOSE 443
|
||||
|
||||
# TODO: make sure --config-dir and --work-dir cannot be changed
|
||||
# through the CLI (letsencrypt-docker wrapper that uses standalone
|
||||
# through the CLI (certbot-docker wrapper that uses standalone
|
||||
# authenticator and text mode only?)
|
||||
VOLUME /etc/letsencrypt /var/lib/letsencrypt
|
||||
|
||||
WORKDIR /opt/letsencrypt
|
||||
WORKDIR /opt/certbot
|
||||
|
||||
# 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.
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/ubuntu.sh
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto
|
||||
RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
|
|
@ -33,35 +34,37 @@ RUN /opt/letsencrypt/src/ubuntu.sh && \
|
|||
# Dockerfile we make sure we cache as much as possible
|
||||
|
||||
|
||||
COPY setup.py README.rst CHANGES.rst MANIFEST.in /opt/letsencrypt/src/
|
||||
COPY setup.py README.rst CHANGES.rst MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/
|
||||
|
||||
# all above files are necessary for setup.py, however, package source
|
||||
# code directory has to be copied separately to a subdirectory...
|
||||
# all above files are necessary for setup.py and venv setup, 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/
|
||||
COPY certbot /opt/certbot/src/certbot/
|
||||
COPY acme /opt/certbot/src/acme/
|
||||
COPY certbot-apache /opt/certbot/src/certbot-apache/
|
||||
COPY certbot-nginx /opt/certbot/src/certbot-nginx/
|
||||
|
||||
|
||||
# py26reqs.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
|
||||
RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv
|
||||
|
||||
# PATH is set now so pipstrap upgrades the correct (v)env
|
||||
ENV PATH /opt/certbot/venv/bin:$PATH
|
||||
RUN /opt/certbot/venv/bin/python /opt/certbot/src/pipstrap.py && \
|
||||
/opt/certbot/venv/bin/pip install \
|
||||
-e /opt/certbot/src/acme \
|
||||
-e /opt/certbot/src \
|
||||
-e /opt/certbot/src/certbot-apache \
|
||||
-e /opt/certbot/src/certbot-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);
|
||||
# "rm -rf /opt/certbot/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
|
||||
|
||||
ENTRYPOINT [ "letsencrypt" ]
|
||||
ENTRYPOINT [ "certbot" ]
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ MAINTAINER Yan <yan@eff.org>
|
|||
EXPOSE 443
|
||||
|
||||
# TODO: make sure --config-dir and --work-dir cannot be changed
|
||||
# through the CLI (letsencrypt-docker wrapper that uses standalone
|
||||
# through the CLI (certbot-docker wrapper that uses standalone
|
||||
# authenticator and text mode only?)
|
||||
VOLUME /etc/letsencrypt /var/lib/letsencrypt
|
||||
|
||||
WORKDIR /opt/letsencrypt
|
||||
WORKDIR /opt/certbot
|
||||
|
||||
# no need to mkdir anything:
|
||||
# https://docs.docker.com/reference/builder/#copy
|
||||
|
|
@ -22,8 +22,8 @@ WORKDIR /opt/letsencrypt
|
|||
|
||||
# TODO: Install non-default Python versions for tox.
|
||||
# TODO: Install Apache/Nginx for plugin development.
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/ubuntu.sh
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto
|
||||
RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
|
|
@ -32,8 +32,7 @@ RUN /opt/letsencrypt/src/ubuntu.sh && \
|
|||
# the above is not likely to change, so by putting it further up the
|
||||
# Dockerfile we make sure we cache as much as possible
|
||||
|
||||
# py26reqs.txt not installed!
|
||||
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/letsencrypt/src/
|
||||
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/certbot/src/
|
||||
|
||||
# all above files are necessary for setup.py, however, package source
|
||||
# code directory has to be copied separately to a subdirectory...
|
||||
|
|
@ -43,27 +42,27 @@ COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh t
|
|||
# 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/
|
||||
COPY letshelp-letsencrypt /opt/letsencrypt/src/letshelp-letsencrypt/
|
||||
COPY letsencrypt-compatibility-test /opt/letsencrypt/src/letsencrypt-compatibility-test/
|
||||
COPY tests /opt/letsencrypt/src/tests/
|
||||
COPY certbot /opt/certbot/src/certbot/
|
||||
COPY acme /opt/certbot/src/acme/
|
||||
COPY certbot-apache /opt/certbot/src/certbot-apache/
|
||||
COPY certbot-nginx /opt/certbot/src/certbot-nginx/
|
||||
COPY letshelp-certbot /opt/certbot/src/letshelp-certbot/
|
||||
COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/
|
||||
COPY tests /opt/certbot/src/tests/
|
||||
|
||||
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 \
|
||||
-e /opt/letsencrypt/src/letshelp-letsencrypt \
|
||||
-e /opt/letsencrypt/src/letsencrypt-compatibility-test \
|
||||
-e /opt/letsencrypt/src[dev,docs,testing]
|
||||
RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
|
||||
/opt/certbot/venv/bin/pip install \
|
||||
-e /opt/certbot/src/acme \
|
||||
-e /opt/certbot/src \
|
||||
-e /opt/certbot/src/certbot-apache \
|
||||
-e /opt/certbot/src/certbot-nginx \
|
||||
-e /opt/certbot/src/letshelp-certbot \
|
||||
-e /opt/certbot/src/certbot-compatibility-test \
|
||||
-e /opt/certbot/src[dev,docs]
|
||||
|
||||
# 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);
|
||||
# "rm -rf /opt/certbot/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
|
||||
ENV PATH /opt/certbot/venv/bin:$PATH
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Let's Encrypt Python Client
|
||||
Certbot ACME Client
|
||||
Copyright (c) Electronic Frontier Foundation and others
|
||||
Licensed Apache Version 2.0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
include py26reqs.txt
|
||||
include README.rst
|
||||
include CHANGES.rst
|
||||
include CONTRIBUTING.md
|
||||
|
|
@ -6,4 +5,4 @@ include LICENSE.txt
|
|||
include linter_plugin.py
|
||||
recursive-include docs *
|
||||
recursive-include examples *
|
||||
recursive-include letsencrypt/tests/testdata *
|
||||
recursive-include certbot/tests/testdata *
|
||||
|
|
|
|||
119
README.rst
119
README.rst
|
|
@ -3,59 +3,80 @@
|
|||
Disclaimer
|
||||
==========
|
||||
|
||||
The Let's Encrypt Client is **BETA SOFTWARE**. It contains plenty of bugs and
|
||||
rough edges, and should be tested thoroughly in staging environments before use
|
||||
on production systems.
|
||||
Certbot (previously, the Let's Encrypt client) is **BETA SOFTWARE**. It
|
||||
contains plenty of bugs and rough edges, and should be tested thoroughly in
|
||||
staging environments before use on production systems.
|
||||
|
||||
For more information regarding the status of the project, please see
|
||||
https://letsencrypt.org. Be sure to checkout the
|
||||
`Frequently Asked Questions (FAQ) <https://community.letsencrypt.org/t/frequently-asked-questions-faq/26#topic-title>`_.
|
||||
|
||||
About the Let's Encrypt Client
|
||||
About Certbot
|
||||
==============================
|
||||
|
||||
The Let's Encrypt Client is a fully-featured, extensible client for the Let's
|
||||
Certbot is a fully-featured, extensible client for the Let's
|
||||
Encrypt CA (or any other CA that speaks the `ACME
|
||||
<https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md>`_
|
||||
protocol) that can automate the tasks of obtaining certificates and
|
||||
configuring webservers to use them.
|
||||
configuring webservers to use them. This client runs on Unix-based operating
|
||||
systems.
|
||||
|
||||
Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``,
|
||||
depending on install method. Instructions on the Internet, and some pieces of the
|
||||
software, may still refer to this older name.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
If you'd like to contribute to this project please read `Developer Guide
|
||||
<https://certbot.eff.org/docs/contributing.html>`_.
|
||||
|
||||
.. _installation:
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
If ``letsencrypt`` is packaged for your OS, you can install it from there, and
|
||||
run it by typing ``letsencrypt``. Because not all operating systems have
|
||||
packages yet, we provide a temporary solution via the ``letsencrypt-auto``
|
||||
wrapper script, which obtains some dependencies from your OS and puts others
|
||||
in a python virtual environment::
|
||||
If ``certbot`` (or ``letsencrypt``) is packaged for your Unix OS (visit
|
||||
certbot.eff.org_ to find out), you can install it
|
||||
from there, and run it by typing ``certbot`` (or ``letsencrypt``). Because
|
||||
not all operating systems have packages yet, we provide a temporary solution
|
||||
via the ``certbot-auto`` wrapper script, which obtains some dependencies from
|
||||
your OS and puts others in a python virtual environment::
|
||||
|
||||
user@webserver:~$ git clone https://github.com/letsencrypt/letsencrypt
|
||||
user@webserver:~$ cd letsencrypt
|
||||
user@webserver:~/letsencrypt$ ./letsencrypt-auto --help
|
||||
user@webserver:~$ wget https://dl.eff.org/certbot-auto
|
||||
user@webserver:~$ chmod a+x ./certbot-auto
|
||||
user@webserver:~$ ./certbot-auto --help
|
||||
|
||||
Or for full command line help, type::
|
||||
.. hint:: The certbot-auto download is protected by HTTPS, which is pretty good, but if you'd like to
|
||||
double check the integrity of the ``certbot-auto`` script, you can use these steps for verification before running it::
|
||||
|
||||
./letsencrypt-auto --help all
|
||||
user@server:~$ wget -N https://dl.eff.org/certbot-auto.asc
|
||||
user@server:~$ gpg2 --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
|
||||
user@server:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc certbot-auto
|
||||
|
||||
``letsencrypt-auto`` updates to the latest client release automatically. And
|
||||
since ``letsencrypt-auto`` is a wrapper to ``letsencrypt``, it accepts exactly
|
||||
And for full command line help, you can type::
|
||||
|
||||
./certbot-auto --help all
|
||||
|
||||
``certbot-auto`` updates to the latest client release automatically. And
|
||||
since ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly
|
||||
the same command line flags and arguments. More details about this script and
|
||||
other installation methods can be found `in the User Guide
|
||||
<https://letsencrypt.readthedocs.org/en/latest/using.html#installation>`_.
|
||||
<https://certbot.eff.org/docs/using.html#installation>`_.
|
||||
|
||||
How to run the client
|
||||
---------------------
|
||||
|
||||
In many cases, you can just run ``letsencrypt-auto`` or ``letsencrypt``, and the
|
||||
In many cases, you can just run ``certbot-auto`` or ``certbot``, and the
|
||||
client will guide you through the process of obtaining and installing certs
|
||||
interactively.
|
||||
|
||||
You can also tell it exactly what you want it to do from the command line.
|
||||
For instance, if you want to obtain a cert for ``thing.com``,
|
||||
``www.thing.com``, and ``otherthing.net``, using the Apache plugin to both
|
||||
For instance, if you want to obtain a cert for ``example.com``,
|
||||
``www.example.com``, and ``other.example.net``, using the Apache plugin to both
|
||||
obtain and install the certs, you could do this::
|
||||
|
||||
./letsencrypt-auto --apache -d thing.com -d www.thing.com -d otherthing.net
|
||||
./certbot-auto --apache -d example.com -d www.example.com -d other.example.net
|
||||
|
||||
(The first time you run the command, it will make an account, and ask for an
|
||||
email and agreement to the Let's Encrypt Subscriber Agreement; you can
|
||||
|
|
@ -64,7 +85,7 @@ automate those with ``--email`` and ``--agree-tos``)
|
|||
If you want to use a webserver that doesn't have full plugin support yet, you
|
||||
can still use "standalone" or "webroot" plugins to obtain a certificate::
|
||||
|
||||
./letsencrypt-auto certonly --standalone --email admin@thing.com -d thing.com -d www.thing.com -d otherthing.net
|
||||
./certbot-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net
|
||||
|
||||
|
||||
Understanding the client in more depth
|
||||
|
|
@ -72,24 +93,29 @@ Understanding the client in more depth
|
|||
|
||||
To understand what the client is doing in detail, it's important to
|
||||
understand the way it uses plugins. Please see the `explanation of
|
||||
plugins <https://letsencrypt.readthedocs.org/en/latest/using.html#plugins>`_ in
|
||||
plugins <https://certbot.eff.org/docs/using.html#plugins>`_ in
|
||||
the User Guide.
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
Documentation: https://letsencrypt.readthedocs.org
|
||||
Documentation: https://certbot.eff.org/docs
|
||||
|
||||
Software project: https://github.com/letsencrypt/letsencrypt
|
||||
Software project: https://github.com/certbot/certbot
|
||||
|
||||
Notes for developers: https://letsencrypt.readthedocs.org/en/latest/contributing.html
|
||||
Notes for developers: https://certbot.eff.org/docs/contributing.html
|
||||
|
||||
Main Website: https://letsencrypt.org/
|
||||
|
||||
IRC Channel: #letsencrypt on `Freenode`_
|
||||
IRC Channel: #letsencrypt on `Freenode`_ or #certbot on `OFTC`_
|
||||
|
||||
Community: https://community.letsencrypt.org
|
||||
|
||||
ACME spec: http://ietf-wg-acme.github.io/acme/
|
||||
|
||||
ACME working area in github: https://github.com/ietf-wg-acme/acme
|
||||
|
||||
|
||||
Mailing list: `client-dev`_ (to subscribe without a Google account, send an
|
||||
email to client-dev+subscribe@letsencrypt.org)
|
||||
|
||||
|
|
@ -97,12 +123,12 @@ email to client-dev+subscribe@letsencrypt.org)
|
|||
|
||||
|
||||
|
||||
.. |build-status| image:: https://travis-ci.org/letsencrypt/letsencrypt.svg?branch=master
|
||||
:target: https://travis-ci.org/letsencrypt/letsencrypt
|
||||
.. |build-status| image:: https://travis-ci.org/certbot/certbot.svg?branch=master
|
||||
:target: https://travis-ci.org/certbot/certbot
|
||||
:alt: Travis CI status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/letsencrypt/letsencrypt/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/letsencrypt/letsencrypt
|
||||
.. |coverage| image:: https://coveralls.io/repos/certbot/certbot/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/certbot/certbot
|
||||
:alt: Coverage status
|
||||
|
||||
.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/
|
||||
|
|
@ -122,16 +148,15 @@ System Requirements
|
|||
===================
|
||||
|
||||
The Let's Encrypt Client presently only runs on Unix-ish OSes that include
|
||||
Python 2.6 or 2.7; Python 3.x support will be added after the Public Beta
|
||||
launch. The client requires root access in order to write to
|
||||
``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to
|
||||
bind to ports 80 and 443 (if you use the ``standalone`` plugin) and to read and
|
||||
modify webserver configurations (if you use the ``apache`` or ``nginx``
|
||||
plugins). If none of these apply to you, it is theoretically possible to run
|
||||
without root privileges, but for most users who want to avoid running an ACME
|
||||
client as root, either `letsencrypt-nosudo
|
||||
<https://github.com/diafygi/letsencrypt-nosudo>`_ or `simp_le
|
||||
<https://github.com/kuba/simp_le>`_ are more appropriate choices.
|
||||
Python 2.6 or 2.7; Python 3.x support will hopefully be added in the future. The
|
||||
client requires root access in order to write to ``/etc/letsencrypt``,
|
||||
``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to bind to ports 80 and 443
|
||||
(if you use the ``standalone`` plugin) and to read and modify webserver
|
||||
configurations (if you use the ``apache`` or ``nginx`` plugins). If none of
|
||||
these apply to you, it is theoretically possible to run without root privileges,
|
||||
but for most users who want to avoid running an ACME client as root, either
|
||||
`letsencrypt-nosudo <https://github.com/diafygi/letsencrypt-nosudo>`_ or
|
||||
`simp_le <https://github.com/kuba/simp_le>`_ are more appropriate choices.
|
||||
|
||||
The Apache plugin currently requires a Debian-based OS with augeas version
|
||||
1.0; this includes Ubuntu 12.04+ and Debian 7+.
|
||||
|
|
@ -146,10 +171,10 @@ Current Features
|
|||
- standalone (runs its own simple webserver to prove you control a domain)
|
||||
- webroot (adds files to webroot directories in order to prove control of
|
||||
domains and obtain certs)
|
||||
- nginx/0.8.48+ (highly experimental, not included in letsencrypt-auto)
|
||||
- nginx/0.8.48+ (highly experimental, not included in certbot-auto)
|
||||
|
||||
* The private key is generated locally on your system.
|
||||
* Can talk to the Let's Encrypt CA or optionally to other ACME
|
||||
* Can talk to the Let's Encrypt CA or optionally to other ACME
|
||||
compliant services.
|
||||
* Can get domain-validated (DV) certificates.
|
||||
* Can revoke certificates.
|
||||
|
|
@ -163,5 +188,7 @@ Current Features
|
|||
* Free and Open Source Software, made with Python.
|
||||
|
||||
|
||||
.. _Freenode: https://freenode.net
|
||||
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
|
||||
.. _OFTC: https://webchat.oftc.net?channels=%23certbot
|
||||
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
|
||||
.. _certbot.eff.org: https://certbot.eff.org/
|
||||
|
|
|
|||
17
Vagrantfile
vendored
17
Vagrantfile
vendored
|
|
@ -5,10 +5,19 @@
|
|||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
# Setup instructions from docs/contributing.rst
|
||||
# Script installs dependencies for tox and boulder integration
|
||||
$ubuntu_setup_script = <<SETUP_SCRIPT
|
||||
cd /vagrant
|
||||
./bootstrap/install-deps.sh
|
||||
./bootstrap/dev/venv.sh
|
||||
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
|
||||
./tools/venv.sh
|
||||
wget https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz -P /tmp/
|
||||
sudo tar -C /usr/local -xzf /tmp/go1.5.3.linux-amd64.tar.gz
|
||||
if ! grep -Fxq "export GOROOT=/usr/local/go" /home/vagrant/.profile ; then echo "export GOROOT=/usr/local/go" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "export PATH=\\$GOROOT/bin:\\$PATH" /home/vagrant/.profile ; then echo "export PATH=\\$GOROOT/bin:\\$PATH" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "export GOPATH=\\$HOME/go" /home/vagrant/.profile ; then echo "export GOPATH=\\$HOME/go" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "cd /vagrant/; ./tests/boulder-start.sh &" /etc/rc.local ; then sed -i -e '$i \cd /vagrant/; ./tests/boulder-start.sh &\n' /etc/rc.local; fi
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo -E apt-get -q -y install git make libltdl-dev mariadb-server rabbitmq-server nginx-light
|
||||
SETUP_SCRIPT
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
|
@ -21,6 +30,10 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
# Cannot allocate memory" when running
|
||||
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
|
||||
v.memory = 1024
|
||||
|
||||
# Handle cases when the host is behind a private network by making the
|
||||
# NAT engine use the host's resolver mechanisms to handle DNS requests.
|
||||
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
4
acme/.pep8
Normal file
4
acme/.pep8
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[pep8]
|
||||
# E265 block comment should start with '# '
|
||||
# E501 line too long (X > 79 characters)
|
||||
ignore = E265,E501
|
||||
383
acme/.pylintrc
Normal file
383
acme/.pylintrc
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
[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
|
||||
|
||||
# DEPRECATED
|
||||
include-ids=no
|
||||
|
||||
# DEPRECATED
|
||||
symbols=no
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=1
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# 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
|
||||
# bstract-class-not-used cannot be disabled locally (at least in
|
||||
# pylint 1.4.1/2)
|
||||
|
||||
|
||||
|
||||
[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=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# 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=1000
|
||||
|
||||
# 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
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[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
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[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=
|
||||
|
||||
# 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=no
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[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=_$|dummy|unused
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
|
||||
[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,input
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_,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,30}$
|
||||
|
||||
# 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,49}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines 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
|
||||
|
||||
# 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
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,_fields,_replace,_source,_make
|
||||
|
||||
|
||||
[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
|
||||
|
||||
|
||||
[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=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
"""ACME protocol implementation.
|
||||
|
||||
This module is an implementation of the `ACME protocol`_. Latest
|
||||
supported version: `v02`_.
|
||||
supported version: `draft-ietf-acme-01`_.
|
||||
|
||||
.. _`ACME protocol`: https://github.com/letsencrypt/acme-spec
|
||||
|
||||
.. _`v02`:
|
||||
https://github.com/letsencrypt/acme-spec/commit/d328fea2d507deb9822793c512830d827a4150c4
|
||||
.. _`ACME protocol`: https://ietf-wg-acme.github.io/acme
|
||||
|
||||
.. _`draft-ietf-acme-01`:
|
||||
https://github.com/ietf-wg-acme/acme/tree/draft-ietf-acme-acme-01
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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__)
|
||||
|
|
@ -36,14 +35,6 @@ class Challenge(jose.TypedJSONObjectWithFields):
|
|||
return UnrecognizedChallenge.from_json(jobj)
|
||||
|
||||
|
||||
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."""
|
||||
|
|
@ -78,8 +69,8 @@ class UnrecognizedChallenge(Challenge):
|
|||
return cls(jobj)
|
||||
|
||||
|
||||
class _TokenDVChallenge(DVChallenge):
|
||||
"""DV Challenge with token.
|
||||
class _TokenChallenge(Challenge):
|
||||
"""Challenge with token.
|
||||
|
||||
:ivar bytes token:
|
||||
|
||||
|
|
@ -149,7 +140,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
|
|||
return True
|
||||
|
||||
|
||||
class KeyAuthorizationChallenge(_TokenDVChallenge):
|
||||
class KeyAuthorizationChallenge(_TokenChallenge):
|
||||
# pylint: disable=abstract-class-little-used,too-many-ancestors
|
||||
"""Challenge based on Key Authorization.
|
||||
|
||||
|
|
@ -236,10 +227,8 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
|||
|
||||
:param challenges.SimpleHTTP chall: Corresponding challenge.
|
||||
:param unicode domain: Domain name being verified.
|
||||
:param account_public_key: Public key for the key pair
|
||||
being authorized. If ``None`` key verification is not
|
||||
performed!
|
||||
:param JWK account_public_key:
|
||||
:param JWK account_public_key: Public key for the key pair
|
||||
being authorized.
|
||||
:param int port: Port used in the validation.
|
||||
|
||||
:returns: ``True`` iff validation is successful, ``False``
|
||||
|
|
@ -336,7 +325,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
|||
"""
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
def z(self): # pylint: disable=invalid-name
|
||||
"""``z`` value used for verification.
|
||||
|
||||
:rtype bytes:
|
||||
|
|
@ -391,7 +380,14 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
|||
return crypto_util.probe_sni(**kwargs)
|
||||
|
||||
def verify_cert(self, cert):
|
||||
"""Verify tls-sni-01 challenge certificate."""
|
||||
"""Verify tls-sni-01 challenge certificate.
|
||||
|
||||
:param OpensSSL.crypto.X509 cert: Challenge certificate.
|
||||
|
||||
:returns: Whether the certificate was successfully verified.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
|
||||
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
|
||||
|
|
@ -455,108 +451,8 @@ class TLSSNI01(KeyAuthorizationChallenge):
|
|||
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
|
||||
|
||||
|
||||
@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 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 jwk: JSON Web Key
|
||||
: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 # pylint: disable=too-many-ancestors
|
||||
class DNS(_TokenDVChallenge):
|
||||
class DNS(_TokenChallenge):
|
||||
"""ACME "dns" challenge."""
|
||||
typ = "dns"
|
||||
|
||||
|
|
@ -604,7 +500,7 @@ class DNS(_TokenDVChallenge):
|
|||
|
||||
"""
|
||||
return DNSResponse(validation=self.gen_validation(
|
||||
self, account_key, **kwargs))
|
||||
account_key, **kwargs))
|
||||
|
||||
def validation_domain_name(self, name):
|
||||
"""Domain name for TXT validation record.
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err
|
|||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import other
|
||||
from acme import test_util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
|
||||
|
||||
|
||||
|
|
@ -73,7 +72,8 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
|
|||
def test_verify_wrong_form(self):
|
||||
from acme.challenges import KeyAuthorizationChallengeResponse
|
||||
response = KeyAuthorizationChallengeResponse(
|
||||
key_authorization='.foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
|
||||
key_authorization='.foo.oKGqedy-b-acd5eoybm2f-'
|
||||
'NVFxvyOoET5CNy3xnv8WY')
|
||||
self.assertFalse(response.verify(self.chall, KEY.public_key()))
|
||||
|
||||
|
||||
|
|
@ -273,10 +273,12 @@ class TLSSNI01ResponseTest(unittest.TestCase):
|
|||
@mock.patch('acme.challenges.TLSSNI01Response.verify_cert', autospec=True)
|
||||
def test_simple_verify(self, mock_verify_cert):
|
||||
mock_verify_cert.return_value = mock.sentinel.verification
|
||||
self.assertEqual(mock.sentinel.verification, self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key(),
|
||||
cert=mock.sentinel.cert))
|
||||
mock_verify_cert.assert_called_once_with(self.response, mock.sentinel.cert)
|
||||
self.assertEqual(
|
||||
mock.sentinel.verification, self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key(),
|
||||
cert=mock.sentinel.cert))
|
||||
mock_verify_cert.assert_called_once_with(
|
||||
self.response, mock.sentinel.cert)
|
||||
|
||||
@mock.patch('acme.challenges.TLSSNI01Response.probe_cert')
|
||||
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
|
||||
|
|
@ -321,233 +323,6 @@ class TLSSNI01Test(unittest.TestCase):
|
|||
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
|
||||
|
||||
|
||||
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 ProofOfPossessionHintsTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
jwk = 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=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=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):
|
||||
|
|
@ -590,7 +365,8 @@ class DNSTest(unittest.TestCase):
|
|||
|
||||
def test_check_validation_wrong_fields(self):
|
||||
bad_validation = jose.JWS.sign(
|
||||
payload=self.msg.update(token=b'x' * 20).json_dumps().encode('utf-8'),
|
||||
payload=self.msg.update(
|
||||
token=b'x' * 20).json_dumps().encode('utf-8'),
|
||||
alg=jose.RS256, key=KEY)
|
||||
self.assertFalse(self.msg.check_validation(
|
||||
bad_validation, KEY.public_key()))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""ACME client API."""
|
||||
import collections
|
||||
import datetime
|
||||
from email.utils import parsedate_tz
|
||||
import heapq
|
||||
import logging
|
||||
import time
|
||||
|
|
@ -10,7 +12,6 @@ from six.moves import http_client # pylint: disable=import-error
|
|||
import OpenSSL
|
||||
import requests
|
||||
import sys
|
||||
import werkzeug
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
|
|
@ -20,7 +21,9 @@ from acme import messages
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Python does not validate certificates by default before version 2.7.9
|
||||
# Prior to Python 2.7.9 the stdlib SSL module did not allow a user to configure
|
||||
# many important security related options. On these platforms we use PyOpenSSL
|
||||
# for SSL, which does allow these options to be configured.
|
||||
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
|
||||
if sys.version_info < (2, 7, 9): # pragma: no cover
|
||||
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()
|
||||
|
|
@ -64,15 +67,13 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
@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 'terms-of-service' in response.links:
|
||||
terms_of_service = response.links['terms-of-service']['url']
|
||||
if 'next' in response.links:
|
||||
new_authzr_uri = response.links['next']['url']
|
||||
|
||||
if new_authzr_uri is None:
|
||||
try:
|
||||
new_authzr_uri = response.links['next']['url']
|
||||
except KeyError:
|
||||
raise errors.ClientError('"next" link missing')
|
||||
raise errors.ClientError('"next" link missing')
|
||||
|
||||
return messages.RegistrationResource(
|
||||
body=messages.Registration.from_json(response.json()),
|
||||
|
|
@ -179,40 +180,41 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
raise errors.UnexpectedUpdate(authzr)
|
||||
return authzr
|
||||
|
||||
def request_challenges(self, identifier, new_authzr_uri):
|
||||
def request_challenges(self, identifier, new_authzr_uri=None):
|
||||
"""Request challenges.
|
||||
|
||||
:param identifier: Identifier to be challenged.
|
||||
:type identifier: `.messages.Identifier`
|
||||
|
||||
:param str new_authzr_uri: new-authorization URI
|
||||
:param .messages.Identifier identifier: Identifier to be challenged.
|
||||
:param str new_authzr_uri: ``new-authorization`` URI. If omitted,
|
||||
will default to value found in ``directory``.
|
||||
|
||||
:returns: Authorization Resource.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
"""
|
||||
new_authz = messages.NewAuthorization(identifier=identifier)
|
||||
response = self.net.post(new_authzr_uri, new_authz)
|
||||
response = self.net.post(self.directory.new_authz
|
||||
if new_authzr_uri is None else 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):
|
||||
def request_domain_challenges(self, domain, new_authzr_uri=None):
|
||||
"""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.
|
||||
generic identifiers. See ``request_challenges`` for more
|
||||
documentation.
|
||||
|
||||
: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)
|
||||
typ=messages.IDENTIFIER_FQDN, value=domain), new_authzr_uri)
|
||||
|
||||
def answer_challenge(self, challb, response):
|
||||
"""Answer challenge.
|
||||
|
|
@ -246,6 +248,9 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
def retry_after(cls, response, default):
|
||||
"""Compute next `poll` time based on response ``Retry-After`` header.
|
||||
|
||||
Handles integers and various datestring formats per
|
||||
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37
|
||||
|
||||
:param requests.Response response: Response from `poll`.
|
||||
:param int default: Default value (in seconds), used when
|
||||
``Retry-After`` header is not present or invalid.
|
||||
|
|
@ -258,12 +263,16 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
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
|
||||
# The RFC 2822 parser handles all of RFC 2616's cases in modern
|
||||
# environments (primarily HTTP 1.1+ but also py27+)
|
||||
when = parsedate_tz(retry_after)
|
||||
if when is not None:
|
||||
try:
|
||||
tz_secs = datetime.timedelta(when[-1] if when[-1] else 0)
|
||||
return datetime.datetime(*when[:7]) - tz_secs
|
||||
except (ValueError, OverflowError):
|
||||
pass
|
||||
seconds = default
|
||||
|
||||
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
|
||||
|
||||
|
|
@ -334,11 +343,12 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
:param authzrs: `list` of `.AuthorizationResource`
|
||||
:param int mintime: Minimum time before next attempt, used if
|
||||
``Retry-After`` is not present in the response.
|
||||
:param int max_attempts: Maximum number of attempts before
|
||||
`PollError` with non-empty ``waiting`` is raised.
|
||||
:param int max_attempts: Maximum number of attempts (per
|
||||
authorization) before `PollError` with non-empty ``waiting``
|
||||
is raised.
|
||||
|
||||
:returns: ``(cert, updated_authzrs)`` `tuple` where ``cert`` is
|
||||
the issued certificate (`.messages.CertificateResource.),
|
||||
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
|
||||
|
|
@ -349,15 +359,19 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
was marked by the CA as invalid
|
||||
|
||||
"""
|
||||
# priority queue with datetime (based on Retry-After) as key,
|
||||
# pylint: disable=too-many-locals
|
||||
assert max_attempts > 0
|
||||
attempts = collections.defaultdict(int)
|
||||
exhausted = set()
|
||||
|
||||
# priority queue with datetime.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 and max_attempts:
|
||||
max_attempts -= 1
|
||||
while waiting:
|
||||
# find the smallest Retry-After, and sleep if necessary
|
||||
when, authzr = heapq.heappop(waiting)
|
||||
now = datetime.datetime.now()
|
||||
|
|
@ -371,16 +385,20 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
updated_authzr, response = self.poll(updated[authzr])
|
||||
updated[authzr] = updated_authzr
|
||||
|
||||
attempts[authzr] += 1
|
||||
# pylint: disable=no-member
|
||||
if updated_authzr.body.status not in (
|
||||
messages.STATUS_VALID, messages.STATUS_INVALID):
|
||||
# push back to the priority queue, with updated retry_after
|
||||
heapq.heappush(waiting, (self.retry_after(
|
||||
response, default=mintime), authzr))
|
||||
if attempts[authzr] < max_attempts:
|
||||
# push back to the priority queue, with updated retry_after
|
||||
heapq.heappush(waiting, (self.retry_after(
|
||||
response, default=mintime), authzr))
|
||||
else:
|
||||
exhausted.add(authzr)
|
||||
|
||||
if not max_attempts or any(authzr.body.status == messages.STATUS_INVALID
|
||||
for authzr in six.itervalues(updated)):
|
||||
raise errors.PollError(waiting, updated)
|
||||
if exhausted or any(authzr.body.status == messages.STATUS_INVALID
|
||||
for authzr in six.itervalues(updated)):
|
||||
raise errors.PollError(exhausted, updated)
|
||||
|
||||
updated_authzrs = tuple(updated[authzr] for authzr in authzrs)
|
||||
return self.request_issuance(csr, updated_authzrs), updated_authzrs
|
||||
|
|
@ -481,7 +499,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
|||
'Successful revocation must return HTTP OK status')
|
||||
|
||||
|
||||
class ClientNetwork(object):
|
||||
class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
|
||||
"""Client network."""
|
||||
JSON_CONTENT_TYPE = 'application/json'
|
||||
JSON_ERROR_CONTENT_TYPE = 'application/problem+json'
|
||||
|
|
@ -494,6 +512,10 @@ class ClientNetwork(object):
|
|||
self.verify_ssl = verify_ssl
|
||||
self._nonces = set()
|
||||
self.user_agent = user_agent
|
||||
self.session = requests.Session()
|
||||
|
||||
def __del__(self):
|
||||
self.session.close()
|
||||
|
||||
def _wrap_in_jws(self, obj, nonce):
|
||||
"""Wrap `JSONDeSerializable` object in JWS.
|
||||
|
|
@ -537,7 +559,7 @@ class ClientNetwork(object):
|
|||
# TODO: response.json() is called twice, once here, and
|
||||
# once in _get and _post clients
|
||||
jobj = response.json()
|
||||
except ValueError as error:
|
||||
except ValueError:
|
||||
jobj = None
|
||||
|
||||
if not response.ok:
|
||||
|
|
@ -588,7 +610,7 @@ class ClientNetwork(object):
|
|||
kwargs['verify'] = self.verify_ssl
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('User-Agent', self.user_agent)
|
||||
response = requests.request(method, url, *args, **kwargs)
|
||||
response = self.session.request(method, url, *args, **kwargs)
|
||||
logging.debug('Received %s. Headers: %s. Content: %r',
|
||||
response, response.headers, response.content)
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -34,8 +34,12 @@ class ClientTest(unittest.TestCase):
|
|||
self.net.get.return_value = self.response
|
||||
|
||||
self.directory = messages.Directory({
|
||||
messages.NewRegistration: 'https://www.letsencrypt-demo.org/acme/new-reg',
|
||||
messages.Revocation: 'https://www.letsencrypt-demo.org/acme/revoke-cert',
|
||||
messages.NewRegistration:
|
||||
'https://www.letsencrypt-demo.org/acme/new-reg',
|
||||
messages.Revocation:
|
||||
'https://www.letsencrypt-demo.org/acme/revoke-cert',
|
||||
messages.NewAuthorization:
|
||||
'https://www.letsencrypt-demo.org/acme/new-authz',
|
||||
})
|
||||
|
||||
from acme.client import Client
|
||||
|
|
@ -127,13 +131,20 @@ class ClientTest(unittest.TestCase):
|
|||
self.response.json.return_value = self.regr.body.to_json()
|
||||
self.assertEqual(self.regr, self.client.query_registration(self.regr))
|
||||
|
||||
def test_query_registration_updates_new_authzr_uri(self):
|
||||
self.response.json.return_value = self.regr.body.to_json()
|
||||
self.response.links = {'next': {'url': 'UPDATED'}}
|
||||
self.assertEqual(
|
||||
'UPDATED',
|
||||
self.client.query_registration(self.regr).new_authzr_uri)
|
||||
|
||||
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):
|
||||
def _prepare_response_for_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()
|
||||
|
|
@ -141,10 +152,20 @@ class ClientTest(unittest.TestCase):
|
|||
'next': {'url': self.authzr.new_cert_uri},
|
||||
}
|
||||
|
||||
self.client.request_challenges(self.identifier, self.authzr.uri)
|
||||
# TODO: test POST call arguments
|
||||
def test_request_challenges(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.client.request_challenges(self.identifier)
|
||||
self.net.post.assert_called_once_with(
|
||||
self.directory.new_authz,
|
||||
messages.NewAuthorization(identifier=self.identifier))
|
||||
|
||||
# TODO: split here and separate test
|
||||
def test_requets_challenges_custom_uri(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.client.request_challenges(self.identifier, 'URI')
|
||||
self.net.post.assert_called_once_with('URI', mock.ANY)
|
||||
|
||||
def test_request_challenges_unexpected_update(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.response.json.return_value = self.authz.update(
|
||||
identifier=self.identifier.update(value='foo')).to_json()
|
||||
self.assertRaises(
|
||||
|
|
@ -153,15 +174,20 @@ class ClientTest(unittest.TestCase):
|
|||
|
||||
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)
|
||||
self.assertRaises(errors.ClientError, self.client.request_challenges,
|
||||
self.identifier)
|
||||
|
||||
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))
|
||||
self.client.request_domain_challenges('example.com'))
|
||||
|
||||
def test_request_domain_challenges_custom_uri(self):
|
||||
self.client.request_challenges = mock.MagicMock()
|
||||
self.assertEqual(
|
||||
self.client.request_challenges(self.identifier, 'URI'),
|
||||
self.client.request_domain_challenges('example.com', 'URI'))
|
||||
|
||||
def test_answer_challenge(self):
|
||||
self.response.links['up'] = {'url': self.challr.authzr_uri}
|
||||
|
|
@ -196,6 +222,17 @@ class ClientTest(unittest.TestCase):
|
|||
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_overflow(self, dt_mock):
|
||||
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
|
||||
dt_mock.timedelta = datetime.timedelta
|
||||
dt_mock.datetime.side_effect = datetime.datetime
|
||||
|
||||
self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST"
|
||||
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)
|
||||
|
|
@ -310,7 +347,10 @@ class ClientTest(unittest.TestCase):
|
|||
)
|
||||
|
||||
cert, updated_authzrs = self.client.poll_and_request_issuance(
|
||||
csr, authzrs, mintime=mintime)
|
||||
csr, authzrs, mintime=mintime,
|
||||
# make sure that max_attempts is per-authorization, rather
|
||||
# than global
|
||||
max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries)))
|
||||
self.assertTrue(cert[0] is csr)
|
||||
self.assertTrue(cert[1] is updated_authzrs)
|
||||
self.assertEqual(updated_authzrs[0].uri, 'a...')
|
||||
|
|
@ -331,7 +371,8 @@ class ClientTest(unittest.TestCase):
|
|||
self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7))
|
||||
|
||||
# CA sets invalid | TODO: move to a separate test
|
||||
invalid_authzr = mock.MagicMock(times=[], retries=[messages.STATUS_INVALID])
|
||||
invalid_authzr = mock.MagicMock(
|
||||
times=[], retries=[messages.STATUS_INVALID])
|
||||
self.assertRaises(
|
||||
errors.PollError, self.client.poll_and_request_issuance,
|
||||
csr, authzrs=(invalid_authzr,), mintime=mintime)
|
||||
|
|
@ -443,9 +484,11 @@ class ClientNetworkTest(unittest.TestCase):
|
|||
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)
|
||||
with mock.patch('acme.client.messages.Error.from_json') as from_json:
|
||||
from_json.side_effect = jose.DeserializationError
|
||||
# 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
|
||||
|
|
@ -487,40 +530,49 @@ class ClientNetworkTest(unittest.TestCase):
|
|||
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
|
||||
def test_send_request(self):
|
||||
self.net.session = mock.MagicMock()
|
||||
self.net.session.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', headers=mock.ANY)
|
||||
'HEAD', 'http://example.com/', 'foo', bar='baz'))
|
||||
self.net.session.request.assert_called_once_with(
|
||||
'HEAD', 'http://example.com/', 'foo',
|
||||
headers=mock.ANY, verify=mock.ANY, bar='baz')
|
||||
|
||||
@mock.patch('acme.client.requests')
|
||||
def test_send_request_verify_ssl(self, mock_requests):
|
||||
def test_send_request_verify_ssl(self):
|
||||
# pylint: disable=protected-access
|
||||
for verify in True, False:
|
||||
mock_requests.request.reset_mock()
|
||||
mock_requests.request.return_value = self.response
|
||||
self.net.session = mock.MagicMock()
|
||||
self.net.session.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, headers=mock.ANY)
|
||||
self.response,
|
||||
self.net._send_request('GET', 'http://example.com/'))
|
||||
self.net.session.request.assert_called_once_with(
|
||||
'GET', 'http://example.com/', verify=verify, headers=mock.ANY)
|
||||
|
||||
@mock.patch('acme.client.requests')
|
||||
def test_send_request_user_agent(self, mock_requests):
|
||||
mock_requests.request.return_value = self.response
|
||||
def test_send_request_user_agent(self):
|
||||
self.net.session = mock.MagicMock()
|
||||
# pylint: disable=protected-access
|
||||
self.net._send_request('GET', 'url', headers={'bar': 'baz'})
|
||||
mock_requests.request.assert_called_once_with(
|
||||
'GET', 'url', verify=mock.ANY,
|
||||
self.net._send_request('GET', 'http://example.com/',
|
||||
headers={'bar': 'baz'})
|
||||
self.net.session.request.assert_called_once_with(
|
||||
'GET', 'http://example.com/', verify=mock.ANY,
|
||||
headers={'User-Agent': 'acme-python-test', 'bar': 'baz'})
|
||||
|
||||
self.net._send_request('GET', 'url', headers={'User-Agent': 'foo2'})
|
||||
mock_requests.request.assert_called_with(
|
||||
'GET', 'url', verify=mock.ANY, headers={'User-Agent': 'foo2'})
|
||||
self.net._send_request('GET', 'http://example.com/',
|
||||
headers={'User-Agent': 'foo2'})
|
||||
self.net.session.request.assert_called_with(
|
||||
'GET', 'http://example.com/',
|
||||
verify=mock.ANY, headers={'User-Agent': 'foo2'})
|
||||
|
||||
def test_del(self):
|
||||
sess = mock.MagicMock()
|
||||
self.net.session = sess
|
||||
del self.net
|
||||
sess.close.assert_called_once_with()
|
||||
|
||||
@mock.patch('acme.client.requests')
|
||||
def test_requests_error_passthrough(self, mock_requests):
|
||||
|
|
@ -573,14 +625,16 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
|
|||
return self.checked_response
|
||||
|
||||
def test_head(self):
|
||||
self.assertEqual(self.response, self.net.head('url', 'foo', bar='baz'))
|
||||
self.assertEqual(self.response, self.net.head(
|
||||
'http://example.com/', 'foo', bar='baz'))
|
||||
self.send_request.assert_called_once_with(
|
||||
'HEAD', 'url', 'foo', bar='baz')
|
||||
'HEAD', 'http://example.com/', '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')
|
||||
'http://example.com/', content_type=self.content_type, bar='baz'))
|
||||
self.send_request.assert_called_once_with(
|
||||
'GET', 'http://example.com/', bar='baz')
|
||||
|
||||
def test_post(self):
|
||||
# pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""Crypto utilities."""
|
||||
import binascii
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from six.moves import range # pylint: disable=import-error,redefined-builtin
|
||||
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
|
|
@ -70,7 +70,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
|
|||
class FakeConnection(object):
|
||||
"""Fake OpenSSL.SSL.Connection."""
|
||||
|
||||
# pylint: disable=missing-docstring
|
||||
# pylint: disable=too-few-public-methods,missing-docstring
|
||||
|
||||
def __init__(self, connection):
|
||||
self._wrapped = connection
|
||||
|
|
@ -161,31 +161,31 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
|
|||
:rtype: `list` of `unicode`
|
||||
|
||||
"""
|
||||
# constants based on implementation of
|
||||
# OpenSSL.crypto.X509Error._subjectAltNameString
|
||||
parts_separator = ", "
|
||||
# This function finds SANs by dumping the certificate/CSR to text and
|
||||
# searching for "X509v3 Subject Alternative Name" in the text. This method
|
||||
# is used to support PyOpenSSL version 0.13 where the
|
||||
# `_subjectAltNameString` and `get_extensions` methods are not available
|
||||
# for CSRs.
|
||||
|
||||
# constants based on PyOpenSSL certificate/CSR text dump
|
||||
part_separator = ":"
|
||||
extension_short_name = b"subjectAltName"
|
||||
parts_separator = ", "
|
||||
prefix = "DNS" + part_separator
|
||||
|
||||
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]
|
||||
if isinstance(cert_or_req, OpenSSL.crypto.X509):
|
||||
func = OpenSSL.crypto.dump_certificate
|
||||
else:
|
||||
func = OpenSSL.crypto.dump_certificate_request
|
||||
text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
|
||||
# WARNING: this function does not support multiple SANs extensions.
|
||||
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
|
||||
match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text)
|
||||
# WARNING: this function assumes that no SAN can include
|
||||
# parts_separator, hence the split!
|
||||
sans_parts = [] if match is None else match.group(1).split(parts_separator)
|
||||
|
||||
return [part.split(part_separator)[1] for parts in san_extensions
|
||||
for part in parts if part.startswith(prefix)]
|
||||
return [part.split(part_separator)[1]
|
||||
for part in sans_parts if part.startswith(prefix)]
|
||||
|
||||
|
||||
def gen_ss_cert(key, domains, not_before=None,
|
||||
|
|
@ -204,7 +204,7 @@ def gen_ss_cert(key, domains, not_before=None,
|
|||
"""
|
||||
assert domains, "Must provide one or more hostnames for the cert."
|
||||
cert = OpenSSL.crypto.X509()
|
||||
cert.set_serial_number(1337)
|
||||
cert.set_serial_number(int(binascii.hexlify(OpenSSL.rand.bytes(16)), 16))
|
||||
cert.set_version(2)
|
||||
|
||||
extensions = [
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
"""Tests for acme.crypto_util."""
|
||||
import itertools
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import six
|
||||
from six.moves import socketserver # pylint: disable=import-error
|
||||
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import test_util
|
||||
|
|
@ -15,10 +19,10 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
|
|||
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
|
||||
|
||||
def setUp(self):
|
||||
self.cert = test_util.load_cert('cert.pem')
|
||||
self.cert = test_util.load_comparable_cert('cert.pem')
|
||||
key = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
# pylint: disable=protected-access
|
||||
certs = {b'foo': (key, self.cert._wrapped)}
|
||||
certs = {b'foo': (key, self.cert.wrapped)}
|
||||
|
||||
from acme.crypto_util import SSLSocket
|
||||
|
||||
|
|
@ -69,6 +73,15 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
|||
from acme.crypto_util import _pyopenssl_cert_or_req_san
|
||||
return _pyopenssl_cert_or_req_san(loader(name))
|
||||
|
||||
@classmethod
|
||||
def _get_idn_names(cls):
|
||||
"""Returns expected names from '{cert,csr}-idnsans.pem'."""
|
||||
chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400),
|
||||
range(0x641, 0x6fc),
|
||||
range(0x1820, 0x1877))]
|
||||
return [''.join(chars[i: i + 45]) + '.invalid'
|
||||
for i in range(0, len(chars), 45)]
|
||||
|
||||
def _call_cert(self, name):
|
||||
return self._call(test_util.load_cert, name)
|
||||
|
||||
|
|
@ -82,6 +95,14 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
|||
self.assertEqual(self._call_cert('cert-san.pem'),
|
||||
['example.com', 'www.example.com'])
|
||||
|
||||
def test_cert_hundred_sans(self):
|
||||
self.assertEqual(self._call_cert('cert-100sans.pem'),
|
||||
['example{0}.com'.format(i) for i in range(1, 101)])
|
||||
|
||||
def test_cert_idn_sans(self):
|
||||
self.assertEqual(self._call_cert('cert-idnsans.pem'),
|
||||
self._get_idn_names())
|
||||
|
||||
def test_csr_no_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-nosans.pem'), [])
|
||||
|
||||
|
|
@ -94,10 +115,36 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
|||
|
||||
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"])
|
||||
['example.com', 'example.org', 'example.net',
|
||||
'example.info', 'subdomain.example.com',
|
||||
'other.subdomain.example.com'])
|
||||
|
||||
def test_csr_hundred_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-100sans.pem'),
|
||||
['example{0}.com'.format(i) for i in range(1, 101)])
|
||||
|
||||
def test_csr_idn_sans(self):
|
||||
self.assertEqual(self._call_csr('csr-idnsans.pem'),
|
||||
self._get_idn_names())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
class RandomSnTest(unittest.TestCase):
|
||||
"""Test for random certificate serial numbers."""
|
||||
|
||||
def setUp(self):
|
||||
self.cert_count = 5
|
||||
self.serial_num = []
|
||||
self.key = OpenSSL.crypto.PKey()
|
||||
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
|
||||
|
||||
def test_sn_collisions(self):
|
||||
from acme.crypto_util import gen_ss_cert
|
||||
|
||||
for _ in range(self.cert_count):
|
||||
cert = gen_ss_cert(self.key, ['dummy'], force_san=True)
|
||||
self.serial_num.append(cert.get_serial_number())
|
||||
self.assertTrue(len(set(self.serial_num)) > 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -56,26 +56,25 @@ class MissingNonce(NonceError):
|
|||
class PollError(ClientError):
|
||||
"""Generic error when polling for authorization fails.
|
||||
|
||||
This might be caused by either timeout (`waiting` will be non-empty)
|
||||
This might be caused by either timeout (`exhausted` will be non-empty)
|
||||
or by some authorization being invalid.
|
||||
|
||||
:ivar waiting: Priority queue with `datetime.datatime` (based on
|
||||
``Retry-After``) as key, and original `.AuthorizationResource`
|
||||
as value.
|
||||
:ivar exhausted: Set of `.AuthorizationResource` that didn't finish
|
||||
within max allowed attempts.
|
||||
:ivar updated: Mapping from original `.AuthorizationResource`
|
||||
to the most recently updated one
|
||||
|
||||
"""
|
||||
def __init__(self, waiting, updated):
|
||||
self.waiting = waiting
|
||||
def __init__(self, exhausted, updated):
|
||||
self.exhausted = exhausted
|
||||
self.updated = updated
|
||||
super(PollError, self).__init__()
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Was the error caused by timeout?"""
|
||||
return bool(self.waiting)
|
||||
return bool(self.exhausted)
|
||||
|
||||
def __repr__(self):
|
||||
return '{0}(waiting={1!r}, updated={2!r})'.format(
|
||||
self.__class__.__name__, self.waiting, self.updated)
|
||||
return '{0}(exhausted={1!r}, updated={2!r})'.format(
|
||||
self.__class__.__name__, self.exhausted, self.updated)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""Tests for acme.errors."""
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
|
@ -36,9 +35,9 @@ class PollErrorTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
from acme.errors import PollError
|
||||
self.timeout = PollError(
|
||||
waiting=[(datetime.datetime(2015, 11, 29), mock.sentinel.AR)],
|
||||
exhausted=set([mock.sentinel.AR]),
|
||||
updated={})
|
||||
self.invalid = PollError(waiting=[], updated={
|
||||
self.invalid = PollError(exhausted=set(), updated={
|
||||
mock.sentinel.AR: mock.sentinel.AR2})
|
||||
|
||||
def test_timeout(self):
|
||||
|
|
@ -46,8 +45,8 @@ class PollErrorTest(unittest.TestCase):
|
|||
self.assertFalse(self.invalid.timeout)
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual('PollError(waiting=[], updated={sentinel.AR: '
|
||||
'sentinel.AR2})', repr(self.invalid))
|
||||
self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: '
|
||||
'sentinel.AR2})' % repr(set()), repr(self.invalid))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable):
|
|||
|
||||
:param str name: Name of the field to be encoded.
|
||||
|
||||
:raises erors.SerializationError: if field cannot be serialized
|
||||
:raises errors.SerializationError: if field cannot be serialized
|
||||
:raises errors.Error: if field could not be found
|
||||
|
||||
"""
|
||||
|
|
@ -373,7 +373,7 @@ def encode_cert(cert):
|
|||
|
||||
"""
|
||||
return encode_b64jose(OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert))
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped))
|
||||
|
||||
|
||||
def decode_cert(b64der):
|
||||
|
|
@ -398,7 +398,7 @@ def encode_csr(csr):
|
|||
|
||||
"""
|
||||
return encode_b64jose(OpenSSL.crypto.dump_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr))
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr.wrapped))
|
||||
|
||||
|
||||
def decode_csr(b64der):
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ from acme.jose import interfaces
|
|||
from acme.jose import util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CSR = test_util.load_csr('csr.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
CSR = test_util.load_comparable_csr('csr.pem')
|
||||
|
||||
|
||||
class FieldTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class Header(json_util.JSONObjectWithFields):
|
|||
@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]
|
||||
OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value]
|
||||
|
||||
@x5c.decoder
|
||||
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from acme.jose import jwa
|
|||
from acme.jose import jwk
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.pem')
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
|
||||
|
||||
|
||||
|
|
@ -68,13 +68,12 @@ class HeaderTest(unittest.TestCase):
|
|||
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))
|
||||
cert_asn1 = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)
|
||||
cert_b64 = base64.b64encode(cert_asn1)
|
||||
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))
|
||||
jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1)
|
||||
self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
|
||||
|
||||
def test_find_key(self):
|
||||
|
|
|
|||
|
|
@ -29,32 +29,41 @@ class abstractclassmethod(classmethod):
|
|||
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`
|
||||
:ivar wrapped: Wrapped certificate or certificate request.
|
||||
:type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
||||
|
||||
"""
|
||||
def __init__(self, wrapped):
|
||||
assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance(
|
||||
wrapped, OpenSSL.crypto.X509Req)
|
||||
self._wrapped = wrapped
|
||||
self.wrapped = wrapped
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped, 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):
|
||||
"""Dumps the object into a buffer with the specified encoding.
|
||||
|
||||
:param int filetype: The desired encoding. Should be one of
|
||||
`OpenSSL.crypto.FILETYPE_ASN1`,
|
||||
`OpenSSL.crypto.FILETYPE_PEM`, or
|
||||
`OpenSSL.crypto.FILETYPE_TEXT`.
|
||||
|
||||
:returns: Encoded X509 object.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
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)
|
||||
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
|
||||
# pylint: disable=protected-access
|
||||
return self._dump() == other._dump()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, self._dump()))
|
||||
|
|
@ -63,7 +72,7 @@ class ComparableX509(object): # pylint: disable=too-few-public-methods
|
|||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped)
|
||||
|
||||
|
||||
class ComparableKey(object): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -130,7 +139,7 @@ class ImmutableMap(collections.Mapping, collections.Hashable):
|
|||
"""Immutable key to value mapping with attribute access."""
|
||||
|
||||
__slots__ = ()
|
||||
"""Must be overriden in subclasses."""
|
||||
"""Must be overridden in subclasses."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if set(kwargs) != set(self.__slots__):
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ 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')
|
||||
# test_util.load_comparable_{csr,cert} return ComparableX509
|
||||
self.req1 = test_util.load_comparable_csr('csr.pem')
|
||||
self.req2 = test_util.load_comparable_csr('csr.pem')
|
||||
self.req_other = test_util.load_comparable_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')
|
||||
self.cert1 = test_util.load_comparable_cert('cert.pem')
|
||||
self.cert2 = test_util.load_comparable_cert('cert.pem')
|
||||
self.cert_other = test_util.load_comparable_cert('cert-san.pem')
|
||||
|
||||
def test_getattr_proxy(self):
|
||||
self.assertTrue(self.cert1.has_expired())
|
||||
|
||||
def test_eq(self):
|
||||
self.assertEqual(self.req1, self.req2)
|
||||
|
|
@ -41,8 +44,8 @@ class ComparableX509Test(unittest.TestCase):
|
|||
|
||||
def test_repr(self):
|
||||
for x509 in self.req1, self.cert1:
|
||||
self.assertTrue(repr(x509).startswith(
|
||||
'<ComparableX509(<OpenSSL.crypto.X509'))
|
||||
self.assertEqual(repr(x509),
|
||||
'<ComparableX509({0!r})>'.format(x509.wrapped))
|
||||
|
||||
|
||||
class ComparableRSAKeyTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -22,20 +22,24 @@ class Error(jose.JSONObjectWithFields, errors.Error):
|
|||
('urn:acme:error:' + name, description) for name, description in (
|
||||
('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'),
|
||||
('connection', 'The server could not connect to the client to '
|
||||
'verify the domain'),
|
||||
('dnssec', 'The server could not validate a DNSSEC signed domain'),
|
||||
('invalidEmail',
|
||||
'The provided email for a registration was invalid'),
|
||||
('malformed', 'The request message was malformed'),
|
||||
('rateLimited', 'There were too many requests of a given type'),
|
||||
('serverInternal', 'The server experienced an internal error'),
|
||||
('tls', 'The server experienced a TLS error during DV'),
|
||||
('tls', 'The server experienced a TLS error during domain '
|
||||
'verification'),
|
||||
('unauthorized', 'The client lacks sufficient authorization'),
|
||||
('unknownHost', 'The server could not resolve a domain name'),
|
||||
)
|
||||
)
|
||||
|
||||
typ = jose.Field('type')
|
||||
typ = jose.Field('type', omitempty=True, default='about:blank')
|
||||
title = jose.Field('title', omitempty=True)
|
||||
detail = jose.Field('detail')
|
||||
detail = jose.Field('detail', omitempty=True)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
|
|
@ -119,6 +123,12 @@ class Directory(jose.JSONDeSerializable):
|
|||
|
||||
_REGISTERED_TYPES = {}
|
||||
|
||||
class Meta(jose.JSONObjectWithFields):
|
||||
"""Directory Meta."""
|
||||
terms_of_service = jose.Field('terms-of-service', omitempty=True)
|
||||
website = jose.Field('website', omitempty=True)
|
||||
caa_identities = jose.Field('caa-identities', omitempty=True)
|
||||
|
||||
@classmethod
|
||||
def _canon_key(cls, key):
|
||||
return getattr(key, 'resource_type', key)
|
||||
|
|
@ -126,17 +136,13 @@ class Directory(jose.JSONDeSerializable):
|
|||
@classmethod
|
||||
def register(cls, resource_body_cls):
|
||||
"""Register resource."""
|
||||
assert resource_body_cls.resource_type not in cls._REGISTERED_TYPES
|
||||
cls._REGISTERED_TYPES[resource_body_cls.resource_type] = resource_body_cls
|
||||
resource_type = resource_body_cls.resource_type
|
||||
assert resource_type not in cls._REGISTERED_TYPES
|
||||
cls._REGISTERED_TYPES[resource_type] = resource_body_cls
|
||||
return resource_body_cls
|
||||
|
||||
def __init__(self, jobj):
|
||||
canon_jobj = util.map_keys(jobj, self._canon_key)
|
||||
if not set(canon_jobj).issubset(self._REGISTERED_TYPES):
|
||||
# TODO: acme-spec is not clear about this: 'It is a JSON
|
||||
# dictionary, whose keys are the "resource" values listed
|
||||
# in {{https-requests}}'z
|
||||
raise ValueError('Wrong directory fields')
|
||||
# TODO: check that everything is an absolute URL; acme-spec is
|
||||
# not clear on that
|
||||
self._jobj = canon_jobj
|
||||
|
|
@ -158,10 +164,8 @@ class Directory(jose.JSONDeSerializable):
|
|||
|
||||
@classmethod
|
||||
def from_json(cls, jobj):
|
||||
try:
|
||||
return cls(jobj)
|
||||
except ValueError as error:
|
||||
raise jose.DeserializationError(str(error))
|
||||
jobj['meta'] = cls.Meta.from_json(jobj.pop('meta', {}))
|
||||
return cls(jobj)
|
||||
|
||||
|
||||
class Resource(jose.JSONObjectWithFields):
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ from acme import jose
|
|||
from acme import test_util
|
||||
|
||||
|
||||
CERT = test_util.load_cert('cert.der')
|
||||
CSR = test_util.load_csr('csr.der')
|
||||
CERT = test_util.load_comparable_cert('cert.der')
|
||||
CSR = test_util.load_comparable_csr('csr.der')
|
||||
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
|
||||
|
||||
|
||||
|
|
@ -28,6 +28,14 @@ class ErrorTest(unittest.TestCase):
|
|||
self.error_custom = Error(typ='custom', detail='bar')
|
||||
self.jobj_cusom = {'type': 'custom', 'detail': 'bar'}
|
||||
|
||||
def test_default_typ(self):
|
||||
from acme.messages import Error
|
||||
self.assertEqual(Error().typ, 'about:blank')
|
||||
|
||||
def test_from_json_empty(self):
|
||||
from acme.messages import Error
|
||||
self.assertEqual(Error(), Error.from_json('{}'))
|
||||
|
||||
def test_from_json_hashable(self):
|
||||
from acme.messages import Error
|
||||
hash(Error.from_json(self.error.to_json()))
|
||||
|
|
@ -90,11 +98,16 @@ class DirectoryTest(unittest.TestCase):
|
|||
self.dir = Directory({
|
||||
'new-reg': 'reg',
|
||||
mock.MagicMock(resource_type='new-cert'): 'cert',
|
||||
'meta': Directory.Meta(
|
||||
terms_of_service='https://example.com/acme/terms',
|
||||
website='https://www.example.com/',
|
||||
caa_identities=['example.com'],
|
||||
),
|
||||
})
|
||||
|
||||
def test_init_wrong_key_value_error(self):
|
||||
def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use
|
||||
from acme.messages import Directory
|
||||
self.assertRaises(ValueError, Directory, {'foo': 'bar'})
|
||||
Directory({'foo': 'bar'})
|
||||
|
||||
def test_getitem(self):
|
||||
self.assertEqual('reg', self.dir['new-reg'])
|
||||
|
|
@ -111,14 +124,20 @@ class DirectoryTest(unittest.TestCase):
|
|||
def test_getattr_fails_with_attribute_error(self):
|
||||
self.assertRaises(AttributeError, self.dir.__getattr__, 'foo')
|
||||
|
||||
def test_to_partial_json(self):
|
||||
self.assertEqual(
|
||||
self.dir.to_partial_json(), {'new-reg': 'reg', 'new-cert': 'cert'})
|
||||
def test_to_json(self):
|
||||
self.assertEqual(self.dir.to_json(), {
|
||||
'new-reg': 'reg',
|
||||
'new-cert': 'cert',
|
||||
'meta': {
|
||||
'terms-of-service': 'https://example.com/acme/terms',
|
||||
'website': 'https://www.example.com/',
|
||||
'caa-identities': ['example.com'],
|
||||
},
|
||||
})
|
||||
|
||||
def test_from_json_deserialization_error_on_wrong_key(self):
|
||||
def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use
|
||||
from acme.messages import Directory
|
||||
self.assertRaises(
|
||||
jose.DeserializationError, Directory.from_json, {'foo': 'bar'})
|
||||
Directory.from_json({'foo': 'bar'})
|
||||
|
||||
|
||||
class RegistrationTest(unittest.TestCase):
|
||||
|
|
@ -271,10 +290,8 @@ class AuthorizationTest(unittest.TestCase):
|
|||
ChallengeBody(uri='http://challb2', status=STATUS_VALID,
|
||||
chall=challenges.DNS(
|
||||
token=b'DGyRejmCefe7v4NfDGDKfA')),
|
||||
ChallengeBody(uri='http://challb3', status=STATUS_VALID,
|
||||
chall=challenges.RecoveryContact()),
|
||||
)
|
||||
combinations = ((0, 2), (1, 2))
|
||||
combinations = ((0,), (1,))
|
||||
|
||||
from acme.messages import Authorization
|
||||
from acme.messages import Identifier
|
||||
|
|
@ -300,8 +317,8 @@ class AuthorizationTest(unittest.TestCase):
|
|||
|
||||
def test_resolved_combinations(self):
|
||||
self.assertEqual(self.authz.resolved_combinations, (
|
||||
(self.challbs[0], self.challbs[2]),
|
||||
(self.challbs[1], self.challbs[2]),
|
||||
(self.challbs[0],),
|
||||
(self.challbs[1],),
|
||||
))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
"""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.asymmetric.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)
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
"""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
|
||||
|
|
@ -32,11 +32,10 @@ class TLSSNI01ServerTest(unittest.TestCase):
|
|||
"""Test for acme.standalone.TLSSNI01Server."""
|
||||
|
||||
def setUp(self):
|
||||
self.certs = {
|
||||
b'localhost': (test_util.load_pyopenssl_private_key('rsa512_key.pem'),
|
||||
# pylint: disable=protected-access
|
||||
test_util.load_cert('cert.pem')._wrapped),
|
||||
}
|
||||
self.certs = {b'localhost': (
|
||||
test_util.load_pyopenssl_private_key('rsa512_key.pem'),
|
||||
test_util.load_cert('cert.pem'),
|
||||
)}
|
||||
from acme.standalone import TLSSNI01Server
|
||||
self.server = TLSSNI01Server(("", 0), certs=self.certs)
|
||||
# pylint: disable=no-member
|
||||
|
|
@ -49,7 +48,8 @@ class TLSSNI01ServerTest(unittest.TestCase):
|
|||
|
||||
def test_it(self):
|
||||
host, port = self.server.socket.getsockname()[:2]
|
||||
cert = crypto_util.probe_sni(b'localhost', host=host, port=port, timeout=1)
|
||||
cert = crypto_util.probe_sni(
|
||||
b'localhost', host=host, port=port, timeout=1)
|
||||
self.assertEqual(jose.ComparableX509(cert),
|
||||
jose.ComparableX509(self.certs[b'localhost'][1]))
|
||||
|
||||
|
|
@ -140,13 +140,14 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
|
|||
while max_attempts:
|
||||
max_attempts -= 1
|
||||
try:
|
||||
cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', self.port)
|
||||
cert = crypto_util.probe_sni(
|
||||
b'localhost', b'0.0.0.0', self.port)
|
||||
except errors.Error:
|
||||
self.assertTrue(max_attempts > 0, "Timeout!")
|
||||
time.sleep(1) # wait until thread starts
|
||||
else:
|
||||
self.assertEqual(jose.ComparableX509(cert),
|
||||
test_util.load_cert('cert.pem'))
|
||||
test_util.load_comparable_cert('cert.pem'))
|
||||
break
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,16 +40,24 @@ 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)))
|
||||
return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_cert(*names):
|
||||
"""Load ComparableX509 cert."""
|
||||
return jose.ComparableX509(load_cert(*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)))
|
||||
return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
|
||||
|
||||
|
||||
def load_comparable_csr(*names):
|
||||
"""Load ComparableX509 certificate request."""
|
||||
return jose.ComparableX509(load_csr(*names))
|
||||
|
||||
|
||||
def load_rsa_private_key(*names):
|
||||
|
|
|
|||
44
acme/acme/testdata/cert-100sans.pem
vendored
Normal file
44
acme/acme/testdata/cert-100sans.pem
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
|
||||
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
|
||||
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
|
||||
DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU
|
||||
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
|
||||
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
|
||||
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
|
||||
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T
|
||||
BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t
|
||||
ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt
|
||||
cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j
|
||||
b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN
|
||||
ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh
|
||||
bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs
|
||||
ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx
|
||||
LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv
|
||||
bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN
|
||||
ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh
|
||||
bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs
|
||||
ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3
|
||||
LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv
|
||||
bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN
|
||||
ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh
|
||||
bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs
|
||||
ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz
|
||||
LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv
|
||||
bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN
|
||||
ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh
|
||||
bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs
|
||||
ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5
|
||||
LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv
|
||||
bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN
|
||||
ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh
|
||||
bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs
|
||||
ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1
|
||||
LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv
|
||||
bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN
|
||||
ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh
|
||||
bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs
|
||||
ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN
|
||||
AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ
|
||||
XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG
|
||||
-----END CERTIFICATE-----
|
||||
30
acme/acme/testdata/cert-idnsans.pem
vendored
Normal file
30
acme/acme/testdata/cert-idnsans.pem
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
|
||||
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
|
||||
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
|
||||
DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU
|
||||
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
|
||||
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
|
||||
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
|
||||
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T
|
||||
BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I
|
||||
z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g
|
||||
z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z
|
||||
z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM
|
||||
2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf
|
||||
2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3
|
||||
2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi
|
||||
2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi
|
||||
2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs
|
||||
aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN
|
||||
247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p
|
||||
bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4
|
||||
27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt
|
||||
4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh
|
||||
oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh
|
||||
oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh
|
||||
oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm
|
||||
4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2
|
||||
LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT
|
||||
TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G
|
||||
-----END CERTIFICATE-----
|
||||
41
acme/acme/testdata/csr-100sans.pem
vendored
Normal file
41
acme/acme/testdata/csr-100sans.pem
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
|
||||
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
|
||||
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
|
||||
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
|
||||
lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud
|
||||
EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv
|
||||
bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh
|
||||
bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu
|
||||
Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C
|
||||
DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4
|
||||
YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w
|
||||
bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy
|
||||
MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j
|
||||
b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C
|
||||
DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4
|
||||
YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w
|
||||
bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz
|
||||
Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j
|
||||
b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C
|
||||
DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4
|
||||
YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w
|
||||
bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1
|
||||
My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j
|
||||
b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C
|
||||
DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4
|
||||
YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w
|
||||
bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2
|
||||
OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j
|
||||
b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C
|
||||
DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4
|
||||
YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w
|
||||
bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4
|
||||
NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j
|
||||
b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C
|
||||
DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4
|
||||
YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w
|
||||
bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3
|
||||
DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo
|
||||
duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
acme/acme/testdata/csr-idnsans.pem
vendored
Normal file
27
acme/acme/testdata/csr-idnsans.pem
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
|
||||
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
|
||||
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
|
||||
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
|
||||
lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud
|
||||
EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP
|
||||
iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P
|
||||
oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP
|
||||
s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ
|
||||
jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z
|
||||
n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ
|
||||
t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC
|
||||
YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa
|
||||
otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh
|
||||
bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb
|
||||
jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu
|
||||
aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb
|
||||
uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg
|
||||
reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5
|
||||
4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ
|
||||
4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ
|
||||
4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh
|
||||
puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh
|
||||
ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr
|
||||
dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
|
@ -28,8 +28,7 @@ acme = client.Client(DIRECTORY_URL, key)
|
|||
|
||||
regr = acme.register()
|
||||
logging.info('Auto-accepting TOS: %s', regr.terms_of_service)
|
||||
acme.update_registration(regr.update(
|
||||
body=regr.body.update(agreement=regr.terms_of_service)))
|
||||
acme.agree_to_tos(regr)
|
||||
logging.debug(regr)
|
||||
|
||||
authzr = acme.request_challenges(
|
||||
|
|
@ -43,7 +42,7 @@ csr = OpenSSL.crypto.load_certificate_request(
|
|||
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
|
||||
'acme', os.path.join('testdata', 'csr.der')))
|
||||
try:
|
||||
acme.request_issuance(csr, (authzr,))
|
||||
acme.request_issuance(jose.util.ComparableX509(csr), (authzr,))
|
||||
except messages.Error as error:
|
||||
print ("This script is doomed to fail as no authorization "
|
||||
"challenges are ever solved. Error from server: {0}".format(error))
|
||||
|
|
|
|||
2
acme/setup.cfg
Normal file
2
acme/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
|
@ -4,25 +4,28 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.2.0.dev0'
|
||||
version = '0.8.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
# load_pem_private/public_key (>=0.6)
|
||||
# rsa_recover_prime_factors (>=0.8)
|
||||
'cryptography>=0.8',
|
||||
'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304)
|
||||
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
|
||||
# Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15)
|
||||
'PyOpenSSL>=0.15',
|
||||
# Connection.set_tlsext_host_name (>=0.13)
|
||||
'PyOpenSSL>=0.13',
|
||||
'pyrfc3339',
|
||||
'pytz',
|
||||
'requests',
|
||||
'setuptools', # pkg_resources
|
||||
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
|
||||
# will tolerate; see #2599:
|
||||
'setuptools>=1.0',
|
||||
'six',
|
||||
'werkzeug',
|
||||
]
|
||||
|
||||
# env markers in extras_require cause problems with older pip: #517
|
||||
# Keep in sync with conditional_requirements.py.
|
||||
if sys.version_info < (2, 7):
|
||||
install_requires.extend([
|
||||
# only some distros recognize stdlib argparse as already satisfying
|
||||
|
|
@ -32,24 +35,25 @@ if sys.version_info < (2, 7):
|
|||
else:
|
||||
install_requires.append('mock')
|
||||
|
||||
dev_extras = [
|
||||
'nose',
|
||||
'pep8',
|
||||
'tox',
|
||||
]
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
'sphinxcontrib-programoutput',
|
||||
]
|
||||
|
||||
testing_extras = [
|
||||
'nose',
|
||||
'tox',
|
||||
]
|
||||
|
||||
|
||||
setup(
|
||||
name='acme',
|
||||
version=version,
|
||||
description='ACME protocol implementation in Python',
|
||||
url='https://github.com/letsencrypt/letsencrypt',
|
||||
author="Let's Encrypt Project",
|
||||
author="Certbot Project",
|
||||
author_email='client-dev@letsencrypt.org',
|
||||
license='Apache License 2.0',
|
||||
classifiers=[
|
||||
|
|
@ -63,6 +67,7 @@ setup(
|
|||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
],
|
||||
|
|
@ -71,8 +76,8 @@ setup(
|
|||
include_package_data=True,
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'dev': dev_extras,
|
||||
'docs': docs_extras,
|
||||
'testing': testing_extras,
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
This directory contains scripts that install necessary OS-specific
|
||||
prerequisite dependencies (see docs/using.rst).
|
||||
|
||||
General dependencies:
|
||||
- git-core: py26reqs.txt git+https://*
|
||||
- ca-certificates: communication with demo ACMO server at
|
||||
https://www.letsencrypt-demo.org, py26reqs.txt git+https://*
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Tested with:
|
||||
# - ArchLinux (x86_64)
|
||||
#
|
||||
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
|
||||
# only "virtualenv2" binary, not "virtualenv" necessary in
|
||||
# ./bootstrap/dev/_common_venv.sh
|
||||
|
||||
deps="
|
||||
git
|
||||
python2
|
||||
python-virtualenv
|
||||
gcc
|
||||
dialog
|
||||
augeas
|
||||
openssl
|
||||
libffi
|
||||
ca-certificates
|
||||
pkg-config
|
||||
"
|
||||
|
||||
missing=$(pacman -T $deps)
|
||||
|
||||
if [ "$missing" ]; then
|
||||
pacman -S --needed $missing
|
||||
fi
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Current version tested with:
|
||||
#
|
||||
# - Ubuntu
|
||||
# - 14.04 (x64)
|
||||
# - 15.04 (x64)
|
||||
# - Debian
|
||||
# - 7.9 "wheezy" (x64)
|
||||
# - sid (2015-10-21) (x64)
|
||||
|
||||
# Past versions tested with:
|
||||
#
|
||||
# - Debian 8.0 "jessie" (x64)
|
||||
# - Raspbian 7.8 (armhf)
|
||||
|
||||
# Believed not to work:
|
||||
#
|
||||
# - Debian 6.0.10 "squeeze" (x64)
|
||||
|
||||
apt-get update
|
||||
|
||||
# virtualenv binary can be found in different packages depending on
|
||||
# distro version (#346)
|
||||
|
||||
virtualenv=
|
||||
if apt-cache show virtualenv > /dev/null ; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
|
||||
if apt-cache show python-virtualenv > /dev/null ; then
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
fi
|
||||
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
python \
|
||||
python-dev \
|
||||
$virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
libaugeas0 \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
ca-certificates \
|
||||
|
||||
if ! command -v virtualenv > /dev/null ; then
|
||||
echo Failed to install a working \"virtualenv\" command, exiting
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
PACKAGES="dev-vcs/git
|
||||
dev-lang/python:2.7
|
||||
dev-python/virtualenv
|
||||
dev-util/dialog
|
||||
app-admin/augeas
|
||||
dev-libs/openssl
|
||||
dev-libs/libffi
|
||||
app-misc/ca-certificates
|
||||
virtual/pkgconfig"
|
||||
|
||||
case "$PACKAGE_MANAGER" in
|
||||
(paludis)
|
||||
cave resolve --keep-targets if-possible $PACKAGES -x
|
||||
;;
|
||||
(pkgcore)
|
||||
pmerge --noreplace $PACKAGES
|
||||
;;
|
||||
(portage|*)
|
||||
emerge --noreplace $PACKAGES
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Tested with:
|
||||
# - Fedora 22, 23 (x64)
|
||||
# - Centos 7 (x64: onD igitalOcean droplet)
|
||||
|
||||
if type dnf 2>/dev/null
|
||||
then
|
||||
tool=dnf
|
||||
elif type yum 2>/dev/null
|
||||
then
|
||||
tool=yum
|
||||
|
||||
else
|
||||
echo "Neither yum nor dnf found. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Some distros and older versions of current distros use a "python27"
|
||||
# instead of "python" naming convention. Try both conventions.
|
||||
if ! $tool install -y \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
if ! $tool install -y \
|
||||
python27 \
|
||||
python27-devel \
|
||||
python27-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# "git-core" seems to be an alias for "git" in CentOS 7 (yum search fails)
|
||||
if ! $tool install -y \
|
||||
git-core \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-libs \
|
||||
openssl-devel \
|
||||
libffi-devel \
|
||||
redhat-rpm-config \
|
||||
ca-certificates
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SLE12 don't have python-virtualenv
|
||||
|
||||
zypper -nq in -l git-core \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-lenses \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
ca-certificates \
|
||||
|
|
@ -1 +0,0 @@
|
|||
_arch_common.sh
|
||||
|
|
@ -1 +0,0 @@
|
|||
_rpm_common.sh
|
||||
|
|
@ -1 +0,0 @@
|
|||
_deb_common.sh
|
||||
|
|
@ -1 +0,0 @@
|
|||
This directory contains developer setup.
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#!/bin/sh -xe
|
||||
# Developer virtualenv setup for Let's Encrypt client
|
||||
|
||||
export VENV_ARGS="--python python2"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
-r py26reqs.txt \
|
||||
-e acme[testing] \
|
||||
-e .[dev,docs,testing] \
|
||||
-e letsencrypt-apache \
|
||||
-e letsencrypt-nginx \
|
||||
-e letshelp-letsencrypt \
|
||||
-e letsencrypt-compatibility-test
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh -xe
|
||||
# Developer Python3 virtualenv setup for Let's Encrypt
|
||||
|
||||
export VENV_NAME="${VENV_NAME:-venv3}"
|
||||
export VENV_ARGS="--python python3"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
-e acme[testing] \
|
||||
|
|
@ -1 +0,0 @@
|
|||
_rpm_common.sh
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
pkg install -Ay \
|
||||
git \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
libffi \
|
||||
|
|
@ -1 +0,0 @@
|
|||
_gentoo_common.sh
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
#
|
||||
# Install OS dependencies. In the glorious future, letsencrypt-auto will
|
||||
# source this...
|
||||
|
||||
if test "`id -u`" -ne "0" ; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
BOOTSTRAP=`dirname $0`
|
||||
if [ ! -f $BOOTSTRAP/debian.sh ] ; then
|
||||
echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f /etc/debian_version ] ; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_deb_common.sh
|
||||
elif [ -f /etc/arch-release ] ; then
|
||||
echo "Bootstrapping dependencies for Archlinux..."
|
||||
$SUDO $BOOTSTRAP/archlinux.sh
|
||||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_rpm_common.sh
|
||||
elif [ -f /etc/gentoo-release ] ; then
|
||||
echo "Bootstrapping dependencies for Gentoo-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_gentoo_common.sh
|
||||
elif uname | grep -iq FreeBSD ; then
|
||||
echo "Bootstrapping dependencies for FreeBSD..."
|
||||
$SUDO $BOOTSTRAP/freebsd.sh
|
||||
elif `grep -qs openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE.."
|
||||
$SUDO $BOOTSTRAP/suse.sh
|
||||
elif uname | grep -iq Darwin ; then
|
||||
echo "Bootstrapping dependencies for Mac OS X..."
|
||||
echo "WARNING: Mac support is very experimental at present..."
|
||||
$BOOTSTRAP/mac.sh
|
||||
else
|
||||
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
|
||||
echo
|
||||
echo "You will need to bootstrap, configure virtualenv, and run a pip install manually"
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
if ! hash brew 2>/dev/null; then
|
||||
echo "Homebrew Not Installed\nDownloading..."
|
||||
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
fi
|
||||
|
||||
brew install augeas
|
||||
brew install dialog
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip Not Installed\nInstalling python from Homebrew..."
|
||||
brew install python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv Not Installed\nInstalling with pip"
|
||||
pip install virtualenv
|
||||
fi
|
||||
|
|
@ -1 +0,0 @@
|
|||
_arch_common.sh
|
||||
|
|
@ -1 +0,0 @@
|
|||
_suse_common.sh
|
||||
|
|
@ -1 +0,0 @@
|
|||
_deb_common.sh
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
#
|
||||
# Installs and updates letencrypt virtualenv
|
||||
#
|
||||
# USAGE: source ./dev/venv.sh
|
||||
|
||||
|
||||
XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
||||
VENV_NAME="letsencrypt"
|
||||
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
|
||||
|
||||
# virtualenv call is not idempotent: it overwrites pip upgraded in
|
||||
# later steps, causing "ImportError: cannot import name unpack_url"
|
||||
if [ ! -d $VENV_PATH ]
|
||||
then
|
||||
virtualenv --no-site-packages --python ${LE_PYTHON:-python2} $VENV_PATH
|
||||
fi
|
||||
|
||||
. $VENV_PATH/bin/activate
|
||||
pip install -U setuptools
|
||||
pip install -U pip
|
||||
|
||||
pip install -U -r py26reqs.txt letsencrypt letsencrypt-apache # letsencrypt-nginx
|
||||
|
||||
echo
|
||||
echo "Congratulations, Let's Encrypt has been successfully installed/updated!"
|
||||
echo
|
||||
printf "%s" "Your prompt should now be prepended with ($VENV_NAME). Next "
|
||||
printf "time, if the prompt is different, 'source' this script again "
|
||||
printf "before running 'letsencrypt'."
|
||||
echo
|
||||
echo
|
||||
echo "You can now run 'letsencrypt --help'."
|
||||
7
certbot-apache/MANIFEST.in
Normal file
7
certbot-apache/MANIFEST.in
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include certbot_apache/tests/testdata *
|
||||
include certbot_apache/centos-options-ssl-apache.conf
|
||||
include certbot_apache/options-ssl-apache.conf
|
||||
recursive-include certbot_apache/augeas_lens *.aug
|
||||
1
certbot-apache/README.rst
Normal file
1
certbot-apache/README.rst
Normal file
|
|
@ -0,0 +1 @@
|
|||
Apache plugin for Certbot
|
||||
1
certbot-apache/certbot_apache/__init__.py
Normal file
1
certbot-apache/certbot_apache/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Certbot Apache plugin."""
|
||||
|
|
@ -3,11 +3,11 @@ import logging
|
|||
|
||||
import augeas
|
||||
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import reverter
|
||||
from letsencrypt.plugins import common
|
||||
from certbot import errors
|
||||
from certbot import reverter
|
||||
from certbot.plugins import common
|
||||
|
||||
from letsencrypt_apache import constants
|
||||
from certbot_apache import constants
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -16,14 +16,14 @@ class AugeasConfigurator(common.Plugin):
|
|||
"""Base Augeas Configurator class.
|
||||
|
||||
:ivar config: Configuration.
|
||||
:type config: :class:`~letsencrypt.interfaces.IConfig`
|
||||
:type config: :class:`~certbot.interfaces.IConfig`
|
||||
|
||||
:ivar aug: Augeas object
|
||||
:type aug: :class:`augeas.Augeas`
|
||||
|
||||
:ivar str save_notes: Human-readable configuration change notes
|
||||
:ivar reverter: saves and reverts checkpoints
|
||||
:type reverter: :class:`letsencrypt.reverter.Reverter`
|
||||
:type reverter: :class:`certbot.reverter.Reverter`
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -120,7 +120,8 @@ class AugeasConfigurator(common.Plugin):
|
|||
self.reverter.add_to_temp_checkpoint(
|
||||
save_files, self.save_notes)
|
||||
else:
|
||||
self.reverter.add_to_checkpoint(save_files, self.save_notes)
|
||||
self.reverter.add_to_checkpoint(save_files,
|
||||
self.save_notes)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
2
certbot-apache/certbot_apache/augeas_lens/README
Normal file
2
certbot-apache/certbot_apache/augeas_lens/README
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Certbot includes the very latest Augeas lenses in order to ship bug fixes
|
||||
to Apache configuration handling bugs as quickly as possible
|
||||
|
|
@ -45,13 +45,12 @@ autoload xfm
|
|||
let dels (s:string) = del s s
|
||||
|
||||
(* deal with continuation lines *)
|
||||
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "
|
||||
|
||||
let sep_osp = Sep.opt_space
|
||||
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)+/ " "
|
||||
let sep_osp = del /([ \t]*|[ \t]*\\\\\r?\n[ \t]*)*/ ""
|
||||
let sep_eq = del /[ \t]*=[ \t]*/ "="
|
||||
|
||||
let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
|
||||
let word = /[a-zA-Z][a-zA-Z0-9._-]*/
|
||||
let word = /[a-z][a-z0-9._-]*/i
|
||||
|
||||
let comment = Util.comment
|
||||
let eol = Util.doseol
|
||||
|
|
@ -59,13 +58,18 @@ let empty = Util.empty_dos
|
|||
let indent = Util.indent
|
||||
|
||||
(* borrowed from shellvars.aug *)
|
||||
let char_arg_dir = /([^\\ '"\t\r\n]|[^\\ '"\t\r\n][^ '"\t\r\n]*[^\\ '"\t\r\n])|\\\\"|\\\\'/
|
||||
let char_arg_sec = /[^ '"\t\r\n>]|\\\\"|\\\\'/
|
||||
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'|\\\\ /
|
||||
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ /
|
||||
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
|
||||
|
||||
let cdot = /\\\\./
|
||||
let cl = /\\\\\n/
|
||||
let dquot =
|
||||
let no_dquot = /[^"\\\r\n]/
|
||||
in /"/ . (no_dquot|cdot|cl)* . /"/
|
||||
let dquot_msg =
|
||||
let no_dquot = /([^ \t"\\\r\n]|[^"\\\r\n]+[^ \t"\\\r\n])/
|
||||
in /"/ . (no_dquot|cdot|cl)*
|
||||
let squot =
|
||||
let no_squot = /[^'\\\r\n]/
|
||||
in /'/ . (no_squot|cdot|cl)* . /'/
|
||||
|
|
@ -76,12 +80,24 @@ let comp = /[<>=]?=/
|
|||
*****************************************************************)
|
||||
|
||||
let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
|
||||
(* message argument starts with " but ends at EOL *)
|
||||
let arg_dir_msg = [ label "arg" . store dquot_msg ]
|
||||
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]
|
||||
let arg_wl = [ label "arg" . store (char_arg_wl+|dquot|squot) ]
|
||||
|
||||
(* comma-separated wordlist as permitted in the SSLRequire directive *)
|
||||
let arg_wordlist =
|
||||
let wl_start = Util.del_str "{" in
|
||||
let wl_end = Util.del_str "}" in
|
||||
let wl_sep = del /[ \t]*,[ \t]*/ ", "
|
||||
in [ label "wordlist" . wl_start . arg_wl . (wl_sep . arg_wl)* . wl_end ]
|
||||
|
||||
let argv (l:lens) = l . (sep_spc . l)*
|
||||
|
||||
let directive = [ indent . label "directive" . store word .
|
||||
(sep_spc . argv arg_dir)? . eol ]
|
||||
let directive =
|
||||
(* arg_dir_msg may be the last or only argument *)
|
||||
let dir_args = (argv (arg_dir|arg_wordlist) . (sep_spc . arg_dir_msg)?) | arg_dir_msg
|
||||
in [ indent . label "directive" . store word . (sep_spc . dir_args)? . eol ]
|
||||
|
||||
let section (body:lens) =
|
||||
(* opt_eol includes empty lines *)
|
||||
|
|
@ -89,11 +105,17 @@ let section (body:lens) =
|
|||
let inner = (sep_spc . argv arg_sec)? . sep_osp .
|
||||
dels ">" . opt_eol . ((body|comment) . (body|empty|comment)*)? .
|
||||
indent . dels "</" in
|
||||
let kword = key word in
|
||||
let dword = del word "a" in
|
||||
[ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]
|
||||
let kword = key (word - /perl/i) in
|
||||
let dword = del (word - /perl/i) "a" in
|
||||
[ indent . dels "<" . square kword inner dword . del />[ \t\n\r]*/ ">\n" ]
|
||||
|
||||
let perl_section = [ indent . label "Perl" . del /<perl>/i "<Perl>"
|
||||
. store /[^<]*/
|
||||
. del /<\/perl>/i "</Perl>" . eol ]
|
||||
|
||||
|
||||
let rec content = section (content|directive)
|
||||
| perl_section
|
||||
|
||||
let lns = (content|directive|comment|empty)*
|
||||
|
||||
|
|
@ -104,6 +126,7 @@ let filter = (incl "/etc/apache2/apache2.conf") .
|
|||
(incl "/etc/apache2/conf-available/*.conf") .
|
||||
(incl "/etc/apache2/mods-available/*") .
|
||||
(incl "/etc/apache2/sites-available/*") .
|
||||
(incl "/etc/apache2/vhosts.d/*.conf") .
|
||||
(incl "/etc/httpd/conf.d/*.conf") .
|
||||
(incl "/etc/httpd/httpd.conf") .
|
||||
(incl "/etc/httpd/conf/httpd.conf") .
|
||||
21
certbot-apache/certbot_apache/centos-options-ssl-apache.conf
Normal file
21
certbot-apache/certbot_apache/centos-options-ssl-apache.conf
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Baseline setting to Include for SSL sites
|
||||
|
||||
SSLEngine on
|
||||
|
||||
# Intermediate configuration, tweak to your needs
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
|
||||
SSLHonorCipherOrder on
|
||||
|
||||
SSLOptions +StrictRequire
|
||||
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
|
||||
#CustomLog /var/log/apache2/access.log vhost_combined
|
||||
#LogLevel warn
|
||||
#ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Always ensure Cookies have "Secure" set (JAH 2012/1)
|
||||
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
|
||||
File diff suppressed because it is too large
Load diff
127
certbot-apache/certbot_apache/constants.py
Normal file
127
certbot-apache/certbot_apache/constants.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Apache plugin constants."""
|
||||
import pkg_resources
|
||||
from certbot import util
|
||||
|
||||
|
||||
CLI_DEFAULTS_DEBIAN = dict(
|
||||
server_root="/etc/apache2",
|
||||
vhost_root="/etc/apache2/sites-available",
|
||||
vhost_files="*",
|
||||
version_cmd=['apache2ctl', '-v'],
|
||||
define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||
restart_cmd=['apache2ctl', 'graceful'],
|
||||
conftest_cmd=['apache2ctl', 'configtest'],
|
||||
enmod="a2enmod",
|
||||
dismod="a2dismod",
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
handle_mods=True,
|
||||
handle_sites=True,
|
||||
challenge_location="/etc/apache2",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS_CENTOS = dict(
|
||||
server_root="/etc/httpd",
|
||||
vhost_root="/etc/httpd/conf.d",
|
||||
vhost_files="*.conf",
|
||||
version_cmd=['apachectl', '-v'],
|
||||
define_cmd=['apachectl', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
handle_mods=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/httpd/conf.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "centos-options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS_GENTOO = dict(
|
||||
server_root="/etc/apache2",
|
||||
vhost_root="/etc/apache2/vhosts.d",
|
||||
vhost_files="*.conf",
|
||||
version_cmd=['/usr/sbin/apache2', '-v'],
|
||||
define_cmd=['apache2ctl', 'virtualhosts'],
|
||||
restart_cmd=['apache2ctl', 'graceful'],
|
||||
conftest_cmd=['apache2ctl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
handle_mods=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/vhosts.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS_DARWIN = dict(
|
||||
server_root="/etc/apache2",
|
||||
vhost_root="/etc/apache2/other",
|
||||
vhost_files="*.conf",
|
||||
version_cmd=['/usr/sbin/httpd', '-v'],
|
||||
define_cmd=['/usr/sbin/httpd', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
handle_mods=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/other",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS = {
|
||||
"debian": CLI_DEFAULTS_DEBIAN,
|
||||
"ubuntu": CLI_DEFAULTS_DEBIAN,
|
||||
"centos": CLI_DEFAULTS_CENTOS,
|
||||
"centos linux": CLI_DEFAULTS_CENTOS,
|
||||
"fedora": CLI_DEFAULTS_CENTOS,
|
||||
"red hat enterprise linux server": CLI_DEFAULTS_CENTOS,
|
||||
"rhel": CLI_DEFAULTS_CENTOS,
|
||||
"amazon": CLI_DEFAULTS_CENTOS,
|
||||
"gentoo": CLI_DEFAULTS_GENTOO,
|
||||
"gentoo base system": CLI_DEFAULTS_GENTOO,
|
||||
"darwin": CLI_DEFAULTS_DARWIN,
|
||||
}
|
||||
"""CLI defaults."""
|
||||
|
||||
MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
|
||||
"""Name of the mod_ssl config file as saved in `IConfig.config_dir`."""
|
||||
|
||||
AUGEAS_LENS_DIR = pkg_resources.resource_filename(
|
||||
"certbot_apache", "augeas_lens")
|
||||
"""Path to the Augeas lens directory"""
|
||||
|
||||
REWRITE_HTTPS_ARGS = [
|
||||
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"]
|
||||
"""Apache version<2.3.9 rewrite rule arguments used for redirections to
|
||||
https vhost"""
|
||||
|
||||
REWRITE_HTTPS_ARGS_WITH_END = [
|
||||
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]
|
||||
"""Apache version >= 2.3.9 rewrite rule arguments used for redirections to
|
||||
https vhost"""
|
||||
|
||||
HSTS_ARGS = ["always", "set", "Strict-Transport-Security",
|
||||
"\"max-age=31536000\""]
|
||||
"""Apache header arguments for HSTS"""
|
||||
|
||||
UIR_ARGS = ["always", "set", "Content-Security-Policy",
|
||||
"upgrade-insecure-requests"]
|
||||
|
||||
HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
|
||||
"Upgrade-Insecure-Requests": UIR_ARGS}
|
||||
|
||||
|
||||
def os_constant(key):
|
||||
"""Get a constant value for operating system
|
||||
:param key: name of cli constant
|
||||
:return: value of constant for active os
|
||||
"""
|
||||
os_info = util.get_os_info()
|
||||
try:
|
||||
constants = CLI_DEFAULTS[os_info[0].lower()]
|
||||
except KeyError:
|
||||
constants = CLI_DEFAULTS["debian"]
|
||||
return constants[key]
|
||||
|
|
@ -4,9 +4,10 @@ import os
|
|||
|
||||
import zope.component
|
||||
|
||||
from letsencrypt import interfaces
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
|
||||
import letsencrypt.display.util as display_util
|
||||
import certbot.display.util as display_util
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -49,7 +50,7 @@ def _vhost_menu(domain, vhosts):
|
|||
|
||||
if free_chars < 2:
|
||||
logger.debug("Display size is too small for "
|
||||
"letsencrypt_apache.display_ops._vhost_menu()")
|
||||
"certbot_apache.display_ops._vhost_menu()")
|
||||
# This runs the edge off the screen, but it doesn't cause an "error"
|
||||
filename_size = 1
|
||||
disp_name_size = 1
|
||||
|
|
@ -78,11 +79,19 @@ def _vhost_menu(domain, vhosts):
|
|||
name_size=disp_name_size)
|
||||
)
|
||||
|
||||
code, tag = zope.component.getUtility(interfaces.IDisplay).menu(
|
||||
"We were unable to find a vhost with a ServerName or Address of {0}.{1}"
|
||||
"Which virtual host would you like to choose?".format(
|
||||
domain, os.linesep),
|
||||
choices, help_label="More Info", ok_label="Select")
|
||||
try:
|
||||
code, tag = zope.component.getUtility(interfaces.IDisplay).menu(
|
||||
"We were unable to find a vhost with a ServerName "
|
||||
"or Address of {0}.{1}Which virtual host would you "
|
||||
"like to choose?\n(note: conf files with multiple "
|
||||
"vhosts are not yet supported)".format(domain, os.linesep),
|
||||
choices, help_label="More Info", ok_label="Select")
|
||||
except errors.MissingCommandlineFlag as e:
|
||||
msg = ("Failed to run Apache plugin non-interactively{1}{0}{1}"
|
||||
"(The best solution is to add ServerName or ServerAlias "
|
||||
"entries to the VirtualHost directives of your apache "
|
||||
"configuration files.)".format(e, os.linesep))
|
||||
raise errors.MissingCommandlineFlag(msg)
|
||||
|
||||
return code, tag
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""Module contains classes used by the Apache Configurator."""
|
||||
import re
|
||||
|
||||
from letsencrypt.plugins import common
|
||||
from certbot.plugins import common
|
||||
|
||||
|
||||
class Addr(common.Addr):
|
||||
|
|
@ -189,22 +189,29 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
return True
|
||||
return False
|
||||
|
||||
def same_server(self, vhost):
|
||||
def same_server(self, vhost, generic=False):
|
||||
"""Determines if the vhost is the same 'server'.
|
||||
|
||||
Used in redirection - indicates whether or not the two virtual hosts
|
||||
serve on the exact same IP combinations, but different ports.
|
||||
The generic flag indicates that that we're trying to match to a
|
||||
default or generic vhost
|
||||
|
||||
.. todo:: Handle _default_
|
||||
|
||||
"""
|
||||
|
||||
if vhost.get_names() != self.get_names():
|
||||
return False
|
||||
if not generic:
|
||||
if vhost.get_names() != self.get_names():
|
||||
return False
|
||||
|
||||
# If equal and set is not empty... assume same server
|
||||
if self.name is not None or self.aliases:
|
||||
return True
|
||||
# If equal and set is not empty... assume same server
|
||||
if self.name is not None or self.aliases:
|
||||
return True
|
||||
# If we're looking for a generic vhost,
|
||||
# don't return one with a ServerName
|
||||
elif self.name:
|
||||
return False
|
||||
|
||||
# Both sets of names are empty.
|
||||
|
||||
|
|
@ -14,9 +14,9 @@ SSLOptions +StrictRequire
|
|||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
|
||||
CustomLog /var/log/apache2/access.log vhost_combined
|
||||
LogLevel warn
|
||||
ErrorLog /var/log/apache2/error.log
|
||||
#CustomLog /var/log/apache2/access.log vhost_combined
|
||||
#LogLevel warn
|
||||
#ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Always ensure Cookies have "Secure" set (JAH 2012/1)
|
||||
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
|
||||
|
|
@ -6,8 +6,9 @@ import os
|
|||
import re
|
||||
import subprocess
|
||||
|
||||
from letsencrypt import errors
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache import constants
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -19,7 +20,6 @@ class ApacheParser(object):
|
|||
|
||||
:ivar str root: Normalized absolute path to the server root
|
||||
directory. Without trailing slash.
|
||||
:ivar str root: Server root
|
||||
:ivar set modules: All module names that are currently enabled.
|
||||
:ivar dict loc: Location to place directives, root - configuration origin,
|
||||
default - user config file, name - NameVirtualHost,
|
||||
|
|
@ -28,15 +28,17 @@ class ApacheParser(object):
|
|||
arg_var_interpreter = re.compile(r"\$\{[^ \}]*}")
|
||||
fnmatch_chars = set(["*", "?", "\\", "[", "]"])
|
||||
|
||||
def __init__(self, aug, root, ctl):
|
||||
def __init__(self, aug, root, vhostroot, version=(2, 4)):
|
||||
# Note: Order is important here.
|
||||
|
||||
# This uses the binary, so it can be done first.
|
||||
# https://httpd.apache.org/docs/2.4/mod/core.html#define
|
||||
# https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine
|
||||
# This only handles invocation parameters and Define directives!
|
||||
self.parser_paths = {}
|
||||
self.variables = {}
|
||||
self.update_runtime_variables(ctl)
|
||||
if version >= (2, 4):
|
||||
self.update_runtime_variables()
|
||||
|
||||
self.aug = aug
|
||||
# Find configuration root and make sure augeas can parse it.
|
||||
|
|
@ -44,6 +46,8 @@ class ApacheParser(object):
|
|||
self.loc = {"root": self._find_config_root()}
|
||||
self._parse_file(self.loc["root"])
|
||||
|
||||
self.vhostroot = os.path.abspath(vhostroot)
|
||||
|
||||
# This problem has been fixed in Augeas 1.0
|
||||
self.standardize_excl()
|
||||
|
||||
|
|
@ -56,9 +60,14 @@ class ApacheParser(object):
|
|||
# Set up rest of locations
|
||||
self.loc.update(self._set_locations())
|
||||
|
||||
# Must also attempt to parse sites-available or equivalent
|
||||
# Sites-available is not included naturally in configuration
|
||||
self._parse_file(os.path.join(self.root, "sites-available") + "/*")
|
||||
# Must also attempt to parse virtual host root
|
||||
self._parse_file(self.vhostroot + "/" +
|
||||
constants.os_constant("vhost_files"))
|
||||
|
||||
# check to see if there were unparsed define statements
|
||||
if version < (2, 4):
|
||||
if self.find_dir("Define", exclude=False):
|
||||
raise errors.PluginError("Error parsing runtime variables")
|
||||
|
||||
def init_modules(self):
|
||||
"""Iterates on the configuration until no new modules are loaded.
|
||||
|
|
@ -84,29 +93,30 @@ class ApacheParser(object):
|
|||
self.modules.add(
|
||||
os.path.basename(self.get_arg(match_filename))[:-2] + "c")
|
||||
|
||||
def update_runtime_variables(self, ctl):
|
||||
def update_runtime_variables(self):
|
||||
""""
|
||||
|
||||
.. note:: Compile time variables (apache2ctl -V) are not used within the
|
||||
dynamic configuration files. These should not be parsed or
|
||||
.. note:: Compile time variables (apache2ctl -V) are not used within
|
||||
the dynamic configuration files. These should not be parsed or
|
||||
interpreted.
|
||||
|
||||
.. todo:: Create separate compile time variables... simply for arg_get()
|
||||
.. todo:: Create separate compile time variables...
|
||||
simply for arg_get()
|
||||
|
||||
"""
|
||||
stdout = self._get_runtime_cfg(ctl)
|
||||
stdout = self._get_runtime_cfg()
|
||||
|
||||
variables = dict()
|
||||
matches = re.compile(r"Define: ([^ \n]*)").findall(stdout)
|
||||
try:
|
||||
matches.remove("DUMP_RUN_CFG")
|
||||
except ValueError:
|
||||
raise errors.PluginError("Unable to parse runtime variables")
|
||||
return
|
||||
|
||||
for match in matches:
|
||||
if match.count("=") > 1:
|
||||
logger.error("Unexpected number of equal signs in "
|
||||
"apache2ctl -D DUMP_RUN_CFG")
|
||||
"runtime config dump.")
|
||||
raise errors.PluginError(
|
||||
"Error parsing Apache runtime variables")
|
||||
parts = match.partition("=")
|
||||
|
|
@ -114,7 +124,7 @@ class ApacheParser(object):
|
|||
|
||||
self.variables = variables
|
||||
|
||||
def _get_runtime_cfg(self, ctl): # pylint: disable=no-self-use
|
||||
def _get_runtime_cfg(self): # pylint: disable=no-self-use
|
||||
"""Get runtime configuration info.
|
||||
|
||||
:returns: stdout from DUMP_RUN_CFG
|
||||
|
|
@ -122,16 +132,18 @@ class ApacheParser(object):
|
|||
"""
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[ctl, "-t", "-D", "DUMP_RUN_CFG"],
|
||||
constants.os_constant("define_cmd"),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
except (OSError, ValueError):
|
||||
logger.error(
|
||||
"Error accessing %s for runtime parameters!%s", ctl, os.linesep)
|
||||
"Error running command %s for runtime parameters!%s",
|
||||
constants.os_constant("define_cmd"), os.linesep)
|
||||
raise errors.MisconfigurationError(
|
||||
"Error accessing loaded Apache parameters: %s", ctl)
|
||||
"Error accessing loaded Apache parameters: %s",
|
||||
constants.os_constant("define_cmd"))
|
||||
# Small errors that do not impede
|
||||
if proc.returncode != 0:
|
||||
logger.warn("Error in checking parameter list: %s", stderr)
|
||||
|
|
@ -166,7 +178,8 @@ class ApacheParser(object):
|
|||
# Make sure we don't cause an IndexError (end of list)
|
||||
# Check to make sure arg + 1 doesn't exist
|
||||
if (i == (len(matches) - 1) or
|
||||
not matches[i + 1].endswith("/arg[%d]" % (args + 1))):
|
||||
not matches[i + 1].endswith("/arg[%d]" %
|
||||
(args + 1))):
|
||||
filtered.append(matches[i][:-len("/arg[%d]" % args)])
|
||||
|
||||
return filtered
|
||||
|
|
@ -300,8 +313,6 @@ class ApacheParser(object):
|
|||
for match in matches:
|
||||
dir_ = self.aug.get(match).lower()
|
||||
if dir_ == "include" or dir_ == "includeoptional":
|
||||
# start[6:] to strip off /files
|
||||
#print self._get_include_path(self.get_arg(match +"/arg")), directive, arg
|
||||
ordered_matches.extend(self.find_dir(
|
||||
directive, arg,
|
||||
self._get_include_path(self.get_arg(match + "/arg")),
|
||||
|
|
@ -320,8 +331,8 @@ class ApacheParser(object):
|
|||
"""
|
||||
value = self.aug.get(match)
|
||||
|
||||
# No need to strip quotes for variables, as apache2ctl already does this
|
||||
# but we do need to strip quotes for all normal arguments.
|
||||
# No need to strip quotes for variables, as apache2ctl already does
|
||||
# this, but we do need to strip quotes for all normal arguments.
|
||||
|
||||
# Note: normal argument may be a quoted variable
|
||||
# e.g. strip now, not later
|
||||
|
|
@ -443,7 +454,7 @@ class ApacheParser(object):
|
|||
https://apr.apache.org/docs/apr/2.0/apr__fnmatch_8h_source.html
|
||||
http://apache2.sourcearchive.com/documentation/2.2.16-6/apr__fnmatch_8h_source.html
|
||||
|
||||
:param str clean_fn_match: Apache style filename match, similar to globs
|
||||
:param str clean_fn_match: Apache style filename match, like globs
|
||||
|
||||
:returns: regex suitable for augeas
|
||||
:rtype: str
|
||||
|
|
@ -461,16 +472,63 @@ class ApacheParser(object):
|
|||
:param str filepath: Apache config file path
|
||||
|
||||
"""
|
||||
use_new, remove_old = self._check_path_actions(filepath)
|
||||
# Test if augeas included file for Httpd.lens
|
||||
# Note: This works for augeas globs, ie. *.conf
|
||||
inc_test = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % filepath)
|
||||
if not inc_test:
|
||||
# Load up files
|
||||
# This doesn't seem to work on TravisCI
|
||||
# self.aug.add_transform("Httpd.lns", [filepath])
|
||||
self._add_httpd_transform(filepath)
|
||||
self.aug.load()
|
||||
if use_new:
|
||||
inc_test = self.aug.match(
|
||||
"/augeas/load/Httpd['%s' =~ glob(incl)]" % filepath)
|
||||
if not inc_test:
|
||||
# Load up files
|
||||
# This doesn't seem to work on TravisCI
|
||||
# self.aug.add_transform("Httpd.lns", [filepath])
|
||||
if remove_old:
|
||||
self._remove_httpd_transform(filepath)
|
||||
self._add_httpd_transform(filepath)
|
||||
self.aug.load()
|
||||
|
||||
def _check_path_actions(self, filepath):
|
||||
"""Determine actions to take with a new augeas path
|
||||
|
||||
This helper function will return a tuple that defines
|
||||
if we should try to append the new filepath to augeas
|
||||
parser paths, and / or remove the old one with more
|
||||
narrow matching.
|
||||
|
||||
:param str filepath: filepath to check the actions for
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
new_file_match = os.path.basename(filepath)
|
||||
existing_matches = self.parser_paths[os.path.dirname(filepath)]
|
||||
if "*" in existing_matches:
|
||||
use_new = False
|
||||
else:
|
||||
use_new = True
|
||||
if new_file_match == "*":
|
||||
remove_old = True
|
||||
else:
|
||||
remove_old = False
|
||||
except KeyError:
|
||||
use_new = True
|
||||
remove_old = False
|
||||
return use_new, remove_old
|
||||
|
||||
def _remove_httpd_transform(self, filepath):
|
||||
"""Remove path from Augeas transform
|
||||
|
||||
:param str filepath: filepath to remove
|
||||
"""
|
||||
|
||||
remove_basenames = self.parser_paths[os.path.dirname(filepath)]
|
||||
remove_dirname = os.path.dirname(filepath)
|
||||
for name in remove_basenames:
|
||||
remove_path = remove_dirname + "/" + name
|
||||
remove_inc = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % remove_path)
|
||||
self.aug.remove(remove_inc[0])
|
||||
self.parser_paths.pop(remove_dirname)
|
||||
|
||||
def _add_httpd_transform(self, incl):
|
||||
"""Add a transform to Augeas.
|
||||
|
|
@ -492,6 +550,13 @@ class ApacheParser(object):
|
|||
# Augeas uses base 1 indexing... insert at beginning...
|
||||
self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns")
|
||||
self.aug.set("/augeas/load/Httpd/incl", incl)
|
||||
# Add included path to paths dictionary
|
||||
try:
|
||||
self.parser_paths[os.path.dirname(incl)].append(
|
||||
os.path.basename(incl))
|
||||
except KeyError:
|
||||
self.parser_paths[os.path.dirname(incl)] = [
|
||||
os.path.basename(incl)]
|
||||
|
||||
def standardize_excl(self):
|
||||
"""Standardize the excl arguments for the Httpd lens in Augeas.
|
||||
|
|
@ -532,7 +597,7 @@ class ApacheParser(object):
|
|||
.. todo:: Make sure that files are included
|
||||
|
||||
"""
|
||||
default = self._set_user_config_file()
|
||||
default = self.loc["root"]
|
||||
|
||||
temp = os.path.join(self.root, "ports.conf")
|
||||
if os.path.isfile(temp):
|
||||
|
|
@ -546,31 +611,13 @@ class ApacheParser(object):
|
|||
|
||||
def _find_config_root(self):
|
||||
"""Find the Apache Configuration Root file."""
|
||||
location = ["apache2.conf", "httpd.conf"]
|
||||
|
||||
location = ["apache2.conf", "httpd.conf", "conf/httpd.conf"]
|
||||
for name in location:
|
||||
if os.path.isfile(os.path.join(self.root, name)):
|
||||
return os.path.join(self.root, name)
|
||||
|
||||
raise errors.NoInstallationError("Could not find configuration root")
|
||||
|
||||
def _set_user_config_file(self):
|
||||
"""Set the appropriate user configuration file
|
||||
|
||||
.. todo:: This will have to be updated for other distros versions
|
||||
|
||||
:param str root: pathname which contains the user config
|
||||
|
||||
"""
|
||||
# Basic check to see if httpd.conf exists and
|
||||
# in hierarchy via direct include
|
||||
# httpd.conf was very common as a user file in Apache 2.2
|
||||
if (os.path.isfile(os.path.join(self.root, "httpd.conf")) and
|
||||
self.find_dir("Include", "httpd.conf", self.loc["root"])):
|
||||
return os.path.join(self.root, "httpd.conf")
|
||||
else:
|
||||
return os.path.join(self.root, "apache2.conf")
|
||||
|
||||
|
||||
def case_i(string):
|
||||
"""Returns case insensitive regex.
|
||||
1
certbot-apache/certbot_apache/tests/__init__.py
Normal file
1
certbot-apache/certbot_apache/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Certbot Apache Tests"""
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
Issues for which some kind of test case should be constructable, but we do not
|
||||
currently have one:
|
||||
|
||||
https://github.com/certbot/certbot/issues/1213
|
||||
https://github.com/certbot/certbot/issues/1602
|
||||
|
||||
77
certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test
Executable file
77
certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash
|
||||
|
||||
# A hackish script to see if the client is behaving as expected
|
||||
# with each of the "passing" conf files.
|
||||
|
||||
export EA=/etc/apache2/
|
||||
TESTDIR="`dirname $0`"
|
||||
cd $TESTDIR/passing
|
||||
|
||||
function CleanupExit() {
|
||||
echo control c, exiting tests...
|
||||
if [ "$f" != "" ] ; then
|
||||
Cleanup
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Setup() {
|
||||
if [ "$APPEND_APACHECONF" = "" ] ; then
|
||||
sudo cp "$f" "$EA"/sites-available/
|
||||
sudo ln -sf "$EA/sites-available/$f" "$EA/sites-enabled/$f"
|
||||
echo "
|
||||
<VirtualHost *:80>
|
||||
ServerName example.com
|
||||
DocumentRoot /tmp/
|
||||
ErrorLog /tmp/error.log
|
||||
CustomLog /tmp/requests.log combined
|
||||
</VirtualHost>" | sudo tee $EA/sites-available/throwaway-example.conf >/dev/null
|
||||
else
|
||||
TMP="/tmp/`basename \"$APPEND_APACHECONF\"`.$$"
|
||||
sudo cp -a "$APPEND_APACHECONF" "$TMP"
|
||||
sudo bash -c "cat \"$f\" >> \"$APPEND_APACHECONF\""
|
||||
fi
|
||||
}
|
||||
|
||||
function Cleanup() {
|
||||
if [ "$APPEND_APACHECONF" = "" ] ; then
|
||||
sudo rm /etc/apache2/sites-{enabled,available}/"$f"
|
||||
sudo rm $EA/sites-available/throwaway-example.conf
|
||||
else
|
||||
sudo mv "$TMP" "$APPEND_APACHECONF"
|
||||
fi
|
||||
}
|
||||
|
||||
# if our environment asks us to enable modules, do our best!
|
||||
if [ "$1" = --debian-modules ] ; then
|
||||
sudo apt-get install -y libapache2-mod-wsgi
|
||||
sudo apt-get install -y libapache2-mod-macro
|
||||
|
||||
for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do
|
||||
echo -n enabling $mod
|
||||
sudo a2enmod $mod
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
FAILS=0
|
||||
trap CleanupExit INT
|
||||
for f in *.conf ; do
|
||||
echo -n testing "$f"...
|
||||
Setup
|
||||
RESULT=`echo c | sudo $(command -v certbot) -vvvv --debug --staging --apache --register-unsafely-without-email --agree-tos certonly -t 2>&1`
|
||||
if echo $RESULT | grep -Eq \("Which names would you like"\|"mod_macro is not yet"\) ; then
|
||||
echo passed
|
||||
else
|
||||
echo failed
|
||||
echo $RESULT
|
||||
echo
|
||||
echo
|
||||
FAILS=`expr $FAILS + 1`
|
||||
fi
|
||||
Cleanup
|
||||
done
|
||||
if [ "$FAILS" -ne 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<VirtualHost *:443>
|
||||
ServerAdmin webmaster@localhost
|
||||
ServerAlias www.example.com
|
||||
ServerName example.com
|
||||
DocumentRoot /var/www/example.com/www/
|
||||
SSLEngine on
|
||||
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
|
||||
<Directory />
|
||||
Options FollowSymLinks
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
<Directory /var/www/example.com/www>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride All
|
||||
Order allow,deny
|
||||
allow from all
|
||||
# This directive allows us to have apache2's default start page
|
||||
# in /apache2-default/, but still have / go to the right place
|
||||
</Directory>
|
||||
|
||||
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
|
||||
<Directory "/usr/lib/cgi-bin">
|
||||
AllowOverride None
|
||||
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
LogLevel warn
|
||||
|
||||
CustomLog /var/log/apache2/access.log combined
|
||||
ServerSignature On
|
||||
|
||||
Alias /apache_doc/ "/usr/share/doc/"
|
||||
<Directory "/usr/share/doc/">
|
||||
Options Indexes MultiViews FollowSymLinks
|
||||
AllowOverride None
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
Allow from 127.0.0.0/255.0.0.0 ::1/128
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<VirtualHost *:80>
|
||||
ServerAdmin denver@ossguy.com
|
||||
ServerName c-beta.ossguy.com
|
||||
|
||||
Alias /robots.txt /home/denver/www/c-beta.ossguy.com/static/robots.txt
|
||||
Alias /favicon.ico /home/denver/www/c-beta.ossguy.com/static/favicon.ico
|
||||
|
||||
AliasMatch /(.*\.css) /home/denver/www/c-beta.ossguy.com/static/$1
|
||||
AliasMatch /(.*\.js) /home/denver/www/c-beta.ossguy.com/static/$1
|
||||
AliasMatch /(.*\.png) /home/denver/www/c-beta.ossguy.com/static/$1
|
||||
AliasMatch /(.*\.gif) /home/denver/www/c-beta.ossguy.com/static/$1
|
||||
AliasMatch /(.*\.jpg) /home/denver/www/c-beta.ossguy.com/static/$1
|
||||
|
||||
WSGIScriptAlias / /home/denver/www/c-beta.ossguy.com/django.wsgi
|
||||
WSGIDaemonProcess c-beta-ossguy user=www-data group=www-data home=/var/www processes=5 threads=10 maximum-requests=1000 umask=0007 display-name=c-beta-ossguy
|
||||
WSGIProcessGroup c-beta-ossguy
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
|
||||
DocumentRoot /home/denver/www/c-beta.ossguy.com/static
|
||||
|
||||
<Directory /home/denver/www/c-beta.ossguy.com/static>
|
||||
Options -Indexes +FollowSymLinks -MultiViews
|
||||
Require all granted
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
|
||||
<Directory /home/denver/www/c-beta.ossguy.com/static/source>
|
||||
Options +Indexes +FollowSymLinks -MultiViews
|
||||
Require all granted
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
|
||||
# Custom log file locations
|
||||
LogLevel warn
|
||||
ErrorLog /tmp/error.log
|
||||
CustomLog /tmp/access.log combined
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Modules required to parse these conf files:
|
||||
ssl
|
||||
rewrite
|
||||
macro
|
||||
wsgi
|
||||
deflate
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
#
|
||||
# Apache/PHP/Drupal settings:
|
||||
#
|
||||
|
||||
# Protect files and directories from prying eyes.
|
||||
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl|svn-base)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template|all-wcprops|entries|format)$">
|
||||
Order allow,deny
|
||||
</FilesMatch>
|
||||
|
||||
# Don't show directory listings for URLs which map to a directory.
|
||||
Options -Indexes
|
||||
|
||||
# Follow symbolic links in this directory.
|
||||
Options +FollowSymLinks
|
||||
|
||||
# Make Drupal handle any 404 errors.
|
||||
ErrorDocument 404 /index.php
|
||||
|
||||
# Force simple error message for requests for non-existent favicon.ico.
|
||||
<Files favicon.ico>
|
||||
# There is no end quote below, for compatibility with Apache 1.3.
|
||||
ErrorDocument 404 "The requested file favicon.ico was not found.
|
||||
</Files>
|
||||
|
||||
# Set the default handler.
|
||||
DirectoryIndex index.php
|
||||
|
||||
# Override PHP settings. More in sites/default/settings.php
|
||||
# but the following cannot be changed at runtime.
|
||||
|
||||
# PHP 4, Apache 1.
|
||||
<IfModule mod_php4.c>
|
||||
php_value magic_quotes_gpc 0
|
||||
php_value register_globals 0
|
||||
php_value session.auto_start 0
|
||||
php_value mbstring.http_input pass
|
||||
php_value mbstring.http_output pass
|
||||
php_value mbstring.encoding_translation 0
|
||||
</IfModule>
|
||||
|
||||
# PHP 4, Apache 2.
|
||||
<IfModule sapi_apache2.c>
|
||||
php_value magic_quotes_gpc 0
|
||||
php_value register_globals 0
|
||||
php_value session.auto_start 0
|
||||
php_value mbstring.http_input pass
|
||||
php_value mbstring.http_output pass
|
||||
php_value mbstring.encoding_translation 0
|
||||
</IfModule>
|
||||
|
||||
# PHP 5, Apache 1 and 2.
|
||||
<IfModule mod_php5.c>
|
||||
php_value magic_quotes_gpc 0
|
||||
php_value register_globals 0
|
||||
php_value session.auto_start 0
|
||||
php_value mbstring.http_input pass
|
||||
php_value mbstring.http_output pass
|
||||
php_value mbstring.encoding_translation 0
|
||||
</IfModule>
|
||||
|
||||
# Requires mod_expires to be enabled.
|
||||
<IfModule mod_expires.c>
|
||||
# Enable expirations.
|
||||
ExpiresActive On
|
||||
|
||||
# Cache all files for 2 weeks after access (A).
|
||||
ExpiresDefault A1209600
|
||||
|
||||
<FilesMatch \.php$>
|
||||
# Do not allow PHP scripts to be cached unless they explicitly send cache
|
||||
# headers themselves. Otherwise all scripts would have to overwrite the
|
||||
# headers set by mod_expires if they want another caching behavior. This may
|
||||
# fail if an error occurs early in the bootstrap process, and it may cause
|
||||
# problems if a non-Drupal PHP file is installed in a subdirectory.
|
||||
ExpiresActive Off
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Various rewrite rules.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
|
||||
# If your site can be accessed both with and without the 'www.' prefix, you
|
||||
# can use one of the following settings to redirect users to your preferred
|
||||
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
|
||||
#
|
||||
# To redirect all users to access the site WITH the 'www.' prefix,
|
||||
# (http://example.com/... will be redirected to http://www.example.com/...)
|
||||
# adapt and uncomment the following:
|
||||
# RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
|
||||
# RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
|
||||
#
|
||||
# To redirect all users to access the site WITHOUT the 'www.' prefix,
|
||||
# (http://www.example.com/... will be redirected to http://example.com/...)
|
||||
# uncomment and adapt the following:
|
||||
# RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
|
||||
# RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
|
||||
|
||||
# Modify the RewriteBase if you are using Drupal in a subdirectory or in a
|
||||
# VirtualDocumentRoot and the rewrite rules are not working properly.
|
||||
# For example if your site is at http://example.com/drupal uncomment and
|
||||
# modify the following line:
|
||||
# RewriteBase /drupal
|
||||
#
|
||||
# If your site is running in a VirtualDocumentRoot at http://example.com/,
|
||||
# uncomment the following line:
|
||||
# RewriteBase /
|
||||
|
||||
# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !=/favicon.ico
|
||||
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
|
||||
</IfModule>
|
||||
|
||||
# $Id$
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
RewriteCond %{HTTP:Content-Disposition} \.php [NC]
|
||||
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.+/trackback/?\ HTTP/ [NC]
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<VirtualHost *:80>
|
||||
# The ServerName directive sets the request scheme, hostname and port that
|
||||
# the server uses to identify itself. This is used when creating
|
||||
# redirection URLs. In the context of virtual hosts, the ServerName
|
||||
# specifies what hostname must appear in the request's Host: header to
|
||||
# match this virtual host. For the default virtual host (this file) this
|
||||
# value is not decisive as it is used as a last resort host regardless.
|
||||
# However, you must set it for any further virtual host explicitly.
|
||||
ServerName www.example.com
|
||||
ServerAlias example.com
|
||||
SetOutputFilter DEFLATE
|
||||
# Do not attempt to compress the following extensions
|
||||
SetEnvIfNoCase Request_URI \
|
||||
\.(?:gif|jpe?g|png|swf|flv|zip|gz|tar|mp3|mp4|m4v)$ no-gzip dont-vary
|
||||
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/proof
|
||||
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
# modules, e.g.
|
||||
#LogLevel info ssl:warn
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
</VirtualHost>
|
||||
|
||||
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
# certificate chain for the server certificate. Alternatively
|
||||
# the referenced file can be the same as SSLCertificateFile
|
||||
# when the CA certificates are directly appended to the server
|
||||
# certificate for convinience.
|
||||
# certificate for convenience.
|
||||
#SSLCertificateChainFile /etc/apache2/ssl.crt/server-ca.crt
|
||||
|
||||
# Certificate Authority (CA):
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<VirtualHost *:80>
|
||||
|
||||
WSGIDaemonProcess _graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120 user=www-data group=www-data
|
||||
WSGIProcessGroup _graphite
|
||||
WSGIImportScript /usr/share/graphite-web/graphite.wsgi process-group=_graphite application-group=%{GLOBAL}
|
||||
WSGIScriptAlias / /usr/share/graphite-web/graphite.wsgi
|
||||
|
||||
Alias /content/ /usr/share/graphite-web/static/
|
||||
<Location "/content/">
|
||||
SetHandler None
|
||||
</Location>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/graphite-web_error.log
|
||||
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
LogLevel warn
|
||||
|
||||
CustomLog ${APACHE_LOG_DIR}/graphite-web_access.log combined
|
||||
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<VirtualHost *:80 [::]:80>
|
||||
DocumentRoot /tmp
|
||||
ServerName example.com
|
||||
ServerAlias www.example.com
|
||||
CustomLog ${APACHE_LOG_DIR}/example.log combined
|
||||
<Directory "/tmp">
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<VirtualHost *:443 [::]:443>
|
||||
DocumentRoot /tmp
|
||||
ServerName example.com
|
||||
ServerAlias www.example.com
|
||||
CustomLog ${APACHE_LOG_DIR}/example.log combined
|
||||
<Directory "/tmp">
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
|
||||
SSLEngine on
|
||||
|
||||
SSLHonorCipherOrder On
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
|
||||
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<VirtualHost [::]:80 *:80>
|
||||
DocumentRoot /tmp
|
||||
ServerName example.com
|
||||
ServerAlias www.example.com
|
||||
CustomLog ${APACHE_LOG_DIR}/example.log combined
|
||||
<Directory "/tmp">
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<VirtualHost [::]:443 *:443>
|
||||
DocumentRoot /tmp
|
||||
ServerName example.com
|
||||
ServerAlias www.example.com
|
||||
CustomLog ${APACHE_LOG_DIR}/example.log combined
|
||||
<Directory "/tmp">
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
|
||||
SSLEngine on
|
||||
|
||||
SSLHonorCipherOrder On
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
|
||||
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
</VirtualHost>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<VirtualHost *:443>
|
||||
ServerAdmin webmaster@localhost
|
||||
ServerAlias www.example.com
|
||||
ServerName example.com
|
||||
DocumentRoot /var/www/example.com/www/
|
||||
SSLEngine on
|
||||
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
|
||||
<Directory />
|
||||
Options FollowSymLinks
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
<Directory /var/www/example.com/www>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride All
|
||||
Order allow,deny
|
||||
allow from all
|
||||
# This directive allows us to have apache2's default start page
|
||||
# in /apache2-default/, but still have / go to the right place
|
||||
</Directory>
|
||||
|
||||
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
|
||||
<Directory "/usr/lib/cgi-bin">
|
||||
AllowOverride None
|
||||
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
LogLevel warn
|
||||
|
||||
CustomLog /var/log/apache2/access.log combined
|
||||
ServerSignature On
|
||||
|
||||
Alias /apache_doc/ "/usr/share/doc/"
|
||||
<Directory "/usr/share/doc/">
|
||||
Options Indexes MultiViews FollowSymLinks
|
||||
AllowOverride None
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
Allow from 127.0.0.0/255.0.0.0 ::1/128
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue