blob: 17163655edf14bf0c14d00dceb05eaff170cdaeb (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
#
# Model for certificates
#
# 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(options = {})
cert = CertificateAuthority::Certificate.new
# set subject
if options[:prefix]
cert.subject.common_name = common_name_with_prefix(options[:prefix])
elsif options[:common_name]
cert.subject.common_name = options[:common_name]
else
raise ArgumentError.new
end
# set expiration
cert.not_before = last_month
cert.not_after = expiry
# 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
self.cert = cert
end
def to_s
self.key.to_pem + self.cert.to_pem
end
def fingerprint
OpenSSL::Digest::SHA1.hexdigest(openssl_cert.to_der).scan(/../).join(':')
end
def expiry
@expiry ||= lifespan
end
private
def openssl_cert
cert.openssl_body
end
def self.root_ca
@root_ca ||= begin
crt = File.read(APP_CONFIG[:client_ca_cert])
key = File.read(APP_CONFIG[:client_ca_key])
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
def common_name_with_prefix(prefix = nil)
[prefix, random_common_name].join
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
#
# We normalize timestamps at utc and midnight
# to reduce the fingerprinting possibilities.
#
def last_month
1.month.ago.utc.at_midnight
end
def lifespan
number, unit = APP_CONFIG[:client_cert_lifespan].split(' ')
unit ||= :months
Time.now.utc.at_midnight.advance(unit.to_sym => number.to_i)
end
end
|