commit e3deb729ceee13f2c713a840f5c575042b4b144d
parent 40e7af92630a78b2b4541fcde961c21d152690de
Author: Daniel Barlow <dan@telent.net>
Date: Mon, 9 Aug 2021 09:36:59 +0100
rename to "moss"
Diffstat:
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