From 662998494f91995338e14098377eb06e9c007b98 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 16 Jan 2021 16:07:46 +0100 Subject: [PATCH 1/3] cmd/age: automatically load default SSH key paths --- cmd/age/age.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/cmd/age/age.go b/cmd/age/age.go index acfa1f87..375fcbcb 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -12,6 +12,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" _log "log" "os" "runtime/debug" @@ -293,8 +294,26 @@ func decrypt(keys []string, in io.Reader, out io.Writer) { &LazyScryptIdentity{passphrasePrompt}, } - // TODO: check the default SSH location if no arguments are provided - // (~/.ssh/id_rsa, ~/.ssh/id_ed25519). + // If they exist and are well-formed, load the default SSH keys. If they are + // passphrase protected, the passphrase will only be requested if the + // identity matches a recipient stanza. + for _, path := range []string{ + os.ExpandEnv("$HOME/.ssh/id_rsa"), + os.ExpandEnv("$HOME/.ssh/id_ed25519"), + } { + content, err := ioutil.ReadFile(path) + if err != nil { + continue + } + ids, err := parseSSHIdentity(path, content) + if err != nil { + // If the key is explicitly requested, this error will be caught + // below, otherwise ignore it silently. + continue + } + identities = append(identities, ids...) + } + for _, name := range keys { ids, err := parseIdentitiesFile(name) if err != nil { From 31e0d226807f679cce89b67dfde6201b62582158 Mon Sep 17 00:00:00 2001 From: James Brown Date: Thu, 21 Jan 2021 03:04:02 -0800 Subject: [PATCH 2/3] HomebrewFormula: update to v1.0.0-beta6 (#180) --- HomebrewFormula/age.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HomebrewFormula/age.rb b/HomebrewFormula/age.rb index 224e32e9..bad303d4 100644 --- a/HomebrewFormula/age.rb +++ b/HomebrewFormula/age.rb @@ -7,8 +7,8 @@ class Age < Formula desc "Simple, modern, secure file encryption" homepage "https://filippo.io/age" - url "https://github.com/FiloSottile/age/archive/v1.0.0-beta2.zip" - sha256 "b7417e94c32c7e9356e441815f814073009c4a6455da96bde1536fae8cb0edbf" + url "https://github.com/FiloSottile/age/archive/v1.0.0-beta6.zip" + sha256 "6ffa23aee0f03c3e00707915e4300591847a2b0c5157ca7a696eb39bfeb7359c" depends_on "go" => :build From d3cd2ad563ad35a349339e899a8ed39f7e29dc02 Mon Sep 17 00:00:00 2001 From: Sylvain Rabot Date: Sat, 23 Jan 2021 16:24:27 +0100 Subject: [PATCH 3/3] Implement in place YAML encrypting/decrypting Signed-off-by: Sylvain Rabot --- cmd/age/age.go | 150 +++++++++++++++++--- cmd/age/age_test.go | 280 ++++++++++++++++++++++++++++++++++++++ cmd/age/testdata/yaml.key | 1 + cmd/age/testdata/yaml.pub | 1 + go.mod | 4 +- go.sum | 26 ++++ 6 files changed, 443 insertions(+), 19 deletions(-) create mode 100644 cmd/age/testdata/yaml.key create mode 100644 cmd/age/testdata/yaml.pub diff --git a/cmd/age/age.go b/cmd/age/age.go index 375fcbcb..a24a1314 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -21,6 +21,8 @@ import ( "filippo.io/age" "filippo.io/age/armor" "golang.org/x/crypto/ssh/terminal" + yage "sylr.dev/yaml/age/v3" + "sylr.dev/yaml/v3" ) type multiFlag []string @@ -45,6 +47,8 @@ Options: -R, --recipients-file PATH Encrypt to recipients listed at PATH. Can be repeated. -d, --decrypt Decrypt the input to the output. -i, --identity PATH Use the identity file at PATH. Can be repeated. + -y, --yaml Treat input as YAML and perform in-place encryption / decryption. + --yaml-discard-notag Does not honour NoTag attribute when decrypting (useful for re-keying). INPUT defaults to standard input, and OUTPUT defaults to standard output. @@ -64,7 +68,12 @@ Example: $ age-keygen -o key.txt Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p $ tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age - $ age --decrypt -i key.txt -o data.tar.gz data.tar.gz.age` + $ age --decrypt -i key.txt -o data.tar.gz data.tar.gz.age + + # only yaml keys tagged with !crypto/age will be encrypted + $ age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p -y config.yaml > config.yaml.age + $ age --decrypt -i key.txt -y config.yaml.age +` // Version can be set at link time to override debug.BuildInfo.Main.Version, // which is "(devel)" when building from within the module. See @@ -81,11 +90,12 @@ func main() { } var ( - outFlag string - decryptFlag, armorFlag bool - passFlag, versionFlag bool - recipientFlags, identityFlags multiFlag - recipientsFileFlags multiFlag + outFlag string + decryptFlag, armorFlag bool + passFlag, versionFlag bool + yamlFlag, yamlDiscardNotagFlag bool + recipientFlags, identityFlags multiFlag + recipientsFileFlags multiFlag ) flag.BoolVar(&versionFlag, "version", false, "print the version") @@ -103,6 +113,9 @@ func main() { flag.Var(&recipientsFileFlags, "recipients-file", "recipients file (can be repeated)") flag.Var(&identityFlags, "i", "identity (can be repeated)") flag.Var(&identityFlags, "identity", "identity (can be repeated)") + flag.BoolVar(&yamlFlag, "y", false, "in-place yaml encrypting/decrypting") + flag.BoolVar(&yamlFlag, "yaml", false, "in-place yaml encrypting/decrypting") + flag.BoolVar(&yamlDiscardNotagFlag, "yaml-discard-notag", false, "do not honour NoTag YAML tag attribute") flag.Parse() if versionFlag { @@ -155,10 +168,14 @@ func main() { if len(recipientsFileFlags) > 0 && passFlag { logFatalf("Error: -p/--passphrase can't be combined with -R/--recipients-file.") } + if yamlFlag { + armorFlag = true + } } var in io.Reader = os.Stdin var out io.Writer = os.Stdout + if name := flag.Arg(0); name != "" && name != "-" { f, err := os.Open(name) if err != nil { @@ -198,15 +215,19 @@ func main() { switch { case decryptFlag: - decrypt(identityFlags, in, out) + if yamlFlag { + decryptYAML(identityFlags, in, out, yamlDiscardNotagFlag) + } else { + decrypt(identityFlags, in, out) + } case passFlag: pass, err := passphrasePromptForEncryption() if err != nil { logFatalf("Error: %v", err) } - encryptPass(pass, in, out, armorFlag) + encryptPass(pass, in, out, armorFlag, yamlFlag) default: - encryptKeys(recipientFlags, recipientsFileFlags, in, out, armorFlag) + encryptKeys(recipientFlags, recipientsFileFlags, in, out, armorFlag, yamlFlag) } } @@ -237,7 +258,7 @@ func passphrasePromptForEncryption() (string, error) { return p, nil } -func encryptKeys(keys, files []string, in io.Reader, out io.Writer, armor bool) { +func encryptKeys(keys, files []string, in io.Reader, out io.Writer, armor bool, yaml bool) { var recipients []age.Recipient for _, arg := range keys { r, err := parseRecipient(arg) @@ -253,15 +274,26 @@ func encryptKeys(keys, files []string, in io.Reader, out io.Writer, armor bool) } recipients = append(recipients, recs...) } - encrypt(recipients, in, out, armor) + + if yaml { + encryptYAML(recipients, in, out) + } else { + encrypt(recipients, in, out, armor) + } } -func encryptPass(pass string, in io.Reader, out io.Writer, armor bool) { +func encryptPass(pass string, in io.Reader, out io.Writer, armor bool, yaml bool) { r, err := age.NewScryptRecipient(pass) if err != nil { logFatalf("Error: %v", err) } encrypt([]age.Recipient{r}, in, out, armor) + + if yaml { + encryptYAML([]age.Recipient{r}, in, out) + } else { + encrypt([]age.Recipient{r}, in, out, armor) + } } func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor bool) { @@ -287,13 +319,39 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor } } -func decrypt(keys []string, in io.Reader, out io.Writer) { - identities := []age.Identity{ - // If there is an scrypt recipient (it will have to be the only one and) - // this identity will be invoked. - &LazyScryptIdentity{passphrasePrompt}, +func encryptYAML(recipients []age.Recipient, in io.Reader, out io.Writer) { + node := yaml.Node{} + w := yage.Wrapper{Value: &node} + + decoder := yaml.NewDecoder(in) + encoder := yaml.NewEncoder(out) + encoder.SetIndent(2) + defer encoder.Close() + + for { + err := decoder.Decode(&w) + if err == io.EOF { + break + } else if err != nil { + logFatalf("Error: %v", err) + } + + // Encrypt the Nodes with the !crypto/age tag + encNode, err := yage.MarshalYAML(&node, recipients) + + if err != nil { + logFatalf("Error: %v", err) + } + + err = encoder.Encode(&encNode) + + if err != nil { + logFatalf("Error: %v", err) + } } +} +func addOpenSSHIdentities(identities *[]age.Identity) { // If they exist and are well-formed, load the default SSH keys. If they are // passphrase protected, the passphrase will only be requested if the // identity matches a recipient stanza. @@ -311,8 +369,18 @@ func decrypt(keys []string, in io.Reader, out io.Writer) { // below, otherwise ignore it silently. continue } - identities = append(identities, ids...) + *identities = append(*identities, ids...) } +} + +func decrypt(keys []string, in io.Reader, out io.Writer) { + identities := []age.Identity{ + // If there is an scrypt recipient (it will have to be the only one and) + // this identity will be invoked. + &LazyScryptIdentity{passphrasePrompt}, + } + + addOpenSSHIdentities(&identities) for _, name := range keys { ids, err := parseIdentitiesFile(name) @@ -338,6 +406,52 @@ func decrypt(keys []string, in io.Reader, out io.Writer) { } } +func decryptYAML(keys []string, in io.Reader, out io.Writer, discardNoTag bool) { + identities := []age.Identity{ + // If there is an scrypt recipient (it will have to be the only one and) + // this identity will be invoked. + &LazyScryptIdentity{passphrasePrompt}, + } + + addOpenSSHIdentities(&identities) + + for _, name := range keys { + ids, err := parseIdentitiesFile(name) + if err != nil { + logFatalf("Error reading %q: %v", name, err) + } + identities = append(identities, ids...) + } + + node := yaml.Node{} + w := yage.Wrapper{ + Value: &node, + Identities: identities, + DiscardNoTag: discardNoTag, + } + + decoder := yaml.NewDecoder(in) + encoder := yaml.NewEncoder(out) + encoder.SetIndent(2) + + for { + err := decoder.Decode(&w) + if err == io.EOF { + break + } else if err != nil { + logFatalf("Error: %v", err) + } + + err = encoder.Encode(&node) + + if err != nil { + logFatalf("Error: %v", err) + } + } + + encoder.Close() +} + func passphrasePrompt() (string, error) { fmt.Fprintf(os.Stderr, "Enter passphrase: ") pass, err := readPassphrase() diff --git a/cmd/age/age_test.go b/cmd/age/age_test.go index ddbcaf80..8f4d6bb6 100644 --- a/cmd/age/age_test.go +++ b/cmd/age/age_test.go @@ -7,6 +7,8 @@ package main import ( + "bytes" + "fmt" "io/ioutil" "os" "path/filepath" @@ -59,3 +61,281 @@ func TestVectors(t *testing.T) { }) } } + +func TestYAML(t *testing.T) { + tests := []struct { + Description string + Input string + Expected string + DiscardNoTag bool + }{ + { + Description: "Not style defined", + Input: `password: !crypto/age | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Double quoted", + Input: `password: !crypto/age:DoubleQuoted | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age:DoubleQuoted "ThisIsMyReallyEncryptedPassword"`), + }, + { + Description: "Single quoted", + Input: `password: !crypto/age:SingleQuoted | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age:SingleQuoted 'ThisIsMyReallyEncryptedPassword'`), + }, + { + Description: "Literal", + Input: `password: !crypto/age:Literal | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age:Literal |- + ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Folded", + Input: `password: !crypto/age:Folded | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age:Folded >- + ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Flow", + Input: `password: !crypto/age:Flow | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: !crypto/age:Flow ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "No tag", + Input: `password: !crypto/age:NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Double quoted, No Tag", + Input: `password: !crypto/age:DoubleQuoted,NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: "ThisIsMyReallyEncryptedPassword"`), + }, + { + Description: "Single quoted, No Tag", + Input: `password: !crypto/age:SingleQuoted,NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: 'ThisIsMyReallyEncryptedPassword'`), + }, + { + Description: "Literal, No Tag", + Input: `password: !crypto/age:Literal,NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: |- + ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Folded, No Tag", + Input: `password: !crypto/age:Folded,NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: >- + ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Flow, No Tag", + Input: `password: !crypto/age:Flow,NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE-----`, + Expected: fmt.Sprintln(`password: ThisIsMyReallyEncryptedPassword`), + }, + { + Description: "Anchor", + Input: `password: &password !crypto/age | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE----- +dup: *password`, + Expected: fmt.Sprintln(`password: &password !crypto/age ThisIsMyReallyEncryptedPassword +dup: *password`), + }, + { + Description: "Anchor, No Tag", + Input: `db_password: &password !crypto/age:NoTag | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE----- +dup: *password`, + Expected: fmt.Sprintln(`db_password: &password ThisIsMyReallyEncryptedPassword +dup: *password`), + }, + { + Description: "Comment, Anchor, No Tag", + Input: `db: + # this is a head comment + password: &password !crypto/age:NoTag | # this is a line comment + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE----- + # this is a footer comment +dup: *password # alias comment`, + Expected: fmt.Sprintln(`db: + # this is a head comment + password: &password ThisIsMyReallyEncryptedPassword # this is a line comment + # this is a footer comment +dup: *password # alias comment`), + }, + { + Description: "Documents, Comment, Anchor, No Tag", + Input: `db: + # this is a head comment + password: &password !crypto/age | # this is a line comment + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE----- + # this is a footer comment +dup: *password # alias comment +--- +db: + # this is a head comment + password: &password !crypto/age:NoTag | # this is a line comment + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpTmZNODFnSlAzM0F2TEs0 + OU9iYk54T0tPN2E5OGdvVkZhVGw1anFyVEV3CjlyaE5RUkh6cStLT2V6aFJua0VD + amlzc3lyS09sVjZKV0FjUjZzMmVTWm8KLS0tIFFHeURlKzB4QW91WE5GZnNNdGdn + alEvdW5oaGVocUp5bVVTNzlQRmduZmcK66z0fR47miRVT/0t8obsCRfacNgy5T6C + gLJ+Nu91e/apOC85VBL/rDgbakSmfHPsCo486rDB0N3Ul0qtHT1m + -----END AGE ENCRYPTED FILE----- + # this is a footer comment +dup: *password # alias comment`, + Expected: fmt.Sprintln(`db: + # this is a head comment + password: &password !crypto/age ThisIsMyReallyEncryptedPassword # this is a line comment + # this is a footer comment +dup: *password # alias comment +--- +db: + # this is a head comment + password: &password ThisIsMyReallyEncryptedPassword # this is a line comment + # this is a footer comment +dup: *password # alias comment`), + }, + } + + recFile, err := os.Open("./testdata/yaml.pub") + if err != nil { + t.Fatal(err) + } + + recs, err := age.ParseRecipients(recFile) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + input := test.Input + for i := 0; i < 2; i++ { + in := bytes.NewBufferString(input) + decryptOut := bytes.NewBuffer(nil) + encryptOut := bytes.NewBuffer(nil) + + // decrypt + decryptYAML([]string{"./testdata/yaml.key"}, in, decryptOut, test.DiscardNoTag) + + // compare decrypted with expected + if decryptOut.String() != test.Expected { + t.Errorf("Test \"%s\" failed:\nExpected:\n%sActual:\n%s", test.Description, test.Expected, decryptOut.String()) + } + + // re-encrypt data for second pass + encryptYAML(recs, decryptOut, encryptOut) + + // assign encrypted result to test input + input = encryptOut.String() + } + } +} diff --git a/cmd/age/testdata/yaml.key b/cmd/age/testdata/yaml.key new file mode 100644 index 00000000..ba02fc4b --- /dev/null +++ b/cmd/age/testdata/yaml.key @@ -0,0 +1 @@ +AGE-SECRET-KEY-16T6LNUPYD6LM64F5GHZGQTWHDYSFTQRF447PLYW6Z35S5T9KXARQLW4AQH diff --git a/cmd/age/testdata/yaml.pub b/cmd/age/testdata/yaml.pub new file mode 100644 index 00000000..f48f919b --- /dev/null +++ b/cmd/age/testdata/yaml.pub @@ -0,0 +1 @@ +age17wnc03eahggjwzry6edaxv4ndhxc3ylwdl5924chkxeh09a3nyfsavr6gu diff --git a/go.mod b/go.mod index 58d81018..bb7ab5bc 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,7 @@ go 1.13 require ( filippo.io/edwards25519 v1.0.0-alpha.2 github.com/sergi/go-diff v1.1.0 - golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 + golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 + sylr.dev/yaml/age/v3 v3.0.0-20210124165718-e183355958de + sylr.dev/yaml/v3 v3.0.0-20210121142446-5fe289210a56 ) diff --git a/go.sum b/go.sum index b065abde..b4e89103 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,17 @@ +filippo.io/age v1.0.0-beta6.0.20210121110402-31e0d226807f/go.mod h1:chAuTrTb0FTTmKtvs6fQTGhYTvH9AigjN1uEUsvLdZ0= filippo.io/edwards25519 v1.0.0-alpha.2 h1:EWbZLqGEPSIj2W69gx04KtNVkyPIfe3uj0DhDQJonbQ= filippo.io/edwards25519 v1.0.0-alpha.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -12,20 +19,39 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f h1:QdHQnPce6K4XQewki9WNbG5KOROuDzqO3NaYjI1cXJ0= +golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +sylr.dev/yaml/age/v3 v3.0.0-20210124165718-e183355958de h1:qPTKBaYwzYfZPNaCyMu4OivBQKMMCeVuCqtKqWwXvIk= +sylr.dev/yaml/age/v3 v3.0.0-20210124165718-e183355958de/go.mod h1:uDQSkLAzHqdchPOX/psvSfGlDSmjUasNLDq/K++f6KU= +sylr.dev/yaml/v3 v3.0.0-20210121142446-5fe289210a56 h1:RkhpOJdDvkxi9oz5wn8szqh7tEsh/1+3IgGYYlpf0NE= +sylr.dev/yaml/v3 v3.0.0-20210121142446-5fe289210a56/go.mod h1:4Zku0VUqOuAOvzh8sDnNBOWxgBFIbfHfWtGZI+Qjwx4=