diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 6e28cb36..c308de7f 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -407,9 +407,6 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { fallthrough // When passing Dilithium + EdDSA or ECDSA, we generate a Kyber + ECDH subkey case packet.PubKeyAlgoKyber768X25519, packet.PubKeyAlgoKyber1024X448, packet.PubKeyAlgoKyber768P256, packet.PubKeyAlgoKyber1024P384, packet.PubKeyAlgoKyber768Brainpool256, packet.PubKeyAlgoKyber1024Brainpool384: - if !config.V6() { - return nil, goerrors.New("openpgp: cannot create a non-v6 kyber_ecdh key") - } c, err := packet.GetECDHCurveFromAlgID(pubKeyAlgo) if err != nil { diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index b05eb470..e69fadac 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1848,3 +1848,90 @@ mQ00BF00000BCAD0000000000000000000000000000000000000000000000000 000000000000000000000000000000000000ABE000G0Dn000000000000000000iQ00BB0BAgAGBCG00000` ReadArmoredKeyRing(strings.NewReader(data)) } + +func TestAddV4KyberSubkey(t *testing.T) { + eddsaConfig := &packet.Config{ + DefaultHash: crypto.SHA512, + Algorithm: packet.PubKeyAlgoEdDSA, + V6Keys: false, + Time: func() time.Time { + parsed, _ := time.Parse("2006-01-02", "2013-07-01") + return parsed + }, + } + + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", eddsaConfig) + if err != nil { + t.Fatal(err) + } + + testAddKyberSubkey(t, entity, false) +} + + +func testAddKyberSubkey(t *testing.T, entity *Entity, v6Keys bool) { + var err error + + asymmAlgos := map[string] packet.PublicKeyAlgorithm{ + "Kyber768_X25519": packet.PubKeyAlgoKyber768X25519, + "Kyber1024_X448": packet.PubKeyAlgoKyber1024X448, + "Kyber768_P256": packet.PubKeyAlgoKyber768P256, + "Kyber1024_P384":packet.PubKeyAlgoKyber1024P384, + "Kyber768_Brainpool256": packet.PubKeyAlgoKyber768Brainpool256, + "Kyber1024_Brainpool384":packet.PubKeyAlgoKyber1024Brainpool384, + } + + for name, algo := range asymmAlgos { + // Remove existing subkeys + entity.Subkeys = []Subkey{} + + t.Run(name, func(t *testing.T) { + kyberConfig := &packet.Config{ + 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 + }, + } + + err = entity.AddEncryptionSubkey(kyberConfig) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys) != 1 { + t.Fatalf("Expected 1 subkey, got %d", len(entity.Subkeys)) + } + + if entity.Subkeys[0].PublicKey.PubKeyAlgo != algo { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, + entity.Subkeys[0].PublicKey.PubKeyAlgo) + } + + serializedEntity := bytes.NewBuffer(nil) + err = entity.SerializePrivate(serializedEntity, nil) + if err != nil { + t.Fatalf("Failed to serialize entity: %s", err) + } + + read, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } + + if len(read.Subkeys) != 1 { + t.Fatalf("Expected 1 subkey, got %d", len(entity.Subkeys)) + } + + if read.Subkeys[0].PublicKey.PubKeyAlgo != algo { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, + entity.Subkeys[0].PublicKey.PubKeyAlgo) + } + }) + } +} \ No newline at end of file diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index 420e8785..ac1cc4c8 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -328,11 +328,11 @@ func testKyberSubkey(t *testing.T, subkey Subkey, randomPassword []byte) { } -func TestAddKyberSubkey(t *testing.T) { +func TestAddV6KyberSubkey(t *testing.T) { eddsaConfig := &packet.Config{ DefaultHash: crypto.SHA512, - Algorithm: packet.PubKeyAlgoEdDSA, - V6Keys: true, + Algorithm: packet.PubKeyAlgoEd25519, + V6Keys: true, Time: func() time.Time { parsed, _ := time.Parse("2006-01-02", "2013-07-01") return parsed @@ -344,66 +344,5 @@ func TestAddKyberSubkey(t *testing.T) { t.Fatal(err) } - asymmAlgos := map[string] packet.PublicKeyAlgorithm{ - "Kyber768_X25519": packet.PubKeyAlgoKyber768X25519, - "Kyber1024_X448": packet.PubKeyAlgoKyber1024X448, - "Kyber768_P256": packet.PubKeyAlgoKyber768P256, - "Kyber1024_P384":packet.PubKeyAlgoKyber1024P384, - "Kyber768_Brainpool256": packet.PubKeyAlgoKyber768Brainpool256, - "Kyber1024_Brainpool384":packet.PubKeyAlgoKyber1024Brainpool384, - } - - for name, algo := range asymmAlgos { - // Remove existing subkeys - entity.Subkeys = []Subkey{} - - t.Run(name, func(t *testing.T) { - kyberConfig := &packet.Config{ - DefaultHash: crypto.SHA512, - Algorithm: algo, - V6Keys: true, - AEADConfig: &packet.AEADConfig{ - DefaultMode: packet.AEADModeOCB, - }, - Time: func() time.Time { - parsed, _ := time.Parse("2006-01-02", "2013-07-01") - return parsed - }, - } - - err = entity.AddEncryptionSubkey(kyberConfig) - if err != nil { - t.Fatal(err) - } - - if len(entity.Subkeys) != 1 { - t.Fatalf("Expected 1 subkey, got %d", len(entity.Subkeys)) - } - - if entity.Subkeys[0].PublicKey.PubKeyAlgo != algo { - t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, - entity.Subkeys[0].PublicKey.PubKeyAlgo) - } - - serializedEntity := bytes.NewBuffer(nil) - err = entity.SerializePrivate(serializedEntity, nil) - if err != nil { - t.Fatalf("Failed to serialize entity: %s", err) - } - - read, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) - if err != nil { - t.Fatal(err) - } - - if len(read.Subkeys) != 1 { - t.Fatalf("Expected 1 subkey, got %d", len(entity.Subkeys)) - } - - if read.Subkeys[0].PublicKey.PubKeyAlgo != algo { - t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, - entity.Subkeys[0].PublicKey.PubKeyAlgo) - } - }) - } + testAddKyberSubkey(t, entity, true) } diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index a6870150..8a9751b5 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -139,27 +139,27 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { return } case PubKeyAlgoKyber768X25519: - if err = e.readKyberECDHKey(r, 32, 1088); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 32, 1088, e.Version == 6); err != nil { return err } case PubKeyAlgoKyber1024X448: - if err = e.readKyberECDHKey(r, 56, 1568); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 56, 1568, e.Version == 6); err != nil { return err } case PubKeyAlgoKyber768P256: - if err = e.readKyberECDHKey(r, 65, 1088); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 65, 1088, e.Version == 6); err != nil { return err } case PubKeyAlgoKyber1024P384: - if err = e.readKyberECDHKey(r, 97, 1568); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 97, 1568, e.Version == 6); err != nil { return err } case PubKeyAlgoKyber768Brainpool256: - if err = e.readKyberECDHKey(r, 65, 1088); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 65, 1088, e.Version == 6); err != nil { return err } case PubKeyAlgoKyber1024Brainpool384: - if err = e.readKyberECDHKey(r, 97, 1568); err != nil { + if cipherFunction, err = e.readKyberECDHKey(r, 97, 1568, e.Version == 6); err != nil { return err } } @@ -167,7 +167,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { switch e.Algo { case PubKeyAlgoX25519, PubKeyAlgoX448: e.CipherFunc = CipherFunction(cipherFunction) - // Check for validiy is in the Decrypt method + // Check for validity is in the Decrypt method } } @@ -177,11 +177,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { // readKyberECDHKey reads Kyber + ECC PKESK as specified in // https://www.ietf.org/archive/id/draft-wussler-openpgp-pqc-00.html#section-4.3.1 -func (e *EncryptedKey) readKyberECDHKey(r io.Reader, lenEcc, lenKyber int) (err error) { - if e.Version != 6 { - return errors.StructuralError("reading non-v6 kyber PKESK") - } - +func (e *EncryptedKey) readKyberECDHKey(r io.Reader, lenEcc, lenKyber int, v6 bool) (cipherFunction byte, err error) { e.encryptedMPI1 = encoding.NewEmptyOctetArray(lenEcc) if _, err = e.encryptedMPI1.ReadFrom(r); err != nil { return @@ -197,6 +193,15 @@ func (e *EncryptedKey) readKyberECDHKey(r io.Reader, lenEcc, lenKyber int) (err return } + if !v6 { + var buf [1]byte + _, err = io.ReadFull(r, buf[:]) + if err != nil { + return + } + cipherFunction = buf[0] + } + return } @@ -271,19 +276,17 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { } } key, err = decodeChecksumKey(b[keyOffset:]) - case PubKeyAlgoX25519, PubKeyAlgoX448: + case PubKeyAlgoX25519, PubKeyAlgoX448, PubKeyAlgoKyber768X25519, PubKeyAlgoKyber1024X448, PubKeyAlgoKyber768P256, + PubKeyAlgoKyber1024P384, PubKeyAlgoKyber768Brainpool256, PubKeyAlgoKyber1024Brainpool384: if e.Version < 6 { switch e.CipherFunc { case CipherAES128, CipherAES192, CipherAES256: break default: - return errors.StructuralError("v3 PKESK mandates AES as cipher function for x25519 and x448") + return errors.StructuralError("v3 PKESK mandates AES as cipher function for x25519, x448, and PQC") } } key = b[:] - case PubKeyAlgoKyber768X25519, PubKeyAlgoKyber1024X448, PubKeyAlgoKyber768P256, PubKeyAlgoKyber1024P384, - PubKeyAlgoKyber768Brainpool256, PubKeyAlgoKyber1024Brainpool384: - key = b[:] } if err != nil { return err @@ -309,6 +312,9 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { case PubKeyAlgoKyber768X25519, PubKeyAlgoKyber1024X448, PubKeyAlgoKyber768P256, PubKeyAlgoKyber1024P384, PubKeyAlgoKyber768Brainpool256, PubKeyAlgoKyber1024Brainpool384: encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + int(e.encryptedMPI3.EncodedLength()) + if e.Version < 6 { + encodedLength += 1 + } default: return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } @@ -387,7 +393,12 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { if _, err := w.Write(e.encryptedMPI2.EncodedBytes()); err != nil { return err } - _, err := w.Write(e.encryptedMPI3.EncodedBytes()) + if _, err := w.Write(e.encryptedMPI3.EncodedBytes()); err != nil { + return err + } + if e.Version < 6 { + _, err = w.Write([]byte{byte(e.CipherFunc)}) + } return err default: panic("internal error") diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 4256d825..634e2bb7 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -1252,9 +1252,6 @@ func (pk *PrivateKey) parseDilithiumEdDSAPrivateKey(data []byte, ecLen, dLen int // parseKyberECDHPrivateKey parses a Kyber + ECC private key as specified in // https://www.ietf.org/archive/id/draft-wussler-openpgp-pqc-00.html#section-4.3.2 func (pk *PrivateKey) parseKyberECDHPrivateKey(data []byte, ecLen, kLen int) (err error) { - if pk.Version != 6 { - return goerrors.New("openpgp: cannot parse non-v6 kyber_ecdh key") - } pub := pk.PublicKey.PublicKey.(*kyber_ecdh.PublicKey) priv := new(kyber_ecdh.PrivateKey) priv.PublicKey = *pub