From 5c7a58996ae7c68ffdacf9f526417174a1ca9330 Mon Sep 17 00:00:00 2001 From: asq Date: Wed, 26 Feb 2014 13:08:04 +0100 Subject: x509 format --- lib/trocla/formats/x509.rb | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 lib/trocla/formats/x509.rb diff --git a/lib/trocla/formats/x509.rb b/lib/trocla/formats/x509.rb new file mode 100644 index 0000000..bf94cec --- /dev/null +++ b/lib/trocla/formats/x509.rb @@ -0,0 +1,120 @@ +class Trocla::Formats::X509 + require 'openssl' + def format(plain_password,options={}) + + 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 + cert.subject = subject + cert.issuer = issuer + 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 = cert + 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 + subreq.get_password("#{ca}_serial",'plain') + 1 || serial + 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}" unless ca + 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.subject, 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, subj, 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 + -- cgit v1.2.3 From 4d002c7d46f4a2c87ec1e3e27c4ee695f60ffa7f Mon Sep 17 00:00:00 2001 From: asq Date: Wed, 26 Feb 2014 18:00:51 +0100 Subject: x509 format - fixes --- lib/trocla/formats/x509.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/trocla/formats/x509.rb b/lib/trocla/formats/x509.rb index bf94cec..5cb1fb2 100644 --- a/lib/trocla/formats/x509.rb +++ b/lib/trocla/formats/x509.rb @@ -2,6 +2,11 @@ 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'] @@ -36,8 +41,9 @@ class Trocla::Formats::X509 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 + cert.issuer = issuer.subject cert.not_before = Time.now cert.not_after = Time.now + days * 24 * 60 * 60 cert.public_key = public_key @@ -46,7 +52,7 @@ class Trocla::Formats::X509 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert - ef.issuer_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 @@ -64,7 +70,12 @@ class Trocla::Formats::X509 def getserial(ca,serial) subreq = Trocla.new - subreq.get_password("#{ca}_serial",'plain') + 1 || serial + newser = subreq.get_password("#{ca}_serial",'plain') + if newser + newser + 1 + else + serial + end end def setserial(ca,serial) @@ -84,7 +95,7 @@ class Trocla::Formats::X509 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}" unless ca + raise "Value of #{sign_with} can't be loaded as CA: #{e.message}" end begin @@ -96,7 +107,7 @@ class Trocla::Formats::X509 end begin - csr_cert = mkcert(caserial, request.subject, ca.subject, request.public_key, days, altnames) + 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 @@ -107,7 +118,7 @@ class Trocla::Formats::X509 else # self-signed certificate begin subj = OpenSSL::X509::Name.parse(subject) - cert = mkcert(serial, subj, subj, key.public_key, days, altnames) + 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}" @@ -117,4 +128,3 @@ class Trocla::Formats::X509 end end end - -- cgit v1.2.3 From 3da4ca80bcdf1e7dcff60a8bf61fb93eb291c419 Mon Sep 17 00:00:00 2001 From: asq Date: Wed, 26 Feb 2014 18:10:32 +0100 Subject: x509 format - fix test --- spec/trocla_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/trocla_spec.rb b/spec/trocla_spec.rb index fe95520..afb838a 100644 --- a/spec/trocla_spec.rb +++ b/spec/trocla_spec.rb @@ -121,6 +121,7 @@ describe "Trocla" do def format_options @format_options ||= Hash.new({}).merge({ 'pgsql' => { 'username' => 'test' } + 'x509' => { 'CN' => 'test' } }) end -- cgit v1.2.3 From c7521013ba8b2d14915624c7deebe0d659675cdd Mon Sep 17 00:00:00 2001 From: asq Date: Wed, 26 Feb 2014 18:30:03 +0100 Subject: x509 format - fix test --- spec/trocla_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/trocla_spec.rb b/spec/trocla_spec.rb index afb838a..61d9921 100644 --- a/spec/trocla_spec.rb +++ b/spec/trocla_spec.rb @@ -120,8 +120,8 @@ describe "Trocla" do def format_options @format_options ||= Hash.new({}).merge({ - 'pgsql' => { 'username' => 'test' } - 'x509' => { 'CN' => 'test' } + 'pgsql' => { 'username' => 'test' }, + 'x509' => { 'CN' => 'test' }, }) end -- cgit v1.2.3 From 037efd0d0201d00fb9421382e32a635feae654b1 Mon Sep 17 00:00:00 2001 From: asq Date: Thu, 3 Apr 2014 17:15:37 +0200 Subject: add option to generate shell-safe passwords basically excludes characters that might be dangerous if used in shell. many passwords generated by trocla may end up in some sort of bash scripts (initscripts, sourced shell variables, etc) which may yeld problems with default trocla random generator. this can be now changed either in troclarc (with "shellsafe: true") or on (ie. "trocla create foo plain '{ length: 32, shellsafe: true}'"). --- lib/trocla.rb | 2 +- lib/trocla/util.rb | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/trocla.rb b/lib/trocla.rb index 4e7bedb..7755149 100644 --- a/lib/trocla.rb +++ b/lib/trocla.rb @@ -22,7 +22,7 @@ class Trocla plain_pwd = get_password(key,'plain') if options['random'] && plain_pwd.nil? - plain_pwd = Trocla::Util.random_str(options['length']) + plain_pwd = Trocla::Util.random_str(options['length'],options['shellsafe']) set_password(key,'plain',plain_pwd) unless format == 'plain' elsif !options['random'] && plain_pwd.nil? raise "Password must be present as plaintext if you don't want a random password" diff --git a/lib/trocla/util.rb b/lib/trocla/util.rb index 2b1c6c6..ff7e3ce 100644 --- a/lib/trocla/util.rb +++ b/lib/trocla/util.rb @@ -2,8 +2,12 @@ require 'securerandom' class Trocla class Util class << self - def random_str(length=12) - (1..length).collect{|a| chars[SecureRandom.random_number(chars.size)] }.join.to_s + def random_str(length=12,shellsafe=:undef) + if 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 end def salt(length=8) @@ -14,12 +18,18 @@ class Trocla def chars @chars ||= normal_chars + special_chars end + def safechars + @chars ||= normal_chars + shellsafe_chars + end def normal_chars @normal_chars ||= ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a end def special_chars @special_chars ||= "+*%/()@&=?![]{}-_.,;:".split(//) end + def shellsafe_chars + @shellsafe_chars ||= "+%/@=?_.,:".split(//) + end end end end -- cgit v1.2.3