Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APIs to get the creation time of verified detached signatures #157

Merged
merged 2 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
- Function to verify a detached signature and get its creation time:
```go
func (keyRing *KeyRing) GetVerifiedSignatureTimestamp(message *PlainMessage, signature *PGPSignature, verifyTime int64) (int64, error)
```

## [2.3.1] 2021-12-15
### Fixed
- Fix the verification of PGP/MIME message signatures:
Expand Down
38 changes: 38 additions & 0 deletions crypto/keyring_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,44 @@ func (keyRing *KeyRing) VerifyDetachedEncrypted(message *PlainMessage, encrypted
return keyRing.VerifyDetached(message, signature, verifyTime)
}

// GetVerifiedSignatureTimestamp verifies a PlainMessage with a detached PGPSignature
// returns the creation time of the signature if it succeeds
// and returns a SignatureVerificationError if fails.
func (keyRing *KeyRing) GetVerifiedSignatureTimestamp(message *PlainMessage, signature *PGPSignature, verifyTime int64) (int64, error) {
packets := packet.NewReader(bytes.NewReader(signature.Data))
var err error
var p packet.Packet
for {
p, err = packets.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
continue
}
sigPacket, ok := p.(*packet.Signature)
if !ok {
continue
}
var outBuf bytes.Buffer
err = sigPacket.Serialize(&outBuf)
if err != nil {
continue
}
err = verifySignature(
keyRing.entities,
message.NewReader(),
outBuf.Bytes(),
verifyTime,
)
if err != nil {
continue
}
return sigPacket.CreationTime.Unix(), nil
}
return 0, errors.Wrap(err, "gopenpgp: can't verify any signature packets")
}

// ------ INTERNAL FUNCTIONS -------

// Core for encryption+signature (non-streaming) functions.
Expand Down
16 changes: 8 additions & 8 deletions crypto/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,23 +423,23 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
}

