summaryrefslogtreecommitdiff
path: root/certs/app/models
diff options
context:
space:
mode:
authorazul <azul@riseup.net>2013-01-31 10:26:33 -0800
committerazul <azul@riseup.net>2013-01-31 10:26:33 -0800
commit1bef299f60d742abb3a1b1db7fb6e6021f11ece2 (patch)
treeb449fea406e45de9e4cfd9462c4fc5cb01c74cee /certs/app/models
parentdac578781baf73a006cc78e29588dd1f6fdc0fd3 (diff)
parent8d9c2e90b77d417f9715c95de91c629e80ca6603 (diff)
Merge pull request #22 from leapcode/feature/merge_leap_ca
merge leap ca
Diffstat (limited to 'certs/app/models')
-rw-r--r--certs/app/models/client_certificate.rb105
-rw-r--r--certs/app/models/leap_ca/cert.rb56
2 files changed, 105 insertions, 56 deletions
diff --git a/certs/app/models/client_certificate.rb b/certs/app/models/client_certificate.rb
new file mode 100644
index 0000000..b2b8c0d
--- /dev/null
+++ b/certs/app/models/client_certificate.rb
@@ -0,0 +1,105 @@
+#
+# 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
+
+ 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 initialize
+ cert = CertificateAuthority::Certificate.new
+
+ # set subject
+ cert.subject.common_name = random_common_name
+
+ # set expiration
+ cert.not_before = yesterday
+ cert.not_after = months_from_yesterday(APP_CONFIG[:client_cert_lifespan])
+
+ # generate key
+ cert.serial_number.number = cert_serial_number
+ cert.key_material.generate_key(APP_CONFIG[:client_cert_bit_size])
+
+ # sign
+ cert.parent = ClientCertificate.root_ca
+ cert.sign! client_signing_profile
+
+ self.key = cert.key_material.private_key.to_pem
+ self.cert = cert.to_pem
+ end
+
+ private
+
+ def self.root_ca
+ @root_ca ||= begin
+ 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, APP_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" => APP_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 9d4f15e..0000000
--- a/certs/app/models/leap_ca/cert.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Model for certificates stored in CouchDB.
-#
-# This file must be loaded after Config has been loaded.
-#
-
-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 :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
-
- def set_random
- self.random = rand
- 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
-
- end
-end