certmeister 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +27 -0
  8. data/LICENSE +20 -0
  9. data/README.md +32 -0
  10. data/Rakefile +6 -0
  11. data/certmeister.gemspec +26 -0
  12. data/fixtures/ca.crt +15 -0
  13. data/fixtures/ca.csr +12 -0
  14. data/fixtures/ca.key +15 -0
  15. data/fixtures/client.crt +15 -0
  16. data/fixtures/client.csr +12 -0
  17. data/fixtures/client.key +15 -0
  18. data/lib/certmeister.rb +14 -0
  19. data/lib/certmeister/base.rb +92 -0
  20. data/lib/certmeister/config.rb +129 -0
  21. data/lib/certmeister/in_memory_store.rb +43 -0
  22. data/lib/certmeister/policy.rb +21 -0
  23. data/lib/certmeister/policy/blackhole.rb +15 -0
  24. data/lib/certmeister/policy/chain_all.rb +36 -0
  25. data/lib/certmeister/policy/domain.rb +37 -0
  26. data/lib/certmeister/policy/existing.rb +32 -0
  27. data/lib/certmeister/policy/fcrdns.rb +40 -0
  28. data/lib/certmeister/policy/noop.rb +15 -0
  29. data/lib/certmeister/policy/psk.rb +37 -0
  30. data/lib/certmeister/policy/response.rb +24 -0
  31. data/lib/certmeister/response.rb +47 -0
  32. data/lib/certmeister/store_error.rb +6 -0
  33. data/lib/certmeister/test/memory_store_interface.rb +54 -0
  34. data/lib/certmeister/version.rb +5 -0
  35. data/signit.rb +39 -0
  36. data/spec/certmeister/base_spec.rb +205 -0
  37. data/spec/certmeister/config_spec.rb +170 -0
  38. data/spec/certmeister/in_memory_store_spec.rb +40 -0
  39. data/spec/certmeister/policy/blackhole_spec.rb +19 -0
  40. data/spec/certmeister/policy/chain_all_spec.rb +40 -0
  41. data/spec/certmeister/policy/domain_spec.rb +38 -0
  42. data/spec/certmeister/policy/existing_spec.rb +39 -0
  43. data/spec/certmeister/policy/fcrdns_spec.rb +45 -0
  44. data/spec/certmeister/policy/noop_spec.rb +17 -0
  45. data/spec/certmeister/policy/psk_spec.rb +38 -0
  46. data/spec/certmeister/policy/response_spec.rb +35 -0
  47. data/spec/certmeister/response_spec.rb +73 -0
  48. data/spec/helpers/certmeister_config_helper.rb +21 -0
  49. data/spec/helpers/certmeister_fetching_request_helper.rb +9 -0
  50. data/spec/helpers/certmeister_policy_helper.rb +14 -0
  51. data/spec/helpers/certmeister_removing_request_helper.rb +9 -0
  52. data/spec/helpers/certmeister_signing_request_helper.rb +10 -0
  53. data/spec/spec_helper.rb +20 -0
  54. 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
@@ -0,0 +1,20 @@
1
+ *~
2
+ *.sw*
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ coverage
8
+ InstalledFiles
9
+ lib/bundler/man
10
+ pkg
11
+ rdoc
12
+ spec/reports
13
+ test/tmp
14
+ test/version_tmp
15
+ tmp
16
+
17
+ # YARD artifacts
18
+ .yardoc
19
+ _yardoc
20
+ doc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
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
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -0,0 +1,14 @@
1
+ module Certmeister
2
+ end
3
+
4
+ Dir.glob(File.join(File.dirname(__FILE__), "certmeister", "*.rb")) do |path|
5
+ require path
6
+ end
7
+
8
+ module Certmeister
9
+
10
+ def self.new(*args)
11
+ Certmeister::Base.new(*args)
12
+ end
13
+
14
+ end
@@ -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