[DRE-commits] [ruby-trocla] 01/01: Imported Upstream version 0.1.1

Jonas Genannt genannt at moszumanska.debian.org
Mon Jun 1 16:52:25 UTC 2015


This is an automated email from the git hooks/post-receive script.

genannt pushed a commit to branch master
in repository ruby-trocla.

commit 7e0344722a5b0fd69ef02a27d49d38c0d58436b4
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Mon Jun 1 18:52:09 2015 +0200

    Imported Upstream version 0.1.1
---
 .document                           |   4 +
 .rspec                              |   1 +
 .travis.yml                         |   6 +
 Gemfile                             |  29 +++++
 LICENSE.txt                         |  15 +++
 README.md                           | 218 ++++++++++++++++++++++++++++++++++++
 Rakefile                            |  53 +++++++++
 bin/trocla                          | 129 +++++++++++++++++++++
 lib/VERSION                         |   4 +
 lib/trocla.rb                       | 111 ++++++++++++++++++
 lib/trocla/default_config.yaml      |   8 ++
 lib/trocla/encryptions.rb           |  53 +++++++++
 lib/trocla/encryptions/none.rb      |  10 ++
 lib/trocla/encryptions/ssl.rb       |  53 +++++++++
 lib/trocla/formats.rb               |  40 +++++++
 lib/trocla/formats/bcrypt.rb        |   6 +
 lib/trocla/formats/md5crypt.rb      |   6 +
 lib/trocla/formats/mysql.rb         |   6 +
 lib/trocla/formats/pgsql.rb         |   7 ++
 lib/trocla/formats/plain.rb         |   7 ++
 lib/trocla/formats/sha1.rb          |   7 ++
 lib/trocla/formats/sha256crypt.rb   |   6 +
 lib/trocla/formats/sha512crypt.rb   |   6 +
 lib/trocla/formats/ssha.rb          |   9 ++
 lib/trocla/formats/x509.rb          | 145 ++++++++++++++++++++++++
 lib/trocla/util.rb                  |  53 +++++++++
 lib/trocla/version.rb               |  22 ++++
 metadata.yml                        | 178 +++++++++++++++++++++++++++++
 spec/data/.keep                     |   0
 spec/spec_helper.rb                 |  69 ++++++++++++
 spec/trocla/encryptions/ssl_spec.rb |  56 +++++++++
 spec/trocla/util_spec.rb            |  34 ++++++
 spec/trocla_spec.rb                 | 128 +++++++++++++++++++++
 trocla.gemspec                      |  92 +++++++++++++++
 34 files changed, 1571 insertions(+)

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
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..4e1e0d2
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8fc17b4
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: ruby
+rvm:
+  - 2.1.0
+  - 2.0.0
+  - 1.9.3
+  - 1.8.7
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..26559b6
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,29 @@
+source "http://rubygems.org"
+# Add dependencies required to use your gem here.
+# Example:
+#   gem "activesupport", ">= 2.3.5"
+
+if RUBY_VERSION.to_f > 1.8
+  gem "moneta"
+  gem "highline"
+else
+  gem "moneta", "~> 0.7.20"
+  gem "highline", "~> 1.6.2"
+end
+
+gem "bcrypt"
+
+# Add dependencies to develop your gem here.
+# Include everything needed to run rake, tests, features, etc.
+group :development do
+  gem "mocha"
+  if RUBY_VERSION.to_f > 1.8
+    gem "rspec"
+    gem "rdoc"
+    gem "jeweler"
+  else
+    gem "rspec", "~> 2.4"
+    gem "rdoc", "~> 3.8"
+    gem "jeweler", "~> 1.6"
+  end
+end
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/README.md b/README.md
new file mode 100644
index 0000000..ff66cb7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,218 @@
+# trocla
+[![Build Status](https://travis-ci.org/duritong/trocla.png)](https://travis-ci.org/duritong/trocla)
+
+Trocla provides you a simple way to create and store (random) passwords on a
+central server, which can be retrieved by other applications. An example for
+such an application is puppet and trocla can help you to not store any
+plaintext or hashed passwords in your manifests by keeping these passwords only
+on your puppetmaster.
+
+Furthermore it provides you a simple cli that helps you to modify the password
+storage from the cli.
+
+Trocla does not only create and/or store a plain password, it is also able to
+generate (and store) any kind hashed passwords based on the plain password.
+As long as the plain password is preset, trocla is able to generate any kind
+of hashed passwords through an easy extendible plugin system.
+
+It is not necessary to store the plain password on the server, you can also
+just feed trocla with the hashed password and use that in your other tools.
+A common example for that is that you let puppet retrieve (and hence create)
+a salted md5 password for a user. This will then store the salted md5 of
+a random password AND the plain text password in trocla. Later you can
+retrieve (by deleting) the plain password and send it to the user. Puppet
+will still simply retrieve the hashed password that is stored in trocla,
+while the plain password is not anymore stored on the server.
+
+You can use any kind of key/value based storage supported by moneta for
+trocla. By default it uses a simple yaml file.
+
+## Usage
+
+### create
+
+Assuming that we have an empty trocla storage.
+
+    trocla create user1 plain
+
+This will create (if not already stored in trocla) a random password and
+store its plain text under key user1. The password will also be returned
+by trocla.
+
+    trocla create user2 mysql
+
+This will create a random password and store its plain and mysql-style hashed
+sha1 password in trocla. The hashed password is returned.
+
+    trocla create user1 mysql
+
+This will take the already stored plain text password of key user1 and generate
+and store the mysql-style hashed sha1 password.
+
+It is possible that certain hash formats require additional options. For example
+the pgsql hash requires also the user to create the md5 hash for the password.
+You can pass these additional requirements as yaml-based strings to the format:
+
+    trocla create user1 pgsql 'username: user1'
+
+This will create a pgsql password hash using the username user1.
+
+Valid global options are:
+
+* length: int - Define any lenght that a newly created password should have. Default: 12 - or whatever you define in your global settings.
+* charset: (default|alphanumeric|shellsafe) - Which set of chars should be used for a random password? Default: default - or whatever you define in your global settings.
+
+Example:
+
+    trocla create some_shellsafe_password plain 'charset: shellsafe'
+    trocla create another_alphanumeric_20_char_password plain "charset: alphanumeric
+    length: 20"
+
+### get
+
+Get simply returns a stored password. It will not create a new password.
+
+Assuming that we are still working with the same storage
+
+    trocla get user2 plain
+
+will return the plain text password of the key user2.
+
+    trocla get user3 plain
+
+This will return nothing, as no password with this format have been stored so
+far.
+
+### set
+
+    trocla set user3 plain
+
+This will ask you for a password and set it under the appropriate key/format.
+
+    trocla set --password mysupersecretpassword user4 plain
+
+This will take the password from the cli without asking you.
+
+    trocla set user5 mysql -p *ABC....
+
+This will store a mysql sha1 hash for the key user5, without storing any kind
+of plain text password.
+
+You can also pipe in a password:
+
+    echo -n foo | trocla set user6 plain -p
+
+or a file
+
+    cat some_file | trocla set user6 plain -p
+    trocla set user6 plain -p < some_file
+
+### reset
+
+    trocla reset user1 md5crypt
+
+This will recreate the salted md5 shadow-style hash. However, it will not create
+a new plain text passwords. Hence, this is mainly usefull to create new hashed
+passwords based on new salts.
+
+If the plain password of a key is resetted, every already hashed password is
+deleted as well, as the hashes wouldn't match anymore the plain text password.
+
+### delete
+
+    trocla delete user1 plain
+
+This will delete the plain password of the key user1 and return it.
+
+## Attention
+
+If you don't feed trocla initially with a hash and/or delete the generated
+plain text passwords trocla will likely create a lot of plain text passwords
+and store them on your machine/server. This is by intend and is all about which
+problems (mainly passwords in configuration management manifests) trocla tries
+to address.
+
+## Installation
+
+Simply build and install the gem. 
+
+## Configuration
+
+Trocla can be configured in /etc/troclarc.yaml and in ~/.troclarc.yaml. A sample configuration file can be found in `lib/trocla/default_config.yaml`.
+
+### Storage backends
+
+Trocla can store your passwords in all backends supported by moneta. A simple YAML file configuration may look as follows:
+
+```YAML
+adapter: :YAML
+adapter_options:
+    :file: '/tmp/trocla.yaml'
+```
+
+In environments with multiple Puppet masters using an existing DB cluster might make sense. The configured user needs to be granted at least SELECT, INSERT, UPDATE, DELETE and CREATE permissions on your database:
+
+```YAML
+adapter: :Sequel
+adapter_options:
+    :db: 'mysql://db.server.name'
+    :user: 'trocla'
+    :password: '***'
+    :database: 'trocladb'
+    :table: 'trocla'
+```
+
+These examples are by no way complete, moneta has much more to offer.
+
+### SSL encryption
+
+You might want to let Trocla encrypt all your passwords
+
+```YAML
+encryption: :ssl
+ssl_options:
+    :private_key: '/var/lib/puppet/ssl/private_keys/trocla.pem'
+    :public_key: '/var/lib/puppet/ssl/public_keys/trocla.pem'
+```
+
+## Update & Changes
+
+### to 0.1.1
+
+1. fix storing data longer that public Keysize -11. Thanks [Timo Goebel](https://github.com/timogoebel)
+1. add a numeric only charset. Thanks [Jonas Genannt](https://github.com/hggh)
+1. fix reading key expire time. Thanks [asquelt](https://github.com/asquelt)
+
+### to 0.1.0
+
+1. Supporting encryption of the backends. Many thanks to Thomas Gelf
+1. Adding a windows safe password charset
+
+### to 0.0.12
+
+1. change from sha1 signature for the x509 format to sha2
+1. Fix an issue where shellsafe characters might have already been initialized with shell-unsafe characters. Plz review any shell-safe character passwords regarding this problem. See the [fix](https://github.com/duritong/trocla/pull/19) for more information. Thanks [asquelt](https://github.com/asquelt) for the fix.
+
+### to 0.0.8
+
+1. be sure to update as well the moneta gem, trocla now uses the official moneta releases and supports current avaiable versions.
+1. Options for moneta's backends have changed. For example, if you are using the yaml-backend you will likely need to change the adapter option `:path:` to `:file:` to match moneta's new API.
+1. **IMPORTANT:** If you are using the yaml backend you need to migrate the current data *before* using the new trocla version! You can migrate the datastore by using the following two sed commands: `sed -i 's/^\s\{3\}/ /' /PATH/TO/trocla_data.yaml` && `sed -i '/^\s\{2\}value\:/d' /PATH/TO/trocla_data.yaml`.
+1. **SECURITY:** Previous versions of trocla used quite a simple random generator. Especially in combination with the puppet `fqdn_rand` function, you likely have very predictable random passwords and I recommend you to regenerate all randomly generated passwords! Now!
+1. We now support reading passwords from files, which means that you can now also easily add multi-line passwords. Have a look at the documentation above.
+
+## Contributing to trocla
+ 
+* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
+* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
+* Fork the project
+* Start a feature/bugfix branch
+* Commit and push until you are happy with your contribution
+* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
+* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
+
+## Copyright
+
+Copyright (c) 2015 mh. See LICENSE.txt for
+further details.
+
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..9b1114b
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+require 'rubygems'
+require 'bundler'
+begin
+  Bundler.setup(:default, :development)
+rescue Bundler::BundlerError => e
+  $stderr.puts e.message
+  $stderr.puts "Run `bundle install` to install missing gems"
+  exit e.status_code
+end
+require 'rake'
+
+
+require 'jeweler'
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
+require 'trocla'
+Jeweler::Tasks.new do |gem|
+  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
+  gem.name = "trocla"
+  gem.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/"
+  gem.license = "GPLv3"
+  gem.summary = "Trocla a simple password generator and storage" 
+  gem.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival."
+  gem.email = "mh+trocla at immerda.ch"
+  gem.authors = ["mh"]
+  gem.version = Trocla::VERSION::STRING
+  # dependencies defined in Gemfile
+end
+Jeweler::RubygemsDotOrgTasks.new
+
+require 'rspec/core'
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new(:spec) do |spec|
+  spec.pattern = FileList['spec/**/*_spec.rb']
+end
+
+RSpec::Core::RakeTask.new(:rcov) do |spec|
+  spec.pattern = 'spec/**/*_spec.rb'
+  spec.rcov = true
+end
+
+task :default => :spec
+
+gem 'rdoc'
+require 'rdoc/task'
+RDoc::Task.new do |rdoc|
+  version = Trocla::VERSION::STRING
+  rdoc.rdoc_dir = 'rdoc'
+  rdoc.title = "trocla #{version}"
+  rdoc.rdoc_files.include('README*')
+  rdoc.rdoc_files.include('lib/**/*.rb')
+end
diff --git a/bin/trocla b/bin/trocla
new file mode 100755
index 0000000..85bed57
--- /dev/null
+++ b/bin/trocla
@@ -0,0 +1,129 @@
+#!/usr/bin/env ruby
+# CLI client for Trocla.
+#
+require 'rubygems'
+require 'trocla'
+require 'optparse'
+require 'yaml'
+
+options = { :config_file => nil, :ask_password => true, :trace => false }
+
+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("--trace", "Show stack trace on failure") do
+    options[:trace] = true
+  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
+      unless e.message == 'exit'
+        STDERR.puts "Action failed with the following message: #{e.message}"
+        STDERR.puts "(See full trace by running task with --trace)"
+      end
+      raise e if options[:trace]
+      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..66a6ca7
--- /dev/null
+++ b/lib/VERSION
@@ -0,0 +1,4 @@
+major:0
+minor:1
+patch:1
+build:
diff --git a/lib/trocla.rb b/lib/trocla.rb
new file mode 100644
index 0000000..a94d52f
--- /dev/null
+++ b/lib/trocla.rb
@@ -0,0 +1,111 @@
+require 'trocla/version'
+require 'trocla/util'
+require 'trocla/formats'
+require 'trocla/encryptions'
+
+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)
+    decrypt(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?
+      decrypt(cache.delete(key))
+    else
+      old_val = (h = cache.fetch(key,{})).delete(format)
+      h.empty? ? cache.delete(key) : cache[key] = h
+      decrypt(old_val)
+    end
+  end
+
+  def set_password(key,format,password)
+    if (format == 'plain')
+      h = (cache[key] = { 'plain' => encrypt(password) })
+    else
+      h = (cache[key] = cache.fetch(key,{}).merge({ format => encrypt(password) }))
+    end
+    decrypt h[format]
+  end
+
+  def formats(format)
+    (@format_cache||={})[format] ||= Trocla::Formats[format].new(self)
+  end
+
+  def encryption
+    enc = config['encryption']
+    enc ||= :none
+    @encryption ||= Trocla::Encryptions[enc].new(self)
+    @encryption
+  end
+
+  def config
+    @config ||= read_config
+  end
+
+  private
+  def cache
+    @cache ||= build_cache
+  end
+
+  def build_cache
+    require 'moneta'
+    lconfig = config
+    Moneta.new(lconfig['adapter'], lconfig['adapter_options']||{})
+  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 encrypt(value)
+    encryption.encrypt(value)
+  end
+
+  def decrypt(value)
+    return nil if value.nil?
+    encryption.decrypt(value)
+  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/encryptions.rb b/lib/trocla/encryptions.rb
new file mode 100644
index 0000000..6823665
--- /dev/null
+++ b/lib/trocla/encryptions.rb
@@ -0,0 +1,53 @@
+class Trocla::Encryptions
+
+  class Base
+    attr_reader :trocla
+    def initialize(trocla)
+      @trocla = trocla
+    end
+
+    def encrypt(value)
+      raise NoMethodError.new("#{self.class.name} needs to implement 'encrypt()'")
+    end
+
+    def decrypt(value)
+      raise NoMethodError.new("#{self.class.name} needs to implement 'decrypt()'")
+    end
+  end
+
+  class << self
+    def [](enc)
+      encryptions[enc.to_s.downcase]
+    end
+
+    def all
+      Dir[ path '*' ].collect do |enc|
+        File.basename(enc, '.rb').downcase
+      end
+    end
+
+    def available?(encryption)
+      all.include?(encryption.to_s.downcase)
+    end
+
+    private
+    def encryptions
+      @@encryptions ||= Hash.new do |hash, encryption|
+        encryption = encryption.to_s.downcase
+        if File.exists?( path encryption )
+          require "trocla/encryptions/#{encryption}"
+          class_name = "Trocla::Encryptions::#{encryption.capitalize}"
+          hash[encryption] = (eval class_name)
+        else
+          raise "Encryption #{encryption} is not supported!"
+        end
+      end
+    end
+
+    def path(encryption)
+      File.expand_path(
+        File.join(File.dirname(__FILE__), 'encryptions', "#{encryption}.rb")
+      )
+    end
+  end
+end
diff --git a/lib/trocla/encryptions/none.rb b/lib/trocla/encryptions/none.rb
new file mode 100644
index 0000000..99779e4
--- /dev/null
+++ b/lib/trocla/encryptions/none.rb
@@ -0,0 +1,10 @@
+class Trocla::Encryptions::None < Trocla::Encryptions::Base
+  def encrypt(value)
+    value
+  end
+
+  def decrypt(value)
+    value
+  end
+end
+
diff --git a/lib/trocla/encryptions/ssl.rb b/lib/trocla/encryptions/ssl.rb
new file mode 100644
index 0000000..f3cc1fb
--- /dev/null
+++ b/lib/trocla/encryptions/ssl.rb
@@ -0,0 +1,53 @@
+require 'openssl'
+require 'base64'
+
+class Trocla::Encryptions::Ssl < Trocla::Encryptions::Base
+  def encrypt(value)
+    ciphertext = ''
+    value.scan(/.{0,#{chunksize}}/m).each do |chunk|
+      ciphertext += Base64.encode64(public_key.public_encrypt(chunk)).gsub("\n",'')+"\n" if chunk
+    end
+    ciphertext
+  end
+
+  def decrypt(value)
+    plaintext = ''
+    value.split(/\n/).each do |line|
+      plaintext += private_key.private_decrypt(Base64.decode64(line)) if line
+    end
+    plaintext
+  end
+
+  private
+
+  def chunksize
+      public_key.n.num_bytes - 11
+  end
+
+  def private_key
+      pass = nil
+      file = require_option(:private_key)
+      @private_key ||= OpenSSL::PKey::RSA.new(File.read(file), nil)
+  end
+
+  def public_key
+      file = require_option(:public_key)
+      @public_key ||= OpenSSL::PKey::RSA.new(File.read(file), nil)
+  end
+
+  def config
+    @config = @trocla.config['ssl_options']
+    @config ||= Hash.new
+  end
+
+  def option(key)
+    config[key]
+  end
+
+  def require_option(key)
+    val = option(key)
+    raise "Config error: 'ssl_options' => :#{key} is not defined" if val.nil?
+    val
+  end
+end
+
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..8d1cd9a
--- /dev/null
+++ b/lib/trocla/formats/x509.rb
@@ -0,0 +1,145 @@
+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
+    hash = options['hash'] || 'sha2'
+    sign_with = options['ca'] || nil
+    keysize = options['keysize'] || 2048
+    serial = options['serial'] || 1
+    days = options['days'].to_i || 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, signature(hash))
+      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, signature(hash))
+        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, signature(hash))
+      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 signature(hash = 'sha2')
+    if hash == 'sha1'
+        OpenSSL::Digest::SHA1.new
+    elsif hash == 'sha224'
+        OpenSSL::Digest::SHA224.new
+    elsif hash == 'sha2' || hash == 'sha256'
+        OpenSSL::Digest::SHA256.new
+    elsif hash == 'sha384'
+        OpenSSL::Digest::SHA384.new
+    elsif hash == 'sha512'
+        OpenSSL::Digest::SHA512.new
+    else
+        raise "Unrecognized hash: #{hash}"
+    end
+  end
+
+  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..0e14eb3
--- /dev/null
+++ b/lib/trocla/util.rb
@@ -0,0 +1,53 @@
+require 'securerandom'
+class Trocla
+  class Util
+    class << self
+      def random_str(length=12, charset='default')
+        _charsets = charsets[charset] || charsets['default']
+        (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,
+          'windowssafe'   => windowssafe,
+          'numeric'       => numeric,
+        }
+      end
+
+      def chars
+        @chars ||= shellsafe + special_chars
+      end
+      def shellsafe
+        @shellsafe ||= alphanumeric + shellsafe_chars
+      end
+      def windowssafe
+        @windowssafe ||= alphanumeric + windowssafe_chars
+      end
+      def alphanumeric
+        @alphanumeric ||= ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
+      end
+      def numeric
+        @numeric ||= ('0'..'9').to_a
+      end
+      def special_chars
+        @special_chars ||= "*()&![]{}-".split(//)
+      end
+      def shellsafe_chars
+        @shellsafe_chars ||= "+%/@=?_.,:".split(//)
+      end
+      def windowssafe_chars
+        @windowssafe_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/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..875be41
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,178 @@
+--- !ruby/object:Gem::Specification
+name: trocla
+version: !ruby/object:Gem::Version
+  version: 0.1.1
+platform: ruby
+authors:
+- mh
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2015-04-19 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: moneta
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: highline
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: bcrypt
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: mocha
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rspec
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rdoc
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: jeweler
+  requirement: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    requirements:
+    - - ">="
+      - !ruby/object:Gem::Version
+        version: '0'
+description: Trocla helps you to generate random passwords and to store them in various
+  formats (plain, MD5, bcrypt) for later retrival.
+email: mh+trocla at immerda.ch
+executables:
+- trocla
+extensions: []
+extra_rdoc_files:
+- LICENSE.txt
+- README.md
+files:
+- ".document"
+- ".rspec"
+- ".travis.yml"
+- Gemfile
+- LICENSE.txt
+- README.md
+- Rakefile
+- bin/trocla
+- lib/VERSION
+- lib/trocla.rb
+- lib/trocla/default_config.yaml
+- lib/trocla/encryptions.rb
+- lib/trocla/encryptions/none.rb
+- lib/trocla/encryptions/ssl.rb
+- 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/encryptions/ssl_spec.rb
+- spec/trocla/util_spec.rb
+- spec/trocla_spec.rb
+- trocla.gemspec
+homepage: https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/
+licenses:
+- GPLv3
+metadata: {}
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">="
+    - !ruby/object:Gem::Version
+      version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+  requirements:
+  - - ">="
+    - !ruby/object:Gem::Version
+      version: '0'
+requirements: []
+rubyforge_project: 
+rubygems_version: 2.3.0
+signing_key: 
+specification_version: 4
+summary: Trocla a simple password generator and storage
+test_files: []
diff --git a/spec/data/.keep b/spec/data/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..10491d8
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,69 @@
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+require 'rspec'
+require 'mocha'
+require 'yaml'
+require 'trocla'
+
+# Requires supporting files with custom matchers and macros, etc,
+# in ./support/ and its subdirectories.
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
+
+RSpec.configure do |config|
+  
+end
+
+def default_config
+  @default_config ||= YAML.load(File.read(File.expand_path(base_dir+'/lib/trocla/default_config.yaml')))
+end
+
+def test_config
+  return @config unless @config.nil?
+  @config = default_config
+  @config.delete('adapter_options')
+  @config['adapter'] = :Memory
+  @config
+end
+
+def ssl_test_config
+  return @ssl_config unless @ssl_config.nil?
+  @ssl_config = test_config
+  @ssl_config['encryption'] = :ssl
+  @ssl_config['ssl_options'] = {
+    :private_key => data_dir('trocla.key'),
+    :public_key  => data_dir('trocla.pub')
+  }
+  @ssl_config['adapter'] = :YAML
+  @ssl_config['adapter_options'] = {
+    :file => trocla_yaml_file
+  }
+  @ssl_config
+end
+
+def base_dir
+  File.dirname(__FILE__)+'/../'
+end
+
+def data_dir(file = nil)
+  File.expand_path(File.join(base_dir, 'spec/data', file))
+end
+
+def trocla_yaml_file
+  data_dir('trocla_store.yaml')
+end
+
+def generate_ssl_keys
+  require 'openssl'
+  rsa_key = OpenSSL::PKey::RSA.new(4096)
+  File.open(data_dir('trocla.key'), 'w') { |f| f.write(rsa_key.to_pem) }
+  File.open(data_dir('trocla.pub'), 'w') { |f| f.write(rsa_key.public_key.to_pem) }
+end
+
+def remove_ssl_keys
+  File.unlink(data_dir('trocla.key'))
+  File.unlink(data_dir('trocla.pub'))
+end
+
+def remove_yaml_store
+  File.unlink(trocla_yaml_file)
+end
diff --git a/spec/trocla/encryptions/ssl_spec.rb b/spec/trocla/encryptions/ssl_spec.rb
new file mode 100644
index 0000000..bf687a2
--- /dev/null
+++ b/spec/trocla/encryptions/ssl_spec.rb
@@ -0,0 +1,56 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "Trocla::Encryptions::Ssl" do
+
+  before(:all) do
+    generate_ssl_keys
+  end
+
+  after(:all) do
+    remove_ssl_keys
+  end
+
+  before(:each) do
+    expect_any_instance_of(Trocla).to receive(:read_config).and_return(ssl_test_config)
+    @trocla = Trocla.new
+  end
+
+  after(:each) do
+    remove_yaml_store
+  end
+
+  describe "encrypt" do
+    it "should be able to store random passwords" do
+      @trocla.password('random1', 'plain').length.should eql(12)
+    end
+
+    it "should be able to store long random passwords" do
+      @trocla.set_password('random1_long','plain',4096.times.collect{|s| 'x' }.join('')).length.should eql(4096)
+    end
+
+    it "should be able to retrieve stored random passwords" do
+      stored = @trocla.password('random1', 'plain')
+      retrieved = @trocla.password('random1', 'plain')
+      retrieved_again = @trocla.password('random1', 'plain')
+      retrieved.should eql(stored)
+      retrieved_again.should eql(stored)
+    end
+
+    it "should be able to read encrypted passwords" do
+      @trocla.set_password('some_pass', 'plain', 'super secret')
+      @trocla.get_password('some_pass', 'plain').should eql('super secret')
+    end
+
+    it "should not store plaintext passwords" do
+      @trocla.set_password('noplain', 'plain', 'plaintext_password')
+      File.readlines(trocla_yaml_file).grep(/plaintext_password/).should be_empty
+    end
+
+    it "should make sure identical passwords do not match when stored" do
+      @trocla.set_password('one_key', 'plain', 'super secret')
+      @trocla.set_password('another_key', 'plain', 'super secret')
+      yaml = YAML.load_file(trocla_yaml_file)
+      yaml['one_key']['plain'].should_not eql(yaml['another_key']['plain'])
+    end
+  end
+end
diff --git a/spec/trocla/util_spec.rb b/spec/trocla/util_spec.rb
new file mode 100644
index 0000000..4d49150
--- /dev/null
+++ b/spec/trocla/util_spec.rb
@@ -0,0 +1,34 @@
+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 :numeric_generator do
+    it "should create random numeric password" do
+      Trocla::Util.send(:random_str, 12, 'numeric' ).should =~ /^[0-9]{12}$/
+    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..dbd4abd
--- /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
+    expect_any_instance_of(Trocla).to receive(:read_config).and_return(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
+  
+  def format_options
+    @format_options ||= Hash.new({}).merge({
+      'pgsql' => { 'username' => 'test' },
+      'x509'  => { 'CN' => 'test' },
+    })
+  end
+  
+end
+
+describe "VERSION" do
+  it "should return a version" do
+    Trocla::VERSION::STRING.should_not be_empty
+  end
+end
diff --git a/trocla.gemspec b/trocla.gemspec
new file mode 100644
index 0000000..158fecd
--- /dev/null
+++ b/trocla.gemspec
@@ -0,0 +1,92 @@
+# 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.1.1 ruby lib
+
+Gem::Specification.new do |s|
+  s.name = "trocla"
+  s.version = "0.1.1"
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+  s.require_paths = ["lib"]
+  s.authors = ["mh"]
+  s.date = "2015-04-19"
+  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 at immerda.ch"
+  s.executables = ["trocla"]
+  s.extra_rdoc_files = [
+    "LICENSE.txt",
+    "README.md"
+  ]
+  s.files = [
+    ".document",
+    ".rspec",
+    ".travis.yml",
+    "Gemfile",
+    "LICENSE.txt",
+    "README.md",
+    "Rakefile",
+    "bin/trocla",
+    "lib/VERSION",
+    "lib/trocla.rb",
+    "lib/trocla/default_config.yaml",
+    "lib/trocla/encryptions.rb",
+    "lib/trocla/encryptions/none.rb",
+    "lib/trocla/encryptions/ssl.rb",
+    "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/encryptions/ssl_spec.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"])
+      s.add_runtime_dependency(%q<highline>, [">= 0"])
+      s.add_runtime_dependency(%q<bcrypt>, [">= 0"])
+      s.add_development_dependency(%q<mocha>, [">= 0"])
+      s.add_development_dependency(%q<rspec>, [">= 0"])
+      s.add_development_dependency(%q<rdoc>, [">= 0"])
+      s.add_development_dependency(%q<jeweler>, [">= 0"])
+    else
+      s.add_dependency(%q<moneta>, [">= 0"])
+      s.add_dependency(%q<highline>, [">= 0"])
+      s.add_dependency(%q<bcrypt>, [">= 0"])
+      s.add_dependency(%q<mocha>, [">= 0"])
+      s.add_dependency(%q<rspec>, [">= 0"])
+      s.add_dependency(%q<rdoc>, [">= 0"])
+      s.add_dependency(%q<jeweler>, [">= 0"])
+    end
+  else
+    s.add_dependency(%q<moneta>, [">= 0"])
+    s.add_dependency(%q<highline>, [">= 0"])
+    s.add_dependency(%q<bcrypt>, [">= 0"])
+    s.add_dependency(%q<mocha>, [">= 0"])
+    s.add_dependency(%q<rspec>, [">= 0"])
+    s.add_dependency(%q<rdoc>, [">= 0"])
+    s.add_dependency(%q<jeweler>, [">= 0"])
+  end
+end
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-trocla.git



More information about the Pkg-ruby-extras-commits mailing list