mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-03 10:55:14 +00:00
age,cmd/age: add ParseRecipients and -R for recipient files
Fixes #84 Fixes #66 Closes #165 Closes #158 Closes #115 Closes #64 Closes #43 Closes #20
This commit is contained in:
committed by
Filippo Valsorda
parent
7ab2008136
commit
f8507c1cac
22
README.md
22
README.md
@@ -22,6 +22,7 @@ An alternative interoperable Rust implementation is available at [github.com/str
|
|||||||
```
|
```
|
||||||
Usage:
|
Usage:
|
||||||
age -r RECIPIENT [-a] [-o OUTPUT] [INPUT]
|
age -r RECIPIENT [-a] [-o OUTPUT] [INPUT]
|
||||||
|
age --passphrase [-a] [-o OUTPUT] [INPUT]
|
||||||
age --decrypt [-i KEY] [-o OUTPUT] [INPUT]
|
age --decrypt [-i KEY] [-o OUTPUT] [INPUT]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -29,6 +30,7 @@ Options:
|
|||||||
-a, --armor Encrypt to a PEM encoded format.
|
-a, --armor Encrypt to a PEM encoded format.
|
||||||
-p, --passphrase Encrypt with a passphrase.
|
-p, --passphrase Encrypt with a passphrase.
|
||||||
-r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated.
|
-r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated.
|
||||||
|
-R, --recipients-file PATH Encrypt to recipients listed at PATH. Can be repeated.
|
||||||
-d, --decrypt Decrypt the input to the output.
|
-d, --decrypt Decrypt the input to the output.
|
||||||
-i, --identity KEY Use the private key file at path KEY. Can be repeated.
|
-i, --identity KEY Use the private key file at path KEY. Can be repeated.
|
||||||
|
|
||||||
@@ -37,6 +39,9 @@ INPUT defaults to standard input, and OUTPUT defaults to standard output.
|
|||||||
RECIPIENT can be an age public key, as generated by age-keygen, ("age1...")
|
RECIPIENT can be an age public key, as generated by age-keygen, ("age1...")
|
||||||
or an SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").
|
or an SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").
|
||||||
|
|
||||||
|
Recipient files contain one or more recipients, one per line. Empty lines
|
||||||
|
and lines starting with "#" are ignored as comments.
|
||||||
|
|
||||||
KEY is a path to a file with age secret keys, one per line
|
KEY is a path to a file with age secret keys, one per line
|
||||||
(ignoring "#" prefixed comments and empty lines), or to an SSH key file.
|
(ignoring "#" prefixed comments and empty lines), or to an SSH key file.
|
||||||
Multiple keys can be provided, and any unused ones will be ignored.
|
Multiple keys can be provided, and any unused ones will be ignored.
|
||||||
@@ -51,6 +56,19 @@ $ age -o example.jpg.age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sf
|
|||||||
-r age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg example.jpg
|
-r age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg example.jpg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Recipient files
|
||||||
|
|
||||||
|
Multiple recipients can also be listed one per line in one or more files passed with the `-R/--recipients-file` flag.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cat recipients.txt
|
||||||
|
# Alice
|
||||||
|
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||||
|
# Bob
|
||||||
|
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
|
||||||
|
$ age -R recipients.txt example.jpg > example.jpg.age
|
||||||
|
```
|
||||||
|
|
||||||
### Passphrases
|
### Passphrases
|
||||||
|
|
||||||
Files can be encrypted with a passphrase by using `-p/--passphrase`. By default age will automatically generate a secure passphrase. Passphrase protected files are automatically detected at decrypt time.
|
Files can be encrypted with a passphrase by using `-p/--passphrase`. By default age will automatically generate a secure passphrase. Passphrase protected files are automatically detected at decrypt time.
|
||||||
@@ -68,9 +86,7 @@ Enter passphrase:
|
|||||||
As a convenience feature, age also supports encrypting to `ssh-rsa` and `ssh-ed25519` SSH public keys, and decrypting with the respective private key file. (`ssh-agent` is not supported.)
|
As a convenience feature, age also supports encrypting to `ssh-rsa` and `ssh-ed25519` SSH public keys, and decrypting with the respective private key file. (`ssh-agent` is not supported.)
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cat ~/.ssh/id_ed25519.pub
|
$ age -R ~/.ssh/id_ed25519.pub example.jpg > example.jpg.age
|
||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIZDRcvS8PnhXr30WKSKmf7WKKi92ACUa5nW589WukJz filippo@Bistromath.local
|
|
||||||
$ age -r "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIZDRcvS8PnhXr30WKSKmf7WKKi92ACUa5nW589WukJz" example.jpg > example.jpg.age
|
|
||||||
$ age -d -i ~/.ssh/id_ed25519 example.jpg.age > example.jpg
|
$ age -d -i ~/.ssh/id_ed25519 example.jpg.age > example.jpg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// keys, and native X25519 keys should be preferred otherwise.
|
// keys, and native X25519 keys should be preferred otherwise.
|
||||||
//
|
//
|
||||||
// Note that these recipient types are not anonymous: the encrypted message will
|
// Note that these recipient types are not anonymous: the encrypted message will
|
||||||
// include a short 32-bit ID of the public key,
|
// include a short 32-bit ID of the public key.
|
||||||
package agessh
|
package agessh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Options:
|
|||||||
-a, --armor Encrypt to a PEM encoded format.
|
-a, --armor Encrypt to a PEM encoded format.
|
||||||
-p, --passphrase Encrypt with a passphrase.
|
-p, --passphrase Encrypt with a passphrase.
|
||||||
-r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated.
|
-r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated.
|
||||||
|
-R, --recipients-file PATH Encrypt to recipients listed at PATH. Can be repeated.
|
||||||
-d, --decrypt Decrypt the input to the output.
|
-d, --decrypt Decrypt the input to the output.
|
||||||
-i, --identity KEY Use the private key file at path KEY. Can be repeated.
|
-i, --identity KEY Use the private key file at path KEY. Can be repeated.
|
||||||
|
|
||||||
@@ -47,6 +48,9 @@ INPUT defaults to standard input, and OUTPUT defaults to standard output.
|
|||||||
RECIPIENT can be an age public key, as generated by age-keygen, ("age1...")
|
RECIPIENT can be an age public key, as generated by age-keygen, ("age1...")
|
||||||
or an SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").
|
or an SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").
|
||||||
|
|
||||||
|
Recipient files contain one or more recipients, one per line. Empty lines
|
||||||
|
and lines starting with "#" are ignored as comments.
|
||||||
|
|
||||||
KEY is a path to a file with age secret keys, one per line
|
KEY is a path to a file with age secret keys, one per line
|
||||||
(ignoring "#" prefixed comments and empty lines), or to an SSH key file.
|
(ignoring "#" prefixed comments and empty lines), or to an SSH key file.
|
||||||
Multiple keys can be provided, and any unused ones will be ignored.
|
Multiple keys can be provided, and any unused ones will be ignored.
|
||||||
@@ -65,6 +69,7 @@ func main() {
|
|||||||
outFlag string
|
outFlag string
|
||||||
decryptFlag, armorFlag, passFlag bool
|
decryptFlag, armorFlag, passFlag bool
|
||||||
recipientFlags, identityFlags multiFlag
|
recipientFlags, identityFlags multiFlag
|
||||||
|
recipientsFileFlags multiFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.BoolVar(&decryptFlag, "d", false, "decrypt the input")
|
flag.BoolVar(&decryptFlag, "d", false, "decrypt the input")
|
||||||
@@ -77,6 +82,8 @@ func main() {
|
|||||||
flag.BoolVar(&armorFlag, "armor", false, "generate an armored file")
|
flag.BoolVar(&armorFlag, "armor", false, "generate an armored file")
|
||||||
flag.Var(&recipientFlags, "r", "recipient (can be repeated)")
|
flag.Var(&recipientFlags, "r", "recipient (can be repeated)")
|
||||||
flag.Var(&recipientFlags, "recipient", "recipient (can be repeated)")
|
flag.Var(&recipientFlags, "recipient", "recipient (can be repeated)")
|
||||||
|
flag.Var(&recipientsFileFlags, "R", "recipients file (can be repeated)")
|
||||||
|
flag.Var(&recipientsFileFlags, "recipients-file", "recipients file (can be repeated)")
|
||||||
flag.Var(&identityFlags, "i", "identity (can be repeated)")
|
flag.Var(&identityFlags, "i", "identity (can be repeated)")
|
||||||
flag.Var(&identityFlags, "identity", "identity (can be repeated)")
|
flag.Var(&identityFlags, "identity", "identity (can be repeated)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -99,18 +106,25 @@ func main() {
|
|||||||
logFatalf("Error: -r/--recipient can't be used with -d/--decrypt.\n" +
|
logFatalf("Error: -r/--recipient can't be used with -d/--decrypt.\n" +
|
||||||
"Did you mean to use -i/--identity to specify a private key?")
|
"Did you mean to use -i/--identity to specify a private key?")
|
||||||
}
|
}
|
||||||
|
if len(recipientsFileFlags) > 0 {
|
||||||
|
logFatalf("Error: -R/--recipients-file can't be used with -d/--decrypt.\n" +
|
||||||
|
"Did you mean to use -i/--identity to specify a private key?")
|
||||||
|
}
|
||||||
default: // encrypt
|
default: // encrypt
|
||||||
if len(identityFlags) > 0 {
|
if len(identityFlags) > 0 {
|
||||||
logFatalf("Error: -i/--identity can't be used in encryption mode.\n" +
|
logFatalf("Error: -i/--identity can't be used in encryption mode.\n" +
|
||||||
"Did you forget to specify -d/--decrypt?")
|
"Did you forget to specify -d/--decrypt?")
|
||||||
}
|
}
|
||||||
if len(recipientFlags) == 0 && !passFlag {
|
if len(recipientFlags) == 0 && len(recipientsFileFlags) == 0 && !passFlag {
|
||||||
logFatalf("Error: missing recipients.\n" +
|
logFatalf("Error: missing recipients.\n" +
|
||||||
"Did you forget to specify -r/--recipient or -p/--passphrase?")
|
"Did you forget to specify -r/--recipient or -p/--passphrase?")
|
||||||
}
|
}
|
||||||
if len(recipientFlags) > 0 && passFlag {
|
if len(recipientFlags) > 0 && passFlag {
|
||||||
logFatalf("Error: -p/--passphrase can't be combined with -r/--recipient.")
|
logFatalf("Error: -p/--passphrase can't be combined with -r/--recipient.")
|
||||||
}
|
}
|
||||||
|
if len(recipientsFileFlags) > 0 && passFlag {
|
||||||
|
logFatalf("Error: -p/--passphrase can't be combined with -R/--recipients-file.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var in, out io.ReadWriter = os.Stdin, os.Stdout
|
var in, out io.ReadWriter = os.Stdin, os.Stdout
|
||||||
@@ -158,7 +172,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
encryptPass(pass, in, out, armorFlag)
|
encryptPass(pass, in, out, armorFlag)
|
||||||
default:
|
default:
|
||||||
encryptKeys(recipientFlags, in, out, armorFlag)
|
encryptKeys(recipientFlags, recipientsFileFlags, in, out, armorFlag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +203,7 @@ func passphrasePromptForEncryption() (string, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encryptKeys(keys []string, in io.Reader, out io.Writer, armor bool) {
|
func encryptKeys(keys, files []string, in io.Reader, out io.Writer, armor bool) {
|
||||||
var recipients []age.Recipient
|
var recipients []age.Recipient
|
||||||
for _, arg := range keys {
|
for _, arg := range keys {
|
||||||
r, err := parseRecipient(arg)
|
r, err := parseRecipient(arg)
|
||||||
@@ -198,6 +212,20 @@ func encryptKeys(keys []string, in io.Reader, out io.Writer, armor bool) {
|
|||||||
}
|
}
|
||||||
recipients = append(recipients, r)
|
recipients = append(recipients, r)
|
||||||
}
|
}
|
||||||
|
for _, name := range files {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
logFatalf("Error: failed to open recipient file: %v", err)
|
||||||
|
}
|
||||||
|
recs, err := parseRecipients(f, func(format string, a ...interface{}) {
|
||||||
|
a = append([]interface{}{name}, a...)
|
||||||
|
_log.Printf("Warning: recipients file %q: "+format, a...)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logFatalf("Error: failed to parse recipient file %q: %v", name, err)
|
||||||
|
}
|
||||||
|
recipients = append(recipients, recs...)
|
||||||
|
}
|
||||||
encrypt(recipients, in, out, armor)
|
encrypt(recipients, in, out, armor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"filippo.io/age"
|
"filippo.io/age"
|
||||||
"filippo.io/age/agessh"
|
"filippo.io/age/agessh"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +32,66 @@ func parseRecipient(arg string) (age.Recipient, error) {
|
|||||||
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseRecipients(f io.Reader, warnf func(string, ...interface{})) ([]age.Recipient, error) {
|
||||||
|
const recipientFileSizeLimit = 16 << 20 // 16 MiB
|
||||||
|
const lineLengthLimit = 8 << 10 // 8 KiB, same as sshd(8)
|
||||||
|
var recs []age.Recipient
|
||||||
|
scanner := bufio.NewScanner(io.LimitReader(f, recipientFileSizeLimit))
|
||||||
|
var n int
|
||||||
|
for scanner.Scan() {
|
||||||
|
n++
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") || line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(line) > lineLengthLimit {
|
||||||
|
return nil, fmt.Errorf("line %d is too long", n)
|
||||||
|
}
|
||||||
|
r, err := parseRecipient(line)
|
||||||
|
if err != nil {
|
||||||
|
if t, ok := sshKeyType(line); ok {
|
||||||
|
// Skip unsupported but valid SSH public keys with a warning.
|
||||||
|
warnf("ignoring unsupported SSH key of type %q at line %d", t, n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Hide the error since it might unintentionally leak the contents
|
||||||
|
// of confidential files.
|
||||||
|
return nil, fmt.Errorf("malformed recipient at line %d", n)
|
||||||
|
}
|
||||||
|
recs = append(recs, r)
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read recipients file: %v", err)
|
||||||
|
}
|
||||||
|
if len(recs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no recipients found")
|
||||||
|
}
|
||||||
|
return recs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshKeyType(s string) (string, bool) {
|
||||||
|
// TODO: also ignore options? And maybe support multiple spaces and tabs as
|
||||||
|
// field separators like OpenSSH?
|
||||||
|
fields := strings.Split(s, " ")
|
||||||
|
if len(fields) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
key, err := base64.StdEncoding.DecodeString(fields[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
k := cryptobyte.String(key)
|
||||||
|
var typeLen uint32
|
||||||
|
var typeBytes []byte
|
||||||
|
if !k.ReadUint32(&typeLen) || !k.ReadBytes(&typeBytes, int(typeLen)) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if t := fields[0]; t == string(typeBytes) {
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
||||||
f, err := os.Open(name)
|
f, err := os.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
86
parse.go
Normal file
86
parse.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2021 Google LLC
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
package age
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseIdentities parses a file with one or more private key encodings, one per
|
||||||
|
// line. Empty lines and lines starting with "#" are ignored.
|
||||||
|
//
|
||||||
|
// This is the same syntax as the private key files accepted by the CLI, except
|
||||||
|
// the CLI also accepts SSH private keys, which are not recommended for the
|
||||||
|
// average application.
|
||||||
|
//
|
||||||
|
// Currently, all returned values are of type *X25519Identity, but different
|
||||||
|
// types might be returned in the future.
|
||||||
|
func ParseIdentities(f io.Reader) ([]Identity, error) {
|
||||||
|
const privateKeySizeLimit = 1 << 24 // 16 MiB
|
||||||
|
var ids []Identity
|
||||||
|
scanner := bufio.NewScanner(io.LimitReader(f, privateKeySizeLimit))
|
||||||
|
var n int
|
||||||
|
for scanner.Scan() {
|
||||||
|
n++
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") || line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i, err := ParseX25519Identity(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error at line %d: %v", n, err)
|
||||||
|
}
|
||||||
|
ids = append(ids, i)
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read secret keys file: %v", err)
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil, fmt.Errorf("no secret keys found")
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRecipients parses a file with one or more public key encodings, one per
|
||||||
|
// line. Empty lines and lines starting with "#" are ignored.
|
||||||
|
//
|
||||||
|
// This is the same syntax as the recipients files accepted by the CLI, except
|
||||||
|
// the CLI also accepts SSH recipients, which are not recommended for the
|
||||||
|
// average application.
|
||||||
|
//
|
||||||
|
// Currently, all returned values are of type *X25519Recipient, but different
|
||||||
|
// types might be returned in the future.
|
||||||
|
func ParseRecipients(f io.Reader) ([]Recipient, error) {
|
||||||
|
const recipientFileSizeLimit = 1 << 24 // 16 MiB
|
||||||
|
var recs []Recipient
|
||||||
|
scanner := bufio.NewScanner(io.LimitReader(f, recipientFileSizeLimit))
|
||||||
|
var n int
|
||||||
|
for scanner.Scan() {
|
||||||
|
n++
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") || line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, err := ParseX25519Recipient(line)
|
||||||
|
if err != nil {
|
||||||
|
// Hide the error since it might unintentionally leak the contents
|
||||||
|
// of confidential files.
|
||||||
|
return nil, fmt.Errorf("malformed recipient at line %d", n)
|
||||||
|
}
|
||||||
|
recs = append(recs, r)
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read recipients file: %v", err)
|
||||||
|
}
|
||||||
|
if len(recs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no recipients found")
|
||||||
|
}
|
||||||
|
return recs, nil
|
||||||
|
}
|
||||||
35
x25519.go
35
x25519.go
@@ -7,7 +7,6 @@
|
|||||||
package age
|
package age
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -159,40 +158,6 @@ func ParseX25519Identity(s string) (*X25519Identity, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseIdentities parses a file with one or more private key encodings, one per
|
|
||||||
// line. Empty lines and lines starting with "#" are ignored.
|
|
||||||
//
|
|
||||||
// This is the same syntax as the private key files accepted by the CLI, except
|
|
||||||
// the CLI also accepts SSH private keys, which are not recommended for the
|
|
||||||
// average application.
|
|
||||||
//
|
|
||||||
// Currently, all returned values are of type X25519Identity, but different
|
|
||||||
// types might be returned in the future.
|
|
||||||
func ParseIdentities(f io.Reader) ([]Identity, error) {
|
|
||||||
const privateKeySizeLimit = 1 << 24 // 16 MiB
|
|
||||||
var ids []Identity
|
|
||||||
scanner := bufio.NewScanner(io.LimitReader(f, privateKeySizeLimit))
|
|
||||||
var n int
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if strings.HasPrefix(line, "#") || line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i, err := ParseX25519Identity(line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error at line %d: %v", n, err)
|
|
||||||
}
|
|
||||||
ids = append(ids, i)
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read secret keys file: %v", err)
|
|
||||||
}
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return nil, fmt.Errorf("no secret keys found")
|
|
||||||
}
|
|
||||||
return ids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *X25519Identity) Unwrap(block *Stanza) ([]byte, error) {
|
func (i *X25519Identity) Unwrap(block *Stanza) ([]byte, error) {
|
||||||
if block.Type != "X25519" {
|
if block.Type != "X25519" {
|
||||||
return nil, ErrIncorrectIdentity
|
return nil, ErrIncorrectIdentity
|
||||||
|
|||||||
Reference in New Issue
Block a user