Skip to content

Commit

Permalink
Revert "Update to draft-ietf-openpgp-persistent-symmetric-keys-00"
Browse files Browse the repository at this point in the history
This reverts commit 99debaa.
  • Loading branch information
twiss committed Nov 14, 2024
1 parent 8c808a9 commit eebabee
Show file tree
Hide file tree
Showing 19 changed files with 334 additions and 186 deletions.
5 changes: 0 additions & 5 deletions openpgp/internal/algorithm/aead.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import (
// operation.
type AEADMode uint8

// Id returns the algorithm ID, as a byte, of mode.
func (mode AEADMode) Id() uint8 {
return uint8(mode)
}

// Supported modes of operation (see RFC4880bis [EAX] and RFC7253)
const (
AEADModeEAX = AEADMode(1)
Expand Down
2 changes: 1 addition & 1 deletion openpgp/internal/algorithm/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ var CipherById = map[uint8]Cipher{

type CipherFunction uint8

// Id returns the algorithm ID, as a byte, of cipher.
// ID returns the algorithm Id, as a byte, of cipher.
func (sk CipherFunction) Id() uint8 {
return uint8(sk)
}
Expand Down
50 changes: 50 additions & 0 deletions openpgp/internal/encoding/short_byte_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package encoding

import (
"io"
)

type ShortByteString struct {
length uint8
data []byte
}

func NewShortByteString(data []byte) *ShortByteString {
byteLength := uint8(len(data))

return &ShortByteString{byteLength, data}
}

func (byteString *ShortByteString) Bytes() []byte {
return byteString.data
}

func (byteString *ShortByteString) BitLength() uint16 {
return uint16(byteString.length) * 8
}

func (byteString *ShortByteString) EncodedBytes() []byte {
encodedLength := [1]byte{
uint8(byteString.length),
}
return append(encodedLength[:], byteString.data...)
}

func (byteString *ShortByteString) EncodedLength() uint16 {
return uint16(byteString.length) + 1
}

func (byteString *ShortByteString) ReadFrom(r io.Reader) (int64, error) {
var lengthBytes [1]byte
if n, err := io.ReadFull(r, lengthBytes[:]); err != nil {
return int64(n), err
}

byteString.length = uint8(lengthBytes[0])

byteString.data = make([]byte, byteString.length)
if n, err := io.ReadFull(r, byteString.data); err != nil {
return int64(n + 1), err
}
return int64(byteString.length + 1), nil
}
61 changes: 61 additions & 0 deletions openpgp/internal/encoding/short_byte_string_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package encoding

import (
"bytes"
"testing"
)

var octetStreamTests = []struct {
data []byte
}{
{
data: []byte{0x0, 0x0, 0x0},
},
{
data: []byte{0x1, 0x2, 0x03},
},
{
data: make([]byte, 255),
},
}

func TestShortByteString(t *testing.T) {
for i, test := range octetStreamTests {
octetStream := NewShortByteString(test.data)

if b := octetStream.Bytes(); !bytes.Equal(b, test.data) {
t.Errorf("#%d: bad creation got:%x want:%x", i, b, test.data)
}

expectedBitLength := uint16(len(test.data)) * 8
if bitLength := octetStream.BitLength(); bitLength != expectedBitLength {
t.Errorf("#%d: bad bit length got:%d want :%d", i, bitLength, expectedBitLength)
}

expectedEncodedLength := uint16(len(test.data)) + 1
if encodedLength := octetStream.EncodedLength(); encodedLength != expectedEncodedLength {
t.Errorf("#%d: bad encoded length got:%d want:%d", i, encodedLength, expectedEncodedLength)
}

encodedBytes := octetStream.EncodedBytes()
if !bytes.Equal(encodedBytes[1:], test.data) {
t.Errorf("#%d: bad encoded bytes got:%x want:%x", i, encodedBytes[1:], test.data)
}

encodedLength := int(encodedBytes[0])
if encodedLength != len(test.data) {
t.Errorf("#%d: bad encoded length got:%d want%d", i, encodedLength, len(test.data))
}

newStream := new(ShortByteString)
newStream.ReadFrom(bytes.NewReader(encodedBytes))

if !checkEquality(newStream, octetStream) {
t.Errorf("#%d: bad parsing of encoded octet stream", i)
}
}
}

func checkEquality(left *ShortByteString, right *ShortByteString) bool {
return (left.length == right.length) && (bytes.Equal(left.data, right.data))
}
5 changes: 2 additions & 3 deletions openpgp/key_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,9 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) {
return x25519.GenerateKey(config.Random())
case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey
return x448.GenerateKey(config.Random())
case packet.ExperimentalPubKeyAlgoHMAC, packet.ExperimentalPubKeyAlgoAEAD: // When passing HMAC, we generate an AEAD subkey
case packet.ExperimentalPubKeyAlgoAEAD:
cipher := algorithm.CipherFunction(config.Cipher())
aead := algorithm.AEADMode(config.AEAD().Mode())
return symmetric.AEADGenerateKey(config.Random(), cipher, aead)
return symmetric.AEADGenerateKey(config.Random(), cipher)
case packet.PubKeyAlgoMldsa65Ed25519, packet.PubKeyAlgoMldsa87Ed448:
if pubKeyAlgo, err = packet.GetMatchingMlkem(config.PublicKeyAlgorithm()); err != nil {
return nil, err
Expand Down
47 changes: 24 additions & 23 deletions openpgp/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1214,13 +1214,16 @@ func TestAddHMACSubkey(t *testing.T) {
t.Error("generated Public and Private Key differ")
}

if !bytes.Equal(parsedPublicKey.FpSeed[:], generatedPublicKey.FpSeed[:]) {
if !bytes.Equal(parsedPrivateKey.HashSeed[:], generatedPrivateKey.HashSeed[:]) {
t.Error("parsed wrong hash seed")
}

if parsedPrivateKey.PublicKey.Hash != generatedPrivateKey.PublicKey.Hash {
t.Error("parsed wrong cipher id")
}
if !bytes.Equal(parsedPrivateKey.PublicKey.BindingHash[:], generatedPrivateKey.PublicKey.BindingHash[:]) {
t.Error("parsed wrong binding hash")
}
}

func TestSerializeSymmetricSubkeyError(t *testing.T) {
Expand All @@ -1232,13 +1235,13 @@ func TestSerializeSymmetricSubkeyError(t *testing.T) {
buf := bytes.NewBuffer(nil)
w, _ := armor.Encode(buf, "PGP PRIVATE KEY BLOCK", nil)

entity.PrimaryKey.PubKeyAlgo = 128
entity.PrimaryKey.PubKeyAlgo = 100
err = entity.Serialize(w)
if err == nil {
t.Fatal(err)
}

entity.PrimaryKey.PubKeyAlgo = 129
entity.PrimaryKey.PubKeyAlgo = 101
err = entity.Serialize(w)
if err == nil {
t.Fatal(err)
Expand Down Expand Up @@ -1289,15 +1292,15 @@ func TestAddAEADSubkey(t *testing.T) {
t.Error("generated Public and Private Key differ")
}

if !bytes.Equal(parsedPublicKey.FpSeed[:], generatedPublicKey.FpSeed[:]) {
if !bytes.Equal(parsedPrivateKey.HashSeed[:], generatedPrivateKey.HashSeed[:]) {
t.Error("parsed wrong hash seed")
}

if parsedPrivateKey.PublicKey.Cipher.Id() != generatedPrivateKey.PublicKey.Cipher.Id() {
t.Error("parsed wrong cipher id")
}
if parsedPrivateKey.PublicKey.AEADMode.Id() != generatedPrivateKey.PublicKey.AEADMode.Id() {
t.Error("parsed wrong aead mode")
if !bytes.Equal(parsedPrivateKey.PublicKey.BindingHash[:], generatedPrivateKey.PublicKey.BindingHash[:]) {
t.Error("parsed wrong binding hash")
}
}

Expand Down Expand Up @@ -1341,11 +1344,11 @@ func TestNoSymmetricKeySerialized(t *testing.T) {
t.Error("Private key was serialized with public")
}

firstFpSeed := entity.Subkeys[1].PublicKey.PublicKey.(*symmetric.AEADPublicKey).FpSeed
i = bytes.Index(w.Bytes(), firstFpSeed[:])
firstBindingHash := entity.Subkeys[1].PublicKey.PublicKey.(*symmetric.AEADPublicKey).BindingHash
i = bytes.Index(w.Bytes(), firstBindingHash[:])

secondFpSeed := entity.Subkeys[2].PublicKey.PublicKey.(*symmetric.HMACPublicKey).FpSeed
k = bytes.Index(w.Bytes(), secondFpSeed[:])
secondBindingHash := entity.Subkeys[2].PublicKey.PublicKey.(*symmetric.HMACPublicKey).BindingHash
k = bytes.Index(w.Bytes(), secondBindingHash[:])
if (i > 0) || (k > 0) {
t.Errorf("Symmetric public key metadata exported %d %d", i, k)
}
Expand Down Expand Up @@ -2052,19 +2055,17 @@ mQ00BF00000BCAD0000000000000000000000000000000000000000000000000
func TestSymmetricKeys(t *testing.T) {
data := `-----BEGIN PGP PRIVATE KEY BLOCK-----
xUoEZyoQrIEImuGs5gaOTekO00WQx6MDnyBPvxmpMiOgeVse7+aqarsAc8F5
NFm3pVkFDZxX0MqRCPqCwsa/BXJGlrEdMAwSNckOV80xUGVyc2lzdGVudCBT
eW1tZXRyaWMgS2V5IDxwZXJzaXN0ZW50QGV4YW1wbGUub3JnPsKvBBOBCgCF
BYJnKhCsAwsJBwmQDqlD7wlMH9dFFAAAAAAAHAAgc2FsdEBub3RhdGlvbnMu
b3BlbnBncGpzLm9yZ4pMjYSZvCHJsWo5/hQJ3qfDMVMnetCsdS4ZSR6oeO7l
BRUKCAwOBBYAAgECGQECmwMCHgEWIQSbMhUPoVGIuE9u9GAOqUPvCUwf1wAA
QXxcTdhWEMhv+uYj8lUjGbDiqMHc7oGQSattlK89H9KT18dLBGcqEKyACQPs
AUFGawprheOyMQEYmVQUCoTdw4SVAxPk3Wkdbd7YtQATgtwB+JTCDy4de8F+
yKpsXCJEFrVCsVnFyyY3gH5Wgw5PwpoEGIEKAHAFgmcqEKwJkA6pQ+8JTB/X
RRQAAAAAABwAIHNhbHRAbm90YXRpb25zLm9wZW5wZ3Bqcy5vcmdwNnP67WFb
3vwFQkTQHsuFKLqvtvpQdnDs9RmvPxLZUwKbDBYhBJsyFQ+hUYi4T270YA6p
Q+8JTB/XAAC0o7OPSjaqMfpfYDUewr7Ehi5kFRCDBwbxLWFryAiICULT
=ywfD
xWoEYs7w5mUIcFvlmkuricX26x138uvHGlwIaxWIbRnx1+ggPcveTcwA4zSZ
n6XcD0Q5aLe6dTEBwCyfUecZ/nA0W8Pl9xBHfjIjQuxcUBnIqxZ061RZPjef
D/XIQga1ftLDelhylQwL7R3TzQ1TeW1tZXRyaWMgS2V5wmkEEGUIAB0FAmLO
8OYECwkHCAMVCAoEFgACAQIZAQIbAwIeAQAhCRCRTKq2ObiQKxYhBMHTTXXF
ULQ2M2bYNJFMqrY5uJArIawgJ+5RSsN8VNuZTKJbG88TIedU05wwKjW3wqvT
X6Z7yfbHagRizvDmZAluL/kJo6hZ1kFENpQkWD/Kfv1vAG3nbxhsVEzBQ6a1
OAD24BaKJz6gWgj4lASUNK5OuXnLc3J79Bt1iRGkSbiPzRs/bplB4TwbILeC
ZLeDy9kngZDosgsIk5sBgGEqS9y5HiHCVQQYZQgACQUCYs7w5gIbDAAhCRCR
TKq2ObiQKxYhBMHTTXXFULQ2M2bYNJFMqrY5uJArENkgL0Bc+OI/1na0XWqB
TxGVotQ4A/0u0VbOMEUfnrI8Fms=
=RdCW
-----END PGP PRIVATE KEY BLOCK-----
`
keys, err := ReadArmoredKeyRing(strings.NewReader(data))
Expand Down
48 changes: 30 additions & 18 deletions openpgp/packet/encrypted_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
"github.com/ProtonMail/go-crypto/openpgp/elgamal"
"github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
"github.com/ProtonMail/go-crypto/openpgp/mlkem_ecdh"
"github.com/ProtonMail/go-crypto/openpgp/symmetric"
Expand All @@ -35,12 +36,15 @@ type EncryptedKey struct {
CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet
Key []byte // only valid after a successful Decrypt

encryptedMPI1 encoding.Field // Only valid in RSA, Elgamal, ECDH, AEAD and PQC keys
encryptedMPI1 encoding.Field // Only valid in RSA, Elgamal, ECDH, and PQC keys
encryptedMPI2 encoding.Field // Only valid in Elgamal, ECDH and PQC keys
encryptedMPI3 encoding.Field // Only valid in PQC keys
ephemeralPublicX25519 *x25519.PublicKey // used for x25519
ephemeralPublicX448 *x448.PublicKey // used for x448
encryptedSession []byte // used for x25519 and x448

nonce []byte
aeadMode algorithm.AEADMode
}

func (e *EncryptedKey) parse(r io.Reader) (err error) {
Expand Down Expand Up @@ -138,11 +142,20 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) {
return
}
case ExperimentalPubKeyAlgoAEAD:
ivAndCiphertext, err := io.ReadAll(r)
if err != nil {
return err
var aeadMode [1]byte
if _, err = readFull(r, aeadMode[:]); err != nil {
return
}
e.aeadMode = algorithm.AEADMode(aeadMode[0])
nonceLength := e.aeadMode.NonceLength()
e.nonce = make([]byte, nonceLength)
if _, err = readFull(r, e.nonce); err != nil {
return
}
e.encryptedMPI1 = new(encoding.ShortByteString)
if _, err = e.encryptedMPI1.ReadFrom(r); err != nil {
return
}
e.encryptedMPI1 = encoding.NewOctetArray(ivAndCiphertext)
case PubKeyAlgoMlkem768X25519:
if e.encryptedMPI1, e.encryptedMPI2, e.encryptedMPI3, cipherFunction, err = mlkem_ecdh.DecodeFields(r, 32, 1088, e.Version == 6); err != nil {
return err
Expand Down Expand Up @@ -211,7 +224,7 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
b, err = x448.Decrypt(priv.PrivateKey.(*x448.PrivateKey), e.ephemeralPublicX448, e.encryptedSession)
case ExperimentalPubKeyAlgoAEAD:
priv := priv.PrivateKey.(*symmetric.AEADPrivateKey)
b, err = priv.Decrypt(e.encryptedMPI1.Bytes(), priv.PublicKey.AEADMode)
b, err = priv.Decrypt(e.nonce, e.encryptedMPI1.Bytes(), e.aeadMode)
case PubKeyAlgoMlkem768X25519, PubKeyAlgoMlkem1024X448:
ecE := e.encryptedMPI1.Bytes()
kE := e.encryptedMPI2.Bytes()
Expand Down Expand Up @@ -453,7 +466,7 @@ func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, ciph
case PubKeyAlgoX448:
return serializeEncryptedKeyX448(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x448.PublicKey), keyBlock, byte(cipherFunc), version)
case ExperimentalPubKeyAlgoAEAD:
return serializeEncryptedKeyAEAD(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*symmetric.AEADPublicKey), keyBlock)
return serializeEncryptedKeyAEAD(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*symmetric.AEADPublicKey), keyBlock, config.AEAD())
case PubKeyAlgoMlkem768X25519, PubKeyAlgoMlkem1024X448:
return serializeEncryptedKeyMlkem(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*mlkem_ecdh.PublicKey), keyBlock, byte(cipherFunc), version)
case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly, ExperimentalPubKeyAlgoHMAC:
Expand Down Expand Up @@ -627,16 +640,20 @@ func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *
return x448.EncodeFields(w, ephemeralPublicX448, ciphertext, cipherFunc, version == 6)
}

func serializeEncryptedKeyAEAD(w io.Writer, rand io.Reader, header []byte, pub *symmetric.AEADPublicKey, keyBlock []byte) error {
mode := pub.AEADMode
iv, ciphertext, err := pub.Encrypt(rand, keyBlock, mode)
func serializeEncryptedKeyAEAD(w io.Writer, rand io.Reader, header []byte, pub *symmetric.AEADPublicKey, keyBlock []byte, config *AEADConfig) error {
mode := algorithm.AEADMode(config.Mode())
iv, ciphertextRaw, err := pub.Encrypt(rand, keyBlock, mode)
if err != nil {
return errors.InvalidArgumentError("AEAD encryption failed: " + err.Error())
}

ciphertextShortByteString := encoding.NewShortByteString(ciphertextRaw)

buffer := append([]byte{byte(mode)}, iv...)
buffer = append(buffer, ciphertextShortByteString.EncodedBytes()...)

packetLen := len(header) /* header length */
packetLen += int(len(iv))
packetLen += int(len(ciphertext))
packetLen += int(len(buffer))

err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
if err != nil {
Expand All @@ -648,12 +665,7 @@ func serializeEncryptedKeyAEAD(w io.Writer, rand io.Reader, header []byte, pub *
return err
}

_, err = w.Write(iv[:])
if err != nil {
return err
}

_, err = w.Write(ciphertext)
_, err = w.Write(buffer)
return err
}

Expand Down
19 changes: 15 additions & 4 deletions openpgp/packet/encrypted_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"crypto"
"crypto/rsa"

"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
"github.com/ProtonMail/go-crypto/openpgp/x25519"
"github.com/ProtonMail/go-crypto/openpgp/x448"
)
Expand Down Expand Up @@ -340,9 +341,11 @@ func TestSerializingEncryptedKey(t *testing.T) {
}

func TestSymmetricallyEncryptedKey(t *testing.T) {
const encryptedKeyHex = "c13d03999bd17d726446da80df9db940896a0b0e48f4d3b26e2dfbcf59ca7d30b65ea95ebb072e643407c732c479093b9d180c2eb51c98814e1bbbc6d0a17f"
const encryptedKeyHex = "c14f03999bd17d726446da64018cb4d628ae753c646b81f87f21269cd733df9db940896a0b0e48f4d3b26e2dfbcf59ca7d30b65ea95ebb072e643407c732c479093b9d180c2eb51c98814e1bbbc6d0a17f"

expectedIvAndCiphertext := []byte{0xdf, 0x9d, 0xb9, 0x40, 0x89, 0x6a, 0x0b, 0x0e, 0x48, 0xf4, 0xd3, 0xb2, 0x6e, 0x2d, 0xfb, 0xcf, 0x59, 0xca, 0x7d, 0x30, 0xb6, 0x5e, 0xa9, 0x5e, 0xbb, 0x07, 0x2e, 0x64, 0x34, 0x07, 0xc7, 0x32, 0xc4, 0x79, 0x09, 0x3b, 0x9d, 0x18, 0x0c, 0x2e, 0xb5, 0x1c, 0x98, 0x81, 0x4e, 0x1b, 0xbb, 0xc6, 0xd0, 0xa1, 0x7f}
expectedNonce := []byte{0x8c, 0xb4, 0xd6, 0x28, 0xae, 0x75, 0x3c, 0x64, 0x6b, 0x81, 0xf8, 0x7f, 0x21, 0x26, 0x9c, 0xd7}

expectedCiphertext := []byte{0xdf, 0x9d, 0xb9, 0x40, 0x89, 0x6a, 0x0b, 0x0e, 0x48, 0xf4, 0xd3, 0xb2, 0x6e, 0x2d, 0xfb, 0xcf, 0x59, 0xca, 0x7d, 0x30, 0xb6, 0x5e, 0xa9, 0x5e, 0xbb, 0x07, 0x2e, 0x64, 0x34, 0x07, 0xc7, 0x32, 0xc4, 0x79, 0x09, 0x3b, 0x9d, 0x18, 0x0c, 0x2e, 0xb5, 0x1c, 0x98, 0x81, 0x4e, 0x1b, 0xbb, 0xc6, 0xd0, 0xa1, 0x7f}

p, err := Read(readerFromHex(encryptedKeyHex))
if err != nil {
Expand All @@ -354,7 +357,15 @@ func TestSymmetricallyEncryptedKey(t *testing.T) {
t.Fatalf("didn't parse and EncryptedKey, got %#v", p)
}

if !bytes.Equal(expectedIvAndCiphertext, ek.encryptedMPI1.Bytes()) {
t.Errorf("Parsed wrong ciphertext, got %x, expected %x", ek.encryptedMPI1.Bytes(), expectedIvAndCiphertext)
if ek.aeadMode != algorithm.AEADModeEAX {
t.Errorf("Parsed wrong aead mode, got %d, expected: 1", ek.aeadMode)
}

if !bytes.Equal(expectedNonce, ek.nonce) {
t.Errorf("Parsed wrong nonce, got %x, expected %x", ek.nonce, expectedNonce)
}

if !bytes.Equal(expectedCiphertext, ek.encryptedMPI1.Bytes()) {
t.Errorf("Parsed wrong ciphertext, got %x, expected %x", ek.encryptedMPI1.Bytes(), expectedCiphertext)
}
}
Loading

0 comments on commit eebabee

Please sign in to comment.