commit 5aa1f9c25aecf99e7257cd1699c9e1b560613d0b
parent 732ad3eca2f550d6b155ba7108140dbfbde06b2e
Author: Daniel Barlow <dan@telent.net>
Date: Tue, 10 Aug 2021 23:27:54 +0100
support encrypted identity
Diffstat:
6 files changed, 63 insertions(+), 7 deletions(-)
diff --git a/features/moss.feature b/features/moss.feature
@@ -85,4 +85,11 @@ Feature: command-line password management
When I create a moss instance with identity "me.key"
Then the instance store exists
And the instance identity is "me.key"
- And the store root contains .recipients for the identity "me.key"
+ And the store root has .recipients for the identity "me.key"
+
+ Scenario: I can create a new store with an encrypted key
+ Given I set MOSS_STORE to a unique temporary pathname
+ When I interactively create a moss instance with identity "encrypted.key" and passphrase "hello foo"
+ Then the instance store exists
+ And the instance identity is "encrypted.key"
+ And the store root has recipient "age1qqawnse9rfxjun2xmklk3r2pudlfjx0z4utjuwlmq7t9l60z9e2sap9vc8"
diff --git a/features/step_definitions/steps.rb b/features/step_definitions/steps.rb
@@ -25,13 +25,24 @@ def add_to_store(path, content)
end
def recipient_for_identity(identity_file)
- File.open(identity_file).read.match(/(age1.+)$/).captures.first
+ File.open(identity_file).read.match(/(age1.+)$/).captures.first or
+ raise "can't get pubkey for identity #{identity_file}"
end
+
+Given("I have the identity {string}") do |keyfile|
+ @identity_path = "fixtures/keys/#{keyfile}"
+end
+
+Given("I set MOSS_STORE to a unique temporary pathname") do
+ ENV["MOSS_STORE"] = Dir.mktmpdir + "/store"
+end
+
Given("I am using a temporary password store") do
+ @identity_path ||= "fixtures/keys/me.key"
ENV["MOSS_STORE"] = Dir.mktmpdir + "/store"
- add_to_store(".recipients", recipient_for_identity("fixtures/keys/me.key"))
+ add_to_store(".recipients", recipient_for_identity(@identity_path))
end
Given("I am using the example password store") do
@@ -148,6 +159,10 @@ When("I create a moss instance with identity {string}") do |keyfile|
shell "#{MOSS} init fixtures/keys/#{keyfile}"
end
+When("I interactively create a moss instance with identity {string} and passphrase {string}") do |keyfile, passphrase|
+ shell "expect moss-init-with-passphrase.expect fixtures/keys/#{keyfile} #{passphrase.inspect}"
+end
+
Then("the instance store exists") do
expect(Pathname.new(ENV['MOSS_STORE'])).to be_directory
end
@@ -156,11 +171,15 @@ Then("the instance identity is {string}") do |keyfile|
expect(store_path("../identity").read).to eq File.read("fixtures/keys/#{keyfile}")
end
-Then("the store root contains .recipients for the identity {string}") do |keyfile|
+Then("the store root has .recipients for the identity {string}") do |keyfile|
expected = `age-keygen -y fixtures/keys/#{keyfile.inspect}`
expect(store_path(".recipients").read).to eq expected
end
+Then("the store root has recipient {string}") do |recipient|
+ expect(store_path(".recipients").read).to match recipient
+end
+
Then("I can run git {string}") do |command|
@i_see = shell "#{MOSS} git #{command}"
end
diff --git a/fixtures/keys/encrypted.key b/fixtures/keys/encrypted.key
Binary files differ.
diff --git a/moss-init-with-passphrase.expect b/moss-init-with-passphrase.expect
@@ -0,0 +1,9 @@
+set keyfile [lindex $argv 0]
+set phrase [lindex $argv 1]
+spawn ./moss.rb init ${keyfile}
+expect passphrase
+send "${phrase}\r"
+interact
+catch wait result
+set STATUS [ lindex $result 3 ]
+exit $STATUS
diff --git a/moss.rb b/moss.rb
@@ -30,14 +30,34 @@ class Moss
# named. "initialize" would be a good name if it weren't special to
# Ruby
+ def encrypted?(filename)
+ magic = "age-encryption.org/v1"
+ File.read(filename, magic.length) == magic
+ end
+
+ def pubkey_for_identity(filename)
+ pubkey =
+ if encrypted?(filename)
+ IO.popen("set -o pipefail; #{AGE} -d #{filename} | #{AGE_KEYGEN} -y -") {|f|
+ f.read
+ }
+ else
+ IO.popen("#{AGE_KEYGEN} -y #{filename}") { |f| f.read }
+ end
+ if $?.exitstatus.zero?
+ pubkey
+ else
+ raise "can't get public key from identity #{filename}"
+ end
+ end
+
def create(keyfile)
keyfile.exist? or raise "Cannot read identity at #{keyfile}"
- keyfile.read.match(/AGE-SECRET-KEY-1/) or
- raise "#{keyfile} does not appear to be an age identity"
+ recipient = pubkey_for_identity(keyfile)
store.mkpath
FileUtils.cp(keyfile, store.parent.join("identity"))
File.open(store.join(".recipients"), "w") do |f|
- f.write `#{AGE_KEYGEN} -y #{keyfile.to_s.inspect}`
+ f.write recipient
end
end
diff --git a/shell.nix b/shell.nix
@@ -8,6 +8,7 @@ let gems = bundlerEnv {
in mkShell {
packages = [
gems gems.wrappedRuby
+ pkgs.expect
bundix age pkgs.entr
];
}