Class: OrangeData::Credentials

Inherits:
Object
  • Object
show all
Defined in:
lib/orange_data/credentials.rb

Overview

wrapper for keys/certs used for connection auth

Defined Under Namespace

Modules: KeyEncoding

Constant Summary collapse

DEFAULT_KEY_LENGTH =
2048

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(signature_key_name: nil, signature_key: nil, certificate: nil, certificate_key: nil, title: nil) ⇒ Credentials

Returns a new instance of Credentials.

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
105
# File 'lib/orange_data/credentials.rb', line 96

def initialize(signature_key_name:nil, signature_key:nil, certificate:nil, certificate_key:nil, title:nil)
  raise ArgumentError, "Signature key should be a private key" if signature_key && !signature_key.private?
  raise ArgumentError, "Certificate key should be a private key" if certificate_key && !certificate_key.private?

  @signature_key_name = signature_key_name
  @signature_key = signature_key
  @certificate = certificate
  @certificate_key = certificate_key
  @title = title
end

Instance Attribute Details

#certificateObject

Returns the value of attribute certificate.



94
95
96
# File 'lib/orange_data/credentials.rb', line 94

def certificate
  @certificate
end

#certificate_keyObject

Returns the value of attribute certificate_key.



94
95
96
# File 'lib/orange_data/credentials.rb', line 94

def certificate_key
  @certificate_key
end

#signature_keyObject

Returns the value of attribute signature_key.



94
95
96
# File 'lib/orange_data/credentials.rb', line 94

def signature_key
  @signature_key
end

#signature_key_nameObject

Returns the value of attribute signature_key_name.



94
95
96
# File 'lib/orange_data/credentials.rb', line 94

def signature_key_name
  @signature_key_name
end

#titleObject

Returns the value of attribute title.



94
95
96
# File 'lib/orange_data/credentials.rb', line 94

def title
  @title
end

Class Method Details

.default_testObject

ключи для тествого окружения



269
270
271
# File 'lib/orange_data/credentials.rb', line 269

def self.default_test
  from_hash(YAML.load_file(File.expand_path('credentials_test.yml', __dir__)))
end

.from_hash(creds, key_pass: nil) ⇒ Object



126
127
128
129
130
131
132
133
134
135
# File 'lib/orange_data/credentials.rb', line 126

def self.from_hash(creds, key_pass:nil)
  key_pass ||= '' # to prevent password prompt, works in fresh openssl gem/ruby
  new(
    title: creds[:title],
    signature_key_name: creds[:signature_key_name],
    signature_key: OpenSSL::PKey::RSA.load_from(creds[:signature_key], creds[:signature_key_pass] || key_pass),
    certificate: creds[:certificate] && OpenSSL::X509::Certificate.new(creds[:certificate]),
    certificate_key: OpenSSL::PKey::RSA.load_from(creds[:certificate_key], creds[:certificate_key_pass] || key_pass)
  )
end

.from_json(json) ⇒ Object



162
163
164
165
# File 'lib/orange_data/credentials.rb', line 162

def self.from_json(json)
  require 'json'
  from_hash(JSON.parse(json, symbolize_names: true))
end

.generate_signature_key(key_length = DEFAULT_KEY_LENGTH) ⇒ Object

Raises:

  • (ArgumentError)


201
202
203
204
205
# File 'lib/orange_data/credentials.rb', line 201

def self.generate_signature_key(key_length=DEFAULT_KEY_LENGTH)
  raise ArgumentError, "key length should be >= 489, recomended #{DEFAULT_KEY_LENGTH}" unless key_length >= 489

  OpenSSL::PKey::RSA.new(key_length)
end

.read_certs_from_pack(path, signature_key_name: nil, cert_key_pass: nil, title: nil, signature_key: nil) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/orange_data/credentials.rb', line 207

def self.read_certs_from_pack(path, signature_key_name:nil, cert_key_pass:nil, title:nil, signature_key:nil)
  path = File.expand_path(path)
  client_cert = Dir.glob("#{path}/*.{crt}").select{|f| File.file?(f.sub(/.crt\z/, '.key')) }
  raise 'Expect to find exactly one <num>.crt with corresponding <num>.key file' unless client_cert.size == 1

  client_cert = client_cert.first

  unless signature_key
    # private_key_test.xml || rsa_\d+_private_key.xml
    xmls = Dir.glob("#{path}/*.{xml}").grep(/private/)
    signature_key = if xmls.size == 1
      File.read(xmls.first)
    else
      generate_signature_key(DEFAULT_KEY_LENGTH)
      # .tap{|k| logger.info("Generated public signature key: #{k.public_key.to_xml}") }
    end
  end

  from_hash({
    title: title || "Generated from #{File.basename(path)}",
    signature_key_name: signature_key_name || File.basename(client_cert).gsub(/\..*/, ''),
    certificate: File.read(client_cert),
    certificate_key: File.read(client_cert.sub(/.crt\z/, '.key')),
    certificate_key_pass: cert_key_pass,
    signature_key: signature_key
  })
end

.read_certs_from_zip_pack(rubyzip_object, signature_key_name: nil, cert_key_pass: nil, title: nil, signature_key: nil) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/orange_data/credentials.rb', line 235

