From dc1905bbc92d5cb64acc4f0c096fed59033b02d8 Mon Sep 17 00:00:00 2001 From: Aron Wussler Date: Fri, 30 Aug 2019 16:51:02 +0200 Subject: [PATCH] Add Notation data support in subpackets --- openpgp/keys_test.go | 42 ++++++++++++++++++++++++++++++++++ openpgp/keys_test_data.go | 32 ++++++++++++++++++++++++++ openpgp/packet/notation.go | 45 +++++++++++++++++++++++++++++++++++++ openpgp/packet/signature.go | 31 +++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 openpgp/packet/notation.go diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index c1c8e825e..89e3b8cbc 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -944,6 +944,48 @@ func TestNewEntityPrivateSerialization(t *testing.T) { } } +func TestNotationPacket(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithNotation)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Errorf("Failed to accept key, %d", len(keys)) + } + + identity := keys[0].Identities["Testt "] + + if numSigs, numExpected := len(identity.Signatures), 1; numSigs != numExpected { + t.Fatalf("got %d signatures, expected %d", numSigs, numExpected) + } + + notations := identity.Signatures[0].Notations + if numSigs, numExpected := len(notations), 2; numSigs != numExpected { + t.Fatalf("got %d Data Notation subpackets, expected %d", numSigs, numExpected) + } + + if notations[0].IsHumanReadable() != true { + t.Fatalf("got false, expected true") + } + + if notations[0].GetName() != "test@example.com" { + t.Fatalf("got %s, expected test@example.com", notations[0].GetName()) + } + + if notations[0].GetStringValue() != "2" { + t.Fatalf("got %s, expected 2", notations[0].GetName()) + } + + if notations[1].GetName() != "test@example.com" { + t.Fatalf("got %s, expected test@example.com", notations[0].GetName()) + } + + if notations[1].GetStringValue() != "3" { + t.Fatalf("got %s, expected 3", notations[0].GetName()) + } +} + func TestEntityPrivateSerialization(t *testing.T) { keys, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock)) if err != nil { diff --git a/openpgp/keys_test_data.go b/openpgp/keys_test_data.go index 4bcfd5fdf..4b07446cf 100644 --- a/openpgp/keys_test_data.go +++ b/openpgp/keys_test_data.go @@ -518,3 +518,35 @@ XLCBln+wdewpU4ChEffMUDRBfqfQco/YsMqWV7bHJHAO0eC/DMKCjyU90xdH7R/d QgqsfguR1PqPuJxpXV4bSr6CGAAAAA== =MSvh -----END PGP PRIVATE KEY BLOCK-----` + +const keyWithNotation = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFzQOToBCADd0Pwh8edZ6gR3x49L1PaBPtiAQUr1QDUDWeNes8co5MTFl5hG +lHzptt+VD0JGucuIkvi34f5z2ZbInAV/xYDX3kSYefy6LB8XJD527I/o9bqY1P7T +PjtTZ4emcqNGkGhV2hNGV+hFcTevUS9Ty4vGg6P7X6RjfjeTrClHelJT8+9IiH+4 +0h4X/Y1hwoijRWanYnZjuAUIrOXnG76iknXQRGc8th8iI0oIZfKQomfF0K5lXFhH +SU8Yvmik3vCTLHC6Ce0GVRCTIcU0/Xi2MK/Yrg9bGzSblHxomLU0NT6pee+2UjqR +BZXOAPLY66Lsh1oqxQ6ihVnOmbraU9glAGm1ABEBAAG0EFRlc3R0IDx0ZXN0QGV4 +YT6JAYoEEwEIAHQCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQQZ +jHIqS6wzbp2qrkRXnQGzq+FUDgUCXNA5VBoUgAAAAAAQAAF0ZXN0QGV4YW1wbGUu +Y29tMhoUgAAAAAAQAAF0ZXN0QGV4YW1wbGUuY29tMwAKCRBXnQGzq+FUDoLiCACn +ls1iy0hT59Xt3o3tmmxe1jLzkbQEprR6MMfZamtex5/BHViu2HPAu5i13mXyBRnJ +4Zvd/HUxJukP3tdQyJIlZFe8XwloMoRAA37KOZ5QGyKH8Jxq3LcAcQOOkFtWgr+Z +JbjUKF1IuqCsK6SYB8f7SVKgpZk/kqG3HE3gk72ONnqdvwOa9cIhAuZScdgZ+PLC +6W/0+IrnQIasvKeEWeK4u6/NYT35HUsUE/9Z6WKF+qxJnp5Pi2Q5cio6bFlGDNQb ++MiuiEb3Mzb3ev2PVg7WELBRXOg8QlCxrghqfi1SH791mmiyGK+GIQgnjRwMejTh +dNsnHYag/KAewag55AQvuQENBFzQOToBCADJD+auK+Opo1q3ZLxODMyw5//XHJH4 +0vQPNawyBiOdBuneWHF3jfDwGa+lOftUx1abSwsq+Qs955THgLVSiJvivHWVy8pN +tPv0XLa9rMj2wh/OmckbcgzSMeJJIz09bTj095ONPGYW2D4AcpkOc+b5bkqV6r+N +yk9nopPJNCNqYYJtecTClDT5haRKBP5XjXRVsIXva/nHZGXKQLX8iWG2D5DOJNDP +ZkAEoIPg+7J85Q3u2iSFPnLPzKHlMAoQW8d9RAEYyJ6WqiILUIDShhvXg+RIkzri +wY/WkvhB/Kpj0r1SRbNhWRpmOWCR+0a2uHaLz9X0KTP7WMqQbmIdpRgZABEBAAGJ +ATwEGAEIACYWIQQZjHIqS6wzbp2qrkRXnQGzq+FUDgUCXNA5OgIbDAUJA8JnAAAK +CRBXnQGzq+FUDgI6B/9Far0CUR6rWvUiviBY4P5oe44I9P9P7ilWmum1cIQWxMyF +0sc5tRcVLpMomURlrDz0TR5GNs+nuGAHTRBfN7VO0Y+R/LyEd1Rf80ONObXOqzMp +vF9CdW3a7W4WicZwnGgUOImTICazR2VmR+RREdZshqrOCaOnuKmN3QwGH1zzFwJA +sTbLoNMdBv8SEARaRVOWPM1HwJ701mMYF48FqhHd5uinH/ZCeBhqrBfhmXa68FWx +xuyJz6ttl5Fp4nsB3waQdgPGZJ9NUrGfopLUZ44xDuJjBONd7rbYOh71TWbHd8wG +V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ +=et/d +-----END PGP PUBLIC KEY BLOCK-----` diff --git a/openpgp/packet/notation.go b/openpgp/packet/notation.go new file mode 100644 index 000000000..967b00fb8 --- /dev/null +++ b/openpgp/packet/notation.go @@ -0,0 +1,45 @@ +package packet + +// Notation type represents a Notation Data subpacket +// see https://tools.ietf.org/html/rfc4880#section-5.2.3.16 +type Notation struct { + flags []byte + name string + value []byte + critical bool +} + +func (not *Notation) IsHumanReadable() (bool) { + return not.flags[0] & 0x80 == 0x80 +} + +func (not *Notation) GetName() (string) { + return not.name +} + +func (not *Notation) GetBinaryValue() ([]byte) { + return not.value +} + +func (not *Notation) GetStringValue() (string) { + return string(not.value) +} + +func (not *Notation) IsCritical() (bool) { + return not.critical +} + +func (not *Notation) getData() ([]byte) { + nameLen := len(not.name) + valueLen := len(not.value) + nameData := []byte(not.name) + + data := not.flags + data[4] = byte(nameLen >> 8) + data[5] = byte(nameLen) + data[6] = byte(valueLen >> 8) + data[7] = byte(valueLen) + + data = append(data, nameData...) + return append(data, not.value...) +} diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 6d2a61ceb..225266b5a 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -71,6 +71,7 @@ type Signature struct { IssuerFingerprint []byte SignerUserId *string IsPrimaryId *bool + Notations []Notation // PolicyURI can be set to the URI of a document that describes the // policy under which the signature was issued. See RFC 4880, section @@ -224,6 +225,7 @@ const ( keyExpirationSubpacket signatureSubpacketType = 9 prefSymmetricAlgosSubpacket signatureSubpacketType = 11 issuerSubpacket signatureSubpacketType = 16 + notationDataSubpacket signatureSubpacketType = 20 prefHashAlgosSubpacket signatureSubpacketType = 21 prefCompressionSubpacket signatureSubpacketType = 22 primaryUserIdSubpacket signatureSubpacketType = 25 @@ -330,6 +332,24 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r } sig.IssuerKeyId = new(uint64) *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) + case notationDataSubpacket: + // Notation data, section 5.2.3.16 + nameLength := uint32(subpacket[4])<<8 | uint32(subpacket[5]) + valueLength := uint32(subpacket[6])<<8 | uint32(subpacket[7]) + + if len(subpacket) != (int(nameLength) + int(valueLength) + 8) { + err = errors.StructuralError("notation data subpacket with bad length") + return + } + + notation := Notation{ + flags: subpacket[0:4], + name: string(subpacket[8: (nameLength + 8)]), + value: subpacket[(nameLength + 8) : (valueLength + nameLength + 8)], + critical: isCritical, + } + + sig.Notations = append(sig.Notations, notation) case prefHashAlgosSubpacket: // Preferred hash algorithms, section 5.2.3.8 if !isHashed { @@ -885,6 +905,17 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}}) } + for _, notation := range sig.Notations { + subpackets = append( + subpackets, + outputSubpacket{ + true, + notationDataSubpacket, + notation.IsCritical(), + notation.getData(), + }) + } + // The following subpackets may only appear in self-signatures. var features = byte(0x00)