moss

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit e3deb729ceee13f2c713a840f5c575042b4b144d
parent 40e7af92630a78b2b4541fcde961c21d152690de
Author: Daniel Barlow <dan@telent.net>
Date:   Mon,  9 Aug 2021 09:36:59 +0100

rename to "moss"

Diffstat:
MREADME.md | 13++++++++++---
Rfeatures/rjpass.feature -> features/moss.feature | 0
Mfeatures/step_definitions/steps.rb | 18+++++++++---------
Amoss.rb | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Drjpass.rb | 79-------------------------------------------------------------------------------
5 files changed, 98 insertions(+), 91 deletions(-)

diff --git a/README.md b/README.md @@ -1,11 +1,18 @@ +MOSS - command-line password manager using age-encryption + +* PGP is "Pretty Good Privacy" +* AGE is "Actuallty Good Encryption" +* MOSS is the Maybe OK Secret Store Yes, it's yet another pass-like program using age instead of GPG, because it seemed like less effort to write one than to understand the -uses and limitations of anyone else's +uses and limitations of anyone else's. I am not a security +professional, so the best offer I can make for its abilities is that +it *may* be OK -* rjpass.rb is a standalone Ruby file that uses nothing but stdlib +* moss.rb is a standalone Ruby file that uses nothing but stdlib -* The scenarios in features/rjpass.feature should illustrate what it does: +* The scenarios in features/moss.feature should illustrate what it does: add, generate, edit, show a password, search password names. * the Gemfile is needed for installing Cucumber diff --git a/features/rjpass.feature b/features/moss.feature diff --git a/features/step_definitions/steps.rb b/features/step_definitions/steps.rb @@ -1,4 +1,4 @@ -RJPASS="./rjpass.rb" +MOSS="./moss.rb" require 'tmpdir' require 'fileutils' @@ -10,25 +10,25 @@ def shell(s) end def store_path(s) - Pathname.new(ENV["RJPASS_STORE"]).join(s) + Pathname.new(ENV["MOSS_STORE"]).join(s) end Given("I am using a temporary password store") do - ENV["RJPASS_STORE"] = Dir.mktmpdir + ENV["MOSS_STORE"] = Dir.mktmpdir Dir.mkdir(store_path(".age")) FileUtils.cp("fixtures/store/.age/identity", store_path(".age/identity")) end Given("I am using the example password store") do - ENV["RJPASS_STORE"] = "fixtures/store" + ENV["MOSS_STORE"] = "fixtures/store" end When("I generate a secret for {string} with length {int}") do |name, length| - @i_see = shell "#{RJPASS} generate #{name} #{length}" + @i_see = shell "#{MOSS} generate #{name} #{length}" end When("I store a secret for {string} with content {string}") do |name, content| - shell "echo -n #{content} | #{RJPASS} insert #{name}" + shell "echo -n #{content} | #{MOSS} insert #{name}" end Then("I see a {int} character string") do |len| @@ -56,16 +56,16 @@ Then("{string} plaintext matches {word}") do |name, re| end When("I view the secret {string}") do |name| - @i_see = shell "#{RJPASS} cat #{name}" + @i_see = shell "#{MOSS} cat #{name}" end When("I search for {string}") do |term| - @i_see = shell "#{RJPASS} search #{term}" + @i_see = shell "#{MOSS} search #{term}" end When("I edit {string}") do |name| ENV["EDITOR"] = "./fixtures/fakedit.sh" - @i_see = shell "#{RJPASS} edit #{name}" + @i_see = shell "#{MOSS} edit #{name}" end Then("the editor opens a temporary file containing {string}") do |expected| diff --git a/moss.rb b/moss.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'tempfile' + +STORE = Pathname.new(ENV['MOSS_STORE'] || "/tmp/store") +IDENTITY = "age1wc392uhfm04sy5nmpu86ea077ymjxs6e3gnda54jvpmuqchj9d5s6d0q2x" + +def random_alnum(length) + bytes = File.open("/dev/urandom", "rb") do |random| + random.read(length).unpack("C*").map {|c| + c = c % 62 + case + when c < 26 + ('A'.ord + c) + when c < 52 + ('a'.ord + (c-26)) + else + ('0'.ord + (c-52)) + end + } + end + bytes.pack("C*") +end + +def write_secret(name, content) + pathname = STORE.join("#{name}.age") + pathname.dirname.mkpath + IO.popen("age -a -r #{IDENTITY} -o #{pathname.to_s}", "w") do |f| + f.write(content) + end +end + +def read_secret(name) + pathname = STORE.join("#{name}.age") + File.exist?(pathname) or raise "Can't open #{pathname}: $!" + `age -i #{STORE.join(".age/identity")} -d #{pathname}` +end + +action, *parameters = ARGV +case action +when 'generate' + file , len = parameters + secret = random_alnum(len.to_i) + print secret + write_secret(file, secret) +when 'insert' + file, = parameters + secret = STDIN.read + write_secret(file, secret) +when 'cat','show' + file, = parameters + STDOUT.write(read_secret(file)) +when 'edit' + file, = parameters + content = read_secret(file) + Tempfile.create(file.gsub(/[\W]/,"")) do |f| + f.write(content) + f.flush + if Kernel.system(ENV['EDITOR'], f.path) + f.rewind + secret = f.read + write_secret(file, secret) + else + raise "#{$!}" + end + end +when 'search' + term = parameters.join(" ") + files = Dir[STORE.join('**/*.age')].map {|n| + Pathname.new(n).relative_path_from(STORE).sub_ext('').to_s + }.filter {|f| + f.match(Regexp.new(term)) + } + files.each do |n| + puts n + end +else + raise "command #{action} unrecognized" +end diff --git a/rjpass.rb b/rjpass.rb @@ -1,79 +0,0 @@ -#!/usr/bin/env ruby -require 'pathname' -require 'tempfile' - -STORE = Pathname.new(ENV['RJPASS_STORE'] || "/tmp/store") -IDENTITY = "age1wc392uhfm04sy5nmpu86ea077ymjxs6e3gnda54jvpmuqchj9d5s6d0q2x" - -def random_alnum(length) - bytes = File.open("/dev/urandom", "rb") do |random| - random.read(length).unpack("C*").map {|c| - c = c % 62 - case - when c < 26 - ('A'.ord + c) - when c < 52 - ('a'.ord + (c-26)) - else - ('0'.ord + (c-52)) - end - } - end - bytes.pack("C*") -end - -def write_secret(name, content) - pathname = STORE.join("#{name}.age") - pathname.dirname.mkpath - IO.popen("age -a -r #{IDENTITY} -o #{pathname.to_s}", "w") do |f| - f.write(content) - end -end - -def read_secret(name) - pathname = STORE.join("#{name}.age") - File.exist?(pathname) or raise "Can't open #{pathname}: $!" - `age -i #{STORE.join(".age/identity")} -d #{pathname}` -end - -action, *parameters = ARGV -case action -when 'generate' - file , len = parameters - secret = random_alnum(len.to_i) - print secret - write_secret(file, secret) -when 'insert' - file, = parameters - secret = STDIN.read - write_secret(file, secret) -when 'cat','show' - file, = parameters - STDOUT.write(read_secret(file)) -when 'edit' - file, = parameters - content = read_secret(file) - Tempfile.create(file.gsub(/[\W]/,"")) do |f| - f.write(content) - f.flush - if Kernel.system(ENV['EDITOR'], f.path) - f.rewind - secret = f.read - write_secret(file, secret) - else - raise "#{$!}" - end - end -when 'search' - term = parameters.join(" ") - files = Dir[STORE.join('**/*.age')].map {|n| - Pathname.new(n).relative_path_from(STORE).sub_ext('').to_s - }.filter {|f| - f.match(Regexp.new(term)) - } - files.each do |n| - puts n - end -else - raise "command #{action} unrecognized" -end