Retiring Project
http://lists.openstack.org/pipermail/openstack-sigs/2018-August/000481.html Depends-On: 90ca23f2ef5bf2cfdaf63552a7d8d8be325a03e6 Change-Id: I9ebc8cfcbb8906e9c4e1fd9e91205fe364bdc3c9
This commit is contained in:
parent
0ecce126dc
commit
93aacb43e6
@ -1,7 +0,0 @@
|
|||||||
[run]
|
|
||||||
branch = True
|
|
||||||
|
|
||||||
[report]
|
|
||||||
exclude_lines =
|
|
||||||
pragma: no cover
|
|
||||||
raise NotImplementedError
|
|
29
.gitignore
vendored
29
.gitignore
vendored
@ -1,29 +0,0 @@
|
|||||||
*.pyc
|
|
||||||
temp-*.crt
|
|
||||||
config.cfg
|
|
||||||
.venv
|
|
||||||
*.sw[op]
|
|
||||||
certs/*.crt
|
|
||||||
dist/*
|
|
||||||
build/*
|
|
||||||
.tox/*
|
|
||||||
.DS_Store
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
.testrepository
|
|
||||||
build/*
|
|
||||||
cover/*
|
|
||||||
.cover
|
|
||||||
.coverage
|
|
||||||
doc/build
|
|
||||||
.eggs/
|
|
||||||
pep8.txt
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
|
|
||||||
# mentioned in README for test/bootstrap
|
|
||||||
anchor-test.example.com.key
|
|
||||||
anchor-test.example.com.csr
|
|
||||||
CA/serial
|
|
||||||
CA/*.key
|
|
||||||
CA/*.crt
|
|
@ -1,7 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
|
||||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
|
||||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
|
||||||
${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
|
|
||||||
test_id_option=--load-list $IDFILE
|
|
||||||
test_list_option=--list
|
|
@ -1,17 +0,0 @@
|
|||||||
This project is part of the OpenStack / Stackforge family. If you would like to
|
|
||||||
contribute to the development, you must follow the steps in this page:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html
|
|
||||||
|
|
||||||
Once those steps have been completed, changes to OpenStack should be submitted
|
|
||||||
for review via the Gerrit tool, following the workflow documented at:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
(in short - install git-review package, then submit changes via `git review`)
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
|
|
||||||
Bugs should be filed on Launchpad, not GitHub:
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/anchor
|
|
@ -1,6 +0,0 @@
|
|||||||
FROM python:2.7
|
|
||||||
RUN pip install pecan
|
|
||||||
ADD . /code
|
|
||||||
WORKDIR /code
|
|
||||||
RUN pip install -e .
|
|
||||||
ENTRYPOINT ["python","bin/container_bootstrap.py"]
|
|
364
README.rst
364
README.rst
@ -1,358 +1,10 @@
|
|||||||
Anchor
|
This project is no longer maintained.
|
||||||
======
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/anchor.svg
|
The contents of this repository are still available in the Git
|
||||||
:target: https://pypi.python.org/pypi/anchor/
|
source code management system. To see the contents of this
|
||||||
:alt: Latest Version
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/anchor.svg
|
For any further questions, please email
|
||||||
:target: https://pypi.python.org/pypi/anchor/
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
:alt: Python Versions
|
Freenode.
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/format/anchor.svg
|
|
||||||
:target: https://pypi.python.org/pypi/anchor/
|
|
||||||
:alt: Format
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/license-Apache%202-blue.svg
|
|
||||||
:target: https://git.openstack.org/cgit/openstack/anchor/plain/LICENSE
|
|
||||||
:alt: License
|
|
||||||
|
|
||||||
Anchor is an ephemeral PKI service that, based on certain conditions,
|
|
||||||
automates the verification of CSRs and signs certificates for clients.
|
|
||||||
The validity period can be set in the config file with hour resolution.
|
|
||||||
|
|
||||||
Ideas behind Anchor
|
|
||||||
===================
|
|
||||||
|
|
||||||
A critical capability within PKI is to revoke a certificate - to ensure
|
|
||||||
that it is no longer trusted by any peer. Unfortunately research has
|
|
||||||
demonstrated that the two typical methods of revocation (Certificate
|
|
||||||
Revocation Lists and Online Certificate Status Protocol) both have
|
|
||||||
failings that make them unreliable, especially when attempting to
|
|
||||||
leverage PKI outside of web-browser software.
|
|
||||||
|
|
||||||
Through the use of short-lifetime certificates Anchor introduces the
|
|
||||||
concept of "passive revocation". By issuing certificates with lifetimes
|
|
||||||
measured in hours, revocation can be achieved by simply not re-issuing
|
|
||||||
certificates to clients.
|
|
||||||
|
|
||||||
The benefits of using Anchor instead of manual long-term certificates
|
|
||||||
are:
|
|
||||||
|
|
||||||
* quick certificate revoking / rotation
|
|
||||||
* always tested certificate update mechanism (used daily)
|
|
||||||
* easy integration with certmonger for service restarting
|
|
||||||
* certificates are signed only when validation is passed
|
|
||||||
* signing certificates follows consistent process
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
In order to install Anchor from source, the following system
|
|
||||||
dependencies need to be present:
|
|
||||||
|
|
||||||
* python 2.7
|
|
||||||
* python (dev files)
|
|
||||||
* libffi (dev)
|
|
||||||
* libssl (dev)
|
|
||||||
|
|
||||||
When everything is in place, Anchor can be installed in one of three
|
|
||||||
ways: a local development instance in a python virtual environment, a local
|
|
||||||
production instance or a test instance in a docker container.
|
|
||||||
|
|
||||||
For a development instance with virtualenv, run:
|
|
||||||
|
|
||||||
virtualenv .venv && source .venv/bin/activate && pip install .
|
|
||||||
|
|
||||||
For installing in production, either install a perpared system package,
|
|
||||||
or install globally in the system:
|
|
||||||
|
|
||||||
python setup.py install
|
|
||||||
|
|
||||||
Running the service
|
|
||||||
===================
|
|
||||||
|
|
||||||
In order to run the service, it needs to be started via the `pecan`
|
|
||||||
application server. The only extra parameter is a config file:
|
|
||||||
|
|
||||||
pecan serve anchor/config.py
|
|
||||||
|
|
||||||
For development, an additional `--reload` parameter may be used. It will
|
|
||||||
cause the service to reload every time a source file is changed, however
|
|
||||||
it requires installing an additional `watchdog` python module.
|
|
||||||
|
|
||||||
In the default configuration, Anchor will wait for web requests on port
|
|
||||||
5016 on local network interface. This can be adjusted in the `config.py`
|
|
||||||
file.
|
|
||||||
|
|
||||||
Preparing a test environment
|
|
||||||
============================
|
|
||||||
|
|
||||||
In order to test Anchor with the default configuration, the following
|
|
||||||
can be done to create a test CA. The test certificate can be then used
|
|
||||||
to sign the new certificates.
|
|
||||||
|
|
||||||
openssl req -out CA/root-ca.crt -keyout CA/root-ca-unwrapped.key \
|
|
||||||
-newkey rsa:4096 -subj "/CN=Anchor Test CA" -nodes -x509 -days 365 \
|
|
||||||
-sha256
|
|
||||||
chmod 0400 CA/root-ca-unwrapped.key
|
|
||||||
|
|
||||||
Next, a new certificate request may be generated:
|
|
||||||
|
|
||||||
openssl req -out anchor-test.example.com.csr -nodes \
|
|
||||||
-keyout anchor-test.example.com.key -newkey rsa:2048 \
|
|
||||||
-subj "/CN=anchor-test.example.com" -sha256
|
|
||||||
|
|
||||||
That reqest can be submitted using curl (while `pecan serve config.py`
|
|
||||||
is running):
|
|
||||||
|
|
||||||
curl http://0.0.0.0:5016/v1/sign/default -F user='myusername' \
|
|
||||||
-F secret='simplepassword' -F encoding=pem \
|
|
||||||
-F 'csr=<anchor-test.example.com.csr'
|
|
||||||
|
|
||||||
This will result in the signed request being created in the `certs`
|
|
||||||
directory.
|
|
||||||
|
|
||||||
Docker test environment
|
|
||||||
=======================
|
|
||||||
We have published a docker image for anchor at
|
|
||||||
https://hub.docker.com/r/openstacksecurity/anchor/ These instructions expect
|
|
||||||
the reader to have a working Docker install already. Docker should *not* be
|
|
||||||
used to serve Anchor in any production environments.
|
|
||||||
|
|
||||||
The behaviour of the Anchor container is controlled through docker volumes. To
|
|
||||||
run a plain version of Anchor, with a default configuration and a dynamically
|
|
||||||
generated private key simply invoke the container without any volumes. Note
|
|
||||||
that Anchor exposes port 5016:
|
|
||||||
|
|
||||||
docker run -p 5016:5016 openstacksecurity/anchor
|
|
||||||
|
|
||||||
The recommended way to use the anchor container is to use a pre-compiled private
|
|
||||||
key and certificate. You can read more about generating these (if you do not
|
|
||||||
already have them) in this readme.
|
|
||||||
|
|
||||||
Once a key and certificate have been created, they can be provided to Anchor
|
|
||||||
using docker volumes. In this example we've stored the sensitive data in
|
|
||||||
/var/keys (note, docker must be able to access the folder where you have stored
|
|
||||||
your keys). When the container starts it looks for a mounted volume in '/key'
|
|
||||||
and files called root-ca-unwrapped.key and root-ca.crt that it will use.
|
|
||||||
|
|
||||||
docker run -p 5016:5016 -v /var/keys:/key anchor
|
|
||||||
|
|
||||||
Anchor is highly configurable, you can read more about Anchor configuration in
|
|
||||||
the documentation here:
|
|
||||||
http://docs.openstack.org/developer/anchor/configuration.html the method for
|
|
||||||
exposing configuration to Anchor is very similar as for keys, simply provide
|
|
||||||
docker with the folder the config.json is within and create a volume called
|
|
||||||
/config In the below example, Anchor will start with a custom configuration but
|
|
||||||
as no key was provided it will generate one on the fly.
|
|
||||||
|
|
||||||
docker run -p 5016:5016 -v /var/config:/config anchor
|
|
||||||
|
|
||||||
Obviously it's possible to run Anchor with a custom configuration and a custom
|
|
||||||
key/certificate by running the following (note in this case we've used -d to
|
|
||||||
detach the container from our terminal)
|
|
||||||
|
|
||||||
docker run -d -p 5016:5016 -v /var/config:/config -v /var/keys:/key anchor
|
|
||||||
|
|
||||||
If you prefer to use locally built containers or want to modify the container
|
|
||||||
build you can do that, we provide a simple Dockerfile to make the process
|
|
||||||
easier.
|
|
||||||
|
|
||||||
Assuming you are already in the anchor directory, build a container
|
|
||||||
called 'anchor' that runs the anchor service, with any local changes
|
|
||||||
that have been made in the repo:
|
|
||||||
|
|
||||||
docker build -t anchor .
|
|
||||||
|
|
||||||
To start the service in the container and serve Anchor on port 5016:
|
|
||||||
|
|
||||||
docker run -p 5016:5016 anchor
|
|
||||||
|
|
||||||
When Anchor is running in a container, certificate requests will not pass
|
|
||||||
validation unless the docker network is added as a source_cidr in the Anchor
|
|
||||||
configuration and then passed into the container. Find the network by starting
|
|
||||||
the container, inspecting the docker network and finding the anchor container:
|
|
||||||
|
|
||||||
docker run -p 5016:5016 --name=anchor anchor
|
|
||||||
docker network inspect bridge
|
|
||||||
|
|
||||||
Under the 'containers' section, find the 'anchor' container and find the
|
|
||||||
IPv4Address. For example:
|
|
||||||
|
|
||||||
"Containers": {
|
|
||||||
"6998a....5f4a57": {
|
|
||||||
"Name": "anchor",
|
|
||||||
"MacAddress": "02:42:ac:11:00:03",
|
|
||||||
"IPv4Address": "172.17.0.3/16",
|
|
||||||
|
|
||||||
Add this network as a source_cidr to the config.json, and pass it to the
|
|
||||||
docker container as described above.
|
|
||||||
|
|
||||||
Running Anchor in production
|
|
||||||
============================
|
|
||||||
|
|
||||||
Anchor shouldn't be exposed directly to the network. It's running via an
|
|
||||||
application server (Pecan) and doesn't have all the features you'd
|
|
||||||
normally expect from a http proxy - for example dealing well with
|
|
||||||
deliberately slow connections, or using multiple workers. Anchor can
|
|
||||||
however be run in production using a better frontend.
|
|
||||||
|
|
||||||
To run Anchor using uwsgi you can use the following command:
|
|
||||||
|
|
||||||
uwsgi --http-socket :5016 --venv path/to/venv --pecan config.py -p 4
|
|
||||||
|
|
||||||
In case a more complex scripted configuration is needed, for example to
|
|
||||||
handle custom headers, rate limiting, or source filtering a complete
|
|
||||||
HTTP proxy like Nginx may be needed. This is however out of scope for
|
|
||||||
Anchor project. You can read more about production deployment in
|
|
||||||
`Pecan documentation <http://pecan.readthedocs.org/en/latest/deployment.html>`_.
|
|
||||||
|
|
||||||
Additionally, using an AppArmor profile for Anchor is a good idea to
|
|
||||||
prevent exploits relying on one of the native libraries used by Anchor
|
|
||||||
(for example OpenSSL). This can be done with sample profiles which you
|
|
||||||
can find in the `tools/apparmor.anchor_*` files. The used file needs to
|
|
||||||
be reviewed and updated with the right paths depending on the deployment
|
|
||||||
location.
|
|
||||||
|
|
||||||
Validators
|
|
||||||
==========
|
|
||||||
|
|
||||||
One of the main features of Anchor are the validators which make sure
|
|
||||||
that all requests match a given set of rules. They're configured in
|
|
||||||
`config.json` and the sample configuration includes a few of them.
|
|
||||||
|
|
||||||
Each validator takes a dictionary of options which provide the specific
|
|
||||||
matching conditions.
|
|
||||||
|
|
||||||
Currently available validators are:
|
|
||||||
|
|
||||||
* `common_name` ensures CN matches one of names in `allowed_domains` or
|
|
||||||
ranges in `allowed_networks`
|
|
||||||
|
|
||||||
* `alternative_names` ensures alternative names match one of the names
|
|
||||||
in `allowed_domains`
|
|
||||||
|
|
||||||
* `alternative_names_ip` ensures alternative names match one of the
|
|
||||||
names in `allowed_domains` or IP ranges in `allowed_networks`
|
|
||||||
|
|
||||||
* `blacklist_names` ensures CN and alternative names do not contain any
|
|
||||||
of the configured `domains`
|
|
||||||
|
|
||||||
* `server_group` ensures the group the requester is contained within
|
|
||||||
`group_prefixes`
|
|
||||||
|
|
||||||
* `extensions` ensures only `allowed_extensions` are present in the
|
|
||||||
request
|
|
||||||
|
|
||||||
* `key_usage` ensures only `allowed_usage` is requested for the
|
|
||||||
certificate
|
|
||||||
|
|
||||||
* `ca_status` ensures the request does/doesn't require the CA flag
|
|
||||||
|
|
||||||
* `source_cidrs` ensures the request comes from one of the ranges in
|
|
||||||
`cidrs`
|
|
||||||
|
|
||||||
A configuration entry for a validator might look like one from the
|
|
||||||
sample config:
|
|
||||||
|
|
||||||
"key_usage": {
|
|
||||||
"allowed_usage": [
|
|
||||||
"Digital Signature",
|
|
||||||
"Key Encipherment",
|
|
||||||
"Non Repudiation"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication
|
|
||||||
==============
|
|
||||||
|
|
||||||
Anchor can use one of the following authentication modules: static,
|
|
||||||
keystone, ldap.
|
|
||||||
|
|
||||||
Static: Username and password are present in `config.json`. This mode
|
|
||||||
should be used only for development and testing.
|
|
||||||
|
|
||||||
"auth": {
|
|
||||||
"static": {
|
|
||||||
"secret": "simplepassword",
|
|
||||||
"user": "myusername"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keystone: Username is ignored, but password is a token valid in the
|
|
||||||
configured keystone location.
|
|
||||||
|
|
||||||
"auth": {
|
|
||||||
"keystone": {
|
|
||||||
"url": "https://keystone.example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LDAP: Username and password are used to bind to an LDAP user in a
|
|
||||||
configured domain. User's groups for the `server_group` filter are
|
|
||||||
retrieved from attribute `memberOf` in search for
|
|
||||||
`(sAMAccountName=username@domain)`. The search is done in the configured
|
|
||||||
base.
|
|
||||||
|
|
||||||
"auth": {
|
|
||||||
"ldap": {
|
|
||||||
"host": "ldap.example.com",
|
|
||||||
"base": "ou=Users,dc=example,dc=com",
|
|
||||||
"domain": "example.com"
|
|
||||||
"port": 636,
|
|
||||||
"ssl": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Signing backends
|
|
||||||
================
|
|
||||||
|
|
||||||
Anchor allows the use of configurable signing backend. Currently it provides two
|
|
||||||
implementation: one based on cryptography.io ("anchor"), the other using PKCS#11
|
|
||||||
libraries ("pkcs11"). The first one is used in the sample config. Other backends
|
|
||||||
may have extra dependencies: pkcs11 requires the PyKCS11 module, not required by
|
|
||||||
anchor by default.
|
|
||||||
|
|
||||||
The resulting certificate is stored locally if the `output_path` is set
|
|
||||||
to any string. This does not depend on the configured backend.
|
|
||||||
|
|
||||||
Backends can specify their own options - please refer to the backend
|
|
||||||
documentation for the specific list. The default backend takes the
|
|
||||||
following options:
|
|
||||||
|
|
||||||
* `cert_path`: path where local CA certificate can be found
|
|
||||||
|
|
||||||
* `key_path`: path to the key for that certificate
|
|
||||||
|
|
||||||
* `signing_hash`: which hash method to use when producing signatures
|
|
||||||
|
|
||||||
* `valid_hours`: number of hours the signed certificates are valid for
|
|
||||||
|
|
||||||
Sample configuration for the default backend:
|
|
||||||
|
|
||||||
"ca": {
|
|
||||||
"backend": "anchor"
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"key_path": "CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
|
|
||||||
Other backends may be created too. For more information, please refer to the
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Fixups
|
|
||||||
======
|
|
||||||
|
|
||||||
Anchor can modify the submitted CSRs in order to enforce some rules,
|
|
||||||
remove deprecated elements, or just add information. Submitted CSR may
|
|
||||||
be modified or entirely redone. Fixup are loaded from "anchor.fixups"
|
|
||||||
namespace and can take parameters just like validators.
|
|
||||||
|
|
||||||
Reporting bugs and contributing
|
|
||||||
===============================
|
|
||||||
|
|
||||||
For bug reporting and contributing, please check the CONTRIBUTING.rst
|
|
||||||
file.
|
|
||||||
|
@ -1,284 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import binascii
|
|
||||||
import io
|
|
||||||
|
|
||||||
from cryptography.hazmat import backends as cio_backends
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from pyasn1.codec.ber import encoder as ber_encoder
|
|
||||||
from pyasn1.codec.der import decoder
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
from pyasn1_modules import pem
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name
|
|
||||||
from anchor.X509 import signature
|
|
||||||
from anchor.X509 import utils
|
|
||||||
|
|
||||||
|
|
||||||
SIGNING_ALGORITHMS = {
|
|
||||||
('RSA', 'SHA224'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.14'),
|
|
||||||
('RSA', 'SHA256'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.11'),
|
|
||||||
('RSA', 'SHA384'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.12'),
|
|
||||||
('RSA', 'SHA512'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.13'),
|
|
||||||
('DSA', 'SHA224'): asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.1'),
|
|
||||||
('DSA', 'SHA256'): asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.2'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SIGNING_ALGORITHMS_INV = dict((v, k) for k, v in SIGNING_ALGORITHMS.items())
|
|
||||||
|
|
||||||
|
|
||||||
class X509CertificateError(errors.X509Error):
|
|
||||||
"""Specific error for X509 certificate operations."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class X509Certificate(signature.SignatureMixin):
|
|
||||||
"""X509 certificate class."""
|
|
||||||
def __init__(self, certificate=None):
|
|
||||||
if certificate is None:
|
|
||||||
self._cert = rfc5280.Certificate()
|
|
||||||
self._cert['tbsCertificate'] = rfc5280.TBSCertificate()
|
|
||||||
else:
|
|
||||||
self._cert = certificate
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_open_file(f):
|
|
||||||
try:
|
|
||||||
der_content = pem.readPemFromFile(f)
|
|
||||||
certificate = decoder.decode(der_content,
|
|
||||||
asn1Spec=rfc5280.Certificate())[0]
|
|
||||||
return X509Certificate(certificate)
|
|
||||||
except Exception:
|
|
||||||
raise X509CertificateError("Could not read X509 certificate from "
|
|
||||||
"PEM data.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_buffer(data):
|
|
||||||
"""Build this X509 object from a data buffer in memory.
|
|
||||||
|
|
||||||
:param data: A data buffer
|
|
||||||
"""
|
|
||||||
return X509Certificate.from_open_file(io.StringIO(data))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_file(path):
|
|
||||||
"""Build this X509 certificate object from a data file on disk.
|
|
||||||
|
|
||||||
:param path: A data buffer
|
|
||||||
"""
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
return X509Certificate.from_open_file(f)
|
|
||||||
|
|
||||||
def as_pem(self):
|
|
||||||
"""Serialise this X509 certificate object as PEM string."""
|
|
||||||
|
|
||||||
header = '-----BEGIN CERTIFICATE-----'
|
|
||||||
footer = '-----END CERTIFICATE-----'
|
|
||||||
der_cert = encoder.encode(self._cert)
|
|
||||||
b64_encoder = (base64.encodestring if str is bytes else
|
|
||||||
base64.encodebytes)
|
|
||||||
b64_cert = b64_encoder(der_cert).decode('ascii')
|
|
||||||
return "%s\n%s%s\n" % (header, b64_cert, footer)
|
|
||||||
|
|
||||||
def set_version(self, v):
|
|
||||||
"""Set the version of this X509 certificate object.
|
|
||||||
|
|
||||||
:param v: The version
|
|
||||||
"""
|
|
||||||
self._cert['tbsCertificate']['version'] = v
|
|
||||||
|
|
||||||
def get_version(self):
|
|
||||||
"""Get the version of this X509 certificate object."""
|
|
||||||
return self._cert['tbsCertificate']['version']
|
|
||||||
|
|
||||||
def get_validity(self):
|
|
||||||
if self._cert['tbsCertificate']['validity'] is None:
|
|
||||||
self._cert['tbsCertificate']['validity'] = None
|
|
||||||
return self._cert['tbsCertificate']['validity']
|
|
||||||
|
|
||||||
def set_not_before(self, t):
|
|
||||||
"""Set the 'not before' date field.
|
|
||||||
|
|
||||||
:param t: time in seconds since the epoch
|
|
||||||
"""
|
|
||||||
asn1_time = utils.timestamp_to_asn1_time(t)
|
|
||||||
validity = self.get_validity()
|
|
||||||
validity['notBefore'] = asn1_time
|
|
||||||
|
|
||||||
def get_not_before(self):
|
|
||||||
"""Get the 'not before' date field as seconds since the epoch."""
|
|
||||||
validity = self.get_validity()
|
|
||||||
not_before = validity['notBefore']
|
|
||||||
return utils.asn1_time_to_timestamp(not_before)
|
|
||||||
|
|
||||||
def set_not_after(self, t):
|
|
||||||
"""Set the 'not after' date field.
|
|
||||||
|
|
||||||
:param t: time in seconds since the epoch
|
|
||||||
"""
|
|
||||||
asn1_time = utils.timestamp_to_asn1_time(t)
|
|
||||||
validity = self.get_validity()
|
|
||||||
validity['notAfter'] = asn1_time
|
|
||||||
|
|
||||||
def get_not_after(self):
|
|
||||||
"""Get the 'not after' date field as seconds since the epoch."""
|
|
||||||
validity = self.get_validity()
|
|
||||||
not_after = validity['notAfter']
|
|
||||||
return utils.asn1_time_to_timestamp(not_after)
|
|
||||||
|
|
||||||
def set_pubkey(self, pkey):
|
|
||||||
"""Set the public key field.
|
|
||||||
|
|
||||||
:param pkey: The public key, rfc5280.SubjectPublicKeyInfo description
|
|
||||||
"""
|
|
||||||
self._cert['tbsCertificate']['subjectPublicKeyInfo'] = pkey
|
|
||||||
|
|
||||||
def get_subject(self):
|
|
||||||
"""Get the subject name field value.
|
|
||||||
|
|
||||||
:return: An X509Name object instance
|
|
||||||
"""
|
|
||||||
val = self._cert['tbsCertificate']['subject'][0]
|
|
||||||
return name.X509Name(val)
|
|
||||||
|
|
||||||
def set_subject(self, subject):
|
|
||||||
"""Set the subject name filed value.
|
|
||||||
|
|
||||||
:param subject: An X509Name object instance
|
|
||||||
"""
|
|
||||||
val = subject._name_obj
|
|
||||||
if self._cert['tbsCertificate']['subject'] is None:
|
|
||||||
self._cert['tbsCertificate']['subject'] = rfc5280.Name()
|
|
||||||
self._cert['tbsCertificate']['subject'][0] = val
|
|
||||||
|
|
||||||
def set_issuer(self, issuer):
|
|
||||||
"""Set the issuer name field value.
|
|
||||||
|
|
||||||
:param issuer: An X509Name object instance
|
|
||||||
"""
|
|
||||||
val = issuer._name_obj
|
|
||||||
if self._cert['tbsCertificate']['issuer'] is None:
|
|
||||||
self._cert['tbsCertificate']['issuer'] = rfc5280.Name()
|
|
||||||
self._cert['tbsCertificate']['issuer'][0] = val
|
|
||||||
|
|
||||||
def get_issuer(self):
|
|
||||||
"""Get the issuer name field value.
|
|
||||||
|
|
||||||
:return: An X509Name object instance
|
|
||||||
"""
|
|
||||||
val = self._cert['tbsCertificate']['issuer'][0]
|
|
||||||
return name.X509Name(val)
|
|
||||||
|
|
||||||
def set_serial_number(self, serial):
|
|
||||||
"""Set the serial number
|
|
||||||
|
|
||||||
The serial number is a 32 bit integer value that should be unique to
|
|
||||||
each certificate issued by a given certificate authority.
|
|
||||||
|
|
||||||
:param serial: The serial number, 32 bit integer
|
|
||||||
"""
|
|
||||||
self._cert['tbsCertificate']['serialNumber'] = serial
|
|
||||||
|
|
||||||
def get_serial_number(self,):
|
|
||||||
return self._cert['tbsCertificate']['serialNumber']
|
|
||||||
|
|
||||||
def _get_extensions(self):
|
|
||||||
if self._cert['tbsCertificate']['extensions'] is None:
|
|
||||||
# this actually initialises the extensions tag rather than
|
|
||||||
# assign None
|
|
||||||
self._cert['tbsCertificate']['extensions'] = None
|
|
||||||
return self._cert['tbsCertificate']['extensions']
|
|
||||||
|
|
||||||
def get_extensions(self, ext_type=None):
|
|
||||||
extensions = self._get_extensions()
|
|
||||||
return [extension.construct_extension(e) for e in extensions
|
|
||||||
if ext_type is None or e['extnID'] == ext_type._oid]
|
|
||||||
|
|
||||||
def add_extension(self, ext, index):
|
|
||||||
"""Add an X509 V3 Certificate extension.
|
|
||||||
|
|
||||||
:param ext: An X509Extension instance
|
|
||||||
:param index: The index of the extension
|
|
||||||
"""
|
|
||||||
if not isinstance(ext, extension.X509Extension):
|
|
||||||
raise errors.X509Error("ext needs to be a pyasn1 extension")
|
|
||||||
|
|
||||||
extensions = self._get_extensions()
|
|
||||||
extensions[index] = ext.as_asn1()
|
|
||||||
|
|
||||||
def _get_bytes_to_sign(self):
|
|
||||||
return encoder.encode(self._cert['tbsCertificate'])
|
|
||||||
|
|
||||||
def _embed_signature_algorithm(self, algo_id):
|
|
||||||
self._cert['tbsCertificate']['signature'] = algo_id
|
|
||||||
|
|
||||||
def _embed_signature(self, algo_id, signature):
|
|
||||||
self._cert['signature'] = "'%s'H" % (
|
|
||||||
str(binascii.hexlify(signature).decode('ascii')),)
|
|
||||||
self._cert['signatureAlgorithm'] = algo_id
|
|
||||||
|
|
||||||
def _get_signature(self):
|
|
||||||
return utils.bin_to_bytes(self._cert['signature'])
|
|
||||||
|
|
||||||
def _get_signing_algorithm(self):
|
|
||||||
tbs_signature = self._cert['tbsCertificate']['signature']
|
|
||||||
cert_signature = self._cert['signatureAlgorithm']
|
|
||||||
if tbs_signature != cert_signature:
|
|
||||||
raise errors.X509Error("algorithms mismatch")
|
|
||||||
|
|
||||||
return tbs_signature['algorithm']
|
|
||||||
|
|
||||||
def as_der(self):
|
|
||||||
"""Return this X509 certificate as DER encoded data."""
|
|
||||||
return encoder.encode(self._cert)
|
|
||||||
|
|
||||||
def get_fingerprint(self, md='sha256'):
|
|
||||||
"""Get the fingerprint of this X509 certificate.
|
|
||||||
|
|
||||||
:param md: The message digest algorithm used to compute the fingerprint
|
|
||||||
:return: The fingerprint encoded as a hex string
|
|
||||||
"""
|
|
||||||
hash_class = utils.get_hash_class(md)
|
|
||||||
if hash_class is None:
|
|
||||||
raise errors.X509Error(
|
|
||||||
"Unknown hash %s" % (md,))
|
|
||||||
hasher = hashes.Hash(hash_class(),
|
|
||||||
backend=cio_backends.default_backend())
|
|
||||||
hasher.update(self.as_der())
|
|
||||||
return binascii.hexlify(hasher.finalize()).upper().decode('ascii')
|
|
||||||
|
|
||||||
def get_key_id(self):
|
|
||||||
"""Construct a key identifier from public key.
|
|
||||||
|
|
||||||
Return the hash useful for keyIdentifier field, constructed as
|
|
||||||
described in RFC5280 section 4.2.1.2, method 1. The result is
|
|
||||||
SHA1(subjectPublicKey).
|
|
||||||
"""
|
|
||||||
key_info = self._cert['tbsCertificate']['subjectPublicKeyInfo']
|
|
||||||
public_key = key_info['subjectPublicKey']
|
|
||||||
# get the actual bit string value, without the length and tags
|
|
||||||
value = ber_encoder.BitStringEncoder().encodeValue(
|
|
||||||
None, public_key, True, None)[0][1:]
|
|
||||||
digest = hashes.Hash(hashes.SHA1(),
|
|
||||||
backend=cio_backends.default_backend())
|
|
||||||
digest.update(value)
|
|
||||||
return digest.finalize()
|
|
@ -1,31 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# not needed right now, just to be consistent and future-proof
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
|
|
||||||
class X509Error(Exception):
|
|
||||||
"""Base exception for X509 errors."""
|
|
||||||
def __init__(self, what):
|
|
||||||
super(X509Error, self).__init__(what)
|
|
||||||
|
|
||||||
|
|
||||||
class ASN1TimeError(Exception):
|
|
||||||
"""Base exception for ASN1-time related errors."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ASN1StringError(X509Error):
|
|
||||||
"""Base exception for ASN1-string related errors."""
|
|
||||||
pass
|
|
@ -1,523 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from pyasn1.codec.der import decoder
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import constraint as asn1_constraint
|
|
||||||
from pyasn1.type import namedtype as asn1_namedtype
|
|
||||||
from pyasn1.type import tag as asn1_tag
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
from anchor.X509 import utils
|
|
||||||
|
|
||||||
|
|
||||||
# missing extended use ids from rfc5280
|
|
||||||
id_kp_OCSPSigning = asn1_univ.ObjectIdentifier(rfc5280.id_kp.asTuple() + (9,))
|
|
||||||
anyExtendedKeyUsage = asn1_univ.ObjectIdentifier(
|
|
||||||
rfc5280.id_ce_extKeyUsage.asTuple() + (0,))
|
|
||||||
|
|
||||||
|
|
||||||
# names matching openssl
|
|
||||||
EXT_KEY_USAGE_NAMES = {
|
|
||||||
rfc5280.id_kp_serverAuth: "TLS Web Server Authentication",
|
|
||||||
rfc5280.id_kp_clientAuth: "TLS Web Client Authentication",
|
|
||||||
rfc5280.id_kp_codeSigning: "Code Signing",
|
|
||||||
rfc5280.id_kp_emailProtection: "E-mail Protection",
|
|
||||||
rfc5280.id_kp_timeStamping: "Time Stamping",
|
|
||||||
id_kp_OCSPSigning: "OCSP Signing",
|
|
||||||
anyExtendedKeyUsage: "Any Extended Key Usage",
|
|
||||||
}
|
|
||||||
EXT_KEY_USAGE_NAMES_INV = dict((v, k) for k, v in EXT_KEY_USAGE_NAMES.items())
|
|
||||||
|
|
||||||
|
|
||||||
EXT_KEY_USAGE_SHORT_NAMES = {
|
|
||||||
rfc5280.id_kp_serverAuth: "serverAuth",
|
|
||||||
rfc5280.id_kp_clientAuth: "clientAuth",
|
|
||||||
rfc5280.id_kp_codeSigning: "codeSigning",
|
|
||||||
rfc5280.id_kp_emailProtection: "emailProtection",
|
|
||||||
rfc5280.id_kp_timeStamping: "timeStamping",
|
|
||||||
id_kp_OCSPSigning: "ocspSigning",
|
|
||||||
anyExtendedKeyUsage: "anyExtendedKeyUsage",
|
|
||||||
}
|
|
||||||
EXT_KEY_USAGE_SHORT_NAMES_INV = dict((v, k) for k, v in
|
|
||||||
EXT_KEY_USAGE_SHORT_NAMES.items())
|
|
||||||
|
|
||||||
|
|
||||||
EXTENSION_NAMES = {
|
|
||||||
rfc5280.id_ce_policyConstraints: 'policyConstraints',
|
|
||||||
rfc5280.id_ce_basicConstraints: 'basicConstraints',
|
|
||||||
rfc5280.id_ce_subjectDirectoryAttributes: 'subjectDirectoryAttributes',
|
|
||||||
rfc5280.id_ce_deltaCRLIndicator: 'deltaCRLIndicator',
|
|
||||||
rfc5280.id_ce_cRLDistributionPoints: 'cRLDistributionPoints',
|
|
||||||
rfc5280.id_ce_issuingDistributionPoint: 'issuingDistributionPoint',
|
|
||||||
rfc5280.id_ce_nameConstraints: 'nameConstraints',
|
|
||||||
rfc5280.id_ce_certificatePolicies: 'certificatePolicies',
|
|
||||||
rfc5280.id_ce_policyMappings: 'policyMappings',
|
|
||||||
rfc5280.id_ce_privateKeyUsagePeriod: 'privateKeyUsagePeriod',
|
|
||||||
rfc5280.id_ce_keyUsage: 'keyUsage',
|
|
||||||
rfc5280.id_ce_authorityKeyIdentifier: 'authorityKeyIdentifier',
|
|
||||||
rfc5280.id_ce_subjectKeyIdentifier: 'subjectKeyIdentifier',
|
|
||||||
rfc5280.id_ce_certificateIssuer: 'certificateIssuer',
|
|
||||||
rfc5280.id_ce_subjectAltName: 'subjectAltName',
|
|
||||||
rfc5280.id_ce_issuerAltName: 'issuerAltName',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
LONG_KEY_USAGE_NAMES = {
|
|
||||||
"Digital Signature": "digitalSignature",
|
|
||||||
"Non Repudiation": "nonRepudiation",
|
|
||||||
"Key Encipherment": "keyEncipherment",
|
|
||||||
"Data Encipherment": "dataEncipherment",
|
|
||||||
"Key Agreement": "keyAgreement",
|
|
||||||
"Certificate Sign": "keyCertSign",
|
|
||||||
"CRL Sign": "cRLSign",
|
|
||||||
"Encipher Only": "encipherOnly",
|
|
||||||
"Decipher Only": "decipherOnly",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def uses_ext_value(f):
|
|
||||||
"""Wrapper allowing reading of extension value.
|
|
||||||
|
|
||||||
Because the value is normally saved in a (double) serialised way, it's
|
|
||||||
not easily accessible to the member methods. This is made easier by
|
|
||||||
unpacking the extension value into an extra argument.
|
|
||||||
"""
|
|
||||||
@functools.wraps(f)
|
|
||||||
def ext_value_filled(self, *args, **kwargs):
|
|
||||||
kwargs['ext_value'] = self._get_value()
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
return ext_value_filled
|
|
||||||
|
|
||||||
|
|
||||||
def modifies_ext_value(f):
|
|
||||||
"""Wrapper allowing modification of extension value.
|
|
||||||
|
|
||||||
Because the value is normally saved in a (double) serialised way, it's
|
|
||||||
not easily accessible to the member methods. This is made easier by
|
|
||||||
unpacking the extension value into an extra argument.
|
|
||||||
New value needs to be returned from the method.
|
|
||||||
"""
|
|
||||||
@functools.wraps(f)
|
|
||||||
def ext_value_filled(self, *args, **kwargs):
|
|
||||||
value = self._get_value()
|
|
||||||
kwargs['ext_value'] = value
|
|
||||||
# since some elements like NamedValue are pure value types, there is
|
|
||||||
# no interface to modify them and new versions have to be returned
|
|
||||||
value = f(self, *args, **kwargs)
|
|
||||||
self._set_value(value)
|
|
||||||
return ext_value_filled
|
|
||||||
|
|
||||||
|
|
||||||
class BasicConstraints(asn1_univ.Sequence):
|
|
||||||
"""Custom BasicConstraint implementation until pyasn1_modules is fixes."""
|
|
||||||
componentType = asn1_namedtype.NamedTypes(
|
|
||||||
asn1_namedtype.DefaultedNamedType('cA', asn1_univ.Boolean(False)),
|
|
||||||
asn1_namedtype.OptionalNamedType(
|
|
||||||
'pathLenConstraint',
|
|
||||||
asn1_univ.Integer().subtype(
|
|
||||||
subtypeSpec=asn1_constraint.ValueRangeConstraint(0, 64)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NameConstraints(asn1_univ.Sequence):
|
|
||||||
"""Custom NameConstraints implementation until pyasn1_modules is fixed."""
|
|
||||||
componentType = asn1_namedtype.NamedTypes(
|
|
||||||
asn1_namedtype.OptionalNamedType(
|
|
||||||
'permittedSubtrees',
|
|
||||||
rfc5280.GeneralSubtrees().subtype(
|
|
||||||
implicitTag=asn1_tag.Tag(asn1_tag.tagClassContext,
|
|
||||||
asn1_tag.tagFormatConstructed, 0))),
|
|
||||||
asn1_namedtype.OptionalNamedType(
|
|
||||||
'excludedSubtrees',
|
|
||||||
rfc5280.GeneralSubtrees().subtype(
|
|
||||||
implicitTag=asn1_tag.Tag(asn1_tag.tagClassContext,
|
|
||||||
asn1_tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class X509Extension(object):
|
|
||||||
"""Abstraction for the pyasn1 Extension structures.
|
|
||||||
|
|
||||||
The object should normally be constructed using `construct_extension`,
|
|
||||||
which will choose the right extension type based on the id.
|
|
||||||
Each extension has an immutable oid and a spec of the internal value
|
|
||||||
representation.
|
|
||||||
Unknown extension types can be still represented by the
|
|
||||||
X509Extension object and copied/serialised without understanding the
|
|
||||||
value details. The value will not be displayed properly in the logs
|
|
||||||
in the case.
|
|
||||||
"""
|
|
||||||
_oid = None
|
|
||||||
spec = None
|
|
||||||
|
|
||||||
"""An X509 V3 Certificate extension."""
|
|
||||||
def __init__(self, ext=None):
|
|
||||||
if ext is None:
|
|
||||||
if self.spec is None:
|
|
||||||
raise errors.X509Error("cannot create generic extension")
|
|
||||||
self._ext = rfc5280.Extension()
|
|
||||||
self._ext['extnID'] = self._oid
|
|
||||||
self._set_value(self._get_default_value())
|
|
||||||
else:
|
|
||||||
if not isinstance(ext, rfc5280.Extension):
|
|
||||||
raise errors.X509Error("extension has incorrect type")
|
|
||||||
self._ext = ext
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_default_value(cls):
|
|
||||||
# if there are any non-optional fields, this needs to be defined in
|
|
||||||
# the class
|
|
||||||
return cls.spec()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s: %s" % (self.get_name(), self.get_value_as_str())
|
|
||||||
|
|
||||||
def get_value_as_str(self):
|
|
||||||
return "<unknown>"
|
|
||||||
|
|
||||||
def get_oid(self):
|
|
||||||
return self._ext['extnID']
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
"""Get the extension name as a python string."""
|
|
||||||
oid = self.get_oid()
|
|
||||||
return EXTENSION_NAMES.get(oid, oid)
|
|
||||||
|
|
||||||
def get_critical(self):
|
|
||||||
return self._ext['critical']
|
|
||||||
|
|
||||||
def set_critical(self, critical):
|
|
||||||
self._ext['critical'] = critical
|
|
||||||
|
|
||||||
def _get_value(self):
|
|
||||||
return decoder.decode(self._ext['extnValue'].asOctets(),
|
|
||||||
asn1Spec=self.spec())[0]
|
|
||||||
|
|
||||||
def _set_value(self, value):
|
|
||||||
if not isinstance(value, self.spec):
|
|
||||||
raise errors.X509Error("extension value has incorrect type")
|
|
||||||
self._ext['extnValue'] = encoder.encode(value)
|
|
||||||
|
|
||||||
def as_der(self):
|
|
||||||
return encoder.encode(self._ext)
|
|
||||||
|
|
||||||
def as_asn1(self):
|
|
||||||
return self._ext
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionBasicConstraints(X509Extension):
|
|
||||||
spec = BasicConstraints
|
|
||||||
_oid = rfc5280.id_ce_basicConstraints
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_ca(self, ext_value=None):
|
|
||||||
return bool(ext_value['cA'])
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_ca(self, ca, ext_value=None):
|
|
||||||
ext_value['cA'] = ca
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_path_len_constraint(self, ext_value=None):
|
|
||||||
return ext_value['pathLenConstraint']
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_path_len_constraint(self, length, ext_value=None):
|
|
||||||
ext_value['pathLenConstraint'] = length
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "basicConstraints: CA: %s, pathLen: %s" % (
|
|
||||||
str(self.get_ca()).upper(), self.get_path_len_constraint())
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionKeyUsage(X509Extension):
|
|
||||||
spec = rfc5280.KeyUsage
|
|
||||||
_oid = rfc5280.id_ce_keyUsage
|
|
||||||
|
|
||||||
fields = dict(spec.namedValues.namedValues)
|
|
||||||
inv_fields = dict((v, k) for k, v in spec.namedValues.namedValues)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_default_value(cls):
|
|
||||||
# if there are any non-optional fields, this needs to be defined in
|
|
||||||
# the class
|
|
||||||
return cls.spec("''B")
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_usage(self, usage, ext_value=None):
|
|
||||||
usage = LONG_KEY_USAGE_NAMES.get(usage, usage)
|
|
||||||
pos = self.fields[usage]
|
|
||||||
if pos >= len(ext_value):
|
|
||||||
return False
|
|
||||||
return bool(ext_value[pos])
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_all_usages(self, ext_value=None):
|
|
||||||
return [self.inv_fields[i] for i, enabled in enumerate(ext_value)
|
|
||||||
if enabled]
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_usage(self, usage, state, ext_value=None):
|
|
||||||
usage = LONG_KEY_USAGE_NAMES.get(usage, usage)
|
|
||||||
pos = self.fields[usage]
|
|
||||||
values = [x for x in ext_value]
|
|
||||||
|
|
||||||
if state:
|
|
||||||
while pos >= len(values):
|
|
||||||
values.append(0)
|
|
||||||
values[pos] = 1
|
|
||||||
else:
|
|
||||||
if pos < len(values):
|
|
||||||
values[pos] = 0
|
|
||||||
|
|
||||||
bits = ''.join(str(x) for x in values)
|
|
||||||
return self.spec("'%s'B" % bits)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "keyUsage: " + ", ".join(self.get_all_usages())
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionSubjectAltName(X509Extension):
|
|
||||||
spec = rfc5280.SubjectAltName
|
|
||||||
_oid = rfc5280.id_ce_subjectAltName
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_dns_ids(self, ext_value=None):
|
|
||||||
dns_ids = []
|
|
||||||
for name in ext_value:
|
|
||||||
if name.getName() != 'dNSName':
|
|
||||||
continue
|
|
||||||
component = name.getComponent()
|
|
||||||
dns_id = component.asOctets().decode(component.encoding)
|
|
||||||
dns_ids.append(dns_id)
|
|
||||||
return dns_ids
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_ips(self, ext_value=None):
|
|
||||||
ips = []
|
|
||||||
for name in ext_value:
|
|
||||||
if name.getName() != 'iPAddress':
|
|
||||||
continue
|
|
||||||
ips.append(utils.asn1_to_netaddr(name.getComponent()))
|
|
||||||
return ips
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def has_unknown_entries(self, ext_value=None):
|
|
||||||
for name in ext_value:
|
|
||||||
if name.getName() not in ('dNSName', 'iPAddress'):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def add_dns_id(self, dns_id, validate=True, ext_value=None):
|
|
||||||
new_pos = len(ext_value)
|
|
||||||
ext_value[new_pos] = None
|
|
||||||
ext_value[new_pos]['dNSName'] = dns_id
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def add_ip(self, ip, ext_value=None):
|
|
||||||
if not isinstance(ip, netaddr.IPAddress):
|
|
||||||
raise errors.X509Error("not a real ip address provided")
|
|
||||||
new_pos = len(ext_value)
|
|
||||||
ext_value[new_pos] = None
|
|
||||||
ext_value[new_pos]['iPAddress'] = utils.netaddr_to_asn1(ip)
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def __str__(self, ext_value=None):
|
|
||||||
entries = ["DNS:%s" % (x,) for x in self.get_dns_ids()]
|
|
||||||
entries += ["IP:%s" % (x,) for x in self.get_ips()]
|
|
||||||
return "subjectAltName: " + ", ".join(entries)
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionNameConstraints(X509Extension):
|
|
||||||
spec = NameConstraints
|
|
||||||
_oid = rfc5280.id_ce_nameConstraints
|
|
||||||
|
|
||||||
def _get_permitted(self, ext_value):
|
|
||||||
return ext_value['permittedSubtrees'] or []
|
|
||||||
|
|
||||||
def _get_excluded(self, ext_value):
|
|
||||||
return ext_value['excludedSubtrees'] or []
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_permitted_length(self, ext_value=None):
|
|
||||||
return len(self._get_permitted(ext_value))
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_permitted_name(self, n, ext_value=None):
|
|
||||||
name = self._get_permitted(ext_value)[n]['base']
|
|
||||||
return (name.getName(), name.getComponent())
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_permitted_range(self, n, ext_value=None):
|
|
||||||
entry = self._get_permitted(ext_value)[n]
|
|
||||||
return (entry['minimum'], entry['maximum'])
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_excluded_length(self, ext_value=None):
|
|
||||||
return len(self._get_excluded(ext_value))
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_excluded_name(self, n, ext_value=None):
|
|
||||||
name = self._get_excluded(ext_value)[n]['base']
|
|
||||||
return (name.getName(), name.getComponent())
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_excluded_range(self, n, ext_value=None):
|
|
||||||
entry = self._get_excluded(ext_value)[n]
|
|
||||||
return (entry['minimum'], entry['maximum'])
|
|
||||||
|
|
||||||
def _add_to_tree(self, ext_value, tree_name, position, name_type, name):
|
|
||||||
if ext_value[tree_name] is None:
|
|
||||||
ext_value[tree_name] = None
|
|
||||||
ext_value[tree_name][position] = None
|
|
||||||
ext_value[tree_name][position]['base'] = None
|
|
||||||
ext_value[tree_name][position]['base'][name_type] = name
|
|
||||||
ext_value[tree_name][position]['minimum'] = 0
|
|
||||||
# maximum should be missing (RFC5280/4.2.1.10)
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def add_permitted(self, name_type, name, ext_value=None):
|
|
||||||
last = self.get_permitted_length()
|
|
||||||
self._add_to_tree(ext_value, 'permittedSubtrees', last,
|
|
||||||
name_type, name)
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def add_excluded(self, name_type, name, ext_value=None):
|
|
||||||
last = self.get_excluded_length()
|
|
||||||
self._add_to_tree(ext_value, 'excludedSubtrees', last, name_type, name)
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionExtendedKeyUsage(X509Extension):
|
|
||||||
spec = rfc5280.ExtKeyUsageSyntax
|
|
||||||
_oid = rfc5280.id_ce_extKeyUsage
|
|
||||||
|
|
||||||
_valid = list(EXT_KEY_USAGE_NAMES.keys())
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_all_usages(self, ext_value=None):
|
|
||||||
return [usage for usage in ext_value]
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_usage(self, usage, ext_value=None):
|
|
||||||
if usage not in self._valid:
|
|
||||||
raise ValueError("usage not valid")
|
|
||||||
return (usage in ext_value)
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_usage(self, usage, state, ext_value=None):
|
|
||||||
if usage not in self._valid:
|
|
||||||
raise ValueError("usage not valid")
|
|
||||||
|
|
||||||
if state:
|
|
||||||
if usage not in ext_value:
|
|
||||||
ext_value[len(ext_value)] = usage
|
|
||||||
else:
|
|
||||||
if usage in ext_value:
|
|
||||||
old = [x for x in ext_value if x != usage]
|
|
||||||
ext_value.clear()
|
|
||||||
for i, x in enumerate(old):
|
|
||||||
ext_value[i] = x
|
|
||||||
return ext_value
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def __str__(self, ext_value=None):
|
|
||||||
usages = [EXT_KEY_USAGE_NAMES.get(u) for u in ext_value]
|
|
||||||
return "extKeyUsage: " + ", ".join(usages)
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionAuthorityKeyId(X509Extension):
|
|
||||||
spec = rfc5280.AuthorityKeyIdentifier
|
|
||||||
_oid = rfc5280.id_ce_authorityKeyIdentifier
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_key_id(self, ext_value=None):
|
|
||||||
ki = ext_value['keyIdentifier']
|
|
||||||
if ki:
|
|
||||||
return ki.asOctets()
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_serial(self, ext_value=None):
|
|
||||||
return ext_value['authorityCertSerialNumber']
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_key_id(self, key, ext_value=None):
|
|
||||||
# new extension, pyasn1 cannot remove values
|
|
||||||
new_ext = self.spec()
|
|
||||||
new_ext['keyIdentifier'] = key
|
|
||||||
return new_ext
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_serial(self, serial, ext_value=None):
|
|
||||||
# new extension, pyasn1 cannot remove values
|
|
||||||
new_ext = self.spec()
|
|
||||||
new_ext['authorityCertSerialNumber'] = int(serial)
|
|
||||||
return new_ext
|
|
||||||
|
|
||||||
|
|
||||||
class X509ExtensionSubjectKeyId(X509Extension):
|
|
||||||
spec = rfc5280.SubjectKeyIdentifier
|
|
||||||
_oid = rfc5280.id_ce_subjectKeyIdentifier
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_default_value(cls):
|
|
||||||
return cls.spec(b"")
|
|
||||||
|
|
||||||
@uses_ext_value
|
|
||||||
def get_key_id(self, ext_value=None):
|
|
||||||
return ext_value.asOctets()
|
|
||||||
|
|
||||||
@modifies_ext_value
|
|
||||||
def set_key_id(self, key, ext_value=None):
|
|
||||||
return self.spec(key)
|
|
||||||
|
|
||||||
|
|
||||||
EXTENSION_CLASSES = {
|
|
||||||
rfc5280.id_ce_basicConstraints: X509ExtensionBasicConstraints,
|
|
||||||
rfc5280.id_ce_keyUsage: X509ExtensionKeyUsage,
|
|
||||||
rfc5280.id_ce_extKeyUsage: X509ExtensionExtendedKeyUsage,
|
|
||||||
rfc5280.id_ce_subjectAltName: X509ExtensionSubjectAltName,
|
|
||||||
rfc5280.id_ce_nameConstraints: X509ExtensionNameConstraints,
|
|
||||||
rfc5280.id_ce_authorityKeyIdentifier: X509ExtensionAuthorityKeyId,
|
|
||||||
rfc5280.id_ce_subjectKeyIdentifier: X509ExtensionSubjectKeyId,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def construct_extension(ext):
|
|
||||||
"""Construct an extension object of the right type.
|
|
||||||
|
|
||||||
While X509Extension can provide basic access to the extension elements,
|
|
||||||
it cannot parse details of extensions. This function detects which type
|
|
||||||
should be used based on the extension id.
|
|
||||||
If the type is unknown, generic X509Extension is used instead.
|
|
||||||
"""
|
|
||||||
if not isinstance(ext, rfc5280.Extension):
|
|
||||||
raise errors.X509Error("extension has incorrect type")
|
|
||||||
ext_class = EXTENSION_CLASSES.get(ext['extnID'], X509Extension)
|
|
||||||
return ext_class(ext)
|
|
@ -1,172 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from pyasn1.codec.der import decoder
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import error as asn1_error
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
|
|
||||||
OID_commonName = rfc5280.id_at_commonName
|
|
||||||
OID_localityName = rfc5280.id_at_localityName
|
|
||||||
OID_stateOrProvinceName = rfc5280.id_at_stateOrProvinceName
|
|
||||||
OID_organizationName = rfc5280.id_at_organizationName
|
|
||||||
OID_organizationalUnitName = rfc5280.id_at_organizationalUnitName
|
|
||||||
OID_countryName = rfc5280.id_at_countryName
|
|
||||||
OID_pkcs9_emailAddress = rfc5280.id_emailAddress
|
|
||||||
OID_surname = rfc5280.id_at_surname
|
|
||||||
OID_givenName = rfc5280.id_at_givenName
|
|
||||||
|
|
||||||
name_oids = {
|
|
||||||
rfc5280.id_at_name: rfc5280.X520name,
|
|
||||||
rfc5280.id_at_surname: rfc5280.X520name,
|
|
||||||
rfc5280.id_at_givenName: rfc5280.X520name,
|
|
||||||
rfc5280.id_at_initials: rfc5280.X520name,
|
|
||||||
rfc5280.id_at_generationQualifier: rfc5280.X520name,
|
|
||||||
rfc5280.id_at_commonName: rfc5280.X520CommonName,
|
|
||||||
rfc5280.id_at_localityName: rfc5280.X520LocalityName,
|
|
||||||
rfc5280.id_at_stateOrProvinceName: rfc5280.X520StateOrProvinceName,
|
|
||||||
rfc5280.id_at_organizationName: rfc5280.X520OrganizationName,
|
|
||||||
rfc5280.id_at_organizationalUnitName: rfc5280.X520OrganizationalUnitName,
|
|
||||||
rfc5280.id_at_title: rfc5280.X520Title,
|
|
||||||
rfc5280.id_at_dnQualifier: rfc5280.X520dnQualifier,
|
|
||||||
rfc5280.id_at_countryName: rfc5280.X520countryName,
|
|
||||||
rfc5280.id_emailAddress: rfc5280.EmailAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
code_names = {
|
|
||||||
rfc5280.id_at_commonName: "CN",
|
|
||||||
rfc5280.id_at_localityName: "L",
|
|
||||||
rfc5280.id_at_stateOrProvinceName: "ST",
|
|
||||||
rfc5280.id_at_organizationName: "O",
|
|
||||||
rfc5280.id_at_organizationalUnitName: "OU",
|
|
||||||
rfc5280.id_at_countryName: "C",
|
|
||||||
rfc5280.id_at_givenName: "GN",
|
|
||||||
rfc5280.id_at_surname: "SN",
|
|
||||||
rfc5280.id_emailAddress: "emailAddress",
|
|
||||||
}
|
|
||||||
|
|
||||||
short_names = {
|
|
||||||
rfc5280.id_at_commonName: "commonName",
|
|
||||||
rfc5280.id_at_localityName: "localityName",
|
|
||||||
rfc5280.id_at_stateOrProvinceName: "stateOrProvinceName",
|
|
||||||
rfc5280.id_at_organizationName: "organizationName",
|
|
||||||
rfc5280.id_at_organizationalUnitName: "organizationalUnitName",
|
|
||||||
rfc5280.id_at_countryName: "countryName",
|
|
||||||
rfc5280.id_at_givenName: "givenName",
|
|
||||||
rfc5280.id_at_surname: "surname",
|
|
||||||
rfc5280.id_emailAddress: "emailAddress",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class X509Name(object):
|
|
||||||
"""An X509 Name object."""
|
|
||||||
|
|
||||||
class Entry():
|
|
||||||
"""An X509 Name sub-entry object."""
|
|
||||||
def __init__(self, obj):
|
|
||||||
self._obj = obj
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s: %s" % (self.get_name(), self.get_value())
|
|
||||||
|
|
||||||
def get_oid(self):
|
|
||||||
return self._obj[0]['type']
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
"""Get the name of this entry.
|
|
||||||
|
|
||||||
:return: entry name as a python string
|
|
||||||
"""
|
|
||||||
oid = self.get_oid()
|
|
||||||
return short_names.get(oid, str(oid))
|
|
||||||
|
|
||||||
def get_code(self):
|
|
||||||
"""Get the name of this entry.
|
|
||||||
|
|
||||||
:return: entry name as a python string
|
|
||||||
"""
|
|
||||||
oid = self.get_oid()
|
|
||||||
return code_names.get(oid, str(oid))
|
|
||||||
|
|
||||||
def get_value(self):
|
|
||||||
"""Get the value of this entry.
|
|
||||||
|
|
||||||
:return: entry value as a python string
|
|
||||||
"""
|
|
||||||
value = self._obj[0]['value']
|
|
||||||
der = value.asOctets()
|
|
||||||
oid = self.get_oid()
|
|
||||||
if oid not in name_oids:
|
|
||||||
return 'UNKNOWN'
|
|
||||||
|
|
||||||
name_spec = name_oids[oid]()
|
|
||||||
|
|
||||||
value = decoder.decode(der, asn1Spec=name_spec)[0]
|
|
||||||
if hasattr(value, 'getComponent'):
|
|
||||||
value = value.getComponent()
|
|
||||||
return value.asOctets().decode(value.encoding)
|
|
||||||
|
|
||||||
def __init__(self, name_obj=None):
|
|
||||||
if name_obj is not None:
|
|
||||||
if not isinstance(name_obj, rfc5280.RDNSequence):
|
|
||||||
raise TypeError("name is not an RDNSequence")
|
|
||||||
self._name_obj = name_obj.clone(cloneValueFlag=True)
|
|
||||||
else:
|
|
||||||
self._name_obj = rfc5280.RDNSequence()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '/' + '/'.join("%s=%s" % (e.get_code(), e.get_value())
|
|
||||||
for e in self)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._name_obj)
|
|
||||||
|
|
||||||
def __getitem__(self, idx):
|
|
||||||
return X509Name.Entry(self._name_obj[idx])
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for i in range(len(self)):
|
|
||||||
yield self[i]
|
|
||||||
|
|
||||||
def add_name_entry(self, oid, text):
|
|
||||||
if not isinstance(oid, asn1_univ.ObjectIdentifier):
|
|
||||||
raise errors.X509Error("oid '%s' is not valid" % (oid,))
|
|
||||||
atv = rfc5280.AttributeTypeAndValue()
|
|
||||||
atv['type'] = oid
|
|
||||||
name_type = name_oids[oid]
|
|
||||||
try:
|
|
||||||
if name_type in (rfc5280.X520countryName, rfc5280.EmailAddress):
|
|
||||||
val = name_type(text)
|
|
||||||
else:
|
|
||||||
val = name_type()
|
|
||||||
val['utf8String'] = text
|
|
||||||
except asn1_error.ValueConstraintError:
|
|
||||||
raise errors.X509Error("Name '%s' is not valid" % text)
|
|
||||||
atv['value'] = rfc5280.AttributeValue(encoder.encode(val))
|
|
||||||
|
|
||||||
entry = rfc5280.RelativeDistinguishedName()
|
|
||||||
entry[0] = atv
|
|
||||||
self._name_obj[len(self)] = entry
|
|
||||||
|
|
||||||
def get_entries_by_oid(self, oid):
|
|
||||||
"""Get a name entry corresponding to an NID name.
|
|
||||||
|
|
||||||
:param nid: an NID for the new name entry
|
|
||||||
:return: An X509Name.Entry object
|
|
||||||
"""
|
|
||||||
return [entry for entry in self if entry.get_oid() == oid]
|
|
@ -1,175 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from cryptography import exceptions as cio_exceptions
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import dsa
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEPRECATED_ALGORITHM_NAMES = {
|
|
||||||
asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.2'): 'MD2 with RSA',
|
|
||||||
asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.3'): 'MD4 with RSA',
|
|
||||||
asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.4'): 'MD5 with RSA',
|
|
||||||
asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.5'): 'SHA1 with RSA',
|
|
||||||
asn1_univ.ObjectIdentifier('1.2.840.10040.4.3'): 'SHA1 with DSA',
|
|
||||||
}
|
|
||||||
|
|
||||||
# valid algorithms
|
|
||||||
sha224WithRSAEncryption = asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.14')
|
|
||||||
sha256WithRSAEncryption = asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.11')
|
|
||||||
sha384WithRSAEncryption = asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.12')
|
|
||||||
sha512WithRSAEncryption = asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.13')
|
|
||||||
id_dsa_with_sha224 = asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.1')
|
|
||||||
id_dsa_with_sha256 = asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.2')
|
|
||||||
|
|
||||||
SIGNING_ALGORITHMS = {
|
|
||||||
('RSA', 'SHA224'): sha224WithRSAEncryption,
|
|
||||||
('RSA', 'SHA256'): sha256WithRSAEncryption,
|
|
||||||
('RSA', 'SHA384'): sha384WithRSAEncryption,
|
|
||||||
('RSA', 'SHA512'): sha512WithRSAEncryption,
|
|
||||||
('DSA', 'SHA224'): id_dsa_with_sha224,
|
|
||||||
('DSA', 'SHA256'): id_dsa_with_sha256,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SIGNING_ALGORITHMS_INV = dict((v, k) for k, v in SIGNING_ALGORITHMS.items())
|
|
||||||
|
|
||||||
|
|
||||||
VERIFIER_CONSTRUCTION = {
|
|
||||||
sha224WithRSAEncryption: (lambda key, signature: key.verifier(
|
|
||||||
signature, padding.PKCS1v15(), hashes.SHA224())),
|
|
||||||
sha256WithRSAEncryption: (lambda key, signature: key.verifier(
|
|
||||||
signature, padding.PKCS1v15(), hashes.SHA256())),
|
|
||||||
sha384WithRSAEncryption: (lambda key, signature: key.verifier(
|
|
||||||
signature, padding.PKCS1v15(), hashes.SHA384())),
|
|
||||||
sha512WithRSAEncryption: (lambda key, signature: key.verifier(
|
|
||||||
signature, padding.PKCS1v15(), hashes.SHA512())),
|
|
||||||
id_dsa_with_sha224: (lambda key, signature: key.verifier(
|
|
||||||
signature, hashes.SHA224())),
|
|
||||||
id_dsa_with_sha256: (lambda key, signature: key.verifier(
|
|
||||||
signature, hashes.SHA256())),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ALGORITHM_PARAMETERS = {
|
|
||||||
sha224WithRSAEncryption: encoder.encode(asn1_univ.Null()),
|
|
||||||
sha256WithRSAEncryption: encoder.encode(asn1_univ.Null()),
|
|
||||||
sha384WithRSAEncryption: encoder.encode(asn1_univ.Null()),
|
|
||||||
sha512WithRSAEncryption: encoder.encode(asn1_univ.Null()),
|
|
||||||
id_dsa_with_sha224: None,
|
|
||||||
id_dsa_with_sha256: None,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureMixin(object):
|
|
||||||
"""Provides the sign() and verify() functions.
|
|
||||||
|
|
||||||
Both operations rely on the functions provided by the certificate and
|
|
||||||
csr classes.
|
|
||||||
"""
|
|
||||||
def sign(self, encryption, md, signer):
|
|
||||||
"""Sign the current object."""
|
|
||||||
md = md.upper()
|
|
||||||
|
|
||||||
signature_type = SIGNING_ALGORITHMS.get((encryption, md))
|
|
||||||
if signature_type is None:
|
|
||||||
raise errors.X509Error(
|
|
||||||
"Unknown encryption/hash combination %s/%s" % (encryption, md))
|
|
||||||
|
|
||||||
algo_id = rfc5280.AlgorithmIdentifier()
|
|
||||||
algo_id['algorithm'] = signature_type
|
|
||||||
algo_params = ALGORITHM_PARAMETERS[signature_type]
|
|
||||||
if algo_params is not None:
|
|
||||||
algo_id['parameters'] = algo_params
|
|
||||||
|
|
||||||
self._embed_signature_algorithm(algo_id)
|
|
||||||
to_sign = self._get_bytes_to_sign()
|
|
||||||
signature = signer(to_sign)
|
|
||||||
|
|
||||||
self._embed_signature(algo_id, signature)
|
|
||||||
|
|
||||||
def verify(self, key=None):
|
|
||||||
algo_id = self._get_signing_algorithm()
|
|
||||||
if algo_id not in SIGNING_ALGORITHMS_INV:
|
|
||||||
LOG.warning("Signature algorithm %s is unknown, cannot verify",
|
|
||||||
algo_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if key is None:
|
|
||||||
key = self._get_public_key()
|
|
||||||
|
|
||||||
encryption, hash_algo = SIGNING_ALGORITHMS_INV[algo_id]
|
|
||||||
to_sign = self._get_bytes_to_sign()
|
|
||||||
signature = self._get_signature()
|
|
||||||
if ((encryption == 'RSA' and not isinstance(key, rsa.RSAPublicKey)) or
|
|
||||||
(encryption == 'DSA' and not isinstance(key,
|
|
||||||
dsa.DSAPublicKey))):
|
|
||||||
raise errors.X509Error("Key type mismatch: object %s, key %s" %
|
|
||||||
(encryption, key.__class__))
|
|
||||||
verifier = VERIFIER_CONSTRUCTION[algo_id](key, signature)
|
|
||||||
|
|
||||||
verifier.update(to_sign)
|
|
||||||
try:
|
|
||||||
verifier.verify()
|
|
||||||
return True
|
|
||||||
except cio_exceptions.InvalidSignature:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def uses_deprecated_algorithm(self):
|
|
||||||
"""Check for deprecated algorithm in signatures.
|
|
||||||
|
|
||||||
Returns the name of the algorithm found, or None if everything's ok.
|
|
||||||
"""
|
|
||||||
name = DEPRECATED_ALGORITHM_NAMES.get(self._get_signing_algorithm())
|
|
||||||
return name
|
|
||||||
|
|
||||||
def _get_bytes_to_sign(self):
|
|
||||||
"""Get bytes which are giong to be hashed and signed."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _get_public_key(self):
|
|
||||||
"""Get public key for verifying CSR self-signatures."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _get_signature(self):
|
|
||||||
"""Get the current signature value as bytes."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _get_signing_algorithm(self):
|
|
||||||
"""Get the description of algorithm used to sign object."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _embed_signature_algorithm(self, algo_id):
|
|
||||||
"""Called before the signature is calculated.
|
|
||||||
|
|
||||||
Since signature of the certificate depends on the signature algorithm,
|
|
||||||
it needs to be saved first.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _embed_signature(self, algo_id, signature):
|
|
||||||
"""Called after the signature is calculated."""
|
|
||||||
raise NotImplementedError()
|
|
@ -1,244 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
import io
|
|
||||||
|
|
||||||
from pyasn1.codec.der import decoder
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.asn1 import rfc6402
|
|
||||||
from anchor import util
|
|
||||||
from anchor.X509 import errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name
|
|
||||||
from anchor.X509 import signature
|
|
||||||
from anchor.X509 import utils as x509_utils
|
|
||||||
|
|
||||||
|
|
||||||
OID_extensionRequest = asn1_univ.ObjectIdentifier('1.2.840.113549.1.9.14')
|
|
||||||
|
|
||||||
|
|
||||||
class X509CsrError(errors.X509Error):
|
|
||||||
def __init__(self, what):
|
|
||||||
super(X509CsrError, self).__init__(what)
|
|
||||||
|
|
||||||
|
|
||||||
class X509Csr(signature.SignatureMixin):
|
|
||||||
"""An X509 Certificate Signing Request."""
|
|
||||||
def __init__(self, csr=None):
|
|
||||||
if csr is None:
|
|
||||||
self._csr = rfc6402.CertificationRequest()
|
|
||||||
else:
|
|
||||||
self._csr = csr
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_open_file(f, encoding='pem'):
|
|
||||||
if encoding == 'pem':
|
|
||||||
try:
|
|
||||||
der_content = util.extract_pem(f.read())
|
|
||||||
except IOError:
|
|
||||||
raise X509CsrError("Could not read from source %s" % f)
|
|
||||||
except Exception:
|
|
||||||
raise X509CsrError(
|
|
||||||
"Data source not readable or not in PEM format")
|
|
||||||
|
|
||||||
if not der_content:
|
|
||||||
raise X509CsrError("No PEM data found")
|
|
||||||
elif encoding == 'der':
|
|
||||||
der_content = f.read()
|
|
||||||
else:
|
|
||||||
raise X509CsrError("Unknown encoding")
|
|
||||||
|
|
||||||
try:
|
|
||||||
csr = decoder.decode(der_content,
|
|
||||||
asn1Spec=rfc6402.CertificationRequest())[0]
|
|
||||||
return X509Csr(csr)
|
|
||||||
except Exception:
|
|
||||||
raise X509CsrError("Could not read X509 certificate from data.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_buffer(data, encoding='pem'):
|
|
||||||
"""Create this CSR from a buffer
|
|
||||||
|
|
||||||
:param data: The data buffer
|
|
||||||
"""
|
|
||||||
return X509Csr.from_open_file(io.BytesIO(data), encoding)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_file(path, encoding='pem'):
|
|
||||||
"""Create this CSR from a file on disk
|
|
||||||
|
|
||||||
:param path: Path to the file on disk
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
return X509Csr.from_open_file(f, encoding)
|
|
||||||
except IOError:
|
|
||||||
raise X509CsrError("Could not read file %s" % path)
|
|
||||||
|
|
||||||
def get_pubkey(self):
|
|
||||||
"""Get the public key from the CSR
|
|
||||||
|
|
||||||
:return: ASN.1 description of public key
|
|
||||||
"""
|
|
||||||
return self._csr['certificationRequestInfo']['subjectPublicKeyInfo']
|
|
||||||
|
|
||||||
def get_request_info(self):
|
|
||||||
if self._csr['certificationRequestInfo'] is None:
|
|
||||||
self._csr['certificationRequestInfo'] = None
|
|
||||||
return self._csr['certificationRequestInfo']
|
|
||||||
|
|
||||||
def get_subject(self):
|
|
||||||
"""Get the subject name field from the CSR
|
|
||||||
|
|
||||||
:return: an X509Name object
|
|
||||||
"""
|
|
||||||
ri = self.get_request_info()
|
|
||||||
if ri['subject'] is None:
|
|
||||||
ri['subject'] = None
|
|
||||||
# setup first RDN sequence
|
|
||||||
ri['subject'][0] = None
|
|
||||||
|
|
||||||
subject = ri['subject'][0]
|
|
||||||
return name.X509Name(subject)
|
|
||||||
|
|
||||||
def set_subject(self, subject):
|
|
||||||
if not isinstance(subject, name.X509Name):
|
|
||||||
raise TypeError("subject must be an X509Name")
|
|
||||||
ri = self.get_request_info()
|
|
||||||
if ri['subject'] is None:
|
|
||||||
ri['subject'] = None
|
|
||||||
|
|
||||||
ri['subject'][0] = subject._name_obj
|
|
||||||
|
|
||||||
def get_attributes(self):
|
|
||||||
ri = self.get_request_info()
|
|
||||||
if ri['attributes'] is None:
|
|
||||||
ri['attributes'] = None
|
|
||||||
return ri['attributes']
|
|
||||||
|
|
||||||
def get_subject_cn(self):
|
|
||||||
"""Get the CN part of subject.
|
|
||||||
|
|
||||||
:return subject's CN
|
|
||||||
"""
|
|
||||||
subject = self.get_subject()
|
|
||||||
cns = subject.get_entries_by_oid(name.OID_commonName)
|
|
||||||
return [cn.get_value() for cn in cns]
|
|
||||||
|
|
||||||
def get_extensions(self, ext_type=None):
|
|
||||||
"""Get the list of all X509 V3 Extensions on this CSR
|
|
||||||
|
|
||||||
:return: a list of X509Extension objects
|
|
||||||
"""
|
|
||||||
ext_attrs = [a for a in self.get_attributes()
|
|
||||||
if a['attrType'] == OID_extensionRequest]
|
|
||||||
if len(ext_attrs) == 0:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
exts_der = ext_attrs[0]['attrValues'][0].asOctets()
|
|
||||||
exts = decoder.decode(exts_der, asn1Spec=rfc5280.Extensions())[0]
|
|
||||||
return [extension.construct_extension(e) for e in exts
|
|
||||||
if ext_type is None or e['extnID'] == ext_type._oid]
|
|
||||||
|
|
||||||
def add_extension(self, new_ext):
|
|
||||||
"""Add a new extension or replace existing one."""
|
|
||||||
if not isinstance(new_ext, extension.X509Extension):
|
|
||||||
raise errors.X509Error("ext is not an anchor X509Extension")
|
|
||||||
attributes = self.get_attributes()
|
|
||||||
ext_attrs = [a for a in attributes
|
|
||||||
if a['attrType'] == OID_extensionRequest]
|
|
||||||
if not ext_attrs:
|
|
||||||
new_attr_index = len(attributes)
|
|
||||||
attributes[new_attr_index] = None
|
|
||||||
ext_attr = attributes[new_attr_index]
|
|
||||||
ext_attr['attrType'] = OID_extensionRequest
|
|
||||||
ext_attr['attrValues'] = None
|
|
||||||
exts = rfc5280.Extensions()
|
|
||||||
else:
|
|
||||||
ext_attr = ext_attrs[0]
|
|
||||||
exts = decoder.decode(ext_attr['attrValues'][0].asOctets(),
|
|
||||||
asn1Spec=rfc5280.Extensions())[0]
|
|
||||||
|
|
||||||
# the end is the default position
|
|
||||||
new_ext_index = len(exts)
|
|
||||||
# unless there's an existing extension with the same OID
|
|
||||||
for i, ext_i in enumerate(exts):
|
|
||||||
if ext_i['extnID'] == new_ext.get_oid():
|
|
||||||
new_ext_index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
exts[new_ext_index] = new_ext._ext
|
|
||||||
|
|
||||||
ext_attr['attrValues'][0] = encoder.encode(exts)
|
|
||||||
|
|
||||||
def get_subject_dns_ids(self):
|
|
||||||
names = []
|
|
||||||
for ext in self.get_extensions(extension.X509ExtensionSubjectAltName):
|
|
||||||
for dns_id in ext.get_dns_ids():
|
|
||||||
names.append(dns_id)
|
|
||||||
return names
|
|
||||||
|
|
||||||
def get_subject_ip_ids(self):
|
|
||||||
names = []
|
|
||||||
for ext in self.get_extensions(extension.X509ExtensionSubjectAltName):
|
|
||||||
for ip in ext.get_ips():
|
|
||||||
names.append(ip)
|
|
||||||
return names
|
|
||||||
|
|
||||||
def has_unknown_san_entries(self):
|
|
||||||
for ext in self.get_extensions(extension.X509ExtensionSubjectAltName):
|
|
||||||
if ext.has_unknown_entries():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_public_key_algo(self):
|
|
||||||
csr_info = self._csr['certificationRequestInfo']
|
|
||||||
key_info = csr_info['subjectPublicKeyInfo']
|
|
||||||
return key_info['algorithm']['algorithm']
|
|
||||||
|
|
||||||
def get_public_key_size(self):
|
|
||||||
return self._get_public_key().key_size
|
|
||||||
|
|
||||||
def get_public_key(self):
|
|
||||||
return self._get_public_key()
|
|
||||||
|
|
||||||
def get_signing_algorithm(self):
|
|
||||||
return self._get_signing_algorithm()
|
|
||||||
|
|
||||||
def _get_signature(self):
|
|
||||||
return x509_utils.bin_to_bytes(self._csr['signature'])
|
|
||||||
|
|
||||||
def _get_signing_algorithm(self):
|
|
||||||
return self._csr['signatureAlgorithm']['algorithm']
|
|
||||||
|
|
||||||
def _get_public_key(self):
|
|
||||||
csr_info = self._csr['certificationRequestInfo']
|
|
||||||
key_info = csr_info['subjectPublicKeyInfo']
|
|
||||||
return x509_utils.get_public_key_from_der(encoder.encode(key_info))
|
|
||||||
|
|
||||||
def _get_bytes_to_sign(self):
|
|
||||||
return encoder.encode(self._csr['certificationRequestInfo'])
|
|
||||||
|
|
||||||
def _embed_signature_algorithm(self, algo_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _embed_signature(self, algo_id, signature):
|
|
||||||
self._csr['signatureAlgorithm'] = algo_id
|
|
||||||
self._csr['signature'] = "'%s'H" % (
|
|
||||||
str(binascii.hexlify(signature).decode('ascii')),)
|
|
@ -1,180 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import calendar
|
|
||||||
import datetime
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from cryptography.hazmat import backends
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from cryptography.hazmat.primitives import serialization
|
|
||||||
import netaddr
|
|
||||||
from pyasn1.type import useful as asn1_useful
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
|
|
||||||
|
|
||||||
def create_timezone(minute_offset):
|
|
||||||
"""Create a new timezone with a specified offset.
|
|
||||||
|
|
||||||
Since tzinfo is just a base class, and tzinfo subclasses need a
|
|
||||||
no-arguments __init__(), we need to generate a new class dynamically.
|
|
||||||
|
|
||||||
:param minute_offset: total timezone offset in minutes
|
|
||||||
"""
|
|
||||||
|
|
||||||
class SpecificTZ(datetime.tzinfo):
|
|
||||||
def utcoffset(self, _dt):
|
|
||||||
return datetime.timedelta(minutes=minute_offset)
|
|
||||||
|
|
||||||
def dst(self, _dt):
|
|
||||||
return datetime.timedelta(0)
|
|
||||||
|
|
||||||
def tzname(self, _dt):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
sign = "+" if minute_offset > 0 else "-"
|
|
||||||
hh = minute_offset / 60
|
|
||||||
mm = minute_offset % 60
|
|
||||||
return "Timezone %s%02i%02i" % (sign, hh, mm)
|
|
||||||
|
|
||||||
return SpecificTZ()
|
|
||||||
|
|
||||||
|
|
||||||
def asn1_time_to_timestamp(t):
|
|
||||||
"""Convert from ASN1_TIME type to a UTC-based timestamp.
|
|
||||||
|
|
||||||
:param t: ASN1_TIME to convert
|
|
||||||
"""
|
|
||||||
component = t.getComponent()
|
|
||||||
timestring = component.asOctets().decode(component.encoding)
|
|
||||||
if isinstance(component, asn1_useful.UTCTime):
|
|
||||||
if int(timestring[0]) >= 5:
|
|
||||||
timestring = "19" + timestring
|
|
||||||
else:
|
|
||||||
timestring = "20" + timestring
|
|
||||||
return asn1_timestring_to_timestamp(timestring)
|
|
||||||
|
|
||||||
|
|
||||||
def asn1_timestring_to_timestamp(timestring):
|
|
||||||
"""Convert from ASN1_GENERALIZEDTIME to UTC-based timestamp.
|
|
||||||
|
|
||||||
:param gt: ASN1_GENERALIZEDTIME to convert
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ASN1_GENERALIZEDTIME is actually a string in known formats,
|
|
||||||
# so the conversion can be done in this code
|
|
||||||
before_tz = timestring[:14]
|
|
||||||
tz_str = timestring[14:]
|
|
||||||
d = datetime.datetime.strptime(before_tz, "%Y%m%d%H%M%S")
|
|
||||||
if tz_str == 'Z':
|
|
||||||
# YYYYMMDDhhmmssZ
|
|
||||||
d.replace(tzinfo=create_timezone(0))
|
|
||||||
else:
|
|
||||||
# YYYYMMDDhhmmss+hhmm
|
|
||||||
# YYYYMMDDhhmmss-hhmm
|
|
||||||
sign = -1 if tz_str[0] == '-' else 1
|
|
||||||
hh = tz_str[1:3]
|
|
||||||
mm = tz_str[3:5]
|
|
||||||
minute_offset = sign * (int(mm) + int(hh) * 60)
|
|
||||||
d.replace(tzinfo=create_timezone(minute_offset))
|
|
||||||
return calendar.timegm(d.timetuple())
|
|
||||||
|
|
||||||
|
|
||||||
def timestamp_to_asn1_time(t):
|
|
||||||
"""Convert from UTC-based timestamp to ASN1_TIME
|
|
||||||
|
|
||||||
:param t: time in seconds since the epoch
|
|
||||||
"""
|
|
||||||
|
|
||||||
d = datetime.datetime.utcfromtimestamp(t)
|
|
||||||
asn1time = rfc5280.Time()
|
|
||||||
if d.year <= 2049:
|
|
||||||
time_str = d.strftime("%y%m%d%H%M%SZ").encode('ascii')
|
|
||||||
asn1time['utcTime'] = time_str
|
|
||||||
else:
|
|
||||||
time_str = d.strftime("%Y%m%d%H%M%SZ").encode('ascii')
|
|
||||||
asn1time['generalTime'] = time_str
|
|
||||||
return asn1time
|
|
||||||
|
|
||||||
|
|
||||||
# chr good for py2 and py3
|
|
||||||
_chr = chr if str is bytes else lambda x: bytes([x])
|
|
||||||
|
|
||||||
|
|
||||||
# functions needed for converting the pyasn1 signature fields
|
|
||||||
def bin_to_bytes(bits):
|
|
||||||
"""Convert bit string to byte string."""
|
|
||||||
bits = ''.join(str(b) for b in bits)
|
|
||||||
bits = _pad_byte(bits)
|
|
||||||
octets = [bits[8*i:8*(i+1)] for i in range(len(bits)//8)]
|
|
||||||
byte_list = [_chr(int(x, 2)) for x in octets]
|
|
||||||
return b"".join(byte_list)
|
|
||||||
|
|
||||||
|
|
||||||
# ord good for py2 and py3
|
|
||||||
_ord = ord if str is bytes else lambda x: x
|
|
||||||
|
|
||||||
|
|
||||||
def _pad_byte(bits):
|
|
||||||
"""Pad a string of bits with zeros to make its length a multiple of 8."""
|
|
||||||
r = len(bits) % 8
|
|
||||||
return ((8-r) % 8)*'0' + bits
|
|
||||||
|
|
||||||
|
|
||||||
def get_hash_class(md):
|
|
||||||
return getattr(hashes, md.upper(), None)
|
|
||||||
|
|
||||||
|
|
||||||
def get_private_key_from_pem(data):
|
|
||||||
return serialization.load_pem_private_key(
|
|
||||||
data, None, backend=backends.default_backend())
|
|
||||||
|
|
||||||
|
|
||||||
def get_public_key_from_der(data):
|
|
||||||
return serialization.load_der_public_key(
|
|
||||||
data, backend=backends.default_backend())
|
|
||||||
|
|
||||||
|
|
||||||
def get_private_key_from_file(path):
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
return get_private_key_from_pem(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
def asn1_to_netaddr(octet_string):
|
|
||||||
"""Translate the ASN1 IP format to netaddr object."""
|
|
||||||
if not isinstance(octet_string, rfc5280.univ.OctetString):
|
|
||||||
raise TypeError("not an OctetString")
|
|
||||||
|
|
||||||
ip_bytes = octet_string.asOctets()
|
|
||||||
if len(ip_bytes) == 4:
|
|
||||||
ip_num = struct.unpack(">I", ip_bytes)[0]
|
|
||||||
return netaddr.IPAddress(ip_num, 4)
|
|
||||||
elif len(ip_bytes) == 16:
|
|
||||||
ip_num_front, ip_num_back = struct.unpack(">QQ", ip_bytes)
|
|
||||||
ip_num = ip_num_front << 64 | ip_num_back
|
|
||||||
return netaddr.IPAddress(ip_num, 6)
|
|
||||||
else:
|
|
||||||
raise TypeError("ip address is neither v4 nor v6")
|
|
||||||
|
|
||||||
|
|
||||||
def netaddr_to_asn1(ip):
|
|
||||||
"""Translate the netaddr object to ASN1 IP format."""
|
|
||||||
if not isinstance(ip, netaddr.IPAddress):
|
|
||||||
raise errors.X509Error("not a real ip address provided")
|
|
||||||
|
|
||||||
return bytes(ip.packed)
|
|
237
anchor/app.py
237
anchor/app.py
@ -1,237 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import paste
|
|
||||||
from paste import translogger # noqa
|
|
||||||
import pecan
|
|
||||||
|
|
||||||
from anchor import audit
|
|
||||||
from anchor import errors
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def config_check_domains(validator_set):
|
|
||||||
for name, step in validator_set.items():
|
|
||||||
if 'allowed_domains' in step:
|
|
||||||
for domain in step['allowed_domains']:
|
|
||||||
if not domain.startswith('.'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Domain that does not start with "
|
|
||||||
"a '.' <{}>".format(domain))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_config(conf):
|
|
||||||
for old_name in ['auth', 'ca', 'validators']:
|
|
||||||
if old_name in conf.config:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"The config seems to be for an old version of Anchor. Please "
|
|
||||||
"check documentation.")
|
|
||||||
|
|
||||||
if not conf.config.get('registration_authority'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No registration authorities present")
|
|
||||||
|
|
||||||
if not conf.config.get('signing_ca'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No signing CA configurations present")
|
|
||||||
|
|
||||||
if not conf.config.get('authentication'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No authentication methods present")
|
|
||||||
|
|
||||||
for name in conf.registration_authority.keys():
|
|
||||||
logger.info("Checking config for registration authority: %s", name)
|
|
||||||
validate_registration_authority_config(name, conf)
|
|
||||||
|
|
||||||
for name in conf.signing_ca.keys():
|
|
||||||
logger.info("Checking config for signing ca: %s", name)
|
|
||||||
validate_signing_ca_config(name, conf)
|
|
||||||
|
|
||||||
for name in conf.authentication.keys():
|
|
||||||
logger.info("Checking config for authentication method: %s", name)
|
|
||||||
validate_authentication_config(name, conf)
|
|
||||||
|
|
||||||
validate_audit_config(conf)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_audit_config(conf):
|
|
||||||
valid_targets = ('messaging', 'log')
|
|
||||||
|
|
||||||
if not conf.config.get('audit'):
|
|
||||||
# no audit configuration - that's ok
|
|
||||||
return
|
|
||||||
|
|
||||||
audit_conf = conf.audit
|
|
||||||
if audit_conf.get('target', 'log') not in valid_targets:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Audit target not known (expected one of %s)" % (
|
|
||||||
", ".join(valid_targets),))
|
|
||||||
|
|
||||||
if audit_conf.get('target') == 'messaging':
|
|
||||||
if audit_conf.get('url') is None:
|
|
||||||
raise errors.ConfigValidationException("Audit url required")
|
|
||||||
|
|
||||||
|
|
||||||
def validate_authentication_config(name, conf):
|
|
||||||
auth_conf = conf.authentication[name]
|
|
||||||
|
|
||||||
default_user = "myusername"
|
|
||||||
default_secret = "simplepassword"
|
|
||||||
|
|
||||||
if not auth_conf.get('backend'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Authentication method %s doesn't define backend" % name)
|
|
||||||
|
|
||||||
if auth_conf['backend'] not in ('static', 'keystone', 'ldap'):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Authentication backend % unknown" % (auth_conf['backend'],))
|
|
||||||
|
|
||||||
# Check for anchor being run with default user/secret
|
|
||||||
if auth_conf['backend'] == 'static':
|
|
||||||
if auth_conf['user'] == default_user:
|
|
||||||
logger.warning("default user for static auth in use")
|
|
||||||
if auth_conf['secret'] == default_secret:
|
|
||||||
logger.warning("default secret for static auth in use")
|
|
||||||
|
|
||||||
|
|
||||||
def validate_signing_ca_config(name, conf):
|
|
||||||
ca_conf = conf.signing_ca[name]
|
|
||||||
backend_name = ca_conf.get('backend')
|
|
||||||
if not backend_name:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Backend type not defined for RA '%s'" % name)
|
|
||||||
sign_func = jsonloader.conf.get_signing_backend(backend_name)
|
|
||||||
if not sign_func:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Backend '%s' could not be found" % backend_name)
|
|
||||||
|
|
||||||
if hasattr(sign_func, "_config_validator"):
|
|
||||||
sign_func._config_validator(name, ca_conf)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_registration_authority_config(ra_name, conf):
|
|
||||||
ra_conf = conf.registration_authority[ra_name]
|
|
||||||
auth_name = ra_conf.get('authentication')
|
|
||||||
if not auth_name:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No authentication configured for registration authority: %s" %
|
|
||||||
ra_name)
|
|
||||||
|
|
||||||
if not conf.authentication.get(auth_name):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Authentication method %s configured for registration authority "
|
|
||||||
"%s doesn't exist" % (auth_name, ra_name))
|
|
||||||
|
|
||||||
ca_name = ra_conf.get('signing_ca')
|
|
||||||
if not ca_name:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No signing CA configuration present for registration authority: "
|
|
||||||
"%s" % ra_name)
|
|
||||||
|
|
||||||
if not conf.signing_ca.get(ca_name):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Signing CA %s configured for registration authority %s doesn't "
|
|
||||||
"exist" % (ca_name, ra_name))
|
|
||||||
|
|
||||||
if not ra_conf.get("validators"):
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"No validators configured for registration authority: %s" %
|
|
||||||
ra_name)
|
|
||||||
|
|
||||||
ra_validators = ra_conf['validators']
|
|
||||||
|
|
||||||
for step in ra_validators.keys():
|
|
||||||
try:
|
|
||||||
jsonloader.conf.get_validator(step)
|
|
||||||
except KeyError:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Unknown validator <{}> found (for registration "
|
|
||||||
"authority {})".format(step, ra_name))
|
|
||||||
|
|
||||||
config_check_domains(ra_validators)
|
|
||||||
logger.info("Validators OK for registration authority: %s", ra_name)
|
|
||||||
|
|
||||||
ra_fixups = ra_conf.get('fixups', {})
|
|
||||||
|
|
||||||
for step in ra_fixups.keys():
|
|
||||||
try:
|
|
||||||
jsonloader.conf.get_fixup(step)
|
|
||||||
except KeyError:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Unknown fixup <{}> found (for registration "
|
|
||||||
"authority {})".format(step, ra_name))
|
|
||||||
|
|
||||||
logger.info("Fixups OK for registration authority: %s", ra_name)
|
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
"""Attempt to find and load a JSON configuration file.
|
|
||||||
|
|
||||||
We will search in various locations in order for a valid config file
|
|
||||||
to use:
|
|
||||||
|
|
||||||
- the contents of 'ANCHOR_CONF' environment variable
|
|
||||||
- a local 'config.json' file in the invocation folder
|
|
||||||
- a HOME/.config/anchor/config.json file
|
|
||||||
- a /etc/anchor/config.json file
|
|
||||||
"""
|
|
||||||
config_name = 'ANCHOR_CONF'
|
|
||||||
local_config_path = 'config.json'
|
|
||||||
user_config_path = os.path.join(
|
|
||||||
os.environ['HOME'], '.config', 'anchor', 'config.json')
|
|
||||||
|
|
||||||
prefix = os.environ.get('VIRTUAL_ENV', os.sep)
|
|
||||||
sys_config_path = os.path.join(prefix, 'etc', 'anchor', 'config.json')
|
|
||||||
|
|
||||||
if 'registration_authority' not in jsonloader.conf.config:
|
|
||||||
config_path = ""
|
|
||||||
if config_name in os.environ:
|
|
||||||
config_path = os.environ[config_name]
|
|
||||||
elif os.path.isfile(local_config_path):
|
|
||||||
config_path = local_config_path
|
|
||||||
elif os.path.isfile(user_config_path):
|
|
||||||
config_path = user_config_path
|
|
||||||
elif os.path.isfile(sys_config_path):
|
|
||||||
config_path = sys_config_path
|
|
||||||
logger = logging.getLogger("anchor")
|
|
||||||
logger.info("using config: {}".format(config_path))
|
|
||||||
jsonloader.conf.load_file_data(config_path)
|
|
||||||
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_app(config):
|
|
||||||
# initial logging, will be re-configured later
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
|
||||||
app_conf = dict(config.app)
|
|
||||||
|
|
||||||
load_config()
|
|
||||||
validate_config(jsonloader.conf)
|
|
||||||
|
|
||||||
audit.init_audit()
|
|
||||||
|
|
||||||
app = pecan.make_app(
|
|
||||||
app_conf.pop('root'),
|
|
||||||
logging=config.logging,
|
|
||||||
**app_conf
|
|
||||||
)
|
|
||||||
|
|
||||||
return paste.translogger.TransLogger(app, setup_console_handler=False)
|
|
File diff suppressed because it is too large
Load Diff
@ -1,344 +0,0 @@
|
|||||||
# Auto-generated by asn1ate on 2015-12-17 15:14:22.594350
|
|
||||||
from pyasn1.type import univ
|
|
||||||
from pyasn1.type import char
|
|
||||||
from pyasn1.type import namedtype
|
|
||||||
from pyasn1.type import namedval
|
|
||||||
from pyasn1.type import tag
|
|
||||||
from pyasn1.type import constraint
|
|
||||||
from pyasn1.type import useful
|
|
||||||
|
|
||||||
from . import rfc3280
|
|
||||||
|
|
||||||
MAX=64
|
|
||||||
|
|
||||||
def _OID(*components):
|
|
||||||
output = []
|
|
||||||
for x in tuple(components):
|
|
||||||
if isinstance(x, univ.ObjectIdentifier):
|
|
||||||
output.extend(list(x))
|
|
||||||
else:
|
|
||||||
output.append(int(x))
|
|
||||||
|
|
||||||
return univ.ObjectIdentifier(output)
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectDigestInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ObjectDigestInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('digestedObjectType', univ.Enumerated(namedValues=namedval.NamedValues(('publicKey', 0), ('publicKeyCert', 1), ('otherObjectTypes', 2)))),
|
|
||||||
namedtype.OptionalNamedType('otherObjectTypeID', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('digestAlgorithm', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('objectDigest', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IssuerSerial(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
IssuerSerial.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuer', rfc3280.GeneralNames()),
|
|
||||||
namedtype.NamedType('serial', rfc3280.CertificateSerialNumber()),
|
|
||||||
namedtype.OptionalNamedType('issuerUID', rfc3280.UniqueIdentifier())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TargetCert(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TargetCert.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('targetCertificate', IssuerSerial()),
|
|
||||||
namedtype.OptionalNamedType('targetName', rfc3280.GeneralName()),
|
|
||||||
namedtype.OptionalNamedType('certDigestInfo', ObjectDigestInfo())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Target(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Target.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('targetName', rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('targetGroup', rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('targetCert', TargetCert().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Targets(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Targets.componentType = Target()
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyInfo(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ProxyInfo.componentType = Targets()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
id_at_role = _OID(rfc3280.id_at, 72)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_aaControls = _OID(rfc3280.id_pe, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_at_role = _OID(rfc3280.id_at, 72)
|
|
||||||
|
|
||||||
|
|
||||||
id_ce_targetInformation = _OID(rfc3280.id_ce, 55)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_ac_auditIdentity = _OID(rfc3280.id_pe, 4)
|
|
||||||
|
|
||||||
|
|
||||||
class ClassList(univ.BitString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ClassList.namedValues = namedval.NamedValues(
|
|
||||||
('unmarked', 0),
|
|
||||||
('unclassified', 1),
|
|
||||||
('restricted', 2),
|
|
||||||
('confidential', 3),
|
|
||||||
('secret', 4),
|
|
||||||
('topSecret', 5)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityCategory(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SecurityCategory.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('type', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('value', univ.Any().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Clearance(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Clearance.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('policyId', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.DefaultedNamedType('classList',
|
|
||||||
ClassList().subtype(implicitTag=tag.Tag(tag.tagClassContext,
|
|
||||||
tag.tagFormatSimple, 1)).subtype(value="unclassified")),
|
|
||||||
namedtype.OptionalNamedType('securityCategories', univ.SetOf(componentType=SecurityCategory()).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttCertVersion(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttCertVersion.namedValues = namedval.NamedValues(
|
|
||||||
('v2', 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca = _OID(rfc3280.id_pkix, 10)
|
|
||||||
|
|
||||||
|
|
||||||
id_at_clearance = _OID(2, 5, 1, 5, 55)
|
|
||||||
|
|
||||||
|
|
||||||
class AttrSpec(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttrSpec.componentType = univ.ObjectIdentifier()
|
|
||||||
|
|
||||||
|
|
||||||
class AAControls(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AAControls.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('pathLenConstraint', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(0, MAX))),
|
|
||||||
namedtype.OptionalNamedType('permittedAttrs', AttrSpec().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('excludedAttrs', AttrSpec().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.DefaultedNamedType('permitUnSpecified', univ.Boolean().subtype(value=1))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca = _OID(rfc3280.id_pkix, 10)
|
|
||||||
|
|
||||||
|
|
||||||
class AttCertValidityPeriod(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttCertValidityPeriod.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('notBeforeTime', useful.GeneralizedTime()),
|
|
||||||
namedtype.NamedType('notAfterTime', useful.GeneralizedTime())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_ac_auditIdentity = _OID(rfc3280.id_pe, 4)
|
|
||||||
|
|
||||||
|
|
||||||
id_at_clearance = _OID(2, 5, 1, 5, 55)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_authenticationInfo = _OID(id_aca, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class V2Form(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
V2Form.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('issuerName', rfc3280.GeneralNames()),
|
|
||||||
namedtype.OptionalNamedType('baseCertificateID', IssuerSerial().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.OptionalNamedType('objectDigestInfo', ObjectDigestInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttCertIssuer(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttCertIssuer.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('v1Form', rfc3280.GeneralNames()),
|
|
||||||
namedtype.NamedType('v2Form', V2Form().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Holder(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Holder.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('baseCertificateID', IssuerSerial().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.OptionalNamedType('entityName', rfc3280.GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.OptionalNamedType('objectDigestInfo', ObjectDigestInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', AttCertVersion()),
|
|
||||||
namedtype.NamedType('holder', Holder()),
|
|
||||||
namedtype.NamedType('issuer', AttCertIssuer()),
|
|
||||||
namedtype.NamedType('signature', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber()),
|
|
||||||
namedtype.NamedType('attrCertValidityPeriod', AttCertValidityPeriod()),
|
|
||||||
namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc3280.Attribute())),
|
|
||||||
namedtype.OptionalNamedType('issuerUniqueID', rfc3280.UniqueIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('extensions', rfc3280.Extensions())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificate(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('acinfo', AttributeCertificateInfo()),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signatureValue', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_authenticationInfo = _OID(id_aca, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_mod = _OID(rfc3280.id_pkix, 0)
|
|
||||||
|
|
||||||
|
|
||||||
id_mod_attribute_cert = _OID(id_mod, 12)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_accessIdentity = _OID(id_aca, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_accessIdentity = _OID(id_aca, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class RoleSyntax(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RoleSyntax.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('roleAuthority', rfc3280.GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('roleName', rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_chargingIdentity = _OID(id_aca, 3)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_chargingIdentity = _OID(id_aca, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class ACClearAttrs(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ACClearAttrs.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('acIssuer', rfc3280.GeneralName()),
|
|
||||||
namedtype.NamedType('acSerial', univ.Integer()),
|
|
||||||
namedtype.NamedType('attrs', univ.SequenceOf(componentType=rfc3280.Attribute()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_ce_targetInformation = _OID(rfc3280.id_ce, 55)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_group = _OID(id_aca, 4)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_group = _OID(id_aca, 4)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_ac_proxying = _OID(rfc3280.id_pe, 10)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_aaControls = _OID(rfc3280.id_pe, 6)
|
|
||||||
|
|
||||||
|
|
||||||
class SvceAuthInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SvceAuthInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('service', rfc3280.GeneralName()),
|
|
||||||
namedtype.NamedType('ident', rfc3280.GeneralName()),
|
|
||||||
namedtype.OptionalNamedType('authInfo', univ.OctetString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IetfAttrSyntax(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
IetfAttrSyntax.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('policyAuthority', rfc3280.GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('values', univ.SequenceOf(componentType=univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('octets', univ.OctetString()),
|
|
||||||
namedtype.NamedType('oid', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('string', char.UTF8String())
|
|
||||||
))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_encAttrs = _OID(id_aca, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_aca_encAttrs = _OID(id_aca, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_pe_ac_proxying = _OID(rfc3280.id_pe, 10)
|
|
||||||
|
|
||||||
|
|
@ -1,663 +0,0 @@
|
|||||||
# Auto-generated by asn1ate on 2015-12-18 17:39:54.470347
|
|
||||||
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
|
|
||||||
|
|
||||||
MAX = 64
|
|
||||||
|
|
||||||
from . import rfc3280
|
|
||||||
from . import rfc3281
|
|
||||||
|
|
||||||
def _OID(*components):
|
|
||||||
output = []
|
|
||||||
for x in tuple(components):
|
|
||||||
if isinstance(x, univ.ObjectIdentifier):
|
|
||||||
output.extend(list(x))
|
|
||||||
else:
|
|
||||||
output.append(int(x))
|
|
||||||
|
|
||||||
return univ.ObjectIdentifier(output)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeValue(univ.Any):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Attribute(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Attribute.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('attrType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignedAttributes.componentType = Attribute()
|
|
||||||
SignedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherRevocationInfoFormat(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherRevocationInfoFormat.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('otherRevInfoFormat', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('otherRevInfo', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RevocationInfoChoice(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RevocationInfoChoice.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('crl', rfc3280.CertificateList()),
|
|
||||||
namedtype.NamedType('other', OtherRevocationInfoFormat().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RevocationInfoChoices(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RevocationInfoChoices.componentType = RevocationInfoChoice()
|
|
||||||
|
|
||||||
|
|
||||||
class OtherKeyAttribute(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherKeyAttribute.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('keyAttrId', univ.ObjectIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('keyAttr', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_signedData = _OID(1, 2, 840, 113549, 1, 7, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyEncryptionAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedKey(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CMSVersion(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMSVersion.namedValues = namedval.NamedValues(
|
|
||||||
('v0', 0),
|
|
||||||
('v1', 1),
|
|
||||||
('v2', 2),
|
|
||||||
('v3', 3),
|
|
||||||
('v4', 4),
|
|
||||||
('v5', 5)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KEKIdentifier(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KEKIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('keyIdentifier', univ.OctetString()),
|
|
||||||
namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('other', OtherKeyAttribute())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KEKRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KEKRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('kekid', KEKIdentifier()),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyDerivationAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PasswordRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('keyDerivationAlgorithm', KeyDerivationAlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('oriType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('oriValue', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IssuerAndSerialNumber(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
IssuerAndSerialNumber.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuer', rfc3280.Name()),
|
|
||||||
namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SubjectKeyIdentifier(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientKeyIdentifier(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientKeyIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('other', OtherKeyAttribute())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyAgreeRecipientIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyAgreeRecipientIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('rKeyId', RecipientKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientEncryptedKey(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientEncryptedKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('rid', KeyAgreeRecipientIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientEncryptedKeys(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientEncryptedKeys.componentType = RecipientEncryptedKey()
|
|
||||||
|
|
||||||
|
|
||||||
class UserKeyingMaterial(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorPublicKey(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorPublicKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('algorithm', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('publicKey', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorIdentifierOrKey(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorIdentifierOrKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('originatorKey', OriginatorPublicKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyAgreeRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyAgreeRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('originator', OriginatorIdentifierOrKey().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.OptionalNamedType('ukm', UserKeyingMaterial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('recipientEncryptedKeys', RecipientEncryptedKeys())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyTransRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyTransRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('rid', RecipientIdentifier()),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientInfo(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('ktri', KeyTransRecipientInfo()),
|
|
||||||
namedtype.NamedType('kari', KeyAgreeRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
|
||||||
namedtype.NamedType('kekri', KEKRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
|
||||||
namedtype.NamedType('pwri', PasswordRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
|
||||||
namedtype.NamedType('ori', OtherRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientInfos(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientInfos.componentType = RecipientInfo()
|
|
||||||
RecipientInfos.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class DigestAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Signature(univ.BitString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SignerIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnprotectedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnprotectedAttributes.componentType = Attribute()
|
|
||||||
UnprotectedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class ContentType(univ.ObjectIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedContent(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ContentEncryptionAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('contentType', ContentType()),
|
|
||||||
namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('encryptedContent', EncryptedContent().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_contentType = _OID(1, 2, 840, 113549, 1, 9, 3)
|
|
||||||
|
|
||||||
|
|
||||||
id_data = _OID(1, 2, 840, 113549, 1, 7, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_messageDigest = _OID(1, 2, 840, 113549, 1, 9, 4)
|
|
||||||
|
|
||||||
|
|
||||||
class DigestAlgorithmIdentifiers(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
DigestAlgorithmIdentifiers.componentType = DigestAlgorithmIdentifier()
|
|
||||||
|
|
||||||
|
|
||||||
class EncapsulatedContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncapsulatedContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('eContentType', ContentType()),
|
|
||||||
namedtype.OptionalNamedType('eContent', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Digest(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DigestedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
DigestedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.NamedType('digest', Digest())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('contentType', ContentType()),
|
|
||||||
namedtype.NamedType('content', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnauthAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnauthAttributes.componentType = Attribute()
|
|
||||||
UnauthAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificateInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificateInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('certificate', rfc3280.Certificate()),
|
|
||||||
namedtype.NamedType('attributes', UnauthAttributes())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureAlgorithmIdentifier(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificate(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('extendedCertificateInfo', ExtendedCertificateInfo()),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', Signature())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherCertificateFormat(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherCertificateFormat.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('otherCertFormat', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('otherCert', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateV2(rfc3281.AttributeCertificate):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AttCertVersionV1(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttCertVersionV1.namedValues = namedval.NamedValues(
|
|
||||||
('v1', 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateInfoV1(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateInfoV1.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.DefaultedNamedType('version', AttCertVersionV1().subtype(value="v1")),
|
|
||||||
namedtype.NamedType('subject', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('baseCertificateID', rfc3281.IssuerSerial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('subjectName', rfc3280.GeneralNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
))
|
|
||||||
),
|
|
||||||
namedtype.NamedType('issuer', rfc3280.GeneralNames()),
|
|
||||||
namedtype.NamedType('signature', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('serialNumber', rfc3280.CertificateSerialNumber()),
|
|
||||||
namedtype.NamedType('attCertValidityPeriod', rfc3281.AttCertValidityPeriod()),
|
|
||||||
namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc3280.Attribute())),
|
|
||||||
namedtype.OptionalNamedType('issuerUniqueID', rfc3280.UniqueIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('extensions', rfc3280.Extensions())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateV1(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateV1.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('acInfo', AttributeCertificateInfoV1()),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateChoices(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertificateChoices.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certificate', rfc3280.Certificate()),
|
|
||||||
namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('v1AttrCert', AttributeCertificateV1().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('v2AttrCert', AttributeCertificateV2().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.NamedType('other', OtherCertificateFormat().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateSet(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertificateSet.componentType = CertificateChoices()
|
|
||||||
|
|
||||||
|
|
||||||
class MessageAuthenticationCode(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnsignedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnsignedAttributes.componentType = Attribute()
|
|
||||||
UnsignedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureValue(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SignerInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('sid', SignerIdentifier()),
|
|
||||||
namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('signedAttrs', SignedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', SignatureValue()),
|
|
||||||
namedtype.OptionalNamedType('unsignedAttrs', UnsignedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignerInfos(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerInfos.componentType = SignerInfo()
|
|
||||||
|
|
||||||
|
|
||||||
class SignedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('certificates', CertificateSet().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('signerInfos', SignerInfos())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageAuthenticationCodeAlgorithm(rfc3280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MessageDigest(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Time(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Time.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('utcTime', useful.UTCTime()),
|
|
||||||
namedtype.NamedType('generalTime', useful.GeneralizedTime())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('certs', CertificateSet().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AuthAttributes.componentType = Attribute()
|
|
||||||
AuthAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AuthenticatedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('recipientInfos', RecipientInfos()),
|
|
||||||
namedtype.NamedType('macAlgorithm', MessageAuthenticationCodeAlgorithm()),
|
|
||||||
namedtype.OptionalNamedType('digestAlgorithm', DigestAlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('authAttrs', AuthAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.NamedType('mac', MessageAuthenticationCode()),
|
|
||||||
namedtype.OptionalNamedType('unauthAttrs', UnauthAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_ct_contentInfo = _OID(1, 2, 840, 113549, 1, 9, 16, 1, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_envelopedData = _OID(1, 2, 840, 113549, 1, 7, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class EnvelopedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EnvelopedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('recipientInfos', RecipientInfos()),
|
|
||||||
namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Countersignature(SignerInfo):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_digestedData = _OID(1, 2, 840, 113549, 1, 7, 5)
|
|
||||||
|
|
||||||
|
|
||||||
id_signingTime = _OID(1, 2, 840, 113549, 1, 9, 5)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificateOrCertificate(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificateOrCertificate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certificate', rfc3280.Certificate()),
|
|
||||||
namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_encryptedData = _OID(1, 2, 840, 113549, 1, 7, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_ct_authData = _OID(1, 2, 840, 113549, 1, 9, 16, 1, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class SigningTime(Time):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_countersignature = _OID(1, 2, 840, 113549, 1, 9, 6)
|
|
||||||
|
|
||||||
|
|
@ -1,349 +0,0 @@
|
|||||||
# Auto-generated by asn1ate on 2015-12-21 15:05:42.666261
|
|
||||||
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
|
|
||||||
|
|
||||||
from . import rfc3280
|
|
||||||
from . import rfc3852
|
|
||||||
|
|
||||||
MAX = 64
|
|
||||||
|
|
||||||
|
|
||||||
def _OID(*components):
|
|
||||||
output = []
|
|
||||||
for x in tuple(components):
|
|
||||||
if isinstance(x, univ.ObjectIdentifier):
|
|
||||||
output.extend(list(x))
|
|
||||||
else:
|
|
||||||
output.append(int(x))
|
|
||||||
|
|
||||||
return univ.ObjectIdentifier(output)
|
|
||||||
|
|
||||||
|
|
||||||
id_pkix = _OID(1, 3, 6, 1, 5, 5, 7)
|
|
||||||
|
|
||||||
|
|
||||||
id_pkip = _OID(id_pkix, 5)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl = _OID(id_pkip, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class SinglePubInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SinglePubInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('pubMethod', univ.Integer(namedValues=namedval.NamedValues(('dontCare', 0), ('x500', 1), ('web', 2), ('ldap', 3)))),
|
|
||||||
namedtype.OptionalNamedType('pubLocation', rfc3280.GeneralName())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UTF8Pairs(char.UTF8String):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PKMACValue(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PKMACValue.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('algId', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('value', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class POPOSigningKeyInput(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
POPOSigningKeyInput.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('authInfo', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('sender', rfc3280.GeneralName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('publicKeyMAC', PKMACValue())
|
|
||||||
))
|
|
||||||
),
|
|
||||||
namedtype.NamedType('publicKey', rfc3280.SubjectPublicKeyInfo())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class POPOSigningKey(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
POPOSigningKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('poposkInput', POPOSigningKeyInput().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('algorithmIdentifier', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Attributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Attributes.componentType = rfc3280.Attribute()
|
|
||||||
|
|
||||||
|
|
||||||
class PrivateKeyInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PrivateKeyInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', univ.Integer()),
|
|
||||||
namedtype.NamedType('privateKeyAlgorithm', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('privateKey', univ.OctetString()),
|
|
||||||
namedtype.OptionalNamedType('attributes', Attributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedValue(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedValue.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('intendedAlg', rfc3280.AlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('symmAlg', rfc3280.AlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.OptionalNamedType('encSymmKey', univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.OptionalNamedType('keyAlg', rfc3280.AlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))),
|
|
||||||
namedtype.OptionalNamedType('valueHint', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))),
|
|
||||||
namedtype.NamedType('encValue', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedKey(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('encryptedValue', EncryptedValue()),
|
|
||||||
namedtype.NamedType('envelopedData', rfc3852.EnvelopedData().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyGenParameters(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PKIArchiveOptions(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PKIArchiveOptions.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('encryptedPrivKey', EncryptedKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('keyGenParameters', KeyGenParameters().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('archiveRemGenPrivKey', univ.Boolean().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_authenticator = _OID(id_regCtrl, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_regInfo = _OID(id_pkip, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_regInfo_certReq = _OID(id_regInfo, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class ProtocolEncrKey(rfc3280.SubjectPublicKeyInfo):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(char.UTF8String):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SubsequentMessage(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SubsequentMessage.namedValues = namedval.NamedValues(
|
|
||||||
('encrCert', 0),
|
|
||||||
('challengeResp', 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeTypeAndValue(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeTypeAndValue.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('type', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('value', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class POPOPrivKey(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
POPOPrivKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('thisMessage', univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('subsequentMessage', SubsequentMessage().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('dhMAC', univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.NamedType('agreeMAC', PKMACValue().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
|
||||||
namedtype.NamedType('encryptedKey', rfc3852.EnvelopedData().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProofOfPossession(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ProofOfPossession.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('raVerified', univ.Null().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('signature', POPOSigningKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
|
||||||
namedtype.NamedType('keyEncipherment', POPOPrivKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
|
||||||
namedtype.NamedType('keyAgreement', POPOPrivKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OptionalValidity(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OptionalValidity.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('notBefore', rfc3280.Time().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.OptionalNamedType('notAfter', rfc3280.Time().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertTemplate(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertTemplate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('version', rfc3280.Version().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('serialNumber', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.OptionalNamedType('signingAlg', rfc3280.AlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.OptionalNamedType('issuer', rfc3280.Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
|
||||||
namedtype.OptionalNamedType('validity', OptionalValidity().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4))),
|
|
||||||
namedtype.OptionalNamedType('subject', rfc3280.Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 5))),
|
|
||||||
namedtype.OptionalNamedType('publicKey', rfc3280.SubjectPublicKeyInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))),
|
|
||||||
namedtype.OptionalNamedType('issuerUID', rfc3280.UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))),
|
|
||||||
namedtype.OptionalNamedType('subjectUID', rfc3280.UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))),
|
|
||||||
namedtype.OptionalNamedType('extensions', rfc3280.Extensions().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 9)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Controls(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Controls.componentType = AttributeTypeAndValue()
|
|
||||||
Controls.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class CertRequest(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertRequest.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certReqId', univ.Integer()),
|
|
||||||
namedtype.NamedType('certTemplate', CertTemplate()),
|
|
||||||
namedtype.OptionalNamedType('controls', Controls())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertReqMsg(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertReqMsg.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certReq', CertRequest()),
|
|
||||||
namedtype.OptionalNamedType('popo', ProofOfPossession()),
|
|
||||||
namedtype.OptionalNamedType('regInfo', univ.SequenceOf(componentType=AttributeTypeAndValue()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertReqMessages(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertReqMessages.componentType = CertReqMsg()
|
|
||||||
CertReqMessages.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class CertReq(CertRequest):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_pkiPublicationInfo = _OID(id_regCtrl, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class CertId(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertId.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuer', rfc3280.GeneralName()),
|
|
||||||
namedtype.NamedType('serialNumber', univ.Integer())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OldCertId(CertId):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PKIPublicationInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PKIPublicationInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('action', univ.Integer(namedValues=namedval.NamedValues(('dontPublish', 0), ('pleasePublish', 1)))),
|
|
||||||
namedtype.OptionalNamedType('pubInfos', univ.SequenceOf(componentType=SinglePubInfo()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncKeyWithID(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncKeyWithID.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('privateKey', PrivateKeyInfo()),
|
|
||||||
namedtype.OptionalNamedType('identifier', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('string', char.UTF8String()),
|
|
||||||
namedtype.NamedType('generalName', rfc3280.GeneralName())
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_protocolEncrKey = _OID(id_regCtrl, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_oldCertID = _OID(id_regCtrl, 5)
|
|
||||||
|
|
||||||
|
|
||||||
id_smime = _OID(1, 2, 840, 113549, 1, 9, 16)
|
|
||||||
|
|
||||||
|
|
||||||
class PBMParameter(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PBMParameter.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('salt', univ.OctetString()),
|
|
||||||
namedtype.NamedType('owf', rfc3280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('iterationCount', univ.Integer()),
|
|
||||||
namedtype.NamedType('mac', rfc3280.AlgorithmIdentifier())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_regToken = _OID(id_regCtrl, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_regCtrl_pkiArchiveOptions = _OID(id_regCtrl, 4)
|
|
||||||
|
|
||||||
|
|
||||||
id_regInfo_utf8Pairs = _OID(id_regInfo, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_ct = _OID(id_smime, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_ct_encKeyWithID = _OID(id_ct, 21)
|
|
||||||
|
|
||||||
|
|
||||||
class RegToken(char.UTF8String):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,666 +0,0 @@
|
|||||||
from pyasn1.type import char
|
|
||||||
from pyasn1.type import constraint
|
|
||||||
from pyasn1.type import namedtype
|
|
||||||
from pyasn1.type import namedval
|
|
||||||
from pyasn1.type import tag
|
|
||||||
from pyasn1.type import univ
|
|
||||||
from pyasn1.type import useful
|
|
||||||
|
|
||||||
from . import rfc3281
|
|
||||||
from . import rfc5280
|
|
||||||
|
|
||||||
MAX = 64
|
|
||||||
|
|
||||||
def _OID(*components):
|
|
||||||
output = []
|
|
||||||
for x in tuple(components):
|
|
||||||
if isinstance(x, univ.ObjectIdentifier):
|
|
||||||
output.extend(list(x))
|
|
||||||
else:
|
|
||||||
output.append(int(x))
|
|
||||||
|
|
||||||
return univ.ObjectIdentifier(output)
|
|
||||||
|
|
||||||
|
|
||||||
class AttCertVersionV1(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttCertVersionV1.namedValues = namedval.NamedValues(
|
|
||||||
('v1', 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateInfoV1(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateInfoV1.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.DefaultedNamedType('version', AttCertVersionV1().subtype(value="v1")),
|
|
||||||
namedtype.NamedType('subject', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('baseCertificateID', rfc3281.IssuerSerial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('subjectName', rfc5280.GeneralNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
))
|
|
||||||
),
|
|
||||||
namedtype.NamedType('issuer', rfc5280.GeneralNames()),
|
|
||||||
namedtype.NamedType('signature', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('serialNumber', rfc5280.CertificateSerialNumber()),
|
|
||||||
namedtype.NamedType('attCertValidityPeriod', rfc3281.AttCertValidityPeriod()),
|
|
||||||
namedtype.NamedType('attributes', univ.SequenceOf(componentType=rfc5280.Attribute())),
|
|
||||||
namedtype.OptionalNamedType('issuerUniqueID', rfc5280.UniqueIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('extensions', rfc5280.Extensions())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateV1(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateV1.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('acInfo', AttributeCertificateInfoV1()),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeValue(univ.Any):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Attribute(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Attribute.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('attrType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignedAttributes.componentType = Attribute()
|
|
||||||
SignedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeCertificateV2(rfc3281.AttributeCertificate):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OtherKeyAttribute(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherKeyAttribute.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('keyAttrId', univ.ObjectIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('keyAttr', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnauthAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnauthAttributes.componentType = Attribute()
|
|
||||||
UnauthAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
id_encryptedData = _OID(1, 2, 840, 113549, 1, 7, 6)
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureValue(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IssuerAndSerialNumber(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
IssuerAndSerialNumber.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuer', rfc5280.Name()),
|
|
||||||
namedtype.NamedType('serialNumber', rfc5280.CertificateSerialNumber())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SubjectKeyIdentifier(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientKeyIdentifier(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientKeyIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('other', OtherKeyAttribute())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyAgreeRecipientIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyAgreeRecipientIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('rKeyId', RecipientKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedKey(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientEncryptedKey(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientEncryptedKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('rid', KeyAgreeRecipientIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientEncryptedKeys(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientEncryptedKeys.componentType = RecipientEncryptedKey()
|
|
||||||
|
|
||||||
|
|
||||||
class MessageAuthenticationCode(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CMSVersion(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMSVersion.namedValues = namedval.NamedValues(
|
|
||||||
('v0', 0),
|
|
||||||
('v1', 1),
|
|
||||||
('v2', 2),
|
|
||||||
('v3', 3),
|
|
||||||
('v4', 4),
|
|
||||||
('v5', 5)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherCertificateFormat(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherCertificateFormat.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('otherCertFormat', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('otherCert', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificateInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificateInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('certificate', rfc5280.Certificate()),
|
|
||||||
namedtype.NamedType('attributes', UnauthAttributes())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Signature(univ.BitString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificate(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('extendedCertificateInfo', ExtendedCertificateInfo()),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', Signature())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateChoices(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertificateChoices.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certificate', rfc5280.Certificate()),
|
|
||||||
namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('v1AttrCert', AttributeCertificateV1().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('v2AttrCert', AttributeCertificateV2().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.NamedType('other', OtherCertificateFormat().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateSet(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertificateSet.componentType = CertificateChoices()
|
|
||||||
|
|
||||||
|
|
||||||
class OtherRevocationInfoFormat(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherRevocationInfoFormat.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('otherRevInfoFormat', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('otherRevInfo', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RevocationInfoChoice(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RevocationInfoChoice.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('crl', rfc5280.CertificateList()),
|
|
||||||
namedtype.NamedType('other', OtherRevocationInfoFormat().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RevocationInfoChoices(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RevocationInfoChoices.componentType = RevocationInfoChoice()
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('certs', CertificateSet().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContentType(univ.ObjectIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedContent(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ContentEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('contentType', ContentType()),
|
|
||||||
namedtype.NamedType('contentEncryptionAlgorithm', ContentEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('encryptedContent', EncryptedContent().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnprotectedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnprotectedAttributes.componentType = Attribute()
|
|
||||||
UnprotectedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyEncryptionAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class KEKIdentifier(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KEKIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('keyIdentifier', univ.OctetString()),
|
|
||||||
namedtype.OptionalNamedType('date', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('other', OtherKeyAttribute())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KEKRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KEKRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('kekid', KEKIdentifier()),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyDerivationAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PasswordRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('keyDerivationAlgorithm', KeyDerivationAlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyTransRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyTransRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('rid', RecipientIdentifier()),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encryptedKey', EncryptedKey())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserKeyingMaterial(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorPublicKey(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorPublicKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('algorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('publicKey', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OriginatorIdentifierOrKey(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorIdentifierOrKey.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('originatorKey', OriginatorPublicKey().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyAgreeRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
KeyAgreeRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('originator', OriginatorIdentifierOrKey().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.OptionalNamedType('ukm', UserKeyingMaterial().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('keyEncryptionAlgorithm', KeyEncryptionAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('recipientEncryptedKeys', RecipientEncryptedKeys())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherRecipientInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherRecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('oriType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('oriValue', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientInfo(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('ktri', KeyTransRecipientInfo()),
|
|
||||||
namedtype.NamedType('kari', KeyAgreeRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
|
||||||
namedtype.NamedType('kekri', KEKRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
|
||||||
namedtype.NamedType('pwri', PasswordRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
|
||||||
namedtype.NamedType('ori', OtherRecipientInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RecipientInfos(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RecipientInfos.componentType = RecipientInfo()
|
|
||||||
RecipientInfos.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class EnvelopedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EnvelopedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('recipientInfos', RecipientInfos()),
|
|
||||||
namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DigestAlgorithmIdentifier(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_ct_contentInfo = _OID(1, 2, 840, 113549, 1, 9, 16, 1, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_digestedData = _OID(1, 2, 840, 113549, 1, 7, 5)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('encryptedContentInfo', EncryptedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('unprotectedAttrs', UnprotectedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_messageDigest = _OID(1, 2, 840, 113549, 1, 9, 4)
|
|
||||||
|
|
||||||
|
|
||||||
id_signedData = _OID(1, 2, 840, 113549, 1, 7, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageAuthenticationCodeAlgorithm(rfc5280.AlgorithmIdentifier):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnsignedAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
UnsignedAttributes.componentType = Attribute()
|
|
||||||
UnsignedAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class SignerIdentifier(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerIdentifier.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerAndSerialNumber', IssuerAndSerialNumber()),
|
|
||||||
namedtype.NamedType('subjectKeyIdentifier', SubjectKeyIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignerInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('sid', SignerIdentifier()),
|
|
||||||
namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
|
|
||||||
namedtype.OptionalNamedType('signedAttrs', SignedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', SignatureAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', SignatureValue()),
|
|
||||||
namedtype.OptionalNamedType('unsignedAttrs', UnsignedAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignerInfos(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignerInfos.componentType = SignerInfo()
|
|
||||||
|
|
||||||
|
|
||||||
class Countersignature(SignerInfo):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('contentType', ContentType()),
|
|
||||||
namedtype.NamedType('content', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncapsulatedContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncapsulatedContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('eContentType', ContentType()),
|
|
||||||
namedtype.OptionalNamedType('eContent', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_countersignature = _OID(1, 2, 840, 113549, 1, 9, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_data = _OID(1, 2, 840, 113549, 1, 7, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageDigest(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AuthAttributes(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AuthAttributes.componentType = Attribute()
|
|
||||||
AuthAttributes.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class Time(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
Time.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('utcTime', useful.UTCTime()),
|
|
||||||
namedtype.NamedType('generalTime', useful.GeneralizedTime())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AuthenticatedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.OptionalNamedType('originatorInfo', OriginatorInfo().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('recipientInfos', RecipientInfos()),
|
|
||||||
namedtype.NamedType('macAlgorithm', MessageAuthenticationCodeAlgorithm()),
|
|
||||||
namedtype.OptionalNamedType('digestAlgorithm', DigestAlgorithmIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('authAttrs', AuthAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
|
||||||
namedtype.NamedType('mac', MessageAuthenticationCode()),
|
|
||||||
namedtype.OptionalNamedType('unauthAttrs', UnauthAttributes().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_contentType = _OID(1, 2, 840, 113549, 1, 9, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedCertificateOrCertificate(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificateOrCertificate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certificate', rfc5280.Certificate()),
|
|
||||||
namedtype.NamedType('extendedCertificate', ExtendedCertificate().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Digest(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DigestedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
DigestedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('digestAlgorithm', DigestAlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.NamedType('digest', Digest())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_envelopedData = _OID(1, 2, 840, 113549, 1, 7, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class DigestAlgorithmIdentifiers(univ.SetOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
DigestAlgorithmIdentifiers.componentType = DigestAlgorithmIdentifier()
|
|
||||||
|
|
||||||
|
|
||||||
class SignedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
SignedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', CMSVersion()),
|
|
||||||
namedtype.NamedType('digestAlgorithms', DigestAlgorithmIdentifiers()),
|
|
||||||
namedtype.NamedType('encapContentInfo', EncapsulatedContentInfo()),
|
|
||||||
namedtype.OptionalNamedType('certificates', CertificateSet().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
|
||||||
namedtype.OptionalNamedType('crls', RevocationInfoChoices().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('signerInfos', SignerInfos())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_signingTime = _OID(1, 2, 840, 113549, 1, 9, 5)
|
|
||||||
|
|
||||||
|
|
||||||
class SigningTime(Time):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_ct_authData = _OID(1, 2, 840, 113549, 1, 9, 16, 1, 2)
|
|
@ -1,576 +0,0 @@
|
|||||||
# Auto-generated by asn1ate on 2015-12-21 15:40:08.299576
|
|
||||||
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
|
|
||||||
|
|
||||||
from . import rfc4211
|
|
||||||
from . import rfc5280
|
|
||||||
from . import rfc5652
|
|
||||||
|
|
||||||
MAX = 64
|
|
||||||
|
|
||||||
|
|
||||||
def _OID(*components):
|
|
||||||
output = []
|
|
||||||
for x in tuple(components):
|
|
||||||
if isinstance(x, univ.ObjectIdentifier):
|
|
||||||
output.extend(list(x))
|
|
||||||
else:
|
|
||||||
output.append(int(x))
|
|
||||||
|
|
||||||
return univ.ObjectIdentifier(output)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeSubjectName(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ChangeSubjectName.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.OptionalNamedType('subject', rfc5280.Name()),
|
|
||||||
namedtype.OptionalNamedType('subjectAlt', rfc5280.GeneralNames())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributeValue(univ.Any):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CMCStatus(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCStatus.namedValues = namedval.NamedValues(
|
|
||||||
('success', 0),
|
|
||||||
('failed', 2),
|
|
||||||
('pending', 3),
|
|
||||||
('noSupport', 4),
|
|
||||||
('confirmRequired', 5),
|
|
||||||
('popRequired', 6),
|
|
||||||
('partial', 7)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PendInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PendInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('pendToken', univ.OctetString()),
|
|
||||||
namedtype.NamedType('pendTime', useful.GeneralizedTime())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
bodyIdMax = univ.Integer(4294967295)
|
|
||||||
|
|
||||||
|
|
||||||
class BodyPartID(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
BodyPartID.subtypeSpec = constraint.ValueRangeConstraint(0, bodyIdMax)
|
|
||||||
|
|
||||||
|
|
||||||
class BodyPartPath(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
BodyPartPath.componentType = BodyPartID()
|
|
||||||
BodyPartPath.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
class BodyPartReference(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
BodyPartReference.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('bodyPartPath', BodyPartPath())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CMCFailInfo(univ.Integer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCFailInfo.namedValues = namedval.NamedValues(
|
|
||||||
('badAlg', 0),
|
|
||||||
('badMessageCheck', 1),
|
|
||||||
('badRequest', 2),
|
|
||||||
('badTime', 3),
|
|
||||||
('badCertId', 4),
|
|
||||||
('unsupportedExt', 5),
|
|
||||||
('mustArchiveKeys', 6),
|
|
||||||
('badIdentity', 7),
|
|
||||||
('popRequired', 8),
|
|
||||||
('popFailed', 9),
|
|
||||||
('noKeyReuse', 10),
|
|
||||||
('internalCAError', 11),
|
|
||||||
('tryLater', 12),
|
|
||||||
('authDataFail', 13)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CMCStatusInfoV2(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCStatusInfoV2.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('cMCStatus', CMCStatus()),
|
|
||||||
namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartReference())),
|
|
||||||
namedtype.OptionalNamedType('statusString', char.UTF8String()),
|
|
||||||
namedtype.OptionalNamedType('otherInfo', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('failInfo', CMCFailInfo()),
|
|
||||||
namedtype.NamedType('pendInfo', PendInfo()),
|
|
||||||
namedtype.NamedType('extendedFailInfo', univ.Sequence(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('failInfoOID', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('failInfoValue', AttributeValue())
|
|
||||||
))
|
|
||||||
)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GetCRL(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
GetCRL.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerName', rfc5280.Name()),
|
|
||||||
namedtype.OptionalNamedType('cRLName', rfc5280.GeneralName()),
|
|
||||||
namedtype.OptionalNamedType('time', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('reasons', rfc5280.ReasonFlags())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_pkix = _OID(1, 3, 6, 1, 5, 5, 7)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc = _OID(id_pkix, 7)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_batchResponses = _OID(id_cmc, 29)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_popLinkWitness = _OID(id_cmc, 23)
|
|
||||||
|
|
||||||
|
|
||||||
class PopLinkWitnessV2(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PopLinkWitnessV2.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('keyGenAlgorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('macAlgorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('witness', univ.OctetString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_popLinkWitnessV2 = _OID(id_cmc, 33)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_identityProofV2 = _OID(id_cmc, 34)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_revokeRequest = _OID(id_cmc, 17)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_recipientNonce = _OID(id_cmc, 7)
|
|
||||||
|
|
||||||
|
|
||||||
class ControlsProcessed(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ControlsProcessed.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartReference()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CertificationRequest(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CertificationRequest.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('certificationRequestInfo', univ.Sequence(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('version', univ.Integer()),
|
|
||||||
namedtype.NamedType('subject', rfc5280.Name()),
|
|
||||||
namedtype.NamedType('subjectPublicKeyInfo', univ.Sequence(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('algorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('subjectPublicKey', univ.BitString())
|
|
||||||
))
|
|
||||||
),
|
|
||||||
namedtype.NamedType('attributes', univ.SetOf(componentType=rfc5652.Attribute()).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))
|
|
||||||
))
|
|
||||||
),
|
|
||||||
namedtype.NamedType('signatureAlgorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('signature', univ.BitString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedCertificationRequest(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TaggedCertificationRequest.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('certificationRequest', CertificationRequest())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedRequest(univ.Choice):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TaggedRequest.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('tcr', TaggedCertificationRequest().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
|
||||||
namedtype.NamedType('crm', rfc4211.CertReqMsg().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
|
||||||
namedtype.NamedType('orm', univ.Sequence(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('requestMessageType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('requestMessageValue', univ.Any())
|
|
||||||
))
|
|
||||||
.subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_popLinkRandom = _OID(id_cmc, 22)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_statusInfo = _OID(id_cmc, 1)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_trustedAnchors = _OID(id_cmc, 26)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_transactionId = _OID(id_cmc, 5)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_encryptedPOP = _OID(id_cmc, 9)
|
|
||||||
|
|
||||||
|
|
||||||
class PublishTrustAnchors(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PublishTrustAnchors.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('seqNumber', univ.Integer()),
|
|
||||||
namedtype.NamedType('hashAlgorithm', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('anchorHashes', univ.SequenceOf(componentType=univ.OctetString()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RevokeRequest(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
RevokeRequest.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerName', rfc5280.Name()),
|
|
||||||
namedtype.NamedType('serialNumber', univ.Integer()),
|
|
||||||
namedtype.NamedType('reason', rfc5280.CRLReason()),
|
|
||||||
namedtype.OptionalNamedType('invalidityDate', useful.GeneralizedTime()),
|
|
||||||
namedtype.OptionalNamedType('passphrase', univ.OctetString()),
|
|
||||||
namedtype.OptionalNamedType('comment', char.UTF8String())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_senderNonce = _OID(id_cmc, 6)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_authData = _OID(id_cmc, 27)
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedContentInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TaggedContentInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('contentInfo', rfc5652.ContentInfo())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IdentifyProofV2(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
IdentifyProofV2.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('proofAlgID', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('macAlgId', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('witness', univ.OctetString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CMCPublicationInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCPublicationInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('hashAlg', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('certHashes', univ.SequenceOf(componentType=univ.OctetString())),
|
|
||||||
namedtype.NamedType('pubInfo', rfc4211.PKIPublicationInfo())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_kp_cmcCA = _OID(rfc5280.id_kp, 27)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_confirmCertAcceptance = _OID(id_cmc, 24)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_raIdentityWitness = _OID(id_cmc, 35)
|
|
||||||
|
|
||||||
|
|
||||||
id_ExtensionReq = _OID(1, 2, 840, 113549, 1, 9, 14)
|
|
||||||
|
|
||||||
|
|
||||||
id_cct = _OID(id_pkix, 12)
|
|
||||||
|
|
||||||
|
|
||||||
id_cct_PKIData = _OID(id_cct, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_kp_cmcRA = _OID(rfc5280.id_kp, 28)
|
|
||||||
|
|
||||||
|
|
||||||
class CMCStatusInfo(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCStatusInfo.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('cMCStatus', CMCStatus()),
|
|
||||||
namedtype.NamedType('bodyList', univ.SequenceOf(componentType=BodyPartID())),
|
|
||||||
namedtype.OptionalNamedType('statusString', char.UTF8String()),
|
|
||||||
namedtype.OptionalNamedType('otherInfo', univ.Choice(componentType=namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('failInfo', CMCFailInfo()),
|
|
||||||
namedtype.NamedType('pendInfo', PendInfo())
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DecryptedPOP(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
DecryptedPOP.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('thePOPAlgID', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('thePOP', univ.OctetString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_addExtensions = _OID(id_cmc, 8)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_modCertTemplate = _OID(id_cmc, 31)
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedAttribute(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
TaggedAttribute.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('attrType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('attrValues', univ.SetOf(componentType=AttributeValue()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OtherMsg(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
OtherMsg.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartID', BodyPartID()),
|
|
||||||
namedtype.NamedType('otherMsgType', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('otherMsgValue', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PKIData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PKIData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('controlSequence', univ.SequenceOf(componentType=TaggedAttribute())),
|
|
||||||
namedtype.NamedType('reqSequence', univ.SequenceOf(componentType=TaggedRequest())),
|
|
||||||
namedtype.NamedType('cmsSequence', univ.SequenceOf(componentType=TaggedContentInfo())),
|
|
||||||
namedtype.NamedType('otherMsgSequence', univ.SequenceOf(componentType=OtherMsg()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BodyPartList(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
BodyPartList.componentType = BodyPartID()
|
|
||||||
BodyPartList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_responseBody = _OID(id_cmc, 37)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthPublish(BodyPartID):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CMCUnsignedData(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
CMCUnsignedData.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('bodyPartPath', BodyPartPath()),
|
|
||||||
namedtype.NamedType('identifier', univ.ObjectIdentifier()),
|
|
||||||
namedtype.NamedType('content', univ.Any())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CMCCertId(rfc5652.IssuerAndSerialNumber):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PKIResponse(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
PKIResponse.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('controlSequence', univ.SequenceOf(componentType=TaggedAttribute())),
|
|
||||||
namedtype.NamedType('cmsSequence', univ.SequenceOf(componentType=TaggedContentInfo())),
|
|
||||||
namedtype.NamedType('otherMsgSequence', univ.SequenceOf(componentType=OtherMsg()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ResponseBody(PKIResponse):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_statusInfoV2 = _OID(id_cmc, 25)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_lraPOPWitness = _OID(id_cmc, 11)
|
|
||||||
|
|
||||||
|
|
||||||
class ModCertTemplate(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ModCertTemplate.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('pkiDataReference', BodyPartPath()),
|
|
||||||
namedtype.NamedType('certReferences', BodyPartList()),
|
|
||||||
namedtype.DefaultedNamedType('replace', univ.Boolean().subtype(value=1)),
|
|
||||||
namedtype.NamedType('certTemplate', rfc4211.CertTemplate())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_regInfo = _OID(id_cmc, 18)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_identityProof = _OID(id_cmc, 3)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionReq(univ.SequenceOf):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ExtensionReq.componentType = rfc5280.Extension()
|
|
||||||
ExtensionReq.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
|
|
||||||
|
|
||||||
|
|
||||||
id_kp_cmcArchive = _OID(rfc5280.id_kp, 28)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_publishCert = _OID(id_cmc, 30)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_dataReturn = _OID(id_cmc, 4)
|
|
||||||
|
|
||||||
|
|
||||||
class LraPopWitness(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
LraPopWitness.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('pkiDataBodyid', BodyPartID()),
|
|
||||||
namedtype.NamedType('bodyIds', univ.SequenceOf(componentType=BodyPartID()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_aa = _OID(1, 2, 840, 113549, 1, 9, 16, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_aa_cmc_unsignedData = _OID(id_aa, 34)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_getCert = _OID(id_cmc, 15)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_batchRequests = _OID(id_cmc, 28)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_decryptedPOP = _OID(id_cmc, 10)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_responseInfo = _OID(id_cmc, 19)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_changeSubjectName = _OID(id_cmc, 36)
|
|
||||||
|
|
||||||
|
|
||||||
class GetCert(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
GetCert.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('issuerName', rfc5280.GeneralName()),
|
|
||||||
namedtype.NamedType('serialNumber', univ.Integer())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_identification = _OID(id_cmc, 2)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_queryPending = _OID(id_cmc, 21)
|
|
||||||
|
|
||||||
|
|
||||||
class AddExtensions(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
AddExtensions.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('pkiDataReference', BodyPartID()),
|
|
||||||
namedtype.NamedType('certReferences', univ.SequenceOf(componentType=BodyPartID())),
|
|
||||||
namedtype.NamedType('extensions', univ.SequenceOf(componentType=rfc5280.Extension()))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedPOP(univ.Sequence):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EncryptedPOP.componentType = namedtype.NamedTypes(
|
|
||||||
namedtype.NamedType('request', TaggedRequest()),
|
|
||||||
namedtype.NamedType('cms', rfc5652.ContentInfo()),
|
|
||||||
namedtype.NamedType('thePOPAlgID', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('witnessAlgID', rfc5280.AlgorithmIdentifier()),
|
|
||||||
namedtype.NamedType('witness', univ.OctetString())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_getCRL = _OID(id_cmc, 16)
|
|
||||||
|
|
||||||
|
|
||||||
id_cct_PKIResponse = _OID(id_cct, 3)
|
|
||||||
|
|
||||||
|
|
||||||
id_cmc_controlProcessed = _OID(id_cmc, 32)
|
|
||||||
|
|
||||||
|
|
||||||
class NoSignatureValue(univ.OctetString):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
id_ad_cmc = _OID(rfc5280.id_ad, 12)
|
|
||||||
|
|
||||||
|
|
||||||
id_alg_noSignature = _OID(id_pkix, 6, 2)
|
|
||||||
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
import oslo_config
|
|
||||||
import oslo_messaging
|
|
||||||
from pycadf import cadftaxonomy
|
|
||||||
from pycadf import event
|
|
||||||
from pycadf import identifier
|
|
||||||
from pycadf import resource
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
target = None
|
|
||||||
notifier = None
|
|
||||||
|
|
||||||
ANCHOR_UUID_NS = uuid.UUID('0ff9c8c5-f57e-47aa-bd3d-5407eb907c74')
|
|
||||||
|
|
||||||
|
|
||||||
def _emit_event(event_type, payload):
|
|
||||||
if not payload.is_valid():
|
|
||||||
logger.error("created invalid audit event: %s", payload)
|
|
||||||
return
|
|
||||||
|
|
||||||
if notifier is not None:
|
|
||||||
notifier.info({}, event_type, payload.as_dict())
|
|
||||||
|
|
||||||
|
|
||||||
def _event_defaults(result):
|
|
||||||
# eventType, id, eventTime are filled in automatically by pyCADF
|
|
||||||
return {
|
|
||||||
'outcome': (cadftaxonomy.OUTCOME_SUCCESS if result else
|
|
||||||
cadftaxonomy.OUTCOME_FAILURE),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _user_resource(username, result):
|
|
||||||
if result:
|
|
||||||
res_id = uuid.uuid5(ANCHOR_UUID_NS, result.username)
|
|
||||||
user = result.username
|
|
||||||
else:
|
|
||||||
if username:
|
|
||||||
res_id = uuid.uuid5(ANCHOR_UUID_NS, username.encode('utf-8',
|
|
||||||
'replace'))
|
|
||||||
user = username
|
|
||||||
else:
|
|
||||||
# Authentication was a failure, but there was no username
|
|
||||||
# provided either. This can happen with failed token authentication
|
|
||||||
# for example.
|
|
||||||
res_id = uuid.uuid4()
|
|
||||||
user = None
|
|
||||||
return resource.Resource(
|
|
||||||
id=str(res_id),
|
|
||||||
typeURI=cadftaxonomy.ACCOUNT_USER,
|
|
||||||
name=user)
|
|
||||||
|
|
||||||
|
|
||||||
def _auth_resource(ra_name):
|
|
||||||
return resource.Resource(
|
|
||||||
id='anchor://authentication',
|
|
||||||
typeURI=cadftaxonomy.SERVICE_SECURITY,
|
|
||||||
domain=ra_name)
|
|
||||||
|
|
||||||
|
|
||||||
def _policy_resource(ra_name):
|
|
||||||
return resource.Resource(
|
|
||||||
id='anchor://certificates/policy',
|
|
||||||
typeURI=cadftaxonomy.SECURITY_POLICY,
|
|
||||||
domain=ra_name)
|
|
||||||
|
|
||||||
|
|
||||||
def _certificate_resource(fingerprint):
|
|
||||||
if fingerprint is None:
|
|
||||||
res_id = identifier.generate_uuid()
|
|
||||||
else:
|
|
||||||
res_id = "certificate:%s" % (fingerprint,)
|
|
||||||
return resource.Resource(
|
|
||||||
id=res_id,
|
|
||||||
typeURI=cadftaxonomy.SECURITY_KEY,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def emit_auth_event(ra_name, username, result):
|
|
||||||
success = result is not None
|
|
||||||
params = _event_defaults(success)
|
|
||||||
params['action'] = 'authenticate'
|
|
||||||
params['initiator'] = _user_resource(username, result)
|
|
||||||
auth_res = _auth_resource(ra_name)
|
|
||||||
params['observer'] = auth_res
|
|
||||||
params['target'] = auth_res
|
|
||||||
_emit_event('audit.auth', event.Event(**params))
|
|
||||||
|
|
||||||
|
|
||||||
def emit_signing_event(ra_name, username, result, fingerprint=None):
|
|
||||||
params = _event_defaults(result)
|
|
||||||
params['action'] = 'evaluate'
|
|
||||||
params['initiator'] = _user_resource(username, result)
|
|
||||||
params['observer'] = _policy_resource(ra_name)
|
|
||||||
params['target'] = _certificate_resource(fingerprint)
|
|
||||||
# add when pycadf merges event names
|
|
||||||
# params['name'] = "certificate signing"
|
|
||||||
_emit_event('audit.sign', event.Event(**params))
|
|
||||||
|
|
||||||
|
|
||||||
def init_audit():
|
|
||||||
global target
|
|
||||||
global notifier
|
|
||||||
audit_conf = jsonloader.config_for_audit()
|
|
||||||
if audit_conf is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
target = audit_conf.get('target', 'log')
|
|
||||||
cfg = oslo_config.cfg.ConfigOpts()
|
|
||||||
if target == 'messaging':
|
|
||||||
transport = oslo_messaging.get_transport(cfg, url=audit_conf['url'])
|
|
||||||
else:
|
|
||||||
transport = oslo_messaging.get_transport(cfg)
|
|
||||||
notifier = oslo_messaging.Notifier(transport, 'anchor', driver=target)
|
|
@ -1,44 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import pecan
|
|
||||||
|
|
||||||
from anchor.auth import keystone # noqa
|
|
||||||
from anchor.auth import ldap # noqa
|
|
||||||
from anchor.auth import static # noqa
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
|
|
||||||
def validate(ra_name, user, secret):
|
|
||||||
"""Top-level authN entry point.
|
|
||||||
|
|
||||||
This will return an AuthDetails object or abort. This will only
|
|
||||||
check that a single auth method. That method will either succeed
|
|
||||||
or fail.
|
|
||||||
|
|
||||||
:param ra_name: name of the registration authority
|
|
||||||
:param user: user provided user name
|
|
||||||
:param secret: user provided secret (password or token)
|
|
||||||
:return: AuthDetails if authenticated or aborts
|
|
||||||
"""
|
|
||||||
auth_conf = jsonloader.authentication_for_registration_authority(ra_name)
|
|
||||||
backend_name = auth_conf['backend']
|
|
||||||
backend = jsonloader.conf.get_authentication(backend_name)
|
|
||||||
res = backend(ra_name, user, secret)
|
|
||||||
if res:
|
|
||||||
return res
|
|
||||||
|
|
||||||
# we should only get here if a module failed to abort
|
|
||||||
pecan.abort(401, "authentication failure")
|
|
@ -1,55 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from anchor.auth import results
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def login(_, token):
|
|
||||||
"""Authenticate with the keystone endpoint from configuration file
|
|
||||||
|
|
||||||
:param token: A Keystone Token
|
|
||||||
:returns: AuthDetails -- Class used for authentication information
|
|
||||||
"""
|
|
||||||
req = requests.get(jsonloader.conf.auth['keystone']['url'] +
|
|
||||||
'/v3/auth/tokens',
|
|
||||||
headers={'X-Auth-Token': token,
|
|
||||||
'X-Subject-Token': token})
|
|
||||||
if req.status_code != 200:
|
|
||||||
logger.info("Authentication failed for token <%s>, status %s",
|
|
||||||
token, req.status_code)
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = req.json()
|
|
||||||
user = res['token']['user']
|
|
||||||
user_name = user['name']
|
|
||||||
user_id = user['id']
|
|
||||||
project_id = res['token']['project']['id']
|
|
||||||
|
|
||||||
roles = [role['name'] for role in res['token']['roles']]
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Keystone response was not in the expected format")
|
|
||||||
return None
|
|
||||||
|
|
||||||
return results.AuthDetails(username=user_name, groups=roles,
|
|
||||||
user_id=user_id, project_id=project_id)
|
|
@ -1,75 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import ldap3
|
|
||||||
from ldap3.core import exceptions as ldap3_exc
|
|
||||||
from ldap3.utils import dn
|
|
||||||
|
|
||||||
from anchor.auth import results
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def user_get_groups(attributes):
|
|
||||||
"""Retrieve the group membership
|
|
||||||
|
|
||||||
:param attributes: LDAP attributes for user
|
|
||||||
:returns: List -- A list of groups that the user is a member of
|
|
||||||
"""
|
|
||||||
groups = attributes.get('memberOf', [])
|
|
||||||
group_dns = [dn.parse_dn(g) for g in groups]
|
|
||||||
return [x[0][1] for x in group_dns if x[1] == ('OU', 'Groups', ',')]
|
|
||||||
|
|
||||||
|
|
||||||
def login(ra_name, user, secret):
|
|
||||||
"""Attempt to Authenitcate user using LDAP
|
|
||||||
|
|
||||||
:param ra_name: name of registration authority
|
|
||||||
:param user: Username
|
|
||||||
:param secret: Secret/Passphrase
|
|
||||||
:returns: AuthDetails -- Class used for authentication information
|
|
||||||
"""
|
|
||||||
conf = jsonloader.authentication_for_registration_authority(ra_name)
|
|
||||||
ldap_port = int(conf.get('port', 389))
|
|
||||||
use_ssl = conf.get('ssl', ldap_port == 636)
|
|
||||||
|
|
||||||
lds = ldap3.Server(conf['host'], port=ldap_port,
|
|
||||||
get_info=ldap3.ALL, use_ssl=use_ssl)
|
|
||||||
|
|
||||||
try:
|
|
||||||
ldap_user = "%s@%s" % (user, conf['domain'])
|
|
||||||
ldc = ldap3.Connection(lds, auto_bind=True, client_strategy=ldap3.SYNC,
|
|
||||||
user=ldap_user, password=secret,
|
|
||||||
authentication=ldap3.SIMPLE, check_names=True)
|
|
||||||
|
|
||||||
filter_str = ('(sAMAccountName=%s)' %
|
|
||||||
ldap3.utils.conv.escape_bytes(user))
|
|
||||||
ldc.search(conf['base'], filter_str,
|
|
||||||
ldap3.SUBTREE, attributes=['memberOf'])
|
|
||||||
if ldc.result['result'] != 0:
|
|
||||||
return None
|
|
||||||
user_attrs = ldc.response[0]['attributes']
|
|
||||||
user_groups = user_get_groups(user_attrs)
|
|
||||||
return results.AuthDetails(username=user, groups=user_groups)
|
|
||||||
except ldap3_exc.LDAPSocketOpenError:
|
|
||||||
logger.error("cannot connect to LDAP host '%s' (authority '%s')",
|
|
||||||
conf['host'], ra_name)
|
|
||||||
return None
|
|
||||||
except ldap3_exc.LDAPBindError:
|
|
||||||
logger.info("failed ldap auth for user %s", user)
|
|
||||||
return None
|
|
@ -1,32 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
|
|
||||||
class AuthDetails(object):
|
|
||||||
def __init__(self, username=None, groups=None, user_id=None,
|
|
||||||
project_id=None):
|
|
||||||
self.username = username
|
|
||||||
self.groups = groups or []
|
|
||||||
self.user_id = user_id
|
|
||||||
self.project_id = project_id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.username == other.username and
|
|
||||||
self.groups == other.groups and
|
|
||||||
self.user_id == other.user_id and
|
|
||||||
self.project_id == other.project_id)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
@ -1,69 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from anchor.auth import results
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
from oslo_utils import secretutils as util
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def login(ra_name, user, secret):
|
|
||||||
"""Validates a user supplied user/password against an expected value.
|
|
||||||
|
|
||||||
The expected value is pulled from the pecan config. Note that this
|
|
||||||
value is currently stored in the clear inside that config, so we
|
|
||||||
are assuming that the config is protected using file perms, etc.
|
|
||||||
|
|
||||||
This function provides some resistance to timing attacks, but
|
|
||||||
information on the expected user/password lengths can still be
|
|
||||||
leaked. It may also be possible to use a timing attack to see
|
|
||||||
which input failed validation. See comments below for details.
|
|
||||||
|
|
||||||
:param ra_name: name of the registration authority
|
|
||||||
:param user: The user supplied username (unicode or string)
|
|
||||||
:param secret: The user supplied password (unicode or string)
|
|
||||||
:return: None on failure or an AuthDetails object on success
|
|
||||||
"""
|
|
||||||
auth_conf = jsonloader.authentication_for_registration_authority(ra_name)
|
|
||||||
|
|
||||||
# convert input to strings
|
|
||||||
user = str(user)
|
|
||||||
secret = str(secret)
|
|
||||||
|
|
||||||
# expected values
|
|
||||||
try:
|
|
||||||
expected_user = str(auth_conf['user'])
|
|
||||||
expected_secret = str(auth_conf['secret'])
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
logger.warning("auth conf missing static user or secret")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# This technique is used to provide a constant time string compare
|
|
||||||
# between the user input and the expected values.
|
|
||||||
valid_user = util.constant_time_compare(user, expected_user)
|
|
||||||
valid_secret = util.constant_time_compare(secret, expected_secret)
|
|
||||||
|
|
||||||
# This if statement results in a potential timing attack where the
|
|
||||||
# statement could return more quickly if valid_secret=False. We
|
|
||||||
# do not see an obvious solution to this problem, but also believe
|
|
||||||
# that leaking which input was valid isn't as big of a concern.
|
|
||||||
if valid_user and valid_secret:
|
|
||||||
return results.AuthDetails(username=expected_user, groups=[])
|
|
||||||
|
|
||||||
logger.info("failed static auth for user {}".format(user))
|
|
@ -1,201 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
import pecan
|
|
||||||
from webob import exc as http_status
|
|
||||||
|
|
||||||
from anchor import cmc
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor import util
|
|
||||||
from anchor import validation
|
|
||||||
from anchor.X509 import certificate
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# we only support the PEM encoding for now, but this may grow
|
|
||||||
# to support things like DER in the future
|
|
||||||
VALID_ENCODINGS = ['pem']
|
|
||||||
|
|
||||||
|
|
||||||
def parse_csr(data, encoding):
|
|
||||||
"""Loads the user provided CSR into the backend X509 library.
|
|
||||||
|
|
||||||
:param data: CSR as provided by the API user
|
|
||||||
:param encoding: encoding for the CSR (must be PEM today)
|
|
||||||
:return: CSR object from backend X509 library or aborts
|
|
||||||
"""
|
|
||||||
# validate untrusted input
|
|
||||||
if str(encoding).lower() not in VALID_ENCODINGS:
|
|
||||||
logger.error("parse_csr failed: bad encoding ({})".format(encoding))
|
|
||||||
pecan.abort(400, "invalid CSR")
|
|
||||||
|
|
||||||
if data is None:
|
|
||||||
logger.error("parse_csr failed: missing CSR")
|
|
||||||
pecan.abort(400, "invalid CSR")
|
|
||||||
|
|
||||||
# get DER version
|
|
||||||
der = util.extract_pem(data.encode('ascii'))
|
|
||||||
if der is None:
|
|
||||||
logger.error("parse_csr failed: PEM contents not found")
|
|
||||||
pecan.abort(400, "PEM contents not found")
|
|
||||||
|
|
||||||
# try to unpack the certificate from CMC wrappers
|
|
||||||
try:
|
|
||||||
csr = cmc.parse_request(der)
|
|
||||||
return signing_request.X509Csr(csr)
|
|
||||||
except cmc.CMCParsingError:
|
|
||||||
# it's not CMC data, that's fine, it's likely the CSR itself
|
|
||||||
try:
|
|
||||||
return signing_request.X509Csr.from_buffer(der, 'der')
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Exception while parsing the CSR: %s", e)
|
|
||||||
pecan.abort(400, "CSR cannot be parsed")
|
|
||||||
|
|
||||||
|
|
||||||
def validate_csr(ra_name, auth_result, csr, request):
|
|
||||||
"""Validates various aspects of the CSR based on the loaded config.
|
|
||||||
|
|
||||||
The arguments of this method are passed to the underlying validate
|
|
||||||
methods. Therefore, some may be optional, depending on which
|
|
||||||
validation routines are specified in the configuration.
|
|
||||||
|
|
||||||
:param ra_name: name of the registration authority
|
|
||||||
:param auth_result: AuthDetails value from auth.validate
|
|
||||||
:param csr: CSR value from certificate_ops.parse_csr
|
|
||||||
:param request: pecan request object associated with this action
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
valid = validation.validate_csr(ra_name, auth_result, csr, request)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Error running validators: %s", e)
|
|
||||||
pecan.abort(500, "Internal Validation Error")
|
|
||||||
|
|
||||||
if not all(list(valid.values())):
|
|
||||||
pecan.abort(400, "CSR failed validation")
|
|
||||||
|
|
||||||
|
|
||||||
def certificate_fingerprint(cert_pem, hash_name):
|
|
||||||
"""Get certificate fingerprint."""
|
|
||||||
cert = certificate.X509Certificate.from_buffer(cert_pem)
|
|
||||||
return cert.get_fingerprint(hash_name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ca(ra_name):
|
|
||||||
ca_conf = jsonloader.signing_ca_for_registration_authority(ra_name)
|
|
||||||
|
|
||||||
ca_path = ca_conf.get('cert_path')
|
|
||||||
if not ca_path:
|
|
||||||
pecan.abort(404, "CA certificate not available")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(ca_path) as f:
|
|
||||||
return f.read()
|
|
||||||
except IOError:
|
|
||||||
pecan.abort(500, "CA certificate not available")
|
|
||||||
|
|
||||||
|
|
||||||
def dispatch_sign(ra_name, csr):
|
|
||||||
"""Dispatch the sign call to the configured backend.
|
|
||||||
|
|
||||||
:param csr: X509 certificate signing request
|
|
||||||
:return: signed certificate in PEM format
|
|
||||||
"""
|
|
||||||
ca_conf = jsonloader.signing_ca_for_registration_authority(ra_name)
|
|
||||||
backend_name = ca_conf.get('backend', 'anchor')
|
|
||||||
sign_func = jsonloader.conf.get_signing_backend(backend_name)
|
|
||||||
try:
|
|
||||||
cert_pem = sign_func(csr, ca_conf)
|
|
||||||
except http_status.HTTPException:
|
|
||||||
logger.exception("Failed to sign certificate")
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to sign the certificate")
|
|
||||||
pecan.abort(500, "certificate signing error")
|
|
||||||
|
|
||||||
fingerprint = certificate_fingerprint(cert_pem, 'sha256')
|
|
||||||
if ca_conf.get('output_path') is not None:
|
|
||||||
path = os.path.join(
|
|
||||||
ca_conf['output_path'],
|
|
||||||
'%s.crt' % fingerprint)
|
|
||||||
|
|
||||||
logger.info("Saving certificate to: %s", path)
|
|
||||||
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write(cert_pem)
|
|
||||||
|
|
||||||
return cert_pem, fingerprint
|
|
||||||
|
|
||||||
|
|
||||||
def _run_fixup(name, body, args):
|
|
||||||
"""Parse the fixup tuple, call the fixup, and return the new csr.
|
|
||||||
|
|
||||||
:param name: the fixup name
|
|
||||||
:param body: fixup body, directly from config
|
|
||||||
:param args: additional arguments to pass to the fixup function
|
|
||||||
:return: the fixed csr
|
|
||||||
"""
|
|
||||||
# careful to not modify the master copy of args with local params
|
|
||||||
new_kwargs = args.copy()
|
|
||||||
new_kwargs.update(body)
|
|
||||||
|
|
||||||
# perform the actual check
|
|
||||||
logger.debug("_run_fixup: fixup <%s> with arguments: %s", name, body)
|
|
||||||
try:
|
|
||||||
fixup = jsonloader.conf.get_fixup(name)
|
|
||||||
new_csr = fixup(**new_kwargs)
|
|
||||||
logger.debug("_run_fixup: success: <%s> ", name)
|
|
||||||
return new_csr
|
|
||||||
except Exception:
|
|
||||||
logger.exception("_run_fixup: failed: <%s>", name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def fixup_csr(ra_name, csr, request):
|
|
||||||
"""Apply configured changes to the certificate.
|
|
||||||
|
|
||||||
:param ra_name: registration authority name
|
|
||||||
:param csr: X509 certificate signing request
|
|
||||||
:param request: pecan request
|
|
||||||
"""
|
|
||||||
ra_conf = jsonloader.config_for_registration_authority(ra_name)
|
|
||||||
args = {'csr': csr,
|
|
||||||
'conf': ra_conf,
|
|
||||||
'request': request}
|
|
||||||
|
|
||||||
fixups = ra_conf.get('fixups', {})
|
|
||||||
try:
|
|
||||||
for fixup_name, fixup in fixups.items():
|
|
||||||
new_csr = _run_fixup(fixup_name, fixup, args)
|
|
||||||
if new_csr is None:
|
|
||||||
pecan.abort(500, "Could not finish all required modifications")
|
|
||||||
if not isinstance(new_csr, signing_request.X509Csr):
|
|
||||||
logger.error("Fixup %s returned incorrect object", fixup_name)
|
|
||||||
pecan.abort(500, "Could not finish all required modifications")
|
|
||||||
args['csr'] = new_csr
|
|
||||||
|
|
||||||
except http_status.HTTPInternalServerError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to execute fixups")
|
|
||||||
pecan.abort(500, "Could not finish all required modifications")
|
|
||||||
|
|
||||||
return args['csr']
|
|
@ -1,80 +0,0 @@
|
|||||||
from anchor.asn1 import rfc5652
|
|
||||||
from anchor.asn1 import rfc6402
|
|
||||||
|
|
||||||
from pyasn1.codec.der import decoder
|
|
||||||
from pyasn1 import error
|
|
||||||
|
|
||||||
|
|
||||||
class CMCParsingError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnexpectedContentType(CMCParsingError):
|
|
||||||
def __init__(self, content_type):
|
|
||||||
self.content_type = content_type
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Unexpected content type, got %s" % self.content_type
|
|
||||||
|
|
||||||
|
|
||||||
def _unwrap_signed_data(data):
|
|
||||||
# Since we don't have trust with anyone signing the requests, this
|
|
||||||
# signature is not relevant. The request itself is self-signed which
|
|
||||||
# stops accidents.
|
|
||||||
result = decoder.decode(data, rfc5652.SignedData())[0]
|
|
||||||
return _unwrap_generic(
|
|
||||||
result['encapContentInfo']['eContentType'],
|
|
||||||
result['encapContentInfo']['eContent'])
|
|
||||||
|
|
||||||
|
|
||||||
def _unwrap_content_info(data):
|
|
||||||
result = decoder.decode(data, rfc5652.ContentInfo())[0]
|
|
||||||
return _unwrap_generic(result['contentType'], result['content'])
|
|
||||||
|
|
||||||
|
|
||||||
def _unwrap_generic(content_type, data):
|
|
||||||
unwrapper = CONTENT_TYPES.get(content_type)
|
|
||||||
if unwrapper is None:
|
|
||||||
return (content_type, data)
|
|
||||||
return unwrapper(data)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_wrappers(data):
|
|
||||||
# assume the outer wrapper is contentinfo
|
|
||||||
return _unwrap_content_info(data)
|
|
||||||
|
|
||||||
|
|
||||||
CONTENT_TYPES = {
|
|
||||||
rfc5652.id_ct_contentInfo: _unwrap_content_info,
|
|
||||||
rfc5652.id_signedData: _unwrap_signed_data,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_request(data):
|
|
||||||
try:
|
|
||||||
content_type, data = strip_wrappers(data)
|
|
||||||
except error.PyAsn1Error:
|
|
||||||
raise CMCParsingError("Cannot find valid CMC wrapper")
|
|
||||||
|
|
||||||
if content_type != rfc6402.id_cct_PKIData:
|
|
||||||
raise UnexpectedContentType(content_type)
|
|
||||||
|
|
||||||
pd = decoder.decode(data, rfc6402.PKIData())[0]
|
|
||||||
if len(pd['reqSequence']) == 0:
|
|
||||||
raise CMCParsingError("No certificate requests")
|
|
||||||
if len(pd['reqSequence']) > 1:
|
|
||||||
raise CMCParsingError("Can't handle multiple certificates")
|
|
||||||
req = pd['reqSequence'][0]
|
|
||||||
|
|
||||||
if req.getName() != 'tcr':
|
|
||||||
raise CMCParsingError("Can handle only tagged cert requests")
|
|
||||||
|
|
||||||
return req['tcr']['certificationRequest']
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
with open(sys.argv[1], 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
cert_req = parse_request(data)
|
|
||||||
print(cert_req.prettyPrint())
|
|
@ -1,48 +0,0 @@
|
|||||||
server = {
|
|
||||||
'port': '5016',
|
|
||||||
'host': '0.0.0.0' # nosec
|
|
||||||
}
|
|
||||||
|
|
||||||
# Pecan Application Configurations
|
|
||||||
app = {
|
|
||||||
'root': 'anchor.controllers.RootController',
|
|
||||||
'modules': ['anchor'],
|
|
||||||
# 'static_root': '%(confdir)s/public',
|
|
||||||
# 'template_path': '%(confdir)s/${package}/templates',
|
|
||||||
'debug': True,
|
|
||||||
'errors': {
|
|
||||||
'404': '/error/404',
|
|
||||||
'__force_dict__': True
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
logging = {
|
|
||||||
"formatters": {
|
|
||||||
"simple": {
|
|
||||||
"format": ("%(asctime)s %(levelname)-5.5s [%(name)s][%(process)d/"
|
|
||||||
"%(threadName)s] %(message)s")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"console": {
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"formatter": "simple",
|
|
||||||
"level": "DEBUG"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"anchor": {
|
|
||||||
"level": "DEBUG"
|
|
||||||
},
|
|
||||||
"wsgi": {
|
|
||||||
"level": "INFO"
|
|
||||||
},
|
|
||||||
"oslo_messaging": {
|
|
||||||
"level": "DEBUG"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"handlers": ["console"],
|
|
||||||
"level": "INFO"
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import pecan
|
|
||||||
from pecan import rest
|
|
||||||
from webob import exc as http_status
|
|
||||||
|
|
||||||
from anchor import audit
|
|
||||||
from anchor import auth
|
|
||||||
from anchor import certificate_ops
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RobotsController(rest.RestController):
|
|
||||||
"""Serves /robots.txt that disallows search bots."""
|
|
||||||
|
|
||||||
@pecan.expose(content_type="text/plain")
|
|
||||||
def get(self):
|
|
||||||
return "User-agent: *\nDisallow: /\n"
|
|
||||||
|
|
||||||
|
|
||||||
class GenericInstanceController(rest.RestController):
|
|
||||||
"""Handles requests to /xxx/ra_name."""
|
|
||||||
def __init__(self, ra_name):
|
|
||||||
self.ra_name = ra_name
|
|
||||||
|
|
||||||
|
|
||||||
class SignInstanceController(GenericInstanceController):
|
|
||||||
"""Handles POST requests to /sign/instance."""
|
|
||||||
@pecan.expose(content_type="text/plain")
|
|
||||||
def post(self):
|
|
||||||
ra_name = self.ra_name
|
|
||||||
|
|
||||||
logger.debug("processing signing request in registration authority %s",
|
|
||||||
ra_name)
|
|
||||||
try:
|
|
||||||
auth_result = auth.validate(ra_name,
|
|
||||||
pecan.request.POST.get('user'),
|
|
||||||
pecan.request.POST.get('secret'))
|
|
||||||
audit.emit_auth_event(ra_name, pecan.request.POST.get('user'),
|
|
||||||
auth_result)
|
|
||||||
except http_status.HTTPUnauthorized:
|
|
||||||
audit.emit_auth_event(ra_name, pecan.request.POST.get('user'),
|
|
||||||
None)
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
csr = certificate_ops.parse_csr(pecan.request.POST.get('csr'),
|
|
||||||
pecan.request.POST.get('encoding'))
|
|
||||||
certificate_ops.validate_csr(ra_name, auth_result, csr,
|
|
||||||
pecan.request)
|
|
||||||
csr = certificate_ops.fixup_csr(ra_name, csr, pecan.request)
|
|
||||||
|
|
||||||
cert, fingerprint = certificate_ops.dispatch_sign(ra_name, csr)
|
|
||||||
audit.emit_signing_event(ra_name, pecan.request.POST.get('user'),
|
|
||||||
auth_result, fingerprint=fingerprint)
|
|
||||||
except Exception:
|
|
||||||
audit.emit_signing_event(ra_name, pecan.request.POST.get('user'),
|
|
||||||
auth_result)
|
|
||||||
raise
|
|
||||||
return cert
|
|
||||||
|
|
||||||
|
|
||||||
class CAInstanceController(GenericInstanceController):
|
|
||||||
"""Handles POST requests to /ca/ra_name."""
|
|
||||||
@pecan.expose(content_type="text/plain")
|
|
||||||
def get(self):
|
|
||||||
ra_name = self.ra_name
|
|
||||||
|
|
||||||
return certificate_ops.get_ca(ra_name)
|
|
||||||
|
|
||||||
|
|
||||||
class RAController(rest.RestController):
|
|
||||||
def __init__(self, subcontroller):
|
|
||||||
self._subcontroller = subcontroller
|
|
||||||
|
|
||||||
@pecan.expose()
|
|
||||||
def _lookup(self, ra_name, *remaining):
|
|
||||||
if ra_name in jsonloader.registration_authority_names():
|
|
||||||
return self._subcontroller(ra_name), remaining
|
|
||||||
pecan.abort(404)
|
|
||||||
|
|
||||||
|
|
||||||
class V1Controller(rest.RestController):
|
|
||||||
sign = RAController(SignInstanceController)
|
|
||||||
ca = RAController(CAInstanceController)
|
|
||||||
|
|
||||||
|
|
||||||
class RootController(object):
|
|
||||||
robots = RobotsController()
|
|
||||||
v1 = V1Controller()
|
|
@ -1,2 +0,0 @@
|
|||||||
class ConfigValidationException(Exception):
|
|
||||||
pass
|
|
@ -1,44 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
|
|
||||||
def enforce_alternative_names_present(csr=None, **kwargs):
|
|
||||||
"""Make sure that if CN is set, it's also present in SAN extension."""
|
|
||||||
sans = csr.get_extensions(extension.X509ExtensionSubjectAltName)
|
|
||||||
if sans:
|
|
||||||
san = sans[0]
|
|
||||||
else:
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
|
|
||||||
san_updated = False
|
|
||||||
for cn in csr.get_subject_cn():
|
|
||||||
try:
|
|
||||||
ip = netaddr.IPAddress(cn)
|
|
||||||
if ip not in san.get_ips():
|
|
||||||
san.add_ip(ip)
|
|
||||||
san_updated = True
|
|
||||||
except netaddr.AddrFormatError:
|
|
||||||
if cn not in san.get_dns_ids():
|
|
||||||
san.add_dns_id(cn)
|
|
||||||
san_updated = True
|
|
||||||
|
|
||||||
if san_updated:
|
|
||||||
csr.add_extension(san)
|
|
||||||
return csr
|
|
@ -1,135 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import stevedore
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AnchorConf():
|
|
||||||
|
|
||||||
def __init__(self, logger):
|
|
||||||
'''Attempt to initialize a config dictionary from a JSON file.
|
|
||||||
|
|
||||||
Error out if loading the yaml file fails for any reason.
|
|
||||||
:param logger: Logger to be used in the case of errors
|
|
||||||
:param config_file: The Anchor JSON config file
|
|
||||||
:return: -
|
|
||||||
'''
|
|
||||||
|
|
||||||
self._logger = logger
|
|
||||||
self._config = {}
|
|
||||||
|
|
||||||
def _load_json_file(self, config_file):
|
|
||||||
try:
|
|
||||||
with open(config_file, 'r') as f:
|
|
||||||
return json.load(f)
|
|
||||||
except IOError:
|
|
||||||
logger.error("could not open config file: %s" % config_file)
|
|
||||||
raise
|
|
||||||
except ValueError:
|
|
||||||
logger.error("error parsing config file: %s" % config_file)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def load_file_data(self, config_file):
|
|
||||||
'''Load a config from a file.'''
|
|
||||||
self._config = self._load_json_file(config_file)
|
|
||||||
|
|
||||||
def load_str_data(self, data):
|
|
||||||
'''Load a config from string data.'''
|
|
||||||
self._config = json.loads(data)
|
|
||||||
|
|
||||||
def load_extensions(self):
|
|
||||||
self._signing_backends = stevedore.ExtensionManager(
|
|
||||||
"anchor.signing_backends")
|
|
||||||
self._validators = stevedore.ExtensionManager("anchor.validators")
|
|
||||||
self._authentication = stevedore.ExtensionManager(
|
|
||||||
"anchor.authentication")
|
|
||||||
self._fixups = stevedore.ExtensionManager("anchor.fixups")
|
|
||||||
|
|
||||||
def get_signing_backend(self, name):
|
|
||||||
return self._signing_backends[name].plugin
|
|
||||||
|
|
||||||
def get_validator(self, name):
|
|
||||||
return self._validators[name].plugin
|
|
||||||
|
|
||||||
def get_authentication(self, name):
|
|
||||||
return self._authentication[name].plugin
|
|
||||||
|
|
||||||
def get_fixup(self, name):
|
|
||||||
return self._fixups[name].plugin
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self):
|
|
||||||
'''Property to return the config dictionary
|
|
||||||
|
|
||||||
:return: Config dictionary
|
|
||||||
'''
|
|
||||||
return self._config
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
try:
|
|
||||||
return self._config[name]
|
|
||||||
except KeyError:
|
|
||||||
raise AttributeError("'AnchorConf' object has no attribute '%s'" %
|
|
||||||
name)
|
|
||||||
|
|
||||||
|
|
||||||
conf = AnchorConf(logger)
|
|
||||||
|
|
||||||
|
|
||||||
def config_for_audit():
|
|
||||||
"""Get configuration for a given name."""
|
|
||||||
try:
|
|
||||||
return conf.audit
|
|
||||||
except AttributeError:
|
|
||||||
# it's ok not to configure audit
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def config_for_registration_authority(ra_name):
|
|
||||||
"""Get configuration for a given name."""
|
|
||||||
return conf.registration_authority[ra_name]
|
|
||||||
|
|
||||||
|
|
||||||
def authentication_for_registration_authority(ra_name):
|
|
||||||
"""Get authentication config for a given name.
|
|
||||||
|
|
||||||
This is only supposed to be called after config validation. All the right
|
|
||||||
elements are expected to be in place.
|
|
||||||
"""
|
|
||||||
auth_name = conf.registration_authority[ra_name]['authentication']
|
|
||||||
return conf.authentication[auth_name]
|
|
||||||
|
|
||||||
|
|
||||||
def signing_ca_for_registration_authority(ra_name):
|
|
||||||
"""Get signing ca config for a given name.
|
|
||||||
|
|
||||||
This is only supposed to be called after config validation. All the right
|
|
||||||
elements are expected to be in place.
|
|
||||||
"""
|
|
||||||
ca_name = conf.registration_authority[ra_name]['signing_ca']
|
|
||||||
return conf.signing_ca[ca_name]
|
|
||||||
|
|
||||||
|
|
||||||
def registration_authority_names():
|
|
||||||
"""List the names of supported registration authorities."""
|
|
||||||
return conf.registration_authority.keys()
|
|
@ -1,91 +0,0 @@
|
|||||||
import logging
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from anchor.X509 import certificate
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def config_validator(val):
|
|
||||||
def patcher(f):
|
|
||||||
setattr(f, "_config_validator", val)
|
|
||||||
return f
|
|
||||||
return patcher
|
|
||||||
|
|
||||||
|
|
||||||
class SigningError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def sign_generic(csr, ca_conf, encryption, signer):
|
|
||||||
"""Generate an X.509 certificate and sign it.
|
|
||||||
|
|
||||||
:param csr: X509 certificate signing request
|
|
||||||
:param ca_conf: signing CA configuration
|
|
||||||
:return: signed certificate in PEM format
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ca = certificate.X509Certificate.from_file(
|
|
||||||
ca_conf['cert_path'])
|
|
||||||
except Exception as e:
|
|
||||||
raise SigningError("Cannot load the signing CA: %s" % (e,))
|
|
||||||
|
|
||||||
new_cert = certificate.X509Certificate()
|
|
||||||
new_cert.set_version(2)
|
|
||||||
|
|
||||||
start_time = int(time.time())
|
|
||||||
end_time = start_time + (ca_conf['valid_hours'] * 60 * 60)
|
|
||||||
new_cert.set_not_before(start_time)
|
|
||||||
new_cert.set_not_after(end_time)
|
|
||||||
|
|
||||||
new_cert.set_pubkey(pkey=csr.get_pubkey())
|
|
||||||
new_cert.set_subject(csr.get_subject())
|
|
||||||
new_cert.set_issuer(ca.get_subject())
|
|
||||||
|
|
||||||
serial = int(uuid.uuid4().hex, 16)
|
|
||||||
new_cert.set_serial_number(serial)
|
|
||||||
|
|
||||||
exts = csr.get_extensions()
|
|
||||||
|
|
||||||
ext_i = 0
|
|
||||||
for ext in exts:
|
|
||||||
# this check is separate from standards validator - the signing backend
|
|
||||||
# may know about more/fewer extensions than we do
|
|
||||||
if ext.get_oid() not in extension.EXTENSION_CLASSES.keys():
|
|
||||||
if ext.get_critical():
|
|
||||||
logger.warning("CSR submitted with unknown extension oid %s, "
|
|
||||||
"refusing to sign", ext.get_oid())
|
|
||||||
raise SigningError("Unknown critical extension %s" % (
|
|
||||||
ext.get_oid(),))
|
|
||||||
else:
|
|
||||||
logger.info("CSR submitted with non-critical unknown oid %s, "
|
|
||||||
"not including extension", (ext.get_oid(),))
|
|
||||||
else:
|
|
||||||
logger.info("Adding certificate extension: %i %s", ext_i, str(ext))
|
|
||||||
# authority id will be replaced with current signer
|
|
||||||
# this cannot be a fixup, because they don't get access to the CA
|
|
||||||
if isinstance(ext, extension.X509ExtensionAuthorityKeyId):
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_cert.add_extension(ext, ext_i)
|
|
||||||
ext_i += 1
|
|
||||||
|
|
||||||
ca_exts = ca.get_extensions(extension.X509ExtensionSubjectKeyId)
|
|
||||||
auth_key_id = extension.X509ExtensionAuthorityKeyId()
|
|
||||||
if ca_exts:
|
|
||||||
auth_key_id.set_key_id(ca_exts[0].get_key_id())
|
|
||||||
else:
|
|
||||||
auth_key_id.set_key_id(ca.get_key_id())
|
|
||||||
new_cert.add_extension(auth_key_id, ext_i)
|
|
||||||
|
|
||||||
logger.info("Signing certificate for <%s> with serial <%s>",
|
|
||||||
csr.get_subject(), serial)
|
|
||||||
|
|
||||||
new_cert.sign(encryption, ca_conf['signing_hash'], signer)
|
|
||||||
|
|
||||||
cert_pem = new_cert.as_pem()
|
|
||||||
|
|
||||||
return cert_pem
|
|
@ -1,74 +0,0 @@
|
|||||||
from cryptography.hazmat.primitives.asymmetric import dsa
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
|
|
||||||
from anchor import errors
|
|
||||||
from anchor import signers
|
|
||||||
from anchor import util
|
|
||||||
from anchor.X509 import utils as x509_utils
|
|
||||||
|
|
||||||
|
|
||||||
SIGNER_CONSTRUCTION = {
|
|
||||||
('RSA', 'SHA224'): (lambda key: key.signer(padding.PKCS1v15(),
|
|
||||||
hashes.SHA224())),
|
|
||||||
('RSA', 'SHA256'): (lambda key: key.signer(padding.PKCS1v15(),
|
|
||||||
hashes.SHA256())),
|
|
||||||
('RSA', 'SHA384'): (lambda key: key.signer(padding.PKCS1v15(),
|
|
||||||
hashes.SHA384())),
|
|
||||||
('RSA', 'SHA512'): (lambda key: key.signer(padding.PKCS1v15(),
|
|
||||||
hashes.SHA512())),
|
|
||||||
('DSA', 'SHA224'): (lambda key: key.signer(hashes.SHA224())),
|
|
||||||
('DSA', 'SHA256'): (lambda key: key.signer(hashes.SHA256())),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_validator(name, ca_conf):
|
|
||||||
# mandatory CA settings
|
|
||||||
ca_config_requirements = ["cert_path", "key_path", "output_path",
|
|
||||||
"signing_hash", "valid_hours"]
|
|
||||||
|
|
||||||
for requirement in ca_config_requirements:
|
|
||||||
if requirement not in ca_conf.keys():
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"CA config missing: %s (for signing CA %s)" % (requirement,
|
|
||||||
name))
|
|
||||||
|
|
||||||
# all are specified, check the CA certificate and key are readable with
|
|
||||||
# sane permissions
|
|
||||||
util.check_file_exists(ca_conf['cert_path'])
|
|
||||||
util.check_file_exists(ca_conf['key_path'])
|
|
||||||
|
|
||||||
util.check_file_permissions(ca_conf['key_path'])
|
|
||||||
|
|
||||||
|
|
||||||
def make_signer(key, encryption, md):
|
|
||||||
signer = SIGNER_CONSTRUCTION.get((encryption, md.upper()))
|
|
||||||
if signer is None:
|
|
||||||
raise signers.SigningError(
|
|
||||||
"Unknown hash/encryption combination (%s/%s)" % (md, encryption))
|
|
||||||
signer = signer(key)
|
|
||||||
|
|
||||||
def cryptography_io_signer(to_sign):
|
|
||||||
signer.update(to_sign)
|
|
||||||
return signer.finalize()
|
|
||||||
|
|
||||||
return cryptography_io_signer
|
|
||||||
|
|
||||||
|
|
||||||
@signers.config_validator(conf_validator)
|
|
||||||
def sign(csr, ca_conf):
|
|
||||||
try:
|
|
||||||
key = x509_utils.get_private_key_from_file(ca_conf['key_path'])
|
|
||||||
except Exception as e:
|
|
||||||
raise signers.SigningError("Cannot load the signing CA key: %s" % (e,))
|
|
||||||
|
|
||||||
if isinstance(key, rsa.RSAPrivateKey):
|
|
||||||
encryption = 'RSA'
|
|
||||||
elif isinstance(key, dsa.DSAPrivateKey):
|
|
||||||
encryption = 'DSA'
|
|
||||||
else:
|
|
||||||
raise signers.SigningError("Unknown key type: %s" % (key.__class__,))
|
|
||||||
|
|
||||||
signer = make_signer(key, encryption, ca_conf['signing_hash'])
|
|
||||||
return signers.sign_generic(csr, ca_conf, encryption, signer)
|
|
@ -1,120 +0,0 @@
|
|||||||
from cryptography.hazmat import backends as cio_backends
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
from pyasn1_modules import rfc2315
|
|
||||||
|
|
||||||
from anchor import errors
|
|
||||||
from anchor import signers
|
|
||||||
from anchor import util
|
|
||||||
|
|
||||||
|
|
||||||
def import_pkcs():
|
|
||||||
# separate function for mocking the import failure
|
|
||||||
return __import__("PyKCS11")
|
|
||||||
|
|
||||||
|
|
||||||
def conf_validator(name, ca_conf):
|
|
||||||
# mandatory CA settings
|
|
||||||
ca_config_requirements = ["cert_path", "output_path", "signing_hash",
|
|
||||||
"valid_hours", "slot", "pin", "key_id",
|
|
||||||
"pkcs11_path"]
|
|
||||||
|
|
||||||
for requirement in ca_config_requirements:
|
|
||||||
if requirement not in ca_conf.keys():
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"CA config missing: %s (for signing CA %s)" % (requirement,
|
|
||||||
name))
|
|
||||||
|
|
||||||
# all are specified, check the CA certificate and key are readable with
|
|
||||||
# sane permissions
|
|
||||||
util.check_file_exists(ca_conf['cert_path'])
|
|
||||||
util.check_file_exists(ca_conf['pkcs11_path'])
|
|
||||||
|
|
||||||
# PyKCS11 is an optional dependency
|
|
||||||
try:
|
|
||||||
PyKCS11 = import_pkcs()
|
|
||||||
except ImportError:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"PyKCS11 library cannot be imported")
|
|
||||||
|
|
||||||
# library at the selected path should be possible to load
|
|
||||||
try:
|
|
||||||
pkcslib = PyKCS11.PyKCS11Lib()
|
|
||||||
pkcslib.load(ca_conf['pkcs11_path'])
|
|
||||||
except PyKCS11.PyKCS11Error:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Selected pkcs11 library failed to load")
|
|
||||||
|
|
||||||
slot = ca_conf['slot']
|
|
||||||
slots = pkcslib.getSlotList()
|
|
||||||
if slot not in slots:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Slot %s cannot be found in the pkcs11 store" % slot)
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = pkcslib.openSession(slot)
|
|
||||||
session.login(ca_conf['pin'])
|
|
||||||
except PyKCS11.PyKCS11Error:
|
|
||||||
raise errors.ConfigValidationException(
|
|
||||||
"Cannot login to the selected slot")
|
|
||||||
|
|
||||||
|
|
||||||
def make_signer(key_id, slot, pin, pkcs11_path, md):
|
|
||||||
HASH_OIDS = {
|
|
||||||
'SHA256': asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.2.1'),
|
|
||||||
'SHA384': asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.2.2'),
|
|
||||||
'SHA512': asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.2.3'),
|
|
||||||
'SHA224': asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.2.4'),
|
|
||||||
}
|
|
||||||
|
|
||||||
PyKCS11 = import_pkcs()
|
|
||||||
try:
|
|
||||||
pkcslib = PyKCS11.PyKCS11Lib()
|
|
||||||
pkcslib.load(pkcs11_path)
|
|
||||||
session = pkcslib.openSession(slot)
|
|
||||||
session.login(pin)
|
|
||||||
except PyKCS11.PyKCS11Error:
|
|
||||||
raise signers.SigningError("Could not setup the pkcs11 session")
|
|
||||||
|
|
||||||
keys = session.findObjects((
|
|
||||||
(PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY),
|
|
||||||
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_RSA),
|
|
||||||
(PyKCS11.CKA_SIGN, True),
|
|
||||||
(PyKCS11.CKA_ID, key_id),
|
|
||||||
))
|
|
||||||
if not keys:
|
|
||||||
raise signers.SigningError("Cannot find the requested key")
|
|
||||||
key = keys[0]
|
|
||||||
cio_hash = getattr(hashes, md, None)
|
|
||||||
if not cio_hash:
|
|
||||||
raise signers.SigningError("Requested hash is not supported")
|
|
||||||
|
|
||||||
h = hashes.Hash(cio_hash(), cio_backends.default_backend())
|
|
||||||
|
|
||||||
def pkcs11_signer(to_sign):
|
|
||||||
pkcslib.getInfo # just to keep pkcslib in scope, it's a NOOP
|
|
||||||
h.update(to_sign)
|
|
||||||
di = rfc2315.DigestInfo()
|
|
||||||
di['digestAlgorithm'] = None
|
|
||||||
di['digestAlgorithm'][0] = HASH_OIDS[md]
|
|
||||||
di['digest'] = h.finalize()
|
|
||||||
signature = bytes(session.sign(key, encoder.encode(di),
|
|
||||||
PyKCS11.MechanismRSAPKCS1))
|
|
||||||
session.logout()
|
|
||||||
return signature
|
|
||||||
|
|
||||||
return pkcs11_signer
|
|
||||||
|
|
||||||
|
|
||||||
@signers.config_validator(conf_validator)
|
|
||||||
def sign(csr, ca_conf):
|
|
||||||
slot = ca_conf['slot']
|
|
||||||
pin = ca_conf['pin']
|
|
||||||
pkcs11_path = ca_conf['pkcs11_path']
|
|
||||||
key_id = [int(ca_conf['key_id'][i:i+2], 16) for
|
|
||||||
i in range(0, len(ca_conf['key_id']), 2)]
|
|
||||||
signing_hash = ca_conf['signing_hash'].upper()
|
|
||||||
|
|
||||||
signer = make_signer(key_id, slot, pin, pkcs11_path, signing_hash)
|
|
||||||
return signers.sign_generic(csr, ca_conf, 'RSA', signer)
|
|
@ -1,86 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
|
|
||||||
from anchor import errors
|
|
||||||
|
|
||||||
|
|
||||||
def verify_domain(domain, label_re_comp, allow_wildcards=False):
|
|
||||||
labels = domain.split('.')
|
|
||||||
if labels[-1] == "":
|
|
||||||
# single trailing . is ok, ignore
|
|
||||||
labels.pop(-1)
|
|
||||||
|
|
||||||
for i, label in enumerate(labels):
|
|
||||||
if len(label) > 63:
|
|
||||||
raise ValueError(
|
|
||||||
"domain <%s> it too long (RFC5280/4.2.1.6)" % (domain,))
|
|
||||||
|
|
||||||
# check for wildcard labels, ignore partial-wildcard labels
|
|
||||||
if '*' == label and allow_wildcards:
|
|
||||||
if i != 0:
|
|
||||||
raise ValueError(
|
|
||||||
"domain <%s> has wildcard that's not in the "
|
|
||||||
"left-most label (RFC6125/6.4.3)" % (domain,))
|
|
||||||
else:
|
|
||||||
if label_re_comp.match(label) is None:
|
|
||||||
raise ValueError(
|
|
||||||
"domain <%s> contains invalid characters "
|
|
||||||
"(RFC1034/3.5)" % (domain,))
|
|
||||||
|
|
||||||
|
|
||||||
def extract_pem(data, use_markers=True):
|
|
||||||
"""Extract and unpack PEM data
|
|
||||||
|
|
||||||
Anything between the BEGIN and END lines will be unpacked using base64. The
|
|
||||||
specific BEGIN/END content name is ignored since it's not standard anyway.
|
|
||||||
"""
|
|
||||||
if not isinstance(data, bytes):
|
|
||||||
raise TypeError("data must be bytes")
|
|
||||||
lines = data.splitlines()
|
|
||||||
seen_start = not use_markers
|
|
||||||
b64_content = b""
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith(b"-----END ") and line.endswith(b"-----"):
|
|
||||||
break
|
|
||||||
if seen_start:
|
|
||||||
b64_content += line
|
|
||||||
if line.startswith(b"-----BEGIN ") and line.endswith(b"-----"):
|
|
||||||
seen_start = True
|
|
||||||
|
|
||||||
if not b64_content:
|
|
||||||
return None
|
|
||||||
decoder = getattr(base64, 'decodebytes', base64.decodestring)
|
|
||||||
return decoder(b64_content)
|
|
||||||
|
|
||||||
|
|
||||||
def check_file_permissions(path):
|
|
||||||
# checks that file is owner readable only
|
|
||||||
expected_permissions = (stat.S_IRUSR | stat.S_IFREG) # 0o100400
|
|
||||||
st = os.stat(path)
|
|
||||||
if st.st_mode != expected_permissions:
|
|
||||||
raise errors.ConfigValidationException("CA file: %s has incorrect "
|
|
||||||
"permissions set, expected "
|
|
||||||
"owner readable only" % path)
|
|
||||||
|
|
||||||
|
|
||||||
def check_file_exists(path):
|
|
||||||
if not (os.path.isfile(path) and
|
|
||||||
os.access(path, os.R_OK)):
|
|
||||||
raise errors.ConfigValidationException("could not read file: %s" %
|
|
||||||
path)
|
|
@ -1,84 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor.validators import errors
|
|
||||||
from anchor.validators import internal
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# some validators will be always active because they enforce Anchor design
|
|
||||||
# ideas rather than user configuration
|
|
||||||
ENFORCED_VALIDATORS = [
|
|
||||||
internal.ca_status
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _run_validator(name, validator, body, args):
|
|
||||||
"""Parse the validator tuple, call the validator, and return result.
|
|
||||||
|
|
||||||
:param name: the validator name
|
|
||||||
:param validator: the validator callable
|
|
||||||
:param body: validator body, directly from config
|
|
||||||
:param args: additional arguments to pass to the validator function
|
|
||||||
:return: True on success, else False
|
|
||||||
"""
|
|
||||||
# careful to not modify the master copy of args with local params
|
|
||||||
new_kwargs = args.copy()
|
|
||||||
new_kwargs.update(body)
|
|
||||||
|
|
||||||
# perform the actual check
|
|
||||||
logger.debug("_run_validator: checking <%s> with rules: %s", name, body)
|
|
||||||
try:
|
|
||||||
validator(**new_kwargs)
|
|
||||||
logger.debug("_run_validator: success: <%s> ", name)
|
|
||||||
return True # validator passed b/c no exceptions
|
|
||||||
except errors.ValidationError as e:
|
|
||||||
logger.exception("_run_validator: FAILED: <%s> - %s", name, e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate_csr(ra_name, auth_result, csr, request):
|
|
||||||
"""Validates various aspects of the CSR based on the loaded config.
|
|
||||||
|
|
||||||
The arguments of this method are passed to the underlying validate
|
|
||||||
methods. Therefore, some may be optional, depending on which
|
|
||||||
validation routines are specified in the configuration.
|
|
||||||
|
|
||||||
:param ra_name: name of the registration authority
|
|
||||||
:param auth_result: AuthDetails value from auth.validate
|
|
||||||
:param csr: CSR value from certificate_ops.parse_csr
|
|
||||||
:param request: pecan request object associated with this action
|
|
||||||
"""
|
|
||||||
|
|
||||||
ra_conf = jsonloader.config_for_registration_authority(ra_name)
|
|
||||||
args = {'auth_result': auth_result,
|
|
||||||
'csr': csr,
|
|
||||||
'conf': ra_conf,
|
|
||||||
'request': request}
|
|
||||||
|
|
||||||
# It is ok if the config doesn't have any validators listed
|
|
||||||
valid = {}
|
|
||||||
for validator in ENFORCED_VALIDATORS:
|
|
||||||
vname = validator.__name__
|
|
||||||
valid[vname] = _run_validator(vname, validator, {}, args)
|
|
||||||
|
|
||||||
for vname, options in ra_conf['validators'].items():
|
|
||||||
validator = jsonloader.conf.get_validator(vname)
|
|
||||||
valid[vname] = _run_validator(vname, validator, options, args)
|
|
||||||
|
|
||||||
return valid
|
|
@ -1,313 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from pyasn1.type import univ as pyasn1_univ
|
|
||||||
from pyasn1_modules import rfc2437 # PKCS#1
|
|
||||||
from pyasn1_modules import rfc2459
|
|
||||||
|
|
||||||
from anchor.validators import errors as v_errors
|
|
||||||
from anchor.validators import utils
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name as x509_name
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def common_name(csr, allowed_domains=[], allowed_networks=[], **kwargs):
|
|
||||||
"""Check the CN entry is a known domain.
|
|
||||||
|
|
||||||
Refuse requests for certificates if they contain multiple CN
|
|
||||||
entries, or the domain does not match the list of known suffixes.
|
|
||||||
"""
|
|
||||||
alt_present = any(ext.get_name() == "subjectAltName"
|
|
||||||
for ext in csr.get_extensions())
|
|
||||||
|
|
||||||
CNs = csr.get_subject().get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
|
|
||||||
if len(CNs) > 1:
|
|
||||||
raise v_errors.ValidationError("Too many CNs in the request")
|
|
||||||
|
|
||||||
# rfc2459#section-4.2.1.6 says so
|
|
||||||
if len(CNs) == 0 and not alt_present:
|
|
||||||
raise v_errors.ValidationError("Alt subjects have to exist if the main"
|
|
||||||
" subject doesn't")
|
|
||||||
|
|
||||||
if len(CNs) > 0:
|
|
||||||
cn = utils.csr_require_cn(csr)
|
|
||||||
try:
|
|
||||||
# is it an IP rather than domain?
|
|
||||||
ip = netaddr.IPAddress(cn)
|
|
||||||
if not (utils.check_networks(ip, allowed_networks)):
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Address '%s' not allowed (does not match known networks)"
|
|
||||||
% cn)
|
|
||||||
except netaddr.AddrFormatError:
|
|
||||||
if not (utils.check_domains(cn, allowed_domains)):
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Domain '%s' not allowed (does not match known domains)"
|
|
||||||
% cn)
|
|
||||||
|
|
||||||
|
|
||||||
def alternative_names(csr, allowed_domains=[], **kwargs):
|
|
||||||
"""Check known domain alternative names.
|
|
||||||
|
|
||||||
Refuse requests for certificates if the domain does not match
|
|
||||||
the list of known suffixes, or network ranges.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for _, name in utils.iter_alternative_names(csr, ['DNS']):
|
|
||||||
if not utils.check_domains(name, allowed_domains):
|
|
||||||
raise v_errors.ValidationError("Domain '%s' not allowed (doesn't"
|
|
||||||
" match known domains)" % name)
|
|
||||||
|
|
||||||
|
|
||||||
def alternative_names_ip(csr, allowed_domains=[], allowed_networks=[],
|
|
||||||
**kwargs):
|
|
||||||
"""Check known domain and ip alternative names.
|
|
||||||
|
|
||||||
Refuse requests for certificates if the domain does not match
|
|
||||||
the list of known suffixes, or network ranges.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for name_type, name in utils.iter_alternative_names(csr,
|
|
||||||
['DNS', 'IP Address']):
|
|
||||||
if name_type == 'DNS' and not utils.check_domains(name,
|
|
||||||
allowed_domains):
|
|
||||||
raise v_errors.ValidationError("Domain '%s' not allowed (doesn't"
|
|
||||||
" match known domains)" % name)
|
|
||||||
if name_type == 'IP Address':
|
|
||||||
if not utils.check_networks(name, allowed_networks):
|
|
||||||
raise v_errors.ValidationError("IP '%s' not allowed (doesn't"
|
|
||||||
" match known networks)" % name)
|
|
||||||
|
|
||||||
|
|
||||||
def blacklist_names(csr, domains=[], **kwargs):
|
|
||||||
"""Check for blacklisted names in CN and altNames."""
|
|
||||||
|
|
||||||
if not domains:
|
|
||||||
logger.warning("No domains were configured for the blacklist filter, "
|
|
||||||
"consider disabling the step or providing a list")
|
|
||||||
return
|
|
||||||
|
|
||||||
CNs = csr.get_subject().get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
if len(CNs) > 0:
|
|
||||||
cn = utils.csr_require_cn(csr)
|
|
||||||
if utils.check_domains(cn, domains):
|
|
||||||
raise v_errors.ValidationError("Domain '%s' not allowed "
|
|
||||||
"(CN blacklisted)" % cn)
|
|
||||||
|
|
||||||
for _, name in utils.iter_alternative_names(csr, ['DNS'],
|
|
||||||
fail_other_types=False):
|
|
||||||
if utils.check_domains(name, domains):
|
|
||||||
raise v_errors.ValidationError("Domain '%s' not allowed "
|
|
||||||
"(alt blacklisted)" % name)
|
|
||||||
|
|
||||||
|
|
||||||
def server_group(auth_result=None, csr=None, group_prefixes={}, **kwargs):
|
|
||||||
"""Check Team prefix.
|
|
||||||
|
|
||||||
Make sure that for server names containing a team prefix, the team is
|
|
||||||
verified against the groups the user is a member of.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cn = utils.csr_require_cn(csr)
|
|
||||||
parts = cn.split('-')
|
|
||||||
if len(parts) == 1 or '.' in parts[0]:
|
|
||||||
return # no prefix
|
|
||||||
|
|
||||||
if parts[0] in group_prefixes:
|
|
||||||
if group_prefixes[parts[0]] not in auth_result.groups:
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Server prefix doesn't match user groups")
|
|
||||||
|
|
||||||
|
|
||||||
def extensions(csr=None, allowed_extensions=[], **kwargs):
|
|
||||||
"""Ensure only accepted extensions are used."""
|
|
||||||
exts = csr.get_extensions() or []
|
|
||||||
for ext in exts:
|
|
||||||
if (ext.get_name() not in allowed_extensions and
|
|
||||||
str(ext.get_oid()) not in allowed_extensions):
|
|
||||||
raise v_errors.ValidationError("Extension '%s' not allowed"
|
|
||||||
% ext.get_name())
|
|
||||||
|
|
||||||
|
|
||||||
def key_usage(csr=None, allowed_usage=None, **kwargs):
|
|
||||||
"""Ensure only accepted key usages are specified."""
|
|
||||||
allowed = set(extension.LONG_KEY_USAGE_NAMES.get(x, x) for x in
|
|
||||||
allowed_usage)
|
|
||||||
denied = set()
|
|
||||||
|
|
||||||
for ext in (csr.get_extensions() or []):
|
|
||||||
if isinstance(ext, extension.X509ExtensionKeyUsage):
|
|
||||||
usages = set(ext.get_all_usages())
|
|
||||||
denied = denied | (usages - allowed)
|
|
||||||
if denied:
|
|
||||||
raise v_errors.ValidationError("Found some prohibited key usages: %s"
|
|
||||||
% ', '.join(denied))
|
|
||||||
|
|
||||||
|
|
||||||
def ext_key_usage(csr=None, allowed_usage=None, **kwargs):
|
|
||||||
"""Ensure only accepted extended key usages are specified."""
|
|
||||||
|
|
||||||
# transform all possible names into oids we actually check
|
|
||||||
for i, usage in enumerate(allowed_usage):
|
|
||||||
if usage in extension.EXT_KEY_USAGE_NAMES_INV:
|
|
||||||
allowed_usage[i] = extension.EXT_KEY_USAGE_NAMES_INV[usage]
|
|
||||||
elif usage in extension.EXT_KEY_USAGE_SHORT_NAMES_INV:
|
|
||||||
allowed_usage[i] = extension.EXT_KEY_USAGE_SHORT_NAMES_INV[usage]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
oid = pyasn1_univ.ObjectIdentifier(usage)
|
|
||||||
allowed_usage[i] = oid
|
|
||||||
except Exception:
|
|
||||||
raise v_errors.ValidationError("Unknown usage: %s" % (usage,))
|
|
||||||
|
|
||||||
allowed = set(allowed_usage)
|
|
||||||
denied = set()
|
|
||||||
|
|
||||||
for ext in csr.get_extensions(extension.X509ExtensionExtendedKeyUsage):
|
|
||||||
usages = set(ext.get_all_usages())
|
|
||||||
denied = denied | (usages - allowed)
|
|
||||||
if denied:
|
|
||||||
text_denied = [extension.EXT_KEY_USAGE_SHORT_NAMES.get(x)
|
|
||||||
for x in denied]
|
|
||||||
raise v_errors.ValidationError("Found some prohibited key usages: %s"
|
|
||||||
% ', '.join(text_denied))
|
|
||||||
|
|
||||||
|
|
||||||
def source_cidrs(request=None, cidrs=None, **kwargs):
|
|
||||||
"""Ensure that the request comes from a known source."""
|
|
||||||
for cidr in cidrs:
|
|
||||||
try:
|
|
||||||
r = netaddr.IPNetwork(cidr)
|
|
||||||
if request.client_addr in r:
|
|
||||||
return
|
|
||||||
except netaddr.AddrFormatError:
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Cidr '%s' does not describe a valid network" % cidr)
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"No network matched the request source '%s'" %
|
|
||||||
request.client_addr)
|
|
||||||
|
|
||||||
|
|
||||||
def public_key(csr=None, allowed_keys=None, **kwargs):
|
|
||||||
"""Ensure the public key has the known type and size.
|
|
||||||
|
|
||||||
Configuration provides a dictionary of key types and minimum sizes.
|
|
||||||
"""
|
|
||||||
if allowed_keys is None or not isinstance(allowed_keys, dict):
|
|
||||||
raise v_errors.ValidationError("Allowed keys configuration missing")
|
|
||||||
|
|
||||||
algo = csr.get_public_key_algo()
|
|
||||||
algo_names = {
|
|
||||||
rfc2437.rsaEncryption: 'RSA',
|
|
||||||
rfc2459.id_dsa: 'DSA',
|
|
||||||
}
|
|
||||||
algo_name = algo_names.get(algo)
|
|
||||||
if algo_name is None:
|
|
||||||
raise v_errors.ValidationError("Unknown public key type")
|
|
||||||
|
|
||||||
min_size = allowed_keys.get(algo_name)
|
|
||||||
if min_size is None:
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Key type not allowed (%s)" % (algo_name,))
|
|
||||||
if min_size == 0:
|
|
||||||
# key size is not enforced
|
|
||||||
return
|
|
||||||
|
|
||||||
if csr.get_public_key_size() < min_size:
|
|
||||||
raise v_errors.ValidationError("Key size too small")
|
|
||||||
|
|
||||||
|
|
||||||
def _split_names_by_type(names):
|
|
||||||
"""Identify ips and network ranges in a list of strings."""
|
|
||||||
allowed_domains = []
|
|
||||||
allowed_ips = []
|
|
||||||
allowed_ranges = []
|
|
||||||
for name in names:
|
|
||||||
ip = utils.maybe_ip(name)
|
|
||||||
if ip:
|
|
||||||
allowed_ips.append(ip)
|
|
||||||
continue
|
|
||||||
net = utils.maybe_range(name)
|
|
||||||
if net:
|
|
||||||
allowed_ranges.append(net)
|
|
||||||
continue
|
|
||||||
allowed_domains.append(name)
|
|
||||||
|
|
||||||
return (allowed_domains, allowed_ips, allowed_ranges)
|
|
||||||
|
|
||||||
|
|
||||||
def whitelist_names(csr=None, names=[], allow_cn_id=False, allow_dns_id=False,
|
|
||||||
allow_ip_id=False, allow_wildcard=False, **kwargs):
|
|
||||||
"""Ensure names match the whitelist in the allowed name slots."""
|
|
||||||
|
|
||||||
allowed_domains, allowed_ips, allowed_ranges = _split_names_by_type(names)
|
|
||||||
|
|
||||||
for dns_id in csr.get_subject_dns_ids():
|
|
||||||
if not allow_dns_id:
|
|
||||||
raise v_errors.ValidationError("IP-ID not allowed")
|
|
||||||
valid = False
|
|
||||||
for allowed_domain in allowed_domains:
|
|
||||||
if utils.compare_name_pattern(dns_id, allowed_domain,
|
|
||||||
allow_wildcard):
|
|
||||||
valid = True
|
|
||||||
break
|
|
||||||
if not valid:
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Value `%s` not allowed in DNS-ID" % (dns_id,))
|
|
||||||
|
|
||||||
for ip_id in csr.get_subject_ip_ids():
|
|
||||||
if not allow_ip_id:
|
|
||||||
raise v_errors.ValidationError("IP-ID not allowed")
|
|
||||||
if ip_id in allowed_ips:
|
|
||||||
continue
|
|
||||||
for net in allowed_ranges:
|
|
||||||
if ip_id in net:
|
|
||||||
continue
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Value `%s` not allowed in IP-ID" % (ip_id,))
|
|
||||||
|
|
||||||
for cn_id in csr.get_subject_cn():
|
|
||||||
if not allow_cn_id:
|
|
||||||
raise v_errors.ValidationError("CN-ID not allowed")
|
|
||||||
ip = utils.maybe_ip(cn_id)
|
|
||||||
if ip:
|
|
||||||
# current CN is an ip address
|
|
||||||
if ip in allowed_ips:
|
|
||||||
continue
|
|
||||||
if any((ip in net) for net in allowed_ranges):
|
|
||||||
continue
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Value `%s` not allowed in CN-ID" % (cn_id,))
|
|
||||||
else:
|
|
||||||
# current CN is a domain
|
|
||||||
valid = False
|
|
||||||
for allowed_domain in allowed_domains:
|
|
||||||
if utils.compare_name_pattern(cn_id, allowed_domain,
|
|
||||||
allow_wildcard):
|
|
||||||
valid = True
|
|
||||||
break
|
|
||||||
if valid:
|
|
||||||
continue
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Value `%s` not allowed in CN-ID" % (cn_id,))
|
|
||||||
|
|
||||||
if csr.has_unknown_san_entries():
|
|
||||||
raise v_errors.ValidationError("Request contains unknown SAN entries")
|
|
@ -1,16 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
|
||||||
pass
|
|
@ -1,42 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""Anchor internally used validators. They should not be exposed to the
|
|
||||||
users.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from anchor.validators import errors as v_errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
|
|
||||||
def ca_status(csr=None, **kwargs):
|
|
||||||
"""Ensure the request hasn't got the CA or cert signing flag.
|
|
||||||
|
|
||||||
This validation applies both to the BasicConstraints extension and to the
|
|
||||||
KeyUsage extension.
|
|
||||||
"""
|
|
||||||
basic_constraint = csr.get_extensions(
|
|
||||||
extension.X509ExtensionBasicConstraints)
|
|
||||||
if basic_constraint:
|
|
||||||
if basic_constraint[0].get_ca():
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Request is for a CA certificate")
|
|
||||||
|
|
||||||
key_usage = csr.get_extensions(extension.X509ExtensionKeyUsage)
|
|
||||||
if key_usage:
|
|
||||||
if key_usage[0].get_usage('keyCertSign'):
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Request contains certificates signing usage flag")
|
|
||||||
if key_usage[0].get_usage('cRLSign'):
|
|
||||||
raise v_errors.ValidationError(
|
|
||||||
"Request contains CRL signing usage flag")
|
|
@ -1,111 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Standards based validator.
|
|
||||||
|
|
||||||
This module provides validators which should be included in all deployments and
|
|
||||||
which are based directly on the standards documents. All exceptions must have a
|
|
||||||
comment referencing the document / section they're based on.
|
|
||||||
|
|
||||||
All the rules are pulled into a single validator: ``standards_compliance``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from anchor import util
|
|
||||||
from anchor.validators import errors
|
|
||||||
from anchor.X509 import errors as x509_errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
# RFC1034 allows a simple " " too, but it's not allowed in certificates, so it
|
|
||||||
# will not match
|
|
||||||
#
|
|
||||||
# This pattern is RFC1034 compatible otherwise, but since newer RFCs actually
|
|
||||||
# allow any binary value as the domain label, some operators may want to relax
|
|
||||||
# the pattern in the configuration, for example to allow leading digits or
|
|
||||||
# hyphens.
|
|
||||||
def standards_compliance(csr=None, label_re="^[a-z](?:[-a-z0-9]*[a-z0-9])?$",
|
|
||||||
**kwargs):
|
|
||||||
"""Collection of separate cases of standards validation."""
|
|
||||||
_no_extension_duplicates(csr)
|
|
||||||
_critical_flags(csr)
|
|
||||||
_valid_domains(csr, label_re)
|
|
||||||
_csr_signature(csr)
|
|
||||||
# TODO(stan): validate srv/uri, distinct DNs, email format, identity keys
|
|
||||||
|
|
||||||
|
|
||||||
def _no_extension_duplicates(csr):
|
|
||||||
"""Only one extension with a given oid is allowed.
|
|
||||||
|
|
||||||
See RFC5280 section 4.2
|
|
||||||
"""
|
|
||||||
seen_oids = set()
|
|
||||||
for ext in csr.get_extensions():
|
|
||||||
oid = ext.get_oid()
|
|
||||||
if oid in seen_oids:
|
|
||||||
raise errors.ValidationError(
|
|
||||||
"Duplicate extension with oid %s (RFC5280/4.2)" % oid)
|
|
||||||
seen_oids.add(oid)
|
|
||||||
|
|
||||||
|
|
||||||
def _critical_flags(csr):
|
|
||||||
"""Various rules define whether critical flag is required."""
|
|
||||||
for ext in csr.get_extensions():
|
|
||||||
if isinstance(ext, extension.X509ExtensionSubjectAltName):
|
|
||||||
if len(csr.get_subject()) == 0 and not ext.get_critical():
|
|
||||||
raise errors.ValidationError(
|
|
||||||
"SAN must be critical if subject is empty "
|
|
||||||
"(RFC5280/4.1.2.6)")
|
|
||||||
if isinstance(ext, extension.X509ExtensionBasicConstraints):
|
|
||||||
if not ext.get_critical():
|
|
||||||
raise errors.ValidationError(
|
|
||||||
"Basic constraints has to be marked critical "
|
|
||||||
"(RFC5280/4.1.2.9)")
|
|
||||||
|
|
||||||
|
|
||||||
def _valid_domains(csr, label_re="^[a-z](?:[-a-z0-9]*[a-z0-9])?$"):
|
|
||||||
"""Format of the domin names
|
|
||||||
|
|
||||||
See RFC5280 section 4.2.1.6 / RFC6125 / RFC1034
|
|
||||||
"""
|
|
||||||
sans = csr.get_extensions(extension.X509ExtensionSubjectAltName)
|
|
||||||
if not sans:
|
|
||||||
return
|
|
||||||
|
|
||||||
label_re_comp = re.compile(label_re, re.IGNORECASE)
|
|
||||||
|
|
||||||
ext = sans[0]
|
|
||||||
for domain in ext.get_dns_ids():
|
|
||||||
try:
|
|
||||||
util.verify_domain(domain, label_re_comp, allow_wildcards=True)
|
|
||||||
except ValueError as e:
|
|
||||||
raise errors.ValidationError(str(e))
|
|
||||||
|
|
||||||
|
|
||||||
def _csr_signature(csr):
|
|
||||||
"""Ensure that the CSR has a valid self-signature."""
|
|
||||||
# first check for deprecated signatures - verification on those will fail
|
|
||||||
algo = csr.uses_deprecated_algorithm()
|
|
||||||
if algo:
|
|
||||||
raise errors.ValidationError("CSR rejected for using a known broken, "
|
|
||||||
"or deprecated algorithm: %s" % algo)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not csr.verify():
|
|
||||||
raise errors.ValidationError("Signature on the CSR is not valid")
|
|
||||||
except x509_errors.X509Error:
|
|
||||||
raise errors.ValidationError("Signature on the CSR is not valid")
|
|
@ -1,137 +0,0 @@
|
|||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
|
|
||||||
from anchor.validators import errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def csr_require_cn(csr):
|
|
||||||
cns = csr.get_subject_cn()
|
|
||||||
if not cns:
|
|
||||||
raise errors.ValidationError("CSR is lacking a CN in the Subject")
|
|
||||||
if len(cns) > 1:
|
|
||||||
raise errors.ValidationError("CSR has too many CN entries")
|
|
||||||
return cns[0]
|
|
||||||
|
|
||||||
|
|
||||||
def check_domains(domain, allowed_domains):
|
|
||||||
if allowed_domains:
|
|
||||||
if not any(domain.endswith(suffix) for suffix in allowed_domains):
|
|
||||||
# no domain matched
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# no valid domains were provided, so we can't make any assertions
|
|
||||||
logger.warning("No domains were configured for validation. Anchor "
|
|
||||||
"will issue certificates for any domain, this is not a "
|
|
||||||
"recommended configuration for production environments")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def iter_alternative_names(csr, types, fail_other_types=True):
|
|
||||||
for ext in csr.get_extensions():
|
|
||||||
if isinstance(ext, extension.X509ExtensionSubjectAltName):
|
|
||||||
# TODO(stan): fail on other types
|
|
||||||
if 'DNS' in types:
|
|
||||||
for dns_id in ext.get_dns_ids():
|
|
||||||
yield ('DNS', dns_id)
|
|
||||||
if 'IP Address' in types:
|
|
||||||
for ip in ext.get_ips():
|
|
||||||
yield ('IP Address', ip)
|
|
||||||
|
|
||||||
|
|
||||||
def check_networks(ip, allowed_networks):
|
|
||||||
"""Check the IP is within an allowed network."""
|
|
||||||
if not isinstance(ip, netaddr.IPAddress):
|
|
||||||
raise TypeError("ip must be a netaddr ip address")
|
|
||||||
|
|
||||||
if not allowed_networks:
|
|
||||||
# no valid networks were provided, so we can't make any assertions
|
|
||||||
logger.warning("No valid network IP ranges were given, skipping")
|
|
||||||
return True
|
|
||||||
|
|
||||||
if any(ip in netaddr.IPNetwork(net) for net in allowed_networks):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def maybe_ip(name):
|
|
||||||
try:
|
|
||||||
return netaddr.IPAddress(name)
|
|
||||||
except ValueError:
|
|
||||||
# happens when trying to pass a subnet prefix
|
|
||||||
return None
|
|
||||||
except netaddr.AddrFormatError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def maybe_range(name):
|
|
||||||
try:
|
|
||||||
return netaddr.IPNetwork(name)
|
|
||||||
except netaddr.AddrFormatError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def compare_name_pattern(name, pattern, allow_wildcard):
|
|
||||||
"""Compare domain names including wildcards.
|
|
||||||
|
|
||||||
Wilcard means local Anchor wildcard which is '%'. This allows the pattern
|
|
||||||
to match an actual wildcard entry (*) or name which can be expanded.
|
|
||||||
Partial matches using % are allowed, but % matches only in one label.
|
|
||||||
|
|
||||||
In practice that means:
|
|
||||||
name: pattern: wildard: result:
|
|
||||||
example.com example.com - match
|
|
||||||
*.example.com *.example.com - match
|
|
||||||
*.example.com %.example.com true match
|
|
||||||
*.example.com %.example.com false fail
|
|
||||||
abc.example.com %.example.com - match
|
|
||||||
abc.def.example.com %.example.com - fail
|
|
||||||
abc.def.example.com %.%.example.com - match
|
|
||||||
host-123.example.com host-%.example.com - match
|
|
||||||
"""
|
|
||||||
|
|
||||||
name_labels = name.split('.')
|
|
||||||
patt_labels = pattern.split('.')
|
|
||||||
if len(name_labels) != len(patt_labels):
|
|
||||||
return False
|
|
||||||
|
|
||||||
for nl, pl in zip(name_labels, patt_labels):
|
|
||||||
if '%' in pl:
|
|
||||||
pre, _, post = pl.partition('%')
|
|
||||||
|
|
||||||
if not nl.startswith(pre):
|
|
||||||
return False
|
|
||||||
nl = nl[len(pre):] # strip the pre part of pattern
|
|
||||||
|
|
||||||
if not nl.endswith(post):
|
|
||||||
return False
|
|
||||||
if len(post) > 0:
|
|
||||||
nl = nl[:-len(post)] # strip the post part of pattern
|
|
||||||
|
|
||||||
if '*' in nl and not allow_wildcard:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if nl != pl:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
@ -1,10 +0,0 @@
|
|||||||
Files in "anchor/asn1" have been generated from the asn1 modules in "asn"
|
|
||||||
directory using asn1ate (https://github.com/kimgr/asn1ate/)
|
|
||||||
|
|
||||||
They can be regenerated by:
|
|
||||||
- running asn1ate on each module
|
|
||||||
- putting .i and .e results together (implicit / explicit modules)
|
|
||||||
- removing faked imports
|
|
||||||
- linking missing classes to other rfcXXXX files
|
|
||||||
|
|
||||||
There's currently no fully automatic way to do it.
|
|
@ -1,628 +0,0 @@
|
|||||||
PKIX1Explicit88 { iso(1) identified-organization(3) dod(6) internet(1)
|
|
||||||
security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-explicit(18) }
|
|
||||||
|
|
||||||
DEFINITIONS EXPLICIT TAGS ::=
|
|
||||||
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- EXPORTS ALL --
|
|
||||||
|
|
||||||
-- IMPORTS NONE --
|
|
||||||
|
|
||||||
-- UNIVERSAL Types defined in 1993 and 1998 ASN.1
|
|
||||||
-- and required by this specification
|
|
||||||
|
|
||||||
--UniversalString ::= [UNIVERSAL 28] IMPLICIT OCTET STRING
|
|
||||||
-- UniversalString is defined in ASN.1:1993
|
|
||||||
|
|
||||||
--BMPString ::= [UNIVERSAL 30] IMPLICIT OCTET STRING
|
|
||||||
-- BMPString is the subtype of UniversalString and models
|
|
||||||
-- the Basic Multilingual Plane of ISO/IEC/ITU 10646-1
|
|
||||||
|
|
||||||
--UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
|
|
||||||
-- The content of this type conforms to RFC 2279.
|
|
||||||
|
|
||||||
-- PKIX specific OIDs
|
|
||||||
|
|
||||||
id-pkix OBJECT IDENTIFIER ::=
|
|
||||||
{ iso(1) identified-organization(3) dod(6) internet(1)
|
|
||||||
security(5) mechanisms(5) pkix(7) }
|
|
||||||
|
|
||||||
-- PKIX arcs
|
|
||||||
|
|
||||||
id-pe OBJECT IDENTIFIER ::= { id-pkix 1 }
|
|
||||||
-- arc for private certificate extensions
|
|
||||||
id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
|
|
||||||
-- arc for policy qualifier types
|
|
||||||
id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
|
|
||||||
-- arc for extended key purpose OIDS
|
|
||||||
id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
|
|
||||||
-- arc for access descriptors
|
|
||||||
|
|
||||||
-- policyQualifierIds for Internet policy qualifiers
|
|
||||||
|
|
||||||
id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
|
|
||||||
-- OID for CPS qualifier
|
|
||||||
id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
|
|
||||||
-- OID for user notice qualifier
|
|
||||||
|
|
||||||
-- access descriptor definitions
|
|
||||||
|
|
||||||
id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
|
|
||||||
id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
|
|
||||||
id-ad-timeStamping OBJECT IDENTIFIER ::= { id-ad 3 }
|
|
||||||
id-ad-caRepository OBJECT IDENTIFIER ::= { id-ad 5 }
|
|
||||||
|
|
||||||
-- attribute data types
|
|
||||||
|
|
||||||
Attribute ::= SEQUENCE {
|
|
||||||
type AttributeType,
|
|
||||||
values SET OF AttributeValue }
|
|
||||||
-- at least one value is required
|
|
||||||
|
|
||||||
AttributeType ::= OBJECT IDENTIFIER
|
|
||||||
|
|
||||||
AttributeValue ::= ANY
|
|
||||||
|
|
||||||
AttributeTypeAndValue ::= SEQUENCE {
|
|
||||||
type AttributeType,
|
|
||||||
value AttributeValue }
|
|
||||||
|
|
||||||
-- suggested naming attributes: Definition of the following
|
|
||||||
-- information object set may be augmented to meet local
|
|
||||||
-- requirements. Note that deleting members of the set may
|
|
||||||
-- prevent interoperability with conforming implementations.
|
|
||||||
-- presented in pairs: the AttributeType followed by the
|
|
||||||
-- type definition for the corresponding AttributeValue
|
|
||||||
--Arc for standard naming attributes
|
|
||||||
id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520name
|
|
||||||
|
|
||||||
id-at-name AttributeType ::= { id-at 41 }
|
|
||||||
id-at-surname AttributeType ::= { id-at 4 }
|
|
||||||
id-at-givenName AttributeType ::= { id-at 42 }
|
|
||||||
id-at-initials AttributeType ::= { id-at 43 }
|
|
||||||
id-at-generationQualifier AttributeType ::= { id-at 44 }
|
|
||||||
|
|
||||||
X520name ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-name)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-name)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-name)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-name)),
|
|
||||||
bmpString BMPString (SIZE (1..ub-name)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520CommonName
|
|
||||||
|
|
||||||
id-at-commonName AttributeType ::= { id-at 3 }
|
|
||||||
|
|
||||||
X520CommonName ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-common-name)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-common-name)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-common-name)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-common-name)),
|
|
||||||
bmpString BMPString (SIZE (1..ub-common-name)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520LocalityName
|
|
||||||
|
|
||||||
id-at-localityName AttributeType ::= { id-at 7 }
|
|
||||||
|
|
||||||
X520LocalityName ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-locality-name)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-locality-name)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-locality-name)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-locality-name)),
|
|
||||||
bmpString BMPString (SIZE (1..ub-locality-name)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520StateOrProvinceName
|
|
||||||
|
|
||||||
id-at-stateOrProvinceName AttributeType ::= { id-at 8 }
|
|
||||||
|
|
||||||
X520StateOrProvinceName ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-state-name)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-state-name)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-state-name)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-state-name)),
|
|
||||||
bmpString BMPString (SIZE(1..ub-state-name)) }
|
|
||||||
|
|
||||||
|
|
||||||
-- Naming attributes of type X520OrganizationName
|
|
||||||
|
|
||||||
id-at-organizationName AttributeType ::= { id-at 10 }
|
|
||||||
|
|
||||||
X520OrganizationName ::= CHOICE {
|
|
||||||
teletexString TeletexString
|
|
||||||
(SIZE (1..ub-organization-name)),
|
|
||||||
printableString PrintableString
|
|
||||||
(SIZE (1..ub-organization-name)),
|
|
||||||
universalString UniversalString
|
|
||||||
(SIZE (1..ub-organization-name)),
|
|
||||||
utf8String UTF8String
|
|
||||||
(SIZE (1..ub-organization-name)),
|
|
||||||
bmpString BMPString
|
|
||||||
(SIZE (1..ub-organization-name)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520OrganizationalUnitName
|
|
||||||
|
|
||||||
id-at-organizationalUnitName AttributeType ::= { id-at 11 }
|
|
||||||
|
|
||||||
X520OrganizationalUnitName ::= CHOICE {
|
|
||||||
teletexString TeletexString
|
|
||||||
(SIZE (1..ub-organizational-unit-name)),
|
|
||||||
printableString PrintableString
|
|
||||||
(SIZE (1..ub-organizational-unit-name)),
|
|
||||||
universalString UniversalString
|
|
||||||
(SIZE (1..ub-organizational-unit-name)),
|
|
||||||
utf8String UTF8String
|
|
||||||
(SIZE (1..ub-organizational-unit-name)),
|
|
||||||
bmpString BMPString
|
|
||||||
(SIZE (1..ub-organizational-unit-name)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520Title
|
|
||||||
|
|
||||||
id-at-title AttributeType ::= { id-at 12 }
|
|
||||||
|
|
||||||
X520Title ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-title)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-title)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-title)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-title)),
|
|
||||||
bmpString BMPString (SIZE (1..ub-title)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type X520dnQualifier
|
|
||||||
|
|
||||||
id-at-dnQualifier AttributeType ::= { id-at 46 }
|
|
||||||
|
|
||||||
X520dnQualifier ::= PrintableString
|
|
||||||
|
|
||||||
-- Naming attributes of type X520countryName (digraph from IS 3166)
|
|
||||||
|
|
||||||
id-at-countryName AttributeType ::= { id-at 6 }
|
|
||||||
|
|
||||||
X520countryName ::= PrintableString (SIZE (2))
|
|
||||||
|
|
||||||
-- Naming attributes of type X520SerialNumber
|
|
||||||
|
|
||||||
id-at-serialNumber AttributeType ::= { id-at 5 }
|
|
||||||
|
|
||||||
X520SerialNumber ::= PrintableString (SIZE (1..ub-serial-number))
|
|
||||||
|
|
||||||
-- Naming attributes of type X520Pseudonym
|
|
||||||
|
|
||||||
id-at-pseudonym AttributeType ::= { id-at 65 }
|
|
||||||
|
|
||||||
X520Pseudonym ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..ub-pseudonym)),
|
|
||||||
printableString PrintableString (SIZE (1..ub-pseudonym)),
|
|
||||||
universalString UniversalString (SIZE (1..ub-pseudonym)),
|
|
||||||
utf8String UTF8String (SIZE (1..ub-pseudonym)),
|
|
||||||
bmpString BMPString (SIZE (1..ub-pseudonym)) }
|
|
||||||
|
|
||||||
-- Naming attributes of type DomainComponent (from RFC 2247)
|
|
||||||
|
|
||||||
id-domainComponent AttributeType ::=
|
|
||||||
{ 0 9 2342 19200300 100 1 25 }
|
|
||||||
|
|
||||||
DomainComponent ::= IA5String
|
|
||||||
|
|
||||||
-- Legacy attributes
|
|
||||||
|
|
||||||
pkcs-9 OBJECT IDENTIFIER ::=
|
|
||||||
{ iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
|
|
||||||
|
|
||||||
id-emailAddress AttributeType ::= { pkcs-9 1 }
|
|
||||||
|
|
||||||
EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length))
|
|
||||||
|
|
||||||
-- naming data types --
|
|
||||||
|
|
||||||
Name ::= CHOICE { -- only one possibility for now --
|
|
||||||
rdnSequence RDNSequence }
|
|
||||||
|
|
||||||
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
|
||||||
|
|
||||||
DistinguishedName ::= RDNSequence
|
|
||||||
|
|
||||||
|
|
||||||
RelativeDistinguishedName ::=
|
|
||||||
SET SIZE (1 .. MAX) OF AttributeTypeAndValue
|
|
||||||
|
|
||||||
-- Directory string type --
|
|
||||||
|
|
||||||
DirectoryString ::= CHOICE {
|
|
||||||
teletexString TeletexString (SIZE (1..MAX)),
|
|
||||||
printableString PrintableString (SIZE (1..MAX)),
|
|
||||||
universalString UniversalString (SIZE (1..MAX)),
|
|
||||||
utf8String UTF8String (SIZE (1..MAX)),
|
|
||||||
bmpString BMPString (SIZE (1..MAX)) }
|
|
||||||
|
|
||||||
-- certificate and CRL specific structures begin here
|
|
||||||
|
|
||||||
Certificate ::= SEQUENCE {
|
|
||||||
tbsCertificate TBSCertificate,
|
|
||||||
signatureAlgorithm AlgorithmIdentifier,
|
|
||||||
signature BIT STRING }
|
|
||||||
|
|
||||||
TBSCertificate ::= SEQUENCE {
|
|
||||||
version [0] Version DEFAULT v1,
|
|
||||||
serialNumber CertificateSerialNumber,
|
|
||||||
signature AlgorithmIdentifier,
|
|
||||||
issuer Name,
|
|
||||||
validity Validity,
|
|
||||||
subject Name,
|
|
||||||
subjectPublicKeyInfo SubjectPublicKeyInfo,
|
|
||||||
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
||||||
-- If present, version MUST be v2 or v3
|
|
||||||
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
||||||
-- If present, version MUST be v2 or v3
|
|
||||||
extensions [3] Extensions OPTIONAL
|
|
||||||
-- If present, version MUST be v3 -- }
|
|
||||||
|
|
||||||
Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
|
||||||
|
|
||||||
CertificateSerialNumber ::= INTEGER
|
|
||||||
|
|
||||||
Validity ::= SEQUENCE {
|
|
||||||
notBefore Time,
|
|
||||||
notAfter Time }
|
|
||||||
|
|
||||||
Time ::= CHOICE {
|
|
||||||
utcTime UTCTime,
|
|
||||||
generalTime GeneralizedTime }
|
|
||||||
|
|
||||||
UniqueIdentifier ::= BIT STRING
|
|
||||||
|
|
||||||
|
|
||||||
SubjectPublicKeyInfo ::= SEQUENCE {
|
|
||||||
algorithm AlgorithmIdentifier,
|
|
||||||
subjectPublicKey BIT STRING }
|
|
||||||
|
|
||||||
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
||||||
|
|
||||||
Extension ::= SEQUENCE {
|
|
||||||
extnID OBJECT IDENTIFIER,
|
|
||||||
critical BOOLEAN DEFAULT FALSE,
|
|
||||||
extnValue OCTET STRING }
|
|
||||||
|
|
||||||
-- CRL structures
|
|
||||||
|
|
||||||
CertificateList ::= SEQUENCE {
|
|
||||||
tbsCertList TBSCertList,
|
|
||||||
signatureAlgorithm AlgorithmIdentifier,
|
|
||||||
signature BIT STRING }
|
|
||||||
|
|
||||||
TBSCertList ::= SEQUENCE {
|
|
||||||
version Version OPTIONAL,
|
|
||||||
-- if present, MUST be v2
|
|
||||||
signature AlgorithmIdentifier,
|
|
||||||
issuer Name,
|
|
||||||
thisUpdate Time,
|
|
||||||
nextUpdate Time OPTIONAL,
|
|
||||||
revokedCertificates SEQUENCE OF SEQUENCE {
|
|
||||||
userCertificate CertificateSerialNumber,
|
|
||||||
revocationDate Time,
|
|
||||||
crlEntryExtensions Extensions OPTIONAL
|
|
||||||
-- if present, MUST be v2
|
|
||||||
} OPTIONAL,
|
|
||||||
crlExtensions [0] Extensions OPTIONAL }
|
|
||||||
-- if present, MUST be v2
|
|
||||||
|
|
||||||
-- Version, Time, CertificateSerialNumber, and Extensions were
|
|
||||||
-- defined earlier for use in the certificate structure
|
|
||||||
|
|
||||||
AlgorithmIdentifier ::= SEQUENCE {
|
|
||||||
algorithm OBJECT IDENTIFIER,
|
|
||||||
parameters ANY DEFINED BY algorithm OPTIONAL }
|
|
||||||
-- contains a value of the type
|
|
||||||
-- registered for use with the
|
|
||||||
-- algorithm object identifier value
|
|
||||||
|
|
||||||
-- X.400 address syntax starts here
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ORAddress ::= SEQUENCE {
|
|
||||||
built-in-standard-attributes BuiltInStandardAttributes,
|
|
||||||
built-in-domain-defined-attributes
|
|
||||||
BuiltInDomainDefinedAttributes OPTIONAL,
|
|
||||||
-- see also teletex-domain-defined-attributes
|
|
||||||
extension-attributes ExtensionAttributes OPTIONAL }
|
|
||||||
|
|
||||||
-- Built-in Standard Attributes
|
|
||||||
|
|
||||||
BuiltInStandardAttributes ::= SEQUENCE {
|
|
||||||
country-name CountryName OPTIONAL,
|
|
||||||
administration-domain-name AdministrationDomainName OPTIONAL,
|
|
||||||
network-address [0] IMPLICIT NetworkAddress OPTIONAL,
|
|
||||||
-- see also extended-network-address
|
|
||||||
terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL,
|
|
||||||
private-domain-name [2] PrivateDomainName OPTIONAL,
|
|
||||||
organization-name [3] IMPLICIT OrganizationName OPTIONAL,
|
|
||||||
-- see also teletex-organization-name
|
|
||||||
numeric-user-identifier [4] IMPLICIT NumericUserIdentifier
|
|
||||||
OPTIONAL,
|
|
||||||
personal-name [5] IMPLICIT PersonalName OPTIONAL,
|
|
||||||
-- see also teletex-personal-name
|
|
||||||
organizational-unit-names [6] IMPLICIT OrganizationalUnitNames
|
|
||||||
OPTIONAL }
|
|
||||||
-- see also teletex-organizational-unit-names
|
|
||||||
|
|
||||||
CountryName ::= [APPLICATION 1] CHOICE {
|
|
||||||
x121-dcc-code NumericString
|
|
||||||
(SIZE (ub-country-name-numeric-length)),
|
|
||||||
iso-3166-alpha2-code PrintableString
|
|
||||||
(SIZE (ub-country-name-alpha-length)) }
|
|
||||||
|
|
||||||
AdministrationDomainName ::= [APPLICATION 2] CHOICE {
|
|
||||||
numeric NumericString (SIZE (0..ub-domain-name-length)),
|
|
||||||
printable PrintableString (SIZE (0..ub-domain-name-length)) }
|
|
||||||
|
|
||||||
NetworkAddress ::= X121Address -- see also extended-network-address
|
|
||||||
|
|
||||||
X121Address ::= NumericString (SIZE (1..ub-x121-address-length))
|
|
||||||
|
|
||||||
TerminalIdentifier ::= PrintableString (SIZE
|
|
||||||
(1..ub-terminal-id-length))
|
|
||||||
|
|
||||||
PrivateDomainName ::= CHOICE {
|
|
||||||
numeric NumericString (SIZE (1..ub-domain-name-length)),
|
|
||||||
printable PrintableString (SIZE (1..ub-domain-name-length)) }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
OrganizationName ::= PrintableString
|
|
||||||
(SIZE (1..ub-organization-name-length))
|
|
||||||
-- see also teletex-organization-name
|
|
||||||
|
|
||||||
NumericUserIdentifier ::= NumericString
|
|
||||||
(SIZE (1..ub-numeric-user-id-length))
|
|
||||||
|
|
||||||
PersonalName ::= SET {
|
|
||||||
surname [0] IMPLICIT PrintableString
|
|
||||||
(SIZE (1..ub-surname-length)),
|
|
||||||
given-name [1] IMPLICIT PrintableString
|
|
||||||
(SIZE (1..ub-given-name-length)) OPTIONAL,
|
|
||||||
initials [2] IMPLICIT PrintableString
|
|
||||||
(SIZE (1..ub-initials-length)) OPTIONAL,
|
|
||||||
generation-qualifier [3] IMPLICIT PrintableString
|
|
||||||
(SIZE (1..ub-generation-qualifier-length))
|
|
||||||
OPTIONAL }
|
|
||||||
-- see also teletex-personal-name
|
|
||||||
|
|
||||||
OrganizationalUnitNames ::= SEQUENCE SIZE (1..ub-organizational-units)
|
|
||||||
OF OrganizationalUnitName
|
|
||||||
-- see also teletex-organizational-unit-names
|
|
||||||
|
|
||||||
OrganizationalUnitName ::= PrintableString (SIZE
|
|
||||||
(1..ub-organizational-unit-name-length))
|
|
||||||
|
|
||||||
-- Built-in Domain-defined Attributes
|
|
||||||
|
|
||||||
BuiltInDomainDefinedAttributes ::= SEQUENCE SIZE
|
|
||||||
(1..ub-domain-defined-attributes) OF
|
|
||||||
BuiltInDomainDefinedAttribute
|
|
||||||
|
|
||||||
BuiltInDomainDefinedAttribute ::= SEQUENCE {
|
|
||||||
type PrintableString (SIZE
|
|
||||||
(1..ub-domain-defined-attribute-type-length)),
|
|
||||||
value PrintableString (SIZE
|
|
||||||
(1..ub-domain-defined-attribute-value-length)) }
|
|
||||||
|
|
||||||
-- Extension Attributes
|
|
||||||
|
|
||||||
ExtensionAttributes ::= SET SIZE (1..ub-extension-attributes) OF
|
|
||||||
ExtensionAttribute
|
|
||||||
|
|
||||||
ExtensionAttribute ::= SEQUENCE {
|
|
||||||
extension-attribute-type [0] IMPLICIT INTEGER
|
|
||||||
(0..ub-extension-attributes),
|
|
||||||
extension-attribute-value [1]
|
|
||||||
ANY DEFINED BY extension-attribute-type }
|
|
||||||
|
|
||||||
-- Extension types and attribute values
|
|
||||||
|
|
||||||
common-name INTEGER ::= 1
|
|
||||||
|
|
||||||
CommonName ::= PrintableString (SIZE (1..ub-common-name-length))
|
|
||||||
|
|
||||||
teletex-common-name INTEGER ::= 2
|
|
||||||
|
|
||||||
TeletexCommonName ::= TeletexString (SIZE (1..ub-common-name-length))
|
|
||||||
|
|
||||||
teletex-organization-name INTEGER ::= 3
|
|
||||||
|
|
||||||
TeletexOrganizationName ::=
|
|
||||||
TeletexString (SIZE (1..ub-organization-name-length))
|
|
||||||
|
|
||||||
teletex-personal-name INTEGER ::= 4
|
|
||||||
|
|
||||||
TeletexPersonalName ::= SET {
|
|
||||||
surname [0] IMPLICIT TeletexString
|
|
||||||
(SIZE (1..ub-surname-length)),
|
|
||||||
given-name [1] IMPLICIT TeletexString
|
|
||||||
(SIZE (1..ub-given-name-length)) OPTIONAL,
|
|
||||||
initials [2] IMPLICIT TeletexString
|
|
||||||
(SIZE (1..ub-initials-length)) OPTIONAL,
|
|
||||||
generation-qualifier [3] IMPLICIT TeletexString
|
|
||||||
(SIZE (1..ub-generation-qualifier-length))
|
|
||||||
OPTIONAL }
|
|
||||||
|
|
||||||
teletex-organizational-unit-names INTEGER ::= 5
|
|
||||||
|
|
||||||
TeletexOrganizationalUnitNames ::= SEQUENCE SIZE
|
|
||||||
(1..ub-organizational-units) OF TeletexOrganizationalUnitName
|
|
||||||
|
|
||||||
TeletexOrganizationalUnitName ::= TeletexString
|
|
||||||
(SIZE (1..ub-organizational-unit-name-length))
|
|
||||||
|
|
||||||
pds-name INTEGER ::= 7
|
|
||||||
|
|
||||||
PDSName ::= PrintableString (SIZE (1..ub-pds-name-length))
|
|
||||||
|
|
||||||
physical-delivery-country-name INTEGER ::= 8
|
|
||||||
|
|
||||||
PhysicalDeliveryCountryName ::= CHOICE {
|
|
||||||
x121-dcc-code NumericString (SIZE
|
|
||||||
(ub-country-name-numeric-length)),
|
|
||||||
iso-3166-alpha2-code PrintableString
|
|
||||||
(SIZE (ub-country-name-alpha-length)) }
|
|
||||||
|
|
||||||
|
|
||||||
postal-code INTEGER ::= 9
|
|
||||||
|
|
||||||
PostalCode ::= CHOICE {
|
|
||||||
numeric-code NumericString (SIZE (1..ub-postal-code-length)),
|
|
||||||
printable-code PrintableString (SIZE (1..ub-postal-code-length)) }
|
|
||||||
|
|
||||||
physical-delivery-office-name INTEGER ::= 10
|
|
||||||
|
|
||||||
PhysicalDeliveryOfficeName ::= PDSParameter
|
|
||||||
|
|
||||||
physical-delivery-office-number INTEGER ::= 11
|
|
||||||
|
|
||||||
PhysicalDeliveryOfficeNumber ::= PDSParameter
|
|
||||||
|
|
||||||
extension-OR-address-components INTEGER ::= 12
|
|
||||||
|
|
||||||
ExtensionORAddressComponents ::= PDSParameter
|
|
||||||
|
|
||||||
physical-delivery-personal-name INTEGER ::= 13
|
|
||||||
|
|
||||||
PhysicalDeliveryPersonalName ::= PDSParameter
|
|
||||||
|
|
||||||
physical-delivery-organization-name INTEGER ::= 14
|
|
||||||
|
|
||||||
PhysicalDeliveryOrganizationName ::= PDSParameter
|
|
||||||
|
|
||||||
extension-physical-delivery-address-components INTEGER ::= 15
|
|
||||||
|
|
||||||
ExtensionPhysicalDeliveryAddressComponents ::= PDSParameter
|
|
||||||
|
|
||||||
unformatted-postal-address INTEGER ::= 16
|
|
||||||
|
|
||||||
UnformattedPostalAddress ::= SET {
|
|
||||||
printable-address SEQUENCE SIZE (1..ub-pds-physical-address-lines)
|
|
||||||
OF PrintableString (SIZE (1..ub-pds-parameter-length))
|
|
||||||
OPTIONAL,
|
|
||||||
teletex-string TeletexString
|
|
||||||
(SIZE (1..ub-unformatted-address-length)) OPTIONAL }
|
|
||||||
|
|
||||||
street-address INTEGER ::= 17
|
|
||||||
|
|
||||||
StreetAddress ::= PDSParameter
|
|
||||||
|
|
||||||
post-office-box-address INTEGER ::= 18
|
|
||||||
|
|
||||||
PostOfficeBoxAddress ::= PDSParameter
|
|
||||||
|
|
||||||
poste-restante-address INTEGER ::= 19
|
|
||||||
|
|
||||||
PosteRestanteAddress ::= PDSParameter
|
|
||||||
|
|
||||||
unique-postal-name INTEGER ::= 20
|
|
||||||
|
|
||||||
UniquePostalName ::= PDSParameter
|
|
||||||
|
|
||||||
local-postal-attributes INTEGER ::= 21
|
|
||||||
|
|
||||||
LocalPostalAttributes ::= PDSParameter
|
|
||||||
|
|
||||||
PDSParameter ::= SET {
|
|
||||||
printable-string PrintableString
|
|
||||||
(SIZE(1..ub-pds-parameter-length)) OPTIONAL,
|
|
||||||
teletex-string TeletexString
|
|
||||||
(SIZE(1..ub-pds-parameter-length)) OPTIONAL }
|
|
||||||
|
|
||||||
extended-network-address INTEGER ::= 22
|
|
||||||
|
|
||||||
ExtendedNetworkAddress ::= CHOICE {
|
|
||||||
e163-4-address SEQUENCE {
|
|
||||||
number [0] IMPLICIT NumericString
|
|
||||||
(SIZE (1..ub-e163-4-number-length)),
|
|
||||||
sub-address [1] IMPLICIT NumericString
|
|
||||||
(SIZE (1..ub-e163-4-sub-address-length))
|
|
||||||
OPTIONAL },
|
|
||||||
psap-address [0] IMPLICIT PresentationAddress }
|
|
||||||
|
|
||||||
PresentationAddress ::= SEQUENCE {
|
|
||||||
pSelector [0] EXPLICIT OCTET STRING OPTIONAL,
|
|
||||||
sSelector [1] EXPLICIT OCTET STRING OPTIONAL,
|
|
||||||
tSelector [2] EXPLICIT OCTET STRING OPTIONAL,
|
|
||||||
nAddresses [3] EXPLICIT SET SIZE (1..MAX) OF OCTET STRING }
|
|
||||||
|
|
||||||
terminal-type INTEGER ::= 23
|
|
||||||
|
|
||||||
TerminalType ::= INTEGER {
|
|
||||||
telex (3),
|
|
||||||
teletex (4),
|
|
||||||
g3-facsimile (5),
|
|
||||||
g4-facsimile (6),
|
|
||||||
ia5-terminal (7),
|
|
||||||
videotex (8) } --(0..ub-integer-options)
|
|
||||||
|
|
||||||
-- Extension Domain-defined Attributes
|
|
||||||
|
|
||||||
teletex-domain-defined-attributes INTEGER ::= 6
|
|
||||||
|
|
||||||
|
|
||||||
TeletexDomainDefinedAttributes ::= SEQUENCE SIZE
|
|
||||||
(1..ub-domain-defined-attributes) OF TeletexDomainDefinedAttribute
|
|
||||||
|
|
||||||
TeletexDomainDefinedAttribute ::= SEQUENCE {
|
|
||||||
type TeletexString
|
|
||||||
(SIZE (1..ub-domain-defined-attribute-type-length)),
|
|
||||||
value TeletexString
|
|
||||||
(SIZE (1..ub-domain-defined-attribute-value-length)) }
|
|
||||||
|
|
||||||
-- specifications of Upper Bounds MUST be regarded as mandatory
|
|
||||||
-- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
|
|
||||||
-- Upper Bounds
|
|
||||||
|
|
||||||
-- Upper Bounds
|
|
||||||
ub-name INTEGER ::= 32768
|
|
||||||
ub-common-name INTEGER ::= 64
|
|
||||||
ub-locality-name INTEGER ::= 128
|
|
||||||
ub-state-name INTEGER ::= 128
|
|
||||||
ub-organization-name INTEGER ::= 64
|
|
||||||
ub-organizational-unit-name INTEGER ::= 64
|
|
||||||
ub-title INTEGER ::= 64
|
|
||||||
ub-serial-number INTEGER ::= 64
|
|
||||||
ub-match INTEGER ::= 128
|
|
||||||
ub-emailaddress-length INTEGER ::= 128
|
|
||||||
ub-common-name-length INTEGER ::= 64
|
|
||||||
ub-country-name-alpha-length INTEGER ::= 2
|
|
||||||
ub-country-name-numeric-length INTEGER ::= 3
|
|
||||||
ub-domain-defined-attributes INTEGER ::= 4
|
|
||||||
ub-domain-defined-attribute-type-length INTEGER ::= 8
|
|
||||||
ub-domain-defined-attribute-value-length INTEGER ::= 128
|
|
||||||
ub-domain-name-length INTEGER ::= 16
|
|
||||||
ub-extension-attributes INTEGER ::= 256
|
|
||||||
ub-e163-4-number-length INTEGER ::= 15
|
|
||||||
ub-e163-4-sub-address-length INTEGER ::= 40
|
|
||||||
ub-generation-qualifier-length INTEGER ::= 3
|
|
||||||
ub-given-name-length INTEGER ::= 16
|
|
||||||
ub-initials-length INTEGER ::= 5
|
|
||||||
ub-integer-options INTEGER ::= 256
|
|
||||||
ub-numeric-user-id-length INTEGER ::= 32
|
|
||||||
ub-organization-name-length INTEGER ::= 64
|
|
||||||
ub-organizational-unit-name-length INTEGER ::= 32
|
|
||||||
ub-organizational-units INTEGER ::= 4
|
|
||||||
ub-pds-name-length INTEGER ::= 16
|
|
||||||
ub-pds-parameter-length INTEGER ::= 30
|
|
||||||
ub-pds-physical-address-lines INTEGER ::= 6
|
|
||||||
ub-postal-code-length INTEGER ::= 16
|
|
||||||
ub-pseudonym INTEGER ::= 128
|
|
||||||
ub-surname-length INTEGER ::= 40
|
|
||||||
ub-terminal-id-length INTEGER ::= 24
|
|
||||||
ub-unformatted-address-length INTEGER ::= 180
|
|
||||||
ub-x121-address-length INTEGER ::= 16
|
|
||||||
|
|
||||||
-- Note - upper bounds on string types, such as TeletexString, are
|
|
||||||
-- measured in characters. Excepting PrintableString or IA5String, a
|
|
||||||
-- significantly greater number of octets will be required to hold
|
|
||||||
-- such a value. As a minimum, 16 octets, or twice the specified
|
|
||||||
-- upper bound, whichever is the larger, should be allowed for
|
|
||||||
-- TeletexString. For UTF8String or UniversalString at least four
|
|
||||||
-- times the upper bound should be allowed.
|
|
||||||
|
|
||||||
END
|
|
@ -1,347 +0,0 @@
|
|||||||
PKIX1Implicit88 { iso(1) identified-organization(3) dod(6) internet(1)
|
|
||||||
security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-implicit(19) }
|
|
||||||
|
|
||||||
DEFINITIONS IMPLICIT TAGS ::=
|
|
||||||
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- EXPORTS ALL --
|
|
||||||
|
|
||||||
-- fake imports
|
|
||||||
id-pe OBJECT IDENTIFIER ::= { id-pkix 1 }
|
|
||||||
id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
|
|
||||||
ORAddress ::= ANY
|
|
||||||
Name ::= CHOICE { any ANY }
|
|
||||||
RelativeDistinguishedName ::= ANY
|
|
||||||
CertificateSerialNumber ::= INTEGER
|
|
||||||
Attribute ::= ANY
|
|
||||||
DirectoryString ::= CHOICE { any ANY }
|
|
||||||
|
|
||||||
-- ISO arc for standard certificate and CRL extensions
|
|
||||||
|
|
||||||
id-ce OBJECT IDENTIFIER ::= {joint-iso-ccitt(2) ds(5) 29}
|
|
||||||
|
|
||||||
-- authority key identifier OID and syntax
|
|
||||||
|
|
||||||
id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
|
|
||||||
|
|
||||||
AuthorityKeyIdentifier ::= SEQUENCE {
|
|
||||||
keyIdentifier [0] KeyIdentifier OPTIONAL,
|
|
||||||
authorityCertIssuer [1] GeneralNames OPTIONAL,
|
|
||||||
authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
|
||||||
-- authorityCertIssuer and authorityCertSerialNumber MUST both
|
|
||||||
-- be present or both be absent
|
|
||||||
|
|
||||||
KeyIdentifier ::= OCTET STRING
|
|
||||||
|
|
||||||
-- subject key identifier OID and syntax
|
|
||||||
|
|
||||||
id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 }
|
|
||||||
|
|
||||||
SubjectKeyIdentifier ::= KeyIdentifier
|
|
||||||
|
|
||||||
-- key usage extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
|
|
||||||
|
|
||||||
KeyUsage ::= BIT STRING {
|
|
||||||
digitalSignature (0),
|
|
||||||
nonRepudiation (1),
|
|
||||||
keyEncipherment (2),
|
|
||||||
dataEncipherment (3),
|
|
||||||
keyAgreement (4),
|
|
||||||
keyCertSign (5),
|
|
||||||
cRLSign (6),
|
|
||||||
encipherOnly (7),
|
|
||||||
decipherOnly (8) }
|
|
||||||
|
|
||||||
-- private key usage period extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-privateKeyUsagePeriod OBJECT IDENTIFIER ::= { id-ce 16 }
|
|
||||||
|
|
||||||
PrivateKeyUsagePeriod ::= SEQUENCE {
|
|
||||||
notBefore [0] GeneralizedTime OPTIONAL,
|
|
||||||
notAfter [1] GeneralizedTime OPTIONAL }
|
|
||||||
-- either notBefore or notAfter MUST be present
|
|
||||||
|
|
||||||
-- certificate policies extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
|
|
||||||
|
|
||||||
anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
|
|
||||||
|
|
||||||
CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
|
||||||
|
|
||||||
PolicyInformation ::= SEQUENCE {
|
|
||||||
policyIdentifier CertPolicyId,
|
|
||||||
policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
|
||||||
PolicyQualifierInfo OPTIONAL }
|
|
||||||
|
|
||||||
CertPolicyId ::= OBJECT IDENTIFIER
|
|
||||||
|
|
||||||
PolicyQualifierInfo ::= SEQUENCE {
|
|
||||||
policyQualifierId PolicyQualifierId,
|
|
||||||
qualifier ANY DEFINED BY policyQualifierId }
|
|
||||||
|
|
||||||
-- Implementations that recognize additional policy qualifiers MUST
|
|
||||||
-- augment the following definition for PolicyQualifierId
|
|
||||||
|
|
||||||
PolicyQualifierId ::=
|
|
||||||
OBJECT IDENTIFIER --( id-qt-cps | id-qt-unotice )
|
|
||||||
|
|
||||||
-- CPS pointer qualifier
|
|
||||||
|
|
||||||
CPSuri ::= IA5String
|
|
||||||
|
|
||||||
-- user notice qualifier
|
|
||||||
|
|
||||||
UserNotice ::= SEQUENCE {
|
|
||||||
noticeRef NoticeReference OPTIONAL,
|
|
||||||
explicitText DisplayText OPTIONAL}
|
|
||||||
|
|
||||||
NoticeReference ::= SEQUENCE {
|
|
||||||
organization DisplayText,
|
|
||||||
noticeNumbers SEQUENCE OF INTEGER }
|
|
||||||
|
|
||||||
DisplayText ::= CHOICE {
|
|
||||||
ia5String IA5String (SIZE (1..200)),
|
|
||||||
visibleString VisibleString (SIZE (1..200)),
|
|
||||||
bmpString BMPString (SIZE (1..200)),
|
|
||||||
utf8String UTF8String (SIZE (1..200)) }
|
|
||||||
|
|
||||||
-- policy mapping extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-policyMappings OBJECT IDENTIFIER ::= { id-ce 33 }
|
|
||||||
|
|
||||||
PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
|
|
||||||
issuerDomainPolicy CertPolicyId,
|
|
||||||
subjectDomainPolicy CertPolicyId }
|
|
||||||
|
|
||||||
-- subject alternative name extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
|
|
||||||
|
|
||||||
SubjectAltName ::= GeneralNames
|
|
||||||
|
|
||||||
GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
|
||||||
|
|
||||||
GeneralName ::= CHOICE {
|
|
||||||
otherName [0] AnotherName,
|
|
||||||
rfc822Name [1] IA5String,
|
|
||||||
dNSName [2] IA5String,
|
|
||||||
x400Address [3] ORAddress,
|
|
||||||
directoryName [4] Name,
|
|
||||||
ediPartyName [5] EDIPartyName,
|
|
||||||
uniformResourceIdentifier [6] IA5String,
|
|
||||||
iPAddress [7] OCTET STRING,
|
|
||||||
registeredID [8] OBJECT IDENTIFIER }
|
|
||||||
|
|
||||||
-- AnotherName replaces OTHER-NAME ::= TYPE-IDENTIFIER, as
|
|
||||||
-- TYPE-IDENTIFIER is not supported in the '88 ASN.1 syntax
|
|
||||||
|
|
||||||
AnotherName ::= SEQUENCE {
|
|
||||||
type-id OBJECT IDENTIFIER,
|
|
||||||
value [0] EXPLICIT ANY DEFINED BY type-id }
|
|
||||||
|
|
||||||
EDIPartyName ::= SEQUENCE {
|
|
||||||
nameAssigner [0] DirectoryString OPTIONAL,
|
|
||||||
partyName [1] DirectoryString }
|
|
||||||
|
|
||||||
-- issuer alternative name extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 }
|
|
||||||
|
|
||||||
IssuerAltName ::= GeneralNames
|
|
||||||
|
|
||||||
id-ce-subjectDirectoryAttributes OBJECT IDENTIFIER ::= { id-ce 9 }
|
|
||||||
|
|
||||||
SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
-- basic constraints extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
|
|
||||||
|
|
||||||
BasicConstraints ::= SEQUENCE {
|
|
||||||
cA BOOLEAN DEFAULT FALSE,
|
|
||||||
pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
|
||||||
|
|
||||||
-- name constraints extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-nameConstraints OBJECT IDENTIFIER ::= { id-ce 30 }
|
|
||||||
|
|
||||||
NameConstraints ::= SEQUENCE {
|
|
||||||
permittedSubtrees [0] GeneralSubtrees OPTIONAL,
|
|
||||||
excludedSubtrees [1] GeneralSubtrees OPTIONAL }
|
|
||||||
|
|
||||||
GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
|
|
||||||
|
|
||||||
GeneralSubtree ::= SEQUENCE {
|
|
||||||
base GeneralName,
|
|
||||||
minimum [0] BaseDistance DEFAULT 0,
|
|
||||||
maximum [1] BaseDistance OPTIONAL }
|
|
||||||
|
|
||||||
BaseDistance ::= INTEGER (0..MAX)
|
|
||||||
|
|
||||||
-- policy constraints extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-policyConstraints OBJECT IDENTIFIER ::= { id-ce 36 }
|
|
||||||
|
|
||||||
PolicyConstraints ::= SEQUENCE {
|
|
||||||
requireExplicitPolicy [0] SkipCerts OPTIONAL,
|
|
||||||
inhibitPolicyMapping [1] SkipCerts OPTIONAL }
|
|
||||||
|
|
||||||
SkipCerts ::= INTEGER (0..MAX)
|
|
||||||
|
|
||||||
-- CRL distribution points extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= {id-ce 31}
|
|
||||||
|
|
||||||
CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
|
||||||
|
|
||||||
DistributionPoint ::= SEQUENCE {
|
|
||||||
distributionPoint [0] DistributionPointName OPTIONAL,
|
|
||||||
reasons [1] ReasonFlags OPTIONAL,
|
|
||||||
cRLIssuer [2] GeneralNames OPTIONAL }
|
|
||||||
|
|
||||||
DistributionPointName ::= CHOICE {
|
|
||||||
fullName [0] GeneralNames,
|
|
||||||
nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
|
|
||||||
|
|
||||||
ReasonFlags ::= BIT STRING {
|
|
||||||
unused (0),
|
|
||||||
keyCompromise (1),
|
|
||||||
cACompromise (2),
|
|
||||||
affiliationChanged (3),
|
|
||||||
superseded (4),
|
|
||||||
cessationOfOperation (5),
|
|
||||||
certificateHold (6),
|
|
||||||
privilegeWithdrawn (7),
|
|
||||||
aACompromise (8) }
|
|
||||||
|
|
||||||
-- extended key usage extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-extKeyUsage OBJECT IDENTIFIER ::= {id-ce 37}
|
|
||||||
|
|
||||||
ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
|
|
||||||
|
|
||||||
|
|
||||||
KeyPurposeId ::= OBJECT IDENTIFIER
|
|
||||||
|
|
||||||
-- permit unspecified key uses
|
|
||||||
|
|
||||||
anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
|
|
||||||
|
|
||||||
-- extended key purpose OIDs
|
|
||||||
|
|
||||||
id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
|
|
||||||
id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
|
|
||||||
id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
|
|
||||||
id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
|
|
||||||
id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 }
|
|
||||||
id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
|
|
||||||
|
|
||||||
-- inhibit any policy OID and syntax
|
|
||||||
|
|
||||||
id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 }
|
|
||||||
|
|
||||||
InhibitAnyPolicy ::= SkipCerts
|
|
||||||
|
|
||||||
-- freshest (delta)CRL extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-freshestCRL OBJECT IDENTIFIER ::= { id-ce 46 }
|
|
||||||
|
|
||||||
FreshestCRL ::= CRLDistributionPoints
|
|
||||||
|
|
||||||
-- authority info access
|
|
||||||
|
|
||||||
id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
|
|
||||||
|
|
||||||
AuthorityInfoAccessSyntax ::=
|
|
||||||
SEQUENCE SIZE (1..MAX) OF AccessDescription
|
|
||||||
|
|
||||||
AccessDescription ::= SEQUENCE {
|
|
||||||
accessMethod OBJECT IDENTIFIER,
|
|
||||||
accessLocation GeneralName }
|
|
||||||
|
|
||||||
-- subject info access
|
|
||||||
|
|
||||||
id-pe-subjectInfoAccess OBJECT IDENTIFIER ::= { id-pe 11 }
|
|
||||||
|
|
||||||
SubjectInfoAccessSyntax ::=
|
|
||||||
SEQUENCE SIZE (1..MAX) OF AccessDescription
|
|
||||||
|
|
||||||
-- CRL number extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-cRLNumber OBJECT IDENTIFIER ::= { id-ce 20 }
|
|
||||||
|
|
||||||
CRLNumber ::= INTEGER (0..MAX)
|
|
||||||
|
|
||||||
-- issuing distribution point extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
|
|
||||||
|
|
||||||
IssuingDistributionPoint ::= SEQUENCE {
|
|
||||||
distributionPoint [0] DistributionPointName OPTIONAL,
|
|
||||||
onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
|
|
||||||
onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
|
|
||||||
onlySomeReasons [3] ReasonFlags OPTIONAL,
|
|
||||||
indirectCRL [4] BOOLEAN DEFAULT FALSE,
|
|
||||||
onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
|
|
||||||
|
|
||||||
id-ce-deltaCRLIndicator OBJECT IDENTIFIER ::= { id-ce 27 }
|
|
||||||
|
|
||||||
BaseCRLNumber ::= CRLNumber
|
|
||||||
|
|
||||||
-- CRL reasons extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-cRLReasons OBJECT IDENTIFIER ::= { id-ce 21 }
|
|
||||||
|
|
||||||
CRLReason ::= ENUMERATED {
|
|
||||||
unspecified (0),
|
|
||||||
keyCompromise (1),
|
|
||||||
cACompromise (2),
|
|
||||||
affiliationChanged (3),
|
|
||||||
superseded (4),
|
|
||||||
cessationOfOperation (5),
|
|
||||||
certificateHold (6),
|
|
||||||
removeFromCRL (8),
|
|
||||||
privilegeWithdrawn (9),
|
|
||||||
aACompromise (10) }
|
|
||||||
|
|
||||||
-- certificate issuer CRL entry extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 }
|
|
||||||
|
|
||||||
CertificateIssuer ::= GeneralNames
|
|
||||||
|
|
||||||
-- hold instruction extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-holdInstructionCode OBJECT IDENTIFIER ::= { id-ce 23 }
|
|
||||||
|
|
||||||
HoldInstructionCode ::= OBJECT IDENTIFIER
|
|
||||||
|
|
||||||
-- ANSI x9 holdinstructions
|
|
||||||
|
|
||||||
-- ANSI x9 arc holdinstruction arc
|
|
||||||
|
|
||||||
holdInstruction OBJECT IDENTIFIER ::=
|
|
||||||
{joint-iso-itu-t(2) member-body(2) us(840) x9cm(10040) 2}
|
|
||||||
|
|
||||||
-- ANSI X9 holdinstructions referenced by this standard
|
|
||||||
|
|
||||||
id-holdinstruction-none OBJECT IDENTIFIER ::=
|
|
||||||
{holdInstruction 1} -- deprecated
|
|
||||||
|
|
||||||
id-holdinstruction-callissuer OBJECT IDENTIFIER ::=
|
|
||||||
{holdInstruction 2}
|
|
||||||
|
|
||||||
id-holdinstruction-reject OBJECT IDENTIFIER ::=
|
|
||||||
{holdInstruction 3}
|
|
||||||
|
|
||||||
-- invalidity date CRL entry extension OID and syntax
|
|
||||||
|
|
||||||
id-ce-invalidityDate OBJECT IDENTIFIER ::= { id-ce 24 }
|
|
||||||
|
|
||||||
InvalidityDate ::= GeneralizedTime
|
|
||||||
|
|
||||||
END
|
|
@ -1,51 +0,0 @@
|
|||||||
AttributeCertificateVersion1
|
|
||||||
{ iso(1) member-body(2) us(840) rsadsi(113549)
|
|
||||||
pkcs(1) pkcs-9(9) smime(16) modules(0) v1AttrCert(15) }
|
|
||||||
|
|
||||||
DEFINITIONS EXPLICIT TAGS ::=
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- EXPORTS All
|
|
||||||
|
|
||||||
-- fake imports
|
|
||||||
-- Imports from RFC 3280 [PROFILE], Appendix A.1
|
|
||||||
AlgorithmIdentifier ::= ANY
|
|
||||||
Attribute ::= ANY
|
|
||||||
CertificateSerialNumber ::= INTEGER
|
|
||||||
Extensions ::= ANY
|
|
||||||
UniqueIdentifier ::= BIT STRING
|
|
||||||
|
|
||||||
-- Imports from RFC 3280 [PROFILE], Appendix A.2
|
|
||||||
GeneralNames ::= ANY
|
|
||||||
|
|
||||||
-- Imports from RFC 3281 [ACPROFILE], Appendix B
|
|
||||||
AttCertValidityPeriod ::= ANY
|
|
||||||
IssuerSerial ::= ANY
|
|
||||||
|
|
||||||
-- Definition extracted from X.509-1997 [X.509-97], but
|
|
||||||
-- different type names are used to avoid collisions.
|
|
||||||
|
|
||||||
|
|
||||||
AttributeCertificateV1 ::= SEQUENCE {
|
|
||||||
acInfo AttributeCertificateInfoV1,
|
|
||||||
signatureAlgorithm AlgorithmIdentifier,
|
|
||||||
signature BIT STRING }
|
|
||||||
|
|
||||||
AttributeCertificateInfoV1 ::= SEQUENCE {
|
|
||||||
version AttCertVersionV1 DEFAULT v1,
|
|
||||||
subject CHOICE {
|
|
||||||
baseCertificateID [0] IssuerSerial,
|
|
||||||
-- associated with a Public Key Certificate
|
|
||||||
subjectName [1] GeneralNames },
|
|
||||||
-- associated with a name
|
|
||||||
issuer GeneralNames,
|
|
||||||
signature AlgorithmIdentifier,
|
|
||||||
serialNumber CertificateSerialNumber,
|
|
||||||
attCertValidityPeriod AttCertValidityPeriod,
|
|
||||||
attributes SEQUENCE OF Attribute,
|
|
||||||
issuerUniqueID UniqueIdentifier OPTIONAL,
|
|
||||||
extensions Extensions OPTIONAL }
|
|
||||||
|
|
||||||
AttCertVersionV1 ::= INTEGER { v1(0) }
|
|
||||||
|
|
||||||
END -- of AttributeCertificateVersion1
|
|
@ -1,333 +0,0 @@
|
|||||||
CryptographicMessageSyntax2004
|
|
||||||
{ iso(1) member-body(2) us(840) rsadsi(113549)
|
|
||||||
pkcs(1) pkcs-9(9) smime(16) modules(0) cms-2004(24) }
|
|
||||||
|
|
||||||
DEFINITIONS IMPLICIT TAGS ::=
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- EXPORTS All
|
|
||||||
-- The types and values defined in this module are exported for use
|
|
||||||
-- in the other ASN.1 modules. Other applications may use them for
|
|
||||||
-- their own purposes.
|
|
||||||
|
|
||||||
-- fake imports
|
|
||||||
-- Imports from RFC 3280 [PROFILE], Appendix A.1
|
|
||||||
AlgorithmIdentifier ::= ANY
|
|
||||||
Certificate ::= ANY
|
|
||||||
CertificateList ::= ANY
|
|
||||||
CertificateSerialNumber ::= INTEGER
|
|
||||||
Name ::= CHOICE { any ANY }
|
|
||||||
|
|
||||||
-- Imports from RFC 3281 [ACPROFILE], Appendix B
|
|
||||||
AttributeCertificate ::= ANY
|
|
||||||
|
|
||||||
-- Imports from Appendix B of this document
|
|
||||||
AttributeCertificateV1 ::= ANY
|
|
||||||
|
|
||||||
-- Cryptographic Message Syntax
|
|
||||||
|
|
||||||
ContentInfo ::= SEQUENCE {
|
|
||||||
contentType ContentType,
|
|
||||||
content [0] EXPLICIT ANY DEFINED BY contentType }
|
|
||||||
|
|
||||||
ContentType ::= OBJECT IDENTIFIER
|
|
||||||
|
|
||||||
|
|
||||||
SignedData ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
digestAlgorithms DigestAlgorithmIdentifiers,
|
|
||||||
encapContentInfo EncapsulatedContentInfo,
|
|
||||||
certificates [0] IMPLICIT CertificateSet OPTIONAL,
|
|
||||||
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
|
|
||||||
signerInfos SignerInfos }
|
|
||||||
|
|
||||||
DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
|
|
||||||
|
|
||||||
SignerInfos ::= SET OF SignerInfo
|
|
||||||
|
|
||||||
EncapsulatedContentInfo ::= SEQUENCE {
|
|
||||||
eContentType ContentType,
|
|
||||||
eContent [0] EXPLICIT OCTET STRING OPTIONAL }
|
|
||||||
|
|
||||||
SignerInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
sid SignerIdentifier,
|
|
||||||
digestAlgorithm DigestAlgorithmIdentifier,
|
|
||||||
signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
|
|
||||||
signatureAlgorithm SignatureAlgorithmIdentifier,
|
|
||||||
signature SignatureValue,
|
|
||||||
unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
|
|
||||||
|
|
||||||
SignerIdentifier ::= CHOICE {
|
|
||||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
|
||||||
subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
|
||||||
|
|
||||||
SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
Attribute ::= SEQUENCE {
|
|
||||||
attrType OBJECT IDENTIFIER,
|
|
||||||
attrValues SET OF AttributeValue }
|
|
||||||
|
|
||||||
AttributeValue ::= ANY
|
|
||||||
|
|
||||||
SignatureValue ::= OCTET STRING
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EnvelopedData ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
|
||||||
recipientInfos RecipientInfos,
|
|
||||||
encryptedContentInfo EncryptedContentInfo,
|
|
||||||
unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
|
|
||||||
|
|
||||||
OriginatorInfo ::= SEQUENCE {
|
|
||||||
certs [0] IMPLICIT CertificateSet OPTIONAL,
|
|
||||||
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL }
|
|
||||||
|
|
||||||
RecipientInfos ::= SET SIZE (1..MAX) OF RecipientInfo
|
|
||||||
|
|
||||||
EncryptedContentInfo ::= SEQUENCE {
|
|
||||||
contentType ContentType,
|
|
||||||
contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
|
|
||||||
encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
|
|
||||||
|
|
||||||
EncryptedContent ::= OCTET STRING
|
|
||||||
|
|
||||||
UnprotectedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
RecipientInfo ::= CHOICE {
|
|
||||||
ktri KeyTransRecipientInfo,
|
|
||||||
kari [1] KeyAgreeRecipientInfo,
|
|
||||||
kekri [2] KEKRecipientInfo,
|
|
||||||
pwri [3] PasswordRecipientInfo,
|
|
||||||
ori [4] OtherRecipientInfo }
|
|
||||||
|
|
||||||
EncryptedKey ::= OCTET STRING
|
|
||||||
|
|
||||||
KeyTransRecipientInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion, -- always set to 0 or 2
|
|
||||||
rid RecipientIdentifier,
|
|
||||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
|
||||||
encryptedKey EncryptedKey }
|
|
||||||
|
|
||||||
RecipientIdentifier ::= CHOICE {
|
|
||||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
|
||||||
subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
|
||||||
|
|
||||||
KeyAgreeRecipientInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion, -- always set to 3
|
|
||||||
originator [0] EXPLICIT OriginatorIdentifierOrKey,
|
|
||||||
ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
|
|
||||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
|
||||||
recipientEncryptedKeys RecipientEncryptedKeys }
|
|
||||||
|
|
||||||
|
|
||||||
OriginatorIdentifierOrKey ::= CHOICE {
|
|
||||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
|
||||||
subjectKeyIdentifier [0] SubjectKeyIdentifier,
|
|
||||||
originatorKey [1] OriginatorPublicKey }
|
|
||||||
|
|
||||||
OriginatorPublicKey ::= SEQUENCE {
|
|
||||||
algorithm AlgorithmIdentifier,
|
|
||||||
publicKey BIT STRING }
|
|
||||||
|
|
||||||
RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey
|
|
||||||
|
|
||||||
RecipientEncryptedKey ::= SEQUENCE {
|
|
||||||
rid KeyAgreeRecipientIdentifier,
|
|
||||||
encryptedKey EncryptedKey }
|
|
||||||
|
|
||||||
KeyAgreeRecipientIdentifier ::= CHOICE {
|
|
||||||
issuerAndSerialNumber IssuerAndSerialNumber,
|
|
||||||
rKeyId [0] IMPLICIT RecipientKeyIdentifier }
|
|
||||||
|
|
||||||
RecipientKeyIdentifier ::= SEQUENCE {
|
|
||||||
subjectKeyIdentifier SubjectKeyIdentifier,
|
|
||||||
date GeneralizedTime OPTIONAL,
|
|
||||||
other OtherKeyAttribute OPTIONAL }
|
|
||||||
|
|
||||||
SubjectKeyIdentifier ::= OCTET STRING
|
|
||||||
|
|
||||||
KEKRecipientInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion, -- always set to 4
|
|
||||||
kekid KEKIdentifier,
|
|
||||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
|
||||||
encryptedKey EncryptedKey }
|
|
||||||
|
|
||||||
KEKIdentifier ::= SEQUENCE {
|
|
||||||
keyIdentifier OCTET STRING,
|
|
||||||
date GeneralizedTime OPTIONAL,
|
|
||||||
other OtherKeyAttribute OPTIONAL }
|
|
||||||
|
|
||||||
PasswordRecipientInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion, -- always set to 0
|
|
||||||
keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier
|
|
||||||
OPTIONAL,
|
|
||||||
keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
|
||||||
encryptedKey EncryptedKey }
|
|
||||||
|
|
||||||
OtherRecipientInfo ::= SEQUENCE {
|
|
||||||
oriType OBJECT IDENTIFIER,
|
|
||||||
oriValue ANY DEFINED BY oriType }
|
|
||||||
|
|
||||||
|
|
||||||
DigestedData ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
digestAlgorithm DigestAlgorithmIdentifier,
|
|
||||||
encapContentInfo EncapsulatedContentInfo,
|
|
||||||
digest Digest }
|
|
||||||
|
|
||||||
Digest ::= OCTET STRING
|
|
||||||
|
|
||||||
EncryptedData ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
encryptedContentInfo EncryptedContentInfo,
|
|
||||||
unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
|
|
||||||
|
|
||||||
AuthenticatedData ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
|
|
||||||
recipientInfos RecipientInfos,
|
|
||||||
macAlgorithm MessageAuthenticationCodeAlgorithm,
|
|
||||||
digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
|
|
||||||
encapContentInfo EncapsulatedContentInfo,
|
|
||||||
authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
|
|
||||||
mac MessageAuthenticationCode,
|
|
||||||
unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
|
|
||||||
|
|
||||||
AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
|
|
||||||
|
|
||||||
MessageAuthenticationCode ::= OCTET STRING
|
|
||||||
|
|
||||||
DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
MessageAuthenticationCodeAlgorithm ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
KeyDerivationAlgorithmIdentifier ::= AlgorithmIdentifier
|
|
||||||
|
|
||||||
RevocationInfoChoices ::= SET OF RevocationInfoChoice
|
|
||||||
|
|
||||||
RevocationInfoChoice ::= CHOICE {
|
|
||||||
crl CertificateList,
|
|
||||||
other [1] IMPLICIT OtherRevocationInfoFormat }
|
|
||||||
|
|
||||||
|
|
||||||
OtherRevocationInfoFormat ::= SEQUENCE {
|
|
||||||
otherRevInfoFormat OBJECT IDENTIFIER,
|
|
||||||
otherRevInfo ANY DEFINED BY otherRevInfoFormat }
|
|
||||||
|
|
||||||
CertificateChoices ::= CHOICE {
|
|
||||||
certificate Certificate,
|
|
||||||
extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
|
|
||||||
v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
|
|
||||||
v2AttrCert [2] IMPLICIT AttributeCertificateV2,
|
|
||||||
other [3] IMPLICIT OtherCertificateFormat }
|
|
||||||
|
|
||||||
AttributeCertificateV2 ::= AttributeCertificate
|
|
||||||
|
|
||||||
OtherCertificateFormat ::= SEQUENCE {
|
|
||||||
otherCertFormat OBJECT IDENTIFIER,
|
|
||||||
otherCert ANY DEFINED BY otherCertFormat }
|
|
||||||
|
|
||||||
CertificateSet ::= SET OF CertificateChoices
|
|
||||||
|
|
||||||
IssuerAndSerialNumber ::= SEQUENCE {
|
|
||||||
issuer Name,
|
|
||||||
serialNumber CertificateSerialNumber }
|
|
||||||
|
|
||||||
CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
|
|
||||||
|
|
||||||
UserKeyingMaterial ::= OCTET STRING
|
|
||||||
|
|
||||||
OtherKeyAttribute ::= SEQUENCE {
|
|
||||||
keyAttrId OBJECT IDENTIFIER,
|
|
||||||
keyAttr ANY DEFINED BY keyAttrId OPTIONAL }
|
|
||||||
|
|
||||||
-- Content Type Object Identifiers
|
|
||||||
|
|
||||||
id-ct-contentInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) smime(16) ct(1) 6 }
|
|
||||||
|
|
||||||
id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }
|
|
||||||
|
|
||||||
id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
|
|
||||||
|
|
||||||
id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }
|
|
||||||
|
|
||||||
id-digestedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 5 }
|
|
||||||
|
|
||||||
|
|
||||||
id-encryptedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs7(7) 6 }
|
|
||||||
|
|
||||||
id-ct-authData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 2 }
|
|
||||||
|
|
||||||
-- The CMS Attributes
|
|
||||||
|
|
||||||
MessageDigest ::= OCTET STRING
|
|
||||||
|
|
||||||
SigningTime ::= Time
|
|
||||||
|
|
||||||
Time ::= CHOICE {
|
|
||||||
utcTime UTCTime,
|
|
||||||
generalTime GeneralizedTime }
|
|
||||||
|
|
||||||
Countersignature ::= SignerInfo
|
|
||||||
|
|
||||||
-- Attribute Object Identifiers
|
|
||||||
|
|
||||||
id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 3 }
|
|
||||||
|
|
||||||
id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 4 }
|
|
||||||
|
|
||||||
id-signingTime OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 5 }
|
|
||||||
|
|
||||||
id-countersignature OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 6 }
|
|
||||||
|
|
||||||
-- Obsolete Extended Certificate syntax from PKCS#6
|
|
||||||
|
|
||||||
ExtendedCertificateOrCertificate ::= CHOICE {
|
|
||||||
certificate Certificate,
|
|
||||||
extendedCertificate [0] IMPLICIT ExtendedCertificate }
|
|
||||||
|
|
||||||
ExtendedCertificate ::= SEQUENCE {
|
|
||||||
extendedCertificateInfo ExtendedCertificateInfo,
|
|
||||||
signatureAlgorithm SignatureAlgorithmIdentifier,
|
|
||||||
signature Signature }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ExtendedCertificateInfo ::= SEQUENCE {
|
|
||||||
version CMSVersion,
|
|
||||||
certificate Certificate,
|
|
||||||
attributes UnauthAttributes }
|
|
||||||
|
|
||||||
Signature ::= BIT STRING
|
|
||||||
|
|
||||||
END -- of CryptographicMessageSyntax2004
|
|
||||||
|
|
284
asn/rfc4211.asn
284
asn/rfc4211.asn
@ -1,284 +0,0 @@
|
|||||||
PKIXCRMF-2005 {iso(1) identified-organization(3) dod(6) internet(1)
|
|
||||||
security(5) mechanisms(5) pkix(7) id-mod(0) id-mod-crmf2005(36)}
|
|
||||||
|
|
||||||
DEFINITIONS IMPLICIT TAGS ::=
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- fake imports
|
|
||||||
|
|
||||||
-- Directory Authentication Framework (X.509)
|
|
||||||
Version ::= INTEGER
|
|
||||||
AlgorithmIdentifier ::= ANY
|
|
||||||
Name ::= CHOICE { any ANY }
|
|
||||||
Time ::= CHOICE { any ANY }
|
|
||||||
SubjectPublicKeyInfo ::= ANY
|
|
||||||
Extensions ::= ANY
|
|
||||||
UniqueIdentifier ::= BIT STRING
|
|
||||||
Attribute ::= ANY
|
|
||||||
|
|
||||||
-- Certificate Extensions (X.509)
|
|
||||||
GeneralName ::= CHOICE { any ANY }
|
|
||||||
|
|
||||||
-- Cryptographic Message Syntax
|
|
||||||
EnvelopedData ::= ANY
|
|
||||||
|
|
||||||
-- The following definition may be uncommented for use with
|
|
||||||
-- ASN.1 compilers that do not understand UTF8String.
|
|
||||||
|
|
||||||
-- UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
|
|
||||||
-- The contents of this type correspond to RFC 2279.
|
|
||||||
|
|
||||||
id-pkix OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
|
|
||||||
dod(6) internet(1) security(5) mechanisms(5) 7 }
|
|
||||||
|
|
||||||
-- arc for Internet X.509 PKI protocols and their components
|
|
||||||
|
|
||||||
id-pkip OBJECT IDENTIFIER ::= { id-pkix 5 }
|
|
||||||
|
|
||||||
id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs9(9) 16 }
|
|
||||||
|
|
||||||
id-ct OBJECT IDENTIFIER ::= { id-smime 1 } -- content types
|
|
||||||
|
|
||||||
-- Core definitions for this module
|
|
||||||
|
|
||||||
CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsg
|
|
||||||
|
|
||||||
CertReqMsg ::= SEQUENCE {
|
|
||||||
certReq CertRequest,
|
|
||||||
popo ProofOfPossession OPTIONAL,
|
|
||||||
-- content depends upon key type
|
|
||||||
regInfo SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue OPTIONAL }
|
|
||||||
|
|
||||||
CertRequest ::= SEQUENCE {
|
|
||||||
certReqId INTEGER, -- ID for matching request and reply
|
|
||||||
certTemplate CertTemplate, -- Selected fields of cert to be issued
|
|
||||||
controls Controls OPTIONAL } -- Attributes affecting issuance
|
|
||||||
|
|
||||||
CertTemplate ::= SEQUENCE {
|
|
||||||
version [0] Version OPTIONAL,
|
|
||||||
serialNumber [1] INTEGER OPTIONAL,
|
|
||||||
signingAlg [2] AlgorithmIdentifier OPTIONAL,
|
|
||||||
issuer [3] Name OPTIONAL,
|
|
||||||
validity [4] OptionalValidity OPTIONAL,
|
|
||||||
subject [5] Name OPTIONAL,
|
|
||||||
publicKey [6] SubjectPublicKeyInfo OPTIONAL,
|
|
||||||
issuerUID [7] UniqueIdentifier OPTIONAL,
|
|
||||||
subjectUID [8] UniqueIdentifier OPTIONAL,
|
|
||||||
extensions [9] Extensions OPTIONAL }
|
|
||||||
|
|
||||||
OptionalValidity ::= SEQUENCE {
|
|
||||||
notBefore [0] Time OPTIONAL,
|
|
||||||
notAfter [1] Time OPTIONAL } -- at least one MUST be present
|
|
||||||
|
|
||||||
Controls ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue
|
|
||||||
AttributeTypeAndValue ::= SEQUENCE {
|
|
||||||
type OBJECT IDENTIFIER,
|
|
||||||
value ANY DEFINED BY type }
|
|
||||||
|
|
||||||
ProofOfPossession ::= CHOICE {
|
|
||||||
raVerified [0] NULL,
|
|
||||||
-- used if the RA has already verified that the requester is in
|
|
||||||
-- possession of the private key
|
|
||||||
signature [1] POPOSigningKey,
|
|
||||||
keyEncipherment [2] POPOPrivKey,
|
|
||||||
keyAgreement [3] POPOPrivKey }
|
|
||||||
|
|
||||||
POPOSigningKey ::= SEQUENCE {
|
|
||||||
poposkInput [0] POPOSigningKeyInput OPTIONAL,
|
|
||||||
algorithmIdentifier AlgorithmIdentifier,
|
|
||||||
signature BIT STRING }
|
|
||||||
|
|
||||||
-- The signature (using "algorithmIdentifier") is on the
|
|
||||||
-- DER-encoded value of poposkInput. NOTE: If the CertReqMsg
|
|
||||||
-- certReq CertTemplate contains the subject and publicKey values,
|
|
||||||
-- then poposkInput MUST be omitted and the signature MUST be
|
|
||||||
-- computed over the DER-encoded value of CertReqMsg certReq. If
|
|
||||||
-- the CertReqMsg certReq CertTemplate does not contain both the
|
|
||||||
-- public key and subject values (i.e., if it contains only one
|
|
||||||
-- of these, or neither), then poposkInput MUST be present and
|
|
||||||
-- MUST be signed.
|
|
||||||
|
|
||||||
|
|
||||||
POPOSigningKeyInput ::= SEQUENCE {
|
|
||||||
authInfo CHOICE {
|
|
||||||
sender [0] GeneralName,
|
|
||||||
-- used only if an authenticated identity has been
|
|
||||||
-- established for the sender (e.g., a DN from a
|
|
||||||
-- previously-issued and currently-valid certificate)
|
|
||||||
publicKeyMAC PKMACValue },
|
|
||||||
-- used if no authenticated GeneralName currently exists for
|
|
||||||
-- the sender; publicKeyMAC contains a password-based MAC
|
|
||||||
-- on the DER-encoded value of publicKey
|
|
||||||
publicKey SubjectPublicKeyInfo } -- from CertTemplate
|
|
||||||
|
|
||||||
PKMACValue ::= SEQUENCE {
|
|
||||||
algId AlgorithmIdentifier,
|
|
||||||
-- algorithm value shall be PasswordBasedMac {1 2 840 113533 7 66 13}
|
|
||||||
-- parameter value is PBMParameter
|
|
||||||
value BIT STRING }
|
|
||||||
|
|
||||||
PBMParameter ::= SEQUENCE {
|
|
||||||
salt OCTET STRING,
|
|
||||||
owf AlgorithmIdentifier,
|
|
||||||
-- AlgId for a One-Way Function (SHA-1 recommended)
|
|
||||||
iterationCount INTEGER,
|
|
||||||
-- number of times the OWF is applied
|
|
||||||
mac AlgorithmIdentifier
|
|
||||||
-- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
|
|
||||||
} -- or HMAC [HMAC, RFC2202])
|
|
||||||
|
|
||||||
POPOPrivKey ::= CHOICE {
|
|
||||||
thisMessage [0] BIT STRING, -- Deprecated
|
|
||||||
-- possession is proven in this message (which contains the private
|
|
||||||
-- key itself (encrypted for the CA))
|
|
||||||
subsequentMessage [1] SubsequentMessage,
|
|
||||||
-- possession will be proven in a subsequent message
|
|
||||||
dhMAC [2] BIT STRING, -- Deprecated
|
|
||||||
agreeMAC [3] PKMACValue,
|
|
||||||
encryptedKey [4] EnvelopedData }
|
|
||||||
|
|
||||||
-- for keyAgreement (only), possession is proven in this message
|
|
||||||
-- (which contains a MAC (over the DER-encoded value of the
|
|
||||||
-- certReq parameter in CertReqMsg, which MUST include both subject
|
|
||||||
-- and publicKey) based on a key derived from the end entity's
|
|
||||||
-- private DH key and the CA's public DH key);
|
|
||||||
|
|
||||||
SubsequentMessage ::= INTEGER {
|
|
||||||
encrCert (0),
|
|
||||||
-- requests that resulting certificate be encrypted for the
|
|
||||||
-- end entity (following which, POP will be proven in a
|
|
||||||
-- confirmation message)
|
|
||||||
challengeResp (1) }
|
|
||||||
-- requests that CA engage in challenge-response exchange with
|
|
||||||
-- end entity in order to prove private key possession
|
|
||||||
|
|
||||||
-- Object identifier assignments --
|
|
||||||
|
|
||||||
-- Registration Controls in CRMF
|
|
||||||
id-regCtrl OBJECT IDENTIFIER ::= { id-pkip 1 }
|
|
||||||
|
|
||||||
|
|
||||||
id-regCtrl-regToken OBJECT IDENTIFIER ::= { id-regCtrl 1 }
|
|
||||||
--with syntax:
|
|
||||||
RegToken ::= UTF8String
|
|
||||||
|
|
||||||
id-regCtrl-authenticator OBJECT IDENTIFIER ::= { id-regCtrl 2 }
|
|
||||||
--with syntax:
|
|
||||||
Authenticator ::= UTF8String
|
|
||||||
|
|
||||||
id-regCtrl-pkiPublicationInfo OBJECT IDENTIFIER ::= { id-regCtrl 3 }
|
|
||||||
--with syntax:
|
|
||||||
|
|
||||||
PKIPublicationInfo ::= SEQUENCE {
|
|
||||||
action INTEGER {
|
|
||||||
dontPublish (0),
|
|
||||||
pleasePublish (1) },
|
|
||||||
pubInfos SEQUENCE SIZE (1..MAX) OF SinglePubInfo OPTIONAL }
|
|
||||||
-- pubInfos MUST NOT be present if action is "dontPublish"
|
|
||||||
-- (if action is "pleasePublish" and pubInfos is omitted,
|
|
||||||
-- "dontCare" is assumed)
|
|
||||||
|
|
||||||
SinglePubInfo ::= SEQUENCE {
|
|
||||||
pubMethod INTEGER {
|
|
||||||
dontCare (0),
|
|
||||||
x500 (1),
|
|
||||||
web (2),
|
|
||||||
ldap (3) },
|
|
||||||
pubLocation GeneralName OPTIONAL }
|
|
||||||
|
|
||||||
id-regCtrl-pkiArchiveOptions OBJECT IDENTIFIER ::= { id-regCtrl 4 }
|
|
||||||
--with syntax:
|
|
||||||
PKIArchiveOptions ::= CHOICE {
|
|
||||||
encryptedPrivKey [0] EncryptedKey,
|
|
||||||
-- the actual value of the private key
|
|
||||||
keyGenParameters [1] KeyGenParameters,
|
|
||||||
-- parameters that allow the private key to be re-generated
|
|
||||||
archiveRemGenPrivKey [2] BOOLEAN }
|
|
||||||
-- set to TRUE if sender wishes receiver to archive the private
|
|
||||||
-- key of a key pair that the receiver generates in response to
|
|
||||||
-- this request; set to FALSE if no archival is desired.
|
|
||||||
|
|
||||||
EncryptedKey ::= CHOICE {
|
|
||||||
encryptedValue EncryptedValue, -- Deprecated
|
|
||||||
envelopedData [0] EnvelopedData }
|
|
||||||
-- The encrypted private key MUST be placed in the envelopedData
|
|
||||||
-- encryptedContentInfo encryptedContent OCTET STRING.
|
|
||||||
|
|
||||||
EncryptedValue ::= SEQUENCE {
|
|
||||||
intendedAlg [0] AlgorithmIdentifier OPTIONAL,
|
|
||||||
-- the intended algorithm for which the value will be used
|
|
||||||
symmAlg [1] AlgorithmIdentifier OPTIONAL,
|
|
||||||
-- the symmetric algorithm used to encrypt the value
|
|
||||||
encSymmKey [2] BIT STRING OPTIONAL,
|
|
||||||
-- the (encrypted) symmetric key used to encrypt the value
|
|
||||||
keyAlg [3] AlgorithmIdentifier OPTIONAL,
|
|
||||||
-- algorithm used to encrypt the symmetric key
|
|
||||||
valueHint [4] OCTET STRING OPTIONAL,
|
|
||||||
-- a brief description or identifier of the encValue content
|
|
||||||
-- (may be meaningful only to the sending entity, and used only
|
|
||||||
-- if EncryptedValue might be re-examined by the sending entity
|
|
||||||
-- in the future)
|
|
||||||
encValue BIT STRING }
|
|
||||||
-- the encrypted value itself
|
|
||||||
-- When EncryptedValue is used to carry a private key (as opposed to
|
|
||||||
-- a certificate), implementations MUST support the encValue field
|
|
||||||
-- containing an encrypted PrivateKeyInfo as defined in [PKCS11],
|
|
||||||
-- section 12.11. If encValue contains some other format/encoding
|
|
||||||
-- for the private key, the first octet of valueHint MAY be used
|
|
||||||
-- to indicate the format/encoding (but note that the possible values
|
|
||||||
-- of this octet are not specified at this time). In all cases, the
|
|
||||||
-- intendedAlg field MUST be used to indicate at least the OID of
|
|
||||||
-- the intended algorithm of the private key, unless this information
|
|
||||||
-- is known a priori to both sender and receiver by some other means.
|
|
||||||
|
|
||||||
KeyGenParameters ::= OCTET STRING
|
|
||||||
|
|
||||||
id-regCtrl-oldCertID OBJECT IDENTIFIER ::= { id-regCtrl 5 }
|
|
||||||
--with syntax:
|
|
||||||
OldCertId ::= CertId
|
|
||||||
|
|
||||||
CertId ::= SEQUENCE {
|
|
||||||
issuer GeneralName,
|
|
||||||
serialNumber INTEGER }
|
|
||||||
|
|
||||||
id-regCtrl-protocolEncrKey OBJECT IDENTIFIER ::= { id-regCtrl 6 }
|
|
||||||
--with syntax:
|
|
||||||
ProtocolEncrKey ::= SubjectPublicKeyInfo
|
|
||||||
|
|
||||||
-- Registration Info in CRMF
|
|
||||||
id-regInfo OBJECT IDENTIFIER ::= { id-pkip 2 }
|
|
||||||
|
|
||||||
id-regInfo-utf8Pairs OBJECT IDENTIFIER ::= { id-regInfo 1 }
|
|
||||||
--with syntax
|
|
||||||
UTF8Pairs ::= UTF8String
|
|
||||||
|
|
||||||
|
|
||||||
id-regInfo-certReq OBJECT IDENTIFIER ::= { id-regInfo 2 }
|
|
||||||
--with syntax
|
|
||||||
CertReq ::= CertRequest
|
|
||||||
|
|
||||||
-- id-ct-encKeyWithID is a new content type used for CMS objects.
|
|
||||||
-- it contains both a private key and an identifier for key escrow
|
|
||||||
-- agents to check against recovery requestors.
|
|
||||||
|
|
||||||
id-ct-encKeyWithID OBJECT IDENTIFIER ::= {id-ct 21}
|
|
||||||
|
|
||||||
EncKeyWithID ::= SEQUENCE {
|
|
||||||
privateKey PrivateKeyInfo,
|
|
||||||
identifier CHOICE {
|
|
||||||
string UTF8String,
|
|
||||||
generalName GeneralName
|
|
||||||
} OPTIONAL
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivateKeyInfo ::= SEQUENCE {
|
|
||||||
version INTEGER,
|
|
||||||
privateKeyAlgorithm AlgorithmIdentifier,
|
|
||||||
privateKey OCTET STRING,
|
|
||||||
attributes [0] IMPLICIT Attributes OPTIONAL
|
|
||||||
}
|
|
||||||
|
|
||||||
Attributes ::= SET OF Attribute
|
|
||||||
|
|
||||||
END
|
|
425
asn/rfc6402.asn
425
asn/rfc6402.asn
@ -1,425 +0,0 @@
|
|||||||
EnrollmentMessageSyntax-2011-v88
|
|
||||||
{ iso(1) identified-organization(3) dod(6) internet(1)
|
|
||||||
security(5) mechanisms(5) pkix(7) id-mod(0)
|
|
||||||
id-mod-enrollMsgSyntax-2011-88(76) }
|
|
||||||
|
|
||||||
DEFINITIONS IMPLICIT TAGS ::=
|
|
||||||
BEGIN
|
|
||||||
|
|
||||||
-- EXPORTS All --
|
|
||||||
-- The types and values defined in this module are exported for use
|
|
||||||
-- in the other ASN.1 modules. Other applications may use them for
|
|
||||||
-- their own purposes.
|
|
||||||
|
|
||||||
-- fake imports
|
|
||||||
|
|
||||||
-- PKIX Part 1 - Implicit From [RFC5280]
|
|
||||||
GeneralName ::= CHOICE { any ANY }
|
|
||||||
CRLReason ::= INTEGER
|
|
||||||
ReasonFlags ::= BIT STRING
|
|
||||||
GeneralNames ::= ANY
|
|
||||||
|
|
||||||
-- PKIX Part 1 - Explicit From [RFC5280]
|
|
||||||
AlgorithmIdentifier ::= ANY
|
|
||||||
Extension ::= ANY
|
|
||||||
Name ::= CHOICE { any ANY }
|
|
||||||
CertificateSerialNumber ::= INTEGER
|
|
||||||
|
|
||||||
-- Cryptographic Message Syntax FROM [CMS]
|
|
||||||
ContentInfo ::= ANY
|
|
||||||
Attribute ::= ANY
|
|
||||||
IssuerAndSerialNumber ::= ANY
|
|
||||||
|
|
||||||
-- CRMF FROM [RFC4211]
|
|
||||||
CertReqMsg ::= ANY
|
|
||||||
PKIPublicationInfo ::= ANY
|
|
||||||
CertTemplate ::= ANY
|
|
||||||
|
|
||||||
-- Global Types
|
|
||||||
-- UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
|
|
||||||
-- The content of this type conforms to RFC 3629.
|
|
||||||
|
|
||||||
id-pkix OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
|
|
||||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7) }
|
|
||||||
id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
|
|
||||||
id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
|
|
||||||
|
|
||||||
id-cmc OBJECT IDENTIFIER ::= {id-pkix 7} -- CMC controls
|
|
||||||
id-cct OBJECT IDENTIFIER ::= {id-pkix 12} -- CMC content types
|
|
||||||
|
|
||||||
-- The following controls have the type OCTET STRING
|
|
||||||
|
|
||||||
id-cmc-identityProof OBJECT IDENTIFIER ::= {id-cmc 3}
|
|
||||||
id-cmc-dataReturn OBJECT IDENTIFIER ::= {id-cmc 4}
|
|
||||||
id-cmc-regInfo OBJECT IDENTIFIER ::= {id-cmc 18}
|
|
||||||
id-cmc-responseInfo OBJECT IDENTIFIER ::= {id-cmc 19}
|
|
||||||
id-cmc-queryPending OBJECT IDENTIFIER ::= {id-cmc 21}
|
|
||||||
id-cmc-popLinkRandom OBJECT IDENTIFIER ::= {id-cmc 22}
|
|
||||||
id-cmc-popLinkWitness OBJECT IDENTIFIER ::= {id-cmc 23}
|
|
||||||
|
|
||||||
-- The following controls have the type UTF8String
|
|
||||||
|
|
||||||
id-cmc-identification OBJECT IDENTIFIER ::= {id-cmc 2}
|
|
||||||
|
|
||||||
-- The following controls have the type INTEGER
|
|
||||||
|
|
||||||
id-cmc-transactionId OBJECT IDENTIFIER ::= {id-cmc 5}
|
|
||||||
|
|
||||||
-- The following controls have the type OCTET STRING
|
|
||||||
|
|
||||||
id-cmc-senderNonce OBJECT IDENTIFIER ::= {id-cmc 6}
|
|
||||||
id-cmc-recipientNonce OBJECT IDENTIFIER ::= {id-cmc 7}
|
|
||||||
|
|
||||||
-- This is the content type used for a request message
|
|
||||||
-- in the protocol
|
|
||||||
|
|
||||||
id-cct-PKIData OBJECT IDENTIFIER ::= { id-cct 2 }
|
|
||||||
|
|
||||||
PKIData ::= SEQUENCE {
|
|
||||||
controlSequence SEQUENCE SIZE(0..MAX) OF TaggedAttribute,
|
|
||||||
reqSequence SEQUENCE SIZE(0..MAX) OF TaggedRequest,
|
|
||||||
cmsSequence SEQUENCE SIZE(0..MAX) OF TaggedContentInfo,
|
|
||||||
otherMsgSequence SEQUENCE SIZE(0..MAX) OF OtherMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyIdMax INTEGER ::= 4294967295
|
|
||||||
|
|
||||||
BodyPartID ::= INTEGER(0..bodyIdMax)
|
|
||||||
|
|
||||||
TaggedAttribute ::= SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
attrType OBJECT IDENTIFIER,
|
|
||||||
attrValues SET OF AttributeValue
|
|
||||||
}
|
|
||||||
|
|
||||||
AttributeValue ::= ANY
|
|
||||||
|
|
||||||
TaggedRequest ::= CHOICE {
|
|
||||||
tcr [0] TaggedCertificationRequest,
|
|
||||||
crm [1] CertReqMsg,
|
|
||||||
orm [2] SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
requestMessageType OBJECT IDENTIFIER,
|
|
||||||
requestMessageValue ANY DEFINED BY requestMessageType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaggedCertificationRequest ::= SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
certificationRequest CertificationRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
CertificationRequest ::= SEQUENCE {
|
|
||||||
certificationRequestInfo SEQUENCE {
|
|
||||||
version INTEGER,
|
|
||||||
subject Name,
|
|
||||||
subjectPublicKeyInfo SEQUENCE {
|
|
||||||
algorithm AlgorithmIdentifier,
|
|
||||||
subjectPublicKey BIT STRING },
|
|
||||||
attributes [0] IMPLICIT SET OF Attribute },
|
|
||||||
signatureAlgorithm AlgorithmIdentifier,
|
|
||||||
signature BIT STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
TaggedContentInfo ::= SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
contentInfo ContentInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
OtherMsg ::= SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
otherMsgType OBJECT IDENTIFIER,
|
|
||||||
otherMsgValue ANY DEFINED BY otherMsgType }
|
|
||||||
|
|
||||||
-- This defines the response message in the protocol
|
|
||||||
id-cct-PKIResponse OBJECT IDENTIFIER ::= { id-cct 3 }
|
|
||||||
|
|
||||||
|
|
||||||
ResponseBody ::= PKIResponse
|
|
||||||
|
|
||||||
PKIResponse ::= SEQUENCE {
|
|
||||||
controlSequence SEQUENCE SIZE(0..MAX) OF TaggedAttribute,
|
|
||||||
cmsSequence SEQUENCE SIZE(0..MAX) OF TaggedContentInfo,
|
|
||||||
otherMsgSequence SEQUENCE SIZE(0..MAX) OF OtherMsg
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Used to return status state in a response
|
|
||||||
|
|
||||||
id-cmc-statusInfo OBJECT IDENTIFIER ::= {id-cmc 1}
|
|
||||||
|
|
||||||
CMCStatusInfo ::= SEQUENCE {
|
|
||||||
cMCStatus CMCStatus,
|
|
||||||
bodyList SEQUENCE SIZE (1..MAX) OF BodyPartID,
|
|
||||||
statusString UTF8String OPTIONAL,
|
|
||||||
otherInfo CHOICE {
|
|
||||||
failInfo CMCFailInfo,
|
|
||||||
pendInfo PendInfo } OPTIONAL
|
|
||||||
}
|
|
||||||
|
|
||||||
PendInfo ::= SEQUENCE {
|
|
||||||
pendToken OCTET STRING,
|
|
||||||
pendTime GeneralizedTime
|
|
||||||
}
|
|
||||||
|
|
||||||
CMCStatus ::= INTEGER {
|
|
||||||
success (0),
|
|
||||||
failed (2),
|
|
||||||
pending (3),
|
|
||||||
noSupport (4),
|
|
||||||
confirmRequired (5),
|
|
||||||
popRequired (6),
|
|
||||||
partial (7)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-- Note:
|
|
||||||
-- The spelling of unsupportedExt is corrected in this version.
|
|
||||||
-- In RFC 2797, it was unsuportedExt.
|
|
||||||
|
|
||||||
CMCFailInfo ::= INTEGER {
|
|
||||||
badAlg (0),
|
|
||||||
badMessageCheck (1),
|
|
||||||
badRequest (2),
|
|
||||||
badTime (3),
|
|
||||||
badCertId (4),
|
|
||||||
unsupportedExt (5),
|
|
||||||
mustArchiveKeys (6),
|
|
||||||
badIdentity (7),
|
|
||||||
popRequired (8),
|
|
||||||
popFailed (9),
|
|
||||||
noKeyReuse (10),
|
|
||||||
internalCAError (11),
|
|
||||||
tryLater (12),
|
|
||||||
authDataFail (13)
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Used for RAs to add extensions to certification requests
|
|
||||||
id-cmc-addExtensions OBJECT IDENTIFIER ::= {id-cmc 8}
|
|
||||||
|
|
||||||
AddExtensions ::= SEQUENCE {
|
|
||||||
pkiDataReference BodyPartID,
|
|
||||||
certReferences SEQUENCE OF BodyPartID,
|
|
||||||
extensions SEQUENCE OF Extension
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
id-cmc-encryptedPOP OBJECT IDENTIFIER ::= {id-cmc 9}
|
|
||||||
id-cmc-decryptedPOP OBJECT IDENTIFIER ::= {id-cmc 10}
|
|
||||||
|
|
||||||
EncryptedPOP ::= SEQUENCE {
|
|
||||||
request TaggedRequest,
|
|
||||||
cms ContentInfo,
|
|
||||||
thePOPAlgID AlgorithmIdentifier,
|
|
||||||
witnessAlgID AlgorithmIdentifier,
|
|
||||||
witness OCTET STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
DecryptedPOP ::= SEQUENCE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
thePOPAlgID AlgorithmIdentifier,
|
|
||||||
thePOP OCTET STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
id-cmc-lraPOPWitness OBJECT IDENTIFIER ::= {id-cmc 11}
|
|
||||||
|
|
||||||
LraPopWitness ::= SEQUENCE {
|
|
||||||
pkiDataBodyid BodyPartID,
|
|
||||||
bodyIds SEQUENCE OF BodyPartID
|
|
||||||
}
|
|
||||||
|
|
||||||
--
|
|
||||||
id-cmc-getCert OBJECT IDENTIFIER ::= {id-cmc 15}
|
|
||||||
|
|
||||||
GetCert ::= SEQUENCE {
|
|
||||||
issuerName GeneralName,
|
|
||||||
serialNumber INTEGER }
|
|
||||||
|
|
||||||
id-cmc-getCRL OBJECT IDENTIFIER ::= {id-cmc 16}
|
|
||||||
|
|
||||||
GetCRL ::= SEQUENCE {
|
|
||||||
issuerName Name,
|
|
||||||
cRLName GeneralName OPTIONAL,
|
|
||||||
time GeneralizedTime OPTIONAL,
|
|
||||||
reasons ReasonFlags OPTIONAL }
|
|
||||||
|
|
||||||
id-cmc-revokeRequest OBJECT IDENTIFIER ::= {id-cmc 17}
|
|
||||||
|
|
||||||
RevokeRequest ::= SEQUENCE {
|
|
||||||
issuerName Name,
|
|
||||||
serialNumber INTEGER,
|
|
||||||
reason CRLReason,
|
|
||||||
invalidityDate GeneralizedTime OPTIONAL,
|
|
||||||
passphrase OCTET STRING OPTIONAL,
|
|
||||||
comment UTF8String OPTIONAL }
|
|
||||||
|
|
||||||
id-cmc-confirmCertAcceptance OBJECT IDENTIFIER ::= {id-cmc 24}
|
|
||||||
|
|
||||||
CMCCertId ::= IssuerAndSerialNumber
|
|
||||||
|
|
||||||
-- The following is used to request V3 extensions be added to a
|
|
||||||
-- certificate
|
|
||||||
|
|
||||||
id-ExtensionReq OBJECT IDENTIFIER ::= {iso(1) member-body(2)
|
|
||||||
us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 14}
|
|
||||||
|
|
||||||
ExtensionReq ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
||||||
|
|
||||||
-- The following exists to allow Diffie-Hellman Certification
|
|
||||||
-- Request Messages to be well-formed
|
|
||||||
|
|
||||||
id-alg-noSignature OBJECT IDENTIFIER ::= {id-pkix id-alg(6) 2}
|
|
||||||
|
|
||||||
NoSignatureValue ::= OCTET STRING
|
|
||||||
|
|
||||||
-- Unauthenticated attribute to carry removable data.
|
|
||||||
-- This could be used in an update of "CMC Extensions: Server
|
|
||||||
-- Side Key Generation and Key Escrow" (February 2005) and in
|
|
||||||
-- other documents.
|
|
||||||
|
|
||||||
id-aa OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840)
|
|
||||||
rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-aa(2)}
|
|
||||||
id-aa-cmc-unsignedData OBJECT IDENTIFIER ::= {id-aa 34}
|
|
||||||
|
|
||||||
CMCUnsignedData ::= SEQUENCE {
|
|
||||||
bodyPartPath BodyPartPath,
|
|
||||||
identifier OBJECT IDENTIFIER,
|
|
||||||
content ANY DEFINED BY identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Replaces CMC Status Info
|
|
||||||
--
|
|
||||||
|
|
||||||
id-cmc-statusInfoV2 OBJECT IDENTIFIER ::= {id-cmc 25}
|
|
||||||
|
|
||||||
CMCStatusInfoV2 ::= SEQUENCE {
|
|
||||||
cMCStatus CMCStatus,
|
|
||||||
bodyList SEQUENCE SIZE (1..MAX) OF
|
|
||||||
BodyPartReference,
|
|
||||||
statusString UTF8String OPTIONAL,
|
|
||||||
otherInfo CHOICE {
|
|
||||||
failInfo CMCFailInfo,
|
|
||||||
pendInfo PendInfo,
|
|
||||||
extendedFailInfo SEQUENCE {
|
|
||||||
failInfoOID OBJECT IDENTIFIER,
|
|
||||||
failInfoValue AttributeValue
|
|
||||||
}
|
|
||||||
} OPTIONAL
|
|
||||||
}
|
|
||||||
|
|
||||||
BodyPartReference ::= CHOICE {
|
|
||||||
bodyPartID BodyPartID,
|
|
||||||
bodyPartPath BodyPartPath
|
|
||||||
}
|
|
||||||
|
|
||||||
BodyPartPath ::= SEQUENCE SIZE (1..MAX) OF BodyPartID
|
|
||||||
|
|
||||||
-- Allow for distribution of trust anchors
|
|
||||||
--
|
|
||||||
|
|
||||||
id-cmc-trustedAnchors OBJECT IDENTIFIER ::= {id-cmc 26}
|
|
||||||
|
|
||||||
PublishTrustAnchors ::= SEQUENCE {
|
|
||||||
seqNumber INTEGER,
|
|
||||||
hashAlgorithm AlgorithmIdentifier,
|
|
||||||
anchorHashes SEQUENCE OF OCTET STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
id-cmc-authData OBJECT IDENTIFIER ::= {id-cmc 27}
|
|
||||||
|
|
||||||
AuthPublish ::= BodyPartID
|
|
||||||
|
|
||||||
-- These two items use BodyPartList
|
|
||||||
id-cmc-batchRequests OBJECT IDENTIFIER ::= {id-cmc 28}
|
|
||||||
id-cmc-batchResponses OBJECT IDENTIFIER ::= {id-cmc 29}
|
|
||||||
|
|
||||||
BodyPartList ::= SEQUENCE SIZE (1..MAX) OF BodyPartID
|
|
||||||
|
|
||||||
--
|
|
||||||
id-cmc-publishCert OBJECT IDENTIFIER ::= {id-cmc 30}
|
|
||||||
|
|
||||||
CMCPublicationInfo ::= SEQUENCE {
|
|
||||||
hashAlg AlgorithmIdentifier,
|
|
||||||
certHashes SEQUENCE OF OCTET STRING,
|
|
||||||
pubInfo PKIPublicationInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
id-cmc-modCertTemplate OBJECT IDENTIFIER ::= {id-cmc 31}
|
|
||||||
|
|
||||||
ModCertTemplate ::= SEQUENCE {
|
|
||||||
pkiDataReference BodyPartPath,
|
|
||||||
certReferences BodyPartList,
|
|
||||||
replace BOOLEAN DEFAULT TRUE,
|
|
||||||
certTemplate CertTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Inform follow-on servers that one or more controls have already
|
|
||||||
-- been processed
|
|
||||||
|
|
||||||
id-cmc-controlProcessed OBJECT IDENTIFIER ::= {id-cmc 32}
|
|
||||||
|
|
||||||
ControlsProcessed ::= SEQUENCE {
|
|
||||||
bodyList SEQUENCE SIZE(1..MAX) OF BodyPartReference
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Identity Proof control w/ algorithm agility
|
|
||||||
|
|
||||||
id-cmc-identityProofV2 OBJECT IDENTIFIER ::= { id-cmc 34 }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
IdentifyProofV2 ::= SEQUENCE {
|
|
||||||
proofAlgID AlgorithmIdentifier,
|
|
||||||
macAlgId AlgorithmIdentifier,
|
|
||||||
witness OCTET STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
id-cmc-popLinkWitnessV2 OBJECT IDENTIFIER ::= { id-cmc 33 }
|
|
||||||
PopLinkWitnessV2 ::= SEQUENCE {
|
|
||||||
keyGenAlgorithm AlgorithmIdentifier,
|
|
||||||
macAlgorithm AlgorithmIdentifier,
|
|
||||||
witness OCTET STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
id-cmc-raIdentityWitness OBJECT IDENTIFIER ::= {id-cmc 35}
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Allow for an End-Entity to request a change in name.
|
|
||||||
-- This item is added to RegControlSet in CRMF.
|
|
||||||
--
|
|
||||||
|
|
||||||
id-cmc-changeSubjectName OBJECT IDENTIFIER ::= {id-cmc 36}
|
|
||||||
|
|
||||||
ChangeSubjectName ::= SEQUENCE {
|
|
||||||
subject Name OPTIONAL,
|
|
||||||
subjectAlt GeneralNames OPTIONAL
|
|
||||||
}
|
|
||||||
-- (WITH COMPONENTS {..., subject PRESENT} |
|
|
||||||
-- WITH COMPONENTS {..., subjectAlt PRESENT} )
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Embedded response from a third party for processing
|
|
||||||
--
|
|
||||||
|
|
||||||
id-cmc-responseBody OBJECT IDENTIFIER ::= {id-cmc 37}
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Key purpose identifiers are in the Extended Key Usage extension
|
|
||||||
--
|
|
||||||
|
|
||||||
id-kp-cmcCA OBJECT IDENTIFIER ::= { id-kp 27 }
|
|
||||||
id-kp-cmcRA OBJECT IDENTIFIER ::= { id-kp 28 }
|
|
||||||
id-kp-cmcArchive OBJECT IDENTIFIER ::= { id-kp 28 }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Subject Information Access identifier
|
|
||||||
--
|
|
||||||
|
|
||||||
id-ad-cmc OBJECT IDENTIFIER ::= { id-ad 12 }
|
|
||||||
|
|
||||||
END
|
|
43
bin/anchor
43
bin/anchor
@ -1,43 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
if [ "$1" = "-h" -o "$1" = "--help" ] ; then
|
|
||||||
echo "Usage: [PY=optional/path/python] $0"
|
|
||||||
echo
|
|
||||||
echo "Run Anchor with default uwsgi settings. It will spawn 4 workers"
|
|
||||||
echo "and use either the default reachable 'python' or one defined in the"
|
|
||||||
echo "\$PY variable."
|
|
||||||
echo
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! which uwsgi > /dev/null ; then
|
|
||||||
echo "You need to install uwsgi to run anchor using this script."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PY=${PY:-python}
|
|
||||||
|
|
||||||
if ! [ -x "$PY" ] ; then
|
|
||||||
if ! [ -x "$(which "$PY")" ] ; then
|
|
||||||
echo "Python interpreter not found (use PY variable to specify)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
STR="import pkg_resources; print(pkg_resources.get_distribution('anchor').location)"
|
|
||||||
PKG_PATH=$("$PY" -c "$STR")
|
|
||||||
|
|
||||||
if ! [ -d "$PKG_PATH" ] ; then
|
|
||||||
echo "Cannot find installed anchor package."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$VIRTUAL_ENV" ]; then
|
|
||||||
OPTS="-p 4"
|
|
||||||
else
|
|
||||||
OPTS="--venv ""${VIRTUAL_ENV}"" -p 4"
|
|
||||||
fi
|
|
||||||
|
|
||||||
uwsgi --http-socket :5016 \
|
|
||||||
--pecan "${PKG_PATH}/anchor/config.py" \
|
|
||||||
${OPTS}
|
|
@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
VENV=$1
|
|
||||||
|
|
||||||
[ -n "$VENV" ] || ( echo "provide virtual env path as parameter" && exit 1 )
|
|
||||||
|
|
||||||
"$VENV/bin/pecan" serve --reload config.py
|
|
@ -1,67 +0,0 @@
|
|||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from subprocess import call
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logging.basicConfig()
|
|
||||||
logger = logging.getLogger('Anchor_Bootstrap')
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# This script looks for two mounted volumes '/key' and '/config'. They can
|
|
||||||
# contain key material and configuration files respectively. If data is found
|
|
||||||
# in either of these volumes it will be used to over-write the defaults within
|
|
||||||
# the Anchor container.
|
|
||||||
# In the case that '/key' is empty. This script will generate a new private key
|
|
||||||
# and copy that over the one to be used by Anchor.
|
|
||||||
# In the case that '/config' is empty no action will be taken
|
|
||||||
|
|
||||||
# It's worth noting that the default location for key material can be modified
|
|
||||||
# in the config.json. That's really up to the deployer.
|
|
||||||
|
|
||||||
# The reason we have a separate /key volume is to trigger a new key to be
|
|
||||||
# created even if we want to use a default configuration.
|
|
||||||
|
|
||||||
newkey_newcert = ["openssl", "req", "-out", "CA/root-ca.crt", "-keyout",
|
|
||||||
"CA/root-ca-unwrapped.key", "-newkey", "rsa:4096", "-subj",
|
|
||||||
"/CN=Anchor Test CA", "-nodes", "-x509", "-days", "365"]
|
|
||||||
|
|
||||||
newcert_existkey = ["openssl", "req", "-new" "-out", "CA/root-ca.crt", "-key",
|
|
||||||
"/key/root-ca-unwrapped.key", "-subj", "/CN=Anchor Test CA",
|
|
||||||
"-nodes", "-x509", "-days", "365"]
|
|
||||||
|
|
||||||
# Anchor containers no longer build with built in keys. See if a deployer has
|
|
||||||
# provided a key, if they have, use that. If not then build one now. The key
|
|
||||||
# built in this way will disappear along with the container.
|
|
||||||
if os.path.exists('/key/root-ca-unwrapped.key'):
|
|
||||||
if os.path.exists('/key/root-ca.crt'):
|
|
||||||
# Provided both a key and a certificate
|
|
||||||
logger.info("Private key and certificate provided")
|
|
||||||
shutil.copy2('/key/root-ca-unwrapped.key', 'CA/')
|
|
||||||
shutil.copy2('/key/root-ca.crt', 'CA/')
|
|
||||||
os.chmod('CA/root-ca-unwrapped.key', 0400)
|
|
||||||
else:
|
|
||||||
# Provided key but no certificate
|
|
||||||
logger.info("Key provided without certificate. Generating certificate")
|
|
||||||
call(newcert_existingkey)
|
|
||||||
shutil.copy2('/key/root-ca-unwrapped.key', 'CA/')
|
|
||||||
os.chmod('CA/root-ca-unwrapped.key', 0400)
|
|
||||||
else:
|
|
||||||
logger.info("No key provided, Anchor will generate a dynamic one")
|
|
||||||
logger.info("To use a persistent key, create one and provide it in a key volume")
|
|
||||||
logger.info("Generating new key and certificate")
|
|
||||||
call(newkey_newcert) #No key or cert provided. Possibly no /key volume at all
|
|
||||||
os.chmod('CA/root-ca-unwrapped.key', 0400)
|
|
||||||
|
|
||||||
|
|
||||||
# If the user has provdided a config file in a /config volume, use that
|
|
||||||
#/config
|
|
||||||
if os.path.exists('/config/config.json'):
|
|
||||||
shutil.copy2('/config/config.json','./')
|
|
||||||
|
|
||||||
if os.path.exists('/config/config.py'):
|
|
||||||
shutil.copy2('/config/config.py','./')
|
|
||||||
|
|
||||||
#Start the pecan service
|
|
||||||
call(['pecan','serve','config.py'])
|
|
36
config.json
36
config.json
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"authentication": {
|
|
||||||
"method_1": {
|
|
||||||
"backend": "static",
|
|
||||||
"secret": "simplepassword",
|
|
||||||
"user": "myusername"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"signing_ca": {
|
|
||||||
"local": {
|
|
||||||
"backend": "anchor",
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"key_path": "CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"registration_authority": {
|
|
||||||
"default": {
|
|
||||||
"authentication": "method_1",
|
|
||||||
"signing_ca": "local",
|
|
||||||
"validators": {
|
|
||||||
"standards_compliance": {
|
|
||||||
"label_re": "^[a-z](?:[-a-z0-9]*[a-z0-9])?$"
|
|
||||||
},
|
|
||||||
"source_cidrs": {
|
|
||||||
"cidrs": ["127.0.0.0/8"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"audit": {
|
|
||||||
"target": "log"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
API version 1
|
|
||||||
=============
|
|
||||||
|
|
||||||
The following endpoints are available in version 1 of the API.
|
|
||||||
|
|
||||||
/robots.txt (GET)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Prevents attempts to index the service.
|
|
||||||
|
|
||||||
/v1/sign/<registration_authority> (POST)
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
Requests signing of the CSR provided in the POST parameters. The request is
|
|
||||||
processed by the selected virtual registration authority.
|
|
||||||
|
|
||||||
Request parameters
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* ``user``: username used in authentication (optional)
|
|
||||||
* ``secret``: secret used in authentication
|
|
||||||
* ``encoding``: request encoding - currently supported: "pem"
|
|
||||||
* ``csr``: the text of the submitted CSR
|
|
||||||
|
|
||||||
Result
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Signed certificate
|
|
||||||
|
|
||||||
/v1/ca/<registration_authority> (GET)
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
Requests the CA which would be used to sign the request made to this
|
|
||||||
registration authority.
|
|
||||||
|
|
||||||
Do *NOT* use this to retrieve the certificate needed to trust communication
|
|
||||||
with Anchor. Connection to Anchor *MUST* be fully trusted before using this
|
|
||||||
endpoint for further configuration.
|
|
||||||
|
|
||||||
Request parameters
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* ``encoding``: request encoding - currently supported: "pem"
|
|
||||||
|
|
||||||
Result
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
CA certificate used to sign for this RA.
|
|
@ -1,27 +0,0 @@
|
|||||||
Audit
|
|
||||||
=====
|
|
||||||
|
|
||||||
Anchor produces audit messages using the PyCADF library and aims for CADF
|
|
||||||
compatibility. The two events being emitted right now are ``audit.sign`` and
|
|
||||||
``audit.auth``, used for certificate signing and authentication events
|
|
||||||
respectively.
|
|
||||||
|
|
||||||
In the configuration, audit events can be sent either to the log stream, or
|
|
||||||
to the standard openstack message queue. This is configured using the
|
|
||||||
``audit.target`` option. See the :doc:`configuration section <configuration>`
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
Capturing events in Ceilometer
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
In order to get events processed by Ceilometer, two configuration files need to
|
|
||||||
be provided - event pipeline and definitions. The default
|
|
||||||
``event_pipeline.yaml`` as described in Ceilometer documentation is compatible
|
|
||||||
with Anchor. As for ``event_definitions.yaml``, it needs to include the
|
|
||||||
``audit.auth`` and ``audit.sign`` events.
|
|
||||||
|
|
||||||
On the Ceilometer side, it needs the `notification agent`_ installed in order
|
|
||||||
to receive data from the message queue. Add incoming events will then be saved
|
|
||||||
and visible after running ``ceilometer event-list``.
|
|
||||||
|
|
||||||
.. _notification agent: http://docs.openstack.org/developer/ceilometer/architecture.html#notification-agents-listening-for-data
|
|
@ -1,258 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Anchor documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Wed Jul 29 15:52:08 2015.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = []
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'Anchor'
|
|
||||||
copyright = u'2015'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = 'dev'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = 'dev'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
#keep_warnings = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
#html_theme = 'alabaster'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
#html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
#html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
#html_extra_path = []
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'Anchordoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#'preamble': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'Anchor.tex', u'Anchor Documentation',
|
|
||||||
u'see git log', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'anchor', u'Anchor Documentation',
|
|
||||||
[u'see git log'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
('index', 'Anchor', u'Anchor Documentation',
|
|
||||||
u'see git log', 'Anchor', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
#texinfo_no_detailmenu = False
|
|
@ -1,240 +0,0 @@
|
|||||||
Configuration files
|
|
||||||
===================
|
|
||||||
|
|
||||||
Anchor is configured using two files: ``config.py`` and ``config.json``. The
|
|
||||||
first one defines the Python and webservice related values. You can change the
|
|
||||||
listening iterface address and port there, as well as logging details to suit
|
|
||||||
your deployment. The second configuration defines the service behaviour at
|
|
||||||
runtime.
|
|
||||||
|
|
||||||
There are three main sections at the moment: ``authentication`` for
|
|
||||||
authentication parameters, ``signing_ca`` for defining signing authorities, and
|
|
||||||
``registration_authority`` for listing virtual registration authorities which
|
|
||||||
can be selected by client requests.
|
|
||||||
|
|
||||||
The main ``config.json`` structure looks like this:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"authentication": { ... },
|
|
||||||
"signing_ca": { ... },
|
|
||||||
"registration_authority": { ... }
|
|
||||||
}
|
|
||||||
|
|
||||||
Each block apart from ``registration_authority`` defines a number of mapping
|
|
||||||
from labels to definitions. Those labels can then be used in the
|
|
||||||
``registration_authority`` block to refer to settings defined earlier.
|
|
||||||
|
|
||||||
Authentication
|
|
||||||
--------------
|
|
||||||
|
|
||||||
The authentication block can define any number of authentication blocks, each
|
|
||||||
using one specific authentication backend.
|
|
||||||
|
|
||||||
Currently available authentication methods are: ``static``, ``keystone``, and
|
|
||||||
``ldap``.
|
|
||||||
|
|
||||||
Static
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Username and password are present in ``config.json``. This mode should be used
|
|
||||||
only for development and testing.
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"authentication": {
|
|
||||||
"method_1": {
|
|
||||||
"backend": "static",
|
|
||||||
"secret": "simplepassword",
|
|
||||||
"user": "myusername"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keystone
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
Username is ignored, but password is a token valid in the configured keystone
|
|
||||||
location.
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"authentication": {
|
|
||||||
"method_2": {
|
|
||||||
"backend": "keystone",
|
|
||||||
"url": "https://keystone.example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LDAP
|
|
||||||
~~~~
|
|
||||||
|
|
||||||
Username and password are used to bind to an LDAP user in a configured domain.
|
|
||||||
User's groups for the ``server_group`` filter are retrieved from attribute
|
|
||||||
``memberOf`` in search for ``(sAMAccountName=username@domain)``. The search is done
|
|
||||||
in the configured base.
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"authentication": {
|
|
||||||
"method_3": {
|
|
||||||
"backend": "ldap",
|
|
||||||
"host": "ldap.example.com",
|
|
||||||
"base": "ou=Users,dc=example,dc=com",
|
|
||||||
"domain": "example.com",
|
|
||||||
"port": 636,
|
|
||||||
"ssl": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Signing authority
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The ``signing_ca`` section defines any number of signing authorities which can
|
|
||||||
be referenced later on. Currently there's only one, default implementation
|
|
||||||
which uses local files. An example configuration looks like this.
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"signing_ca": {
|
|
||||||
"local": {
|
|
||||||
"backend": "anchor",
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"key_path": "CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Anchor allows the use of configurable signing backend. While it provides a
|
|
||||||
default implementation (based on cryptography.io and OpenSSL), other
|
|
||||||
implementations may be configured. The backend is configured by setting the
|
|
||||||
``backend`` value to the name of the right entry point. Backend implementations
|
|
||||||
need to provide only one function: ``sign(csr, config)``, taking the parsed CSR
|
|
||||||
and their own ``singing_ca`` block of the configuration as parameters and
|
|
||||||
returning signed certificate in PEM format.
|
|
||||||
|
|
||||||
The backends are loaded using the ``stevedore`` module from the registered
|
|
||||||
entry points. The name space is ``anchor.signing_backends``.
|
|
||||||
|
|
||||||
Each backend may take different configuration options. Please refer to
|
|
||||||
:doc:`signing backends section </signing_backends>`.
|
|
||||||
|
|
||||||
|
|
||||||
Virtual registration authority
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
The registration authority section puts together previously described elements
|
|
||||||
and the list of validators applied to each request.
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"registration_authority": {
|
|
||||||
"default": {
|
|
||||||
"authentication": "method_1",
|
|
||||||
"signing_ca": "local",
|
|
||||||
"validators": {
|
|
||||||
"ca_status": {
|
|
||||||
"ca_requested": false
|
|
||||||
},
|
|
||||||
"source_cidrs": {
|
|
||||||
"cidrs": [ "127.0.0.0/8" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fixups": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
In the example above, CSRs sent to registration authority ``default`` will be
|
|
||||||
authenticated using previously defined block ``method_1``, will be validated
|
|
||||||
against two validators (``ca_status`` and ``source_cidrs``) and if they pass,
|
|
||||||
the CSR will be signed by the previously defined signing ca called ``local``.
|
|
||||||
|
|
||||||
Each validator has its own set of parameters described separately in the
|
|
||||||
:doc:`validators section </validators>`. Same for fixups described in
|
|
||||||
:doc:`fixups section </fixups>`
|
|
||||||
|
|
||||||
|
|
||||||
Audit
|
|
||||||
-----
|
|
||||||
|
|
||||||
Audit has two possible targets: ``log`` for output in the standard logging
|
|
||||||
stream and ``messaging`` for the openstack message queue. The first one doesn't
|
|
||||||
require any extra options:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"audit": {
|
|
||||||
"target": "log"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
The message queue version requires defining a target in a way compatible with
|
|
||||||
``oslo_messaging`` `transport URIs`_. For example:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"audit": {
|
|
||||||
"target": "messaging",
|
|
||||||
"url": "rabbit:guest@localhost:5672"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Example configuration
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"authentication": {
|
|
||||||
"method_1": {
|
|
||||||
"backend": "static",
|
|
||||||
"secret": "simplepassword",
|
|
||||||
"user": "myusername"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"signing_ca": {
|
|
||||||
"local": {
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"key_path": "CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"registration_authority": {
|
|
||||||
"default": {
|
|
||||||
"authentication": "method_1",
|
|
||||||
"signing_ca": "local",
|
|
||||||
"validators": {
|
|
||||||
"ca_status": {
|
|
||||||
"ca_requested": false
|
|
||||||
},
|
|
||||||
"source_cidrs": {
|
|
||||||
"cidrs": [ "127.0.0.0/8" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fixups": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. _transport URIs: https://wiki.openstack.org/wiki/Oslo/Messaging#Transports
|
|
@ -1,129 +0,0 @@
|
|||||||
Ephemeral PKI
|
|
||||||
=============
|
|
||||||
|
|
||||||
Anchor is a Certificate and Registration Authority built to provide ephemeral
|
|
||||||
PKI services for large scale infrastructure deployments such as OpenStack. It
|
|
||||||
exists to solve two problems that typically affect PKI deployments but that
|
|
||||||
often go ignored by users; securely provision certificates in live environments
|
|
||||||
is incredibly difficult and effectively revoking bad certificates is nearly
|
|
||||||
impossible with the cryptographic libraries that are available to handle PKI
|
|
||||||
operations today.
|
|
||||||
|
|
||||||
Traditional Provisioning
|
|
||||||
------------------------
|
|
||||||
One of the challenges for managing PKI in large infrastructures is ensuring
|
|
||||||
that certificates are provisioned securely and effectively. In traditional PKI
|
|
||||||
a certificate signing request (CSR) would be created by a user who requires a
|
|
||||||
certificate for some service that they are managing. The user would then
|
|
||||||
typically submit that CSR to whatever corporate PKI system is in use, likely
|
|
||||||
Dogtag_ or Active Directory Certificate Services (ADCS_). That submission would
|
|
||||||
then trigger a process of verification that often includes a PKI administrator
|
|
||||||
manually inspecting that the various fields within the CSR and approving the
|
|
||||||
issuing of a certificate. When the certificate is issued the original requestor
|
|
||||||
needs to be notified, often by way of email - the requestor then accesses the
|
|
||||||
CA and retrieves their newly signed certificate.
|
|
||||||
|
|
||||||
.. _Dogtag: http://pki.fedoraproject.org/wiki/PKI_Main_Page
|
|
||||||
.. _ADCS: https://technet.microsoft.com/en-us/windowsserver/dd448615.aspx
|
|
||||||
|
|
||||||
This heavily manual process is fraught with opportunities for human error and
|
|
||||||
tends to scale very poorly. This workflow may have sufficed for managing the
|
|
||||||
certificates that an organization might want to provision at it's edge but it
|
|
||||||
cannot cope with the massive number of certificates required for running large
|
|
||||||
data centers.
|
|
||||||
|
|
||||||
Methods for automatically issuing certificates such as SCEP and ADCS
|
|
||||||
auto-enrollment exist to help solve this problem but often require significant
|
|
||||||
architectural changes to use them securely. For example, SCEP requires a
|
|
||||||
secure network to work (in most cases, if such a network already exists then
|
|
||||||
certificates would not be necessary) so it is typically only used when
|
|
||||||
infrastructure is provisioned - before being moved into production. ADCS
|
|
||||||
auto-enrollment requires all of your servers to be running on Microsoft
|
|
||||||
Windows, which is often not the case for large scale cloud-type environments.
|
|
||||||
|
|
||||||
Anchor provides an alternative mechanism for provisioning certificates that
|
|
||||||
allows each server in a cluster to request its own certificate while
|
|
||||||
enforcing strong issuing policies that introduce capabilities beyond those that
|
|
||||||
can be leveraged by the manual process described above - and it can do it at
|
|
||||||
large scale.
|
|
||||||
|
|
||||||
Anchor Provisioning
|
|
||||||
-------------------
|
|
||||||
Anchor expects that a machine which requires a certificate will request it
|
|
||||||
directly, rather than some user requesting it and then installing it on the
|
|
||||||
machine. This requires the machine to somehow track existing certificates and
|
|
||||||
request new ones when they expire. There are many ways to approach this and
|
|
||||||
often a simple cron.d bash script will suffice. The Cathead_ and Certmonger_
|
|
||||||
projects both exist to help with system based certificate management but only
|
|
||||||
Cathead natively supports Anchor, however Certmonger can be modified to work
|
|
||||||
with Anchor if required.
|
|
||||||
|
|
||||||
.. _Cathead: https://github.com/stackforge/cathead
|
|
||||||
.. _Certmonger: https://fedorahosted.org/certmonger/
|
|
||||||
|
|
||||||
Anchor provides multiple ways for machines to authenticate. The currently
|
|
||||||
supported options are LDAP, Keystone and a pre-shared Username/Password
|
|
||||||
combination. As every machine in a data centre can potentially have it's own
|
|
||||||
set of credentials Anchor can make very fine grained decisions regarding which
|
|
||||||
machines should be trusted at any given time. There's more information on
|
|
||||||
Anchor authentication in the :doc:`configuration` section.
|
|
||||||
|
|
||||||
Along with fine grained access control Anchor, supports various
|
|
||||||
:doc:`validators` that can be used by PKI administrators to set tight policy
|
|
||||||
constraints on what is allowed within a certificate. These validators provide a
|
|
||||||
powerful construct for programmatically verifying that a certificate meets
|
|
||||||
policy requirements for a particular environment.
|
|
||||||
|
|
||||||
Traditional Revocation
|
|
||||||
----------------------
|
|
||||||
Certificates can require revocation for a number of reasons, they may no longer
|
|
||||||
be required, they may have been incorrectly issued or the private key for a
|
|
||||||
certificate may have been compromised.
|
|
||||||
|
|
||||||
There are two methods that exist for revoking certificates; Certificate
|
|
||||||
Revocation Lists (CRL_) and the Online Certificate Status Protocol (OCSP_).
|
|
||||||
Unfortunately neither system is particularly robust when attempting to use them
|
|
||||||
within dynamic, large scale environments. CRLs are updated only periodically
|
|
||||||
and have significant scale issues when used within systems that change
|
|
||||||
certificates regularly. OCSP was created to address a number of the issues that
|
|
||||||
hinder CRLs but unfortunately is very poorly supported in cryptographic
|
|
||||||
libraries outside of web-browser software. Using OCSP incurs some
|
|
||||||
infrastructure overhead because it needs to maintain a level of availability
|
|
||||||
that normally requires it to be load balanced to ensure that a responder is
|
|
||||||
always available, not receiving an OCSP response will cause a client to not
|
|
||||||
trust a certificate.
|
|
||||||
|
|
||||||
.. _CRL: https://www.ietf.org/rfc/rfc5280.txt
|
|
||||||
.. _OCSP: https://tools.ietf.org/html/rfc6960
|
|
||||||
|
|
||||||
To recap; CRLs do not work terribly well in large scale, dynamic environments
|
|
||||||
where multiple certificates might be required in a machine's lifetime as it is
|
|
||||||
repurposed. OCSP doesn't work outside of web browsers and is of little value
|
|
||||||
as a revocation system for large scale infrastructure.
|
|
||||||
|
|
||||||
Passive Revocation
|
|
||||||
------------------
|
|
||||||
During our testing of TLS client libraries it became obvious that OCSP was
|
|
||||||
poorly supported and that CRLs weren't reliable enough to provide strong
|
|
||||||
assurance that certificates would be revoked when required. We did observe that
|
|
||||||
expired certificates were correctly handled in the most common TLS libraries.
|
|
||||||
Anchor leverages expiry dates and issues very short lifetime certificates,
|
|
||||||
typically certificates will be issued with an expiry date set just 12-24 hours
|
|
||||||
into the future.
|
|
||||||
|
|
||||||
Rather than attempting to actively revoke a certificate in the tradition sense,
|
|
||||||
Anchor will refuse to re-issue a certificate to a bad machine or user. The
|
|
||||||
assumption being that a change in policy, or modification to the authentication
|
|
||||||
platform is all that is required to ensure that a bad actor cannot gain access
|
|
||||||
to certificates. We refer to this process as "Passive Revocation".
|
|
||||||
|
|
||||||
When using passive revocation one accepts that there is a certain window of
|
|
||||||
compromise when a "bad" certificate may still be used within the system.
|
|
||||||
Although this may seem like a sub-optimal way to handle revocation it actually
|
|
||||||
results in better performance than more traditional revocation techniques. As
|
|
||||||
discussed earlier, CRLs can be unreliable and OCSP is generally not supported
|
|
||||||
outside of web browsers. However, even if it were, the passive revocation
|
|
||||||
window typically employed by Anchor will be shorter than the OCSP cached
|
|
||||||
response when using an OCSP responder. This means that in most typical
|
|
||||||
configurations, using Anchor will result in more reliable and timely
|
|
||||||
certificate revocation than any other mechanism available today.
|
|
@ -1,42 +0,0 @@
|
|||||||
Extension support
|
|
||||||
=================
|
|
||||||
|
|
||||||
Extensions in Anchor are supported on 3 levels:
|
|
||||||
|
|
||||||
* CSR parser (deciding what OIDs are recognised and the what is the interface
|
|
||||||
to extensions)
|
|
||||||
* validators / fixups which operate on extensions
|
|
||||||
* signing backends which operate on extensions
|
|
||||||
|
|
||||||
Anchor needs to parse the extension to use it in a validator or a fixup. That's
|
|
||||||
not the case of the signing backends however - external backends may add/update
|
|
||||||
extensions according to their own configuration.
|
|
||||||
|
|
||||||
Anchor can parse and analyse the following extensions:
|
|
||||||
|
|
||||||
* Basic Constraints
|
|
||||||
* Key Usage
|
|
||||||
* Extended Key Usage
|
|
||||||
* Name Constraints
|
|
||||||
* Subject Alternative Name
|
|
||||||
|
|
||||||
The following extensions are listed as required or preferred, but due to
|
|
||||||
Anchor's main purpose (ephemeral certificates) they will be either ignored (if
|
|
||||||
they're not critical), or will prevent signing (if they are):
|
|
||||||
|
|
||||||
* Certificate Policies
|
|
||||||
* Policy Mappings
|
|
||||||
* Inhibit anyPolicy
|
|
||||||
* CRL Distribution Points
|
|
||||||
* Freshest CRL
|
|
||||||
|
|
||||||
Other extensions will be added to the implementation when they're needed for
|
|
||||||
validation / fixups.
|
|
||||||
|
|
||||||
Extension limitations
|
|
||||||
=====================
|
|
||||||
Due to how Anchor relies on short-term certificates, issuing a CA certificate
|
|
||||||
from Anchor doesn't really make sense. Certificates which do have a CA flag set
|
|
||||||
will be rejected unconditionally.
|
|
||||||
|
|
||||||
Key usage related to CA status will be treated in a similar way.
|
|
@ -1,19 +0,0 @@
|
|||||||
Fixups
|
|
||||||
======
|
|
||||||
|
|
||||||
Fixups can be used to modify submitted CSRs before signing. That means for
|
|
||||||
example adding extra name elements, or extensions. Each fixup is loaded from
|
|
||||||
the "anchor.fixups" namespace using stevedore and gets access to the parsed CSR
|
|
||||||
and the configuration.
|
|
||||||
|
|
||||||
Unlike validators, each fixup has to return either a new CSR structure or the
|
|
||||||
modified original.
|
|
||||||
|
|
||||||
Included fixups
|
|
||||||
---------------
|
|
||||||
|
|
||||||
``enforce_alternative_names_present``
|
|
||||||
No parameters.
|
|
||||||
|
|
||||||
If the value from CN does not exist in subject alternative names, it will
|
|
||||||
be copied into either then DNS or IP field, depending on the format.
|
|
@ -1,29 +0,0 @@
|
|||||||
Welcome to Anchor!
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Anchor is an ephemeral PKI service that, based on certain conditions,
|
|
||||||
automates the verification of CSRs and signs certificates for clients.
|
|
||||||
The validity period can be set in the config file with hour resolution.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
configuration
|
|
||||||
api
|
|
||||||
extensions
|
|
||||||
signing_backends
|
|
||||||
ephemeralPKI
|
|
||||||
validators
|
|
||||||
fixups
|
|
||||||
audit
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`search`
|
|
@ -1,117 +0,0 @@
|
|||||||
Signing backends
|
|
||||||
================
|
|
||||||
|
|
||||||
Each signing backend must be registered using an entry point. They're loaded
|
|
||||||
using the ``stevedore`` module, however this should not affect the calling
|
|
||||||
behaviour.
|
|
||||||
|
|
||||||
The signing CA configuration block allows the following common options:
|
|
||||||
|
|
||||||
* ``backend``: name of the requested backend ("anchor" not defined)
|
|
||||||
* ``output_path``: local path where anchor saves the issued certificates
|
|
||||||
(optional, output not saved if not defined)
|
|
||||||
|
|
||||||
Anchor provides the following backends out of the box:
|
|
||||||
|
|
||||||
anchor
|
|
||||||
------
|
|
||||||
|
|
||||||
The default signing backend. It doesn't have any external service dependencies
|
|
||||||
and all signing happens inside of the Anchor process.
|
|
||||||
|
|
||||||
This backend will ignore all non-critical extensions which are not understood
|
|
||||||
by Anchor and will reject CSRs with unknown critical extensions.
|
|
||||||
|
|
||||||
A sample configuration for the ``signing_ca`` block looks like this:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"local": {
|
|
||||||
"backend": "anchor",
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"key_path": "CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Valid options for this backend are:
|
|
||||||
|
|
||||||
* ``cert_path``: path to the signing CA certificate
|
|
||||||
* ``key_path``: path to the matching key
|
|
||||||
* ``signing_hash``: hash to use when signing the issued certificate ("md5",
|
|
||||||
"sha1", "sha224, "sha256" are valid options)
|
|
||||||
* ``valid_hours``: validity period for the issued certificates, defined in
|
|
||||||
hours
|
|
||||||
|
|
||||||
pkcs11
|
|
||||||
------
|
|
||||||
|
|
||||||
This backend uses a provided pkcs11 library for the signing operation. The
|
|
||||||
final certificate is created in the same way as with `anchor` backend with
|
|
||||||
regards to extensions and fixups.
|
|
||||||
|
|
||||||
The interface doesn't rely on any special functionality of the store. Only the
|
|
||||||
RSA private key needs to be available as a secret. The only used mechanism is
|
|
||||||
CKM_RSA_PKCS. That means any pkcs11 backend from gnome keyring to tpm and
|
|
||||||
external HSMs should work.
|
|
||||||
|
|
||||||
This backend requires ``PyKCS11`` package to be installed.
|
|
||||||
|
|
||||||
A sample configuration for the ``signing_ca`` block looks like this:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"local": {
|
|
||||||
"backend": "pkcs11",
|
|
||||||
"cert_path": "CA/root-ca.crt",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24,
|
|
||||||
"slot": 18,
|
|
||||||
"pin": "the_pin",
|
|
||||||
"key_id": "b22f6e84a7b29db389b57a24384b95cca0bb4bc0",
|
|
||||||
"pkcs11_path": "/usr/lib/.../pkcs11/...-pkcs11.so"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Valid options for this backend are:
|
|
||||||
|
|
||||||
* ``cert_path``: path to the signing CA certificate
|
|
||||||
* ``signing_hash``: hash to use when signing the issued certificate ("sha224,
|
|
||||||
"sha256", "sha384", "sha512" are valid options)
|
|
||||||
* ``valid_hours``: validity period for the issued certificates, defined in
|
|
||||||
hours
|
|
||||||
* ``slot``: slot number where the key can be found
|
|
||||||
* ``pin``: text version of the pin required to access the right slot
|
|
||||||
* ``key_id``: key id written as a hex string
|
|
||||||
* ``pkcs11_path``: path to the dynamic library compatible with pkcs11 interface
|
|
||||||
|
|
||||||
Backend development
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Backends are simple functions which need to take 2 parameters: the CSR in PEM
|
|
||||||
format and the configuration block contents. Configuration can contain any keys
|
|
||||||
required by the backend.
|
|
||||||
|
|
||||||
The return value must be a signed certificate in PEM format, however in most
|
|
||||||
cases it's enough to implement the actual hash signing part and rely on
|
|
||||||
``anchor.signer.sign_generic`` framework. The backend may either throw a
|
|
||||||
specific ``WebOb`` HTTP exception, or SigningError exception which will result
|
|
||||||
in a 500 response.
|
|
||||||
|
|
||||||
For security, http exceptions from the signing backend should not expose any
|
|
||||||
specific information about the reason for failure. Internal exceptions are
|
|
||||||
preferred for this reason and their details will be logged in Anchor.
|
|
||||||
|
|
||||||
The backend must not rely on the received CSR signature. If any modifications
|
|
||||||
are applied to the submitted CSR in Anchor, they will invalidate the signature.
|
|
||||||
Unless the backend is intended to work only with validators, and not any fixup
|
|
||||||
operations in the future, the signature field should be ignored and the request
|
|
||||||
treated as already correct/verified.
|
|
||||||
|
|
||||||
Configuration is verified using the function provided using the
|
|
||||||
``@signers.config_validator(f)`` decorator.
|
|
@ -1,184 +0,0 @@
|
|||||||
Validators
|
|
||||||
==========
|
|
||||||
|
|
||||||
Currently validators can check three things: the CSR, the incoming connection,
|
|
||||||
and the authentication. The resulting action can be only pass or fail.
|
|
||||||
Validators are configured in the ``config.json`` file and each one comes with
|
|
||||||
different options.
|
|
||||||
|
|
||||||
Included validators
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The following validators are implemented at the moment:
|
|
||||||
|
|
||||||
``standards_compliance``
|
|
||||||
Verifies: CSR.
|
|
||||||
|
|
||||||
Ensures that the CSR does not break any rules defined in the standards
|
|
||||||
documents (mostly RFC5280). Specific checks may be added over time in new
|
|
||||||
versions of Anchor. This validator should be only skipped if there's a
|
|
||||||
known compatibility issue. Otherwise it should be used in all environments.
|
|
||||||
Any requests produced using standard tooling that fail this check should be
|
|
||||||
reported as Anchor issues.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
|
|
||||||
- ``label_re``: pattern for acceptable domain label format
|
|
||||||
|
|
||||||
``whitelist_names``
|
|
||||||
Verifies: CSR. Parameters:
|
|
||||||
|
|
||||||
- ``names``: list of names/ips/ip ranges allowed in various fields
|
|
||||||
- ``allow_cn_id``: allow name in subject CN
|
|
||||||
- ``allow_dns_id``: allow name in SAN dns entry
|
|
||||||
- ``allow_ip_id``: allow name in SAN IP entry
|
|
||||||
- ``allow_wildcard``: allow wildcard certificate to match '%'
|
|
||||||
|
|
||||||
IDs available in various places in the certificate are matched against the
|
|
||||||
patterns in the ``names`` list. These can be:
|
|
||||||
|
|
||||||
- IP addresses: ``1.2.3.4``
|
|
||||||
- IP ranges: ``1.2.3.0/24``
|
|
||||||
- complete names: ``some.example.com``
|
|
||||||
- names with wildcards: ``%.example.com``, ``partial-%.example.com``
|
|
||||||
|
|
||||||
Wildcard (``%``) rules: It matches only a single name label, or part of
|
|
||||||
one. It can be used only in domain names, not IPs. Only one wildcard is
|
|
||||||
allowed in a label, but multiple in a name, so ``%.%.example.com`` is
|
|
||||||
valid.
|
|
||||||
|
|
||||||
Pattern wildcard (``%``) may match a domain wildcard character (``*``)
|
|
||||||
only if ``allow_wildcard`` is set to true.
|
|
||||||
|
|
||||||
This match will fail if the CSR contains any SAN type not included here.
|
|
||||||
|
|
||||||
``blacklist_names``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_domains``, ``allowed_networks``.
|
|
||||||
|
|
||||||
Ensures that the CN and subject alternative names do not contain anything
|
|
||||||
configured in the ``domains``.
|
|
||||||
|
|
||||||
``common_name``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_domains``, ``allowed_networks``.
|
|
||||||
|
|
||||||
Ensures that the CN matches one of names in ``allowed_domains`` or IP
|
|
||||||
ranges in ``allowed_networks``.
|
|
||||||
|
|
||||||
Deprecated: use ``whitelist_names`` / ``blacklist_names`` instead.
|
|
||||||
|
|
||||||
``alternative_names``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_domains``.
|
|
||||||
|
|
||||||
Ensures that names specified in the subject alternative names extension
|
|
||||||
match one of the names in ``allowed_domains``.
|
|
||||||
|
|
||||||
Deprecated: use ``whitelist_names`` / ``blacklist_names`` instead.
|
|
||||||
|
|
||||||
``alternative_names_ip``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_domains``, ``allowed_networks``.
|
|
||||||
|
|
||||||
Ensures that names specified in the subject alternative names extension
|
|
||||||
match one of the names in ``allowed_domains`` or IP ranges in
|
|
||||||
``allowed_networks``.
|
|
||||||
|
|
||||||
Deprecated: use ``whitelist_names`` / ``blacklist_names`` instead.
|
|
||||||
|
|
||||||
``server_group``
|
|
||||||
Verifies: Auth, CSR. Parameters: ``group_prefixes``.
|
|
||||||
|
|
||||||
Ensures the requester is authorised to get a certificate for a given
|
|
||||||
server. This is currently assuming specific server naming scheme which
|
|
||||||
looks like ``{prefix}-{name}.{domain}``. For example if the prefixes are
|
|
||||||
defined as ``{"Nova": "nv"}``, and the client authentication returns group
|
|
||||||
"Nova", then a request for ``nv-compute1.domain`` will succeed, but a
|
|
||||||
request for ``gl-api1.domain`` will fail.
|
|
||||||
|
|
||||||
Only CN is checked and if there are no dashes in the CN, validation
|
|
||||||
succeeds.
|
|
||||||
|
|
||||||
Deprecated: use ``whitelist_names`` / ``blacklist_names`` instead.
|
|
||||||
|
|
||||||
``extensions``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_extensions``.
|
|
||||||
|
|
||||||
Ensures that only ``allowed_extensions`` are present in the request. The
|
|
||||||
names recognised by Anchor are:
|
|
||||||
|
|
||||||
policyConstraints, basicConstraints, subjectDirectoryAttributes,
|
|
||||||
deltaCRLIndicator, cRLDistributionPoints, issuingDistributionPoint,
|
|
||||||
nameConstraints, certificatePolicies, policyMappings,
|
|
||||||
privateKeyUsagePeriod, keyUsage, authorityKeyIdentifier,
|
|
||||||
subjectKeyIdentifier, certificateIssuer, subjectAltName, issuerAltName
|
|
||||||
|
|
||||||
Alternatively, the extension can be specified by the dotted decimal version
|
|
||||||
of OID.
|
|
||||||
|
|
||||||
``key_usage``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_usage``.
|
|
||||||
|
|
||||||
Ensures only ``allowed_usage`` is requested for the certificate. The names
|
|
||||||
recognised by Anchor are:
|
|
||||||
|
|
||||||
Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment,
|
|
||||||
Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only,
|
|
||||||
|
|
||||||
as well as short versions:
|
|
||||||
|
|
||||||
digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment,
|
|
||||||
keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly
|
|
||||||
|
|
||||||
``ext_key_usage``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_usage``.
|
|
||||||
|
|
||||||
Ensures only ``allowed_usage`` is requested for the certificate. The names
|
|
||||||
recognised by Anchor are:
|
|
||||||
|
|
||||||
TLS Web Server Authentication, TLS Web Client Authentication, Code Signing,
|
|
||||||
E-mail Protection, Time Stamping, OCSP Signing, Any Extended Key Usage
|
|
||||||
|
|
||||||
as well as short versions:
|
|
||||||
|
|
||||||
serverAuth, clientAuth, codeSigning, emailProtection, timeStamping,
|
|
||||||
ocspSigning, anyExtendedKeyUsage
|
|
||||||
|
|
||||||
or text representation of custom OIDs.
|
|
||||||
|
|
||||||
``source_cidrs``
|
|
||||||
Verifies: CSR. Parameters: ``cidrs``.
|
|
||||||
|
|
||||||
Ensures the request comes from one of the ranges in `cidrs`.
|
|
||||||
|
|
||||||
``public_key``
|
|
||||||
Verifies: CSR. Parameters: ``allowed_keys``.
|
|
||||||
|
|
||||||
Ensures that only selected keys of a minimum specified length can be used
|
|
||||||
in the CSR. The ``allowed_keys`` parameter is a dictionary where keys are
|
|
||||||
the uppercase key names and values are minimum key lengths. Valid keys
|
|
||||||
at the moment are: ``RSA`` and ``DSA``.
|
|
||||||
|
|
||||||
Extension interface
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Custom validators can be used with Anchor without changing the application
|
|
||||||
itself. All validators are exposed as Stevedore_ extensions. They're registered
|
|
||||||
as entry points in namespace ``anchor.validators`` and each name points to a
|
|
||||||
simple function which accepts the following keyword arguments:
|
|
||||||
|
|
||||||
``csr`` : anchor.X509.signing_request.X509Csr
|
|
||||||
An object describing the submitted CSR.
|
|
||||||
|
|
||||||
``auth_result`` : anchor.auth.results.AuthDetails
|
|
||||||
An object which contains authentication information like username and user
|
|
||||||
groups.
|
|
||||||
|
|
||||||
``request`` : pecan.Request
|
|
||||||
The https request which delivered the CSR.
|
|
||||||
|
|
||||||
``conf`` : dict
|
|
||||||
Dictionary describing the registration authority configuration.
|
|
||||||
|
|
||||||
On successful return, the request is passed on to the next validator or signed
|
|
||||||
if there are no remining ones. On validation failure an
|
|
||||||
``anchor.validators.ValidationError`` exception must be raised.
|
|
||||||
|
|
||||||
.. _Stevedore: http://docs.openstack.org/developer/stevedore/index.html
|
|
@ -1,17 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
|
||||||
pyasn1 # BSD
|
|
||||||
pyasn1-modules # BSD
|
|
||||||
WebOb>=1.6.0 # MIT
|
|
||||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
|
||||||
Paste # MIT
|
|
||||||
netaddr!=0.7.16,>=0.7.13 # BSD
|
|
||||||
ldap3>=1.0.2 # LGPLv3
|
|
||||||
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
|
|
||||||
stevedore>=1.17.1 # Apache-2.0
|
|
||||||
pycadf!=2.0.0,>=1.1.0 # Apache-2.0
|
|
||||||
oslo.config!=3.18.0,>=3.14.0 # Apache-2.0
|
|
||||||
oslo.messaging>=5.14.0 # Apache-2.0
|
|
||||||
oslo.utils>=3.18.0 # Apache-2.0
|
|
161
run_tests.sh
161
run_tests.sh
@ -1,161 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Copyright 2012 OpenStack Foundation
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
function usage {
|
|
||||||
echo "Usage: $0 [OPTION]..."
|
|
||||||
echo "Run Keystone's test suite(s)"
|
|
||||||
echo ""
|
|
||||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
|
||||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
|
||||||
echo " -x, --stop Stop running tests after the first error or failure."
|
|
||||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
|
||||||
echo " -u, --update Update the virtual environment with any newer package versions"
|
|
||||||
echo " -p, --pep8 Just run flake8"
|
|
||||||
echo " -8, --8 Just run flake8, don't show PEP8 text for each error"
|
|
||||||
echo " -P, --no-pep8 Don't run flake8"
|
|
||||||
echo " -c, --coverage Generate coverage report"
|
|
||||||
echo " -h, --help Print this usage message"
|
|
||||||
echo ""
|
|
||||||
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
|
|
||||||
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
|
|
||||||
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
function process_option {
|
|
||||||
case "$1" in
|
|
||||||
-h|--help) usage;;
|
|
||||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
|
||||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
|
||||||
-x|--stop) failfast=1;;
|
|
||||||
-f|--force) force=1;;
|
|
||||||
-u|--update) update=1;;
|
|
||||||
-p|--pep8) just_flake8=1;;
|
|
||||||
-8|--8) short_flake8=1;;
|
|
||||||
-P|--no-pep8) no_flake8=1;;
|
|
||||||
-c|--coverage) coverage=1;;
|
|
||||||
-*) testropts="$testropts $1";;
|
|
||||||
*) testrargs="$testrargs $1"
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
venv=.venv
|
|
||||||
with_venv=tools/with_venv.sh
|
|
||||||
always_venv=0
|
|
||||||
never_venv=0
|
|
||||||
force=0
|
|
||||||
failfast=0
|
|
||||||
testrargs=
|
|
||||||
testropts=--subunit
|
|
||||||
wrapper=""
|
|
||||||
just_flake8=0
|
|
||||||
short_flake8=0
|
|
||||||
no_flake8=0
|
|
||||||
coverage=0
|
|
||||||
update=0
|
|
||||||
|
|
||||||
for arg in "$@"; do
|
|
||||||
process_option $arg
|
|
||||||
done
|
|
||||||
|
|
||||||
TESTRTESTS="python setup.py testr"
|
|
||||||
|
|
||||||
# If enabled, tell nose to collect coverage data
|
|
||||||
if [ $coverage -eq 1 ]; then
|
|
||||||
TESTRTESTS="$TESTRTESTS --coverage"
|
|
||||||
fi
|
|
||||||
|
|
||||||
function run_tests {
|
|
||||||
set -e
|
|
||||||
echo ${wrapper}
|
|
||||||
if [ $failfast -eq 1 ]; then
|
|
||||||
testrargs="$testrargs -- --failfast"
|
|
||||||
fi
|
|
||||||
${wrapper} $TESTRTESTS --testr-args="$testropts $testrargs" | \
|
|
||||||
${wrapper} subunit-2to1 | \
|
|
||||||
${wrapper} tools/colorizer.py
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_flake8 {
|
|
||||||
FLAGS=--show-pep8
|
|
||||||
if [ $# -gt 0 ] && [ 'short' == ''$1 ]; then
|
|
||||||
FLAGS=''
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Running flake8 ..."
|
|
||||||
# Just run flake8 in current environment
|
|
||||||
echo ${wrapper} flake8 $FLAGS | tee pep8.txt
|
|
||||||
${wrapper} flake8 $FLAGS | tee pep8.txt
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $never_venv -eq 0 ]; then
|
|
||||||
# Remove the virtual environment if --force used
|
|
||||||
if [ $force -eq 1 ]; then
|
|
||||||
echo "Cleaning virtualenv..."
|
|
||||||
rm -rf ${venv}
|
|
||||||
fi
|
|
||||||
if [ $update -eq 1 ]; then
|
|
||||||
echo "Updating virtualenv..."
|
|
||||||
python tools/install_venv.py
|
|
||||||
fi
|
|
||||||
if [ -e ${venv} ]; then
|
|
||||||
wrapper="${with_venv}"
|
|
||||||
else
|
|
||||||
if [ $always_venv -eq 1 ]; then
|
|
||||||
# Automatically install the virtualenv
|
|
||||||
python tools/install_venv.py
|
|
||||||
wrapper="${with_venv}"
|
|
||||||
else
|
|
||||||
echo -e "No virtual environment found...create one? (Y/n) \c"
|
|
||||||
read use_ve
|
|
||||||
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
|
|
||||||
# Install the virtualenv and run the test suite in it
|
|
||||||
python tools/install_venv.py
|
|
||||||
wrapper=${with_venv}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Delete old coverage data from previous runs
|
|
||||||
if [ $coverage -eq 1 ]; then
|
|
||||||
${wrapper} coverage erase
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $just_flake8 -eq 1 ]; then
|
|
||||||
run_flake8
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $short_flake8 -eq 1 ]; then
|
|
||||||
run_flake8 short
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
run_tests
|
|
||||||
|
|
||||||
# NOTE(sirp): we only want to run flake8 when we're running the full-test
|
|
||||||
# suite, not when we're running tests individually. To handle this, we need to
|
|
||||||
# distinguish between options (testropts), which begin with a '-', and arguments
|
|
||||||
# (testrargs).
|
|
||||||
if [ -z "$testrargs" ]; then
|
|
||||||
if [ $no_flake8 -eq 0 ]; then
|
|
||||||
run_flake8
|
|
||||||
fi
|
|
||||||
fi
|
|
67
setup.cfg
67
setup.cfg
@ -1,67 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = anchor
|
|
||||||
summary = Webservice to auto-sign certificates for short amount of time
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack Security Group
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = https://wiki.openstack.org/wiki/Security/Projects/Anchor
|
|
||||||
classifier =
|
|
||||||
Environment :: OpenStack
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
Intended Audience :: Developers
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Operating System :: MacOS :: MacOS X
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.5
|
|
||||||
Topic :: Security
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
all_files = 1
|
|
||||||
build-dir = doc/build
|
|
||||||
source-dir = doc/source
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
anchor.signing_backends =
|
|
||||||
anchor = anchor.signers.cryptography_io:sign
|
|
||||||
pkcs11 = anchor.signers.pkcs11:sign
|
|
||||||
|
|
||||||
anchor.validators =
|
|
||||||
common_name = anchor.validators.custom:common_name
|
|
||||||
alternative_names = anchor.validators.custom:alternative_names
|
|
||||||
alternative_names_ip = anchor.validators.custom:alternative_names_ip
|
|
||||||
blacklist_names = anchor.validators.custom:blacklist_names
|
|
||||||
server_group = anchor.validators.custom:server_group
|
|
||||||
extensions = anchor.validators.custom:extensions
|
|
||||||
key_usage = anchor.validators.custom:key_usage
|
|
||||||
ext_key_usage = anchor.validators.custom:ext_key_usage
|
|
||||||
source_cidrs = anchor.validators.custom:source_cidrs
|
|
||||||
whitelist_names = anchor.validators.custom:whitelist_names
|
|
||||||
public_key = anchor.validators.custom:public_key
|
|
||||||
standards_compliance = anchor.validators.standards:standards_compliance
|
|
||||||
|
|
||||||
anchor.authentication =
|
|
||||||
keystone = anchor.auth.keystone:login
|
|
||||||
ldap = anchor.auth.ldap:login
|
|
||||||
static = anchor.auth.static:login
|
|
||||||
|
|
||||||
anchor.fixups =
|
|
||||||
enforce_alternative_names_present = anchor.fixups:enforce_alternative_names_present
|
|
||||||
|
|
||||||
[files]
|
|
||||||
data_files =
|
|
||||||
etc/anchor =
|
|
||||||
config.json
|
|
||||||
packages =
|
|
||||||
anchor
|
|
||||||
scripts =
|
|
||||||
bin/anchor
|
|
||||||
bin/anchor_debug
|
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
universal=1
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=1.8'],
|
|
||||||
pbr=True)
|
|
@ -1,18 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
coverage>=4.0 # Apache-2.0
|
|
||||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
|
||||||
hacking<0.10,>=0.9.2
|
|
||||||
mock>=2.0 # BSD
|
|
||||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
|
||||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
|
||||||
testscenarios>=0.4 # Apache-2.0/BSD
|
|
||||||
testtools>=1.4.0 # MIT
|
|
||||||
requests-mock>=1.1 # Apache-2.0
|
|
||||||
|
|
||||||
# Documentation build requirements
|
|
||||||
sphinx>=1.5.1 # BSD
|
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
|
||||||
|
|
||||||
bandit>=1.1.0 # Apache-2.0
|
|
@ -1,15 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXQIBAAKBgQDXTICDdXtgyMqmfForj49nr4kOBcs9AdG85iIGCErRYC1tC6Sz
|
|
||||||
v1E+lblOfadEyf0nykoyptK3aPgXa5S+GGu2zVSQoXmpixbdAr2MIuAjcnHeomKz
|
|
||||||
EjyjNcbwa5YElhSI3ypiX28ZCFncbVIUN8aUdpfjZCnJKBPpUgT+GGxOFwIDAQAB
|
|
||||||
AoGBAITpG2UMP7BOBJymo9vEclkmCkv307HDz8D3qQVkVRvQbfqld3Xno7YpJA6K
|
|
||||||
j5ptv7Sysv918Rt817tNlLONy+AZGY2hxgmXVob4FdwVoRTj7EJhuciIYzecsfHO
|
|
||||||
PVzc8dF2WCocMWlXQ7a/r2pOa5H/D4x4FyBtpf0ykJps2ZfBAkEA/2SQFYTDBcTX
|
|
||||||
f1vnL/fnNgUS1T84E6FPlRTO8lpNB1atGf97lLzxv4QafJc3w6PhL1DRMeCmG1QD
|
|
||||||
7G1pBG4PpwJBANfPiYRg2jyUANbwE47CjsqiviwAC2aboWGWcC/m+LQ/PdvcTD7V
|
|
||||||
tF83Fn4syEuNKj65xGhmxZmCdn8oHCZBHBECQC0Iam+g7VKDFwyaA/XtXJOl6WA4
|
|
||||||
uYaclw/Oj38kdRiqK/O9nOjpOCdw/8qgT3Dr4LUbJwgIeMGw2tBBqpbhYVkCQFN3
|
|
||||||
diVX3DAfwe9fbQEC6H0g0lJsNfyaZqE6sOsl9ryn1QHqwyZuOtO0l6N3KIRn9ZXK
|
|
||||||
/VavoO8NUU0+sxxshDECQQDtwy89tpRQhqTIQkZzcgXmAiBypdh/Wunj1fzAJ98M
|
|
||||||
Vo8GJXswSuQXDr4mZnsl0F+RRe4LoLM6i3TYeM+qJZmg
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
@ -1,58 +0,0 @@
|
|||||||
Certificate:
|
|
||||||
Data:
|
|
||||||
Version: 3 (0x2)
|
|
||||||
Serial Number: 16983733478354280881 (0xebb2579d693761b1)
|
|
||||||
Signature Algorithm: sha256WithRSAEncryption
|
|
||||||
Issuer: C=AU, ST=Some-State, O=Herp Derp plc, OU=herp.derp.plc, CN=herp.derp.plc
|
|
||||||
Validity
|
|
||||||
Not Before: Sep 1 23:29:35 2015 GMT
|
|
||||||
Not After : Sep 2 23:29:35 2015 GMT
|
|
||||||
Subject: C=AU, ST=Some-State, O=Herp Derp plc, OU=herp.derp.plc, CN=herp.derp.plc
|
|
||||||
Subject Public Key Info:
|
|
||||||
Public Key Algorithm: rsaEncryption
|
|
||||||
Public-Key: (1024 bit)
|
|
||||||
Modulus:
|
|
||||||
00:9e:7a:a8:35:41:e7:1c:bf:c8:6a:8f:50:4f:f4:
|
|
||||||
a1:09:5f:94:2c:14:2c:51:eb:63:3c:a6:53:db:e6:
|
|
||||||
de:2c:2e:8f:14:61:f6:5d:ea:41:4b:70:e3:fc:c7:
|
|
||||||
3c:30:bf:1f:de:15:8e:92:bb:1e:76:7a:74:35:f7:
|
|
||||||
ba:3c:68:cc:32:3f:be:e1:32:16:6a:b5:df:0d:0a:
|
|
||||||
02:c9:31:59:54:6d:18:70:2e:d8:b4:4a:41:c5:3e:
|
|
||||||
27:34:c0:08:3e:7a:c7:d7:6b:ac:a1:77:94:f1:0b:
|
|
||||||
e6:ed:8b:b3:20:57:f9:63:03:cd:17:43:11:c7:f3:
|
|
||||||
13:a3:74:ea:06:37:40:c7:7d
|
|
||||||
Exponent: 65537 (0x10001)
|
|
||||||
X509v3 extensions:
|
|
||||||
X509v3 Subject Key Identifier:
|
|
||||||
DE:D6:97:31:61:61:AB:34:2F:EE:92:CB:85:96:80:86:BF:8D:60:DD
|
|
||||||
X509v3 Authority Key Identifier:
|
|
||||||
keyid:DE:D6:97:31:61:61:AB:34:2F:EE:92:CB:85:96:80:86:BF:8D:60:DD
|
|
||||||
|
|
||||||
X509v3 Basic Constraints:
|
|
||||||
CA:TRUE
|
|
||||||
Signature Algorithm: sha256WithRSAEncryption
|
|
||||||
9a:50:80:40:5a:11:3d:99:0c:85:0a:68:e2:ad:8a:c9:db:c0:
|
|
||||||
9d:2f:80:1a:f6:52:cb:bd:5d:3c:de:41:b3:50:76:d9:d9:7a:
|
|
||||||
e9:ae:97:f4:68:dc:78:4c:90:82:5f:e9:57:17:70:49:26:18:
|
|
||||||
2b:ab:96:b7:26:0d:6f:63:4e:fd:40:6c:44:6a:5f:b9:26:76:
|
|
||||||
8d:1b:4a:74:3b:b2:cf:b5:cc:5b:50:a6:ea:1c:67:3a:13:29:
|
|
||||||
69:93:e2:b6:9e:14:97:a0:b2:3f:5f:3a:f4:c9:7f:5d:5a:7a:
|
|
||||||
7c:95:d4:2c:dc:83:a2:ba:5f:a9:10:de:f7:80:3d:e6:63:e8:
|
|
||||||
5b:ef
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAgugAwIBAgIJAOuyV51pN2GxMA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
|
|
||||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRYwFAYDVQQKDA1IZXJwIERlcnAg
|
|
||||||
cGxjMRYwFAYDVQQLDA1oZXJwLmRlcnAucGxjMRYwFAYDVQQDDA1oZXJwLmRlcnAu
|
|
||||||
cGxjMB4XDTE1MDkwMTIzMjkzNVoXDTE1MDkwMjIzMjkzNVowajELMAkGA1UEBhMC
|
|
||||||
QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxFjAUBgNVBAoMDUhlcnAgRGVycCBwbGMx
|
|
||||||
FjAUBgNVBAsMDWhlcnAuZGVycC5wbGMxFjAUBgNVBAMMDWhlcnAuZGVycC5wbGMw
|
|
||||||
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ56qDVB5xy/yGqPUE/0oQlflCwU
|
|
||||||
LFHrYzymU9vm3iwujxRh9l3qQUtw4/zHPDC/H94VjpK7HnZ6dDX3ujxozDI/vuEy
|
|
||||||
Fmq13w0KAskxWVRtGHAu2LRKQcU+JzTACD56x9drrKF3lPEL5u2LsyBX+WMDzRdD
|
|
||||||
EcfzE6N06gY3QMd9AgMBAAGjUDBOMB0GA1UdDgQWBBTe1pcxYWGrNC/uksuFloCG
|
|
||||||
v41g3TAfBgNVHSMEGDAWgBTe1pcxYWGrNC/uksuFloCGv41g3TAMBgNVHRMEBTAD
|
|
||||||
AQH/MA0GCSqGSIb3DQEBCwUAA4GBAJpQgEBaET2ZDIUKaOKtisnbwJ0vgBr2Usu9
|
|
||||||
XTzeQbNQdtnZeumul/Ro3HhMkIJf6VcXcEkmGCurlrcmDW9jTv1AbERqX7kmdo0b
|
|
||||||
SnQ7ss+1zFtQpuocZzoTKWmT4raeFJegsj9fOvTJf11aenyV1Czcg6K6X6kQ3veA
|
|
||||||
PeZj6Fvv
|
|
||||||
-----END CERTIFICATE-----
|
|
@ -1,270 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from pyasn1.codec.der import encoder
|
|
||||||
from pyasn1.type import univ
|
|
||||||
|
|
||||||
from anchor.asn1 import rfc5280
|
|
||||||
from anchor.X509 import errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
|
|
||||||
|
|
||||||
class TestExtensionBase(unittest.TestCase):
|
|
||||||
def test_no_spec(self):
|
|
||||||
with self.assertRaises(errors.X509Error):
|
|
||||||
extension.X509Extension()
|
|
||||||
|
|
||||||
def test_invalid_asn(self):
|
|
||||||
with self.assertRaises(errors.X509Error):
|
|
||||||
extension.X509Extension("foobar")
|
|
||||||
|
|
||||||
def test_unknown_extension_str(self):
|
|
||||||
asn1 = rfc5280.Extension()
|
|
||||||
asn1['extnID'] = univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
asn1['critical'] = False
|
|
||||||
asn1['extnValue'] = "foobar"
|
|
||||||
ext = extension.X509Extension(asn1)
|
|
||||||
self.assertEqual("1.2.3.4: <unknown>", str(ext))
|
|
||||||
|
|
||||||
def test_construct(self):
|
|
||||||
asn1 = rfc5280.Extension()
|
|
||||||
asn1['extnID'] = univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
asn1['critical'] = False
|
|
||||||
asn1['extnValue'] = "foobar"
|
|
||||||
ext = extension.construct_extension(asn1)
|
|
||||||
self.assertIsInstance(ext, extension.X509Extension)
|
|
||||||
|
|
||||||
def test_construct_invalid_type(self):
|
|
||||||
with self.assertRaises(errors.X509Error):
|
|
||||||
extension.construct_extension("foobar")
|
|
||||||
|
|
||||||
def test_critical(self):
|
|
||||||
asn1 = rfc5280.Extension()
|
|
||||||
asn1['extnID'] = univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
asn1['critical'] = False
|
|
||||||
asn1['extnValue'] = "foobar"
|
|
||||||
ext = extension.construct_extension(asn1)
|
|
||||||
self.assertFalse(ext.get_critical())
|
|
||||||
ext.set_critical(True)
|
|
||||||
self.assertTrue(ext.get_critical())
|
|
||||||
|
|
||||||
def test_serialise(self):
|
|
||||||
asn1 = rfc5280.Extension()
|
|
||||||
asn1['extnID'] = univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
asn1['critical'] = False
|
|
||||||
asn1['extnValue'] = "foobar"
|
|
||||||
ext = extension.construct_extension(asn1)
|
|
||||||
self.assertEqual(ext.as_der(), encoder.encode(asn1))
|
|
||||||
|
|
||||||
def test_broken_set_value(self):
|
|
||||||
class SomeExt(extension.X509Extension):
|
|
||||||
spec = rfc5280.Extension
|
|
||||||
_oid = univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_default_value(cls):
|
|
||||||
return 1234
|
|
||||||
|
|
||||||
with self.assertRaisesRegexp(errors.X509Error, 'incorrect type'):
|
|
||||||
SomeExt()
|
|
||||||
|
|
||||||
|
|
||||||
class TestBasicConstraints(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionBasicConstraints()
|
|
||||||
|
|
||||||
def test_str(self):
|
|
||||||
self.assertEqual(str(self.ext),
|
|
||||||
"basicConstraints: CA: FALSE, pathLen: None")
|
|
||||||
|
|
||||||
def test_ca(self):
|
|
||||||
self.ext.set_ca(True)
|
|
||||||
self.assertTrue(self.ext.get_ca())
|
|
||||||
self.ext.set_ca(False)
|
|
||||||
self.assertFalse(self.ext.get_ca())
|
|
||||||
|
|
||||||
def test_pathlen(self):
|
|
||||||
self.ext.set_path_len_constraint(1)
|
|
||||||
self.assertEqual(1, self.ext.get_path_len_constraint())
|
|
||||||
|
|
||||||
|
|
||||||
class TestKeyUsage(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionKeyUsage()
|
|
||||||
|
|
||||||
def test_usage_set(self):
|
|
||||||
self.ext.set_usage('digitalSignature', True)
|
|
||||||
self.ext.set_usage('keyAgreement', False)
|
|
||||||
self.assertTrue(self.ext.get_usage('digitalSignature'))
|
|
||||||
self.assertFalse(self.ext.get_usage('keyAgreement'))
|
|
||||||
|
|
||||||
def test_usage_reset(self):
|
|
||||||
self.ext.set_usage('digitalSignature', True)
|
|
||||||
self.ext.set_usage('digitalSignature', False)
|
|
||||||
self.assertFalse(self.ext.get_usage('digitalSignature'))
|
|
||||||
|
|
||||||
def test_usage_unset(self):
|
|
||||||
self.assertFalse(self.ext.get_usage('keyAgreement'))
|
|
||||||
|
|
||||||
def test_get_all_usage(self):
|
|
||||||
self.ext.set_usage('digitalSignature', True)
|
|
||||||
self.ext.set_usage('keyAgreement', False)
|
|
||||||
self.ext.set_usage('keyEncipherment', True)
|
|
||||||
self.assertEqual(set(['digitalSignature', 'keyEncipherment']),
|
|
||||||
set(self.ext.get_all_usages()))
|
|
||||||
|
|
||||||
def test_str(self):
|
|
||||||
self.ext.set_usage('digitalSignature', True)
|
|
||||||
self.assertEqual("keyUsage: digitalSignature", str(self.ext))
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubjectAltName(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionSubjectAltName()
|
|
||||||
self.domain = 'example.com'
|
|
||||||
self.ip = netaddr.IPAddress('1.2.3.4')
|
|
||||||
self.ip6 = netaddr.IPAddress('::1')
|
|
||||||
|
|
||||||
def test_dns_ids(self):
|
|
||||||
self.ext.add_dns_id(self.domain)
|
|
||||||
self.ext.add_ip(self.ip)
|
|
||||||
self.assertEqual([self.domain], self.ext.get_dns_ids())
|
|
||||||
|
|
||||||
def test_ips(self):
|
|
||||||
self.ext.add_dns_id(self.domain)
|
|
||||||
self.ext.add_ip(self.ip)
|
|
||||||
self.assertEqual([self.ip], self.ext.get_ips())
|
|
||||||
|
|
||||||
def test_ipv6(self):
|
|
||||||
self.ext.add_ip(self.ip6)
|
|
||||||
self.assertEqual([self.ip6], self.ext.get_ips())
|
|
||||||
|
|
||||||
def test_add_ip_invalid(self):
|
|
||||||
with self.assertRaises(errors.X509Error):
|
|
||||||
self.ext.add_ip("abcdef")
|
|
||||||
|
|
||||||
def test_str(self):
|
|
||||||
self.ext.add_dns_id(self.domain)
|
|
||||||
self.ext.add_ip(self.ip)
|
|
||||||
self.assertEqual("subjectAltName: DNS:example.com, IP:1.2.3.4",
|
|
||||||
str(self.ext))
|
|
||||||
|
|
||||||
|
|
||||||
class TestNameConstraints(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionNameConstraints()
|
|
||||||
|
|
||||||
def test_length(self):
|
|
||||||
self.assertEqual(0, self.ext.get_permitted_length())
|
|
||||||
self.assertEqual(0, self.ext.get_excluded_length())
|
|
||||||
|
|
||||||
def test_add(self):
|
|
||||||
test_name = 'example.com'
|
|
||||||
test_type = 'dNSName'
|
|
||||||
self.assertEqual(0, self.ext.get_permitted_length())
|
|
||||||
self.assertEqual(0, self.ext.get_excluded_length())
|
|
||||||
self.ext.add_permitted(test_type, test_name)
|
|
||||||
self.assertEqual(1, self.ext.get_permitted_length())
|
|
||||||
self.assertEqual(0, self.ext.get_excluded_length())
|
|
||||||
self.ext.add_excluded(test_type, test_name)
|
|
||||||
self.assertEqual(1, self.ext.get_permitted_length())
|
|
||||||
self.assertEqual(1, self.ext.get_excluded_length())
|
|
||||||
|
|
||||||
def test_excluded(self):
|
|
||||||
self.ext.add_excluded('dNSName', 'example.com')
|
|
||||||
self.assertEqual(self.ext.get_excluded_range(0), (0, None))
|
|
||||||
self.assertEqual(self.ext.get_excluded_name(0),
|
|
||||||
('dNSName', b'example.com'))
|
|
||||||
|
|
||||||
def test_permitted(self):
|
|
||||||
self.ext.add_permitted('dNSName', 'example.com')
|
|
||||||
self.assertEqual(self.ext.get_permitted_range(0), (0, None))
|
|
||||||
self.assertEqual(self.ext.get_permitted_name(0),
|
|
||||||
('dNSName', b'example.com'))
|
|
||||||
|
|
||||||
|
|
||||||
class TestExtendedKeyUsage(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionExtendedKeyUsage()
|
|
||||||
|
|
||||||
def test_get_all(self):
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_codeSigning, True)
|
|
||||||
usages = self.ext.get_all_usages()
|
|
||||||
self.assertEqual(2, len(usages))
|
|
||||||
self.assertIn(rfc5280.id_kp_clientAuth, usages)
|
|
||||||
|
|
||||||
def test_get_one(self):
|
|
||||||
self.assertFalse(self.ext.get_usage(rfc5280.id_kp_clientAuth))
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.assertTrue(self.ext.get_usage(rfc5280.id_kp_clientAuth))
|
|
||||||
|
|
||||||
def test_set(self):
|
|
||||||
self.assertEqual(0, len(self.ext.get_all_usages()))
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.assertEqual(1, len(self.ext.get_all_usages()))
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.assertEqual(1, len(self.ext.get_all_usages()))
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_codeSigning, True)
|
|
||||||
self.assertEqual(2, len(self.ext.get_all_usages()))
|
|
||||||
|
|
||||||
def test_unset(self):
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, False)
|
|
||||||
self.assertEqual(0, len(self.ext.get_all_usages()))
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, False)
|
|
||||||
self.assertEqual(0, len(self.ext.get_all_usages()))
|
|
||||||
|
|
||||||
def test_str(self):
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_clientAuth, True)
|
|
||||||
self.ext.set_usage(rfc5280.id_kp_codeSigning, True)
|
|
||||||
self.assertEqual(
|
|
||||||
"extKeyUsage: TLS Web Client Authentication, Code Signing",
|
|
||||||
str(self.ext))
|
|
||||||
|
|
||||||
def test_invalid_usage(self):
|
|
||||||
self.assertRaises(ValueError, self.ext.get_usage,
|
|
||||||
univ.ObjectIdentifier('1.2.3.4'))
|
|
||||||
self.assertRaises(ValueError, self.ext.set_usage, True,
|
|
||||||
univ.ObjectIdentifier('1.2.3.4'))
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthorityKeyId(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionAuthorityKeyId()
|
|
||||||
|
|
||||||
def test_key_id(self):
|
|
||||||
key_id = b"12345678"
|
|
||||||
self.ext.set_key_id(key_id)
|
|
||||||
self.assertEqual(key_id, self.ext.get_key_id())
|
|
||||||
|
|
||||||
def test_name_serial(self):
|
|
||||||
s = 12345678
|
|
||||||
self.ext.set_serial(s)
|
|
||||||
self.assertEqual(s, self.ext.get_serial())
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubjectKeyId(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.ext = extension.X509ExtensionSubjectKeyId()
|
|
||||||
|
|
||||||
def test_key_id(self):
|
|
||||||
key_id = b"12345678"
|
|
||||||
self.ext.set_key_id(key_id)
|
|
||||||
self.assertEqual(key_id, self.ext.get_key_id())
|
|
@ -1,34 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from anchor.X509 import utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestASN1Time(unittest.TestCase):
|
|
||||||
def test_round_check(self):
|
|
||||||
t = 0
|
|
||||||
asn1_time = utils.timestamp_to_asn1_time(t)
|
|
||||||
res = utils.asn1_time_to_timestamp(asn1_time)
|
|
||||||
self.assertEqual(t, res)
|
|
||||||
|
|
||||||
def test_post_2050(self):
|
|
||||||
"""Test date post 2050, which causes different encoding."""
|
|
||||||
t = 2600000000
|
|
||||||
asn1_time = utils.timestamp_to_asn1_time(t)
|
|
||||||
res = utils.asn1_time_to_timestamp(asn1_time)
|
|
||||||
self.assertEqual(t, res)
|
|
@ -1,296 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
import io
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from anchor.X509 import certificate
|
|
||||||
from anchor.X509 import errors as x509_errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name as x509_name
|
|
||||||
|
|
||||||
|
|
||||||
class TestX509Cert(unittest.TestCase):
|
|
||||||
cert_data = textwrap.dedent(u"""
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICuDCCAiGgAwIBAgIJAIaZlZ0Oms2fMA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
|
|
||||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRYwFAYDVQQKDA1IZXJwIERlcnAg
|
|
||||||
cGxjMRYwFAYDVQQLDA1oZXJwLmRlcnAucGxjMRYwFAYDVQQDDA1oZXJwLmRlcnAu
|
|
||||||
cGxjMB4XDTE1MDkwMTIzNDcwNVoXDTE1MDkwMjIzNDcwNVowgZQxCzAJBgNVBAYT
|
|
||||||
AlVLMQ8wDQYDVQQIDAZOYXJuaWExEjAQBgNVBAcMCUZ1bmt5dG93bjEXMBUGA1UE
|
|
||||||
CgwOQW5jaG9yIFRlc3RpbmcxEDAOBgNVBAsMB3Rlc3RpbmcxFDASBgNVBAMMC2Fu
|
|
||||||
Y2hvci50ZXN0MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGFuY2hvci50ZXN0MIGfMA0G
|
|
||||||
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeeqg1Qeccv8hqj1BP9KEJX5QsFCxR62M8
|
|
||||||
plPb5t4sLo8UYfZd6kFLcOP8xzwwvx/eFY6Sux52enQ197o8aMwyP77hMhZqtd8N
|
|
||||||
CgLJMVlUbRhwLti0SkHFPic0wAg+esfXa6yhd5TxC+bti7MgV/ljA80XQxHH8xOj
|
|
||||||
dOoGN0DHfQIDAQABozswOTAfBgNVHSMEGDAWgBTe1pcxYWGrNC/uksuFloCGv41g
|
|
||||||
3TAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DANBgkqhkiG9w0BAQsFAAOBgQAy+2HQ
|
|
||||||
kXyNc5SwjvCXMDWMTKSB5bEWPxuJw3Lf1G4czHAyANzGlm1HJ/h6Z8NSwEy9x0xj
|
|
||||||
iFnpbc39fGoeApkEqVhY0WyJ7qbCuJsExE+ra6w+iPIKvjez+Ymp+zCDsiTIJEnf
|
|
||||||
2jsyzhghVa/FgDpQYQEJHAuGTEAvkQITp8IUvg==
|
|
||||||
-----END CERTIFICATE-----""")
|
|
||||||
|
|
||||||
key_dsa_data = textwrap.dedent("""
|
|
||||||
-----BEGIN DSA PARAMETERS-----
|
|
||||||
MIICLAKCAQEA59W1OsK9Tv7DRbxzibGVpBAL2Oz8JhbV3ii7WAat+UfTBLAnfdva
|
|
||||||
7UE8odu1l8p41N/8H/tDWgPh6tOgdX0YT9HDsILymQxzUEscliFZKmYg7YdSH3Zd
|
|
||||||
6DglOT7CqYxX0r9gK/BOh8ESe3gqKncnThHnO8Eu9wP8HNcrN00EOqP+fJpbS0lu
|
|
||||||
iifD9JdFY5YpCsLDIvpPbM0NCDuANPo10N3qqC8BuNiu0VfZpRSBcqzU1kwABT5n
|
|
||||||
y7+8RMh5Xaa7xnhGctJ9s9n+QfWcF/vbgiDOBttb3d8r8Pqvoou8v7Q38Q6zILhf
|
|
||||||
hajevqjGqZwodbvbHGfFbWapgBjpBIr4zwIhAOq6uryEHQglirWCGFJLQlkzxghy
|
|
||||||
ctHBRXGuKYb+ltRTAoIBAHRUFxzd1vhjKQ5atIdG0AiXUNm7/uboe21EJDLf4lkE
|
|
||||||
7UHDZfwsHXxQHfozzIsp7gHcw7F6AVCgiNRi9vBYOemPswevoWiVKqLTVt1wMogD
|
|
||||||
EJI6VAQEbBmSrtvyuClCkEAlIY6daX9EV9KqbnetS4/xv4WFQ9FPE47VyQ50vvxK
|
|
||||||
JSyNZnJ1lN6FUD9R5YYfwERgND8EYJBD10UBKIvtORICTJUfaDAweTWhaVcXUID7
|
|
||||||
VGNGPauOdVQzWsWTrQn/f/hbXCB/KXgv1l92D6rEoT2j2YrqIv/qD/ZxPwhBfLdr
|
|
||||||
W241Cb+LT05LVCokRbWUdjfuO8SdSBAIvT9P6umG/uQ=
|
|
||||||
-----END DSA PARAMETERS-----
|
|
||||||
-----BEGIN DSA PRIVATE KEY-----
|
|
||||||
MIIDVwIBAAKCAQEA59W1OsK9Tv7DRbxzibGVpBAL2Oz8JhbV3ii7WAat+UfTBLAn
|
|
||||||
fdva7UE8odu1l8p41N/8H/tDWgPh6tOgdX0YT9HDsILymQxzUEscliFZKmYg7YdS
|
|
||||||
H3Zd6DglOT7CqYxX0r9gK/BOh8ESe3gqKncnThHnO8Eu9wP8HNcrN00EOqP+fJpb
|
|
||||||
S0luiifD9JdFY5YpCsLDIvpPbM0NCDuANPo10N3qqC8BuNiu0VfZpRSBcqzU1kwA
|
|
||||||
BT5ny7+8RMh5Xaa7xnhGctJ9s9n+QfWcF/vbgiDOBttb3d8r8Pqvoou8v7Q38Q6z
|
|
||||||
ILhfhajevqjGqZwodbvbHGfFbWapgBjpBIr4zwIhAOq6uryEHQglirWCGFJLQlkz
|
|
||||||
xghyctHBRXGuKYb+ltRTAoIBAHRUFxzd1vhjKQ5atIdG0AiXUNm7/uboe21EJDLf
|
|
||||||
4lkE7UHDZfwsHXxQHfozzIsp7gHcw7F6AVCgiNRi9vBYOemPswevoWiVKqLTVt1w
|
|
||||||
MogDEJI6VAQEbBmSrtvyuClCkEAlIY6daX9EV9KqbnetS4/xv4WFQ9FPE47VyQ50
|
|
||||||
vvxKJSyNZnJ1lN6FUD9R5YYfwERgND8EYJBD10UBKIvtORICTJUfaDAweTWhaVcX
|
|
||||||
UID7VGNGPauOdVQzWsWTrQn/f/hbXCB/KXgv1l92D6rEoT2j2YrqIv/qD/ZxPwhB
|
|
||||||
fLdrW241Cb+LT05LVCokRbWUdjfuO8SdSBAIvT9P6umG/uQCggEBAKrZAppbnKf1
|
|
||||||
pzSvE3gTaloitAJG+79BML5h1n67EWuv0i+Fq4eUAVJ23R8GR1HrYw6utZoYbu8u
|
|
||||||
k8eHrArMfTfbFaLwK/Nv33Hfm3aTTXnY6auLNkpbiZXuCQjWBFhb6F+B42V9/JJ8
|
|
||||||
RJ1UV6Y2ajjjMvpeh0cPlARw5UpKBgQ933DhefCWyFBPsPToFvd3uPO+GUN6VpNY
|
|
||||||
iR7G0AH3/LSVJRuz5/QCp86uLIoU3fBEf1KGYJrkVKlc9DtcNmDXgpP0d3fK+4Jw
|
|
||||||
bGvi5AD1sQOWryNujyS/d2K/PAagsD0M6XJFgkEV592OSlygbYtuo3t4AtAy8F0f
|
|
||||||
VHNXq2l01FMCIQCrkk1749eQg4W6j7HfLFvjbDcuIFTw98IKyEZuZ93cdA==
|
|
||||||
-----END DSA PRIVATE KEY-----""").encode('ascii')
|
|
||||||
|
|
||||||
key_rsa_data = textwrap.dedent("""
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXAIBAAKBgQCeeqg1Qeccv8hqj1BP9KEJX5QsFCxR62M8plPb5t4sLo8UYfZd
|
|
||||||
6kFLcOP8xzwwvx/eFY6Sux52enQ197o8aMwyP77hMhZqtd8NCgLJMVlUbRhwLti0
|
|
||||||
SkHFPic0wAg+esfXa6yhd5TxC+bti7MgV/ljA80XQxHH8xOjdOoGN0DHfQIDAQAB
|
|
||||||
AoGBAJ2ozJpe+7qgGJPaCz3f0izvBwtq7kR49fqqRZbo8HHnx7OxWVVI7LhOkKEy
|
|
||||||
2/Bq0xsvOu1CdiXL4LynvIDIiQqLaeINzG48Rbk+0HadbXblt3nDkIWdYII6zHKI
|
|
||||||
W9ewX4KpHEPbrlEO9BjAlAcYsDIvFIMYpQhtQ+0R/gmZ99WJAkEAz5C2a6FIcMbE
|
|
||||||
o3aTc9ECq99zY7lxh+6aLpUdIeeHyb/QzfGDBdlbpBAkA6EcxSqp0aqH4xIQnYHa
|
|
||||||
3P5ZCShqSwJBAMN1sb76xq94xkg2cxShPFPAE6xKRFyKqLgsBYVtulOdfOtOnjh9
|
|
||||||
1SK2XQQfBRIRdG4Q/gDoCP8XQHpJcWMk+FcCQDnuJqulaOVo5GrG5mJ1nCxCAh98
|
|
||||||
G06X7lo/7dCPoRtSuMExvaK9RlFk29hTeAcjYCAPWzupyA9dtarmJg1jRT8CQCKf
|
|
||||||
gYnb8D/6+9yk0IPR/9ayCooVacCeyz48hgnZowzWs98WwQ4utAd/GED3obVOpDov
|
|
||||||
Bl9wus889i3zPoOac+cCQCZHredQcJGd4dlthbVtP2NhuPXz33JuETGR9pXtsDUZ
|
|
||||||
uX/nSq1oo9kUh/dPOz6aP5Ues1YVe3LExmExPBQfwIE=
|
|
||||||
-----END RSA PRIVATE KEY-----""").encode('ascii')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestX509Cert, self).setUp()
|
|
||||||
self.cert = certificate.X509Certificate.from_buffer(
|
|
||||||
TestX509Cert.cert_data)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_bad_data_throws(self):
|
|
||||||
bad_data = (
|
|
||||||
u"some bad data is "
|
|
||||||
"EHRlc3RAYW5jaG9yLnRlc3QwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA6m")
|
|
||||||
|
|
||||||
cert = certificate.X509Certificate()
|
|
||||||
self.assertRaises(x509_errors.X509Error,
|
|
||||||
cert.from_buffer,
|
|
||||||
bad_data)
|
|
||||||
|
|
||||||
def test_get_subject_countryName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_get_subject_stateOrProvinceName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_stateOrProvinceName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "stateOrProvinceName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Narnia")
|
|
||||||
|
|
||||||
def test_get_subject_localityName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_localityName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "localityName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Funkytown")
|
|
||||||
|
|
||||||
def test_get_subject_organizationName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_organizationName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Anchor Testing")
|
|
||||||
|
|
||||||
def test_get_subject_organizationUnitName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_organizationalUnitName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationalUnitName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "testing")
|
|
||||||
|
|
||||||
def test_get_subject_commonName(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "commonName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "anchor.test")
|
|
||||||
|
|
||||||
def test_get_subject_emailAddress(self):
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_pkcs9_emailAddress)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "emailAddress")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test@anchor.test")
|
|
||||||
|
|
||||||
def test_get_issuer_countryName(self):
|
|
||||||
name = self.cert.get_issuer()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "AU")
|
|
||||||
|
|
||||||
def test_get_issuer_stateOrProvinceName(self):
|
|
||||||
name = self.cert.get_issuer()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_stateOrProvinceName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "stateOrProvinceName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Some-State")
|
|
||||||
|
|
||||||
def test_get_issuer_organizationName(self):
|
|
||||||
name = self.cert.get_issuer()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_organizationName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Herp Derp plc")
|
|
||||||
|
|
||||||
def test_get_issuer_commonName(self):
|
|
||||||
name = self.cert.get_issuer()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "commonName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "herp.derp.plc")
|
|
||||||
|
|
||||||
def test_set_subject(self):
|
|
||||||
name = x509_name.X509Name()
|
|
||||||
name.add_name_entry(x509_name.OID_countryName, 'UK')
|
|
||||||
self.cert.set_subject(name)
|
|
||||||
|
|
||||||
name = self.cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_set_issuer(self):
|
|
||||||
name = x509_name.X509Name()
|
|
||||||
name.add_name_entry(x509_name.OID_countryName, 'UK')
|
|
||||||
self.cert.set_issuer(name)
|
|
||||||
|
|
||||||
name = self.cert.get_issuer()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_read_from_file(self):
|
|
||||||
open_name = 'anchor.X509.certificate.open'
|
|
||||||
f = io.StringIO(TestX509Cert.cert_data)
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.return_value = f
|
|
||||||
|
|
||||||
cert = certificate.X509Certificate.from_file("some_path")
|
|
||||||
name = cert.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_get_fingerprint(self):
|
|
||||||
fp = self.cert.get_fingerprint()
|
|
||||||
self.assertEqual(fp, '03C6B30446157984C28A3C97F1616B96'
|
|
||||||
'5DED16744573F203A4EA51AB1AFA1F10')
|
|
||||||
|
|
||||||
def test_get_fingerprint_invalid_hash(self):
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
self.cert.get_fingerprint('no_such_hash')
|
|
||||||
|
|
||||||
def test_get_version(self):
|
|
||||||
v = self.cert.get_version()
|
|
||||||
self.assertEqual(v, 2)
|
|
||||||
|
|
||||||
def test_set_version(self):
|
|
||||||
self.cert.set_version(5)
|
|
||||||
v = self.cert.get_version()
|
|
||||||
self.assertEqual(v, 5)
|
|
||||||
|
|
||||||
def test_get_not_before(self):
|
|
||||||
val = self.cert.get_not_before()
|
|
||||||
self.assertEqual(1441151225.0, val)
|
|
||||||
|
|
||||||
def test_set_not_before(self):
|
|
||||||
self.cert.set_not_before(0) # seconds since epoch
|
|
||||||
val = self.cert.get_not_before()
|
|
||||||
self.assertEqual(0, val)
|
|
||||||
|
|
||||||
def test_get_not_after(self):
|
|
||||||
val = self.cert.get_not_after()
|
|
||||||
self.assertEqual(1441237625.0, val)
|
|
||||||
|
|
||||||
def test_set_not_after(self):
|
|
||||||
self.cert.set_not_after(0) # seconds since epoch
|
|
||||||
val = self.cert.get_not_after()
|
|
||||||
self.assertEqual(0, val)
|
|
||||||
|
|
||||||
def test_get_extensions(self):
|
|
||||||
exts = self.cert.get_extensions()
|
|
||||||
self.assertEqual(3, len(exts))
|
|
||||||
|
|
||||||
def test_add_extensions(self):
|
|
||||||
bc = extension.X509ExtensionBasicConstraints()
|
|
||||||
self.cert.add_extension(bc, 2)
|
|
||||||
exts = self.cert.get_extensions()
|
|
||||||
self.assertEqual(3, len(exts))
|
|
||||||
|
|
||||||
def test_add_extensions_invalid(self):
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
self.cert.add_extension("abcdef", 2)
|
|
||||||
|
|
||||||
def test_verify_unknown_key(self):
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
self.cert.verify("abc")
|
|
||||||
|
|
||||||
def test_verify_signature_mismatch(self):
|
|
||||||
alg = asn1_univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
self.cert._cert['signatureAlgorithm']['algorithm'] = alg
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
self.cert.verify()
|
|
||||||
|
|
||||||
def test_verify_algo_mismatch(self):
|
|
||||||
alg = asn1_univ.ObjectIdentifier('1.2.3.4')
|
|
||||||
self.cert._cert['signatureAlgorithm']['algorithm'] = alg
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
self.cert.verify("abc")
|
|
@ -1,199 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import io
|
|
||||||
import textwrap
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from pyasn1_modules import rfc2459
|
|
||||||
|
|
||||||
from anchor.signers import cryptography_io
|
|
||||||
from anchor.X509 import errors as x509_errors
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name as x509_name
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
from anchor.X509 import utils
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestX509Csr(tests.DefaultRequestMixin, unittest.TestCase):
|
|
||||||
key_rsa_data = textwrap.dedent("""
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXAIBAAKBgQCeeqg1Qeccv8hqj1BP9KEJX5QsFCxR62M8plPb5t4sLo8UYfZd
|
|
||||||
6kFLcOP8xzwwvx/eFY6Sux52enQ197o8aMwyP77hMhZqtd8NCgLJMVlUbRhwLti0
|
|
||||||
SkHFPic0wAg+esfXa6yhd5TxC+bti7MgV/ljA80XQxHH8xOjdOoGN0DHfQIDAQAB
|
|
||||||
AoGBAJ2ozJpe+7qgGJPaCz3f0izvBwtq7kR49fqqRZbo8HHnx7OxWVVI7LhOkKEy
|
|
||||||
2/Bq0xsvOu1CdiXL4LynvIDIiQqLaeINzG48Rbk+0HadbXblt3nDkIWdYII6zHKI
|
|
||||||
W9ewX4KpHEPbrlEO9BjAlAcYsDIvFIMYpQhtQ+0R/gmZ99WJAkEAz5C2a6FIcMbE
|
|
||||||
o3aTc9ECq99zY7lxh+6aLpUdIeeHyb/QzfGDBdlbpBAkA6EcxSqp0aqH4xIQnYHa
|
|
||||||
3P5ZCShqSwJBAMN1sb76xq94xkg2cxShPFPAE6xKRFyKqLgsBYVtulOdfOtOnjh9
|
|
||||||
1SK2XQQfBRIRdG4Q/gDoCP8XQHpJcWMk+FcCQDnuJqulaOVo5GrG5mJ1nCxCAh98
|
|
||||||
G06X7lo/7dCPoRtSuMExvaK9RlFk29hTeAcjYCAPWzupyA9dtarmJg1jRT8CQCKf
|
|
||||||
gYnb8D/6+9yk0IPR/9ayCooVacCeyz48hgnZowzWs98WwQ4utAd/GED3obVOpDov
|
|
||||||
Bl9wus889i3zPoOac+cCQCZHredQcJGd4dlthbVtP2NhuPXz33JuETGR9pXtsDUZ
|
|
||||||
uX/nSq1oo9kUh/dPOz6aP5Ues1YVe3LExmExPBQfwIE=
|
|
||||||
-----END RSA PRIVATE KEY-----""").encode('ascii')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestX509Csr, self).setUp()
|
|
||||||
self.csr = signing_request.X509Csr.from_buffer(
|
|
||||||
TestX509Csr.csr_sample_bytes)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_get_pubkey(self):
|
|
||||||
pubkey = self.csr.get_pubkey()
|
|
||||||
self.assertEqual(pubkey['algorithm']['algorithm'],
|
|
||||||
rfc2459.rsaEncryption)
|
|
||||||
|
|
||||||
def test_get_extensions(self):
|
|
||||||
exts = self.csr.get_extensions()
|
|
||||||
self.assertEqual(len(exts), 2)
|
|
||||||
self.assertFalse(exts[1].get_ca())
|
|
||||||
self.assertIsNone(exts[1].get_path_len_constraint())
|
|
||||||
self.assertTrue(exts[0].get_usage('digitalSignature'))
|
|
||||||
self.assertTrue(exts[0].get_usage('nonRepudiation'))
|
|
||||||
self.assertTrue(exts[0].get_usage('keyEncipherment'))
|
|
||||||
self.assertFalse(exts[0].get_usage('cRLSign'))
|
|
||||||
|
|
||||||
def test_add_extension(self):
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
bc = extension.X509ExtensionBasicConstraints()
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
csr.add_extension(bc)
|
|
||||||
self.assertEqual(1, len(csr.get_extensions()))
|
|
||||||
csr.add_extension(bc)
|
|
||||||
self.assertEqual(1, len(csr.get_extensions()))
|
|
||||||
csr.add_extension(san)
|
|
||||||
self.assertEqual(2, len(csr.get_extensions()))
|
|
||||||
|
|
||||||
def test_add_extension_invalid_type(self):
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
with self.assertRaises(x509_errors.X509Error):
|
|
||||||
csr.add_extension(1234)
|
|
||||||
|
|
||||||
def test_read_from_file(self):
|
|
||||||
open_name = 'anchor.X509.signing_request.open'
|
|
||||||
f = io.BytesIO(self.csr_sample_bytes)
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.return_value = f
|
|
||||||
csr = signing_request.X509Csr.from_file("some_path")
|
|
||||||
|
|
||||||
name = csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_open_failure_throws(self):
|
|
||||||
open_name = 'anchor.X509.signing_request.open'
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.side_effect = IOError(2, "No such file or directory",
|
|
||||||
"some_path")
|
|
||||||
self.assertRaisesRegexp(x509_errors.X509Error,
|
|
||||||
"Could not read file",
|
|
||||||
signing_request.X509Csr.from_file,
|
|
||||||
"some_path")
|
|
||||||
|
|
||||||
def test_read_failure_throws(self):
|
|
||||||
f = mock.Mock()
|
|
||||||
f.read.side_effect = IOError(5, "Read failed")
|
|
||||||
self.assertRaisesRegexp(x509_errors.X509Error,
|
|
||||||
"Could not read from source",
|
|
||||||
signing_request.X509Csr.from_open_file,
|
|
||||||
f)
|
|
||||||
|
|
||||||
def test_bad_pem_throws(self):
|
|
||||||
bad_data = (
|
|
||||||
b"-----BEGIN SOMETHING-----\n"
|
|
||||||
b"++++++\n"
|
|
||||||
b"-----END SOMETHING-----\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
self.assertRaisesRegexp(x509_errors.X509Error, "not in PEM format",
|
|
||||||
csr.from_buffer,
|
|
||||||
bad_data)
|
|
||||||
|
|
||||||
def test_bad_data_throws(self):
|
|
||||||
bad_data = (
|
|
||||||
b"some bad data is "
|
|
||||||
b"EHRlc3RAYW5jaG9yLnRlc3QwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA6m")
|
|
||||||
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
self.assertRaisesRegexp(x509_errors.X509Error, "No PEM data found",
|
|
||||||
csr.from_buffer,
|
|
||||||
bad_data)
|
|
||||||
|
|
||||||
def test_get_subject_countryName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_get_subject_stateOrProvinceName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_stateOrProvinceName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "stateOrProvinceName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Narnia")
|
|
||||||
|
|
||||||
def test_get_subject_localityName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_localityName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "localityName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Funkytown")
|
|
||||||
|
|
||||||
def test_get_subject_organizationName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_organizationName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "Anchor Testing")
|
|
||||||
|
|
||||||
def test_get_subject_organizationUnitName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_organizationalUnitName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationalUnitName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "testing")
|
|
||||||
|
|
||||||
def test_get_subject_commonName(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "commonName")
|
|
||||||
self.assertEqual(entries[0].get_value(), self.csr_sample_cn)
|
|
||||||
|
|
||||||
def test_get_subject_emailAddress(self):
|
|
||||||
name = self.csr.get_subject()
|
|
||||||
entries = name.get_entries_by_oid(x509_name.OID_pkcs9_emailAddress)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "emailAddress")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test@example.com")
|
|
||||||
|
|
||||||
def test_sign(self):
|
|
||||||
key = utils.get_private_key_from_pem(self.key_rsa_data)
|
|
||||||
signer = cryptography_io.make_signer(key, 'RSA', 'SHA256')
|
|
||||||
self.csr.sign('RSA', 'SHA256', signer)
|
|
||||||
# 10 bytes is definitely enough for non malicious case, right?
|
|
||||||
self.assertEqual(b'\x16\xbd!\x9b\xfb\xfd\x10\xa1\xaf\x92',
|
|
||||||
self.csr._get_signature()[:10])
|
|
||||||
|
|
||||||
def test_verify(self):
|
|
||||||
self.assertTrue(self.csr.verify())
|
|
@ -1,132 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from anchor.X509 import errors as x509_errors
|
|
||||||
from anchor.X509 import name as x509_name
|
|
||||||
|
|
||||||
|
|
||||||
class TestX509Name(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestX509Name, self).setUp()
|
|
||||||
self.name = x509_name.X509Name()
|
|
||||||
self.name.add_name_entry(x509_name.OID_countryName,
|
|
||||||
"UK") # must be 2 chars
|
|
||||||
self.name.add_name_entry(x509_name.OID_stateOrProvinceName, "test_ST")
|
|
||||||
self.name.add_name_entry(x509_name.OID_localityName, "test_L")
|
|
||||||
self.name.add_name_entry(x509_name.OID_organizationName, "test_O")
|
|
||||||
self.name.add_name_entry(x509_name.OID_organizationalUnitName,
|
|
||||||
"test_OU")
|
|
||||||
self.name.add_name_entry(x509_name.OID_commonName, "test_CN")
|
|
||||||
self.name.add_name_entry(x509_name.OID_pkcs9_emailAddress,
|
|
||||||
"test_Email")
|
|
||||||
self.name.add_name_entry(x509_name.OID_surname, "test_SN")
|
|
||||||
self.name.add_name_entry(x509_name.OID_givenName, "test_GN")
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_add_bad_entry_throws(self):
|
|
||||||
self.assertRaises(x509_errors.X509Error,
|
|
||||||
self.name.add_name_entry,
|
|
||||||
-1, "BAD_WRONG")
|
|
||||||
|
|
||||||
def test_set_bad_c_throws(self):
|
|
||||||
self.assertRaises(x509_errors.X509Error,
|
|
||||||
self.name.add_name_entry,
|
|
||||||
x509_name.OID_countryName, "BAD_WRONG")
|
|
||||||
|
|
||||||
def test_name_to_string(self):
|
|
||||||
val = str(self.name)
|
|
||||||
self.assertEqual(val, ("/C=UK/ST=test_ST/L=test_L/O=test_O/OU=test_OU"
|
|
||||||
"/CN=test_CN/emailAddress=test_Email/"
|
|
||||||
"SN=test_SN/GN=test_GN"))
|
|
||||||
|
|
||||||
def test_get_countryName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(x509_name.OID_countryName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "countryName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "UK")
|
|
||||||
|
|
||||||
def test_get_stateOrProvinceName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(
|
|
||||||
x509_name.OID_stateOrProvinceName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "stateOrProvinceName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_ST")
|
|
||||||
|
|
||||||
def test_get_subject_localityName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(x509_name.OID_localityName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "localityName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_L")
|
|
||||||
|
|
||||||
def test_get_organizationName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(x509_name.OID_organizationName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_O")
|
|
||||||
|
|
||||||
def test_get_organizationUnitName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(
|
|
||||||
x509_name.OID_organizationalUnitName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "organizationalUnitName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_OU")
|
|
||||||
|
|
||||||
def test_get_commonName(self):
|
|
||||||
entries = self.name.get_entries_by_oid(x509_name.OID_commonName)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "commonName")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_CN")
|
|
||||||
|
|
||||||
def test_get_emailAddress(self):
|
|
||||||
entries = self.name.get_entries_by_oid(
|
|
||||||
x509_name.OID_pkcs9_emailAddress)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(entries[0].get_name(), "emailAddress")
|
|
||||||
self.assertEqual(entries[0].get_value(), "test_Email")
|
|
||||||
|
|
||||||
def test_entry_to_string(self):
|
|
||||||
entries = self.name.get_entries_by_oid(
|
|
||||||
x509_name.OID_pkcs9_emailAddress)
|
|
||||||
self.assertEqual(len(entries), 1)
|
|
||||||
self.assertEqual(str(entries[0]), "emailAddress: test_Email")
|
|
||||||
|
|
||||||
def test_entry_length(self):
|
|
||||||
num = len(self.name)
|
|
||||||
self.assertEqual(num, 9)
|
|
||||||
|
|
||||||
def test_entry_index_good(self):
|
|
||||||
self.assertEqual("givenName: test_GN", str(self.name[8]))
|
|
||||||
|
|
||||||
def test_entry_index_bad(self):
|
|
||||||
with self.assertRaises(IndexError):
|
|
||||||
self.name[9]
|
|
||||||
|
|
||||||
def test_entry_itter(self):
|
|
||||||
val = [str(e) for e in self.name]
|
|
||||||
self.assertEqual("countryName: UK", val[0])
|
|
||||||
self.assertEqual("givenName: test_GN", val[8])
|
|
||||||
|
|
||||||
def test_deep_clone(self):
|
|
||||||
orig = x509_name.X509Name()
|
|
||||||
orig.add_name_entry(x509_name.OID_countryName, "UK")
|
|
||||||
clone = x509_name.X509Name(orig._name_obj)
|
|
||||||
self.assertEqual(str(orig), str(clone))
|
|
||||||
clone.add_name_entry(x509_name.OID_stateOrProvinceName, "test_ST")
|
|
||||||
self.assertNotEqual(str(orig), str(clone))
|
|
@ -1,102 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
# NOTE(tkelsey): by default Python 2.7 has no default logging handler
|
|
||||||
# this fixes the "No handler for logger ..." message spam
|
|
||||||
#
|
|
||||||
handler = logging.NullHandler()
|
|
||||||
logging.getLogger().addHandler(handler)
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultConfigMixin(object):
|
|
||||||
"""Mixin for reuse in any test class which needs to load a config.
|
|
||||||
|
|
||||||
`sample_conf` is always a valid, no thrills configuration. It can be
|
|
||||||
reused in any test case. Constructing it in setUp() guarantees that it
|
|
||||||
can be changed without affecting other tests.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.sample_conf_auth = {
|
|
||||||
"default_auth": {
|
|
||||||
"backend": "static",
|
|
||||||
"user": "myusername",
|
|
||||||
"secret": "simplepassword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sample_conf_ca = {
|
|
||||||
"default_ca": {
|
|
||||||
"backend": "anchor",
|
|
||||||
"cert_path": "tests/CA/root-ca.crt",
|
|
||||||
"key_path": "tests/CA/root-ca-unwrapped.key",
|
|
||||||
"output_path": "certs",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sample_conf_validators = {
|
|
||||||
"common_name": {
|
|
||||||
"allowed_domains": [".example.com"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sample_conf_fixups = {
|
|
||||||
}
|
|
||||||
self.sample_conf_ra = {
|
|
||||||
"default_ra": {
|
|
||||||
"authentication": "default_auth",
|
|
||||||
"signing_ca": "default_ca",
|
|
||||||
"validators": self.sample_conf_validators,
|
|
||||||
"fixups": self.sample_conf_fixups,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sample_conf = {
|
|
||||||
"authentication": self.sample_conf_auth,
|
|
||||||
"signing_ca": self.sample_conf_ca,
|
|
||||||
"registration_authority": self.sample_conf_ra,
|
|
||||||
}
|
|
||||||
|
|
||||||
super(DefaultConfigMixin, self).setUp()
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultRequestMixin(object):
|
|
||||||
# CN=server1.example.com
|
|
||||||
# 2048 RSA, basicConstraints, keyUsage exts
|
|
||||||
csr_sample_cn = 'server1.example.com'
|
|
||||||
csr_sample = textwrap.dedent("""
|
|
||||||
-----BEGIN CERTIFICATE REQUEST-----
|
|
||||||
MIIDDjCCAfYCAQAwgZwxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIEwZOYXJuaWExEjAQ
|
|
||||||
BgNVBAcTCUZ1bmt5dG93bjEXMBUGA1UEChMOQW5jaG9yIFRlc3RpbmcxEDAOBgNV
|
|
||||||
BAsTB3Rlc3RpbmcxHDAaBgNVBAMTE3NlcnZlcjEuZXhhbXBsZS5jb20xHzAdBgkq
|
|
||||||
hkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
|
||||||
DwAwggEKAoIBAQDhQloUTMZwBFgbseH5vk4S+mgqwyZDytu9S6x7YPv4aav/FTQd
|
|
||||||
W/RJB07YvUIZSJ50YScNSzXrtjqqifjdvnyiVYpS+vP8/yZIclJt8BNLwA3ESvHO
|
|
||||||
75leRhSahxMkIMW7WfaV4ys8jkGDx3fISCn/jo5zelaLXaiHAzGRRMKefWmy54lX
|
|
||||||
W6jh1caoadRsnFQbAmAljW0JNQ53Sr2KOwVu6I8/IJ9PcT16D0WembvuOsNZZ8V9
|
|
||||||
y2FYiJ4FYesN9JGoKvBC8U1pr+FXpNfEdaniNbfRsz5gCsap3mxMMLKlFS7AB2ar
|
|
||||||
zw5awegV9M7gMYkg4e6HWl33fS+kt/zSC53rAgMBAAGgLDAqBgkqhkiG9w0BCQ4x
|
|
||||||
HTAbMAsGA1UdDwQEAwIF4DAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IB
|
|
||||||
AQArTSUNFZHqUnCL+TLVgDSq9oaSutO3vu1g+EKfFxN2rG5HrxbAc2eC8TaMfUVd
|
|
||||||
D2JaEkhi9X7wPpVKIVwMo4nYVO8ke1MdXRLecNzLRT4sC40ZuOoDxOFEzm5BibGv
|
|
||||||
OLty0xKx3fylL0qa+wMXQNDWVcbq3OcJNo4v41fl4jlab4Fx5mWaCnKja+LnJT45
|
|
||||||
4wJQQN+UFPwvEt3Ay2UqvzVVUlJ3tO30f5WZitlpYy9txLaV9v6xdc2N/YMgQ7Tz
|
|
||||||
DxpZNBHlkA6LWaRqAtWws3uvom7IjHGgSr7UITrOR5iO5Hrm85X7K0AT6Bu75RZL
|
|
||||||
+uYLLfj9Nb/iznREl9E3a/fN
|
|
||||||
-----END CERTIFICATE REQUEST-----""")
|
|
||||||
csr_sample_bytes = csr_sample.encode('ascii')
|
|
@ -1,143 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import mock
|
|
||||||
import requests
|
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
from anchor.auth import keystone
|
|
||||||
from anchor.auth import results
|
|
||||||
|
|
||||||
|
|
||||||
class AuthKeystoneTests(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.config = "anchor.jsonloader.conf._config"
|
|
||||||
self.data = {'auth': {'keystone': {'url': 'http://localhost:35357'}}}
|
|
||||||
self.json_response = {
|
|
||||||
"token": {
|
|
||||||
"audit_ids": [
|
|
||||||
"TPDsHuK_QCaKwvkVlAer8A"
|
|
||||||
],
|
|
||||||
"catalog": [
|
|
||||||
{
|
|
||||||
"endpoints": [
|
|
||||||
{
|
|
||||||
"id": "1390df96096d4bd19add44811db34397",
|
|
||||||
"interface": "public",
|
|
||||||
"region": "RegionOne",
|
|
||||||
"region_id": "RegionOne",
|
|
||||||
"url": "http://10.0.2.15:5000/v2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "534bcae735614781a03069d637b21570",
|
|
||||||
"interface": "internal",
|
|
||||||
"region": "RegionOne",
|
|
||||||
"region_id": "RegionOne",
|
|
||||||
"url": "http://10.0.2.15:5000/v2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cc7e879d691e4e4b9f4afecb1a3ce8f0",
|
|
||||||
"interface": "admin",
|
|
||||||
"region": "RegionOne",
|
|
||||||
"region_id": "RegionOne",
|
|
||||||
"url": "http://10.0.2.15:35357/v2.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "3010a0c9af684db28659f0e9e08ee863",
|
|
||||||
"name": "keystone",
|
|
||||||
"type": "identity"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"expires_at": "2015-07-27T02:38:09.000000Z",
|
|
||||||
"extras": {},
|
|
||||||
"issued_at": "2015-07-27T01:38:09.409616",
|
|
||||||
"methods": [
|
|
||||||
"password",
|
|
||||||
"token"
|
|
||||||
],
|
|
||||||
"project": {
|
|
||||||
"domain": {
|
|
||||||
"id": "default",
|
|
||||||
"name": "Default"
|
|
||||||
},
|
|
||||||
"id": "5b2e7bd5d5954fdaa2d931285df8a132",
|
|
||||||
"name": "demo"
|
|
||||||
},
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"id": "35a1d29b54f64c969aa9be288ec9d39a",
|
|
||||||
"name": "anotherrole"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9f64371fcbd64c669ab1a24686a1a367",
|
|
||||||
"name": "Member"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"user": {
|
|
||||||
"domain": {
|
|
||||||
"id": "default",
|
|
||||||
"name": "Default"
|
|
||||||
},
|
|
||||||
"id": "b2016b9338214cda926d5631c1fbc40c",
|
|
||||||
"name": "demo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.user = self.json_response['token']['user']['name']
|
|
||||||
self.roles = [role['name']
|
|
||||||
for role in self.json_response['token']['roles']]
|
|
||||||
self.user_id = self.json_response['token']['user']['id']
|
|
||||||
self.project_id = self.json_response['token']['project']['id']
|
|
||||||
self.expected = results.AuthDetails(
|
|
||||||
username=self.user, groups=self.roles,
|
|
||||||
user_id=self.user_id, project_id=self.project_id)
|
|
||||||
|
|
||||||
self.keystone_url = self.data['auth'][
|
|
||||||
'keystone']['url'] + '/v3/auth/tokens'
|
|
||||||
self.keystone_token = uuid.uuid4().hex
|
|
||||||
|
|
||||||
super(AuthKeystoneTests, self).setUp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_parse_keystone_valid_response(self):
|
|
||||||
with mock.patch.dict(self.config, self.data):
|
|
||||||
with requests_mock.mock() as m:
|
|
||||||
m.get(self.keystone_url, json=self.json_response,
|
|
||||||
status_code=200)
|
|
||||||
requests.get(self.keystone_url)
|
|
||||||
self.assertEqual(keystone.login(
|
|
||||||
None, self.keystone_token), self.expected)
|
|
||||||
|
|
||||||
def test_parse_keystone_auth_fail(self):
|
|
||||||
with mock.patch.dict(self.config, self.data):
|
|
||||||
with requests_mock.mock() as m:
|
|
||||||
m.get(self.keystone_url, status_code=401)
|
|
||||||
self.assertEqual(keystone.login(
|
|
||||||
None, self.keystone_token), None)
|
|
||||||
|
|
||||||
def test_parse_keystone_ok_but_malformed_response(self):
|
|
||||||
with mock.patch.dict(self.config, self.data):
|
|
||||||
with requests_mock.mock() as m:
|
|
||||||
m.get(self.keystone_url, json={}, status_code=200)
|
|
||||||
self.assertEqual(keystone.login(
|
|
||||||
None, self.keystone_token), None)
|
|
@ -1,117 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from ldap3.core import exceptions as ldap3_exc
|
|
||||||
import mock
|
|
||||||
from webob import exc as http_status
|
|
||||||
|
|
||||||
from anchor import auth
|
|
||||||
from anchor.auth import results
|
|
||||||
from anchor import jsonloader
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class AuthLdapTests(tests.DefaultConfigMixin, unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AuthLdapTests, self).setUp()
|
|
||||||
self.sample_conf_auth['default_auth'] = {
|
|
||||||
"backend": "ldap",
|
|
||||||
"host": "ldap.example.com",
|
|
||||||
"base": "CN=Users,DC=example,DC=com",
|
|
||||||
"domain": "example.com",
|
|
||||||
"port": 636,
|
|
||||||
"ssl": True
|
|
||||||
}
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@mock.patch('ldap3.Connection')
|
|
||||||
def test_login_good(self, mock_connection):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
|
|
||||||
mock_ldc = mock.Mock()
|
|
||||||
mock_connection.return_value = mock_ldc
|
|
||||||
mock_ldc.result = {'result': 0}
|
|
||||||
mock_ldc.response = [{'attributes': {}}]
|
|
||||||
|
|
||||||
with mock.patch.dict(config, self.sample_conf):
|
|
||||||
expected = results.AuthDetails(username='user', groups=[])
|
|
||||||
self.assertEqual(auth.validate('default_ra', 'user', 'pass'),
|
|
||||||
expected)
|
|
||||||
|
|
||||||
@mock.patch('ldap3.Connection')
|
|
||||||
def test_login_good_with_groups(self, mock_connection):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
|
|
||||||
mock_ldc = mock.Mock()
|
|
||||||
mock_connection.return_value = mock_ldc
|
|
||||||
mock_ldc.result = {'result': 0}
|
|
||||||
mock_ldc.response = [{'attributes': {'memberOf': [
|
|
||||||
u'CN=some_group,OU=Groups,DC=example,DC=com',
|
|
||||||
u'CN=other_group,OU=Groups,DC=example,DC=com']}}]
|
|
||||||
|
|
||||||
with mock.patch.dict(config, self.sample_conf):
|
|
||||||
expected = results.AuthDetails(
|
|
||||||
username='user',
|
|
||||||
groups=[u'some_group', u'other_group'])
|
|
||||||
self.assertEqual(auth.validate('default_ra', 'user', 'pass'),
|
|
||||||
expected)
|
|
||||||
|
|
||||||
@mock.patch('ldap3.Connection')
|
|
||||||
def test_login_search_fail(self, mock_connection):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
|
|
||||||
mock_ldc = mock.Mock()
|
|
||||||
mock_connection.return_value = mock_ldc
|
|
||||||
mock_ldc.result = {'result': 1}
|
|
||||||
|
|
||||||
with mock.patch.dict(config, self.sample_conf):
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'user', 'pass')
|
|
||||||
|
|
||||||
@mock.patch('ldap3.Connection')
|
|
||||||
def test_login_bind_fail(self, mock_connection):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
|
|
||||||
mock_connection.side_effect = ldap3_exc.LDAPBindError()
|
|
||||||
|
|
||||||
with mock.patch.dict(config, self.sample_conf):
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'user', 'pass')
|
|
||||||
|
|
||||||
@mock.patch('ldap3.Connection')
|
|
||||||
def test_login_connection_fail(self, mock_connection):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
|
|
||||||
mock_connection.side_effect = ldap3_exc.LDAPSocketOpenError()
|
|
||||||
|
|
||||||
with mock.patch.dict(config, self.sample_conf):
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'user', 'pass')
|
|
@ -1,70 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Nebula Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from webob import exc as http_status
|
|
||||||
|
|
||||||
from anchor import auth
|
|
||||||
from anchor.auth import results
|
|
||||||
from anchor import jsonloader
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class AuthStaticTests(tests.DefaultConfigMixin, unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AuthStaticTests, self).setUp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validate_static(self):
|
|
||||||
"""Test all static user/pass authentication paths."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_auth['default_auth'] = {
|
|
||||||
"backend": "static",
|
|
||||||
"user": "myusername",
|
|
||||||
"secret": "simplepassword"
|
|
||||||
}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
valid_user = self.sample_conf_auth['default_auth']['user']
|
|
||||||
valid_pass = self.sample_conf_auth['default_auth']['secret']
|
|
||||||
|
|
||||||
expected = results.AuthDetails(username=valid_user, groups=[])
|
|
||||||
self.assertEqual(auth.validate('default_ra', valid_user,
|
|
||||||
valid_pass), expected)
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', valid_user, 'badpass')
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'baduser', valid_pass)
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'baduser', 'badpass')
|
|
||||||
|
|
||||||
def test_validate_static_malformed1(self):
|
|
||||||
"""Test static user/pass authentication with malformed config."""
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_auth['default_auth'] = {'backend': 'static'}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(http_status.HTTPUnauthorized):
|
|
||||||
auth.validate('default_ra', 'baduser', 'badpass')
|
|
@ -1,256 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from anchor import app
|
|
||||||
from anchor import errors
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor import util
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestApp(tests.DefaultConfigMixin, unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.expected_key_permissions = (stat.S_IRUSR | stat.S_IFREG)
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
super(TestApp, self).setUp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
jsonloader.conf._config = {}
|
|
||||||
super(TestApp, self).tearDown()
|
|
||||||
|
|
||||||
def test_self_test(self):
|
|
||||||
self.assertTrue(True)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_exists')
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_config_check_domains_good(self, a, b):
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {
|
|
||||||
"common_name": {
|
|
||||||
"allowed_domains": [".example.com"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
|
|
||||||
config = {'return_value.st_mode': (stat.S_IRUSR | stat.S_IFREG)}
|
|
||||||
with mock.patch("os.stat", **config):
|
|
||||||
self.assertEqual(app.validate_config(jsonloader.conf), None)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_exists')
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_config_check_domains_bad(self, a, b):
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {
|
|
||||||
"common_name": {
|
|
||||||
"allowed_domains": ["error.example.com"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
|
|
||||||
config = {'return_value.st_mode': (stat.S_IRUSR | stat.S_IFREG)}
|
|
||||||
with mock.patch("os.stat", **config):
|
|
||||||
self.assertRaises(
|
|
||||||
errors.ConfigValidationException,
|
|
||||||
app.validate_config,
|
|
||||||
jsonloader.conf
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_check_file_permissions_good(self):
|
|
||||||
config = {'return_value.st_mode': (stat.S_IRUSR | stat.S_IFREG)}
|
|
||||||
with mock.patch("os.stat", **config):
|
|
||||||
util.check_file_permissions("/mock/path")
|
|
||||||
|
|
||||||
def test_check_file_permissions_bad(self):
|
|
||||||
config = {'return_value.st_mode': (stat.S_IWOTH | stat.S_IFREG)}
|
|
||||||
with mock.patch("os.stat", **config):
|
|
||||||
self.assertRaises(errors.ConfigValidationException,
|
|
||||||
util.check_file_permissions, "/mock/path")
|
|
||||||
|
|
||||||
def test_validate_old_config(self):
|
|
||||||
config = json.dumps({
|
|
||||||
"ca": {},
|
|
||||||
"auth": {},
|
|
||||||
"validators": {},
|
|
||||||
})
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"old version of Anchor",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_no_registration_authorities(self,
|
|
||||||
mock_check_perm):
|
|
||||||
del self.sample_conf['registration_authority']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No registration authorities present",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_no_auth(self, mock_check_perm):
|
|
||||||
del self.sample_conf['authentication']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No authentication methods present",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_no_auth_backend(self, mock_check_perm):
|
|
||||||
del self.sample_conf_auth['default_auth']['backend']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"Authentication method .* doesn't define "
|
|
||||||
"backend",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_no_ra_auth(self, mock_check_perm):
|
|
||||||
del self.sample_conf_ra['default_ra']['authentication']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No authentication .* for .* default_ra",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
def test_validate_config_no_ca(self):
|
|
||||||
del self.sample_conf['signing_ca']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No signing CA configurations present",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_no_ra_ca(self, mock_check_perm):
|
|
||||||
del self.sample_conf_ra['default_ra']['signing_ca']
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No signing CA .* for .* default_ra",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
def test_validate_config_ca_config_reqs(self, mock_check_perm):
|
|
||||||
ca_config_requirements = ["cert_path", "key_path", "output_path",
|
|
||||||
"signing_hash", "valid_hours"]
|
|
||||||
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
|
|
||||||
# Iterate through the ca_config_requirements, replace each one in turn
|
|
||||||
# with 'missing_req', perform validation. Each should raise in turn
|
|
||||||
for req in ca_config_requirements:
|
|
||||||
jsonloader.conf.load_str_data(config.replace(req, "missing_req"))
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"CA config missing: %s" % req,
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('os.path.isfile')
|
|
||||||
def test_validate_config_no_ca_cert_file(self, isfile):
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
isfile.return_value = False
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"could not read file: tests/CA/root-ca.crt",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
@mock.patch('os.path.isfile')
|
|
||||||
@mock.patch('os.access')
|
|
||||||
@mock.patch('os.stat')
|
|
||||||
def test_validate_config_no_validators(self, stat, access, isfile,
|
|
||||||
mock_check_perm):
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {}
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
isfile.return_value = True
|
|
||||||
access.return_value = True
|
|
||||||
stat.return_value.st_mode = self.expected_key_permissions
|
|
||||||
self.assertRaisesRegexp(errors.ConfigValidationException,
|
|
||||||
"No validators configured",
|
|
||||||
app.validate_config, jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
@mock.patch('os.path.isfile')
|
|
||||||
@mock.patch('os.access')
|
|
||||||
@mock.patch('os.stat')
|
|
||||||
def test_validate_config_unknown_validator(self, stat, access, isfile,
|
|
||||||
mock_check_perm):
|
|
||||||
self.sample_conf_validators['unknown_validator'] = {}
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
isfile.return_value = True
|
|
||||||
access.return_value = True
|
|
||||||
stat.return_value.st_mode = self.expected_key_permissions
|
|
||||||
with self.assertRaises(errors.ConfigValidationException,
|
|
||||||
msg="Unknown validator <unknown_validator> "
|
|
||||||
"found (for registration authority "
|
|
||||||
"default)"):
|
|
||||||
app.validate_config(jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.util.check_file_permissions')
|
|
||||||
@mock.patch('os.path.isfile')
|
|
||||||
@mock.patch('os.access')
|
|
||||||
@mock.patch('os.stat')
|
|
||||||
def test_validate_config_good(self, stat, access, isfile, mock_check_perm):
|
|
||||||
config = json.dumps(self.sample_conf)
|
|
||||||
jsonloader.conf.load_str_data(config)
|
|
||||||
isfile.return_value = True
|
|
||||||
access.return_value = True
|
|
||||||
stat.return_value.st_mode = self.expected_key_permissions
|
|
||||||
app.validate_config(jsonloader.conf)
|
|
||||||
|
|
||||||
@mock.patch('anchor.jsonloader.conf.load_file_data')
|
|
||||||
def test_config_paths_env(self, conf):
|
|
||||||
with mock.patch.dict('os.environ', {'ANCHOR_CONF': '/fake/fake'}):
|
|
||||||
app.load_config()
|
|
||||||
conf.assert_called_with('/fake/fake')
|
|
||||||
|
|
||||||
@mock.patch('anchor.jsonloader.conf.load_file_data')
|
|
||||||
def test_config_paths_local(self, conf):
|
|
||||||
ret = lambda x: True if x == 'config.json' else False
|
|
||||||
with mock.patch("os.path.isfile", ret):
|
|
||||||
app.load_config()
|
|
||||||
conf.assert_called_with('config.json')
|
|
||||||
|
|
||||||
@mock.patch('anchor.jsonloader.conf.load_file_data')
|
|
||||||
def test_config_paths_user(self, conf):
|
|
||||||
ret = (lambda x: True if x == '/fake/.config/anchor/config.json'
|
|
||||||
else False)
|
|
||||||
with mock.patch('os.path.isfile', ret):
|
|
||||||
with mock.patch.dict('os.environ', {'HOME': '/fake'}):
|
|
||||||
app.load_config()
|
|
||||||
conf.assert_called_with('/fake/.config/anchor/config.json')
|
|
||||||
|
|
||||||
@mock.patch('anchor.jsonloader.conf.load_file_data')
|
|
||||||
def test_config_paths_system(self, conf):
|
|
||||||
path = os.path.join(os.environ.get('VIRTUAL_ENV', os.sep),
|
|
||||||
'etc/anchor/config.json')
|
|
||||||
ret = lambda x: x == path
|
|
||||||
with mock.patch('os.path.isfile', ret):
|
|
||||||
app.load_config()
|
|
||||||
conf.assert_called_with(path)
|
|
@ -1,109 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
|
|
||||||
from anchor import fixups
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import name
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
|
|
||||||
|
|
||||||
class TestEnsureAlternativeNamesPresent(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestEnsureAlternativeNamesPresent, self).setUp()
|
|
||||||
|
|
||||||
def _csr_with_cn(self, cn):
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
subject = name.X509Name()
|
|
||||||
subject.add_name_entry(name.OID_commonName, cn)
|
|
||||||
csr.set_subject(subject)
|
|
||||||
return csr
|
|
||||||
|
|
||||||
def test_no_cn(self):
|
|
||||||
csr = signing_request.X509Csr()
|
|
||||||
subject = name.X509Name()
|
|
||||||
subject.add_name_entry(name.OID_localityName, "somewhere")
|
|
||||||
csr.set_subject(subject)
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(0, len(new_csr.get_extensions()))
|
|
||||||
|
|
||||||
def test_cn_only_ip(self):
|
|
||||||
csr = self._csr_with_cn("1.2.3.4")
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
self.assertEqual([netaddr.IPAddress("1.2.3.4")], ext.get_ips())
|
|
||||||
|
|
||||||
def test_cn_only_dns(self):
|
|
||||||
csr = self._csr_with_cn("example.com")
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
self.assertEqual(["example.com"], ext.get_dns_ids())
|
|
||||||
|
|
||||||
def test_cn_existing_ip(self):
|
|
||||||
csr = self._csr_with_cn("1.2.3.4")
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
san.add_ip(netaddr.IPAddress("1.2.3.4"))
|
|
||||||
csr.add_extension(san)
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
self.assertEqual([netaddr.IPAddress("1.2.3.4")], ext.get_ips())
|
|
||||||
|
|
||||||
def test_cn_existing_dns(self):
|
|
||||||
csr = self._csr_with_cn("example.com")
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
san.add_dns_id("example.com")
|
|
||||||
csr.add_extension(san)
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
self.assertEqual(["example.com"], ext.get_dns_ids())
|
|
||||||
|
|
||||||
def test_cn_extra_ip(self):
|
|
||||||
csr = self._csr_with_cn("1.2.3.4")
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
san.add_ip(netaddr.IPAddress("2.3.4.5"))
|
|
||||||
csr.add_extension(san)
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
ips = ext.get_ips()
|
|
||||||
self.assertIn(netaddr.IPAddress("1.2.3.4"), ips)
|
|
||||||
self.assertIn(netaddr.IPAddress("2.3.4.5"), ips)
|
|
||||||
|
|
||||||
def test_cn_extra_dns(self):
|
|
||||||
csr = self._csr_with_cn("example.com")
|
|
||||||
san = extension.X509ExtensionSubjectAltName()
|
|
||||||
san.add_dns_id("other.example.com")
|
|
||||||
csr.add_extension(san)
|
|
||||||
|
|
||||||
new_csr = fixups.enforce_alternative_names_present(csr=csr)
|
|
||||||
self.assertEqual(1, len(new_csr.get_extensions()))
|
|
||||||
ext = new_csr.get_extensions(extension.X509ExtensionSubjectAltName)[0]
|
|
||||||
ids = ext.get_dns_ids()
|
|
||||||
self.assertIn("example.com", ids)
|
|
||||||
self.assertIn("other.example.com", ids)
|
|
@ -1,84 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
import webob
|
|
||||||
|
|
||||||
from anchor import certificate_ops
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestFixupFunctionality(tests.DefaultConfigMixin,
|
|
||||||
tests.DefaultRequestMixin,
|
|
||||||
unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFixupFunctionality, self).setUp()
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
self.csr = signing_request.X509Csr.from_buffer(
|
|
||||||
TestFixupFunctionality.csr_sample_bytes)
|
|
||||||
|
|
||||||
def test_with_noop(self):
|
|
||||||
"""Ensure single fixup is processed."""
|
|
||||||
|
|
||||||
self.sample_conf_ra['default_ra']['fixups'] = {'noop': {}}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
mock_noop = mock.MagicMock()
|
|
||||||
mock_noop.name = "noop"
|
|
||||||
mock_noop.plugin.return_value = self.csr
|
|
||||||
|
|
||||||
jsonloader.conf._fixups = jsonloader.conf._fixups.make_test_instance(
|
|
||||||
[mock_noop], 'anchor.fixups')
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
certificate_ops.fixup_csr('default_ra', self.csr, None)
|
|
||||||
|
|
||||||
mock_noop.plugin.assert_called_with(
|
|
||||||
csr=self.csr, conf=self.sample_conf_ra['default_ra'], request=None)
|
|
||||||
|
|
||||||
def test_with_no_fixups(self):
|
|
||||||
"""Ensure no fixups is ok."""
|
|
||||||
|
|
||||||
self.sample_conf_ra['default_ra']['fixups'] = {}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
res = certificate_ops.fixup_csr('default_ra', self.csr, None)
|
|
||||||
self.assertIs(res, self.csr)
|
|
||||||
|
|
||||||
def test_with_broken_fixup(self):
|
|
||||||
"""Ensure broken fixups stop processing."""
|
|
||||||
|
|
||||||
self.sample_conf_ra['default_ra']['fixups'] = {'broken': {}}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
mock_noop = mock.MagicMock()
|
|
||||||
mock_noop.name = "broken"
|
|
||||||
mock_noop.plugin.side_effects = Exception("BOOM")
|
|
||||||
|
|
||||||
jsonloader.conf._fixups = jsonloader.conf._fixups.make_test_instance(
|
|
||||||
[mock_noop], 'anchor.fixups')
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(webob.exc.WSGIHTTPException):
|
|
||||||
certificate_ops.fixup_csr('default_ra', self.csr, None)
|
|
@ -1,148 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Nebula Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from webob import exc as http_status
|
|
||||||
|
|
||||||
from anchor import certificate_ops
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor.X509 import name as x509_name
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateOpsTests(tests.DefaultConfigMixin, tests.DefaultRequestMixin,
|
|
||||||
unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
super(CertificateOpsTests, self).setUp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_parse_csr_success1(self):
|
|
||||||
"""Test basic success path for parse_csr."""
|
|
||||||
result = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
subject = result.get_subject()
|
|
||||||
actual_cn = subject.get_entries_by_oid(
|
|
||||||
x509_name.OID_commonName)[0].get_value()
|
|
||||||
self.assertEqual(actual_cn, self.csr_sample_cn)
|
|
||||||
|
|
||||||
def test_parse_csr_success2(self):
|
|
||||||
"""Test basic success path for parse_csr."""
|
|
||||||
result = certificate_ops.parse_csr(self.csr_sample, 'PEM')
|
|
||||||
subject = result.get_subject()
|
|
||||||
actual_cn = subject.get_entries_by_oid(
|
|
||||||
x509_name.OID_commonName)[0].get_value()
|
|
||||||
self.assertEqual(actual_cn, self.csr_sample_cn)
|
|
||||||
|
|
||||||
def test_parse_csr_fail1(self):
|
|
||||||
"""Test invalid CSR format (wrong value) for parse_csr."""
|
|
||||||
with self.assertRaises(http_status.HTTPClientError):
|
|
||||||
certificate_ops.parse_csr(self.csr_sample, 'blah')
|
|
||||||
|
|
||||||
def test_parse_csr_fail2(self):
|
|
||||||
"""Test invalid CSR format (wrong type) for parse_csr."""
|
|
||||||
with self.assertRaises(http_status.HTTPClientError):
|
|
||||||
certificate_ops.parse_csr(self.csr_sample, True)
|
|
||||||
|
|
||||||
def test_parse_csr_fail3(self):
|
|
||||||
"""Test invalid CSR (None) format for parse_csr."""
|
|
||||||
with self.assertRaises(http_status.HTTPClientError):
|
|
||||||
certificate_ops.parse_csr(None, 'pem')
|
|
||||||
|
|
||||||
def test_parse_csr_fail4(self):
|
|
||||||
"""Test invalid CSR (wrong value) format for parse_csr."""
|
|
||||||
with self.assertRaises(http_status.HTTPClientError):
|
|
||||||
certificate_ops.parse_csr('invalid csr input', 'pem')
|
|
||||||
|
|
||||||
def test_validate_csr_success(self):
|
|
||||||
"""Test basic success path for validate_csr."""
|
|
||||||
csr_obj = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {'extensions': {
|
|
||||||
'allowed_extensions': ['basicConstraints', 'keyUsage']}}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
certificate_ops.validate_csr('default_ra', None, csr_obj, None)
|
|
||||||
|
|
||||||
def test_validate_csr_bypass(self):
|
|
||||||
"""Test empty validator set for validate_csr."""
|
|
||||||
csr_obj = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
# this should work, it allows people to bypass validation
|
|
||||||
certificate_ops.validate_csr('default_ra', None, csr_obj, None)
|
|
||||||
|
|
||||||
def test_validate_csr_fail(self):
|
|
||||||
"""Test failure path for validate_csr."""
|
|
||||||
csr_obj = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_ra['default_ra']['validators'] = {
|
|
||||||
'common_name': {
|
|
||||||
'allowed_domains': ['.testing.example.com']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(http_status.HTTPException) as cm:
|
|
||||||
certificate_ops.validate_csr('default_ra', None, csr_obj, None)
|
|
||||||
self.assertEqual(cm.exception.code, 400)
|
|
||||||
|
|
||||||
def test_ca_cert_read_failure(self):
|
|
||||||
"""Test CA certificate read failure."""
|
|
||||||
csr_obj = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
ca_conf = self.sample_conf_ca['default_ca']
|
|
||||||
ca_conf['cert_path'] = '/xxx/not/a/valid/path'
|
|
||||||
ca_conf['key_path'] = 'tests/CA/root-ca-unwrapped.key'
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(http_status.HTTPException) as cm:
|
|
||||||
certificate_ops.dispatch_sign('default_ra', csr_obj)
|
|
||||||
self.assertEqual(cm.exception.code, 500)
|
|
||||||
|
|
||||||
def test_ca_key_read_failure(self):
|
|
||||||
"""Test CA key read failure."""
|
|
||||||
csr_obj = certificate_ops.parse_csr(self.csr_sample, 'pem')
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_ca['default_ca']['cert_path'] = 'tests/CA/root-ca.crt'
|
|
||||||
self.sample_conf_ca['default_ca']['key_path'] = '/xxx/not/a/valid/path'
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(http_status.HTTPException) as cm:
|
|
||||||
certificate_ops.dispatch_sign('default_ra', csr_obj)
|
|
||||||
self.assertEqual(cm.exception.code, 500)
|
|
||||||
|
|
||||||
def test_ca_cert_not_configured(self):
|
|
||||||
"""Test CA cert read failure."""
|
|
||||||
config = "anchor.jsonloader.conf._config"
|
|
||||||
self.sample_conf_ca['default_ca']['cert_path'] = None
|
|
||||||
data = self.sample_conf
|
|
||||||
|
|
||||||
with mock.patch.dict(config, data):
|
|
||||||
with self.assertRaises(http_status.HTTPException) as cm:
|
|
||||||
certificate_ops.get_ca('default_ra')
|
|
||||||
self.assertEqual(cm.exception.code, 404)
|
|
@ -1,98 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from anchor import jsonloader
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# find the class representing an open file; it depends on the python version
|
|
||||||
# it's used later for mocking
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
file_class = file # noqa
|
|
||||||
else:
|
|
||||||
import _io
|
|
||||||
file_class = _io.TextIOWrapper
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(tests.DefaultConfigMixin, unittest.TestCase):
|
|
||||||
def test_wrong_key(self):
|
|
||||||
"""Wrong config key should raise the right error."""
|
|
||||||
jsonloader.conf = jsonloader.AnchorConf(logger)
|
|
||||||
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
jsonloader.conf.abcdef
|
|
||||||
|
|
||||||
def test_load_file(self):
|
|
||||||
"""Test loading of a correct configuration."""
|
|
||||||
jsonloader.conf = jsonloader.AnchorConf(logger)
|
|
||||||
|
|
||||||
open_name = 'anchor.jsonloader.open'
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.return_value = mock.MagicMock(spec=file_class)
|
|
||||||
m_file = mock_open.return_value.__enter__.return_value
|
|
||||||
m_file.read.return_value = json.dumps(self.sample_conf)
|
|
||||||
|
|
||||||
jsonloader.conf.load_file_data('/tmp/impossible_path')
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
(jsonloader.conf.registration_authority['default_ra']
|
|
||||||
['authentication']),
|
|
||||||
'default_auth')
|
|
||||||
self.assertEqual(
|
|
||||||
jsonloader.conf.signing_ca['default_ca']['valid_hours'],
|
|
||||||
24)
|
|
||||||
|
|
||||||
def test_load_file_cant_open(self):
|
|
||||||
"""Test failures when opening files."""
|
|
||||||
jsonloader.conf = jsonloader.AnchorConf(logger)
|
|
||||||
|
|
||||||
open_name = 'anchor.jsonloader.open'
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.return_value = mock.MagicMock(spec=file_class)
|
|
||||||
mock_open.side_effect = IOError("can't open file")
|
|
||||||
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jsonloader.conf.load_file_data('/tmp/impossible_path')
|
|
||||||
|
|
||||||
def test_load_file_cant_parse(self):
|
|
||||||
"""Test failues when parsing json format."""
|
|
||||||
jsonloader.conf = jsonloader.AnchorConf(logger)
|
|
||||||
|
|
||||||
open_name = 'anchor.jsonloader.open'
|
|
||||||
with mock.patch(open_name, create=True) as mock_open:
|
|
||||||
mock_open.return_value = mock.MagicMock(spec=file_class)
|
|
||||||
m_file = mock_open.return_value.__enter__.return_value
|
|
||||||
m_file.read.return_value = "{{{{ bad json"
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
jsonloader.conf.load_file_data('/tmp/impossible_path')
|
|
||||||
|
|
||||||
def test_registration_authority_names(self):
|
|
||||||
"""Instances should be listed once config is loaded."""
|
|
||||||
jsonloader.conf = jsonloader.AnchorConf(logger)
|
|
||||||
jsonloader.conf.load_str_data(json.dumps(self.sample_conf))
|
|
||||||
self.assertEqual(list(jsonloader.registration_authority_names()),
|
|
||||||
['default_ra'])
|
|
@ -1,149 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
import pecan
|
|
||||||
from pecan import testing as pecan_testing
|
|
||||||
import stevedore
|
|
||||||
|
|
||||||
from anchor import config
|
|
||||||
from anchor import jsonloader
|
|
||||||
from anchor.X509 import certificate as X509_cert
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctional(tests.DefaultConfigMixin, tests.DefaultRequestMixin,
|
|
||||||
unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFunctional, self).setUp()
|
|
||||||
|
|
||||||
# Load config from json test config
|
|
||||||
jsonloader.conf.load_str_data(json.dumps(self.sample_conf))
|
|
||||||
jsonloader.conf.load_extensions()
|
|
||||||
self.conf = jsonloader.conf._config
|
|
||||||
ca_conf = self.conf["signing_ca"]["default_ca"]
|
|
||||||
ca_conf["output_path"] = tempfile.mkdtemp()
|
|
||||||
|
|
||||||
# Set CA file permissions
|
|
||||||
os.chmod(ca_conf["cert_path"], stat.S_IRUSR | stat.S_IFREG)
|
|
||||||
os.chmod(ca_conf["key_path"], stat.S_IRUSR | stat.S_IFREG)
|
|
||||||
|
|
||||||
app_conf = {"app": copy.deepcopy(config.app),
|
|
||||||
"logging": copy.deepcopy(config.logging)}
|
|
||||||
self.app = pecan_testing.load_test_app(app_conf)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pecan.set_config({}, overwrite=True)
|
|
||||||
self.app.reset()
|
|
||||||
|
|
||||||
def test_check_unauthorised(self):
|
|
||||||
resp = self.app.post('/v1/sign/default_ra', expect_errors=True)
|
|
||||||
self.assertEqual(401, resp.status_int)
|
|
||||||
|
|
||||||
def test_robots(self):
|
|
||||||
resp = self.app.get('/robots.txt')
|
|
||||||
self.assertEqual(200, resp.status_int)
|
|
||||||
self.assertEqual("User-agent: *\nDisallow: /\n", resp.text)
|
|
||||||
|
|
||||||
def test_check_missing_csr(self):
|
|
||||||
data = {'user': 'myusername',
|
|
||||||
'secret': 'simplepassword',
|
|
||||||
'encoding': 'pem'}
|
|
||||||
|
|
||||||
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_int)
|
|
||||||
|
|
||||||
def test_check_unknown_instance(self):
|
|
||||||
data = {'user': 'myusername',
|
|
||||||
'secret': 'simplepassword',
|
|
||||||
'encoding': 'pem',
|
|
||||||
'csr': self.csr_sample}
|
|
||||||
|
|
||||||
resp = self.app.post('/v1/sign/unknown', data, expect_errors=True)
|
|
||||||
self.assertEqual(404, resp.status_int)
|
|
||||||
|
|
||||||
def test_check_bad_csr(self):
|
|
||||||
data = {'user': 'myusername',
|
|
||||||
'secret': 'simplepassword',
|
|
||||||
'encoding': 'unknown',
|
|
||||||
'csr': self.csr_sample}
|
|
||||||
|
|
||||||
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_int)
|
|
||||||
|
|
||||||
def test_check_good_csr(self):
|
|
||||||
data = {'user': 'myusername',
|
|
||||||
'secret': 'simplepassword',
|
|
||||||
'encoding': 'pem',
|
|
||||||
'csr': self.csr_sample}
|
|
||||||
|
|
||||||
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=False)
|
|
||||||
self.assertEqual(200, resp.status_int)
|
|
||||||
|
|
||||||
cert = X509_cert.X509Certificate.from_buffer(resp.text)
|
|
||||||
|
|
||||||
# make sure the cert is what we asked for
|
|
||||||
self.assertEqual(("/C=UK/ST=Narnia/L=Funkytown/O=Anchor Testing"
|
|
||||||
"/OU=testing/CN=server1.example.com"
|
|
||||||
"/emailAddress=test@example.com"),
|
|
||||||
str(cert.get_subject()))
|
|
||||||
|
|
||||||
# make sure the cert was issued by anchor
|
|
||||||
self.assertEqual("/C=AU/ST=Some-State/O=Herp Derp plc/OU"
|
|
||||||
"=herp.derp.plc/CN=herp.derp.plc",
|
|
||||||
str(cert.get_issuer()))
|
|
||||||
|
|
||||||
def test_check_broken_validator(self):
|
|
||||||
data = {'user': 'myusername',
|
|
||||||
'secret': 'simplepassword',
|
|
||||||
'encoding': 'pem',
|
|
||||||
'csr': self.csr_sample}
|
|
||||||
|
|
||||||
derp = mock.MagicMock()
|
|
||||||
derp.side_effect = Exception("BOOM")
|
|
||||||
|
|
||||||
derp_ext = stevedore.extension.Extension("broken_validator", None,
|
|
||||||
derp, None)
|
|
||||||
manager = jsonloader.conf._validators.make_test_instance([derp_ext])
|
|
||||||
jsonloader.conf._validators = manager
|
|
||||||
|
|
||||||
ra = jsonloader.conf.registration_authority['default_ra']
|
|
||||||
ra['validators'] = {"broken_validator": {}}
|
|
||||||
|
|
||||||
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
|
|
||||||
self.assertEqual(500, resp.status_int)
|
|
||||||
self.assertTrue(("Internal Validation Error") in str(resp))
|
|
||||||
self.assertTrue(derp.called)
|
|
||||||
|
|
||||||
def test_get_ca(self):
|
|
||||||
data = {'encoding': 'pem'}
|
|
||||||
|
|
||||||
resp = self.app.get('/v1/ca/default_ra', data, expect_errors=False)
|
|
||||||
self.assertEqual(200, resp.status_int)
|
|
||||||
|
|
||||||
cert = X509_cert.X509Certificate.from_buffer(resp.text)
|
|
||||||
|
|
||||||
# make sure the cert is what we asked for
|
|
||||||
self.assertEqual("/C=AU/ST=Some-State/O=Herp Derp plc/OU"
|
|
||||||
"=herp.derp.plc/CN=herp.derp.plc",
|
|
||||||
str(cert.get_subject()))
|
|
@ -1,265 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import textwrap
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from pyasn1.type import univ as asn1_univ
|
|
||||||
|
|
||||||
from anchor import errors
|
|
||||||
from anchor import signers
|
|
||||||
from anchor.signers import cryptography_io
|
|
||||||
from anchor.signers import pkcs11
|
|
||||||
from anchor import util
|
|
||||||
from anchor.X509 import certificate
|
|
||||||
from anchor.X509 import extension
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
from anchor.X509 import utils
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownExtension(extension.X509Extension):
|
|
||||||
_oid = asn1_univ.ObjectIdentifier("1.2.3.4")
|
|
||||||
spec = asn1_univ.Null
|
|
||||||
|
|
||||||
|
|
||||||
class SigningBackendExtensions(tests.DefaultConfigMixin,
|
|
||||||
tests.DefaultRequestMixin, unittest.TestCase):
|
|
||||||
def test_copy_good_extensions(self):
|
|
||||||
csr = signing_request.X509Csr.from_buffer(self.csr_sample_bytes)
|
|
||||||
ext = extension.X509ExtensionSubjectAltName()
|
|
||||||
ext.add_dns_id("example.com")
|
|
||||||
csr.add_extension(ext)
|
|
||||||
|
|
||||||
pem = signers.sign_generic(csr, self.sample_conf_ca['default_ca'],
|
|
||||||
'RSA', lambda x: b"")
|
|
||||||
cert = certificate.X509Certificate.from_buffer(pem)
|
|
||||||
self.assertEqual(1, len(cert.get_extensions(
|
|
||||||
extension.X509ExtensionSubjectAltName)))
|
|
||||||
|
|
||||||
def test_ignore_unknown_extensions(self):
|
|
||||||
csr = signing_request.X509Csr.from_buffer(self.csr_sample_bytes)
|
|
||||||
ext = UnknownExtension()
|
|
||||||
csr.add_extension(ext)
|
|
||||||
|
|
||||||
pem = signers.sign_generic(csr, self.sample_conf_ca['default_ca'],
|
|
||||||
'RSA', lambda x: b"")
|
|
||||||
cert = certificate.X509Certificate.from_buffer(pem)
|
|
||||||
self.assertEqual(0, len(cert.get_extensions(UnknownExtension)))
|
|
||||||
|
|
||||||
def test_fail_critical_unknown_extensions(self):
|
|
||||||
csr = signing_request.X509Csr.from_buffer(self.csr_sample_bytes)
|
|
||||||
ext = UnknownExtension()
|
|
||||||
ext.set_critical(True)
|
|
||||||
csr.add_extension(ext)
|
|
||||||
|
|
||||||
with self.assertRaises(signers.SigningError):
|
|
||||||
signers.sign_generic(csr, self.sample_conf_ca['default_ca'],
|
|
||||||
'RSA', lambda x: b"")
|
|
||||||
|
|
||||||
|
|
||||||
class TestCryptographyBackend(tests.DefaultConfigMixin,
|
|
||||||
tests.DefaultRequestMixin, unittest.TestCase):
|
|
||||||
key_rsa_data = textwrap.dedent("""
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXAIBAAKBgQCeeqg1Qeccv8hqj1BP9KEJX5QsFCxR62M8plPb5t4sLo8UYfZd
|
|
||||||
6kFLcOP8xzwwvx/eFY6Sux52enQ197o8aMwyP77hMhZqtd8NCgLJMVlUbRhwLti0
|
|
||||||
SkHFPic0wAg+esfXa6yhd5TxC+bti7MgV/ljA80XQxHH8xOjdOoGN0DHfQIDAQAB
|
|
||||||
AoGBAJ2ozJpe+7qgGJPaCz3f0izvBwtq7kR49fqqRZbo8HHnx7OxWVVI7LhOkKEy
|
|
||||||
2/Bq0xsvOu1CdiXL4LynvIDIiQqLaeINzG48Rbk+0HadbXblt3nDkIWdYII6zHKI
|
|
||||||
W9ewX4KpHEPbrlEO9BjAlAcYsDIvFIMYpQhtQ+0R/gmZ99WJAkEAz5C2a6FIcMbE
|
|
||||||
o3aTc9ECq99zY7lxh+6aLpUdIeeHyb/QzfGDBdlbpBAkA6EcxSqp0aqH4xIQnYHa
|
|
||||||
3P5ZCShqSwJBAMN1sb76xq94xkg2cxShPFPAE6xKRFyKqLgsBYVtulOdfOtOnjh9
|
|
||||||
1SK2XQQfBRIRdG4Q/gDoCP8XQHpJcWMk+FcCQDnuJqulaOVo5GrG5mJ1nCxCAh98
|
|
||||||
G06X7lo/7dCPoRtSuMExvaK9RlFk29hTeAcjYCAPWzupyA9dtarmJg1jRT8CQCKf
|
|
||||||
gYnb8D/6+9yk0IPR/9ayCooVacCeyz48hgnZowzWs98WwQ4utAd/GED3obVOpDov
|
|
||||||
Bl9wus889i3zPoOac+cCQCZHredQcJGd4dlthbVtP2NhuPXz33JuETGR9pXtsDUZ
|
|
||||||
uX/nSq1oo9kUh/dPOz6aP5Ues1YVe3LExmExPBQfwIE=
|
|
||||||
-----END RSA PRIVATE KEY-----""").encode('ascii')
|
|
||||||
|
|
||||||
def test_sign_bad_md(self):
|
|
||||||
key = utils.get_private_key_from_pem(self.key_rsa_data)
|
|
||||||
with self.assertRaises(signers.SigningError):
|
|
||||||
cryptography_io.make_signer(key, "BAD", "RSA")
|
|
||||||
|
|
||||||
def test_sign_bad_key(self):
|
|
||||||
with self.assertRaises(signers.SigningError):
|
|
||||||
cryptography_io.make_signer("BAD", "sha256", "RSA")
|
|
||||||
|
|
||||||
|
|
||||||
class TestPKCSBackend(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.good_conf = {
|
|
||||||
"cert_path": "tests/CA/root-ca.crt",
|
|
||||||
"output_path": "/somepath",
|
|
||||||
"signing_hash": "sha256",
|
|
||||||
"valid_hours": 24,
|
|
||||||
"slot": 5,
|
|
||||||
"pin": "somepin",
|
|
||||||
"key_id": "aabbccddeeff",
|
|
||||||
"pkcs11_path": "/somepath/library.so",
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_conf_checks_package(self):
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=True):
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs',
|
|
||||||
side_effect=ImportError()):
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_conf_checks_fields(self):
|
|
||||||
for key in self.good_conf:
|
|
||||||
conf = self.good_conf.copy()
|
|
||||||
del conf[key]
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", conf)
|
|
||||||
|
|
||||||
def test_conf_checks_file_permissions(self):
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=False):
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_conf_checks_library_loading(self):
|
|
||||||
class MockExc(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.load.side_effect = MockExc()
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Error = MockExc
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=True):
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_conf_checks_valid_slot(self):
|
|
||||||
class MockExc(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.getSlotList.return_value = [4, 6]
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Error = MockExc
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=True):
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_conf_checks_valid_pin(self):
|
|
||||||
class MockExc(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
session = mock.Mock()
|
|
||||||
session.login.side_effect = MockExc()
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.getSlotList.return_value = [self.good_conf['slot']]
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Error = MockExc
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=True):
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaises(errors.ConfigValidationException):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_conf_allows_valid(self):
|
|
||||||
session = mock.Mock()
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.getSlotList.return_value = [self.good_conf['slot']]
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(util, 'check_file_exists', return_value=True):
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
pkcs11.conf_validator("name", self.good_conf)
|
|
||||||
|
|
||||||
def test_make_signer_fails(self):
|
|
||||||
with mock.patch.object(pkcs11, 'make_signer',
|
|
||||||
side_effect=signers.SigningError):
|
|
||||||
with self.assertRaises(signers.SigningError):
|
|
||||||
pkcs11.sign(mock.Mock(), self.good_conf)
|
|
||||||
|
|
||||||
def test_sign_login_fails(self):
|
|
||||||
class MockExc(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
session = mock.Mock()
|
|
||||||
session.login.side_effect = MockExc()
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Error = MockExc
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaisesRegexp(signers.SigningError,
|
|
||||||
"pkcs11 session"):
|
|
||||||
pkcs11.sign(mock.Mock(), self.good_conf)
|
|
||||||
|
|
||||||
def test_sign_key_missing(self):
|
|
||||||
class MockExc(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
session = mock.Mock()
|
|
||||||
session.findObjects.return_value = []
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaisesRegexp(signers.SigningError,
|
|
||||||
"requested key"):
|
|
||||||
pkcs11.sign(mock.Mock(), self.good_conf)
|
|
||||||
|
|
||||||
def test_sign_bad_hash(self):
|
|
||||||
session = mock.Mock()
|
|
||||||
session.findObjects.return_value = [object()]
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
self.good_conf['signing_hash'] = 'unknown'
|
|
||||||
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
with self.assertRaisesRegexp(signers.SigningError,
|
|
||||||
"hash is not supported"):
|
|
||||||
pkcs11.sign(mock.Mock(), self.good_conf)
|
|
||||||
|
|
||||||
def test_working_signer(self):
|
|
||||||
res = b"123"
|
|
||||||
|
|
||||||
session = mock.Mock()
|
|
||||||
session.findObjects.return_value = [object()]
|
|
||||||
session.sign.return_value = res
|
|
||||||
lib = mock.Mock()
|
|
||||||
lib.openSession.return_value = session
|
|
||||||
mod = mock.Mock()
|
|
||||||
mod.PyKCS11Lib.return_value = lib
|
|
||||||
|
|
||||||
with mock.patch.object(pkcs11, 'import_pkcs', return_value=mod):
|
|
||||||
signer = pkcs11.make_signer((1, 2, 3), self.good_conf['slot'],
|
|
||||||
self.good_conf['pin'],
|
|
||||||
self.good_conf['pkcs11_path'],
|
|
||||||
self.good_conf['signing_hash'].upper())
|
|
||||||
self.assertEqual(res, signer(b"data"))
|
|
@ -1,82 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
|
|
||||||
from anchor.validators import errors
|
|
||||||
from anchor.validators import utils
|
|
||||||
from anchor.X509 import name
|
|
||||||
from anchor.X509 import signing_request
|
|
||||||
import tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestBaseValidators(tests.DefaultRequestMixin, unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBaseValidators, self).setUp()
|
|
||||||
self.csr = signing_request.X509Csr.from_buffer(
|
|
||||||
self.csr_sample_bytes)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super(TestBaseValidators, self).tearDown()
|
|
||||||
|
|
||||||
def test_csr_require_cn(self):
|
|
||||||
common_name = utils.csr_require_cn(self.csr)
|
|
||||||
self.assertEqual(common_name, self.csr_sample_cn)
|
|
||||||
|
|
||||||
self.csr.set_subject(name.X509Name())
|
|
||||||
with self.assertRaises(errors.ValidationError):
|
|
||||||
utils.csr_require_cn(self.csr)
|
|
||||||
|
|
||||||
def test_check_domains(self):
|
|
||||||
test_domain = 'good.example.com'
|
|
||||||
test_allowed = ['.example.com', '.example.net']
|
|
||||||
self.assertTrue(utils.check_domains(test_domain, test_allowed))
|
|
||||||
self.assertFalse(utils.check_domains('bad.example.org',
|
|
||||||
test_allowed))
|
|
||||||
|
|
||||||
def test_check_networks(self):
|
|
||||||
good_ip = netaddr.IPAddress('10.2.3.4')
|
|
||||||
bad_ip = netaddr.IPAddress('88.2.3.4')
|
|
||||||
test_allowed = ['10/8']
|
|
||||||
self.assertTrue(utils.check_networks(good_ip, test_allowed))
|
|
||||||
self.assertFalse(utils.check_networks(bad_ip, test_allowed))
|
|
||||||
|
|
||||||
def test_check_networks_invalid(self):
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
utils.check_networks('1.2.3.4', ['10/8'])
|
|
||||||
|
|
||||||
def test_check_networks_passthrough(self):
|
|
||||||
good_ip = netaddr.IPAddress('10.2.3.4')
|
|
||||||
self.assertTrue(utils.check_networks(good_ip, []))
|
|
||||||
|
|
||||||
def test_check_compare_name_pattern(self):
|
|
||||||
cases = [
|
|
||||||
("example.com", "example.com", False, True),
|
|
||||||
("*.example.com", "*.example.com", False, True),
|
|
||||||
("*.example.com", "%.example.com", True, True),
|
|
||||||
("*.example.com", "%.example.com", False, False),
|
|
||||||
("abc.example.com", "%.example.com", False, True),
|
|
||||||
("abc.def.example.com", "%.example.com", False, False),
|
|
||||||
("abc.def.example.com", "%.%.example.com", False, True),
|
|
||||||
("host-123.example.com", "host-%.example.com", False, True),
|
|
||||||
]
|
|
||||||
for value, pattern, wildcard, result in cases:
|
|
||||||
self.assertEqual(
|
|
||||||
result,
|
|
||||||
utils.compare_name_pattern(value, pattern, wildcard),
|
|
||||||
"checking %s against %s failed" % (value, pattern))
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user