From b70fcc845696f58b2a5d36039df5f2c8c1dd2e04 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 From 9f21b44da1ec9b24820ac08e2e4d1e171fabbf7e Mon Sep 17 00:00:00 2001 From: asq Date: Thu, 3 Apr 2014 19:02:02 +0200 Subject: puppet will convert all values to string, so we need to convert it back to integer for ranges ie. for this to work: $short_and_safe = { 'shellsafe' => 'true', 'length' => 6, # THIS WILL BE STRING! } $x = trocla('foo', 'plain', $short_and_safe) notify { "test: $x": } --- lib/trocla.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/trocla.rb b/lib/trocla.rb index 7755149..74825aa 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'],options['shellsafe']) + plain_pwd = Trocla::Util.random_str(options['length'].to_i,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" -- cgit v1.2.3 From eabd41b10fa6da986e7e5ee2e3d93d3f19100c49 Mon Sep 17 00:00:00 2001 From: Anna Janackova Date: Tue, 24 Jun 2014 08:09:43 +0200 Subject: adds charset option for generating plain passwords --- lib/trocla.rb | 32 ++++++++++++++++---------------- lib/trocla/util.rb | 6 ++++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/trocla.rb b/lib/trocla.rb index 74825aa..a239be8 100644 --- a/lib/trocla.rb +++ b/lib/trocla.rb @@ -3,11 +3,11 @@ require 'trocla/util' require 'trocla/formats' class Trocla - + def initialize(config_file=nil) if config_file @config_file = File.expand_path(config_file) - elsif File.exists?(def_config_file=File.expand_path('~/.troclarc.yaml')) || File.exists?(def_config_file=File.expand_path('/etc/troclarc.yaml')) + elsif File.exists?(def_config_file=File.expand_path('~/.troclarc.yaml')) || File.exists?(def_config_file=File.expand_path('/etc/troclarc.yaml')) @config_file = def_config_file end end @@ -20,27 +20,27 @@ class Trocla return password end - plain_pwd = get_password(key,'plain') + plain_pwd = get_password(key,'plain') if options['random'] && plain_pwd.nil? - plain_pwd = Trocla::Util.random_str(options['length'].to_i,options['shellsafe']) - set_password(key,'plain',plain_pwd) unless format == 'plain' + plain_pwd = Trocla::Util.random_str(options['length'].to_i,options['charset']) + 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" end set_password(key,format,Trocla::Formats[format].format(plain_pwd,options)) end - + def get_password(key,format) cache.fetch(key,{})[format] end - + def reset_password(key,format,options={}) set_password(key,format,nil) password(key,format,options) end - + def delete_password(key,format=nil) - if format.nil? + if format.nil? cache.delete(key) else old_val = (h = cache.fetch(key,{})).delete(format) @@ -48,7 +48,7 @@ class Trocla old_val end end - + def set_password(key,format,password) if (format == 'plain') h = (cache[key] = { 'plain' => password }) @@ -57,22 +57,22 @@ class Trocla end h[format] end - + private def cache @cache ||= build_cache end - + def build_cache require 'moneta' lconfig = config Moneta.new(lconfig['adapter'], lconfig['adapter_options']||{}) end - + def config @config ||= read_config end - + def read_config if @config_file.nil? default_config @@ -81,10 +81,10 @@ class Trocla default_config.merge(YAML.load(File.read(@config_file))) end end - + def default_config require 'yaml' YAML.load(File.read(File.expand_path(File.join(File.dirname(__FILE__),'trocla','default_config.yaml')))) end - + end diff --git a/lib/trocla/util.rb b/lib/trocla/util.rb index ff7e3ce..2826c9e 100644 --- a/lib/trocla/util.rb +++ b/lib/trocla/util.rb @@ -2,8 +2,10 @@ require 'securerandom' class Trocla class Util class << self - def random_str(length=12,shellsafe=:undef) - if shellsafe + 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 -- cgit v1.2.3 From 335773cf82f600f0401dad7492c1de5c098a3d06 Mon Sep 17 00:00:00 2001 From: mh Date: Fri, 27 Jun 2014 19:01:20 +0200 Subject: add SHA1 base64 encoded format --- lib/trocla/formats/sha1.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/trocla/formats/sha1.rb diff --git a/lib/trocla/formats/sha1.rb b/lib/trocla/formats/sha1.rb new file mode 100644 index 0000000..3de75c7 --- /dev/null +++ b/lib/trocla/formats/sha1.rb @@ -0,0 +1,7 @@ +class Trocla::Formats::Sha1 + require 'digest/sha1' + require 'base64' + def format(plain_password,options={}) + '{SHA}' + Base64.encode64(Digest::SHA1.digest(plain_password)) + end +end -- cgit v1.2.3 From 08ac533d2156b666ae6ca68e797992629051315f Mon Sep 17 00:00:00 2001 From: mh Date: Fri, 27 Jun 2014 19:22:44 +0200 Subject: make it possible that formats can query back to trocla itself, so they can lookup other 'keys' --- bin/trocla | 5 +- lib/trocla.rb | 6 +- lib/trocla/formats.rb | 20 +++++-- lib/trocla/formats/bcrypt.rb | 2 +- lib/trocla/formats/md5crypt.rb | 2 +- lib/trocla/formats/mysql.rb | 4 +- lib/trocla/formats/pgsql.rb | 2 +- lib/trocla/formats/plain.rb | 4 +- lib/trocla/formats/sha1.rb | 2 +- lib/trocla/formats/sha256crypt.rb | 2 +- lib/trocla/formats/sha512crypt.rb | 2 +- lib/trocla/formats/ssha.rb | 2 +- lib/trocla/formats/x509.rb | 118 +++++++++++++++++++------------------- 13 files changed, 91 insertions(+), 80 deletions(-) diff --git a/bin/trocla b/bin/trocla index dd32f84..6949318 100755 --- a/bin/trocla +++ b/bin/trocla @@ -65,10 +65,11 @@ def set(options) password = options.delete(:password) || STDIN.read.chomp end format = options.delete(:trocla_format) - Trocla.new(options.delete(:config_file)).set_password( + trocla = Trocla.new(options.delete(:config_file)) + trocla.set_password( options.delete(:trocla_key), format, - Trocla::Formats[format].format(password, options.delete(:other_options).shift.to_s) + trocla.formats(format).format(password, options.delete(:other_options).shift.to_s) ) "" end diff --git a/lib/trocla.rb b/lib/trocla.rb index a239be8..8d916b2 100644 --- a/lib/trocla.rb +++ b/lib/trocla.rb @@ -27,7 +27,7 @@ class Trocla elsif !options['random'] && plain_pwd.nil? raise "Password must be present as plaintext if you don't want a random password" end - set_password(key,format,Trocla::Formats[format].format(plain_pwd,options)) + set_password(key,format,self.formats(format).format(plain_pwd,options)) end def get_password(key,format) @@ -58,6 +58,10 @@ class Trocla h[format] end + def formats(format) + (@format_cache||={})[format] ||= Trocla::Formats[format].new(self) + end + private def cache @cache ||= build_cache diff --git a/lib/trocla/formats.rb b/lib/trocla/formats.rb index 3cf31bd..0103c4e 100644 --- a/lib/trocla/formats.rb +++ b/lib/trocla/formats.rb @@ -1,32 +1,40 @@ class Trocla::Formats + + class Base + attr_reader :trocla + def initialize(trocla) + @trocla = trocla + end + end + class << self def [](format) formats[format.downcase] end - + def all Dir[File.expand_path(File.join(File.dirname(__FILE__),'formats','*.rb'))].collect{|f| File.basename(f,'.rb').downcase } end - + def available?(format) all.include?(format.downcase) end - + private def formats @@formats ||= Hash.new do |hash, format| format = format.downcase if File.exists?(path(format)) require "trocla/formats/#{format}" - hash[format] = (eval "Trocla::Formats::#{format.capitalize}").new + hash[format] = (eval "Trocla::Formats::#{format.capitalize}") else raise "Format #{format} is not supported!" end end end - + def path(format) File.expand_path(File.join(File.dirname(__FILE__),'formats',"#{format}.rb")) end end -end \ No newline at end of file +end diff --git a/lib/trocla/formats/bcrypt.rb b/lib/trocla/formats/bcrypt.rb index 7196e54..4b6fb33 100644 --- a/lib/trocla/formats/bcrypt.rb +++ b/lib/trocla/formats/bcrypt.rb @@ -1,4 +1,4 @@ -class Trocla::Formats::Bcrypt +class Trocla::Formats::Bcrypt < Trocla::Formats::Base require 'bcrypt' def format(plain_password,options={}) BCrypt::Password.create(plain_password).to_s diff --git a/lib/trocla/formats/md5crypt.rb b/lib/trocla/formats/md5crypt.rb index 36e3a3c..80d2f09 100644 --- a/lib/trocla/formats/md5crypt.rb +++ b/lib/trocla/formats/md5crypt.rb @@ -1,5 +1,5 @@ # salted crypt -class Trocla::Formats::Md5crypt +class Trocla::Formats::Md5crypt < Trocla::Formats::Base def format(plain_password,options={}) plain_password.crypt('$1$' << Trocla::Util.salt << '$') end diff --git a/lib/trocla/formats/mysql.rb b/lib/trocla/formats/mysql.rb index 7fbc3a7..a097f95 100644 --- a/lib/trocla/formats/mysql.rb +++ b/lib/trocla/formats/mysql.rb @@ -1,6 +1,6 @@ -class Trocla::Formats::Mysql +class Trocla::Formats::Mysql < Trocla::Formats::Base require 'digest/sha1' def format(plain_password,options={}) "*" + Digest::SHA1.hexdigest(Digest::SHA1.digest(plain_password)).upcase end -end \ No newline at end of file +end diff --git a/lib/trocla/formats/pgsql.rb b/lib/trocla/formats/pgsql.rb index de233c7..ef4fed3 100644 --- a/lib/trocla/formats/pgsql.rb +++ b/lib/trocla/formats/pgsql.rb @@ -1,4 +1,4 @@ -class Trocla::Formats::Pgsql +class Trocla::Formats::Pgsql < Trocla::Formats::Base require 'digest/md5' def format(plain_password,options={}) raise "You need pass the username as an option to use this format" unless options['username'] diff --git a/lib/trocla/formats/plain.rb b/lib/trocla/formats/plain.rb index 98797d2..79502e0 100644 --- a/lib/trocla/formats/plain.rb +++ b/lib/trocla/formats/plain.rb @@ -1,7 +1,7 @@ -class Trocla::Formats::Plain +class Trocla::Formats::Plain < Trocla::Formats::Base def format(plain_password,options={}) plain_password end -end \ No newline at end of file +end diff --git a/lib/trocla/formats/sha1.rb b/lib/trocla/formats/sha1.rb index 3de75c7..1321b35 100644 --- a/lib/trocla/formats/sha1.rb +++ b/lib/trocla/formats/sha1.rb @@ -1,4 +1,4 @@ -class Trocla::Formats::Sha1 +class Trocla::Formats::Sha1 < Trocla::Formats::Base require 'digest/sha1' require 'base64' def format(plain_password,options={}) diff --git a/lib/trocla/formats/sha256crypt.rb b/lib/trocla/formats/sha256crypt.rb index 6bd1b72..e34c149 100644 --- a/lib/trocla/formats/sha256crypt.rb +++ b/lib/trocla/formats/sha256crypt.rb @@ -1,5 +1,5 @@ # salted crypt -class Trocla::Formats::Sha256crypt +class Trocla::Formats::Sha256crypt < Trocla::Formats::Base def format(plain_password,options={}) plain_password.crypt('$5$' << Trocla::Util.salt << '$') end diff --git a/lib/trocla/formats/sha512crypt.rb b/lib/trocla/formats/sha512crypt.rb index d2725c1..47eb11e 100644 --- a/lib/trocla/formats/sha512crypt.rb +++ b/lib/trocla/formats/sha512crypt.rb @@ -1,5 +1,5 @@ # salted crypt -class Trocla::Formats::Sha512crypt +class Trocla::Formats::Sha512crypt < Trocla::Formats::Base def format(plain_password,options={}) plain_password.crypt('$6$' << Trocla::Util.salt << '$') end diff --git a/lib/trocla/formats/ssha.rb b/lib/trocla/formats/ssha.rb index eaac6bb..a2e0d02 100644 --- a/lib/trocla/formats/ssha.rb +++ b/lib/trocla/formats/ssha.rb @@ -1,7 +1,7 @@ # salted crypt require 'base64' require 'digest' -class Trocla::Formats::Ssha +class Trocla::Formats::Ssha < Trocla::Formats::Base def format(plain_password,options={}) salt = options['salt'] || Trocla::Util.salt(16) "{SSHA}"+Base64.encode64("#{Digest::SHA1.digest("#{plain_password}#{salt}")}#{salt}").chomp diff --git a/lib/trocla/formats/x509.rb b/lib/trocla/formats/x509.rb index 5cb1fb2..219cd38 100644 --- a/lib/trocla/formats/x509.rb +++ b/lib/trocla/formats/x509.rb @@ -1,4 +1,4 @@ -class Trocla::Formats::X509 +class Trocla::Formats::X509 < Trocla::Formats::Base require 'openssl' def format(plain_password,options={}) @@ -24,65 +24,6 @@ class Trocla::Formats::X509 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 @@ -127,4 +68,61 @@ class Trocla::Formats::X509 key.send("to_pem") + cert.send("to_pem") end end + private + + # 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) + trocla.get_password(ca,'x509') + end + + def getserial(ca,serial) + newser = trocla.get_password("#{ca}_serial",'plain') + if newser + newser + 1 + else + serial + end + end + + def setserial(ca,serial) + trocla.set_password("#{ca}_serial",'plain',serial) + end end -- cgit v1.2.3