certmeister 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +20 -0
- data/README.md +32 -0
- data/Rakefile +6 -0
- data/certmeister.gemspec +26 -0
- data/fixtures/ca.crt +15 -0
- data/fixtures/ca.csr +12 -0
- data/fixtures/ca.key +15 -0
- data/fixtures/client.crt +15 -0
- data/fixtures/client.csr +12 -0
- data/fixtures/client.key +15 -0
- data/lib/certmeister.rb +14 -0
- data/lib/certmeister/base.rb +92 -0
- data/lib/certmeister/config.rb +129 -0
- data/lib/certmeister/in_memory_store.rb +43 -0
- data/lib/certmeister/policy.rb +21 -0
- data/lib/certmeister/policy/blackhole.rb +15 -0
- data/lib/certmeister/policy/chain_all.rb +36 -0
- data/lib/certmeister/policy/domain.rb +37 -0
- data/lib/certmeister/policy/existing.rb +32 -0
- data/lib/certmeister/policy/fcrdns.rb +40 -0
- data/lib/certmeister/policy/noop.rb +15 -0
- data/lib/certmeister/policy/psk.rb +37 -0
- data/lib/certmeister/policy/response.rb +24 -0
- data/lib/certmeister/response.rb +47 -0
- data/lib/certmeister/store_error.rb +6 -0
- data/lib/certmeister/test/memory_store_interface.rb +54 -0
- data/lib/certmeister/version.rb +5 -0
- data/signit.rb +39 -0
- data/spec/certmeister/base_spec.rb +205 -0
- data/spec/certmeister/config_spec.rb +170 -0
- data/spec/certmeister/in_memory_store_spec.rb +40 -0
- data/spec/certmeister/policy/blackhole_spec.rb +19 -0
- data/spec/certmeister/policy/chain_all_spec.rb +40 -0
- data/spec/certmeister/policy/domain_spec.rb +38 -0
- data/spec/certmeister/policy/existing_spec.rb +39 -0
- data/spec/certmeister/policy/fcrdns_spec.rb +45 -0
- data/spec/certmeister/policy/noop_spec.rb +17 -0
- data/spec/certmeister/policy/psk_spec.rb +38 -0
- data/spec/certmeister/policy/response_spec.rb +35 -0
- data/spec/certmeister/response_spec.rb +73 -0
- data/spec/helpers/certmeister_config_helper.rb +21 -0
- data/spec/helpers/certmeister_fetching_request_helper.rb +9 -0
- data/spec/helpers/certmeister_policy_helper.rb +14 -0
- data/spec/helpers/certmeister_removing_request_helper.rb +9 -0
- data/spec/helpers/certmeister_signing_request_helper.rb +10 -0
- data/spec/spec_helper.rb +20 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 546ac60693e496000a1fcb1c52cfe9324e091efe
|
4
|
+
data.tar.gz: ecfe80b2211241e9782329a705ea9e8e9869d51b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a7528b98e7f6c92527b53f8d0038535ae49c7b62265d97b35964f6c93d9dbda60e7c0dc796ae5b85834f552f0e5ede845314ed412ca32bb50c6abcfebab4210
|
7
|
+
data.tar.gz: 1ddc2e5d38a52438fcb9a1420077e19117edb6c1d08126cbde580c656fb93b946c0135cdcff085d4e82ae5a7ba13e5abed57b08e8325dd750c2a3a4b28571ddb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
certmeister
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p247
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
certmeister (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
rake (0.9.6)
|
11
|
+
rspec (2.14.1)
|
12
|
+
rspec-core (~> 2.14.0)
|
13
|
+
rspec-expectations (~> 2.14.0)
|
14
|
+
rspec-mocks (~> 2.14.0)
|
15
|
+
rspec-core (2.14.7)
|
16
|
+
rspec-expectations (2.14.4)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.14.4)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
bundler (~> 1.5)
|
25
|
+
certmeister!
|
26
|
+
rake (~> 0)
|
27
|
+
rspec (~> 2.14)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Sheldon Hearn
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Certmeister
|
2
|
+
|
3
|
+
Certmeister is a conditionally autosigning Certificate Authority. It was developed for use
|
4
|
+
with the Puppet infrastructure at Hetzner PTY Ltd.
|
5
|
+
|
6
|
+
The service will autosign a certificate request when the configurable access policy permits.
|
7
|
+
The reference access policy in use by Hetzner PTY Ltd is:
|
8
|
+
|
9
|
+
* the Common Name (CN) of the certificate is in the host-h.net domain,
|
10
|
+
* the service has no record of already having signed a certificate for that CN, and
|
11
|
+
* the requesting client IP address has forward confirmed reverse DNS that matches the CN.
|
12
|
+
* Requests to fetch certificates are always allowed.
|
13
|
+
* Requests to delete certificates are only allowed when they originate from
|
14
|
+
a secure operator network.
|
15
|
+
|
16
|
+
This allows us the convenience of Puppet's autosign feature, without the horrendous security implications.
|
17
|
+
|
18
|
+
Certmeister is the core of a fancy web service that does this:
|
19
|
+
|
20
|
+
```
|
21
|
+
cat request/client.csr | openssl x509 -req -CA CA/ca.crt -CAkey CA/ca.key -CAcreateserial -addtrust clientAuth > CA/signed/<cn>.crt
|
22
|
+
```
|
23
|
+
|
24
|
+
To hit the service:
|
25
|
+
|
26
|
+
```
|
27
|
+
$ curl -L \
|
28
|
+
-d "psk=secretkey" \
|
29
|
+
-d "csr=$(perl -MURI::Escape -e 'print uri_escape(join("", <STDIN>));' < request/client.csr)" \
|
30
|
+
http://certmeister.hetzner.co.za/certificate/$(hostname --fqdn) > request/client.crt
|
31
|
+
```
|
32
|
+
|
data/Rakefile
ADDED
data/certmeister.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'certmeister/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "certmeister"
|
8
|
+
spec.version = Certmeister::VERSION
|
9
|
+
spec.authors = ["Sheldon Hearn"]
|
10
|
+
spec.email = ["sheldonh@starjuice.net"]
|
11
|
+
spec.summary = %q{Conditionally autosigning certificate authority.}
|
12
|
+
spec.description = %q{Certificate authority that can be configured to make decisions about whether to autosign certificate signing requests for clients. This gem provides the protocol-agnostic library, which is expected to be used within something like an HTTP REST service.}
|
13
|
+
spec.homepage = "https://github.com/sheldonh/certmeister"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", "~> 0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
data/fixtures/ca.crt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICVzCCAcACCQDZ9VpcDtwY1TANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa
|
3
|
+
QTEVMBMGA1UECAwMV2VzdGVybiBDYXBlMRIwEAYDVQQHDAlDYXBlIFRvd24xGDAW
|
4
|
+
BgNVBAoMD0hldHpuZXIgUFRZIEx0ZDEcMBoGA1UEAwwTQ2VydG1laXN0ZXIgVGVz
|
5
|
+
dCBDQTAeFw0xMzEyMTgwOTQxMTdaFw0xNjEyMTcwOTQxMTdaMHAxCzAJBgNVBAYT
|
6
|
+
AlpBMRUwEwYDVQQIDAxXZXN0ZXJuIENhcGUxEjAQBgNVBAcMCUNhcGUgVG93bjEY
|
7
|
+
MBYGA1UECgwPSGV0em5lciBQVFkgTHRkMRwwGgYDVQQDDBNDZXJ0bWVpc3RlciBU
|
8
|
+
ZXN0IENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDE+JkNX7kXFGZ2JyFH
|
9
|
+
GfMgXhzazKoGJj/HV3aJSqTjRVrYkgTgCz1aIGj/DXd3gTbturC4s4SiU0i1Gi5K
|
10
|
+
J8dHVfDlz/jNbPG4LiMOL1B0TQV4MyVdKJFn6nZCbBqvhCc0vHGw1O7xFiuRBmcP
|
11
|
+
SJyqT7Qwz8B1hYNQggVLbPAwawIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADptm7G+
|
12
|
+
9jt9v9yjReQu8KgPcMOfzvNBH8g6s4WH7fn7FAj7//92YsYzX2Ost2zPzdsYCSgD
|
13
|
+
DucgmG/cezUCyoljqmND5gfcEtk2WjdXF/Sd+Ulnr02L0QTmIzHNf52rRfZGH8O1
|
14
|
+
dfx9ZA9mS0uPXd6ePgJI5/y7x7tgHjsm1VOz
|
15
|
+
-----END CERTIFICATE-----
|
data/fixtures/ca.csr
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
2
|
+
MIIBsDCCARkCAQAwcDELMAkGA1UEBhMCWkExFTATBgNVBAgMDFdlc3Rlcm4gQ2Fw
|
3
|
+
ZTESMBAGA1UEBwwJQ2FwZSBUb3duMRgwFgYDVQQKDA9IZXR6bmVyIFBUWSBMdGQx
|
4
|
+
HDAaBgNVBAMME0NlcnRtZWlzdGVyIFRlc3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQAD
|
5
|
+
gY0AMIGJAoGBAMT4mQ1fuRcUZnYnIUcZ8yBeHNrMqgYmP8dXdolKpONFWtiSBOAL
|
6
|
+
PVogaP8Nd3eBNu26sLizhKJTSLUaLkonx0dV8OXP+M1s8bguIw4vUHRNBXgzJV0o
|
7
|
+
kWfqdkJsGq+EJzS8cbDU7vEWK5EGZw9InKpPtDDPwHWFg1CCBUts8DBrAgMBAAGg
|
8
|
+
ADANBgkqhkiG9w0BAQUFAAOBgQAJL3fCo4b9Y05+Sims2j58dX599cKzjWJFxEFr
|
9
|
+
+nNleWoxRu8yEzKTGW9F8xrBHIM3lN8ULLEWr4B0kG+k1vMc2ZQHaalRL3OE386k
|
10
|
+
Swt5HcL01He6cLnQppIlDNhj5LpkGRipzQZrXip2NCQTYEEKX6PEed/hqv20jXU1
|
11
|
+
ge75bA==
|
12
|
+
-----END CERTIFICATE REQUEST-----
|
data/fixtures/ca.key
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIICXAIBAAKBgQDE+JkNX7kXFGZ2JyFHGfMgXhzazKoGJj/HV3aJSqTjRVrYkgTg
|
3
|
+
Cz1aIGj/DXd3gTbturC4s4SiU0i1Gi5KJ8dHVfDlz/jNbPG4LiMOL1B0TQV4MyVd
|
4
|
+
KJFn6nZCbBqvhCc0vHGw1O7xFiuRBmcPSJyqT7Qwz8B1hYNQggVLbPAwawIDAQAB
|
5
|
+
AoGAQf6hGTAHTcpSAiheJ/pz0VZ3CIAmP2U1XU7asmlGEbe9Fm7mH0LkzXuqcjpK
|
6
|
+
2sl6Y/B3IYtUVybcZ4FcHRBy3ccOZtV7mkZtJqg8Uso9pHKvtgvAleeGB8PRQlfr
|
7
|
+
Ah9hv+vGKqbfrh3gFupzJGkw0oYxknW0Cf/adcoc+FsfgDECQQDveKbW7lUajYkz
|
8
|
+
Gvx0N5VbzemQz01LO8AtYfnV7875lIDaBBxuT5GyOVKq2zCT6t7oOtGdyY520P7J
|
9
|
+
WPu6UvONAkEA0pD8ovjMYVIxe5FrTyIeqhj6zx8Ji1oYA0x/4aEwNjZ3wT8gdJXw
|
10
|
+
3zLkoGbJFj1OD+qvn/IyKdsU+Sjjzsh51wJALZVe1MzbQEmu0x6Q7aJi+O2yRxFe
|
11
|
+
2jJOe5UJ1JJoaJO/D0D3FHxq9Gz68nD0x0NPGQ+RNSLXzoAr77HTgP0nRQJBAJDi
|
12
|
+
XlMs6kRUr+Ocbb/ndD4KLhlx+7k85qTucFep92h2FfSMISLXQQPzGskbsGVzDVF6
|
13
|
+
ZBmkJswSCN5gOk/ANcUCQHJobzZpl7IQmee/qDt8ZIffVUtSuF77y4Xwso9bbcJN
|
14
|
+
Ax03Rg6rhgm0KcklEc/4VTahfNBzhF5YDkaRSTRcUsI=
|
15
|
+
-----END RSA PRIVATE KEY-----
|
data/fixtures/client.crt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICXTCCAcYCCQDomedDhZaLjzANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJa
|
3
|
+
QTEVMBMGA1UECAwMV2VzdGVybiBDYXBlMRIwEAYDVQQHDAlDYXBlIFRvd24xGDAW
|
4
|
+
BgNVBAoMD0hldHpuZXIgUFRZIEx0ZDEjMCEGA1UEAwwaY2VydG1laXN0ZXIuaGV0
|
5
|
+
em5lci5hZnJpY2EwHhcNMTMxMjExMTE0MTU0WhcNMTQwMTEwMTE0MTU0WjBvMQsw
|
6
|
+
CQYDVQQGEwJaQTEVMBMGA1UECAwMV2VzdGVybiBDYXBlMRIwEAYDVQQHDAlDYXBl
|
7
|
+
IFRvd24xGDAWBgNVBAoMD0hldHpuZXIgUFRZIEx0ZDEbMBkGA1UEAwwSYXhsLmhl
|
8
|
+
dHpuZXIuYWZyaWNhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrXgWS0TD1
|
9
|
+
mKXOPkNQpMjYyLHroDRlrmVMB5GY/Uyz0eaNz5GecKa7kvrM+gdjyzr/y1vF7C27
|
10
|
+
zovu/ZJ8qQmMtIrFlAsXEETMwc6DPOIdPJnXNTlI9a8KjwBB2VtkjxaZqLt2THB1
|
11
|
+
oDbFpZHh6UeWBPvzEN1hlfBEvvnB+aQX+QIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
|
12
|
+
ABl5o3iL1t9NY0MAaw53VAsM86n53WBAZEzQ+6z3vsxoKU2ejMp3GACoTsyjGWad
|
13
|
+
EyN2lKfCjPPYsmrmtshXGb/U0xHXeJS4xifibBktfwv2v2eIfrh5CbDMl6EuUzEC
|
14
|
+
mLmVOt4adTt8R8vJ42KaKULKODrsc29AHK79oyxEhYyE
|
15
|
+
-----END CERTIFICATE-----
|
data/fixtures/client.csr
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
2
|
+
MIIBrzCCARgCAQAwbzELMAkGA1UEBhMCWkExFTATBgNVBAgMDFdlc3Rlcm4gQ2Fw
|
3
|
+
ZTESMBAGA1UEBwwJQ2FwZSBUb3duMRgwFgYDVQQKDA9IZXR6bmVyIFBUWSBMdGQx
|
4
|
+
GzAZBgNVBAMMEmF4bC5oZXR6bmVyLmFmcmljYTCBnzANBgkqhkiG9w0BAQEFAAOB
|
5
|
+
jQAwgYkCgYEAq14FktEw9Zilzj5DUKTI2Mix66A0Za5lTAeRmP1Ms9Hmjc+RnnCm
|
6
|
+
u5L6zPoHY8s6/8tbxewtu86L7v2SfKkJjLSKxZQLFxBEzMHOgzziHTyZ1zU5SPWv
|
7
|
+
Co8AQdlbZI8Wmai7dkxwdaA2xaWR4elHlgT78xDdYZXwRL75wfmkF/kCAwEAAaAA
|
8
|
+
MA0GCSqGSIb3DQEBBQUAA4GBAKHHpelQzMYFBXYa0VOWFiqRd1HXJfnUbo8D5xup
|
9
|
+
RzveAVlGTj83slgKvGigUupWdfk1S4KiUG1HsAyLcwl8lgOCO77CrdNPZC0qjB4+
|
10
|
+
pK3Xp2FMsK4+lp24FNR0tM31FA03DU8uhL8v5cvExHBn4idBEwO2W4OWPKVYKrtm
|
11
|
+
w9ne
|
12
|
+
-----END CERTIFICATE REQUEST-----
|
data/fixtures/client.key
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIICXQIBAAKBgQCrXgWS0TD1mKXOPkNQpMjYyLHroDRlrmVMB5GY/Uyz0eaNz5Ge
|
3
|
+
cKa7kvrM+gdjyzr/y1vF7C27zovu/ZJ8qQmMtIrFlAsXEETMwc6DPOIdPJnXNTlI
|
4
|
+
9a8KjwBB2VtkjxaZqLt2THB1oDbFpZHh6UeWBPvzEN1hlfBEvvnB+aQX+QIDAQAB
|
5
|
+
AoGBAJJSkuG821gpghHrmiDGw3RPUZRG09aqpXXY1JpUVmUIahtWTBmESOnK43mY
|
6
|
+
WCz+wB7f7jm+o4JEJ7nmLGljEHH9cvdIo84SgAeIbs1h0o28GJ1LG10L5CvTL3ai
|
7
|
+
dejFXKRNnVGxpGQQi1XqpVqv6CUSmGZoZMeTWIOPVhSddAi9AkEA17Vq6T5gJfz6
|
8
|
+
wRVpSlG1Nz2C3OQ0hPmtgwLJZcVSHUuQvgZfL8O6U7+QBQRHCHNNu0DtrdKheWHt
|
9
|
+
GdqWbWwb4wJBAMtgUsu5U/KVU3i0R8eEBkFFNho7Np4FSBQ5MMPcp727hyI/Neev
|
10
|
+
R/gFx8OSY4EBtu9Npt0fqEWVDKa/WNcU+3MCQDkm5BCwaiEmifmmhqMeSvk73vRP
|
11
|
+
smqZDJPtpRbF1R/V6Z+vaIDrRu7xjmMF4xwmEK5QYajwacATZhK3i6uqPSkCQFJj
|
12
|
+
OgdHk4dhEMiEF9PuWu5UEF+9/xkywIlYxbWCjP1im5K3EqpBqqECDnPeuKqJPEdj
|
13
|
+
KKbJbUyK3e4q891lpZsCQQDWAY3NbuNufmuWp2naACwEcYSjoMfdPoHnaaynJu5h
|
14
|
+
UHcPcCjAijtue9+iPZ/4LPJ5TH4pr8Ww4AxDCgBB8foH
|
15
|
+
-----END RSA PRIVATE KEY-----
|
data/lib/certmeister.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Certmeister
|
5
|
+
|
6
|
+
class Base
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
if config.valid?
|
10
|
+
@sign_policy = config.sign_policy
|
11
|
+
@fetch_policy = config.fetch_policy
|
12
|
+
@remove_policy = config.remove_policy
|
13
|
+
@ca_cert = config.ca_cert
|
14
|
+
@ca_key = config.ca_key
|
15
|
+
@store = config.store
|
16
|
+
@openssl_digest = config.openssl_digest
|
17
|
+
else
|
18
|
+
raise RuntimeError.new("invalid config")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def sign(request)
|
23
|
+
subject_to_policy(@sign_policy, request) do |request|
|
24
|
+
begin
|
25
|
+
csr = OpenSSL::X509::Request.new(request[:csr])
|
26
|
+
rescue OpenSSL::OpenSSLError => e
|
27
|
+
Certmeister::Response.error("invalid CSR (#{e.message})")
|
28
|
+
else
|
29
|
+
if get_cn(csr) == request[:cn]
|
30
|
+
pem = create_signed_certificate(csr).to_pem
|
31
|
+
@store.store(request[:cn], pem)
|
32
|
+
Certmeister::Response.hit(pem)
|
33
|
+
else
|
34
|
+
Certmeister::Response.error("CSR subject (#{get_cn(csr)}) disagrees with request CN (#{request[:cn]})")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch(request)
|
41
|
+
subject_to_policy(@fetch_policy, request) do |request|
|
42
|
+
Certmeister::Response.new(@store.fetch(request[:cn]), nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove(request)
|
47
|
+
subject_to_policy(@remove_policy, request) do |request|
|
48
|
+
Certmeister::Response.new(!!@store.remove(request[:cn]), nil)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def subject_to_policy(policy, request, &block)
|
55
|
+
if !request[:cn]
|
56
|
+
Certmeister::Response.error("request missing CN")
|
57
|
+
else
|
58
|
+
authentication = policy.authenticate(request)
|
59
|
+
if authentication.authenticated?
|
60
|
+
block.call(request)
|
61
|
+
else
|
62
|
+
Certmeister::Response.error("request refused (#{authentication.error})")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_signed_certificate(csr)
|
68
|
+
cert = OpenSSL::X509::Certificate.new
|
69
|
+
|
70
|
+
cert.serial = 0
|
71
|
+
cert.version = 2
|
72
|
+
cert.subject = csr.subject
|
73
|
+
cert.public_key = csr.public_key
|
74
|
+
|
75
|
+
now = DateTime.now
|
76
|
+
cert.not_before = now.to_time
|
77
|
+
cert.not_after = (now + (5 * 365)).to_time
|
78
|
+
cert.issuer = @ca_cert.subject
|
79
|
+
cert.sign @ca_key, @openssl_digest.new
|
80
|
+
|
81
|
+
cert
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_cn(csr)
|
85
|
+
if csr.subject.to_s =~ /CN=(.+)$/
|
86
|
+
$1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
require 'certmeister/policy'
|
4
|
+
|
5
|
+
module Certmeister
|
6
|
+
|
7
|
+
class Config
|
8
|
+
|
9
|
+
attr_reader :store, :sign_policy, :fetch_policy, :remove_policy
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
@store = options[:store]
|
14
|
+
@sign_policy = options[:sign_policy]
|
15
|
+
@fetch_policy = options[:fetch_policy]
|
16
|
+
@remove_policy = options[:remove_policy]
|
17
|
+
@errors = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def ca_cert
|
21
|
+
@ca_cert ||= OpenSSL::X509::Certificate.new(@options[:ca_cert])
|
22
|
+
end
|
23
|
+
|
24
|
+
def ca_key
|
25
|
+
@ca_key ||= OpenSSL::PKey.read(@options[:ca_key])
|
26
|
+
end
|
27
|
+
|
28
|
+
def openssl_digest
|
29
|
+
if OpenSSL::Digest.const_defined?('SHA256')
|
30
|
+
@digest = OpenSSL::Digest::SHA256
|
31
|
+
elsif OpenSSL::Digest.const_defined?('SHA1')
|
32
|
+
@digest = OpenSSL::Digest::SHA1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid?
|
37
|
+
validate
|
38
|
+
@errors.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def errors
|
42
|
+
@errors
|
43
|
+
end
|
44
|
+
|
45
|
+
def error_list
|
46
|
+
@errors.keys.sort.inject([]) do |list, option|
|
47
|
+
list << "#{option} #{@errors[option]}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def validate
|
54
|
+
validate_x509_pem(:ca_cert)
|
55
|
+
validate_pkey_pem(:ca_key)
|
56
|
+
validate_store(:store)
|
57
|
+
validate_policy(:sign_policy)
|
58
|
+
validate_policy(:fetch_policy)
|
59
|
+
validate_policy(:remove_policy)
|
60
|
+
validate_openssl_digest
|
61
|
+
validate_known_options
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_x509_pem(option)
|
65
|
+
if not @options[option]
|
66
|
+
@errors[option] = "is required"
|
67
|
+
else
|
68
|
+
begin
|
69
|
+
OpenSSL::X509::Certificate.new(@options[option])
|
70
|
+
rescue OpenSSL::OpenSSLError => e
|
71
|
+
@errors[option] = "must be a PEM-encoded x509 certificate (#{e.message})"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_pkey_pem(option)
|
77
|
+
if not @options[option]
|
78
|
+
@errors[option] = "is required"
|
79
|
+
else
|
80
|
+
begin
|
81
|
+
OpenSSL::PKey.read(@options[option])
|
82
|
+
rescue ArgumentError => e
|
83
|
+
@errors[option] = "must be a PEM-encoded private key (#{e.message})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_store(option)
|
89
|
+
o = @options[option]
|
90
|
+
if not o
|
91
|
+
@errors[option] = "is required"
|
92
|
+
elsif not o.respond_to?(:store) or o.method(:store).arity != 2
|
93
|
+
@errors[option] = "must provide a binary store method"
|
94
|
+
elsif not o.respond_to?(:fetch) or o.method(:fetch).arity != 1
|
95
|
+
@errors[option] = "must provide a unary fetch method"
|
96
|
+
elsif not o.respond_to?(:remove) or o.method(:remove).arity != 1
|
97
|
+
@errors[option] = "must provide a unary remove method"
|
98
|
+
elsif not o.respond_to?(:health_check) or o.method(:health_check).arity != 0
|
99
|
+
@errors[option] = "must provide a nullary health_check method"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_policy(option)
|
104
|
+
o = @options[option]
|
105
|
+
if not o
|
106
|
+
@errors[option] = "is required"
|
107
|
+
elsif not Certmeister::Policy.validate_authenticate_signature(@options[option])
|
108
|
+
@errors[option] = "must provide a unary authenticate method"
|
109
|
+
elsif not Certmeister::Policy.validate_authenticate_returns_response(@options[option])
|
110
|
+
@errors[option] = "must return a policy response"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_openssl_digest
|
115
|
+
if not openssl_digest
|
116
|
+
@errors[:openssl_digest] = "can't find FIPS 140-2 compliant algorithm in OpenSSL::Digest"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_known_options
|
121
|
+
unexpected = @options.keys - [:ca_cert, :ca_key, :store, :sign_policy, :fetch_policy, :remove_policy]
|
122
|
+
unexpected.each do |option|
|
123
|
+
@errors[option] = "is not a supported option"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|