diff --git a/anchor/jsonloader.py b/anchor/jsonloader.py index a101efb..c4b770b 100644 --- a/anchor/jsonloader.py +++ b/anchor/jsonloader.py @@ -29,7 +29,7 @@ class AnchorConf(): 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 yaml config file + :param config_file: The Anchor JSON config file :return: - ''' @@ -53,6 +53,9 @@ class AnchorConf(): ''' return self._config + def load_str_data(self, data): + self._config = json.loads(data) + def __getattr__(self, name): return self._config[name] diff --git a/tests/CA/root-ca-unwrapped.key b/tests/CA/root-ca-unwrapped.key new file mode 100644 index 0000000..db7b4e6 --- /dev/null +++ b/tests/CA/root-ca-unwrapped.key @@ -0,0 +1,15 @@ +-----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----- diff --git a/tests/CA/root-ca.crt b/tests/CA/root-ca.crt new file mode 100644 index 0000000..988797c --- /dev/null +++ b/tests/CA/root-ca.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + a9:d8:fe:87:d0:95:01:12 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=UK, ST=Some-State, O=OSSG, CN=anchor.example.com + Validity + Not Before: Mar 6 11:44:40 2015 GMT + Not After : Mar 5 11:44:40 2018 GMT + Subject: C=UK, ST=Some-State, O=OSSG, CN=anchor.example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d7:4c:80:83:75:7b:60:c8:ca:a6:7c:5a:2b:8f: + 8f:67:af:89:0e:05:cb:3d:01:d1:bc:e6:22:06:08: + 4a:d1:60:2d:6d:0b:a4:b3:bf:51:3e:95:b9:4e:7d: + a7:44:c9:fd:27:ca:4a:32:a6:d2:b7:68:f8:17:6b: + 94:be:18:6b:b6:cd:54:90:a1:79:a9:8b:16:dd:02: + bd:8c:22:e0:23:72:71:de:a2:62:b3:12:3c:a3:35: + c6:f0:6b:96:04:96:14:88:df:2a:62:5f:6f:19:08: + 59:dc:6d:52:14:37:c6:94:76:97:e3:64:29:c9:28: + 13:e9:52:04:fe:18:6c:4e:17 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 56:35:71:FD:CB:C7:5B:2F:C0:02:C2:2E:3B:9D:7B:FD:6F:CB:BB:9C + X509v3 Authority Key Identifier: + keyid:56:35:71:FD:CB:C7:5B:2F:C0:02:C2:2E:3B:9D:7B:FD:6F:CB:BB:9C + DirName:/C=UK/ST=Some-State/O=OSSG/CN=anchor.example.com + serial:A9:D8:FE:87:D0:95:01:12 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 02:2e:25:2c:7b:ab:d5:cf:98:a7:ee:40:c6:d3:f2:45:4b:1f: + 40:a9:f5:1f:17:2e:1c:96:f8:fa:34:2b:05:e4:e7:f3:94:31: + a6:d9:cc:d4:fa:0c:71:f0:23:7e:d4:c2:84:f0:d6:25:14:41: + 24:aa:52:98:36:a8:37:fa:9f:12:3f:2f:17:22:db:35:1a:01: + 2e:ff:02:de:f5:12:3b:40:7d:7e:c2:80:c6:9a:66:4d:ba:c5: + 43:a8:0f:ec:d3:9c:7c:ec:23:a6:40:6e:a2:c3:5d:e5:1f:78: + cf:da:44:ab:26:b8:91:a5:ef:0f:2e:ce:b9:eb:2a:06:21:88: + e5:2a +-----BEGIN CERTIFICATE----- +MIICyzCCAjSgAwIBAgIJAKnY/ofQlQESMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNV +BAYTAlVLMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0wCwYDVQQKEwRPU1NHMRswGQYD +VQQDExJhbmNob3IuZXhhbXBsZS5jb20wHhcNMTUwMzA2MTE0NDQwWhcNMTgwMzA1 +MTE0NDQwWjBOMQswCQYDVQQGEwJVSzETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsG +A1UEChMET1NTRzEbMBkGA1UEAxMSYW5jaG9yLmV4YW1wbGUuY29tMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQDXTICDdXtgyMqmfForj49nr4kOBcs9AdG85iIG +CErRYC1tC6Szv1E+lblOfadEyf0nykoyptK3aPgXa5S+GGu2zVSQoXmpixbdAr2M +IuAjcnHeomKzEjyjNcbwa5YElhSI3ypiX28ZCFncbVIUN8aUdpfjZCnJKBPpUgT+ +GGxOFwIDAQABo4GwMIGtMB0GA1UdDgQWBBRWNXH9y8dbL8ACwi47nXv9b8u7nDB+ +BgNVHSMEdzB1gBRWNXH9y8dbL8ACwi47nXv9b8u7nKFSpFAwTjELMAkGA1UEBhMC +VUsxEzARBgNVBAgTClNvbWUtU3RhdGUxDTALBgNVBAoTBE9TU0cxGzAZBgNVBAMT +EmFuY2hvci5leGFtcGxlLmNvbYIJAKnY/ofQlQESMAwGA1UdEwQFMAMBAf8wDQYJ +KoZIhvcNAQEFBQADgYEAAi4lLHur1c+Yp+5AxtPyRUsfQKn1HxcuHJb4+jQrBeTn +85QxptnM1PoMcfAjftTChPDWJRRBJKpSmDaoN/qfEj8vFyLbNRoBLv8C3vUSO0B9 +fsKAxppmTbrFQ6gP7NOcfOwjpkBuosNd5R94z9pEqya4kaXvDy7OuesqBiGI5So= +-----END CERTIFICATE----- diff --git a/tests/test_functional.py b/tests/test_functional.py new file mode 100644 index 0000000..8671fcb --- /dev/null +++ b/tests/test_functional.py @@ -0,0 +1,155 @@ +# -*- 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 tempfile +import textwrap +import unittest + +import pecan +from pecan import testing as pecan_testing + +from anchor import jsonloader +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": [ + { + "name": "default", + "steps": [ + [ + "common_name", + { + "allowed_domains": [ + ".test.com" + ] + } + ] + ] + } + ] + } + """ + + csr_good = textwrap.dedent(""" + -----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(""" + -----BEGIN CERTIFICATE REQUEST----- + MIIBWTCCARMCAQAwgZQxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIEwZOYXJuaWExEjAQ + BgNVBAcTCUZ1bmt5dG93bjEXMBUGA1UEChMOQW5jaG9yIFRlc3RpbmcxEDAOBgNV + BAsTB3Rlc3RpbmcxFDASBgNVBAMTC2FuY2hvci50ZXN0MR8wHQYJKoZIhvcNAQkB + FhB0ZXN0QGFuY2hvci50ZXN0MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxAOpvxkCx + NNTc86GVnP4rWvaniOnHaemXbhBOoFxhMwaghiq7u5V9ZKkUZfbu+L+ZSQIDAQAB + oCkwJwYJKoZIhvcNAQkOMRowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DANBgkq + hkiG9w0BAQUFAAMxALaK8/HR73ZSvHiWo7Mduin0S519aJBm+gO8d9iliUkK00gQ + VMs9DuTAxljX7t7Eug== + -----END CERTIFICATE REQUEST-----""") + + def setUp(self): + app_conf = {"app": copy.deepcopy(config.app), + "logging": copy.deepcopy(config.logging)} + self.app = pecan_testing.load_test_app(app_conf) + jsonloader.conf.load_str_data(TestFunctional.config) + + self.conf = getattr(jsonloader.conf, "_config") + self.conf["ca"]["output_path"] = tempfile.mkdtemp() + + 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_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() + cert.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()))