// GetBinary returns the unarmored binary content of the signature as a []byte.
func (msg *PGPSignature) GetBinary() []byte {
return msg.Data
func (sig *PGPSignature) GetBinary() []byte {
return sig.Data
}

// GetArmored returns the armored signature as a string.
func (msg *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
func (sig *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(sig.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)
func (sig *PGPSignature) GetSignatureKeyIDs() ([]uint64, bool) {
return getSignatureKeyIDs(sig.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())
func (sig *PGPSignature) GetHexSignatureKeyIDs() ([]string, bool) {
return getHexKeyIDs(sig.GetSignatureKeyIDs())
}

// GetBinary returns the unarmored signed data as a []byte.
Expand Down
121 changes: 121 additions & 0 deletions crypto/signature_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package crypto

import (
"bytes"
"errors"
"io"
"io/ioutil"
"regexp"
"testing"

"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -73,3 +77,120 @@ func TestVerifyBinDetachedSig(t *testing.T) {
t.Fatal("Cannot verify binary signature:", verificationError)
}
}

func Test_KeyRing_GetVerifiedSignatureTimestampSuccess(t *testing.T) {
message := NewPlainMessageFromString("Hello world!")
var time int64 = 1600000000
pgp.latestServerTime = time
defer func() {
pgp.latestServerTime = testTime
}()
signature, err := keyRingTestPrivate.SignDetached(message)
if err != nil {
t.Errorf("Got an error while generating the signature: %v", err)
}
actualTime, err := keyRingTestPublic.GetVerifiedSignatureTimestamp(message, signature, 0)
if err != nil {
t.Errorf("Got an error while parsing the signature creation time: %v", err)
}
if time != actualTime {
t.Errorf("Expected creation time to be %d, got %d", time, actualTime)
}
}

func Test_KeyRing_GetVerifiedSignatureWithTwoKeysTimestampSuccess(t *testing.T) {
publicKey1Armored, err := ioutil.ReadFile("testdata/signature/publicKey1")
if err != nil {
t.Errorf("Couldn't read the public key file: %v", err)
}
publicKey1 := parseKey(t, string(publicKey1Armored))
publicKey2Armored, err := ioutil.ReadFile("testdata/signature/publicKey2")
if err != nil {
t.Errorf("Couldn't read the public key file: %v", err)
}
publicKey2 := parseKey(t, string(publicKey2Armored))
message := NewPlainMessageFromString("hello world")
signatureArmored, err := ioutil.ReadFile("testdata/signature/detachedSigSignedTwice")
if err != nil {
t.Errorf("Couldn't read the signature file: %v", err)
}
signature, err := NewPGPSignatureFromArmored(string(signatureArmored))
if err != nil {
t.Errorf("Got an error while parsing the signature: %v", err)
}
time1 := getTimestampOfIssuer(signature, publicKey1.GetKeyID())
time2 := getTimestampOfIssuer(signature, publicKey2.GetKeyID())
keyRing, err := NewKeyRing(publicKey1)
if err != nil {
t.Errorf("Got an error while building the key ring: %v", err)
}
err = keyRing.AddKey(publicKey2)
if err != nil {
t.Errorf("Got an error while adding key 2 to the key ring: %v", err)
}
actualTime, err := keyRing.GetVerifiedSignatureTimestamp(message, signature, 0)
if err != nil {
t.Errorf("Got an error while parsing the signature creation time: %v", err)
}
if time1 != actualTime {
t.Errorf("Expected creation time to be %d, got %d", time1, actualTime)
}
if time2 == actualTime {
t.Errorf("Expected creation time to be different from %d", time2)
}
}

func parseKey(t *testing.T, keyArmored string) *Key {
key, err := NewKeyFromArmored(keyArmored)
if err != nil {
t.Errorf("Couldn't parse key: %v", err)
return nil
}
return key
}

func getTimestampOfIssuer(signature *PGPSignature, keyID uint64) int64 {
packets := packet.NewReader(bytes.NewReader(signature.Data))
var err error
var p packet.Packet
for {
p, err = packets.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
continue
}
sigPacket, ok := p.(*packet.Signature)
if !ok {
continue
}
var outBuf bytes.Buffer
err = sigPacket.Serialize(&outBuf)
if err != nil {
continue
}
if *sigPacket.IssuerKeyId == keyID {
return sigPacket.CreationTime.Unix()
}
}
return -1
}

func Test_KeyRing_GetVerifiedSignatureTimestampError(t *testing.T) {
message := NewPlainMessageFromString("Hello world!")
var time int64 = 1600000000
pgp.latestServerTime = time
defer func() {
pgp.latestServerTime = testTime
}()
signature, err := keyRingTestPrivate.SignDetached(message)
if err != nil {
t.Errorf("Got an error while generating the signature: %v", err)
}
message_corrupted := NewPlainMessageFromString("Ciao world!")
_, err = keyRingTestPublic.GetVerifiedSignatureTimestamp(message_corrupted, signature, 0)
if err == nil {
t.Errorf("Expected an error while parsing the creation time of a wrong signature, got nil")
}
}
10 changes: 10 additions & 0 deletions crypto/testdata/signature/detachedSigSignedTwice
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-----BEGIN PGP SIGNATURE-----

wnUEARYKAAYFAmCCo8gAIQkQyQtnL+EYbekWIQTopSabUSqDUEv/FMHJC2cv
4Rht6VeGAP4mUJl+WYN9nLE57YByTh95OmcZmwfgz5Z4R570YqTVngD/VBym
icc7YREcxij1gC6SSAe8kgKW6oVOWzxJ8HkOSQrCdQQBFgoABgUCYIK9vQAh
CRCGHCX3YYW5NRYhBErDPc6OYkUaNQCLhoYcJfdhhbk1W1QBAPhrkAjimO22
jh1V2A8pRCOs53Ig/AMAFbN37BaAIEVKAP0SVMTL6zTxYJcxWNPog7Bv5lM4
Px4G+hZ2Kia//qlgBg==
=0aeU
-----END PGP SIGNATURE-----
13 changes: 13 additions & 0 deletions crypto/testdata/signature/publicKey1
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

xjMEYIKjXxYJKwYBBAHaRw8BAQdAbmODPSLO5tOI0GxfV+x5bgiiFriCcH3t
6lbJkS+OzKbNEHRlc3QgPHRlc3RAYS5pdD7CjAQQFgoAHQUCYIKjXwQLCQcI
AxUICgQWAAIBAhkBAhsDAh4BACEJEMkLZy/hGG3pFiEE6KUmm1Eqg1BL/xTB
yQtnL+EYbenOlAEAn7A7RXQJ9FUzhuiOHeKqczdslgOO5LFcng1LuSIWn1UB
ANWHrxnH63jnFLE82mfhpRZ5FYJ1fEXA9+3v6at3ZE8IzjgEYIKjXxIKKwYB
BAGXVQEFAQEHQA5moGr1AKlYvKI+JpyB6W640eXpQFNSiV6LBjuMteNbAwEI
B8J2BBgWCAAJBQJggqNfAhsMACEJEMkLZy/hGG3pFiEE6KUmm1Eqg1BL/xTB
yQtnL+EYben97QD4hf6DttxyczHGqxGbboatBZ3IufJgFm6r2xNf9d9lSAD3
U12oHbxyYUhapbFFkSIBo7DWJqWvx3iUEPqzY6jIAA==
=ZWrn
-----END PGP PUBLIC KEY BLOCK-----
13 changes: 13 additions & 0 deletions crypto/testdata/signature/publicKey2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

xjMEYIKjgxYJKwYBBAHaRw8BAQdARyd9iDlrlozcTG144XFIjWozyWLz0KQv
fL4lqIrwM8XNEHRlc3QgPHRlc3RAYi5pdD7CjAQQFgoAHQUCYIKjgwQLCQcI
AxUICgQWAAIBAhkBAhsDAh4BACEJEIYcJfdhhbk1FiEESsM9zo5iRRo1AIuG
hhwl92GFuTVPRAD6A6//tK5pLPa1d7mgsoqyJ9BZyTAmnzxtbIgmOU9/TDcB
AI4cGBfCOLzRPw6L0il5Rt78TX1jz4Dlzu6YixJcJ2AFzjgEYIKjgxIKKwYB
BAGXVQEFAQEHQMjb0Q1FWvHzj0hyOiEN5ndChBDceUqxmQ0wOYDVqq8JAwEI
B8J4BBgWCAAJBQJggqODAhsMACEJEIYcJfdhhbk1FiEESsM9zo5iRRo1AIuG
hhwl92GFuTXz4AEAqn4L+ayYgphejF/ZTRIseHPK+t521CT6NZKoVaHnTWQA
/0+kMEB5d+CH3Mb54cUganYHPLj5utO2PexEJc3xARIG
=IEm4
-----END PGP PUBLIC KEY BLOCK-----