def self.read_certs_from_zip_pack(rubyzip_object, signature_key_name:nil, cert_key_pass:nil, title:nil, signature_key:nil)
  client_cert = rubyzip_object.glob("*.crt").select{|f| rubyzip_object.glob(f.name.sub(/.crt\z/, '.key')).any? }
  raise 'Expect to find exactly one <num>.crt with corresponding <num>.key file' unless client_cert.size == 1

  client_cert = client_cert.first
  client_key = rubyzip_object.glob(client_cert.name.sub(/.crt\z/, '.key')).first

  unless signature_key
    # private_key_test.xml || rsa_\d+_private_key.xml
    xmls = rubyzip_object.glob('/*.{xml}').grep(/private/)
    signature_key = if xmls.size == 1
      xmls.first.get_input_stream.read
    else
      generate_signature_key(DEFAULT_KEY_LENGTH)
      # .tap{|k| logger.info("Generated public signature key: #{k.public_key.to_xml}") }
    end
  end

  from_hash({
    title: title || "Generated from zip",
    signature_key_name: signature_key_name || File.basename(client_cert.name).gsub(/\..*/, ''),
    certificate: client_cert.get_input_stream.read,
    certificate_key: client_key.get_input_stream.read,
    certificate_key_pass: cert_key_pass,
    signature_key: signature_key
  })
end

Instance Method Details

#==(other) ⇒ Object



115
116
117
118
119
120
121
122
123
124
# File 'lib/orange_data/credentials.rb', line 115

def ==(other)
  return false unless %i[signature_key_name title].all?{|m| send(m) == other.send(m) }

  # certificates/keys cannot be compared directly, so dump
  %i[signature_key certificate certificate_key].all?{|m|
    c1 = send(m)
    c2 = other.send(m)
    c1 == c2 || (c1 && c2 && c1.to_der == c2.to_der)
  }
end

#certificate_subjectObject



188
189
190
191
192
# File 'lib/orange_data/credentials.rb', line 188

def certificate_subject
  return unless (subj = certificate.subject.to_a.select{|ent| ent.first == 'O' }.first)

  subj[1].force_encoding('UTF-8')
end

#generate_signature_key!(key_length = DEFAULT_KEY_LENGTH) ⇒ Object

deprecated



197
198
199
# File 'lib/orange_data/credentials.rb', line 197

def generate_signature_key!(key_length=DEFAULT_KEY_LENGTH)
  self.signature_key = self.class.generate_signature_key(key_length)
end

#inspectObject



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/orange_data/credentials.rb', line 175

def inspect
  info_fields = {
    title: (title || 'untitled').inspect,
    key_name: signature_key_name.inspect,
  }

  if certificate && (subject_name = certificate_subject)
    info_fields[:certificate] = %("#{(subject_name || 'unknown').gsub('"', '\"')}")
  end

  "#<#{self.class.name}:#{object_id} #{info_fields.map{|(k, v)| "#{k}=#{v}" }.join(' ')}>"
end

#signature_public_xmlObject

публичная часть ключа подписи в формате пригодном для отдачи в ЛК



264
265
266
# File 'lib/orange_data/credentials.rb', line 264

def signature_public_xml
  signature_key.public_key.to_xml
end

#to_hash(key_pass: nil, save_pass: false) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/orange_data/credentials.rb', line 137

def to_hash(key_pass:nil, save_pass:false)
  if key_pass.nil?
    key_pass = SecureRandom.hex
    save_pass = true
  elsif key_pass == false
    key_pass = nil
  end

  {
    title: title,
    signature_key_name: signature_key_name,
    signature_key: signature_key &&
      signature_key.to_pem(key_pass && OpenSSL::Cipher.new("aes-128-cbc"), key_pass),
    certificate: certificate && certificate.to_pem,
    certificate_key: certificate_key &&
      certificate_key.to_pem(key_pass && OpenSSL::Cipher.new("aes-128-cbc"), key_pass),
  }.tap do |h|
    h.delete(:title) if !title || title == ''
    if save_pass
      h[:certificate_key_pass] = key_pass if certificate && key_pass
      h[:signature_key_pass] = key_pass if signature_key && key_pass
    end
  end
end

#to_json(key_pass: nil, save_pass: false) ⇒ Object



167
168
169
# File 'lib/orange_data/credentials.rb', line 167

def to_json(key_pass:nil, save_pass:false)
  to_hash(key_pass:key_pass, save_pass:save_pass).to_json
end

#to_yaml(key_pass: nil, save_pass: false) ⇒ Object



171
172
173
# File 'lib/orange_data/credentials.rb', line 171

def to_yaml(key_pass:nil, save_pass:false)
  to_hash(key_pass:key_pass, save_pass:save_pass).to_yaml
end

#valid?Boolean

Returns:

  • (Boolean)


107
108
109
110
111
112
113
# File 'lib/orange_data/credentials.rb', line 107

def valid?
  signature_key_name &&
    signature_key && signature_key.private? &&
    (signature_key.n.num_bits >= 489) && # minimum working key length for sha256 signature
    certificate && certificate_key &&
    certificate_key.private? && certificate.check_private_key(certificate_key)
end