lib/keystorage/manager.rb
# coding: utf-8
require 'keystorage'
module Keystorage
# ks = keystorage::Manager.new(:file =>"",:secret=> "P@ssword")
# ks.get("mygroup","mykey") # => "mysecret"
class Manager
include Keystorage
attr_reader :options
def initialize options = {}
@options = options
end
def groups
file.keys.delete_if {|i| i == "@" }
end
def keys(group)
file[group].keys
end
def get(group,key)
raise SecretMissMatch unless valid?
decode(file[group][key])
end
def set(group,key,value)
raise RejectGroupName.new("Cannot use '@' for group name.") if group == "@"
raise SecretMissMatch unless valid?
data = file
data[group] = {} unless data.has_key?(group)
data[group][key] = {} unless data[group].has_key?(key)
data[group][key] = encode(value)
write(data)
data[group][key]
rescue Errno::ENOENT
write({})
retry
end
def password new_secret
raise SecretMissMatch unless valid?
# update passwords
data = file.each { |name,keys|
next if name == "@"
keys.each { |key,value|
keys[key] = encode(decode(value),new_secret)
}
}
# update root group and write to file
write root!(new_secret,data)
rescue Errno::ENOENT
write({})
retry
end
def exec *cmd
raise SecretMissMatch unless valid?
system(envs.collect{ |k,v| "#{k}='#{v}'" }.join(' ') + " " + cmd.join(' '))
end
private
def envs
result = {}
groups.each { |g|
keys(g).each { |k|
result["#{g}_#{k}"] = get(g,k)
}
}
result
end
def sign message,_secret=secret
raise NoSecret.new("set env KEYSTORAGE_SECRET") unless _secret
OpenSSL::HMAC.hexdigest('sha512',_secret, message)
end
def token
SecureRandom.urlsafe_base64(nil, false)
end
def root
raise NoRootGroup unless file.has_key?("@")
file["@"] || {}
end
def root! _secret=secret,data=file
data["@"] = {}
data["@"]["token"] = token
data["@"]["sig"] = sign(data["@"]["token"],_secret)
data
end
# file validation
def valid?
sign(root["token"]) == root["sig"]
rescue NoRootGroup
write root! and true
end
def encode(str,_secret=secret)
enc = OpenSSL::Cipher::Cipher.new('aes256')
enc.encrypt.pkcs5_keyivgen(_secret)
((enc.update(str) + enc.final).unpack("H*")).first.to_s
end
def decode(str,_secret=secret)
dec = OpenSSL::Cipher::Cipher.new('aes256')
dec.decrypt.pkcs5_keyivgen(_secret)
(dec.update(Array.new([str]).pack("H*")) + dec.final)
end
def path
options[:file] || ENV['KEYSTORAGE_FILE'] || DEFAULT_FILE
end
def file
YAML.load(File.new(path)) || {}
end
def write data
FileUtils.mkdir_p(File.dirname(path))
File.open(path,'w',0600) { |f| YAML.dump(data,f) }
end
def secret
options[:secret] || ENV['KEYSTORAGE_SECRET'] || DEFAULT_SECRET
end
end
end