summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorduritong <peter.meier+github@immerda.ch>2014-06-27 18:44:55 +0200
committerduritong <peter.meier+github@immerda.ch>2014-06-27 18:44:55 +0200
commit8b23bf2d85c3c7a86ca63b9d58d4f2fd0e131fa9 (patch)
tree4ee57f1736f862958cbcbed89ac42d0bbecf5d5a
parent44027adce7333c406e3308686b53b31c43161efa (diff)
parent037efd0d0201d00fb9421382e32a635feae654b1 (diff)
Merge pull request #14 from asquelt/master
x509 format
-rw-r--r--lib/trocla.rb2
-rw-r--r--lib/trocla/formats/x509.rb130
-rw-r--r--lib/trocla/util.rb14
-rw-r--r--spec/trocla_spec.rb3
4 files changed, 145 insertions, 4 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/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 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
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