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
|
||||
:target: https://pypi.python.org/pypi/anchor/
|
||||
:alt: Latest Version
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
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
|
||||
:target: https://pypi.python.org/pypi/anchor/
|
||||
:alt: Python Versions
|
||||
|
||||
.. 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.
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
@ -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