diff options
-rw-r--r-- | lib/trocla/default_config.yaml | 1 | ||||
-rw-r--r-- | lib/trocla/formats/x509.rb | 130 | ||||
-rw-r--r-- | lib/trocla/util.rb | 34 | ||||
-rw-r--r-- | spec/trocla_spec.rb | 3 |
4 files changed, 152 insertions, 16 deletions
diff --git a/lib/trocla/default_config.yaml b/lib/trocla/default_config.yaml index f46568f..d4037fd 100644 --- a/lib/trocla/default_config.yaml +++ b/lib/trocla/default_config.yaml @@ -2,6 +2,7 @@ options: random: true length: 12 + charset: default adapter: :YAML adapter_options: :file: '/tmp/trocla.yaml' diff --git a/lib/trocla/formats/x509.rb b/lib/trocla/formats/x509.rb new file mode 100644 index 0000000..5cb1fb2 --- /dev/null +++ b/lib/trocla/formats/x509.rb @@ -0,0 +1,130 @@ +class Trocla::Formats::X509 + require 'openssl' + def format(plain_password,options={}) + + if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/m) + # just an import, don't generate any new keys + return plain_password + end + + if options['subject'] + subject = options['subject'] + elsif options['CN'] + subject = '' + ['C','ST','L','O','OU','CN','emailAddress'].each do |field| + subject << "/#{field}=#{options[field]}" if options[field] + end + else + raise "You need to pass \"subject\" or \"CN\" as an option to use this format" + end + sign_with = options['ca'] || nil + keysize = options['keysize'] || 2048 + serial = options['serial'] || 1 + days = options['days'] || 365 + altnames = options['altnames'] || nil + altnames.collect { |v| "DNS:#{v}" }.join(', ') if altnames + + # nice help: https://gist.github.com/mitfik/1922961 + + def mkkey(len) + OpenSSL::PKey::RSA.generate(len) + end + + def mkreq(subject,public_key) + request = OpenSSL::X509::Request.new + request.version = 0 + request.subject = subject + request.public_key = public_key + + request + end + + def mkcert(serial,subject,issuer,public_key,days,altnames) + cert = OpenSSL::X509::Certificate.new + issuer = cert if issuer == nil + cert.subject = subject + cert.issuer = issuer.subject + cert.not_before = Time.now + cert.not_after = Time.now + days * 24 * 60 * 60 + cert.public_key = public_key + cert.serial = serial + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = issuer + cert.extensions = [ ef.create_extension("subjectKeyIdentifier", "hash") ] + cert.add_extension ef.create_extension("basicConstraints","CA:TRUE", true) if subject == issuer + cert.add_extension ef.create_extension("basicConstraints","CA:FALSE", true) if subject != issuer + cert.add_extension ef.create_extension("keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true) + cert.add_extension ef.create_extension("subjectAltName", altnames, true) if altnames + cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") + + cert + end + + def getca(ca) + subreq = Trocla.new + subreq.get_password(ca,'x509') + end + + def getserial(ca,serial) + subreq = Trocla.new + newser = subreq.get_password("#{ca}_serial",'plain') + if newser + newser + 1 + else + serial + end + end + + def setserial(ca,serial) + subreq = Trocla.new + subreq.set_password("#{ca}_serial",'plain',serial) + end + + begin + key = mkkey(keysize) + rescue Exception => e + raise "Private key for #{subject} creation failed: #{e.message}" + end + + if sign_with # certificate signed with CA + begin + ca = OpenSSL::X509::Certificate.new(getca(sign_with)) + cakey = OpenSSL::PKey::RSA.new(getca(sign_with)) + caserial = getserial(sign_with, serial) + rescue Exception => e + raise "Value of #{sign_with} can't be loaded as CA: #{e.message}" + end + + begin + subj = OpenSSL::X509::Name.parse(subject) + request = mkreq(subj, key.public_key) + request.sign(key, OpenSSL::Digest::SHA1.new) + rescue Exception => e + raise "Certificate request #{subject} creation failed: #{e.message}" + end + + begin + csr_cert = mkcert(caserial, request.subject, ca, request.public_key, days, altnames) + csr_cert.sign(cakey, OpenSSL::Digest::SHA1.new) + setserial(sign_with, caserial) + rescue Exception => e + raise "Certificate #{subject} signing failed: #{e.message}" + end + + key.send("to_pem") + csr_cert.send("to_pem") + else # self-signed certificate + begin + subj = OpenSSL::X509::Name.parse(subject) + cert = mkcert(serial, subj, nil, key.public_key, days, altnames) + cert.sign(key, OpenSSL::Digest::SHA1.new) + rescue Exception => e + raise "Self-signed certificate #{subject} creation failed: #{e.message}" + end + + key.send("to_pem") + cert.send("to_pem") + end + end +end diff --git a/lib/trocla/util.rb b/lib/trocla/util.rb index 2826c9e..78462f5 100644 --- a/lib/trocla/util.rb +++ b/lib/trocla/util.rb @@ -2,32 +2,36 @@ require 'securerandom' class Trocla class Util class << self - def random_str(length=12, charset=:undef) - if charset == 'alphanumeric' - (1..length).collect{|a| normal_chars[SecureRandom.random_number(normal_chars.size)] }.join.to_s - elsif charset == 'special_shellsafe' - (1..length).collect{|a| safechars[SecureRandom.random_number(safechars.size)] }.join.to_s - else - (1..length).collect{|a| chars[SecureRandom.random_number(chars.size)] }.join.to_s - end + def random_str(length=12, charset='default') + _charsets = charsets[charset] + (1..length).collect{|a| _charsets[SecureRandom.random_number(_charsets.size)] }.join.to_s end def salt(length=8) - (1..length).collect{|a| normal_chars[SecureRandom.random_number(normal_chars.size)] }.join.to_s + (1..length).collect{|a| alphanumeric[SecureRandom.random_number(alphanumeric.size)] }.join.to_s end private + + def charsets + @charsets ||= { + 'default' => chars, + 'alphanumeric' => alphanumeric, + 'shellsafe' => shellsafe, + } + end + def chars - @chars ||= normal_chars + special_chars + @chars ||= shellsafe + special_chars end - def safechars - @chars ||= normal_chars + shellsafe_chars + def shellsafe + @chars ||= alphanumeric + shellsafe_chars end - def normal_chars - @normal_chars ||= ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + def alphanumeric + @alphanumeric ||= ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a end def special_chars - @special_chars ||= "+*%/()@&=?![]{}-_.,;:".split(//) + @special_chars ||= "*()&![]{}-".split(//) end def shellsafe_chars @shellsafe_chars ||= "+%/@=?_.,:".split(//) diff --git a/spec/trocla_spec.rb b/spec/trocla_spec.rb index fe95520..61d9921 100644 --- a/spec/trocla_spec.rb +++ b/spec/trocla_spec.rb @@ -120,7 +120,8 @@ describe "Trocla" do def format_options @format_options ||= Hash.new({}).merge({ - 'pgsql' => { 'username' => 'test' } + 'pgsql' => { 'username' => 'test' }, + 'x509' => { 'CN' => 'test' }, }) end |