diff options
-rw-r--r-- | .document | 4 | ||||
-rw-r--r-- | .rspec | 1 | ||||
-rw-r--r-- | Gemfile.lock | 35 | ||||
-rw-r--r-- | LICENSE.txt | 15 | ||||
-rwxr-xr-x | bin/trocla | 121 | ||||
-rw-r--r-- | lib/VERSION | 4 | ||||
-rw-r--r-- | lib/trocla.rb | 94 | ||||
-rw-r--r-- | lib/trocla/default_config.yaml | 8 | ||||
-rw-r--r-- | lib/trocla/formats.rb | 40 | ||||
-rw-r--r-- | lib/trocla/formats/bcrypt.rb | 6 | ||||
-rw-r--r-- | lib/trocla/formats/md5crypt.rb | 6 | ||||
-rw-r--r-- | lib/trocla/formats/mysql.rb | 6 | ||||
-rw-r--r-- | lib/trocla/formats/pgsql.rb | 7 | ||||
-rw-r--r-- | lib/trocla/formats/plain.rb | 7 | ||||
-rw-r--r-- | lib/trocla/formats/sha1.rb | 7 | ||||
-rw-r--r-- | lib/trocla/formats/sha256crypt.rb | 6 | ||||
-rw-r--r-- | lib/trocla/formats/sha512crypt.rb | 6 | ||||
-rw-r--r-- | lib/trocla/formats/ssha.rb | 9 | ||||
-rw-r--r-- | lib/trocla/formats/x509.rb | 128 | ||||
-rw-r--r-- | lib/trocla/util.rb | 43 | ||||
-rw-r--r-- | lib/trocla/version.rb | 22 | ||||
-rw-r--r-- | spec/data/.keep | 0 | ||||
-rw-r--r-- | spec/trocla/util_spec.rb | 28 | ||||
-rw-r--r-- | spec/trocla_spec.rb | 128 | ||||
-rw-r--r-- | trocla.gemspec | 89 |
25 files changed, 820 insertions, 0 deletions
diff --git a/.document b/.document new file mode 100644 index 0000000..c98d021 --- /dev/null +++ b/.document @@ -0,0 +1,4 @@ +lib/**/*.rb +bin/* +- +LICENSE.txt @@ -0,0 +1 @@ +--color diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..43bc6bb --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,35 @@ +GEM + remote: http://rubygems.org/ + specs: + bcrypt (3.1.7) + diff-lcs (1.1.3) + git (1.2.5) + highline (1.6.2) + jeweler (1.6.4) + bundler (~> 1.0) + git (>= 1.2.5) + rake + mocha (0.9.12) + moneta (0.7.20) + rake (0.9.2) + rdoc (3.8) + rspec (2.4.0) + rspec-core (~> 2.4.0) + rspec-expectations (~> 2.4.0) + rspec-mocks (~> 2.4.0) + rspec-core (2.4.0) + rspec-expectations (2.4.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.4.0) + +PLATFORMS + ruby + +DEPENDENCIES + bcrypt + highline + jeweler + mocha + moneta (~> 0.7) + rdoc (~> 3.8) + rspec (~> 2.4) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5b0754c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,15 @@ +Trocla - a simple password generator and storage +Copyright (C) 2011 Marcel Haerry + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/bin/trocla b/bin/trocla new file mode 100755 index 0000000..6949318 --- /dev/null +++ b/bin/trocla @@ -0,0 +1,121 @@ +#!/usr/bin/env ruby +# CLI client for Trocla. +# +require 'rubygems' +require 'trocla' +require 'optparse' +require 'yaml' + +options = { :config_file => nil, :ask_password => true } + +OptionParser.new do |opts| + opts.on("--version", "-V", "Version information") do + puts Trocla::VERSION::STRING + exit + end + + opts.on("--config CONFIG", "-c", "Configuration file") do |v| + if File.exist?(v) + options[:config_file] = v + else + STDERR.puts "Cannot find config file: #{v}" + exit 1 + end + end + + opts.on("--no-random") do + options['random'] = false + end + + opts.on("--length LENGTH") do |v| + options['length'] = v.to_i + end + + opts.on("--password [PASSWORD]", "-p", "Provide password at command line") do |pass| + options[:ask_password] = false + options[:password] = pass + end + +end.parse! + +def create(options) + Trocla.new(options.delete(:config_file)).password( + options.delete(:trocla_key), + options.delete(:trocla_format), + options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{}) + ) +end + +def get(options) + Trocla.new(options.delete(:config_file)).get_password( + options.delete(:trocla_key), + options.delete(:trocla_format) + ) +end +def set(options) + if options.delete(:ask_password) + require 'highline/import' + password = ask("Enter your password: ") { |q| q.echo = "x" }.to_s + pwd2 = ask("Repeat password: ") { |q| q.echo = "x" }.to_s + unless password == pwd2 + STDERR.puts "Passwords did not match, exiting!" + exit 1 + end + else + password = options.delete(:password) || STDIN.read.chomp + end + format = options.delete(:trocla_format) + 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) + ) + "" +end + +def reset(options) + Trocla.new(options.delete(:config_file)).reset_password( + options.delete(:trocla_key), + options.delete(:trocla_format), + options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{}) + ) +end + +def delete(options) + Trocla.new(options.delete(:config_file)).delete_password( + options.delete(:trocla_key), + options.delete(:trocla_format) + ) +end + +def check_format(format_name) + if format_name.nil? + STDERR.puts "Missing format, exiting..." + exit 1 + elsif !Trocla::Formats.available?(format_name) + STDERR.puts "Error: The format #{format_name} is not available" + exit 1 + end +end + +actions=['create','get','set','reset','delete'] + +if !(ARGV.length < 2) && (action=ARGV.shift) && actions.include?(action) + options[:trocla_key] = ARGV.shift + options[:trocla_format] = ARGV.shift + options[:other_options] = ARGV + check_format(options[:trocla_format]) unless action == 'delete' + begin + if result = send(action,options) + puts result.is_a?(String) ? result : result.inspect + end + rescue Exception => e + STDERR.puts "Action failed with the following message: #{e.message}" unless e.message == 'exit' + exit 1 + end +else + STDERR.puts "Please supply one of the following actions: #{actions.join(', ')}" + exit 1 +end + diff --git a/lib/VERSION b/lib/VERSION new file mode 100644 index 0000000..158fcec --- /dev/null +++ b/lib/VERSION @@ -0,0 +1,4 @@ +major:0 +minor:0 +patch:11 +build: diff --git a/lib/trocla.rb b/lib/trocla.rb new file mode 100644 index 0000000..8d916b2 --- /dev/null +++ b/lib/trocla.rb @@ -0,0 +1,94 @@ +require 'trocla/version' +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')) + @config_file = def_config_file + end + end + + def password(key,format,options={}) + options = config['options'].merge(options) + raise "Format #{format} is not supported! Supported formats: #{Trocla::Formats.all.join(', ')}" unless Trocla::Formats::available?(format) + + unless (password=get_password(key,format)).nil? + return password + end + + plain_pwd = get_password(key,'plain') + if options['random'] && plain_pwd.nil? + 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,self.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? + cache.delete(key) + else + old_val = (h = cache.fetch(key,{})).delete(format) + h.empty? ? cache.delete(key) : cache[key] = h + old_val + end + end + + def set_password(key,format,password) + if (format == 'plain') + h = (cache[key] = { 'plain' => password }) + else + h = (cache[key] = cache.fetch(key,{}).merge({ format => password })) + end + h[format] + end + + def formats(format) + (@format_cache||={})[format] ||= Trocla::Formats[format].new(self) + 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 + else + raise "Configfile #{@config_file} does not exist!" unless File.exists?(@config_file) + 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/default_config.yaml b/lib/trocla/default_config.yaml new file mode 100644 index 0000000..d4037fd --- /dev/null +++ b/lib/trocla/default_config.yaml @@ -0,0 +1,8 @@ +--- +options: + random: true + length: 12 + charset: default +adapter: :YAML +adapter_options: + :file: '/tmp/trocla.yaml' diff --git a/lib/trocla/formats.rb b/lib/trocla/formats.rb new file mode 100644 index 0000000..0103c4e --- /dev/null +++ b/lib/trocla/formats.rb @@ -0,0 +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}") + 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 diff --git a/lib/trocla/formats/bcrypt.rb b/lib/trocla/formats/bcrypt.rb new file mode 100644 index 0000000..4b6fb33 --- /dev/null +++ b/lib/trocla/formats/bcrypt.rb @@ -0,0 +1,6 @@ +class Trocla::Formats::Bcrypt < Trocla::Formats::Base + require 'bcrypt' + def format(plain_password,options={}) + BCrypt::Password.create(plain_password).to_s + end +end diff --git a/lib/trocla/formats/md5crypt.rb b/lib/trocla/formats/md5crypt.rb new file mode 100644 index 0000000..80d2f09 --- /dev/null +++ b/lib/trocla/formats/md5crypt.rb @@ -0,0 +1,6 @@ +# salted crypt +class Trocla::Formats::Md5crypt < Trocla::Formats::Base + def format(plain_password,options={}) + plain_password.crypt('$1$' << Trocla::Util.salt << '$') + end +end diff --git a/lib/trocla/formats/mysql.rb b/lib/trocla/formats/mysql.rb new file mode 100644 index 0000000..a097f95 --- /dev/null +++ b/lib/trocla/formats/mysql.rb @@ -0,0 +1,6 @@ +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 diff --git a/lib/trocla/formats/pgsql.rb b/lib/trocla/formats/pgsql.rb new file mode 100644 index 0000000..ef4fed3 --- /dev/null +++ b/lib/trocla/formats/pgsql.rb @@ -0,0 +1,7 @@ +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'] + "md5" + Digest::MD5.hexdigest(plain_password + options['username']) + end +end diff --git a/lib/trocla/formats/plain.rb b/lib/trocla/formats/plain.rb new file mode 100644 index 0000000..79502e0 --- /dev/null +++ b/lib/trocla/formats/plain.rb @@ -0,0 +1,7 @@ +class Trocla::Formats::Plain < Trocla::Formats::Base + + def format(plain_password,options={}) + plain_password + end + +end diff --git a/lib/trocla/formats/sha1.rb b/lib/trocla/formats/sha1.rb new file mode 100644 index 0000000..1321b35 --- /dev/null +++ b/lib/trocla/formats/sha1.rb @@ -0,0 +1,7 @@ +class Trocla::Formats::Sha1 < Trocla::Formats::Base + require 'digest/sha1' + require 'base64' + def format(plain_password,options={}) + '{SHA}' + Base64.encode64(Digest::SHA1.digest(plain_password)) + end +end diff --git a/lib/trocla/formats/sha256crypt.rb b/lib/trocla/formats/sha256crypt.rb new file mode 100644 index 0000000..e34c149 --- /dev/null +++ b/lib/trocla/formats/sha256crypt.rb @@ -0,0 +1,6 @@ +# salted crypt +class Trocla::Formats::Sha256crypt < Trocla::Formats::Base + def format(plain_password,options={}) + plain_password.crypt('$5$' << Trocla::Util.salt << '$') + end +end diff --git a/lib/trocla/formats/sha512crypt.rb b/lib/trocla/formats/sha512crypt.rb new file mode 100644 index 0000000..47eb11e --- /dev/null +++ b/lib/trocla/formats/sha512crypt.rb @@ -0,0 +1,6 @@ +# salted crypt +class Trocla::Formats::Sha512crypt < Trocla::Formats::Base + def format(plain_password,options={}) + plain_password.crypt('$6$' << Trocla::Util.salt << '$') + end +end diff --git a/lib/trocla/formats/ssha.rb b/lib/trocla/formats/ssha.rb new file mode 100644 index 0000000..a2e0d02 --- /dev/null +++ b/lib/trocla/formats/ssha.rb @@ -0,0 +1,9 @@ +# salted crypt +require 'base64' +require 'digest' +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 + end +end diff --git a/lib/trocla/formats/x509.rb b/lib/trocla/formats/x509.rb new file mode 100644 index 0000000..219cd38 --- /dev/null +++ b/lib/trocla/formats/x509.rb @@ -0,0 +1,128 @@ +class Trocla::Formats::X509 < Trocla::Formats::Base + 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 + + 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 + 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 diff --git a/lib/trocla/util.rb b/lib/trocla/util.rb new file mode 100644 index 0000000..8e94d0d --- /dev/null +++ b/lib/trocla/util.rb @@ -0,0 +1,43 @@ +require 'securerandom' +class Trocla + class Util + class << self + def random_str(length=12, charset='default') + _charsets = charsets[charset] || charsets['default'] + _charsets_size = _charsets_size + (1..length).collect{|a| _charsets[SecureRandom.random_number(_charsets.size)] }.join.to_s + end + + def salt(length=8) + alphanumeric_size = alphanumeric.size + (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 ||= shellsafe + special_chars + end + def shellsafe + @chars ||= alphanumeric + shellsafe_chars + end + def alphanumeric + @alphanumeric ||= ('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/lib/trocla/version.rb b/lib/trocla/version.rb new file mode 100644 index 0000000..6d9476b --- /dev/null +++ b/lib/trocla/version.rb @@ -0,0 +1,22 @@ +# encoding: utf-8 +class Trocla + class VERSION + version = {} + File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line| + type, value = line.chomp.split(":") + next if type =~ /^\s+$/ || value =~ /^\s+$/ + version[type] = value + end + + MAJOR = version['major'] + MINOR = version['minor'] + PATCH = version['patch'] + BUILD = version['build'] + + STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.') + + def self.version + STRING + end + end +end diff --git a/spec/data/.keep b/spec/data/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/spec/data/.keep diff --git a/spec/trocla/util_spec.rb b/spec/trocla/util_spec.rb new file mode 100644 index 0000000..879b244 --- /dev/null +++ b/spec/trocla/util_spec.rb @@ -0,0 +1,28 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Trocla::Util" do + + { :random_str => 12, :salt => 8 }.each do |m,length| + describe m do + it "should be random" do + Trocla::Util.send(m).should_not eql(Trocla::Util.send(m)) + end + + it "should default to length #{length}" do + Trocla::Util.send(m).length.should == length + end + + it "should be possible to change length" do + Trocla::Util.send(m,8).length.should == 8 + Trocla::Util.send(m,32).length.should == 32 + Trocla::Util.send(m,1).length.should == 1 + end + end + end + + describe :salt do + it "should only contain characters and numbers" do + Trocla::Util.salt =~ /^[a-z0-9]+$/i + end + end +end diff --git a/spec/trocla_spec.rb b/spec/trocla_spec.rb new file mode 100644 index 0000000..61d9921 --- /dev/null +++ b/spec/trocla_spec.rb @@ -0,0 +1,128 @@ +require File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +describe "Trocla" do + + before(:each) do + Trocla.any_instance.expects(:read_config).returns(test_config) + @trocla = Trocla.new + end + + describe "password" do + it "should generate random passwords by default" do + @trocla.password('random1','plain').should_not eql(@trocla.password('random2','plain')) + end + + it "should generate passwords of length #{default_config['options']['length']}" do + @trocla.password('random1','plain').length.should eql(default_config['options']['length']) + end + + Trocla::Formats.all.each do |format| + describe "#{format} password format" do + it "should return a password hashed in the #{format} format" do + @trocla.password('some_test',format,format_options[format]).should_not be_empty + end + + it "should return the same hashed for the #{format} format on multiple invocations" do + (round1=@trocla.password('some_test',format,format_options[format])).should_not be_empty + @trocla.password('some_test',format,format_options[format]).should eql(round1) + end + + it "should also store the plain password by default" do + pwd = @trocla.password('some_test','plain') + pwd.should_not be_empty + pwd.length.should eql(12) + end + end + end + + Trocla::Formats.all.reject{|f| f == 'plain' }.each do |format| + it "should raise an exception if not a random password is asked but plain password is not present for format #{format}" do + lambda{ @trocla.password('not_random',format, 'random' => false) }.should raise_error + end + end + end + + describe "set_password" do + it "should reset hashed passwords on a new plain password" do + @trocla.password('set_test','mysql').should_not be_empty + @trocla.get_password('set_test','mysql').should_not be_nil + (old_plain=@trocla.password('set_test','mysql')).should_not be_empty + + @trocla.set_password('set_test','plain','foobar').should_not eql(old_plain) + @trocla.get_password('set_test','mysql').should be_nil + end + + it "should otherwise only update the hash" do + (mysql = @trocla.password('set_test2','mysql')).should_not be_empty + (md5crypt = @trocla.password('set_test2','md5crypt')).should_not be_empty + (plain = @trocla.get_password('set_test2','plain')).should_not be_empty + + (new_mysql = @trocla.set_password('set_test2','mysql','foo')).should_not eql(mysql) + @trocla.get_password('set_test2','mysql').should eql(new_mysql) + @trocla.get_password('set_test2','md5crypt').should eql(md5crypt) + @trocla.get_password('set_test2','plain').should eql(plain) + end + end + + describe "reset_password" do + it "should reset a password" do + plain1 = @trocla.password('reset_pwd','plain') + plain2 = @trocla.reset_password('reset_pwd','plain') + + plain1.should_not eql(plain2) + end + + it "should not reset other formats" do + (mysql = @trocla.password('reset_pwd2','mysql')).should_not be_empty + (md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).should_not be_empty + + (md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).should_not be_empty + md5crypt2.should_not eql(md5crypt1) + + @trocla.get_password('reset_pwd2','mysql').should eql(mysql) + end + end + + describe "delete_password" do + it "should delete all passwords if no format is given" do + @trocla.password('delete_test1','mysql').should_not be_nil + @trocla.get_password('delete_test1','plain').should_not be_nil + + @trocla.delete_password('delete_test1') + @trocla.get_password('delete_test1','plain').should be_nil + @trocla.get_password('delete_test1','mysql').should be_nil + end + + it "should delete only a given format" do + @trocla.password('delete_test2','mysql').should_not be_nil + @trocla.get_password('delete_test2','plain').should_not be_nil + + @trocla.delete_password('delete_test2','plain') + @trocla.get_password('delete_test2','plain').should be_nil + @trocla.get_password('delete_test2','mysql').should_not be_nil + end + + it "should delete only a given non-plain format" do + @trocla.password('delete_test3','mysql').should_not be_nil + @trocla.get_password('delete_test3','plain').should_not be_nil + + @trocla.delete_password('delete_test3','mysql') + @trocla.get_password('delete_test3','mysql').should be_nil + @trocla.get_password('delete_test3','plain').should_not be_nil + end + end + + describe "VERSION" do + it "should return a version" do + Trocla::VERSION::STRING.should_not be_empty + end + end + + def format_options + @format_options ||= Hash.new({}).merge({ + 'pgsql' => { 'username' => 'test' }, + 'x509' => { 'CN' => 'test' }, + }) + end + +end diff --git a/trocla.gemspec b/trocla.gemspec new file mode 100644 index 0000000..65c63f3 --- /dev/null +++ b/trocla.gemspec @@ -0,0 +1,89 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' +# -*- encoding: utf-8 -*- +# stub: trocla 0.0.11 ruby lib + +Gem::Specification.new do |s| + s.name = "trocla" + s.version = "0.0.11" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.require_paths = ["lib"] + s.authors = ["mh"] + s.date = "2014-08-21" + s.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival." + s.email = "mh+trocla@immerda.ch" + s.executables = ["trocla"] + s.extra_rdoc_files = [ + "LICENSE.txt", + "README.md" + ] + s.files = [ + ".document", + ".rspec", + ".travis.yml", + "Gemfile", + "Gemfile.lock", + "LICENSE.txt", + "README.md", + "Rakefile", + "bin/trocla", + "lib/VERSION", + "lib/trocla.rb", + "lib/trocla/default_config.yaml", + "lib/trocla/formats.rb", + "lib/trocla/formats/bcrypt.rb", + "lib/trocla/formats/md5crypt.rb", + "lib/trocla/formats/mysql.rb", + "lib/trocla/formats/pgsql.rb", + "lib/trocla/formats/plain.rb", + "lib/trocla/formats/sha1.rb", + "lib/trocla/formats/sha256crypt.rb", + "lib/trocla/formats/sha512crypt.rb", + "lib/trocla/formats/ssha.rb", + "lib/trocla/formats/x509.rb", + "lib/trocla/util.rb", + "lib/trocla/version.rb", + "spec/data/.keep", + "spec/spec_helper.rb", + "spec/trocla/util_spec.rb", + "spec/trocla_spec.rb", + "trocla.gemspec" + ] + s.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/" + s.licenses = ["GPLv3"] + s.rubygems_version = "2.3.0" + s.summary = "Trocla a simple password generator and storage" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q<moneta>, ["~> 0.7"]) + s.add_runtime_dependency(%q<highline>, [">= 0"]) + s.add_runtime_dependency(%q<bcrypt>, [">= 0"]) + s.add_development_dependency(%q<rspec>, ["~> 2.4"]) + s.add_development_dependency(%q<rdoc>, ["~> 3.8"]) + s.add_development_dependency(%q<mocha>, [">= 0"]) + s.add_development_dependency(%q<jeweler>, [">= 0"]) + else + s.add_dependency(%q<moneta>, ["~> 0.7"]) + s.add_dependency(%q<highline>, [">= 0"]) + s.add_dependency(%q<bcrypt>, [">= 0"]) + s.add_dependency(%q<rspec>, ["~> 2.4"]) + s.add_dependency(%q<rdoc>, ["~> 3.8"]) + s.add_dependency(%q<mocha>, [">= 0"]) + s.add_dependency(%q<jeweler>, [">= 0"]) + end + else + s.add_dependency(%q<moneta>, ["~> 0.7"]) + s.add_dependency(%q<highline>, [">= 0"]) + s.add_dependency(%q<bcrypt>, [">= 0"]) + s.add_dependency(%q<rspec>, ["~> 2.4"]) + s.add_dependency(%q<rdoc>, ["~> 3.8"]) + s.add_dependency(%q<mocha>, [">= 0"]) + s.add_dependency(%q<jeweler>, [">= 0"]) + end +end + |