Skip to content

Commit

Permalink
WIP: bump to draft-ietf-openpgp-pqc-01
Browse files Browse the repository at this point in the history
  • Loading branch information
Aron Wussler committed Feb 29, 2024
1 parent bbd4510 commit c5ad8fd
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 832 deletions.
9 changes: 6 additions & 3 deletions openpgp/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,7 @@ func TestAddV4MlkemSubkey(t *testing.T) {
DefaultHash: crypto.SHA512,
Algorithm: packet.PubKeyAlgoEdDSA,
V6Keys: false,
DefaultCipher: packet.CipherAES256,
Time: func() time.Time {
parsed, _ := time.Parse("2006-01-02", "2013-07-01")
return parsed
Expand Down Expand Up @@ -1901,9 +1902,6 @@ func testAddMlkemSubkey(t *testing.T, entity *Entity, v6Keys bool) {
DefaultHash: crypto.SHA512,
Algorithm: algo,
V6Keys: v6Keys,
AEADConfig: &packet.AEADConfig{
DefaultMode: packet.AEADModeOCB,
},
Time: func() time.Time {
parsed, _ := time.Parse("2006-01-02", "2013-07-01")
return parsed
Expand All @@ -1924,6 +1922,11 @@ func testAddMlkemSubkey(t *testing.T, entity *Entity, v6Keys bool) {
entity.Subkeys[0].PublicKey.PubKeyAlgo)
}

if entity.Subkeys[0].PublicKey.Version != entity.PrivateKey.Version {
t.Fatalf("Expected subkey version: %d, got: %d", entity.PrivateKey.Version,
entity.Subkeys[0].PublicKey.Version)
}

serializedEntity := bytes.NewBuffer(nil)
err = entity.SerializePrivate(serializedEntity, nil)
if err != nil {
Expand Down
17 changes: 11 additions & 6 deletions openpgp/keys_v6_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ func TestGeneratePqKey(t *testing.T) {
}

asymmAlgos := map[string] packet.PublicKeyAlgorithm{
"ML-DSA3_Ed25519": packet.PubKeyAlgoMldsa65Ed25519,
"ML-DSA5_Ed448": packet.PubKeyAlgoMldsa87Ed448,
"ML-DSA3_P256": packet.PubKeyAlgoMldsa65p256,
"ML-DSA5_P384":packet.PubKeyAlgoMldsa87p384,
"ML-DSA3_Brainpool256": packet.PubKeyAlgoMldsa65Brainpool256,
"ML-DSA5_Brainpool384":packet.PubKeyAlgoMldsa87Brainpool384,
"ML-DSA65_Ed25519": packet.PubKeyAlgoMldsa65Ed25519,
"ML-DSA87_Ed448": packet.PubKeyAlgoMldsa87Ed448,
"ML-DSA65_P256": packet.PubKeyAlgoMldsa65p256,
"ML-DSA87_P384":packet.PubKeyAlgoMldsa87p384,
"ML-DSA65_Brainpool256": packet.PubKeyAlgoMldsa65Brainpool256,
"ML-DSA87_Brainpool384":packet.PubKeyAlgoMldsa87Brainpool384,
"Slhdsa_simple_SHA2":packet.PubKeyAlgoSlhdsaSha2,
"Slhdsa_simple_SHAKE":packet.PubKeyAlgoSlhdsaShake,
}
Expand All @@ -228,6 +228,7 @@ func TestGeneratePqKey(t *testing.T) {
DefaultHash: crypto.SHA512,
Algorithm: algo,
V6Keys: true,
DefaultCipher: packet.CipherAES256,
AEADConfig: &packet.AEADConfig {
DefaultMode: packet.AEADModeOCB,
},
Expand Down Expand Up @@ -332,6 +333,10 @@ func TestAddV6MlkemSubkey(t *testing.T) {
DefaultHash: crypto.SHA512,
Algorithm: packet.PubKeyAlgoEd25519,
V6Keys: true,
DefaultCipher: packet.CipherAES256,
AEADConfig: &packet.AEADConfig {
DefaultMode: packet.AEADModeOCB,
},
Time: func() time.Time {
parsed, _ := time.Parse("2006-01-02", "2013-07-01")
return parsed
Expand Down
71 changes: 71 additions & 0 deletions openpgp/mlkem_ecdh/mlkem_ecdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package mlkem_ecdh

import (
goerrors "errors"
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
"golang.org/x/crypto/sha3"
"io"

Expand Down Expand Up @@ -156,5 +157,75 @@ func Validate(priv *PrivateKey) (err error) {
return errors.KeyInvalidError("mlkem_ecdh: invalid public key")
}

return
}

// EncodeFields encodes an ML-KEM + ECDH session key encryption fields as
// ephemeral ECDH public key | ML-KEM ciphertext | follow byte length | cipherFunction (v3 only) | encryptedSessionKey
// and writes it to writer.
func EncodeFields(w io.Writer, ec, ml, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) {
if _, err = w.Write(ec); err != nil {
return err
}

if _, err = w.Write(ml); err != nil {
return err
}

lenAlgorithm := 0
if !v6 {
lenAlgorithm = 1
}

if _, err = w.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil {
return err
}

if !v6 {
if _, err = w.Write([]byte{cipherFunction}); err != nil {
return err
}
}

_, err = w.Write(encryptedSessionKey)
return err
}

// DecodeFields decodes an ML-KEM + ECDH session key encryption fields as
// ephemeral ECDH public key | ML-KEM ciphertext | follow byte length | cipherFunction (v3 only) | encryptedSessionKey.
func DecodeFields(r io.Reader, lenEcc, lenMlkem int, v6 bool) (encryptedMPI1, encryptedMPI2, encryptedMPI3 encoding.Field, cipherFunction byte, err error) {
var buf [1]byte

encryptedMPI1 = encoding.NewEmptyOctetArray(lenEcc)
if _, err = encryptedMPI1.ReadFrom(r); err != nil {
return
}

encryptedMPI2 = encoding.NewEmptyOctetArray(lenMlkem)
if _, err = encryptedMPI2.ReadFrom(r); err != nil {
return
}

// A one-octet size of the following fields.
if _, err = io.ReadFull(r, buf[:]); err != nil {
return
}

followingLen := buf[0]
// The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
if !v6 {
if _, err = io.ReadFull(r, buf[:]); err != nil {
return
}
cipherFunction = buf[0]
followingLen -= 1
}

// The encrypted session key.
encryptedMPI3 = encoding.NewEmptyOctetArray(int(followingLen))
if _, err = encryptedMPI3.ReadFrom(r); err != nil {
return
}

return
}
81 changes: 14 additions & 67 deletions openpgp/packet/encrypted_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,27 +137,27 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) {
return
}
case PubKeyAlgoMlkem768X25519:
if cipherFunction, err = e.readMlkemEcdhKey(r, 32, 1088, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 32, 1088, e.Version == 6); err != nil {
return err
}
case PubKeyAlgoMlkem1024X448:
if cipherFunction, err = e.readMlkemEcdhKey(r, 56, 1568, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 56, 1568, e.Version == 6); err != nil {
return err
}
case PubKeyAlgoMlkem768P256:
if cipherFunction, err = e.readMlkemEcdhKey(r, 65, 1088, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 65, 1088, e.Version == 6); err != nil {
return err
}
case PubKeyAlgoMlkem1024P384:
if cipherFunction, err = e.readMlkemEcdhKey(r, 97, 1568, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 97, 1568, e.Version == 6); err != nil {
return err
}
case PubKeyAlgoMlkem768Brainpool256:
if cipherFunction, err = e.readMlkemEcdhKey(r, 65, 1088, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 65, 1088, e.Version == 6); err != nil {
return err
}
case PubKeyAlgoMlkem1024Brainpool384:
if cipherFunction, err = e.readMlkemEcdhKey(r, 97, 1568, e.Version == 6); err != nil {
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 97, 1568, e.Version == 6); err != nil {
return err
}
}
Expand All @@ -175,36 +175,6 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) {
return
}

// readMlkemEcdhKey reads ML-KEM + ECC PKESK as specified in
// https://www.ietf.org/archive/id/draft-wussler-openpgp-pqc-03.html#name-public-key-encrypted-sessio
func (e *EncryptedKey) readMlkemEcdhKey(r io.Reader, lenEcc, lenMlkem int, v6 bool) (cipherFunction byte, err error) {
e.encryptedMPI1 = encoding.NewEmptyOctetArray(lenEcc)
if _, err = e.encryptedMPI1.ReadFrom(r); err != nil {
return
}

e.encryptedMPI2 = encoding.NewEmptyOctetArray(lenMlkem)
if _, err = e.encryptedMPI2.ReadFrom(r); err != nil {
return
}

if !v6 {
var buf [1]byte
_, err = io.ReadFull(r, buf[:])
if err != nil {
return
}
cipherFunction = buf[0]
}

e.encryptedMPI3 = new(encoding.OID)
if _, err = e.encryptedMPI3.ReadFrom(r); err != nil {
return
}

return
}

// Decrypt decrypts an encrypted session key with the given private key. The
// private key must have been decrypted first.
// If config is nil, sensible defaults will be used.
Expand Down Expand Up @@ -308,7 +278,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error {
encodedLength = x448.EncodedFieldsLength(e.encryptedSession, e.Version == 6)
case PubKeyAlgoMlkem768X25519, PubKeyAlgoMlkem1024X448, PubKeyAlgoMlkem768P256, PubKeyAlgoMlkem1024P384,
PubKeyAlgoMlkem768Brainpool256, PubKeyAlgoMlkem1024Brainpool384:
encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + int(e.encryptedMPI3.EncodedLength())
encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + int(e.encryptedMPI3.EncodedLength()) + 1
if e.Version < 6 {
encodedLength += 1
}
Expand Down Expand Up @@ -384,19 +354,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error {
return err
case PubKeyAlgoMlkem768X25519, PubKeyAlgoMlkem1024X448, PubKeyAlgoMlkem768P256, PubKeyAlgoMlkem1024P384,
PubKeyAlgoMlkem768Brainpool256, PubKeyAlgoMlkem1024Brainpool384:
if _, err := w.Write(e.encryptedMPI1.EncodedBytes()); err != nil {
return err
}
if _, err := w.Write(e.encryptedMPI2.EncodedBytes()); err != nil {
return err
}
if e.Version < 6 {
if _, err = w.Write([]byte{byte(e.CipherFunc)}); err != nil {
return err
}
}

_, err := w.Write(e.encryptedMPI3.EncodedBytes())
err := mlkem_ecdh.EncodeFields(w, e.encryptedMPI1.EncodedBytes(), e.encryptedMPI2.EncodedBytes(), e.encryptedMPI3.EncodedBytes(), byte(e.CipherFunc), e.Version == 6)
return err
default:
panic("internal error")
Expand Down Expand Up @@ -670,17 +628,17 @@ func encodeChecksumKey(buffer []byte, key []byte) {
}

func serializeEncryptedKeyMlkem(w io.Writer, rand io.Reader, header []byte, pub *mlkem_ecdh.PublicKey, keyBlock []byte, cipherFunc byte, version int) error {
kE, ecE, c, err := mlkem_ecdh.Encrypt(rand, pub, keyBlock)
mlE, ecE, c, err := mlkem_ecdh.Encrypt(rand, pub, keyBlock)
if err != nil {
return errors.InvalidArgumentError("ML-KEM + ECDH encryption failed: " + err.Error())
}

k := encoding.NewOctetArray(kE)
ml := encoding.NewOctetArray(mlE)
ec := encoding.NewOctetArray(ecE)
m := encoding.NewOID(c)
m := encoding.NewOctetArray(c)

packetLen := len(header) /* header length */
packetLen += int(ec.EncodedLength()) + int(k.EncodedLength()) + int(m.EncodedLength())
packetLen += int(ec.EncodedLength()) + int(ml.EncodedLength()) + int(m.EncodedLength()) + 1
if version < 6 {
packetLen += 1
}
Expand All @@ -694,17 +652,6 @@ func serializeEncryptedKeyMlkem(w io.Writer, rand io.Reader, header []byte, pub
if err != nil {
return err
}
if _, err = w.Write(ec.EncodedBytes()); err != nil {
return err
}
if _, err = w.Write(k.EncodedBytes()); err != nil {
return err
}
if version < 6 {
if _, err = w.Write([]byte{cipherFunc}); err != nil {
return err
}
}
_, err = w.Write(m.EncodedBytes())
return err

return mlkem_ecdh.EncodeFields(w, ec.EncodedBytes(), ml.EncodedBytes(), m.EncodedBytes(), cipherFunc, version == 6)
}
10 changes: 5 additions & 5 deletions openpgp/packet/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,21 +511,21 @@ const (
PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3

// Experimental PQC KEM algorithms
PubKeyAlgoMlkem768X25519 = 29
PubKeyAlgoMlkem1024X448 = 30
PubKeyAlgoMlkem768X25519 = 105
PubKeyAlgoMlkem1024X448 = 106
PubKeyAlgoMlkem768P256 = 31
PubKeyAlgoMlkem1024P384 = 32
PubKeyAlgoMlkem768Brainpool256 = 33
PubKeyAlgoMlkem1024Brainpool384 = 34

// Experimental PQC DSA algorithms
PubKeyAlgoMldsa65Ed25519 = 35
PubKeyAlgoMldsa87Ed448 = 36
PubKeyAlgoMldsa65Ed25519 = 107
PubKeyAlgoMldsa87Ed448 = 108
PubKeyAlgoMldsa65p256 = 37
PubKeyAlgoMldsa87p384 = 38
PubKeyAlgoMldsa65Brainpool256 = 39
PubKeyAlgoMldsa87Brainpool384 = 40
PubKeyAlgoSlhdsaSha2 = 41
PubKeyAlgoSlhdsaSha2 = 109
PubKeyAlgoSlhdsaShake = 42
)

Expand Down
17 changes: 10 additions & 7 deletions openpgp/packet/private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/mldsa_ecdsa"
"github.com/ProtonMail/go-crypto/openpgp/mldsa_eddsa"
"github.com/ProtonMail/go-crypto/openpgp/slhdsa"
"github.com/cloudflare/circl/kem/mlkem/mlkem1024"
"github.com/cloudflare/circl/kem/mlkem/mlkem768"
"github.com/cloudflare/circl/sign/dilithium"
"io"
"math/big"
"strconv"
Expand Down Expand Up @@ -909,19 +912,19 @@ func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
case PubKeyAlgoEd448:
return pk.parseEd448PrivateKey(data)
case PubKeyAlgoMlkem768X25519, PubKeyAlgoMlkem768P256, PubKeyAlgoMlkem768Brainpool256:
return pk.parseMlkemEcdhPrivateKey(data, 32, 2400)
return pk.parseMlkemEcdhPrivateKey(data, 32, mlkem768.PrivateKeySize)
case PubKeyAlgoMlkem1024X448:
return pk.parseMlkemEcdhPrivateKey(data, 56, 3168)
return pk.parseMlkemEcdhPrivateKey(data, 56, mlkem1024.PrivateKeySize)
case PubKeyAlgoMlkem1024P384, PubKeyAlgoMlkem1024Brainpool384:
return pk.parseMlkemEcdhPrivateKey(data, 48, 3168)
return pk.parseMlkemEcdhPrivateKey(data, 48, mlkem1024.PrivateKeySize)
case PubKeyAlgoMldsa65Ed25519:
return pk.parseMldsaEddsaPrivateKey(data, 32, 4000)
return pk.parseMldsaEddsaPrivateKey(data, 32, dilithium.MLDSA65.PrivateKeySize())
case PubKeyAlgoMldsa87Ed448:
return pk.parseMldsaEddsaPrivateKey(data, 57, 4864)
return pk.parseMldsaEddsaPrivateKey(data, 57, dilithium.MLDSA87.PrivateKeySize())
case PubKeyAlgoMldsa65p256, PubKeyAlgoMldsa65Brainpool256:
return pk.parseMldsaEcdsaPrivateKey(data, 32, 4000)
return pk.parseMldsaEcdsaPrivateKey(data, 32, dilithium.MLDSA65.PrivateKeySize())
case PubKeyAlgoMldsa87p384, PubKeyAlgoMldsa87Brainpool384:
return pk.parseMldsaEcdsaPrivateKey(data, 48, 4864)
return pk.parseMldsaEcdsaPrivateKey(data, 48, dilithium.MLDSA87.PrivateKeySize())
case PubKeyAlgoSlhdsaSha2, PubKeyAlgoSlhdsaShake:
return pk.parseSlhdsaPrivateKey(data)
default:
Expand Down
Loading

0 comments on commit c5ad8fd

Please sign in to comment.