age

Simple, secure encryption with UNIX-style composability.
git clone git://git.sgregoratto.me/age
Log | Files | Refs | README | LICENSE

ssh.go (9822B)


      1 // Copyright 2019 Google LLC
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file or at
      5 // https://developers.google.com/open-source/licenses/bsd
      6 
      7 package age
      8 
      9 import (
     10 	"bytes"
     11 	"crypto/ed25519"
     12 	"crypto/rand"
     13 	"crypto/rsa"
     14 	"crypto/sha256"
     15 	"crypto/sha512"
     16 	"errors"
     17 	"fmt"
     18 	"io"
     19 	"math/big"
     20 
     21 	"github.com/FiloSottile/age/internal/format"
     22 	"golang.org/x/crypto/chacha20poly1305"
     23 	"golang.org/x/crypto/curve25519"
     24 	"golang.org/x/crypto/hkdf"
     25 	"golang.org/x/crypto/ssh"
     26 )
     27 
     28 const oaepLabel = "age-tool.com ssh-rsa"
     29 
     30 type SSHRSARecipient struct {
     31 	sshKey ssh.PublicKey
     32 	pubKey *rsa.PublicKey
     33 }
     34 
     35 var _ Recipient = &SSHRSARecipient{}
     36 
     37 func (*SSHRSARecipient) Type() string { return "ssh-rsa" }
     38 
     39 func NewSSHRSARecipient(pk ssh.PublicKey) (*SSHRSARecipient, error) {
     40 	if pk.Type() != "ssh-rsa" {
     41 		return nil, errors.New("SSH public key is not an RSA key")
     42 	}
     43 	r := &SSHRSARecipient{
     44 		sshKey: pk,
     45 	}
     46 
     47 	if pk, ok := pk.(ssh.CryptoPublicKey); ok {
     48 		if pk, ok := pk.CryptoPublicKey().(*rsa.PublicKey); ok {
     49 			r.pubKey = pk
     50 		} else {
     51 			return nil, errors.New("unexpected public key type")
     52 		}
     53 	} else {
     54 		return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
     55 	}
     56 	return r, nil
     57 }
     58 
     59 func (r *SSHRSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
     60 	h := sha256.New()
     61 	h.Write(r.sshKey.Marshal())
     62 	hh := h.Sum(nil)
     63 
     64 	l := &format.Recipient{
     65 		Type: "ssh-rsa",
     66 		Args: []string{format.EncodeToString(hh[:4])},
     67 	}
     68 
     69 	wrappedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader,
     70 		r.pubKey, fileKey, []byte(oaepLabel))
     71 	if err != nil {
     72 		return nil, err
     73 	}
     74 	l.Body = []byte(format.EncodeToString(wrappedKey) + "\n")
     75 
     76 	return l, nil
     77 }
     78 
     79 type SSHRSAIdentity struct {
     80 	k      *rsa.PrivateKey
     81 	sshKey ssh.PublicKey
     82 }
     83 
     84 var _ Identity = &SSHRSAIdentity{}
     85 
     86 func (*SSHRSAIdentity) Type() string { return "ssh-rsa" }
     87 
     88 func NewSSHRSAIdentity(key *rsa.PrivateKey) (*SSHRSAIdentity, error) {
     89 	s, err := ssh.NewSignerFromKey(key)
     90 	if err != nil {
     91 		return nil, err
     92 	}
     93 	i := &SSHRSAIdentity{
     94 		k: key, sshKey: s.PublicKey(),
     95 	}
     96 	return i, nil
     97 }
     98 
     99 func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
    100 	if block.Type != "ssh-rsa" {
    101 		return nil, errors.New("wrong recipient block type")
    102 	}
    103 	if len(block.Args) != 1 {
    104 		return nil, errors.New("invalid ssh-rsa recipient block")
    105 	}
    106 	hash, err := format.DecodeString(block.Args[0])
    107 	if err != nil {
    108 		return nil, fmt.Errorf("failed to parse ssh-rsa recipient: %v", err)
    109 	}
    110 	if len(hash) != 4 {
    111 		return nil, errors.New("invalid ssh-rsa recipient block")
    112 	}
    113 	wrappedKey, err := format.DecodeString(string(block.Body))
    114 	if err != nil {
    115 		return nil, fmt.Errorf("failed to parse ssh-rsa recipient: %v", err)
    116 	}
    117 
    118 	h := sha256.New()
    119 	h.Write(i.sshKey.Marshal())
    120 	hh := h.Sum(nil)
    121 	if !bytes.Equal(hh[:4], hash) {
    122 		return nil, errors.New("wrong ssh-rsa key")
    123 	}
    124 
    125 	fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k,
    126 		wrappedKey, []byte(oaepLabel))
    127 	if err != nil {
    128 		return nil, fmt.Errorf("failed to decrypt file key: %v", err)
    129 	}
    130 	return fileKey, nil
    131 }
    132 
    133 type SSHEd25519Recipient struct {
    134 	sshKey         ssh.PublicKey
    135 	theirPublicKey [32]byte
    136 }
    137 
    138 var _ Recipient = &SSHEd25519Recipient{}
    139 
    140 func (*SSHEd25519Recipient) Type() string { return "ssh-ed25519" }
    141 
    142 func NewSSHEd25519Recipient(pk ssh.PublicKey) (*SSHEd25519Recipient, error) {
    143 	if pk.Type() != "ssh-ed25519" {
    144 		return nil, errors.New("SSH public key is not an Ed25519 key")
    145 	}
    146 	r := &SSHEd25519Recipient{
    147 		sshKey: pk,
    148 	}
    149 
    150 	if pk, ok := pk.(ssh.CryptoPublicKey); ok {
    151 		if pk, ok := pk.CryptoPublicKey().(ed25519.PublicKey); ok {
    152 			pubKey := ed25519PublicKeyToCurve25519(pk)
    153 			copy(r.theirPublicKey[:], pubKey)
    154 		} else {
    155 			return nil, errors.New("unexpected public key type")
    156 		}
    157 	} else {
    158 		return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
    159 	}
    160 	return r, nil
    161 }
    162 
    163 func ParseSSHRecipient(s string) (Recipient, error) {
    164 	pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
    165 	if err != nil {
    166 		return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err)
    167 	}
    168 
    169 	var r Recipient
    170 	switch t := pubKey.Type(); t {
    171 	case "ssh-rsa":
    172 		r, err = NewSSHRSARecipient(pubKey)
    173 	case "ssh-ed25519":
    174 		r, err = NewSSHEd25519Recipient(pubKey)
    175 	default:
    176 		return nil, fmt.Errorf("unknown SSH recipient type: %q", t)
    177 	}
    178 	if err != nil {
    179 		return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err)
    180 	}
    181 
    182 	return r, nil
    183 }
    184 
    185 var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
    186 
    187 func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte {
    188 	// ed25519.PublicKey is a little endian representation of the y-coordinate,
    189 	// with the most significant bit set based on the sign of the x-ccordinate.
    190 	bigEndianY := make([]byte, ed25519.PublicKeySize)
    191 	for i, b := range pk {
    192 		bigEndianY[ed25519.PublicKeySize-i-1] = b
    193 	}
    194 	bigEndianY[0] &= 0b0111_1111
    195 
    196 	// The Montgomery u-coordinate is derived through the bilinear map
    197 	//
    198 	//     u = (1 + y) / (1 - y)
    199 	//
    200 	// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
    201 	y := new(big.Int).SetBytes(bigEndianY)
    202 	denom := big.NewInt(1)
    203 	denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y)
    204 	u := y.Mul(y.Add(y, big.NewInt(1)), denom)
    205 	u.Mod(u, curve25519P)
    206 
    207 	out := make([]byte, 32)
    208 	uBytes := u.Bytes()
    209 	for i, b := range uBytes {
    210 		out[len(uBytes)-i-1] = b
    211 	}
    212 
    213 	return out
    214 }
    215 
    216 const ed25519Label = "age-tool.com ssh-ed25519"
    217 
    218 func (r *SSHEd25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
    219 	// TODO: DRY this up with the X25519 implementation.
    220 	var ephemeral, ourPublicKey [32]byte
    221 	if _, err := rand.Read(ephemeral[:]); err != nil {
    222 		return nil, err
    223 	}
    224 	curve25519.ScalarBaseMult(&ourPublicKey, &ephemeral)
    225 
    226 	var sharedSecret, tweak [32]byte
    227 	tH := hkdf.New(sha256.New, nil, r.sshKey.Marshal(), []byte(ed25519Label))
    228 	if _, err := io.ReadFull(tH, tweak[:]); err != nil {
    229 		return nil, err
    230 	}
    231 	curve25519.ScalarMult(&sharedSecret, &ephemeral, &r.theirPublicKey)
    232 	curve25519.ScalarMult(&sharedSecret, &tweak, &sharedSecret)
    233 
    234 	sH := sha256.New()
    235 	sH.Write(r.sshKey.Marshal())
    236 	hh := sH.Sum(nil)
    237 
    238 	l := &format.Recipient{
    239 		Type: "ssh-ed25519",
    240 		Args: []string{format.EncodeToString(hh[:4]),
    241 			format.EncodeToString(ourPublicKey[:])},
    242 	}
    243 
    244 	salt := make([]byte, 0, 32*2)
    245 	salt = append(salt, ourPublicKey[:]...)
    246 	salt = append(salt, r.theirPublicKey[:]...)
    247 	h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(ed25519Label))
    248 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
    249 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
    250 		return nil, err
    251 	}
    252 
    253 	wrappedKey, err := aeadEncrypt(wrappingKey, fileKey)
    254 	if err != nil {
    255 		return nil, err
    256 	}
    257 	l.Body = []byte(format.EncodeToString(wrappedKey) + "\n")
    258 
    259 	return l, nil
    260 }
    261 
    262 type SSHEd25519Identity struct {
    263 	secretKey, ourPublicKey [32]byte
    264 	sshKey                  ssh.PublicKey
    265 }
    266 
    267 var _ Identity = &SSHEd25519Identity{}
    268 
    269 func (*SSHEd25519Identity) Type() string { return "ssh-ed25519" }
    270 
    271 func NewSSHEd25519Identity(key ed25519.PrivateKey) (*SSHEd25519Identity, error) {
    272 	s, err := ssh.NewSignerFromKey(key)
    273 	if err != nil {
    274 		return nil, err
    275 	}
    276 	i := &SSHEd25519Identity{
    277 		sshKey: s.PublicKey(),
    278 	}
    279 	secretKey := ed25519PrivateKeyToCurve25519(key)
    280 	copy(i.secretKey[:], secretKey)
    281 	curve25519.ScalarBaseMult(&i.ourPublicKey, &i.secretKey)
    282 	return i, nil
    283 }
    284 
    285 func ParseSSHIdentity(pemBytes []byte) (Identity, error) {
    286 	k, err := ssh.ParseRawPrivateKey(pemBytes)
    287 	if err != nil {
    288 		return nil, err
    289 	}
    290 
    291 	switch k := k.(type) {
    292 	case *ed25519.PrivateKey:
    293 		return NewSSHEd25519Identity(*k)
    294 	case *rsa.PrivateKey:
    295 		return NewSSHRSAIdentity(k)
    296 	}
    297 
    298 	return nil, fmt.Errorf("unsupported SSH identity type: %T", k)
    299 }
    300 
    301 func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte {
    302 	h := sha512.New()
    303 	h.Write(pk[:32])
    304 	out := h.Sum(nil)
    305 	return out[:32]
    306 }
    307 
    308 func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
    309 	// TODO: DRY this up with the X25519 implementation.
    310 	if block.Type != "ssh-ed25519" {
    311 		return nil, errors.New("wrong recipient block type")
    312 	}
    313 	if len(block.Args) != 2 {
    314 		return nil, errors.New("invalid ssh-ed25519 recipient block")
    315 	}
    316 	hash, err := format.DecodeString(block.Args[0])
    317 	if err != nil {
    318 		return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err)
    319 	}
    320 	if len(hash) != 4 {
    321 		return nil, errors.New("invalid ssh-ed25519 recipient block")
    322 	}
    323 	publicKey, err := format.DecodeString(block.Args[1])
    324 	if err != nil {
    325 		return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err)
    326 	}
    327 	if len(publicKey) != 32 {
    328 		return nil, errors.New("invalid ssh-ed25519 recipient block")
    329 	}
    330 	wrappedKey, err := format.DecodeString(string(block.Body))
    331 	if err != nil {
    332 		return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err)
    333 	}
    334 
    335 	sH := sha256.New()
    336 	sH.Write(i.sshKey.Marshal())
    337 	hh := sH.Sum(nil)
    338 	if !bytes.Equal(hh[:4], hash) {
    339 		return nil, errors.New("wrong ssh-ed25519 key")
    340 	}
    341 
    342 	var sharedSecret, theirPublicKey, tweak [32]byte
    343 	copy(theirPublicKey[:], publicKey)
    344 	tH := hkdf.New(sha256.New, nil, i.sshKey.Marshal(), []byte(ed25519Label))
    345 	if _, err := io.ReadFull(tH, tweak[:]); err != nil {
    346 		return nil, err
    347 	}
    348 	curve25519.ScalarMult(&sharedSecret, &i.secretKey, &theirPublicKey)
    349 	curve25519.ScalarMult(&sharedSecret, &tweak, &sharedSecret)
    350 
    351 	salt := make([]byte, 0, 32*2)
    352 	salt = append(salt, theirPublicKey[:]...)
    353 	salt = append(salt, i.ourPublicKey[:]...)
    354 	h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(ed25519Label))
    355 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
    356 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
    357 		return nil, err
    358 	}
    359 
    360 	fileKey, err := aeadDecrypt(wrappingKey, wrappedKey)
    361 	if err != nil {
    362 		return nil, fmt.Errorf("failed to decrypt file key: %v", err)
    363 	}
    364 	return fileKey, nil
    365 }