omf_common 6.0.7.1 → 6.0.8.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 65fd04c1481079b28cd3b353559c88893704fa25
4
- data.tar.gz: 98e1f8789175aaf85869f463b3add3201959a18e
5
- SHA512:
6
- metadata.gz: 477b8b778f5ddf8284d55cefff962dd3a3efec8cdfe705444ef69790d4e56cde08b6d8e7087339c7c2574c8056291520ee47e3e970b37bc756669f94435c0712
7
- data.tar.gz: 54d3f6a16aeff8e32a5eed85d6400af3ef9811a2db70bc562b3f4a7729376b1aabfe0c5481264629e920b331cc16b08d98fe7c4e3f7aeafeb5f1e76c7a91cdc9
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWJjMTBiYThjZmUxMWJjZDIyMmFiOTU2NDViZjQyMTM0MmQ4OTIwZA==
5
+ data.tar.gz: !binary |-
6
+ NTBjNDYwYzk3NzRlNjFhMTBlYjEzYTZmYjljZjUzZTM0NDVlOTc2ZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MjJkNzIxN2NjZDMzOTcwZTVlNTg3YWZhY2I0NWEyYmI4MzEwMTRmNzg1ZDRh
10
+ M2RhY2RlZGZiNDkwZWI5MDAyMGExY2RiNTA3ZWUwMjg5ZmE2ODExYmIwOWVl
11
+ N2I2MDIzOTE0ZGE5NzBlODJjZTU0Yzc5YmFkMGI2MTY4NzM5NjE=
12
+ data.tar.gz: !binary |-
13
+ MzliNDQ5MTU3ZWJkZjg0Mzg5N2FiMmFhN2Y1ODlkZjFiYzY1YzQ3YzZiNGVi
14
+ MjlmNmZmNjEzNjc0M2RhYzZiZGU1NWM4NjY4YzQ3ZTMzMjg1MjE5OTYyNmQ4
15
+ YmNiYTU1NTI2ZTgzZTEwYzhjM2FkNjA2ZmU1NzRkMzM4OTYyZjY=
data/bin/omf_cert.rb ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env ruby
2
+ BIN_DIR = File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
3
+ TOP_DIR = File.join(BIN_DIR, '..')
4
+ $: << File.join(TOP_DIR, 'lib')
5
+
6
+ require 'json/jwt' # to get around dependencies with activesupport
7
+
8
+ DESCR = %{
9
+ Program to create, read, and manipulate X509 certificates used in OMF.
10
+ create_root ...... Create a root certificate
11
+ create_user ...... Create user certificate (requires: --email, --user)
12
+ create_resource .. Create a certificate for a resource (requires: --resource_type)
13
+ describe ......... Print out some of the key properties found in the cert
14
+ }
15
+
16
+
17
+ require 'omf_common'
18
+ include OmfCommon::Auth
19
+
20
+ DEF_SUBJECT_PREFIX = Certificate.default_domain('US', 'CA', 'ACME', 'Roadrunner')
21
+
22
+ OPTS = {
23
+ duration: Certificate::DEF_DURATION
24
+ }
25
+
26
+ op = OP = OptionParser.new
27
+ op.banner = "\nUsage: #{op.program_name} [options] cmd \n#{DESCR}\n"
28
+ op.on '-o', '--out FILE', "Write result into FILE [STDOUT]" do |file|
29
+ OPTS[:out] = file
30
+ end
31
+ op.on '-i', '--in FILE', "Read certificate from FILE [STDIN]" do |file|
32
+ OPTS[:in] = file
33
+ end
34
+ op.on '--email EMAIL', "Email to add to cert" do |email|
35
+ OPTS[:email] = email
36
+ end
37
+ op.on '--cn CN', "Common name to use. Will be appended to '#{DEF_SUBJECT_PREFIX}'" do |cn|
38
+ OPTS[:cn] = cn
39
+ end
40
+ op.on '--subj SUBJECT', "Subject to use in cert [#{DEF_SUBJECT_PREFIX}/CN=dummy]" do |subject|
41
+ OPTS[:subject] = subject
42
+ end
43
+ op.on '--user USER_NAME', "User name for user certs" do |user|
44
+ OPTS[:user] = user
45
+ end
46
+ op.on '--resource-type TYPE', "Type of resource to create cert for" do |type|
47
+ OPTS[:resource_type] = type
48
+ end
49
+ op.on '--resource-id ID', "ID for resource" do |id|
50
+ OPTS[:resource_id] = id
51
+ end
52
+ op.on '--duration SEC', "Duration the cert will be valid for [#{OPTS[:duration]}]" do |secs|
53
+ OPTS[:duration] = secs
54
+ end
55
+ op.on '--domain C:ST:O:OU', "Domain to us (components are ':' separated) [#{DEF_SUBJECT_PREFIX}]" do |domain|
56
+ unless (p = domain.split(':')).length == 4
57
+ $stderr.puts "ERROR: Domain needs to contain 4 parts separated by ':'\n"
58
+ exit(-1)
59
+ end
60
+ c, st, o, ou = p
61
+ Certificate.default_domain(c, st, o, ou)
62
+ end
63
+
64
+ op.on_tail('-v', "--verbose", "Print summary of created cert (Surpressed when writing cert to stdout)") do
65
+ OPTS[:verbose] = true
66
+ end
67
+ op.on_tail('-h', "--help", "Show this message") { $stderr.puts op; exit }
68
+ rest = op.parse(ARGV) || []
69
+ OPTS[:verbose] = false unless OPTS[:out]
70
+
71
+ if rest.length != 1
72
+ $stderr.puts "ERROR: Can't figure out what is being requested\n"
73
+ $stderr.puts op; exit
74
+ end
75
+
76
+ CertificateStore.init()
77
+
78
+ def write(content)
79
+ if (fname = OPTS[:out]) && fname != '-'
80
+ File.open(fname, 'w') {|f| f.puts content}
81
+ else
82
+ puts content
83
+ end
84
+ end
85
+
86
+ def write_cert(cert)
87
+ write cert.to_pem_with_key
88
+ describe_cert(cert) if OPTS[:verbose]
89
+ end
90
+
91
+ def require_opts(*names)
92
+ fails = false
93
+ names.each do |n|
94
+ unless OPTS[n]
95
+ $stderr.puts "ERROR: Missing option '--#{n}'\n"
96
+ fails = true
97
+ end
98
+ end
99
+ exit if fails
100
+ end
101
+
102
+ def describe_cert(cert = nil)
103
+ unless cert
104
+ if cert_file = OPTS[:in]
105
+ if File.readable?(cert_file)
106
+ pem = File.read(cert_file)
107
+ else
108
+ $stderr.puts "ERROR: Can't open file '#{cert_file}' for reading\n"
109
+ exit
110
+ end
111
+ else
112
+ pem = $stdin.read
113
+ end
114
+ cert = Certificate.create_from_pem(pem)
115
+ end
116
+ cert.describe.each do |k, v|
117
+ puts "#{k}:#{' ' * (15 - k.length)} #{v.inspect}"
118
+ end
119
+ end
120
+
121
+ case cmd = rest[0]
122
+ when /^cre.*_root/
123
+ require_opts(:email)
124
+ cert = Certificate.create_root(OPTS)
125
+ write_cert cert
126
+
127
+ when /^cre.*_user/
128
+ root = Certificate.create_root()
129
+ require_opts(:user, :email)
130
+ cert = root.create_for_user(OPTS[:user], OPTS)
131
+ write_cert cert
132
+
133
+ when /^cre.*_resource/
134
+ root = Certificate.create_root()
135
+ require_opts(:resource_type)
136
+ r_id = OPTS.delete(:resource_id)
137
+ r_type = OPTS.delete(:resource_type)
138
+ cert = root.create_for_resource(r_id, r_type, OPTS)
139
+ write_cert cert
140
+
141
+ when /^des.*/ # describe
142
+ describe_cert
143
+ else
144
+ $stderr.puts "ERROR: Unknown cmd '#{cmd}'\n"
145
+ $stderr.puts op; exit
146
+ end
147
+
148
+ exit
149
+
150
+ # unless resource_url || resource_type
151
+ # $stderr.puts 'Missing --resource-url --type or'
152
+ # $stderr.puts op
153
+ # exit(-1)
154
+ # end
155
+
156
+ adam = root.create_for_user('adam')
157
+ projectA = root.create_for_resource('projectA', :project)
158
+ #puts projectA.to_pem
159
+
160
+ # require 'json/jwt'
161
+ # msg = {cnt: "shit", iss: projectA}
162
+ # p = JSON::JWT.new(msg).sign(projectA.key , :RS256).to_s
163
+
164
+ require 'omf_common/auth/jwt_authenticator'
165
+
166
+ #puts projectA.addresses_raw
167
+
168
+
169
+ p = OmfCommon::Auth::JWTAuthenticator.sign('shit', projectA)
170
+ pn = (p.length / 80 + 1).times.map {|i| p[i * 80, 80]}.join("\n")
171
+ puts pn
172
+
173
+ puts pn.split.join == p
174
+
175
+ puts OmfCommon::Auth::JWTAuthenticator.parse(pn).inspect
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This stand alone program is exercising a few authorization scenarios.
4
+ #
5
+ # Usage: ruby -I .
6
+
7
+ EX_DIR = File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
8
+ TOP_DIR = File.join(EX_DIR, '..')
9
+ $: << File.join(TOP_DIR, 'lib')
10
+
11
+ begin; require 'json/jwt'; rescue Exception; end
12
+ require 'omf_common'
13
+ require 'pry'
14
+
15
+ OP_MODE = :development
16
+
17
+ opts = {
18
+ communication: {
19
+ url: 'xmpp://srv.mytestbed.net',
20
+ auth: {
21
+ authenticate: true,
22
+ pdp: {
23
+ constructor: 'TestPDP',
24
+ trust: ['adam']
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ # Implements a simple PDP which accepts any message from a set of trusted issuers.
31
+ #
32
+ class TestPDP
33
+
34
+ def initialize(opts = {})
35
+ @trust = opts[:trust] || []
36
+ puts "AUTH INIT>>> #{opts}"
37
+ end
38
+
39
+ def authorize(msg, &block)
40
+ info msg.to_s
41
+ iss = msg.issuer.resource_id
42
+ if @trust.include? iss
43
+ puts "AUTH(#{iss}) >>> PASS"
44
+ msg
45
+ else
46
+ puts "AUTH(#{iss}) >>> FAILED"
47
+ end
48
+ end
49
+ end
50
+
51
+ def doit(comm, el)
52
+ init_auth_store(comm)
53
+ comm.subscribe(:test) do |topic|
54
+ topic.on_message do |msg|
55
+ puts "MSG>> #{msg}"
56
+ end
57
+
58
+ topic.configure({foo: 1}, {issuer: 'adam'})
59
+ el.after(1) { topic.configure({foo: 2}, {issuer: 'eve'}) }
60
+ end
61
+ end
62
+
63
+ def init_auth_store(comm)
64
+ root_ca = OmfCommon::Auth::Certificate.create_root
65
+
66
+ root_ca.create_for_resource 'adam', :requester
67
+ root_ca.create_for_resource 'eve', :requester
68
+ OmfCommon::Auth::CertificateStore.instance.pry
69
+ end
70
+
71
+ OmfCommon.init(:development, opts) do |el|
72
+ OmfCommon.comm.on_connected do |comm|
73
+ doit(comm, el)
74
+ end
75
+ el.after(3) do puts "AFTER" end
76
+ end
data/lib/omf_common.rb CHANGED
@@ -142,6 +142,8 @@ module OmfCommon
142
142
  # @param [Hash] opts
143
143
  #
144
144
  def self.init(op_mode, opts = {}, &block)
145
+ opts = _rec_sym_keys(opts)
146
+
145
147
  if op_mode && defs = DEFAULTS[op_mode.to_sym]
146
148
  opts = _rec_merge(defs, opts)
147
149
  end
@@ -157,6 +159,7 @@ module OmfCommon
157
159
  if lopts = opts[:logging]
158
160
  _init_logging(lopts) unless lopts.empty?
159
161
  end
162
+
160
163
  unless copts = opts[:communication]
161
164
  raise "Missing :communication description"
162
165
  end
@@ -294,7 +297,7 @@ module OmfCommon
294
297
  end
295
298
  end
296
299
 
297
- # Recusively Symbolize keys of hash
300
+ # Recursively Symbolize keys of hash
298
301
  #
299
302
  def self._rec_sym_keys(hash)
300
303
  h = {}
@@ -325,4 +328,12 @@ module OmfCommon
325
328
  end
326
329
  end
327
330
  end
331
+
332
+ def self.load_credentials(opts)
333
+ unless opts.nil?
334
+ OmfCommon::Auth::CertificateStore.instance.register_default_certs(File.expand_path(opts[:root_cert_dir]))
335
+ cert_and_priv_key = File.read(File.expand_path(opts[:entity_cert])) << "\n" << File.read(File.expand_path(opts[:entity_key]))
336
+ OmfCommon::Auth::Certificate.create_from_pem(cert_and_priv_key)
337
+ end
338
+ end
328
339
  end
@@ -6,50 +6,141 @@
6
6
  require 'openssl'
7
7
  require 'omf_common/auth'
8
8
  require 'omf_common/auth/ssh_pub_key_convert'
9
+ require 'uuidtools'
9
10
 
10
11
  module OmfCommon::Auth
11
12
 
13
+ class CertificateNoLongerValidException < AuthException; end
14
+
12
15
  class Certificate
13
16
  DEF_DOMAIN_NAME = 'acme'
14
17
  DEF_DURATION = 3600
15
18
 
16
19
  BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"
17
20
  END_CERT = "\n-----END CERTIFICATE-----\n"
18
- @@serial = 0
21
+ BEGIN_KEY = "-----BEGIN RSA PRIVATE KEY-----\n"
22
+ END_KEY = "\n-----END RSA PRIVATE KEY-----\n"
19
23
 
20
- # @param [String] name unique name of the entity (resource name)
21
- # @param [String] type type of the entity (resource type)
22
- # @param [String] domain of the resource
24
+ @@def_x509_name_prefix = [['C', 'US'], ['ST', 'CA'], ['O', 'ACME'], ['OU', 'Roadrunner']]
25
+ @@def_email_domain = 'acme.org'
23
26
  #
24
- def self.create(address, name, type, domain = DEF_DOMAIN_NAME, issuer = nil, not_before = Time.now, duration = 3600, key = nil)
25
- subject = _create_name(name, type, domain)
26
- if key.nil?
27
- key, digest = _create_key()
28
- else
27
+ def self.default_domain(country, state, organisation, org_unit)
28
+ @@def_x509_name_prefix = [
29
+ ['C', c = country.upcase],
30
+ ['ST', st = state.upcase],
31
+ ['O', o = organisation],
32
+ ['OU', ou = org_unit]
33
+ ]
34
+ "/C=#{c}/ST=#{st}/O=#{o}/OU=#{ou}"
35
+ end
36
+
37
+ def self.default_email_domain(email_domain)
38
+ @@def_email_domain = email_domain
39
+ end
40
+
41
+ # @param [String] resource_id unique id of the resource entity
42
+ # @param [String] resource_type type of the resource entity
43
+ # @param [Certificate] Issuer
44
+ # @param [Hash] options
45
+ # @option [Time] :not_before Time the cert will be valid from [now]
46
+ # @option [int] :duration Time in seconds this cert is valid for [3600]
47
+ # @option [OpenSSL::PKey::RSA] :key Key to encode in cert. If not given, will create a new one
48
+ # @option [String] :user_id ID (should be UUID) for user [UUID(email)]
49
+ # @option [String] :email Email to identify user. If not give, user 'name' and @@def_email_domain
50
+ # @option [String] :geni_uri
51
+ # @option [String] :frcp_uri
52
+ # @option [String] :frcp_domain
53
+ # @option [String] :http_uri
54
+ # @option [String] :http_prefix
55
+ #
56
+ def self.create_for_resource(resource_id, resource_type, issuer, opts = {})
57
+ xname = @@def_x509_name_prefix.dup
58
+ xname << ['CN', opts[:cn] || resource_id]
59
+ subject = OpenSSL::X509::Name.new(xname)
60
+
61
+ if key = opts[:key]
29
62
  digest = _create_digest
63
+ else
64
+ key, digest = _create_key()
30
65
  end
31
66
 
32
- c = _create_x509_cert(address, subject, key, digest, issuer, not_before, duration)
33
- c[:address] = address if address
67
+ addresses = opts[:addresses] || []
68
+ addresses << "URI:uuid:#{opts[:resource_uuid]}" if opts[:resource_uuid]
69
+ email_domain = opts[:email] ? opts[:email].split('@')[1] : @@def_email_domain
70
+ addresses << (opts[:geni_uri] || "URI:urn:publicid:IDN+#{email_domain}+#{resource_type}+#{resource_id}")
71
+ if frcp_uri = opts[:frcp_uri]
72
+ unless frcp_uri.to_s.start_with? 'URI'
73
+ frcp_uri = "URI:frcp:#{frcp_uri}"
74
+ end
75
+ addresses << frcp_uri
76
+ end
77
+ # opts[:frcp_uri] || "URI:frcp:#{user_id}@#{opts[:frcp_domain] || @@def_email_domain}",
78
+ # opts[:http_uri] || "URI:http://#{opts[:http_prefix] || @@def_email_domain}/users/#{user_id}"
79
+ not_before = opts[:not_before] || Time.now
80
+ duration = opts[:duration] = 3600
81
+ c = _create_x509_cert(subject, key, digest, issuer, not_before, duration, addresses)
82
+ c[:addresses] = addresses
83
+ c[:resource_id] = resource_id
84
+ c[:subject] = subject
34
85
  self.new c
35
86
  end
36
87
 
37
- # @param [String] pem is the content of existing x509 cert
38
- # @param [OpenSSL::PKey::RSA|String] key is the private key which can be attached to the instance for signing.
39
- def self.create_from_x509(pem, key = nil)
88
+ def self.create_root(opts = {})
89
+ email = opts[:email] ||= "sa@#{@@def_email_domain}"
90
+ opts = {
91
+ addresses: [
92
+ "email:#{email}"
93
+ ]
94
+ }.merge(opts)
95
+ cert = create_for_resource('sa', :authority, nil, opts)
96
+ CertificateStore.instance.register_trusted(cert)
97
+ cert
98
+ end
99
+
100
+ # Return a newly create certificate with properties token from
101
+ # 'pem' encoded string.
102
+ #
103
+ # @param [String] pem is the PEM encoded content of existing x509 cert
104
+ # @return [Certificate] Certificate object
105
+ #
106
+ def self.create_from_pem(pem_s)
107
+ state = :seeking
108
+ cert_pem = []
109
+ key_pem = []
110
+ end_regexp = /^-*END/
111
+ pem_s.each_line do |line|
112
+ state = :seeking if line.match(end_regexp)
113
+ case state
114
+ when :seeking
115
+ case line
116
+ when /^-*BEGIN CERTIFICATE/
117
+ state = :cert
118
+ when /^-*BEGIN RSA PRIVATE KEY/
119
+ state = :key
120
+ end
121
+ when :cert
122
+ cert_pem << line
123
+ when :key
124
+ key_pem << line
125
+ else
126
+ raise "BUG: Unknown state '#{state}'"
127
+ end
128
+ end
40
129
  # Some command list generated cert can use \r\n as newline char
41
- unless pem =~ /^-----BEGIN CERTIFICATE-----/
42
- pem = "#{BEGIN_CERT}#{pem}#{END_CERT}"
130
+ cert_pem = cert_pem.join()
131
+ unless cert_pem =~ /^-----BEGIN CERTIFICATE-----/
132
+ cert_pem = "#{BEGIN_CERT}#{cert_pem.chomp}#{END_CERT}"
43
133
  end
44
-
45
- cert = OpenSSL::X509::Certificate.new(pem)
46
-
47
- key = OpenSSL::PKey::RSA.new(key) if key && key.is_a?(String)
48
-
49
- if key && !cert.check_private_key(key)
50
- raise ArgumentError, "Private key provided could not match the public key of given certificate"
134
+ opts = {}
135
+ opts[:cert] = OpenSSL::X509::Certificate.new(cert_pem)
136
+ if key_pem.size > 0
137
+ key_pem = key_pem.join()
138
+ unless key_pem =~ /^-----BEGIN RSA PRIVATE KEY-----/
139
+ key_pem = "#{BEGIN_KEY}#{key_pem.chomp}#{END_KEY}"
140
+ end
141
+ opts[:key] = OpenSSL::PKey::RSA.new(key_pem)
51
142
  end
52
- self.new({ cert: cert, key: key })
143
+ self.new(opts)
53
144
  end
54
145
 
55
146
  # Returns an array with a new RSA key and a SHA1 digest
@@ -62,13 +153,6 @@ module OmfCommon::Auth
62
153
  OpenSSL::Digest::SHA1.new
63
154
  end
64
155
 
65
- # @param [String] name unique name of the entity (resource name)
66
- # @param [String] type type of the entity (resource type)
67
- #
68
- def self._create_name(name, type, domain = DEF_DOMAIN_NAME)
69
- OpenSSL::X509::Name.new [['CN', "frcp//#{domain}//frcp.#{type}.#{name}"]], {}
70
- end
71
-
72
156
  # Create a X509 certificate
73
157
  #
74
158
  # @param [String] address
@@ -76,96 +160,154 @@ module OmfCommon::Auth
76
160
  # @param [OpenSSL::PKey::RSA] key
77
161
  # @return {cert, key}
78
162
  #
79
- def self._create_x509_cert(address, subject, key, digest = nil,
80
- issuer = nil, not_before = Time.now, duration = DEF_DURATION, extensions = [])
81
- extensions << ["subjectAltName", "URI:#{address}", false] if address
163
+ def self._create_x509_cert(subject, key, digest = nil,
164
+ issuer = nil, not_before = Time.now, duration = DEF_DURATION, addresses = [])
165
+
166
+ if key.nil?
167
+ key, digest = _create_key()
168
+ else
169
+ digest = _create_digest
170
+ end
82
171
 
83
172
  cert = OpenSSL::X509::Certificate.new
84
173
  cert.version = 2
85
174
  # TODO change serial to non-sequential secure random numbers for production use
86
- cert.serial = (@@serial += 1)
175
+ cert.serial = UUIDTools::UUID.random_create.to_i
87
176
  cert.subject = subject
88
177
  cert.public_key = key.public_key
89
178
  cert.not_before = not_before
90
179
  cert.not_after = not_before + duration
91
- unless extensions.empty?
92
- issuer_cert = issuer ? issuer.to_x509 : cert
93
- ef = OpenSSL::X509::ExtensionFactory.new
94
- ef.subject_certificate = cert
95
- ef.issuer_certificate = issuer_cert
96
- extensions.each{|oid, value, critical|
97
- cert.add_extension(ef.create_extension(oid, value, critical))
98
- }
180
+ #extensions << ["subjectAltName", "URI:http://foo.com/users/dc766130, URI:frcp:dc766130-c822-11e0-901e-000c29f89f7b@foo.com", false]
181
+
182
+ issuer_cert = issuer ? issuer.to_x509 : cert
183
+ ef = OpenSSL::X509::ExtensionFactory.new
184
+ ef.subject_certificate = cert
185
+ ef.issuer_certificate = issuer_cert
186
+ unless addresses.empty?
187
+ cert.add_extension(ef.create_extension("subjectAltName", addresses.join(','), false))
99
188
  end
189
+
100
190
  if issuer
101
191
  cert.issuer = issuer.subject
102
192
  cert.sign(issuer.key, issuer.digest)
103
193
  else
104
194
  # self signed
105
195
  cert.issuer = subject
196
+
197
+ # Not exactly sure if that's the right extensions to add. Copied from
198
+ # http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/X509/Certificate.html
199
+ cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
200
+ cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
201
+ cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
202
+ cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
203
+
204
+ # Signing the cert should be ABSOLUTELY the last step
106
205
  cert.sign(key, digest)
107
206
  end
108
207
  { cert: cert, key: key }
109
208
  end
110
209
 
111
- attr_reader :address, :subject, :key, :digest
210
+ attr_reader :addresses, :resource_id # :addresses_raw, :addresses_string
211
+ attr_reader :subject, :key, :digest
212
+ attr_writer :resource_id
112
213
 
113
214
  def initialize(opts)
114
215
  if @cert = opts[:cert]
115
216
  @subject = @cert.subject
116
217
  end
117
- unless @address = opts[:address]
118
- # try to see it it is in cert
119
- if @cert
120
- @cert.extensions.each do |ext|
121
- if ext.oid == 'subjectAltName'
122
- @address = ext.value[4 .. -1] # strip off 'URI:'
123
- end
124
- end
125
- end
126
- end
127
- if @key = opts[:key]
128
- @digest = opts[:digest] || OpenSSL::Digest::SHA1.new
129
- end
218
+ @resource_id = opts[:resource_id]
219
+ _extract_addresses(@cert)
130
220
  unless @subject ||= opts[:subject]
131
221
  name = opts[:name]
132
222
  type = opts[:type]
133
223
  domain = opts[:domain]
134
224
  @subject = _create_name(name, type, domain)
135
225
  end
136
- @cert ||= _create_x509_cert(@address, @subject, @key, @digest)[:cert]
226
+ if key = opts[:key]
227
+ @digest = opts[:digest] || self.class._create_digest
228
+ end
229
+ if @cert
230
+ self.key = key if key # this verifies that key is the right one for this cert
231
+ else
232
+ #@cert ||= _create_x509_cert(@address, @subject, @key, @digest)[:cert]
233
+ @cert = self.class._create_x509_cert(@subject, key, @digest)[:cert]
234
+ @key = key
235
+ end
137
236
  end
138
237
 
139
- # @param [OpenSSL::PKey::RSA|String] key is most likely the public key of the resource.
140
- #
141
- def create_for(address, name, type, domain = DEF_DOMAIN_NAME, duration = 3600, key = nil)
142
- raise ArgumentError, "Address required" unless address
238
+ def valid?
239
+ now = Time.new
240
+ (@cert.not_before <= now && now <= @cert.not_after)
241
+ end
242
+
243
+ def cert_expired?
244
+ debug "Certificate expired!" unless valid?
245
+ !valid?
246
+ end
143
247
 
144
- begin
145
- key = OpenSSL::PKey::RSA.new(key) if key && key.is_a?(String)
146
- rescue OpenSSL::PKey::RSAError
147
- # It might be a SSH pub key, try that
148
- key = OmfCommon::Auth::SSHPubKeyConvert.convert(key)
248
+ def key=(key)
249
+ if @cert && !@cert.check_private_key(key)
250
+ raise ArgumentError, "Private key provided could not match the public key of given certificate"
149
251
  end
252
+ @key = key
253
+ end
150
254
 
151
- cert = self.class.create(address, name, type, domain, self, Time.now, duration, key)
152
- CertificateStore.instance.register(cert, address)
255
+ def create_for_resource(resource_id, resource_type, opts = {})
256
+ unless valid?
257
+ raise CertificateNoLongerValidException.new
258
+ end
259
+ resource_id ||= UUIDTools::UUID.random_create()
260
+ unless opts[:resource_uuid]
261
+ if resource_id.is_a? UUIDTools::UUID
262
+ opts[:resource_uuid] = resource_id
263
+ else
264
+ opts[:resource_uuid] = UUIDTools::UUID.random_create()
265
+ end
266
+ end
267
+ unless opts[:cn]
268
+ opts[:cn] = "#{resource_id}/type=#{resource_type}"
269
+ (opts[:cn] += "/uuid=#{opts[:resource_uuid]}") unless resource_id.is_a? UUIDTools::UUID
270
+ end
271
+ cert = self.class.create_for_resource(resource_id, resource_type, self, opts)
272
+ CertificateStore.instance.register(cert)
153
273
  cert
154
274
  end
155
275
 
276
+ # See #create_for_resource for documentation on 'opts'
277
+ def create_for_user(name, opts = {})
278
+ unless valid?
279
+ raise CertificateNoLongerValidException.new
280
+ end
281
+ email = opts[:email] || "#{name}@#{@@def_email_domain}"
282
+ user_id = opts[:user_id] || UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, email)
283
+ opts[:cn] = "#{user_id}/emailAddress=#{email}"
284
+ opts[:addresses] = [
285
+ "email:#{email}",
286
+ ]
287
+ create_for_resource(user_id, :user, opts)
288
+ end
289
+
156
290
  # Return the X509 certificate. If it hasn't been passed in, return a self-signed one
157
291
  def to_x509()
158
292
  @cert
159
293
  end
160
294
 
161
295
  def can_sign?
162
- !@key.nil? && @key.private?
296
+ !cert_expired? && !@key.nil? && @key.private?
297
+ end
298
+
299
+ def root_ca?
300
+ subject == @cert.issuer
163
301
  end
164
302
 
165
303
  def to_pem
166
304
  to_x509.to_pem
167
305
  end
168
306
 
307
+ def to_pem_with_key
308
+ to_x509.to_pem + @key.to_pem
309
+ end
310
+
169
311
  def to_pem_compact
170
312
  to_pem.lines.to_a[1 ... -1].join.strip
171
313
  end
@@ -178,16 +320,57 @@ module OmfCommon::Auth
178
320
  end
179
321
  end
180
322
 
323
+ # Return a hash of some of the key properties of this cert.
324
+ # To get the full monty, use 'openssl x509 -in xxx.pem -text'
325
+ #
326
+ def describe
327
+ {
328
+ subject: subject,
329
+ issuer: @cert.issuer,
330
+ addresses: addresses,
331
+ can_sign: can_sign?,
332
+ root_ca: root_ca?,
333
+ valid: valid?,
334
+ valid_period: [@cert.not_before, @cert.not_after]
335
+ }
336
+ #(@cert.methods - Object.new.methods).sort
337
+ end
338
+
181
339
  # Will return one of the following
182
340
  #
183
341
  # :HS256, :HS384, :HS512, :RS256, :RS384, :RS512, :ES256, :ES384, :ES512
184
342
  #
185
343
  # def key_algorithm
186
- #
344
+ #
187
345
  # end
188
346
 
189
347
  def to_s
190
- "#<#{self.class} addr=#{@address} subj=#{@subject} can-sign=#{@key != nil}>"
348
+ "#<#{self.class} subj=#{@subject} can-sign=#{@key != nil}>"
349
+ end
350
+
351
+ def _extract_addresses(cert)
352
+ addr = @addresses = {}
353
+ return unless cert
354
+ ext = cert.extensions.find { |ext| ext.oid == 'subjectAltName' }
355
+ return unless ext
356
+ @address_string = ext.value
357
+ @addresses_raw = ext.value.split(',').compact
358
+ @addresses_raw.each do |addr_s|
359
+ parts = addr_s.split(':')
360
+ #puts ">>>>>> #{parts}"
361
+ case parts[0].strip
362
+ when 'email'
363
+ addr[:email] = parts[1]
364
+ when 'URI'
365
+ if parts[1] == 'urn'
366
+ addr[:geni] = parts[3][4 .. -1]
367
+ else
368
+ addr[parts[1].to_sym] = parts[2]
369
+ end
370
+ else
371
+ warn "Unknown address type '#{parts[0]}'"
372
+ end
373
+ end
191
374
  end
192
375
  end # class
193
376
  end # module