age

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

x25519.go (4778B)


      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 	"crypto/rand"
     11 	"crypto/sha256"
     12 	"errors"
     13 	"fmt"
     14 	"io"
     15 	"strings"
     16 
     17 	"github.com/FiloSottile/age/internal/format"
     18 	"golang.org/x/crypto/chacha20poly1305"
     19 	"golang.org/x/crypto/curve25519"
     20 	"golang.org/x/crypto/hkdf"
     21 )
     22 
     23 const x25519Label = "age-tool.com X25519"
     24 
     25 type X25519Recipient struct {
     26 	theirPublicKey [32]byte
     27 }
     28 
     29 var _ Recipient = &X25519Recipient{}
     30 
     31 func (*X25519Recipient) Type() string { return "X25519" }
     32 
     33 func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) {
     34 	if len(publicKey) != 32 {
     35 		return nil, errors.New("invalid X25519 public key")
     36 	}
     37 	r := &X25519Recipient{}
     38 	copy(r.theirPublicKey[:], publicKey)
     39 	return r, nil
     40 }
     41 
     42 func ParseX25519Recipient(s string) (*X25519Recipient, error) {
     43 	if !strings.HasPrefix(s, "pubkey:") {
     44 		return nil, fmt.Errorf("malformed recipient: %s", s)
     45 	}
     46 	pubKey := strings.TrimPrefix(s, "pubkey:")
     47 	k, err := format.DecodeString(pubKey)
     48 	if err != nil {
     49 		return nil, fmt.Errorf("malformed recipient: %s", s)
     50 	}
     51 	r, err := NewX25519Recipient(k)
     52 	if err != nil {
     53 		return nil, fmt.Errorf("malformed recipient: %s", s)
     54 	}
     55 	return r, nil
     56 }
     57 
     58 func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
     59 	var ephemeral, ourPublicKey [32]byte
     60 	if _, err := rand.Read(ephemeral[:]); err != nil {
     61 		return nil, err
     62 	}
     63 	curve25519.ScalarBaseMult(&ourPublicKey, &ephemeral)
     64 
     65 	var sharedSecret [32]byte
     66 	curve25519.ScalarMult(&sharedSecret, &ephemeral, &r.theirPublicKey)
     67 
     68 	l := &format.Recipient{
     69 		Type: "X25519",
     70 		Args: []string{format.EncodeToString(ourPublicKey[:])},
     71 	}
     72 
     73 	salt := make([]byte, 0, 32*2)
     74 	salt = append(salt, ourPublicKey[:]...)
     75 	salt = append(salt, r.theirPublicKey[:]...)
     76 	h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label))
     77 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
     78 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
     79 		return nil, err
     80 	}
     81 
     82 	wrappedKey, err := aeadEncrypt(wrappingKey, fileKey)
     83 	if err != nil {
     84 		return nil, err
     85 	}
     86 	l.Body = []byte(format.EncodeToString(wrappedKey) + "\n")
     87 
     88 	return l, nil
     89 }
     90 
     91 func (r *X25519Recipient) String() string {
     92 	return "pubkey:" + format.EncodeToString(r.theirPublicKey[:])
     93 }
     94 
     95 type X25519Identity struct {
     96 	secretKey, ourPublicKey [32]byte
     97 }
     98 
     99 var _ Identity = &X25519Identity{}
    100 
    101 func (*X25519Identity) Type() string { return "X25519" }
    102 
    103 func NewX25519Identity(secretKey []byte) (*X25519Identity, error) {
    104 	if len(secretKey) != 32 {
    105 		return nil, errors.New("invalid X25519 secret key")
    106 	}
    107 	i := &X25519Identity{}
    108 	copy(i.secretKey[:], secretKey)
    109 	curve25519.ScalarBaseMult(&i.ourPublicKey, &i.secretKey)
    110 	return i, nil
    111 }
    112 
    113 func ParseX25519Identity(s string) (*X25519Identity, error) {
    114 	if !strings.HasPrefix(s, "AGE_SECRET_KEY_") {
    115 		return nil, fmt.Errorf("malformed secret key: %s", s)
    116 	}
    117 	privKey := strings.TrimPrefix(s, "AGE_SECRET_KEY_")
    118 	k, err := format.DecodeString(privKey)
    119 	if err != nil {
    120 		return nil, fmt.Errorf("malformed secret key: %s", s)
    121 	}
    122 	r, err := NewX25519Identity(k)
    123 	if err != nil {
    124 		return nil, fmt.Errorf("malformed secret key: %s", s)
    125 	}
    126 	return r, nil
    127 }
    128 
    129 func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
    130 	if block.Type != "X25519" {
    131 		return nil, errors.New("wrong recipient block type")
    132 	}
    133 	if len(block.Args) != 1 {
    134 		return nil, errors.New("invalid X25519 recipient block")
    135 	}
    136 	publicKey, err := format.DecodeString(block.Args[0])
    137 	if err != nil {
    138 		return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err)
    139 	}
    140 	if len(publicKey) != 32 {
    141 		return nil, errors.New("invalid X25519 recipient block")
    142 	}
    143 	wrappedKey, err := format.DecodeString(string(block.Body))
    144 	if err != nil {
    145 		return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err)
    146 	}
    147 
    148 	var sharedSecret, theirPublicKey [32]byte
    149 	copy(theirPublicKey[:], publicKey)
    150 	curve25519.ScalarMult(&sharedSecret, &i.secretKey, &theirPublicKey)
    151 
    152 	salt := make([]byte, 0, 32*2)
    153 	salt = append(salt, theirPublicKey[:]...)
    154 	salt = append(salt, i.ourPublicKey[:]...)
    155 	h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label))
    156 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
    157 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
    158 		return nil, err
    159 	}
    160 
    161 	fileKey, err := aeadDecrypt(wrappingKey, wrappedKey)
    162 	if err != nil {
    163 		return nil, fmt.Errorf("failed to decrypt file key: %v", err)
    164 	}
    165 	return fileKey, nil
    166 }
    167 
    168 func (i *X25519Identity) Recipient() *X25519Recipient {
    169 	r := &X25519Recipient{}
    170 	r.theirPublicKey = i.ourPublicKey
    171 	return r
    172 }
    173 
    174 func (i *X25519Identity) String() string {
    175 	return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey[:])
    176 }