diff options
author | elijah <elijah@riseup.net> | 2012-11-12 23:53:51 -0800 |
---|---|---|
committer | elijah <elijah@riseup.net> | 2012-11-12 23:53:51 -0800 |
commit | c37a35df81b2d6becc09f1820240db24c3ec632c (patch) | |
tree | 50187e4ab1face237760614ecf844b42efdd51e1 /lib/leap_ca/cert.rb | |
parent | c90d30621e042cc3e52ffc87e3491ab110a57e9e (diff) |
first fully working version of leap_ca
Diffstat (limited to 'lib/leap_ca/cert.rb')
-rw-r--r-- | lib/leap_ca/cert.rb | 131 |
1 files changed, 104 insertions, 27 deletions
diff --git a/lib/leap_ca/cert.rb b/lib/leap_ca/cert.rb index 1459e01..9e4d8ef 100644 --- a/lib/leap_ca/cert.rb +++ b/lib/leap_ca/cert.rb @@ -1,42 +1,119 @@ -require 'couchrest_model' +# +# Model for certificates stored in CouchDB. +# +# This file must be loaded after Config has been loaded. +# -class Cert < CouchRest::Model::Base +require 'base64' +require 'digest/md5' +require 'openssl' +require 'certificate_authority' +require 'date' - use_database 'certs' +module LeapCA + class Cert < CouchRest::Model::Base - timestamps! + use_database LeapCA::Config.db_name - property :random, Float, :accessible => false + timestamps! - before_validation :set_random, :attach_zip, :on => :create + 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 - validates :random, :presence => true, - :numericality => {:greater_than => 0, :less_than => 1} + validates :key, :presence => true + validates :cert, :presence => true + validates :random, :presence => true, :numericality => {:greater_than_or_equal_to => 0, :less_than => 1} - validates :zip_attachment, :presence => true + before_validation :generate, :set_random, :on => :create - design do - end + design do + end - def set_random - self.random = rand - end + # + # generate the private key and client certificate + # + def generate + cert = CertificateAuthority::Certificate.new - def attach_zip - file = File.open File.join(LEAP_CA_ROOT, "config", "cert") - self.create_attachment :file => file, :name => zipname - end + # set subject + cert.subject.common_name = random_common_name - def zipname - 'cert.txt' - end + # set expiration + self.valid_until = months_from_today(Config.client_cert_lifespan) + cert.not_before = today + cert.not_after = self.valid_until - def zip_attachment - attachments[zipname] - end + # generate key + cert.serial_number.number = cert_serial_number + cert.key_material.generate_key(Config.client_cert_bit_size) - def zipped - read_attachment(zipname) - end + # 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 today + t = Time.now + Time.utc t.year, t.month, t.day + end + + def months_from_today(num) + date = Date.today >> num # >> is months in the future operator + Time.utc date.year, date.month, date.day + end + + def client_signing_profile + { + "digest" => Config.client_cert_hash, + "extensions" => { + "keyUsage" => { + "usage" => ["digitalSignature", "keyAgreement"] + }, + "extendedKeyUsage" => { + "usage" => ["clientAuth"] + } + } + } + end + + end end |