leap_ca 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ LEAP Certificate Authority Daemon
2
+ ---------------------------------------------------
3
+
4
+ ``leap_ca_daemon`` is a background daemon that generates x509 certificates as needed and stores them in CouchDB. You can run ``leap_ca`` on a machine that is not connected to a network, and then periodically connect to sync up the cert database.
5
+
6
+ * Its only interface with the outside world is a CouchDB connection (defaults to localhost).
7
+ * The daemon monitors changes to the database and fills it with x509 certs as needed.
8
+ * It requires access to a Certificate Authority (in other words, the RSA private key and x509 root certificate, in PEM format).
9
+
10
+ This program is written in Ruby and is distributed under the following license:
11
+
12
+ > GNU Affero General Public License
13
+ > Version 3.0 or higher
14
+ > http://www.gnu.org/licenses/agpl-3.0.html
15
+
16
+ Installation
17
+ ---------------------
18
+
19
+ Prerequisites:
20
+
21
+ sudo apt-get install ruby ruby-dev couchdb
22
+ # if you are running ruby 1.8, you will also need rubygems.
23
+ # for development, you will also need git, bundle, and rake.
24
+
25
+ From source:
26
+
27
+ git clone git://leap.se/leap_ca
28
+ cd cleap_ca
29
+ bundle
30
+ rake build
31
+ sudo rake install
32
+
33
+ From gem:
34
+
35
+ sudo gem install leap_ca
36
+
37
+ Running
38
+ --------------------
39
+
40
+ See if it worked:
41
+
42
+ leap_ca_daemon run -- test/config/config.yaml
43
+ browse to http://localhost:5984/_utils
44
+
45
+ How you would run normally in production mode:
46
+
47
+ leap_ca_daemon start
48
+ leap_ca_daemon stop
49
+
50
+ See ``leap_ca_daemon --help`` for more options.
51
+
52
+ Configuration
53
+ ---------------------
54
+
55
+ ``leap_ca_daemon`` reads the following configurations files, in this order:
56
+
57
+ * ``$(leap_ca_source)/config/default_config.yaml``
58
+ * ``/etc/leap/leap_ca.yaml``
59
+ * Any file passed to ARGV like so ``leap_ca start -- /etc/leap_ca.yaml``
60
+
61
+ Other than ``ca_key_path`` and ``ca_cert_path`` you can probably leave all other options at their default values.
62
+
63
+ The default options are:
64
+
65
+ #
66
+ # Default configuration options for LEAP Certificate Authority Daemon
67
+ #
68
+
69
+ #
70
+ # Certificate Authority
71
+ #
72
+ ca_key_path: "../test/files/ca.key"
73
+ ca_key_password: nil
74
+ ca_cert_path: "../test/files/ca.crt"
75
+
76
+ #
77
+ # Certificate pool
78
+ #
79
+ max_pool_size: 100
80
+ client_cert_lifespan: 2
81
+ client_cert_bit_size: 2024
82
+ client_cert_hash: "SHA256"
83
+
84
+ #
85
+ # Database
86
+ #
87
+ db_name: "client_certificates"
88
+ couch_connection:
89
+ protocol: "http"
90
+ host: "localhost"
91
+ port: 5984
92
+ username: ~
93
+ password: ~
94
+ prefix: ""
95
+ suffix: ""
96
+
97
+ Rake Tasks
98
+ ----------------------------
99
+
100
+ rake -T
101
+ rake build # Build leap_ca-x.x.x.gem into the pkg directory
102
+ rake install # Install leap_ca-x.x.x.gem into either system-wide or user gems
103
+ rake test # Run tests
104
+ rake uninstall # Uninstall leap_ca-x.x.x.gem from either system-wide or user gems
105
+
106
+ Development
107
+ --------------------
108
+
109
+ For development and debugging you might want to run the programm directly without
110
+ the deamon wrapper. You can do this like this:
111
+
112
+ ruby -I lib lib/leap_ca_daemon.rb
113
+
114
+
115
+ Todo
116
+ ----------------------------
117
+
118
+ * Remove deprecated 'yajl/http_stream'
data/Rakefile ADDED
@@ -0,0 +1,93 @@
1
+ require "rubygems"
2
+ require "highline/import"
3
+ require "pty"
4
+ require "fileutils"
5
+ require 'rake/testtask'
6
+
7
+ ##
8
+ ## HELPER
9
+ ##
10
+
11
+ def run(cmd)
12
+ PTY.spawn(cmd) do |output, input, pid|
13
+ begin
14
+ while line = output.gets do
15
+ puts line
16
+ end
17
+ rescue Errno::EIO
18
+ end
19
+ end
20
+ rescue PTY::ChildExited
21
+ end
22
+
23
+ ##
24
+ ## GEM BUILDING AND INSTALLING
25
+ ##
26
+
27
+ $spec_path = 'leap_ca.gemspec'
28
+ $spec = eval(File.read($spec_path))
29
+ $base_dir = File.dirname(__FILE__)
30
+ $gem_path = File.join($base_dir, 'pkg', "#{$spec.name}-#{$spec.version}.gem")
31
+
32
+ def built_gem_path
33
+ Dir[File.join($base_dir, "#{$spec.name}-*.gem")].sort_by{|f| File.mtime(f)}.last
34
+ end
35
+
36
+ desc "Build #{$spec.name}-#{$spec.version}.gem into the pkg directory"
37
+ task 'build' do
38
+ FileUtils.mkdir_p(File.join($base_dir, 'pkg'))
39
+ FileUtils.rm($gem_path) if File.exists?($gem_path)
40
+ run "gem build -V '#{$spec_path}'"
41
+ file_name = File.basename(built_gem_path)
42
+ FileUtils.mv(built_gem_path, 'pkg')
43
+ say "#{$spec.name} #{$spec.version} built to pkg/#{file_name}"
44
+ end
45
+
46
+ desc "Install #{$spec.name}-#{$spec.version}.gem into either system-wide or user gems"
47
+ task 'install' do
48
+ if !File.exists?($gem_path)
49
+ say("Could not file #{$gem_path}. Try running 'rake build'")
50
+ else
51
+ if ENV["USER"] == "root"
52
+ run "gem install '#{$gem_path}'"
53
+ else
54
+ home_gem_path = Gem.path.grep(/home/).first
55
+ say("You are installing as an unprivileged user, which will result in the installation being placed in '#{home_gem_path}'.")
56
+ if agree("Do you want to continue installing to #{home_gem_path}? ")
57
+ run "gem install '#{$gem_path}' --user-install"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ desc "Uninstall #{$spec.name}-#{$spec.version}.gem from either system-wide or user gems"
64
+ task 'uninstall' do
65
+ if ENV["USER"] == "root"
66
+ say("Removing #{$spec.name}-#{$spec.version}.gem from system-wide gems")
67
+ run "gem uninstall '#{$spec.name}' --version #{$spec.version} --verbose -x -I"
68
+ else
69
+ say("Removing #{$spec.name}-#{$spec.version}.gem from user's gems")
70
+ run "gem uninstall '#{$spec.name}' --version #{$spec.version} --verbose --user-install -x -I"
71
+ end
72
+ end
73
+
74
+ ##
75
+ ## TESTING
76
+ ##
77
+
78
+ Rake::TestTask.new do |t|
79
+ t.pattern = "test/unit/*_test.rb"
80
+ end
81
+ task :default => :test
82
+
83
+ ##
84
+ ## DOCUMENTATION
85
+ ##
86
+
87
+ # require 'rdoc/task'
88
+
89
+ # Rake::RDocTask.new do |rd|
90
+ # rd.main = "README.rdoc"
91
+ # rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
92
+ # rd.title = 'Your application title'
93
+ # end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+
3
+ #
4
+ # LEAP Client Certificate Generation Daemon
5
+ #
6
+
7
+ BASE_DIR = File.expand_path('../..', File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
8
+
9
+ begin
10
+ #
11
+ # try without rubygems (might be already loaded or not present)
12
+ #
13
+ require 'leap_ca/version'
14
+ rescue LoadError
15
+ #
16
+ # try with rubygems
17
+ #
18
+ require "#{BASE_DIR}/lib/leap_ca/version.rb"
19
+ LeapCA::REQUIRE_PATHS.each do |path|
20
+ path = File.expand_path(path, BASE_DIR)
21
+ $LOAD_PATH.unshift path unless $LOAD_PATH.include?(path)
22
+ end
23
+ require 'rubygems'
24
+ require 'leap_ca/version'
25
+ end
26
+
27
+ # Graceful Ctrl-C
28
+ Signal.trap("SIGINT") do
29
+ puts "\nQuit"
30
+ exit
31
+ end
32
+
33
+ # this changes later, so save the initial current directory
34
+ CWD = Dir.pwd
35
+
36
+ # handle --version ourselves
37
+ if ARGV.grep(/--version/).any?
38
+ puts "leap_ca #{LeapCA::VERSION}, ruby #{RUBY_VERSION}"
39
+ exit(0)
40
+ end
41
+
42
+ # --fill-pool will fill the pool and then exit
43
+ if ARGV.grep(/--fill-pool/).any?
44
+ require 'leap_ca'
45
+ pool = LeapCA::Pool.new(:size => LeapCA::Config.max_pool_size)
46
+ pool.fill
47
+ exit(0)
48
+ end
49
+
50
+ #
51
+ # Start the daemon
52
+ #
53
+ require 'daemons'
54
+ if ENV["USER"] == "root"
55
+ options = {:app_name => 'leap_ca', :dir_mode => :system} # this will put the pid file in /var/run
56
+ else
57
+ options = {:app_name => 'leap_ca', :dir_mode => :normal, :dir => '/tmp'} # this will put the pid file in /tmp
58
+ end
59
+ Daemons.run("#{BASE_DIR}/lib/leap_ca_daemon.rb", options)
@@ -0,0 +1,32 @@
1
+ #
2
+ # Default configuration options for LEAP Certificate Authority Daemon
3
+ #
4
+
5
+ #
6
+ # Certificate Authority
7
+ #
8
+ ca_key_path: "./test/files/ca.key"
9
+ ca_key_password: nil
10
+ ca_cert_path: "./test/files/ca.crt"
11
+
12
+ #
13
+ # Certificate pool
14
+ #
15
+ max_pool_size: 100
16
+ client_cert_lifespan: 2
17
+ client_cert_bit_size: 2024
18
+ client_cert_hash: "SHA256"
19
+
20
+ #
21
+ # Database
22
+ #
23
+ db_name: "client_certificates"
24
+ couch_connection:
25
+ protocol: "http"
26
+ host: "localhost"
27
+ port: 5984
28
+ username: ~
29
+ password: ~
30
+ prefix: ""
31
+ suffix: ""
32
+ join: "_"
@@ -0,0 +1,145 @@
1
+ #
2
+ # Model for certificates stored in CouchDB.
3
+ #
4
+ # This file must be loaded after Config has been loaded.
5
+ #
6
+
7
+ require 'base64'
8
+ require 'digest/md5'
9
+ require 'openssl'
10
+ require 'certificate_authority'
11
+ require 'date'
12
+
13
+ module LeapCA
14
+ class Cert < CouchRest::Model::Base
15
+
16
+ use_database LeapCA::Config.db_name
17
+
18
+ timestamps!
19
+
20
+ property :key, String # the client private RSA key
21
+ property :cert, String # the client x509 certificate, signed by the CA
22
+ property :valid_until, Time # expiration time of the client certificate
23
+ property :random, Float, :accessible => false # used to help pick a random cert by the webapp
24
+
25
+ validates :key, :presence => true
26
+ validates :cert, :presence => true
27
+ validates :random, :presence => true, :numericality => {:greater_than_or_equal_to => 0, :less_than => 1}
28
+
29
+ before_validation :generate, :set_random, :on => :create
30
+
31
+ design do
32
+ view :by_random
33
+ end
34
+
35
+ class << self
36
+ def sample
37
+ self.by_random.startkey(rand).first || self.by_random.first
38
+ end
39
+
40
+ def pick_from_pool
41
+ cert = self.sample
42
+ raise RECORD_NOT_FOUND unless cert
43
+ cert.destroy
44
+ return cert
45
+ rescue RESOURCE_NOT_FOUND
46
+ retry if self.by_random.count > 0
47
+ raise RECORD_NOT_FOUND
48
+ end
49
+ end
50
+
51
+ #
52
+ # generate the private key and client certificate
53
+ #
54
+ def generate
55
+ cert = CertificateAuthority::Certificate.new
56
+
57
+ # set subject
58
+ cert.subject.common_name = random_common_name
59
+
60
+ # set expiration
61
+ self.valid_until = months_from_yesterday(Config.client_cert_lifespan)
62
+ cert.not_before = yesterday
63
+ cert.not_after = self.valid_until
64
+
65
+ # generate key
66
+ cert.serial_number.number = cert_serial_number
67
+ cert.key_material.generate_key(Config.client_cert_bit_size)
68
+
69
+ # sign
70
+ cert.parent = Cert.root_ca
71
+ cert.sign! client_signing_profile
72
+
73
+ self.key = cert.key_material.private_key.to_pem
74
+ self.cert = cert.to_pem
75
+ end
76
+
77
+ private
78
+
79
+ def set_random
80
+ self.random = rand
81
+ end
82
+
83
+ def self.root_ca
84
+ @root_ca ||= begin
85
+ crt = File.read(Config.ca_cert_path)
86
+ key = File.read(Config.ca_key_path)
87
+ openssl_cert = OpenSSL::X509::Certificate.new(crt)
88
+ cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
89
+ cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, Config.ca_key_password)
90
+ cert
91
+ end
92
+ end
93
+
94
+ #
95
+ # For cert serial numbers, we need a non-colliding number less than 160 bits.
96
+ # md5 will do nicely, since there is no need for a secure hash, just a short one.
97
+ # (md5 is 128 bits)
98
+ #
99
+ def cert_serial_number
100
+ Digest::MD5.hexdigest("#{rand(10**10)} -- #{Time.now}").to_i(16)
101
+ end
102
+
103
+ #
104
+ # for the random common name, we need a text string that will be unique across all certs.
105
+ # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid
106
+ #
107
+ def random_common_name
108
+ cert_serial_number.to_s(36)
109
+ end
110
+
111
+ def client_signing_profile
112
+ {
113
+ "digest" => Config.client_cert_hash,
114
+ "extensions" => {
115
+ "keyUsage" => {
116
+ "usage" => ["digitalSignature"]
117
+ },
118
+ "extendedKeyUsage" => {
119
+ "usage" => ["clientAuth"]
120
+ }
121
+ }
122
+ }
123
+ end
124
+
125
+ ##
126
+ ## TIME HELPERS
127
+ ##
128
+ ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet
129
+ ## are behind UTC.
130
+ ##
131
+
132
+ def yesterday
133
+ t = Time.now - 24*24*60
134
+ Time.utc t.year, t.month, t.day
135
+ end
136
+
137
+ def months_from_yesterday(num)
138
+ t = yesterday
139
+ date = Date.new t.year, t.month, t.day
140
+ date = date >> num # >> is months in the future operator
141
+ Time.utc date.year, date.month, date.day
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,71 @@
1
+ require 'yaml'
2
+
3
+ module LeapCA
4
+ module Config
5
+ extend self
6
+
7
+ attr_accessor :ca_key_path
8
+ attr_accessor :ca_key_password
9
+ attr_accessor :ca_cert_path
10
+
11
+ attr_accessor :max_pool_size
12
+ attr_accessor :client_cert_lifespan
13
+ attr_accessor :client_cert_bit_size
14
+ attr_accessor :client_cert_hash
15
+
16
+ attr_accessor :db_name
17
+ attr_accessor :couch_connection
18
+
19
+ def self.load(base_dir, *configs)
20
+ configs.each do |file_path|
21
+ file_path = find_file(base_dir, file_path)
22
+ next unless file_path
23
+ puts " * Loading configuration #{file_path}"
24
+ yml = YAML.load(File.read(file_path))
25
+ if yml
26
+ yml.each do |key, value|
27
+ begin
28
+ if value.is_a? Hash
29
+ value = symbolize_keys(value)
30
+ end
31
+ self.send("#{key}=", value)
32
+ rescue NoMethodError => exc
33
+ STDERR.puts "ERROR in file #{file}, '#{key}' is not a valid option"
34
+ exit(1)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ [:ca_key_path, :ca_cert_path].each do |attr|
40
+ path = self.send(attr) || ""
41
+ if path =~ /^\./
42
+ path = File.expand_path(path, base_dir)
43
+ self.send("#{attr}=", path)
44
+ end
45
+ unless File.exists?(path)
46
+ STDERR.puts "ERROR: The config option '#{attr}' is set to '#{path}', but the file does not exist!"
47
+ exit(1)
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def self.find_file(base_dir, file_path)
55
+ return nil unless file_path
56
+ if defined? CWD
57
+ return File.expand_path(file_path, CWD) if File.exists?(File.expand_path(file_path, CWD))
58
+ end
59
+ return File.expand_path(file_path, base_dir) if File.exists?(File.expand_path(file_path, base_dir))
60
+ return nil
61
+ end
62
+
63
+ def self.symbolize_keys(hsh)
64
+ newhsh = {}
65
+ hsh.keys.each do |key|
66
+ newhsh[key.to_sym] = hsh[key]
67
+ end
68
+ newhsh
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ module LeapCA
2
+ class CouchChanges
3
+ def initialize(stream)
4
+ @stream = stream
5
+ end
6
+
7
+ def last_seq
8
+ @stream.get "_changes", :limit => 1, :descending => true do |hash|
9
+ return hash[:last_seq]
10
+ end
11
+ end
12
+
13
+ def follow
14
+ @stream.get "_changes", :feed => :continuous, :since => last_seq do |hash|
15
+ yield(hash)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ require 'yajl/http_stream'
2
+
3
+ module LeapCA
4
+ class CouchStream
5
+ def initialize(database_url)
6
+ @database_url = database_url
7
+ end
8
+
9
+ def get(path, options)
10
+ url = url_for(path, options)
11
+ # puts url
12
+ Yajl::HttpStream.get(url, :symbolize_keys => true) do |hash|
13
+ yield(hash)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def url_for(path, options = {})
20
+ url = [@database_url, path].join('/')
21
+ url += '?' if options.any?
22
+ url += options.map {|k,v| "#{k}=#{v}"}.join('&')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ require 'yaml'
2
+
3
+ module LeapCA
4
+ class Pool
5
+ def initialize(config = {:size => 10})
6
+ @config = config
7
+ end
8
+
9
+ def fill
10
+ while Cert.count < self.size do
11
+ cert = Cert.create!
12
+ puts " * Created client certificate #{cert.id}"
13
+ end
14
+ end
15
+
16
+ def size
17
+ @config[:size]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ module LeapCA
2
+ VERSION = "0.2.0"
3
+ REQUIRE_PATHS = ['lib']
4
+ end
data/lib/leap_ca.rb ADDED
@@ -0,0 +1,26 @@
1
+ unless defined? BASE_DIR
2
+ BASE_DIR = File.expand_path('../..', __FILE__)
3
+ end
4
+ unless defined? LEAP_CA_CONFIG
5
+ LEAP_CA_CONFIG = '/etc/leap/leap_ca.yaml'
6
+ end
7
+
8
+ #
9
+ # Load Config
10
+ # this must come first, because CouchRest needs the connection defined before the models are defined.
11
+ #
12
+ require 'leap_ca/config'
13
+ LeapCA::Config.load(BASE_DIR, 'config/config_default.yaml', LEAP_CA_CONFIG, ARGV.grep(/\.ya?ml$/).first)
14
+
15
+ require 'couchrest_model'
16
+ CouchRest::Model::Base.configure do |config|
17
+ config.connection = LeapCA::Config.couch_connection
18
+ end
19
+
20
+ #
21
+ # Load LeapCA
22
+ #
23
+ require 'leap_ca/cert'
24
+ require 'leap_ca/couch_stream'
25
+ require 'leap_ca/couch_changes'
26
+ require 'leap_ca/pool'
@@ -0,0 +1,24 @@
1
+ #
2
+ # This file should not be required directly. Use it like so:
3
+ #
4
+ # Daemons.run('leap_ca_daemon.rb')
5
+ #
6
+
7
+ require 'leap_ca'
8
+
9
+ module LeapCA
10
+ puts " * Tracking #{Cert.database.root}"
11
+ couch = CouchStream.new(Cert.database.root)
12
+ changes = CouchChanges.new(couch)
13
+ pool = Pool.new(:size => Config.max_pool_size)
14
+
15
+ # fill the pool
16
+ pool.fill
17
+
18
+ # watch for deletions, fill the pool whenever it gets low
19
+ changes.follow do |hash|
20
+ if hash[:deleted]
21
+ pool.fill
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # testing configuration options
3
+ #
4
+
5
+ #
6
+ # Certificate Authority
7
+ #
8
+ ca_key_path: "./test/files/ca.key"
9
+ ca_key_password: ~
10
+ ca_cert_path: "./test/files/ca.crt"
11
+
12
+ #
13
+ # Certificate pool
14
+ #
15
+ max_pool_size: 4
16
+ client_cert_lifespan: 1
17
+ client_cert_bit_size: 1024
18
+ client_cert_hash: "SHA1"
19
+
20
+ db_name: "client_certificates_test"
data/test/files/ca.crt ADDED
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICPDCCAYmgAwIBAgIEUKCI4DANBgkqhkiG9w0BAQsFADAkMSIwIAYDVQQDExlS
3
+ b290IENBIGZvciBydW5uaW5nIHRlc3RzMB4XDTEyMTExMjA1MjgwMFoXDTEzMTEx
4
+ MjA1MjgwMFowJDEiMCAGA1UEAxMZUm9vdCBDQSBmb3IgcnVubmluZyB0ZXN0czCB
5
+ uzANBgkqhkiG9w0BAQEFAAOBqQAwgaUCgZ0ApeqCGQOmiHxCFxsfUKmBV6ruOYar
6
+ EsepFAycTmmakXBjNj4B9Pd3gE3Cc56rvkq0uxluRvqspzpEOQpCg8M5fkft/fxS
7
+ acw+ackj3ys7r0MrXgL66QeLnNGe8+RjBO8UHb3OPx547hqUHVg+3HqSCdn9cGQX
8
+ 9//EJrnSJsLuZw9ktkN4Ytyd1deZo6AkiIeCyz0HxKQBIhdJAPRlAgMBAAGjQzBB
9
+ MA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcEADAdBgNVHQ4EFgQUBe1l
10
+ BbuGErEkHLffGvkY5dDOH1YwDQYJKoZIhvcNAQELBQADgZ0ADpudncToYPS183w8
11
+ c68dObCCvNfv/FTBg4ihCLW6PapADYuvXmCvXgHflylET+rFdcrnUfl+XjNT5IjF
12
+ ImUyyOnCiy7scRgY+9qrEb7neH4CopGZKkWBTadZLu0QZqMcsWyAZBzaI8tBwL+G
13
+ +ylSgw3xTSf/HFjmTJAlDzUieV4DufrPqz7Yx0GrTswdJOcccc/PWUvQIU1GXvto
14
+ -----END CERTIFICATE-----
data/test/files/ca.key ADDED
@@ -0,0 +1,18 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIC2gIBAAKBnQCl6oIZA6aIfEIXGx9QqYFXqu45hqsSx6kUDJxOaZqRcGM2PgH0
3
+ 93eATcJznqu+SrS7GW5G+qynOkQ5CkKDwzl+R+39/FJpzD5pySPfKzuvQyteAvrp
4
+ B4uc0Z7z5GME7xQdvc4/HnjuGpQdWD7cepIJ2f1wZBf3/8QmudImwu5nD2S2Q3hi
5
+ 3J3V15mjoCSIh4LLPQfEpAEiF0kA9GUCAwEAAQKBnAKz9FSgqO42Sq6tBBtAolkh
6
+ nBSXK2L4mmTiOQr/UMOnzLtN0qMBWRK1Bu2dRcz+0zztEs0t45wsfdS0DxYDGy+s
7
+ elBrSOhs/w34IeZ5LM6xY0u4HZDmhn0pQNo6QZcFICr0GkkYdmWDlkLvIeJ/u6+q
8
+ nmyqAQXvj3R4nA7hrKUXzJjfvN3RYrhLN+/T41zLybeJ5vLZQK3jJSiIjQJPAMhS
9
+ HTIbYTUi2pxYVSwJDY4S2klTdroNGvTCkqcTRcB4Ms70FGLPZ6+ZumrkbSohHUsj
10
+ gDRRy3e4fjA9qMSQynVr2gkUobsR0tAdQGVOKwJPANQIUPaTc2ouNYNLAiHoAXoL
11
+ qAcF5g7/vtlMOwr+16EYoG7bLbiEie7nBfg9zz/VUnvOEy6pZ89YvsZOMlGicsRs
12
+ +tfUM1g/u0ZFEoQPrwJOC6bbE+ML0G9qj9WDfsA4DZ+DGujD6yZ//uSiax1v3TYg
13
+ nnEMDoNJ4KjscvM+dkjez1QNTP3E+/27OUsc2fIiFJplYEnW7m6m+Hv7FulpAk8A
14
+ tiASk0oiV/ErLARw53jmU9PRV378lqOcZgAxswclZo3FuJLxmc3WwOuV2B4Xd+gf
15
+ epKPLYR708GR1Lp0RGS6GfjWGi9+ju3nSbuo5OCnAk5yun/UvDdtnZ6fXo9aF22/
16
+ yoiztru7yhJdVrMx3PbbndfN2y9ctqcd6CD5fIQdyZ4K8eTr686RjH8C0XP095Ib
17
+ an3AO/TQG1c4yE2hSvQ=
18
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+
4
+ BASE_DIR = File.expand_path('../..', __FILE__)
5
+ $:.unshift File.expand_path('lib', BASE_DIR)
6
+
7
+ require 'mocha/setup'
8
+
9
+ LEAP_CA_CONFIG = "test/config/config.yaml"
10
+ require 'leap_ca'
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../../test_helper.rb', __FILE__)
2
+
3
+ class CertTest < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @cert = LeapCA::Cert.new
7
+ end
8
+
9
+ def test_generate
10
+ @cert.generate
11
+
12
+ assert @cert.cert, 'certificate should exist'
13
+ assert @cert.key, 'key should exist'
14
+
15
+ ca = OpenSSL::X509::Certificate.new(File.read(LeapCA::Config.ca_cert_path))
16
+ cert = OpenSSL::X509::Certificate.new(@cert.cert)
17
+ key = OpenSSL::PKey::RSA.new(@cert.key)
18
+
19
+ assert cert.verify(ca.public_key), "cert was not signed by CA"
20
+ assert_equal ca.subject.to_s, cert.issuer.to_s, 'issuer should match'
21
+ assert_equal "test", cert.public_key.public_decrypt(key.private_encrypt("test")), 'keypair should be able to encrypt/decrypt'
22
+ end
23
+
24
+ def test_validation_of_random
25
+ @cert.stubs(:set_random)
26
+ [1, nil, "asdf"].each do |invalid|
27
+ @cert.random = invalid
28
+ assert !@cert.valid?, "#{invalid} should not be a valid value for random"
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../../test_helper.rb', __FILE__)
2
+ require 'leap_ca/couch_changes'
3
+
4
+ class CouchChangesTest < MiniTest::Unit::TestCase
5
+
6
+ LAST_SEQ = 12
7
+
8
+ def setup
9
+ @stream = mock()
10
+ @changes = LeapCA::CouchChanges.new(@stream)
11
+ end
12
+
13
+ def test_last_seq
14
+ @stream.expects(:get).
15
+ with('_changes', {:limit => 1, :descending => true}).
16
+ yields(:last_seq => LAST_SEQ)
17
+ assert_equal LAST_SEQ, @changes.last_seq
18
+ end
19
+
20
+ def test_follow
21
+ stub_entry = {:new => :result}
22
+ @stream.expects(:get).
23
+ with('_changes', {:limit => 1, :descending => true}).
24
+ yields(:last_seq => LAST_SEQ)
25
+ @stream.expects(:get).
26
+ with('_changes', {:feed => :continuous, :since => LAST_SEQ}).
27
+ yields(stub_entry)
28
+ @changes.follow do |hash|
29
+ assert_equal stub_entry, hash
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path('../../test_helper.rb', __FILE__)
2
+ require 'leap_ca/couch_stream'
3
+
4
+ # we'll mock this
5
+ module Yajl
6
+ class HttpStream
7
+ end
8
+ end
9
+
10
+ class CouchStreamTest < MiniTest::Unit::TestCase
11
+
12
+ def setup
13
+ @root = "http://server/database"
14
+ @stream = LeapCA::CouchStream.new(@root)
15
+ @url = @root + "/_changes?a=b&c=d"
16
+ @path = "_changes"
17
+ @options = {:a => :b, :c => :d}
18
+ end
19
+
20
+ def test_get
21
+ Yajl::HttpStream.expects(:get).
22
+ with(@url, :symbolize_keys => true).
23
+ yields(stub_hash = stub)
24
+ @stream.get(@path, @options) do |hash|
25
+ assert_equal stub_hash, hash
26
+ end
27
+ end
28
+
29
+ # internal
30
+ def test_url_creation
31
+ assert_equal "http://server/database/", @stream.send(:url_for, "")
32
+ assert_equal @url, @stream.send(:url_for, @path, @options)
33
+ end
34
+
35
+ end
metadata ADDED
@@ -0,0 +1,217 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leap_ca
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Azul
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: couchrest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: couchrest_model
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.0.0.beta2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.0.0.beta2
46
+ - !ruby/object:Gem::Dependency
47
+ name: daemons
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: yajl-ruby
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: certificate_authority
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: minitest
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 3.2.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 3.2.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: mocha
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rake
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: highline
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ description: Provides the executable leap_ca, a deamon that refills a pool of x509
159
+ client certs stored in CouchDB.
160
+ email:
161
+ - azul@leap.se
162
+ executables:
163
+ - leap_ca_daemon
164
+ extensions: []
165
+ extra_rdoc_files: []
166
+ files:
167
+ - config/config_default.yaml
168
+ - lib/leap_ca/couch_stream.rb
169
+ - lib/leap_ca/version.rb
170
+ - lib/leap_ca/config.rb
171
+ - lib/leap_ca/cert.rb
172
+ - lib/leap_ca/pool.rb
173
+ - lib/leap_ca/couch_changes.rb
174
+ - lib/leap_ca_daemon.rb
175
+ - lib/leap_ca.rb
176
+ - bin/leap_ca_daemon
177
+ - Rakefile
178
+ - README.md
179
+ - test/test_helper.rb
180
+ - test/files/ca.crt
181
+ - test/files/ca.key
182
+ - test/unit/couch_stream_test.rb
183
+ - test/unit/couch_changes_test.rb
184
+ - test/unit/cert_test.rb
185
+ - test/config/config.yaml
186
+ homepage: https://leap.se
187
+ licenses: []
188
+ post_install_message:
189
+ rdoc_options: []
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ none: false
200
+ requirements:
201
+ - - ! '>='
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ requirements: []
205
+ rubyforge_project:
206
+ rubygems_version: 1.8.24
207
+ signing_key:
208
+ specification_version: 3
209
+ summary: Certificate Authority deamon for the LEAP Platform
210
+ test_files:
211
+ - test/test_helper.rb
212
+ - test/files/ca.crt
213
+ - test/files/ca.key
214
+ - test/unit/couch_stream_test.rb
215
+ - test/unit/couch_changes_test.rb
216
+ - test/unit/cert_test.rb
217
+ - test/config/config.yaml