Add KeyIDs public API functionality (#76)

* Add public KeyIDs functions

* Add signature keyIDs functions

* Lint code
This commit is contained in:
wussler 2020-09-01 10:02:13 +02:00 committed by GitHub
parent 1f4d966115
commit 2f89b9fa0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 212 additions and 9 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(key *Key) ArmorWithCustomHeaders(comment, version string) (string, error)
(key *Key) GetArmoredPublicKeyWithCustomHeaders(comment, version string) (string, error)
```
- Message armoring with custom headers
```go
(msg *PGPMessage) GetArmoredWithCustomHeaders(comment, version string) (string, error)
@ -18,7 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Extraction of encryption key IDs from a PGP message, i.e. the IDs of the keys used in the encryption of the session key
```go
(msg *PGPMessage) getEncryptionKeyIDs() ([]uint64, bool)
(msg *PGPMessage) GetEncryptionKeyIDs() ([]uint64, bool)
(msg *PGPMessage) GetHexEncryptionKeyIDs() ([]uint64, bool)
```
- Extraction of signing key IDs from a PGP message, i.e. the IDs of the keys used in the signature of the message
(of all the readable, unencrypted signature packets)
```go
(msg *PGPMessage) GetSignatureKeyIDs() ([]uint64, bool)
(msg *PGPMessage) GetHexSignatureKeyIDs() ([]string, bool)
```
- Getter for the x/crypto Entity (internal components of an OpenPGP key) from Key struct
@ -37,17 +46,49 @@ DecryptBinaryMessageArmored(privateKey string, passphrase []byte, ciphertext str
(key *Key) ToPublic() (publicKey *Key, err error)
```
- Helpers to handle detached signatures
```go
EncryptSignArmoredDetached(
publicKey, privateKey string,
passphrase, plainData []byte,
) (ciphertext, signature string, err error)
DecryptVerifyArmoredDetached(
publicKey, privateKey string,
passphrase []byte,
ciphertext string,
armoredSignature string,
) (plainData []byte, err error)
```
- `EncryptSignArmoredDetachedMobileResult` Struct (with its helper) to allow detached signature + encryption in one pass
```go
type EncryptSignArmoredDetachedMobileResult struct {
Ciphertext, Signature string
}
EncryptSignArmoredDetachedMobile(
publicKey, privateKey string,
passphrase, plainData []byte,
) (wrappedTuple *EncryptSignArmoredDetachedMobileResult, err error)
```
### Changed
- Improved key and message armoring testing
- `EncryptSessionKey` now creates encrypted key packets for each valid encryption key in the provided keyring.
Returns a byte slice with all the concatenated key packets.
- Use aes256 chiper for message encryption with password.
- Use aes256 cipher for password-encrypted messages.
- The helpers `EncryptSignMessageArmored`, `DecryptVerifyMessageArmored`, `DecryptVerifyAttachment`, and`DecryptBinaryMessageArmored`
now accept private keys as public keys and perform automatic casting if the keys are locked.
### Fixed
- Public key armoring headers
- `EncryptSessionKey` throws an error when invalid encryption keys are provided
- Session keys' size is now checked against the expected value to prevent panics
- Hex Key IDs returned from `(key *Key) GetHexKeyID() string` are now correctly padded
- Avoid panics in `(msg *PGPMessage) GetEncryptionKeyIDs() ([]uint64, bool)` by breaking the packet.next cycle on specific packet types
- Prevent the server time from going backwards in `UpdateTime`
## [2.0.1] - 2020-05-01
### Security
- Updated underlying crypto library

View file

@ -332,7 +332,7 @@ func (key *Key) PrintFingerprints() {
// GetHexKeyID returns the key ID, hex encoded as a string.
func (key *Key) GetHexKeyID() string {
return strconv.FormatUint(key.GetKeyID(), 16)
return keyIDToHex(key.GetKeyID())
}
// GetKeyID returns the key ID, encoded as 8-byte int.
@ -465,3 +465,8 @@ func generateKey(
return &Key{newEntity}, nil
}
// keyIDToHex casts a keyID to hex with the correct padding.
func keyIDToHex(keyID uint64) string {
return fmt.Sprintf("%016v", strconv.FormatUint(keyID, 16))
}

View file

@ -223,8 +223,8 @@ func (msg *PGPMessage) GetArmoredWithCustomHeaders(comment, version string) (str
return armor.ArmorWithTypeAndCustomHeaders(msg.Data, constants.PGPMessageHeader, version, comment)
}
// getEncryptionKeyIds Returns the key IDs of the keys to which the session key is encrypted.
func (msg *PGPMessage) getEncryptionKeyIDs() ([]uint64, bool) {
// GetEncryptionKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
func (msg *PGPMessage) GetEncryptionKeyIDs() ([]uint64, bool) {
packets := packet.NewReader(bytes.NewReader(msg.Data))
var err error
var ids []uint64
@ -252,6 +252,21 @@ Loop:
return ids, false
}
// GetHexEncryptionKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
func (msg *PGPMessage) GetHexEncryptionKeyIDs() ([]string, bool) {
return getHexKeyIDs(msg.GetEncryptionKeyIDs())
}
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
func (msg *PGPMessage) GetSignatureKeyIDs() ([]uint64, bool) {
return getSignatureKeyIDs(msg.Data)
}
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
func (msg *PGPMessage) GetHexSignatureKeyIDs() ([]string, bool) {
return getHexKeyIDs(msg.GetSignatureKeyIDs())
}
// GetBinaryDataPacket returns the unarmored binary datapacket as a []byte.
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
return msg.DataPacket
@ -386,6 +401,16 @@ func (msg *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
}
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
func (msg *PGPSignature) GetSignatureKeyIDs() ([]uint64, bool) {
return getSignatureKeyIDs(msg.Data)
}
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
func (msg *PGPSignature) GetHexSignatureKeyIDs() ([]string, bool) {
return getHexKeyIDs(msg.GetSignatureKeyIDs())
}
// GetBinary returns the unarmored signed data as a []byte.
func (msg *ClearTextMessage) GetBinary() []byte {
return msg.Data
@ -425,3 +450,48 @@ func IsPGPMessage(data string) bool {
constants.PGPMessageHeader + "-----")
return re.MatchString(data)
}
func getSignatureKeyIDs(data []byte) ([]uint64, bool) {
packets := packet.NewReader(bytes.NewReader(data))
var err error
var ids []uint64
var onePassSignaturePacket *packet.OnePassSignature
var signaturePacket *packet.Signature
Loop:
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
break
}
switch p := p.(type) {
case *packet.OnePassSignature:
onePassSignaturePacket = p
ids = append(ids, onePassSignaturePacket.KeyId)
case *packet.Signature:
signaturePacket = p
if signaturePacket.IssuerKeyId != nil {
ids = append(ids, *signaturePacket.IssuerKeyId)
}
case *packet.SymmetricallyEncrypted,
*packet.AEADEncrypted,
*packet.Compressed,
*packet.LiteralData:
break Loop
}
}
if len(ids) > 0 {
return ids, true
}
return ids, false
}
func getHexKeyIDs(keyIDs []uint64, ok bool) ([]string, bool) {
hexIDs := make([]string, len(keyIDs))
for i, id := range keyIDs {
hexIDs[i] = keyIDToHex(id)
}
return hexIDs, ok
}

View file

@ -228,7 +228,7 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
assert.Exactly(t, message.GetString(), decrypted.GetString())
}
func TestMessagegetGetEncryptionKeyIDs(t *testing.T) {
func TestMessageGetEncryptionKeyIDs(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
@ -236,7 +236,7 @@ func TestMessagegetGetEncryptionKeyIDs(t *testing.T) {
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
ids, ok := ciphertext.getEncryptionKeyIDs()
ids, ok := ciphertext.GetEncryptionKeyIDs()
assert.Exactly(t, 3, len(ids))
assert.True(t, ok)
encKey, ok := keyRingTestMultiple.entities[0].EncryptionKey(time.Now())
@ -244,6 +244,50 @@ func TestMessagegetGetEncryptionKeyIDs(t *testing.T) {
assert.Exactly(t, encKey.PublicKey.KeyId, ids[0])
}
func TestMessageGetHexGetEncryptionKeyIDs(t *testing.T) {
ciphertext, err := NewPGPMessageFromArmored(readTestFile("message_multipleKeyID", false))
if err != nil {
t.Fatal("Expected no error when reading message, got:", err)
}
ids, ok := ciphertext.GetHexEncryptionKeyIDs()
assert.Exactly(t, 2, len(ids))
assert.True(t, ok)
assert.Exactly(t, "76ad736fa7e0e83c", ids[0])
assert.Exactly(t, "0f65b7ae456a9ceb", ids[1])
}
func TestMessageGetSignatureKeyIDs(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
signature, err := keyRingTestPrivate.SignDetached(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
ids, ok := signature.GetSignatureKeyIDs()
assert.Exactly(t, 1, len(ids))
assert.True(t, ok)
signingKey, ok := keyRingTestPrivate.entities[0].SigningKey(time.Now())
assert.True(t, ok)
assert.Exactly(t, signingKey.PublicKey.KeyId, ids[0])
}
func TestMessageGetHexSignatureKeyIDs(t *testing.T) {
ciphertext, err := NewPGPMessageFromArmored(readTestFile("message_plainSignature", false))
if err != nil {
t.Fatal("Expected no error when reading message, got:", err)
}
ids, ok := ciphertext.GetHexSignatureKeyIDs()
assert.Exactly(t, 2, len(ids))
assert.True(t, ok)
assert.Exactly(t, "3eb6259edf21df24", ids[0])
assert.Exactly(t, "d05b722681936ad0", ids[1])
}
func TestMessageGetArmoredWithCustomHeaders(t *testing.T) {
var message = NewPlainMessageFromString("plain text")

View file

@ -143,7 +143,7 @@ func TestDataPacketEncryption(t *testing.T) {
if err != nil {
t.Fatal("Unable to unarmor pgp message, got:", err)
}
ids, ok := pgpMessage.getEncryptionKeyIDs()
ids, ok := pgpMessage.GetEncryptionKeyIDs()
assert.True(t, ok)
assert.Exactly(t, 3, len(ids))

20
crypto/testdata/message_multipleKeyID vendored Normal file
View file

@ -0,0 +1,20 @@
-----BEGIN PGP MESSAGE-----
Version: OpenPGP.js v3.1.3
Comment: https://openpgpjs.org
wcBMA3atc2+n4Og8AQf+PiFMi7BkL/Ppe6kILs0E/ik0jR4sAXaNA0kpJYYE
FhWBZ8RWOFLeriyABaY2gQD86kQ7TZSgJcOoBYWLEIRdnHP0ss4niG1WuEPB
gyKXdyyS99URdYtxCV0ErxPHObdO61vxwsE7Fx9nC3Lk8mnktzfqWQelA9Ej
TLj9pO3M+uo2H5baZ8ylErcTRmgyQlSy+gcjCDeLs94kDpbdJlZX1x+79Zub
2iWnOSmdmW2ZhbBq7w8az5qhPUfMMNIRcfieOAQwkKz5dBoCO81mwHcd+LWY
EiiNDNVrD5uYV4t0PqHp9o5537JV//tlG1UEWvNhQ35tqXgsmND7MQkb2ct3
6cHATAMPZbeuRWqc6wEH/2wKL4h9QOOxBAJKHf1MJlVbMN8qgwtKr7q/YcdH
gooTF5asSMq91qKCcW38NuFXkdzf5sGZsOA0yLTQnjbu+42RzBfty10qTZg0
v1zmgKErACFpsztYNOdsJh3aGJ4WjytpZWKL+PqHnX4/HR+zbEMEGfDjhTBZ
eL8TLa3F3OK533F2oNAO5ITYsdnipVmiy89FL5yt/9ZpvFRRuj8lOwJYTe4O
pQ+ZbenRZ0sXyEIV6xZguqvFICOELoy4LM3kHpQfY4Gi1JPT/buWxnDFugEW
u5JQ6qix0Y1KunuWSogEjfaJ8BgLSBs/U7RxxjLYETFB15VxyEaQJx9wx3Id
uU7SRQG+UkFbDn9ghZoF7ROPTXAUnHlqODGxdgnPhJJQaPSkNOMALkBI6I4Q
lqsO2LprVTVeCGo+Qd3WrE5MqGunvDli5VK37A==
=QeVN
-----END PGP MESSAGE-----

23
crypto/testdata/message_plainSignature vendored Normal file
View file

@ -0,0 +1,23 @@
-----BEGIN PGP MESSAGE-----
Comment: https://gopenpgp.org
Version: GopenPGP 2.0.1
wcBMAw9lt65FapzrAQf/bAoviH1A47EEAkod/UwmVVsw3yqDC0qvur9hx0eCihMX
lqxIyr3WooJxbfw24VeR3N/mwZmw4DTItNCeNu77jZHMF+3LXSpNmDS/XOaAoSsA
IWmzO1g052wmHdoYnhaPK2llYov4+oedfj8dH7NsQwQZ8OOFMFl4vxMtrcXc4rnf
cXag0A7khNix2eKlWaLLz0UvnK3/1mm8VFG6PyU7AlhN7g6lD5lt6dFnSxfIQhXr
FmC6q8UgI4QujLgszeQelB9jgaLUk9P9u5bGcMW6ARa7klDqqLHRjUq6e5ZKiASN
9onwGAtIGz9TtHHGMtgRMUHXlXHIRpAnH3DHch25TsLAXAQAAQoAEAUCXNlzAwkQ
PrYlnt8h3yQAAMuZCACipXp2GmSo26JgRJADNan01cBu6nVbzpNHNqKqUNLDnCvZ
L4HjXeUQ/o+vl8GSpy51kvcXmNsD36d4agnzDf7OjiIdcLns/ARSUESQyrprf+oF
+OYTeRXufxCoiG35Kn82g4ML2ifj52c+E/mS7ZTupQgSZrXPcS7XNAEuAuOnjC8O
5TpzlA3hKwirVzRVmyn2wlTVWQWWMNoci8esnLH/eZVt/3DFCBwVG3D+avsnKXN3
CI6kAFtiRA9drDHXT56AR4lZGotTltG2g5D3exAzczuHpxA4Qshp/03dDADBAm4T
3IJ6p/7rZ3wgmeaRfcg9sAxEJ4y1pME1ma8jBrB8wpwEAAEKABAFAlzZcwMJENBb
ciaBk2rQAADeRAP+LCDF4zEXVQYhQXBnvVGEK1Ar3R/0lj3GSWczb3+QYbcmwAZR
n+ll3xlrgCqls4BfVBvXQ/hyABF3HPlkFRNodHLonq+fuvjCgEnsdJG18/yzfeP6
Ox0w2vHRE3ad78dhJvyuWqL7Wd8L2EG9MsCdzx5MQnfWShzQE4EcSnbCtprSRQG+
UkFbDn9ghZoF7ROPTXAUnHlqODGxdgnPhJJQaPSkNOMALkBI6I4QlqsO2LprVTVe
CGo+Qd3WrE5MqGunvDli5VK37A==
=cPmR
-----END PGP MESSAGE-----