Skip to content

Commit

Permalink
internal/age: use a prototype of X25519 from golang/go#32670
Browse files Browse the repository at this point in the history
  • Loading branch information
FiloSottile committed Oct 13, 2019
1 parent 11fc3e2 commit 2a0aef5
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 36 deletions.
7 changes: 1 addition & 6 deletions cmd/age/age.go
Expand Up @@ -7,7 +7,6 @@
package main

import (
"crypto/rand"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -47,11 +46,7 @@ func generate() {
log.Fatalf("-generate takes no arguments")
}

key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
log.Fatalf("Internal error: %v", err)
}
k, err := age.NewX25519Identity(key)
k, err := age.GenerateX25519Identity()
if err != nil {
log.Fatalf("Internal error: %v", err)
}
Expand Down
78 changes: 48 additions & 30 deletions internal/age/x25519.go
Expand Up @@ -14,28 +14,30 @@ import (
"io"
"strings"

"github.com/FiloSottile/age/internal/curve25519"
"github.com/FiloSottile/age/internal/format"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)

const x25519Label = "age-tool.com X25519"

type X25519Recipient struct {
theirPublicKey [32]byte
theirPublicKey []byte
}

var _ Recipient = &X25519Recipient{}

func (*X25519Recipient) Type() string { return "X25519" }

func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) {
if len(publicKey) != 32 {
if len(publicKey) != curve25519.PointSize {
return nil, errors.New("invalid X25519 public key")
}
r := &X25519Recipient{}
copy(r.theirPublicKey[:], publicKey)
r := &X25519Recipient{
theirPublicKey: make([]byte, curve25519.PointSize),
}
copy(r.theirPublicKey, publicKey)
return r, nil
}

Expand All @@ -56,24 +58,29 @@ func ParseX25519Recipient(s string) (*X25519Recipient, error) {
}

func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
var ephemeral, ourPublicKey [32]byte
if _, err := rand.Read(ephemeral[:]); err != nil {
ephemeral := make([]byte, curve25519.ScalarSize)
if _, err := rand.Read(ephemeral); err != nil {
return nil, err
}
ourPublicKey, err := curve25519.X25519(ephemeral, curve25519.Basepoint)
if err != nil {
return nil, err
}
curve25519.ScalarBaseMult(&ourPublicKey, &ephemeral)

var sharedSecret [32]byte
curve25519.ScalarMult(&sharedSecret, &ephemeral, &r.theirPublicKey)
sharedSecret, err := curve25519.X25519(ephemeral, r.theirPublicKey)
if err != nil {
return nil, err
}

l := &format.Recipient{
Type: "X25519",
Args: []string{format.EncodeToString(ourPublicKey[:])},
Args: []string{format.EncodeToString(ourPublicKey)},
}

salt := make([]byte, 0, 32*2)
salt = append(salt, ourPublicKey[:]...)
salt = append(salt, r.theirPublicKey[:]...)
h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label))
salt := make([]byte, 0, len(ourPublicKey)+len(r.theirPublicKey))
salt = append(salt, ourPublicKey...)
salt = append(salt, r.theirPublicKey...)
h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label))
wrappingKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, wrappingKey); err != nil {
return nil, err
Expand All @@ -89,27 +96,37 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) {
}

func (r *X25519Recipient) String() string {
return "pubkey:" + format.EncodeToString(r.theirPublicKey[:])
return "pubkey:" + format.EncodeToString(r.theirPublicKey)
}

type X25519Identity struct {
secretKey, ourPublicKey [32]byte
secretKey, ourPublicKey []byte
}

var _ Identity = &X25519Identity{}

func (*X25519Identity) Type() string { return "X25519" }

