Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions HomebrewFormula/age.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
161 changes: 147 additions & 14 deletions cmd/age/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
_log "log"
"os"
"runtime/debug"
Expand All @@ -20,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
Expand All @@ -44,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.

Expand All @@ -63,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
Expand All @@ -80,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")
Expand All @@ -102,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 {
Expand Down Expand Up @@ -154,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 {
Expand Down Expand Up @@ -197,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)
}
}

Expand Down Expand Up @@ -236,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)
Expand All @@ -252,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) {
Expand All @@ -286,15 +319,69 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor
}
}

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.
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...)
}
}

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},
}

// TODO: check the default SSH location if no arguments are provided
// (~/.ssh/id_rsa, ~/.ssh/id_ed25519).
addOpenSSHIdentities(&identities)

for _, name := range keys {
ids, err := parseIdentitiesFile(name)
if err != nil {
Expand All @@ -319,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()
Expand Down
Loading