mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-04 03:13:57 +00:00
internal/age: use a prototype of X25519 from golang/go#32670
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -14,16 +14,16 @@ 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{}
|
||||
@@ -31,11 +31,13 @@ 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -89,11 +96,11 @@ 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{}
|
||||
@@ -101,15 +108,25 @@ 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)
|
||||
@@ -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
|
||||
@@ -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
internal/curve25519/x25519.go
Normal file
86
internal/curve25519/x25519.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user