
Instead of relying on openssl code for certificate parsing, use the ASN.1 representation directly. All previous features are supported. Not all the extensions are full parsed yet, but the code doesn't require them for now. The code makes accessing and modifying the certificate structure simpler and requires less error checking than the original version. The code leaves few TODOs, but nothing that destroys previous behaviour. It still uses the cryptography.io backend for loading keys and producing signatures for the certificates. Implements: blueprint direct-asn1 Change-Id: Ic555d3d056ca8da7016e2d8b434506cf214d06a1
180 lines
6.8 KiB
Python
180 lines
6.8 KiB
Python
# -*- 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 os
|
|
import stat
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
|
|
import pecan
|
|
from pecan import testing as pecan_testing
|
|
|
|
from anchor import jsonloader
|
|
from anchor import validators
|
|
from anchor.X509 import certificate as X509_cert
|
|
import config
|
|
|
|
|
|
class TestFunctional(unittest.TestCase):
|
|
config = """
|
|
{
|
|
"auth": {
|
|
"static": {
|
|
"secret": "simplepassword",
|
|
"user": "myusername"
|
|
}
|
|
},
|
|
"ca": {
|
|
"cert_path": "tests/CA/root-ca.crt",
|
|
"key_path": "tests/CA/root-ca-unwrapped.key",
|
|
"output_path": "certs",
|
|
"signing_hash": "sha1",
|
|
"valid_hours": 24
|
|
},
|
|
"validators": {
|
|
"default": {
|
|
"common_name": {
|
|
"allowed_domains": [
|
|
".test.com"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
csr_good = textwrap.dedent(u"""
|
|
-----BEGIN CERTIFICATE REQUEST-----
|
|
MIIEDzCCAncCAQAwcjELMAkGA1UEBhMCR0IxEzARBgNVBAgTCkNhbGlmb3JuaWEx
|
|
FjAUBgNVBAcTDVNhbiBGcmFuY3NpY28xDTALBgNVBAoTBE9TU0cxDTALBgNVBAsT
|
|
BE9TU0cxGDAWBgNVBAMTD21hc3Rlci50ZXN0LmNvbTCCAaIwDQYJKoZIhvcNAQEB
|
|
BQADggGPADCCAYoCggGBALnhCRvwMoaZa4car663lwcwn86PO3BS90X8b2wIZjkf
|
|
rq/eePz2J3Ox8/BbsYiYICHn8oSd/VVXUnqHMFU9xTeJwsDLbyc+0P4S9Fj+RkbM
|
|
W+YQZsG8Wy9M8aKi9hNtIGiqknyzcOfCQcGPpcKqXRXAW1afqLmifBcFqN1qcpT8
|
|
OooGNtgo4Ix/fA7omZaKkIXSi5FovC8mFPUm2VqDyvctxBGq0EngIOB9rczloun0
|
|
nO8PpWBsX2rg3uIs6GIejVrx1ZkcHxJbrze/Nt9vt4C11hJAiAUlHDl0cf50/Pck
|
|
g0T3ehEqr0zdzCx+wXr3AzStcoOow+REb8CbTt2QaUbZ5izrZFX0JC73mRtqDhuc
|
|
UxUaguLK9ufhUfA0I1j++w/pQkBEu5PGNX7YpRLImEp636lD8RJ9Ced7oii+gjY0
|
|
OXlVPRv9MMPvkCWnjNjLapz8kzypJr94BQz1AffHxVfmGGQh60vq4KINm+etuI0Q
|
|
kfI9NRa/ficRhsuh7yxQRwIDAQABoFgwVgYJKoZIhvcNAQkOMUkwRzAJBgNVHRME
|
|
AjAAMAsGA1UdDwQEAwIF4DAtBgNVHREEJjAkghBzZXJ2ZXIxLnRlc3QuY29tghBz
|
|
ZXJ2ZXIyLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBdyATuNnfVIeQL2odc
|
|
zV7f9c/tvN5/Mn4AmGt5S457FGO/s3J7hWX9L02VYPWwORbtkBvZZKtQWLjHbMzU
|
|
oGsfxeo6vUv+dSP6bjqKibFyMArdaRIobFMvM/5N6g9zcP4sQEnpUyIeV2g6b0Os
|
|
FoKGsLPIMiS69mAVdfKrgXnmXApXu5zjAoPnSzcc+wKTCbzVIRLZIopEtet84atN
|
|
7Tf9xokgrDZppJE76w3zXYWPkUDbVuWTuO4afQxujHbJYiZblxJz/gRbMgugAt4V
|
|
ftlI3EGnGaBQHcZfmyZz1F8ti1jteWMMQZHtWr32cF9Lw/jd2adYFYVTez3BXtQW
|
|
pULCxdq8G2CFdrV/atIL8Vadf2dOzn2tZIFFihzuilWbcmTP7+8UI8MOKkrqfWN+
|
|
Q6yV3I896rSprU7WAmWSq+jXkOOwNGDEbmaWsxu4AjvfGty5v2lZqdYJRkbjerXD
|
|
tR7XqQGqJKca/vRTfJ+zIAxMEeH1N9Lx7YBO6VdVja+yG1E=
|
|
-----END CERTIFICATE REQUEST-----""")
|
|
|
|
csr_bad = textwrap.dedent(u"""
|
|
-----BEGIN CERTIFICATE REQUEST-----
|
|
MIIBWTCCARMCAQAwgZQxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIEwZOYXJuaWExEjAQ
|
|
BgNVBAcTCUZ1bmt5dG93bjEXMBUGA1UEChMOQW5jaG9yIFRlc3RpbmcxEDAOBgNV
|
|
BAsTB3Rlc3RpbmcxFDASBgNVBAMTC2FuY2hvci50ZXN0MR8wHQYJKoZIhvcNAQkB
|
|
FhB0ZXN0QGFuY2hvci50ZXN0MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxAOpvxkCx
|
|
NNTc86GVnP4rWvaniOnHaemXbhBOoFxhMwaghiq7u5V9ZKkUZfbu+L+ZSQIDAQAB
|
|
oCkwJwYJKoZIhvcNAQkOMRowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DANBgkq
|
|
hkiG9w0BAQUFAAMxALaK8/HR73ZSvHiWo7Mduin0S519aJBm+gO8d9iliUkK00gQ
|
|
VMs9DuTAxljX7t7Eug==
|
|
-----END CERTIFICATE REQUEST-----""")
|
|
|
|
def setUp(self):
|
|
# Load config from json test config
|
|
jsonloader.conf.load_str_data(TestFunctional.config)
|
|
self.conf = getattr(jsonloader.conf, "_config")
|
|
self.conf["ca"]["output_path"] = tempfile.mkdtemp()
|
|
|
|
# Set CA file permissions
|
|
os.chmod(self.conf["ca"]["cert_path"], stat.S_IRUSR | stat.S_IFREG)
|
|
os.chmod(self.conf["ca"]["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('/sign', 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('/sign', data, expect_errors=True)
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
def test_check_bad_csr(self):
|
|
data = {'user': 'myusername',
|
|
'secret': 'simplepassword',
|
|
'encoding': 'pem',
|
|
'csr': TestFunctional.csr_bad}
|
|
|
|
resp = self.app.post('/sign', data, expect_errors=True)
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
def test_check_good_csr(self):
|
|
data = {'user': 'myusername',
|
|
'secret': 'simplepassword',
|
|
'encoding': 'pem',
|
|
'csr': TestFunctional.csr_good}
|
|
|
|
resp = self.app.post('/sign', 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=GB/ST=California/L=San Francsico/O=OSSG"
|
|
"/OU=OSSG/CN=master.test.com"),
|
|
str(cert.get_subject()))
|
|
|
|
# make sure the cert was issued by anchor
|
|
self.assertEqual("/C=UK/ST=Some-State/O=OSSG/CN=anchor.example.com",
|
|
str(cert.get_issuer()))
|
|
|
|
def test_check_broken_validator(self):
|
|
data = {'user': 'myusername',
|
|
'secret': 'simplepassword',
|
|
'encoding': 'pem',
|
|
'csr': TestFunctional.csr_good}
|
|
|
|
def derp(**kwdargs):
|
|
raise Exception("BOOM")
|
|
|
|
validators.broken_validator = derp
|
|
jsonloader.conf.validators["default"]["broken_validator"] = {}
|
|
|
|
resp = self.app.post('/sign', data, expect_errors=True)
|
|
self.assertEqual(500, resp.status_int)
|
|
self.assertTrue(("Internal Validation Error running "
|
|
"validator 'broken_validator' "
|
|
"in set 'default'") in str(resp))
|