mirror of https://github.com/cheat/cheat.git
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
|
// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
|
||
|
package ecc
|
||
|
|
||
|
import (
|
||
|
"crypto/subtle"
|
||
|
"io"
|
||
|
|
||
|
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||
|
x25519lib "github.com/cloudflare/circl/dh/x25519"
|
||
|
)
|
||
|
|
||
|
type curve25519 struct {}
|
||
|
|
||
|
func NewCurve25519() *curve25519 {
|
||
|
return &curve25519{}
|
||
|
}
|
||
|
|
||
|
func (c *curve25519) GetCurveName() string {
|
||
|
return "curve25519"
|
||
|
}
|
||
|
|
||
|
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
||
|
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
||
|
func (c *curve25519) MarshalBytePoint(point [] byte) []byte {
|
||
|
return append([]byte{0x40}, point...)
|
||
|
}
|
||
|
|
||
|
// UnmarshalBytePoint decodes the public point to native format, removing the prefix.
|
||
|
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
||
|
func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
|
||
|
if len(point) != x25519lib.Size + 1 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Remove prefix
|
||
|
return point[1:]
|
||
|
}
|
||
|
|
||
|
// MarshalByteSecret encodes the secret scalar from native format.
|
||
|
// Note that the EC secret scalar differs from the definition of public keys in
|
||
|
// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
|
||
|
// more uniform with how big integers are represented in OpenPGP, and (2) the
|
||
|
// leading zeros are truncated.
|
||
|
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
|
||
|
// Note that leading zero bytes are stripped later when encoding as an MPI.
|
||
|
func (c *curve25519) MarshalByteSecret(secret []byte) []byte {
|
||
|
d := make([]byte, x25519lib.Size)
|
||
|
copyReversed(d, secret)
|
||
|
|
||
|
// The following ensures that the private key is a number of the form
|
||
|
// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of
|
||
|
// the curve.
|
||
|
//
|
||
|
// This masking is done internally in the underlying lib and so is unnecessary
|
||
|
// for security, but OpenPGP implementations require that private keys be
|
||
|
// pre-masked.
|
||
|
d[0] &= 127
|
||
|
d[0] |= 64
|
||
|
d[31] &= 248
|
||
|
|
||
|
return d
|
||
|
}
|
||
|
|
||
|
// UnmarshalByteSecret decodes the secret scalar from native format.
|
||
|
// Note that the EC secret scalar differs from the definition of public keys in
|
||
|
// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
|
||
|
// more uniform with how big integers are represented in OpenPGP, and (2) the
|
||
|
// leading zeros are truncated.
|
||
|
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
|
||
|
func (c *curve25519) UnmarshalByteSecret(d []byte) []byte {
|
||
|
if len(d) > x25519lib.Size {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Ensure truncated leading bytes are re-added
|
||
|
secret := make([]byte, x25519lib.Size)
|
||
|
copyReversed(secret, d)
|
||
|
|
||
|
return secret
|
||
|
}
|
||
|
|
||
|
// generateKeyPairBytes Generates a private-public key-pair.
|
||
|
// 'priv' is a private key; a little-endian scalar belonging to the set
|
||
|
// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the
|
||
|
// curve. 'pub' is simply 'priv' * G where G is the base point.
|
||
|
// See https://cr.yp.to/ecdh.html and RFC7748, sec 5.
|
||
|
func (c *curve25519) generateKeyPairBytes(rand io.Reader) (priv, pub x25519lib.Key, err error) {
|
||
|
_, err = io.ReadFull(rand, priv[:])
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
x25519lib.KeyGen(&pub, &priv)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *curve25519) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) {
|
||
|
priv, pub, err := c.generateKeyPairBytes(rand)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return pub[:], priv[:], nil
|
||
|
}
|
||
|
|
||
|
func (c *genericCurve) MaskSecret(secret []byte) []byte {
|
||
|
return secret
|
||
|
}
|
||
|
|
||
|
func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
|
||
|
// RFC6637 §8: "Generate an ephemeral key pair {v, V=vG}"
|
||
|
// ephemeralPrivate corresponds to `v`.
|
||
|
// ephemeralPublic corresponds to `V`.
|
||
|
ephemeralPrivate, ephemeralPublic, err := c.generateKeyPairBytes(rand)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
// RFC6637 §8: "Obtain the authenticated recipient public key R"
|
||
|
// pubKey corresponds to `R`.
|
||
|
var pubKey x25519lib.Key
|
||
|
copy(pubKey[:], point)
|
||
|
|
||
|
// RFC6637 §8: "Compute the shared point S = vR"
|
||
|
// "VB = convert point V to the octet string"
|
||
|
// sharedPoint corresponds to `VB`.
|
||
|
var sharedPoint x25519lib.Key
|
||
|
x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey)
|
||
|
|
||
|
return ephemeralPublic[:], sharedPoint[:], nil
|
||
|
}
|
||
|
|
||
|
func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error) {
|
||
|
var ephemeralPublic, decodedPrivate, sharedPoint x25519lib.Key
|
||
|
// RFC6637 §8: "The decryption is the inverse of the method given."
|
||
|
// All quoted descriptions in comments below describe encryption, and
|
||
|
// the reverse is performed.
|
||
|
// vsG corresponds to `VB` in RFC6637 §8 .
|
||
|
|
||
|
// RFC6637 §8: "VB = convert point V to the octet string"
|
||
|
copy(ephemeralPublic[:], vsG)
|
||
|
|
||
|
// decodedPrivate corresponds to `r` in RFC6637 §8 .
|
||
|
copy(decodedPrivate[:], secret)
|
||
|
|
||
|
// RFC6637 §8: "Note that the recipient obtains the shared secret by calculating
|
||
|
// S = rV = rvG, where (r,R) is the recipient's key pair."
|
||
|
// sharedPoint corresponds to `S`.
|
||
|
x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic)
|
||
|
|
||
|
return sharedPoint[:], nil
|
||
|
}
|
||
|
|
||
|
func (c *curve25519) ValidateECDH(point []byte, secret []byte) (err error) {
|
||
|
var pk, sk x25519lib.Key
|
||
|
copy(sk[:], secret)
|
||
|
x25519lib.KeyGen(&pk, &sk)
|
||
|
|
||
|
if subtle.ConstantTimeCompare(point, pk[:]) == 0 {
|
||
|
return errors.KeyInvalidError("ecc: invalid curve25519 public point")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func copyReversed(out []byte, in []byte) {
|
||
|
l := len(in)
|
||
|
for i := 0; i < l; i++ {
|
||
|
out[i] = in[l-i-1]
|
||
|
}
|
||
|
}
|