1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package ecdh implements ECDH encryption, suitable for OpenPGP,
6// as specified in RFC 6637, section 8.
7package ecdh
8
9import (
10	"errors"
11	"io"
12	"math/big"
13
14	"golang.org/x/crypto/curve25519"
15	"golang.org/x/crypto/openpgp/aes/keywrap"
16	"golang.org/x/crypto/openpgp/internal/ecc"
17)
18
19// Generates a private-public key-pair.
20// 'priv' is a private key; a scalar belonging to the set
21// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the
22// curve. 'pub' is simply 'priv' * G where G is the base point.
23// See https://cr.yp.to/ecdh.html and RFC7748, sec 5.
24func x25519GenerateKeyPairBytes(rand io.Reader) (priv [32]byte, pub [32]byte, err error) {
25	var n, helper = new(big.Int), new(big.Int)
26	n.SetUint64(1)
27	n.Lsh(n, 252)
28	helper.SetString("27742317777372353535851937790883648493", 10)
29	n.Add(n, helper)
30
31	for true {
32		_, err = io.ReadFull(rand, priv[:])
33		if err != nil {
34			return
35		}
36		// The following ensures that the private key is a number of the form
37		// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of
38		// of the curve.
39		priv[0] &= 248
40		priv[31] &= 127
41		priv[31] |= 64
42
43		// If the scalar is out of range, sample another random number.
44		if new(big.Int).SetBytes(priv[:]).Cmp(n) >= 0 {
45			continue
46		}
47
48		curve25519.ScalarBaseMult(&pub, &priv)
49		return
50	}
51	return
52}
53
54// X25519GenerateKey samples the key pair according to the correct distribution.
55// It also sets the given key-derivation function and returns the *PrivateKey
56// object along with an error.
57func X25519GenerateKey(rand io.Reader, kdf KDF) (priv *PrivateKey, err error) {
58	ci := ecc.FindByName("Curve25519")
59	priv = new(PrivateKey)
60	priv.PublicKey.Curve = ci.Curve
61	d, pubKey, err := x25519GenerateKeyPairBytes(rand)
62	if err != nil {
63		return nil, err
64	}
65	priv.PublicKey.KDF = kdf
66	priv.D = make([]byte, 32)
67	copyReversed(priv.D, d[:])
68	priv.PublicKey.CurveType = ci.CurveType
69	priv.PublicKey.Curve = ci.Curve
70	/*
71	 * Note that ECPoint.point differs from the definition of public keys in
72	 * [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
73	 * more uniform with how big integers are represented in TLS, and (2) there
74	 * is an additional length byte (so ECpoint.point is actually 33 bytes),
75	 * again for uniformity (and extensibility).
76	 */
77	var encodedKey = make([]byte, 33)
78	encodedKey[0] = 0x40
79	copy(encodedKey[1:], pubKey[:])
80	priv.PublicKey.X = new(big.Int).SetBytes(encodedKey[:])
81	priv.PublicKey.Y = new(big.Int)
82	return priv, nil
83}
84
85func X25519Encrypt(random io.Reader, pub *PublicKey, msg, curveOID, fingerprint []byte) (vsG, c []byte, err error) {
86	d, ephemeralKey, err := x25519GenerateKeyPairBytes(random)
87	if err != nil {
88		return nil, nil, err
89	}
90	var pubKey [32]byte
91
92	if pub.X.BitLen() > 33*264 {
93		return nil, nil, errors.New("ecdh: invalid key")
94	}
95	copy(pubKey[:], pub.X.Bytes()[1:])
96
97	var zb [32]byte
98	curve25519.ScalarBaseMult(&zb, &d)
99	curve25519.ScalarMult(&zb, &d, &pubKey)
100	z, err := buildKey(pub, zb[:], curveOID, fingerprint, false, false)
101
102	if err != nil {
103		return nil, nil, err
104	}
105
106	if c, err = keywrap.Wrap(z, msg); err != nil {
107		return nil, nil, err
108	}
109
110	var vsg [33]byte
111	vsg[0] = 0x40
112	copy(vsg[1:], ephemeralKey[:])
113
114	return vsg[:], c, nil
115}
116
117func X25519Decrypt(priv *PrivateKey, vsG, m, curveOID, fingerprint []byte) (msg []byte, err error) {
118	var zb, d, ephemeralKey [32]byte
119	if len(vsG) != 33 || vsG[0] != 0x40 {
120		return nil, errors.New("ecdh: invalid key")
121	}
122	copy(ephemeralKey[:], vsG[1:33])
123
124	copyReversed(d[:], priv.D)
125	curve25519.ScalarBaseMult(&zb, &d)
126	curve25519.ScalarMult(&zb, &d, &ephemeralKey)
127
128	var c []byte
129
130	for i := 0; i < 3; i++ {
131		// Try buildKey three times for compat, see comments in buildKey.
132		z, err := buildKey(&priv.PublicKey, zb[:], curveOID, fingerprint, i == 1, i == 2)
133		if err != nil {
134			return nil, err
135		}
136
137		res, err := keywrap.Unwrap(z, m)
138		if i == 2 && err != nil {
139			// Only return an error after we've tried all variants of buildKey.
140			return nil, err
141		}
142
143		c = res
144		if err == nil {
145			break
146		}
147	}
148
149	return c[:len(c)-int(c[len(c)-1])], nil
150}
151
152func copyReversed(out []byte, in []byte) {
153	l := len(in)
154	for i := 0; i < l; i++ {
155		out[i] = in[l-i-1]
156	}
157}
158