certmeister 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|