From 77a51e1de520299afd2b33e7a3992aaafab1d6ae Mon Sep 17 00:00:00 2001 From: jessib Date: Thu, 24 Jan 2013 12:59:55 -0800 Subject: Rough start to merging leap_ca into webapp. --- certs/app/models/leap_ca/cert.rb | 104 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 5 deletions(-) (limited to 'certs/app') diff --git a/certs/app/models/leap_ca/cert.rb b/certs/app/models/leap_ca/cert.rb index 9d4f15e..6c59144 100644 --- a/certs/app/models/leap_ca/cert.rb +++ b/certs/app/models/leap_ca/cert.rb @@ -3,6 +3,11 @@ # # This file must be loaded after Config has been loaded. # +require 'base64' +require 'digest/md5' +require 'openssl' +require 'certificate_authority' +require 'date' module LeapCA class Cert < CouchRest::Model::Base @@ -17,7 +22,7 @@ module LeapCA property :valid_until, Time # expiration time of the client certificate property :random, Float, :accessible => false # used to help pick a random cert by the webapp - before_validation :set_random, :on => :create + before_validation :generate, :set_random, :on => :create validates :key, :presence => true validates :cert, :presence => true @@ -28,10 +33,6 @@ module LeapCA view :by_random end - def set_random - self.random = rand - end - class << self def sample self.by_random.startkey(rand).first || self.by_random.first @@ -52,5 +53,98 @@ module LeapCA end end + # + # generate the private key and client certificate + # + def generate + cert = CertificateAuthority::Certificate.new + + # set subject + cert.subject.common_name = random_common_name + + # set expiration + self.valid_until = months_from_yesterday(Config.client_cert_lifespan) + cert.not_before = yesterday + cert.not_after = self.valid_until + + # generate key + cert.serial_number.number = cert_serial_number + cert.key_material.generate_key(Config.client_cert_bit_size) + + # sign + cert.parent = Cert.root_ca + cert.sign! client_signing_profile + + self.key = cert.key_material.private_key.to_pem + self.cert = cert.to_pem + end + + private + + def set_random + self.random = rand + end + + def self.root_ca + @root_ca ||= begin + crt = File.read(Config.ca_cert_path) + key = File.read(Config.ca_key_path) + openssl_cert = OpenSSL::X509::Certificate.new(crt) + cert = CertificateAuthority::Certificate.from_openssl(openssl_cert) + cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, Config.ca_key_password) + cert + end + end + + # + # For cert serial numbers, we need a non-colliding number less than 160 bits. + # md5 will do nicely, since there is no need for a secure hash, just a short one. + # (md5 is 128 bits) + # + def cert_serial_number + Digest::MD5.hexdigest("#{rand(10**10)} -- #{Time.now}").to_i(16) + end + + # + # for the random common name, we need a text string that will be unique across all certs. + # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid + # + def random_common_name + cert_serial_number.to_s(36) + end + + def client_signing_profile + { + "digest" => Config.client_cert_hash, + "extensions" => { + "keyUsage" => { + "usage" => ["digitalSignature"] + }, + "extendedKeyUsage" => { + "usage" => ["clientAuth"] + } + } + } + end + + ## + ## TIME HELPERS + ## + ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet + ## are behind UTC. + ## + + def yesterday + t = Time.now - 24*24*60 + Time.utc t.year, t.month, t.day + end + + def months_from_yesterday(num) + t = yesterday + date = Date.new t.year, t.month, t.day + date = date >> num # >> is months in the future operator + Time.utc date.year, date.month, date.day + end + end end -- cgit v1.2.3 From 195224a989fa57e9a70fa10c0cdd6603295bb0dd Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 26 Jan 2013 10:52:36 +0100 Subject: removing the leap_ca namespacing from certs --- certs/app/models/client_certificate.rb | 148 ++++++++++++++++++++++++++++++++ certs/app/models/leap_ca/cert.rb | 150 --------------------------------- 2 files changed, 148 insertions(+), 150 deletions(-) create mode 100644 certs/app/models/client_certificate.rb delete mode 100644 certs/app/models/leap_ca/cert.rb (limited to 'certs/app') diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb new file mode 100644 index 0000000..23b66a2 --- /dev/null +++ b/certs/app/models/client_certificate.rb @@ -0,0 +1,148 @@ +# +# Model for certificates stored in CouchDB. +# +# This file must be loaded after Config has been loaded. +# +require 'base64' +require 'digest/md5' +require 'openssl' +require 'certificate_authority' +require 'date' + +class ClientCertificate < CouchRest::Model::Base + + # No config yet. use_database LeapCA::Config.db_name + use_database 'client_certificates' + + timestamps! + + property :key, String # the client private RSA key + property :cert, String # the client x509 certificate, signed by the CA + property :valid_until, Time # expiration time of the client certificate + property :random, Float, :accessible => false # used to help pick a random cert by the webapp + + before_validation :generate, :set_random, :on => :create + + validates :key, :presence => true + validates :cert, :presence => true + validates :random, :presence => true + validates :random, :numericality => {:greater_than => 0, :less_than => 1} + + design do + view :by_random + end + + class << self + def sample + self.by_random.startkey(rand).first || self.by_random.first + end + + def pick_from_pool + cert = self.sample + raise RECORD_NOT_FOUND unless cert + cert.destroy + return cert + rescue RESOURCE_NOT_FOUND + retry if self.by_random.count > 0 + raise RECORD_NOT_FOUND + end + + def valid_attributes_hash + {:key => "ABCD", :cert => "A123"} + end + end + + # + # generate the private key and client certificate + # + def generate + cert = CertificateAuthority::Certificate.new + + # set subject + cert.subject.common_name = random_common_name + + # set expiration + self.valid_until = months_from_yesterday(Config.client_cert_lifespan) + cert.not_before = yesterday + cert.not_after = self.valid_until + + # generate key + cert.serial_number.number = cert_serial_number + cert.key_material.generate_key(Config.client_cert_bit_size) + + # sign + cert.parent = Cert.root_ca + cert.sign! client_signing_profile + + self.key = cert.key_material.private_key.to_pem + self.cert = cert.to_pem + end + + private + + def set_random + self.random = rand + end + + def self.root_ca + @root_ca ||= begin + crt = File.read(Config.ca_cert_path) + key = File.read(Config.ca_key_path) + openssl_cert = OpenSSL::X509::Certificate.new(crt) + cert = CertificateAuthority::Certificate.from_openssl(openssl_cert) + cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, Config.ca_key_password) + cert + end + end + + # + # For cert serial numbers, we need a non-colliding number less than 160 bits. + # md5 will do nicely, since there is no need for a secure hash, just a short one. + # (md5 is 128 bits) + # + def cert_serial_number + Digest::MD5.hexdigest("#{rand(10**10)} -- #{Time.now}").to_i(16) + end + + # + # for the random common name, we need a text string that will be unique across all certs. + # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid + # + def random_common_name + cert_serial_number.to_s(36) + end + + def client_signing_profile + { + "digest" => Config.client_cert_hash, + "extensions" => { + "keyUsage" => { + "usage" => ["digitalSignature"] + }, + "extendedKeyUsage" => { + "usage" => ["clientAuth"] + } + } + } + end + + ## + ## TIME HELPERS + ## + ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet + ## are behind UTC. + ## + + def yesterday + t = Time.now - 24*24*60 + Time.utc t.year, t.month, t.day + end + + def months_from_yesterday(num) + t = yesterday + date = Date.new t.year, t.month, t.day + date = date >> num # >> is months in the future operator + Time.utc date.year, date.month, date.day + end + +end diff --git a/certs/app/models/leap_ca/cert.rb b/certs/app/models/leap_ca/cert.rb deleted file mode 100644 index 6c59144..0000000 --- a/certs/app/models/leap_ca/cert.rb +++ /dev/null @@ -1,150 +0,0 @@ -# -# Model for certificates stored in CouchDB. -# -# This file must be loaded after Config has been loaded. -# -require 'base64' -require 'digest/md5' -require 'openssl' -require 'certificate_authority' -require 'date' - -module LeapCA - class Cert < CouchRest::Model::Base - -# No config yet. use_database LeapCA::Config.db_name - use_database 'client_certificates' - - timestamps! - - property :key, String # the client private RSA key - property :cert, String # the client x509 certificate, signed by the CA - property :valid_until, Time # expiration time of the client certificate - property :random, Float, :accessible => false # used to help pick a random cert by the webapp - - before_validation :generate, :set_random, :on => :create - - validates :key, :presence => true - validates :cert, :presence => true - validates :random, :presence => true - validates :random, :numericality => {:greater_than => 0, :less_than => 1} - - design do - view :by_random - end - - class << self - def sample - self.by_random.startkey(rand).first || self.by_random.first - end - - def pick_from_pool - cert = self.sample - raise RECORD_NOT_FOUND unless cert - cert.destroy - return cert - rescue RESOURCE_NOT_FOUND - retry if self.by_random.count > 0 - raise RECORD_NOT_FOUND - end - - def valid_attributes_hash - {:key => "ABCD", :cert => "A123"} - end - end - - # - # generate the private key and client certificate - # - def generate - cert = CertificateAuthority::Certificate.new - - # set subject - cert.subject.common_name = random_common_name - - # set expiration - self.valid_until = months_from_yesterday(Config.client_cert_lifespan) - cert.not_before = yesterday - cert.not_after = self.valid_until - - # generate key - cert.serial_number.number = cert_serial_number - cert.key_material.generate_key(Config.client_cert_bit_size) - - # sign - cert.parent = Cert.root_ca - cert.sign! client_signing_profile - - self.key = cert.key_material.private_key.to_pem - self.cert = cert.to_pem - end - - private - - def set_random - self.random = rand - end - - def self.root_ca - @root_ca ||= begin - crt = File.read(Config.ca_cert_path) - key = File.read(Config.ca_key_path) - openssl_cert = OpenSSL::X509::Certificate.new(crt) - cert = CertificateAuthority::Certificate.from_openssl(openssl_cert) - cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, Config.ca_key_password) - cert - end - end - - # - # For cert serial numbers, we need a non-colliding number less than 160 bits. - # md5 will do nicely, since there is no need for a secure hash, just a short one. - # (md5 is 128 bits) - # - def cert_serial_number - Digest::MD5.hexdigest("#{rand(10**10)} -- #{Time.now}").to_i(16) - end - - # - # for the random common name, we need a text string that will be unique across all certs. - # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid - # - def random_common_name - cert_serial_number.to_s(36) - end - - def client_signing_profile - { - "digest" => Config.client_cert_hash, - "extensions" => { - "keyUsage" => { - "usage" => ["digitalSignature"] - }, - "extendedKeyUsage" => { - "usage" => ["clientAuth"] - } - } - } - end - - ## - ## TIME HELPERS - ## - ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet - ## are behind UTC. - ## - - def yesterday - t = Time.now - 24*24*60 - Time.utc t.year, t.month, t.day - end - - def months_from_yesterday(num) - t = yesterday - date = Date.new t.year, t.month, t.day - date = date >> num # >> is months in the future operator - Time.utc date.year, date.month, date.day - end - - end -end -- cgit v1.2.3 From 4c2abd107f5959ea0f15f052acf73440648d8d52 Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 26 Jan 2013 11:03:18 +0100 Subject: moving leap_ca configs into defaults.yml --- certs/app/models/client_certificate.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'certs/app') diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb index 23b66a2..0b1e43f 100644 --- a/certs/app/models/client_certificate.rb +++ b/certs/app/models/client_certificate.rb @@ -11,7 +11,6 @@ require 'date' class ClientCertificate < CouchRest::Model::Base - # No config yet. use_database LeapCA::Config.db_name use_database 'client_certificates' timestamps! @@ -62,16 +61,16 @@ class ClientCertificate < CouchRest::Model::Base cert.subject.common_name = random_common_name # set expiration - self.valid_until = months_from_yesterday(Config.client_cert_lifespan) + self.valid_until = months_from_yesterday(APP_CONFIG[:client_cert_lifespan]) cert.not_before = yesterday cert.not_after = self.valid_until # generate key cert.serial_number.number = cert_serial_number - cert.key_material.generate_key(Config.client_cert_bit_size) + cert.key_material.generate_key(APP_CONFIG[:client_cert_bit_size]) # sign - cert.parent = Cert.root_ca + cert.parent = ClientCertificate.root_ca cert.sign! client_signing_profile self.key = cert.key_material.private_key.to_pem @@ -86,11 +85,11 @@ class ClientCertificate < CouchRest::Model::Base def self.root_ca @root_ca ||= begin - crt = File.read(Config.ca_cert_path) - key = File.read(Config.ca_key_path) + crt = File.read(APP_CONFIG[:ca_cert_path]) + key = File.read(APP_CONFIG[:ca_key_path]) openssl_cert = OpenSSL::X509::Certificate.new(crt) cert = CertificateAuthority::Certificate.from_openssl(openssl_cert) - cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, Config.ca_key_password) + cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, APP_CONFIG[:ca_key_password]) cert end end @@ -114,7 +113,7 @@ class ClientCertificate < CouchRest::Model::Base def client_signing_profile { - "digest" => Config.client_cert_hash, + "digest" => APP_CONFIG[:client_cert_hash], "extensions" => { "keyUsage" => { "usage" => ["digitalSignature"] -- cgit v1.2.3 From 0975583e3c6ec9d2bf0269841073031537db1c37 Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 26 Jan 2013 11:08:05 +0100 Subject: we're not using a cert pool anymore - remove anything related --- certs/app/controllers/certs_controller.rb | 2 +- certs/app/models/client_certificate.rb | 24 +----------------------- 2 files changed, 2 insertions(+), 24 deletions(-) (limited to 'certs/app') diff --git a/certs/app/controllers/certs_controller.rb b/certs/app/controllers/certs_controller.rb index d81aea0..3ec2f68 100644 --- a/certs/app/controllers/certs_controller.rb +++ b/certs/app/controllers/certs_controller.rb @@ -4,7 +4,7 @@ class CertsController < ApplicationController # GET /cert def show - @cert = LeapCA::Cert.pick_from_pool + @cert = ClientCertificate.create render :text => @cert.key + @cert.cert, :content_type => 'text/plain' rescue RECORD_NOT_FOUND flash[:error] = t(:cert_pool_empty) diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb index 0b1e43f..6abc1ee 100644 --- a/certs/app/models/client_certificate.rb +++ b/certs/app/models/client_certificate.rb @@ -18,34 +18,16 @@ class ClientCertificate < CouchRest::Model::Base property :key, String # the client private RSA key property :cert, String # the client x509 certificate, signed by the CA property :valid_until, Time # expiration time of the client certificate - property :random, Float, :accessible => false # used to help pick a random cert by the webapp - before_validation :generate, :set_random, :on => :create + before_validation :generate, :on => :create validates :key, :presence => true validates :cert, :presence => true - validates :random, :presence => true - validates :random, :numericality => {:greater_than => 0, :less_than => 1} design do - view :by_random end class << self - def sample - self.by_random.startkey(rand).first || self.by_random.first - end - - def pick_from_pool - cert = self.sample - raise RECORD_NOT_FOUND unless cert - cert.destroy - return cert - rescue RESOURCE_NOT_FOUND - retry if self.by_random.count > 0 - raise RECORD_NOT_FOUND - end - def valid_attributes_hash {:key => "ABCD", :cert => "A123"} end @@ -79,10 +61,6 @@ class ClientCertificate < CouchRest::Model::Base private - def set_random - self.random = rand - end - def self.root_ca @root_ca ||= begin crt = File.read(APP_CONFIG[:ca_cert_path]) -- cgit v1.2.3 From 0f8efed9afa480174c77c89d4d9d4a40f99bddab Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 26 Jan 2013 11:13:24 +0100 Subject: adopting tests to the way certs work now. should pass. * We now generate cert and key on validate. * we don't expect the controller to pick from the pool anymore - just create instead --- certs/app/models/client_certificate.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'certs/app') diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb index 6abc1ee..b664ff0 100644 --- a/certs/app/models/client_certificate.rb +++ b/certs/app/models/client_certificate.rb @@ -11,8 +11,6 @@ require 'date' class ClientCertificate < CouchRest::Model::Base - use_database 'client_certificates' - timestamps! property :key, String # the client private RSA key -- cgit v1.2.3 From 8d9c2e90b77d417f9715c95de91c629e80ca6603 Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 26 Jan 2013 11:23:43 +0100 Subject: no need to store the cert anymore - just new initialize and send it --- certs/app/controllers/certs_controller.rb | 5 +---- certs/app/models/client_certificate.rb | 28 +++++----------------------- 2 files changed, 6 insertions(+), 27 deletions(-) (limited to 'certs/app') diff --git a/certs/app/controllers/certs_controller.rb b/certs/app/controllers/certs_controller.rb index 3ec2f68..6db270c 100644 --- a/certs/app/controllers/certs_controller.rb +++ b/certs/app/controllers/certs_controller.rb @@ -4,11 +4,8 @@ class CertsController < ApplicationController # GET /cert def show - @cert = ClientCertificate.create + @cert = ClientCertificate.new render :text => @cert.key + @cert.cert, :content_type => 'text/plain' - rescue RECORD_NOT_FOUND - flash[:error] = t(:cert_pool_empty) - redirect_to root_path end end diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb index b664ff0..b2b8c0d 100644 --- a/certs/app/models/client_certificate.rb +++ b/certs/app/models/client_certificate.rb @@ -9,41 +9,23 @@ require 'openssl' require 'certificate_authority' require 'date' -class ClientCertificate < CouchRest::Model::Base +class ClientCertificate - timestamps! - - property :key, String # the client private RSA key - property :cert, String # the client x509 certificate, signed by the CA - property :valid_until, Time # expiration time of the client certificate - - before_validation :generate, :on => :create - - validates :key, :presence => true - validates :cert, :presence => true - - design do - end - - class << self - def valid_attributes_hash - {:key => "ABCD", :cert => "A123"} - end - end + attr_accessor :key # the client private RSA key + attr_accessor :cert # the client x509 certificate, signed by the CA # # generate the private key and client certificate # - def generate + def initialize cert = CertificateAuthority::Certificate.new # set subject cert.subject.common_name = random_common_name # set expiration - self.valid_until = months_from_yesterday(APP_CONFIG[:client_cert_lifespan]) cert.not_before = yesterday - cert.not_after = self.valid_until + cert.not_after = months_from_yesterday(APP_CONFIG[:client_cert_lifespan]) # generate key cert.serial_number.number = cert_serial_number -- cgit v1.2.3