...

Source file src/github.com/ProtonMail/go-crypto/openpgp/internal/ecc/curve25519.go

Documentation: github.com/ProtonMail/go-crypto/openpgp/internal/ecc

     1  // Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
     2  package ecc
     3  
     4  import (
     5  	"crypto/subtle"
     6  	"io"
     7  
     8  	"github.com/ProtonMail/go-crypto/openpgp/errors"
     9  	x25519lib "github.com/cloudflare/circl/dh/x25519"
    10  )
    11  
    12  type curve25519 struct{}
    13  
    14  func NewCurve25519() *curve25519 {
    15  	return &curve25519{}
    16  }
    17  
    18  func (c *curve25519) GetCurveName() string {
    19  	return "curve25519"
    20  }
    21  
    22  // MarshalBytePoint encodes the public point from native format, adding the prefix.
    23  // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
    24  func (c *curve25519) MarshalBytePoint(point []byte) []byte {
    25  	return append([]byte{0x40}, point...)
    26  }
    27  
    28  // UnmarshalBytePoint decodes the public point to native format, removing the prefix.
    29  // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
    30  func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
    31  	if len(point) != x25519lib.Size+1 {
    32  		return nil
    33  	}
    34  
    35  	// Remove prefix
    36  	return point[1:]
    37  }
    38  
    39  // MarshalByteSecret encodes the secret scalar from native format.
    40  // Note that the EC secret scalar differs from the definition of public keys in
    41  // [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
    42  // more uniform with how big integers are represented in OpenPGP, and (2) the
    43  // leading zeros are truncated.
    44  // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
    45  // Note that leading zero bytes are stripped later when encoding as an MPI.
    46  func (c *curve25519) MarshalByteSecret(secret []byte) []byte {
    47  	d := make([]byte, x25519lib.Size)
    48  	copyReversed(d, secret)
    49  
    50  	// The following ensures that the private key is a number of the form
    51  	// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of
    52  	// the curve.
    53  	//
    54  	// This masking is done internally in the underlying lib and so is unnecessary
    55  	// for security, but OpenPGP implementations require that private keys be
    56  	// pre-masked.
    57  	d[0] &= 127
    58  	d[0] |= 64
    59  	d[31] &= 248
    60  
    61  	return d
    62  }
    63  
    64  // UnmarshalByteSecret decodes the secret scalar from native format.
    65  // Note that the EC secret scalar differs from the definition of public keys in
    66  // [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
    67  // more uniform with how big integers are represented in OpenPGP, and (2) the
    68  // leading zeros are truncated.
    69  // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
    70  func (c *curve25519) UnmarshalByteSecret(d []byte) []byte {
    71  	if len(d) > x25519lib.Size {
    72  		return nil
    73  	}
    74  
    75  	// Ensure truncated leading bytes are re-added
    76  	secret := make([]byte, x25519lib.Size)
    77  	copyReversed(secret, d)
    78  
    79  	return secret
    80  }
    81  
    82  // generateKeyPairBytes Generates a private-public key-pair.
    83  // 'priv' is a private key; a little-endian scalar belonging to the set
    84  // 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the
    85  // curve. 'pub' is simply 'priv' * G where G is the base point.
    86  // See https://cr.yp.to/ecdh.html and RFC7748, sec 5.
    87  func (c *curve25519) generateKeyPairBytes(rand io.Reader) (priv, pub x25519lib.Key, err error) {
    88  	_, err = io.ReadFull(rand, priv[:])
    89  	if err != nil {
    90  		return
    91  	}
    92  
    93  	x25519lib.KeyGen(&pub, &priv)
    94  	return
    95  }
    96  
    97  func (c *curve25519) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) {
    98  	priv, pub, err := c.generateKeyPairBytes(rand)
    99  	if err != nil {
   100  		return
   101  	}
   102  
   103  	return pub[:], priv[:], nil
   104  }
   105  
   106  func (c *genericCurve) MaskSecret(secret []byte) []byte {
   107  	return secret
   108  }
   109  
   110  func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
   111  	// RFC6637 §8: "Generate an ephemeral key pair {v, V=vG}"
   112  	// ephemeralPrivate corresponds to `v`.
   113  	// ephemeralPublic corresponds to `V`.
   114  	ephemeralPrivate, ephemeralPublic, err := c.generateKeyPairBytes(rand)
   115  	if err != nil {
   116  		return nil, nil, err
   117  	}
   118  
   119  	// RFC6637 §8: "Obtain the authenticated recipient public key R"
   120  	// pubKey corresponds to `R`.
   121  	var pubKey x25519lib.Key
   122  	copy(pubKey[:], point)
   123  
   124  	// RFC6637 §8: "Compute the shared point S = vR"
   125  	//	"VB = convert point V to the octet string"
   126  	// sharedPoint corresponds to `VB`.
   127  	var sharedPoint x25519lib.Key
   128  	x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey)
   129  
   130  	return ephemeralPublic[:], sharedPoint[:], nil
   131  }
   132  
   133  func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error) {
   134  	var ephemeralPublic, decodedPrivate, sharedPoint x25519lib.Key
   135  	// RFC6637 §8: "The decryption is the inverse of the method given."
   136  	// All quoted descriptions in comments below describe encryption, and
   137  	// the reverse is performed.
   138  	// vsG corresponds to `VB` in RFC6637 §8 .
   139  
   140  	// RFC6637 §8: "VB = convert point V to the octet string"
   141  	copy(ephemeralPublic[:], vsG)
   142  
   143  	// decodedPrivate corresponds to `r` in RFC6637 §8 .
   144  	copy(decodedPrivate[:], secret)
   145  
   146  	// RFC6637 §8: "Note that the recipient obtains the shared secret by calculating
   147  	//   S = rV = rvG, where (r,R) is the recipient's key pair."
   148  	// sharedPoint corresponds to `S`.
   149  	x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic)
   150  
   151  	return sharedPoint[:], nil
   152  }
   153  
   154  func (c *curve25519) ValidateECDH(point []byte, secret []byte) (err error) {
   155  	var pk, sk x25519lib.Key
   156  	copy(sk[:], secret)
   157  	x25519lib.KeyGen(&pk, &sk)
   158  
   159  	if subtle.ConstantTimeCompare(point, pk[:]) == 0 {
   160  		return errors.KeyInvalidError("ecc: invalid curve25519 public point")
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  func copyReversed(out []byte, in []byte) {
   167  	l := len(in)
   168  	for i := 0; i < l; i++ {
   169  		out[i] = in[l-i-1]
   170  	}
   171  }
   172  

View as plain text