Merge pull request #157 from ProtonMail/feat/signature_creation_time
Add APIs to get the creation time of verified detached signatures
This commit is contained in:
commit
e8c7fa3ac9
7 changed files with 211 additions and 8 deletions
|
|
@ -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/),
|
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).
|
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
|
## [2.3.1] 2021-12-15
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix the verification of PGP/MIME message signatures:
|
- Fix the verification of PGP/MIME message signatures:
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,44 @@ func (keyRing *KeyRing) VerifyDetachedEncrypted(message *PlainMessage, encrypted
|
||||||
return keyRing.VerifyDetached(message, signature, verifyTime)
|
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 -------
|
// ------ INTERNAL FUNCTIONS -------
|
||||||
|
|
||||||
// Core for encryption+signature (non-streaming) functions.
|
// Core for encryption+signature (non-streaming) functions.
|
||||||
|
|
|
||||||
|
|
@ -423,23 +423,23 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBinary returns the unarmored binary content of the signature as a []byte.
|
// GetBinary returns the unarmored binary content of the signature as a []byte.
|
||||||
func (msg *PGPSignature) GetBinary() []byte {
|
func (sig *PGPSignature) GetBinary() []byte {
|
||||||
return msg.Data
|
return sig.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArmored returns the armored signature as a string.
|
// GetArmored returns the armored signature as a string.
|
||||||
func (msg *PGPSignature) GetArmored() (string, error) {
|
func (sig *PGPSignature) GetArmored() (string, error) {
|
||||||
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
|
return armor.ArmorWithType(sig.Data, constants.PGPSignatureHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
|
// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
|
||||||
func (msg *PGPSignature) GetSignatureKeyIDs() ([]uint64, bool) {
|
func (sig *PGPSignature) GetSignatureKeyIDs() ([]uint64, bool) {
|
||||||
return getSignatureKeyIDs(msg.Data)
|
return getSignatureKeyIDs(sig.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
// GetHexSignatureKeyIDs Returns the key IDs of the keys to which the session key is encrypted.
|
||||||
func (msg *PGPSignature) GetHexSignatureKeyIDs() ([]string, bool) {
|
func (sig *PGPSignature) GetHexSignatureKeyIDs() ([]string, bool) {
|
||||||
return getHexKeyIDs(msg.GetSignatureKeyIDs())
|
return getHexKeyIDs(sig.GetSignatureKeyIDs())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBinary returns the unarmored signed data as a []byte.
|
// GetBinary returns the unarmored signed data as a []byte.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
@ -73,3 +77,120 @@ func TestVerifyBinDetachedSig(t *testing.T) {
|
||||||
t.Fatal("Cannot verify binary signature:", verificationError)
|
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
crypto/testdata/signature/detachedSigSignedTwice
vendored
Normal file
10
crypto/testdata/signature/detachedSigSignedTwice
vendored
Normal file
|
|
@ -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
crypto/testdata/signature/publicKey1
vendored
Normal file
13
crypto/testdata/signature/publicKey1
vendored
Normal file
|
|
@ -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
crypto/testdata/signature/publicKey2
vendored
Normal file
13
crypto/testdata/signature/publicKey2
vendored
Normal file
|
|
@ -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-----
|
||||||
Loading…
Add table
Add a link
Reference in a new issue