func NewX25519Identity(secretKey []byte) (*X25519Identity, error) {
if len(secretKey) != 32 {
if len(secretKey) != curve25519.ScalarSize {
return nil, errors.New("invalid X25519 secret key")
}
i := &X25519Identity{}
copy(i.secretKey[:], secretKey)
curve25519.ScalarBaseMult(&i.ourPublicKey, &i.secretKey)
i := &X25519Identity{
secretKey: make([]byte, curve25519.ScalarSize),
}
copy(i.secretKey, secretKey)
i.ourPublicKey, _ = curve25519.X25519(i.secretKey, curve25519.Basepoint)
return i, nil
}

func GenerateX25519Identity() (*X25519Identity, error) {
secretKey := make([]byte, 32)
if _, err := rand.Read(secretKey); err != nil {
return nil, fmt.Errorf("internal error: %v", err)
}
return NewX25519Identity(secretKey)
}

func ParseX25519Identity(s string) (*X25519Identity, error) {
if !strings.HasPrefix(s, "AGE_SECRET_KEY_") {
return nil, fmt.Errorf("malformed secret key: %s", s)
Expand Down Expand Up @@ -137,18 +154,19 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err)
}
if len(publicKey) != 32 {
if len(publicKey) != curve25519.PointSize {
return nil, errors.New("invalid X25519 recipient block")
}

var sharedSecret, theirPublicKey [32]byte
copy(theirPublicKey[:], publicKey)
curve25519.ScalarMult(&sharedSecret, &i.secretKey, &theirPublicKey)
sharedSecret, err := curve25519.X25519(i.secretKey, publicKey)
if err != nil {
return nil, fmt.Errorf("invalid X25519 recipient: %v", err)
}

salt := make([]byte, 0, 32*2)
salt = append(salt, theirPublicKey[:]...)
salt = append(salt, i.ourPublicKey[:]...)
h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label))
salt := make([]byte, 0, len(publicKey)+len(i.ourPublicKey))
salt = append(salt, publicKey...)
salt = append(salt, i.ourPublicKey...)
h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label))
wrappingKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, wrappingKey); err != nil {
return nil, err
Expand All @@ -168,5 +186,5 @@ func (i *X25519Identity) Recipient() *X25519Recipient {
}

func (i *X25519Identity) String() string {
return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey[:])
return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey)
}
86 changes: 86 additions & 0 deletions internal/curve25519/x25519.go
@@ -0,0 +1,86 @@
// 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 curve25519 implements the new proposed API for
// golang.org/x/crypto/curve25519 from golang.org/issue/32670.
package curve25519

import (
"crypto/subtle"
"fmt"

"golang.org/x/crypto/curve25519"
)

const (
// ScalarSize is the size of the scalar input to X25519.
ScalarSize = 32
// PointSize is the size of the point input to X25519.
PointSize = 32
)

// Basepoint is the canonical Curve25519 generator.
var Basepoint []byte

func init() {
Basepoint = []byte{
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
}

func checkBasepoint() {
if subtle.ConstantTimeCompare(Basepoint, []byte{
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}) != 1 {
panic("curve25519: global Basepoint value was modified")
}
}

// X25519 returns the result of the scalar multiplication (scalar * point),
// according to RFC 7748, Section 5. scalar, point and the return value are
// slices of 32 bytes.
//
// scalar can be egenrated at random, for example with crypto/rand. point should
// be either Basepoint or the output of an X25519 call.
//
// If point is Basepoint (but not if it's a different slice with the same
// contents) a precomputed implementation might be used for performance.
func X25519(scalar, point []byte) ([]byte, error) {
// Outline the body of function, to let the allocation be inlined in the
// caller, and possibly avoid escaping to the heap.
var dst [32]byte
return x25519(&dst, scalar, point)
}

func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
var in [32]byte
if l := len(scalar); l != 32 {
return nil, fmt.Errorf("bad scalar length: %d, expected %d", l, 32)
}
if l := len(point); l != 32 {
return nil, fmt.Errorf("bad point length: %d, expected %d", l, 32)
}
copy(in[:], scalar)
if &point[0] == &Basepoint[0] {
checkBasepoint()
curve25519.ScalarBaseMult(dst, &in)
} else {
var base, zero [32]byte
copy(base[:], point)
curve25519.ScalarMult(dst, &in, &base)
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
// TODO: test this codepath with all low order points.
return nil, fmt.Errorf("bad input point: low order point")
}
}
return dst[:], nil
}

0 comments on commit 2a0aef5

Please sign in to comment.