mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-03 19:03:57 +00:00
internal/agessh: new package
Move the SSH recipient types out of the main package to declutter the godoc. This also allows us to drop the x/crypto/ssh build dependency entirely from the age package import tree.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"filippo.io/age/internal/age"
|
"filippo.io/age/internal/age"
|
||||||
|
"filippo.io/age/internal/agessh"
|
||||||
"filippo.io/age/internal/format"
|
"filippo.io/age/internal/format"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
@@ -62,9 +63,9 @@ func (i *EncryptedSSHIdentity) Unwrap(block *format.Recipient) (fileKey []byte,
|
|||||||
|
|
||||||
switch k := k.(type) {
|
switch k := k.(type) {
|
||||||
case *ed25519.PrivateKey:
|
case *ed25519.PrivateKey:
|
||||||
i.decrypted, err = age.NewSSHEd25519Identity(*k)
|
i.decrypted, err = agessh.NewEd25519Identity(*k)
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
i.decrypted, err = age.NewSSHRSAIdentity(k)
|
i.decrypted, err = agessh.NewRSAIdentity(k)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected SSH key type: %T", k)
|
return nil, fmt.Errorf("unexpected SSH key type: %T", k)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"filippo.io/age/internal/age"
|
"filippo.io/age/internal/age"
|
||||||
|
"filippo.io/age/internal/agessh"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ func parseRecipient(arg string) (age.Recipient, error) {
|
|||||||
case strings.HasPrefix(arg, "age1"):
|
case strings.HasPrefix(arg, "age1"):
|
||||||
return age.ParseX25519Recipient(arg)
|
return age.ParseX25519Recipient(arg)
|
||||||
case strings.HasPrefix(arg, "ssh-"):
|
case strings.HasPrefix(arg, "ssh-"):
|
||||||
return age.ParseSSHRecipient(arg)
|
return agessh.ParseRecipient(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
||||||
@@ -82,7 +83,7 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
|
func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
|
||||||
id, err := age.ParseSSHIdentity(pemBytes)
|
id, err := agessh.ParseIdentity(pemBytes)
|
||||||
if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
|
if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
|
||||||
pubKey := sshErr.PublicKey
|
pubKey := sshErr.PublicKey
|
||||||
if pubKey == nil {
|
if pubKey == nil {
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ package age_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"filippo.io/age/internal/age"
|
"filippo.io/age/internal/age"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestX25519RoundTrip(t *testing.T) {
|
func TestX25519RoundTrip(t *testing.T) {
|
||||||
@@ -109,93 +106,3 @@ func TestScryptRoundTrip(t *testing.T) {
|
|||||||
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSHRSARoundTrip(t *testing.T) {
|
|
||||||
pk, err := rsa.GenerateKey(rand.Reader, 768)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pub, err := ssh.NewPublicKey(&pk.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := age.NewSSHRSARecipient(pub)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i, err := age.NewSSHRSAIdentity(pk)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Type() != i.Type() || r.Type() != "ssh-rsa" {
|
|
||||||
t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
fileKey := make([]byte, 16)
|
|
||||||
if _, err := rand.Read(fileKey); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
block, err := r.Wrap(fileKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
block.Marshal(b)
|
|
||||||
t.Logf("%s", b.Bytes())
|
|
||||||
|
|
||||||
out, err := i.Unwrap(block)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fileKey, out) {
|
|
||||||
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSSHEd25519RoundTrip(t *testing.T) {
|
|
||||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
sshPubKey, err := ssh.NewPublicKey(pub)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := age.NewSSHEd25519Recipient(sshPubKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
i, err := age.NewSSHEd25519Identity(priv)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Type() != i.Type() || r.Type() != "ssh-ed25519" {
|
|
||||||
t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
fileKey := make([]byte, 16)
|
|
||||||
if _, err := rand.Read(fileKey); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
block, err := r.Wrap(fileKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
block.Marshal(b)
|
|
||||||
t.Logf("%s", b.Bytes())
|
|
||||||
|
|
||||||
out, err := i.Unwrap(block)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fileKey, out) {
|
|
||||||
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
package age
|
// Package agessh provides age.Identity and age.Recipient implementations of
|
||||||
|
// types "ssh-rsa" and "ssh-ed25519", which allow reusing existing SSH key files
|
||||||
|
// for encryption with age-encryption.org/v1.
|
||||||
|
//
|
||||||
|
// These should only be used for compatibility with existing keys, and native
|
||||||
|
// X25519 keys should be preferred otherwise.
|
||||||
|
package agessh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
@@ -17,6 +23,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"filippo.io/age/internal/age"
|
||||||
"filippo.io/age/internal/format"
|
"filippo.io/age/internal/format"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
@@ -31,20 +38,20 @@ func sshFingerprint(pk ssh.PublicKey) string {
|
|||||||
|
|
||||||
const oaepLabel = "age-encryption.org/v1/ssh-rsa"
|
const oaepLabel = "age-encryption.org/v1/ssh-rsa"
|
||||||
|
|
||||||
type SSHRSARecipient struct {
|
type RSARecipient struct {
|
||||||
sshKey ssh.PublicKey
|
sshKey ssh.PublicKey
|
||||||
pubKey *rsa.PublicKey
|
pubKey *rsa.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Recipient = &SSHRSARecipient{}
|
var _ age.Recipient = &RSARecipient{}
|
||||||
|
|
||||||
func (*SSHRSARecipient) Type() string { return "ssh-rsa" }
|
func (*RSARecipient) Type() string { return "ssh-rsa" }
|
||||||
|
|
||||||
func NewSSHRSARecipient(pk ssh.PublicKey) (*SSHRSARecipient, error) {
|
func NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {
|
||||||
if pk.Type() != "ssh-rsa" {
|
if pk.Type() != "ssh-rsa" {
|
||||||
return nil, errors.New("SSH public key is not an RSA key")
|
return nil, errors.New("SSH public key is not an RSA key")
|
||||||
}
|
}
|
||||||
r := &SSHRSARecipient{
|
r := &RSARecipient{
|
||||||
sshKey: pk,
|
sshKey: pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +67,7 @@ func NewSSHRSARecipient(pk ssh.PublicKey) (*SSHRSARecipient, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SSHRSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
func (r *RSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||||
l := &format.Recipient{
|
l := &format.Recipient{
|
||||||
Type: "ssh-rsa",
|
Type: "ssh-rsa",
|
||||||
Args: []string{sshFingerprint(r.sshKey)},
|
Args: []string{sshFingerprint(r.sshKey)},
|
||||||
@@ -76,36 +83,36 @@ func (r *SSHRSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
|||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHRSAIdentity struct {
|
type RSAIdentity struct {
|
||||||
k *rsa.PrivateKey
|
k *rsa.PrivateKey
|
||||||
sshKey ssh.PublicKey
|
sshKey ssh.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Identity = &SSHRSAIdentity{}
|
var _ age.Identity = &RSAIdentity{}
|
||||||
|
|
||||||
func (*SSHRSAIdentity) Type() string { return "ssh-rsa" }
|
func (*RSAIdentity) Type() string { return "ssh-rsa" }
|
||||||
|
|
||||||
func NewSSHRSAIdentity(key *rsa.PrivateKey) (*SSHRSAIdentity, error) {
|
func NewRSAIdentity(key *rsa.PrivateKey) (*RSAIdentity, error) {
|
||||||
s, err := ssh.NewSignerFromKey(key)
|
s, err := ssh.NewSignerFromKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i := &SSHRSAIdentity{
|
i := &RSAIdentity{
|
||||||
k: key, sshKey: s.PublicKey(),
|
k: key, sshKey: s.PublicKey(),
|
||||||
}
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
|
func (i *RSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||||
if block.Type != "ssh-rsa" {
|
if block.Type != "ssh-rsa" {
|
||||||
return nil, ErrIncorrectIdentity
|
return nil, age.ErrIncorrectIdentity
|
||||||
}
|
}
|
||||||
if len(block.Args) != 1 {
|
if len(block.Args) != 1 {
|
||||||
return nil, errors.New("invalid ssh-rsa recipient block")
|
return nil, errors.New("invalid ssh-rsa recipient block")
|
||||||
}
|
}
|
||||||
|
|
||||||
if block.Args[0] != sshFingerprint(i.sshKey) {
|
if block.Args[0] != sshFingerprint(i.sshKey) {
|
||||||
return nil, ErrIncorrectIdentity
|
return nil, age.ErrIncorrectIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k,
|
fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k,
|
||||||
@@ -116,20 +123,20 @@ func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
|
|||||||
return fileKey, nil
|
return fileKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHEd25519Recipient struct {
|
type Ed25519Recipient struct {
|
||||||
sshKey ssh.PublicKey
|
sshKey ssh.PublicKey
|
||||||
theirPublicKey []byte
|
theirPublicKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Recipient = &SSHEd25519Recipient{}
|
var _ age.Recipient = &Ed25519Recipient{}
|
||||||
|
|
||||||
func (*SSHEd25519Recipient) Type() string { return "ssh-ed25519" }
|
func (*Ed25519Recipient) Type() string { return "ssh-ed25519" }
|
||||||
|
|
||||||
func NewSSHEd25519Recipient(pk ssh.PublicKey) (*SSHEd25519Recipient, error) {
|
func NewEd25519Recipient(pk ssh.PublicKey) (*Ed25519Recipient, error) {
|
||||||
if pk.Type() != "ssh-ed25519" {
|
if pk.Type() != "ssh-ed25519" {
|
||||||
return nil, errors.New("SSH public key is not an Ed25519 key")
|
return nil, errors.New("SSH public key is not an Ed25519 key")
|
||||||
}
|
}
|
||||||
r := &SSHEd25519Recipient{
|
r := &Ed25519Recipient{
|
||||||
sshKey: pk,
|
sshKey: pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,18 +152,18 @@ func NewSSHEd25519Recipient(pk ssh.PublicKey) (*SSHEd25519Recipient, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSSHRecipient(s string) (Recipient, error) {
|
func ParseRecipient(s string) (age.Recipient, error) {
|
||||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err)
|
return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r Recipient
|
var r age.Recipient
|
||||||
switch t := pubKey.Type(); t {
|
switch t := pubKey.Type(); t {
|
||||||
case "ssh-rsa":
|
case "ssh-rsa":
|
||||||
r, err = NewSSHRSARecipient(pubKey)
|
r, err = NewRSARecipient(pubKey)
|
||||||
case "ssh-ed25519":
|
case "ssh-ed25519":
|
||||||
r, err = NewSSHEd25519Recipient(pubKey)
|
r, err = NewEd25519Recipient(pubKey)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown SSH recipient type: %q", t)
|
return nil, fmt.Errorf("unknown SSH recipient type: %q", t)
|
||||||
}
|
}
|
||||||
@@ -200,7 +207,7 @@ func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte {
|
|||||||
|
|
||||||
const ed25519Label = "age-encryption.org/v1/ssh-ed25519"
|
const ed25519Label = "age-encryption.org/v1/ssh-ed25519"
|
||||||
|
|
||||||
func (r *SSHEd25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
func (r *Ed25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
||||||
ephemeral := make([]byte, curve25519.ScalarSize)
|
ephemeral := make([]byte, curve25519.ScalarSize)
|
||||||
if _, err := rand.Read(ephemeral); err != nil {
|
if _, err := rand.Read(ephemeral); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -246,21 +253,21 @@ func (r *SSHEd25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
|
|||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHEd25519Identity struct {
|
type Ed25519Identity struct {
|
||||||
secretKey, ourPublicKey []byte
|
secretKey, ourPublicKey []byte
|
||||||
sshKey ssh.PublicKey
|
sshKey ssh.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Identity = &SSHEd25519Identity{}
|
var _ age.Identity = &Ed25519Identity{}
|
||||||
|
|
||||||
func (*SSHEd25519Identity) Type() string { return "ssh-ed25519" }
|
func (*Ed25519Identity) Type() string { return "ssh-ed25519" }
|
||||||
|
|
||||||
func NewSSHEd25519Identity(key ed25519.PrivateKey) (*SSHEd25519Identity, error) {
|
func NewEd25519Identity(key ed25519.PrivateKey) (*Ed25519Identity, error) {
|
||||||
s, err := ssh.NewSignerFromKey(key)
|
s, err := ssh.NewSignerFromKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i := &SSHEd25519Identity{
|
i := &Ed25519Identity{
|
||||||
sshKey: s.PublicKey(),
|
sshKey: s.PublicKey(),
|
||||||
secretKey: ed25519PrivateKeyToCurve25519(key),
|
secretKey: ed25519PrivateKeyToCurve25519(key),
|
||||||
}
|
}
|
||||||
@@ -268,7 +275,7 @@ func NewSSHEd25519Identity(key ed25519.PrivateKey) (*SSHEd25519Identity, error)
|
|||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSSHIdentity(pemBytes []byte) (Identity, error) {
|
func ParseIdentity(pemBytes []byte) (age.Identity, error) {
|
||||||
k, err := ssh.ParseRawPrivateKey(pemBytes)
|
k, err := ssh.ParseRawPrivateKey(pemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -276,9 +283,9 @@ func ParseSSHIdentity(pemBytes []byte) (Identity, error) {
|
|||||||
|
|
||||||
switch k := k.(type) {
|
switch k := k.(type) {
|
||||||
case *ed25519.PrivateKey:
|
case *ed25519.PrivateKey:
|
||||||
return NewSSHEd25519Identity(*k)
|
return NewEd25519Identity(*k)
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
return NewSSHRSAIdentity(k)
|
return NewRSAIdentity(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported SSH identity type: %T", k)
|
return nil, fmt.Errorf("unsupported SSH identity type: %T", k)
|
||||||
@@ -291,9 +298,9 @@ func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte {
|
|||||||
return out[:curve25519.ScalarSize]
|
return out[:curve25519.ScalarSize]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
func (i *Ed25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
||||||
if block.Type != "ssh-ed25519" {
|
if block.Type != "ssh-ed25519" {
|
||||||
return nil, ErrIncorrectIdentity
|
return nil, age.ErrIncorrectIdentity
|
||||||
}
|
}
|
||||||
if len(block.Args) != 2 {
|
if len(block.Args) != 2 {
|
||||||
return nil, errors.New("invalid ssh-ed25519 recipient block")
|
return nil, errors.New("invalid ssh-ed25519 recipient block")
|
||||||
@@ -307,7 +314,7 @@ func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if block.Args[0] != sshFingerprint(i.sshKey) {
|
if block.Args[0] != sshFingerprint(i.sshKey) {
|
||||||
return nil, ErrIncorrectIdentity
|
return nil, age.ErrIncorrectIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedSecret, err := curve25519.X25519(i.secretKey, publicKey)
|
sharedSecret, err := curve25519.X25519(i.secretKey, publicKey)
|
||||||
@@ -337,3 +344,23 @@ func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return fileKey, nil
|
return fileKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// aeadEncrypt and aeadDecrypt are copied from package age.
|
||||||
|
|
||||||
|
func aeadEncrypt(key, plaintext []byte) ([]byte, error) {
|
||||||
|
aead, err := chacha20poly1305.New(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nonce := make([]byte, chacha20poly1305.NonceSize)
|
||||||
|
return aead.Seal(nil, nonce, plaintext, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aeadDecrypt(key, ciphertext []byte) ([]byte, error) {
|
||||||
|
aead, err := chacha20poly1305.New(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nonce := make([]byte, chacha20poly1305.NonceSize)
|
||||||
|
return aead.Open(nil, nonce, ciphertext, nil)
|
||||||
|
}
|
||||||
108
internal/agessh/agessh_test.go
Normal file
108
internal/agessh/agessh_test.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2019 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 agessh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"filippo.io/age/internal/agessh"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSHRSARoundTrip(t *testing.T) {
|
||||||
|
pk, err := rsa.GenerateKey(rand.Reader, 768)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pub, err := ssh.NewPublicKey(&pk.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := agessh.NewRSARecipient(pub)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
i, err := agessh.NewRSAIdentity(pk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type() != i.Type() || r.Type() != "ssh-rsa" {
|
||||||
|
t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileKey := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(fileKey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
block, err := r.Wrap(fileKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
block.Marshal(b)
|
||||||
|
t.Logf("%s", b.Bytes())
|
||||||
|
|
||||||
|
out, err := i.Unwrap(block)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(fileKey, out) {
|
||||||
|
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHEd25519RoundTrip(t *testing.T) {
|
||||||
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sshPubKey, err := ssh.NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := agessh.NewEd25519Recipient(sshPubKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
i, err := agessh.NewEd25519Identity(priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Type() != i.Type() || r.Type() != "ssh-ed25519" {
|
||||||
|
t.Errorf("invalid Type values: %v, %v", r.Type(), i.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileKey := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(fileKey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
block, err := r.Wrap(fileKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
block.Marshal(b)
|
||||||
|
t.Logf("%s", b.Bytes())
|
||||||
|
|
||||||
|
out, err := i.Unwrap(block)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(fileKey, out) {
|
||||||
|
t.Errorf("invalid output: %x, expected %x", out, fileKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user