From 1ec90e34ea1e970c01260f6f4a0794d4586cfaf6 Mon Sep 17 00:00:00 2001 From: "M. Thiercelin" Date: Thu, 2 Mar 2023 15:33:12 +0100 Subject: [PATCH] Add API to add contexts to detached signatures. Using the notation data packets of signatures, we add a way to set a context to detached signatures. We also add a way to enforce that signatures have the right context during verification. --- CHANGELOG.md | 14 +- constants/context.go | 3 + crypto/keyring_message.go | 59 ++- crypto/keyring_streaming.go | 21 ++ crypto/signature.go | 125 +++++- crypto/signature_test.go | 355 +++++++++++++++++- .../signature/critical_context_detached_sig | 14 + .../double_critical_context_detached_sig | 15 + .../signature/no_context_detached_sig | 13 + .../non_critical_context_detached_sig | 14 + 10 files changed, 614 insertions(+), 19 deletions(-) create mode 100644 constants/context.go create mode 100644 crypto/testdata/signature/critical_context_detached_sig create mode 100644 crypto/testdata/signature/double_critical_context_detached_sig create mode 100644 crypto/testdata/signature/no_context_detached_sig create mode 100644 crypto/testdata/signature/non_critical_context_detached_sig diff --git a/CHANGELOG.md b/CHANGELOG.md index c92f1ba..42e7500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,23 @@ 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 -# Changed + +### Added +- API for adding context to detached signatures: + ```go + sig, err := keyRing.SignDetachedWithContext(message, context) + ``` +- API to verify the context of detached signatures: + ```go + err := keyRing.VerifyDetachedWithContext(message, signature, verifyTime, verificationContext) + ``` +### Changed - Update `github.com/ProtonMail/go-crypto` to the latest version - More strictly verify detached signatures: reject detached signatures from revoked and expired keys. - In `GetVerifiedSignatureTimestamp`, use the new `VerifyDetachedSignatureAndHash` function to get the verified signature, instead of parsing the signature packets manually to get the timestamp. ## [2.5.2] 2022-01-25 -# Changed +### Changed - Update `github.com/ProtonMail/go-crypto` to the latest version ## [2.5.1] 2022-01-24 diff --git a/constants/context.go b/constants/context.go new file mode 100644 index 0000000..ca00152 --- /dev/null +++ b/constants/context.go @@ -0,0 +1,3 @@ +package constants + +const SignatureContextName = "context@proton.ch" diff --git a/crypto/keyring_message.go b/crypto/keyring_message.go index 999ed73..6bd48a9 100644 --- a/crypto/keyring_message.go +++ b/crypto/keyring_message.go @@ -62,12 +62,26 @@ func (keyRing *KeyRing) Decrypt( // SignDetached generates and returns a PGPSignature for a given PlainMessage. func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) { + return keyRing.SignDetachedWithContext(message, nil) +} + +// SignDetachedWithContext generates and returns a PGPSignature for a given PlainMessage. +// If a context is provided, it is added to the signature as notation data +// with the name set in `constants.SignatureContextName`. +func (keyRing *KeyRing) SignDetachedWithContext(message *PlainMessage, context *SigningContext) (*PGPSignature, error) { signEntity, err := keyRing.getSigningEntity() if err != nil { return nil, err } - - config := &packet.Config{DefaultHash: crypto.SHA512, Time: getTimeGenerator()} + var signatureNotations []*packet.Notation + if context != nil { + signatureNotations = []*packet.Notation{context.getNotation()} + } + config := &packet.Config{ + DefaultHash: crypto.SHA512, + Time: getTimeGenerator(), + SignatureNotations: signatureNotations, + } var outBuf bytes.Buffer if message.IsBinary() { err = openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config) @@ -89,6 +103,22 @@ func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSign message.NewReader(), signature.GetBinary(), verifyTime, + nil, + ) + return err +} + +// VerifyDetachedWithContext verifies a PlainMessage with a detached PGPSignature +// and returns a SignatureVerificationError if fails. +// If a context is provided, it verifies that the signature is valid in the given context, using +// the signature notation with name the name set in `constants.SignatureContextName`. +func (keyRing *KeyRing) VerifyDetachedWithContext(message *PlainMessage, signature *PGPSignature, verifyTime int64, verificationContext *VerificationContext) error { + _, err := verifySignature( + keyRing.entities, + message.NewReader(), + signature.GetBinary(), + verifyTime, + verificationContext, ) return err } @@ -132,6 +162,31 @@ func (keyRing *KeyRing) GetVerifiedSignatureTimestamp(message *PlainMessage, sig message.NewReader(), signature.GetBinary(), verifyTime, + nil, + ) + if err != nil { + return 0, err + } + return sigPacket.CreationTime.Unix(), nil +} + +// GetVerifiedSignatureTimestampWithContext verifies a PlainMessage with a detached PGPSignature +// returns the creation time of the signature if it succeeds +// and returns a SignatureVerificationError if fails. +// If a context is provided, it verifies that the signature is valid in the given context, using +// the signature notation with name the name set in `constants.SignatureContextName`. +func (keyRing *KeyRing) GetVerifiedSignatureTimestampWithContext( + message *PlainMessage, + signature *PGPSignature, + verifyTime int64, + verificationContext *VerificationContext, +) (int64, error) { + sigPacket, err := verifySignature( + keyRing.entities, + message.NewReader(), + signature.GetBinary(), + verifyTime, + verificationContext, ) if err != nil { return 0, err diff --git a/crypto/keyring_streaming.go b/crypto/keyring_streaming.go index 51ef80d..65f6afb 100644 --- a/crypto/keyring_streaming.go +++ b/crypto/keyring_streaming.go @@ -329,6 +329,27 @@ func (keyRing *KeyRing) VerifyDetachedStream( message, signature.GetBinary(), verifyTime, + nil, + ) + return err +} + +// VerifyDetachedStreamWithContext verifies a message reader with a detached PGPSignature +// and returns a SignatureVerificationError if fails. +// If a context is provided, it verifies that the signature is valid in the given context, using +// the signature notations. +func (keyRing *KeyRing) VerifyDetachedStreamWithContext( + message Reader, + signature *PGPSignature, + verifyTime int64, + verificationContext *VerificationContext, +) error { + _, err := verifySignature( + keyRing.entities, + message, + signature.GetBinary(), + verifyTime, + verificationContext, ) return err } diff --git a/crypto/signature.go b/crypto/signature.go index 609b347..c94c966 100644 --- a/crypto/signature.go +++ b/crypto/signature.go @@ -118,8 +118,96 @@ func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) er return nil } +// SigningContext gives the context that will be +// included in the signature's notation data. +type SigningContext struct { + Value string + IsCritical bool +} + +// NewSigningContext creates a new signing context. +// The value is set to the notation data. +// isCritical controls whether the notation is flagged as a critical packet. +func NewSigningContext(value string, isCritical bool) *SigningContext { + return &SigningContext{Value: value, IsCritical: isCritical} +} + +func (context *SigningContext) getNotation() *packet.Notation { + return &packet.Notation{ + Name: constants.SignatureContextName, + Value: []byte(context.Value), + IsCritical: context.IsCritical, + IsHumanReadable: true, + } +} + +// VerificationContext gives the context that will be +// used to verify the signature. +type VerificationContext struct { + Value string + IsRequired bool + RequiredAfter int64 +} + +// NewVerificationContext creates a new verification context. +// The value is checked against the signature's notation data. +// If isRequired is false, the signature is allowed to have no context set. +// If requiredAfter is != 0, the signature is allowed to have no context set if it +// was created before the unix time set in requiredAfter. +func NewVerificationContext(value string, isRequired bool, requiredAfter int64) *VerificationContext { + return &VerificationContext{ + Value: value, + IsRequired: isRequired, + RequiredAfter: requiredAfter, + } +} + +func (context *VerificationContext) isRequiredAtTime(signatureTime time.Time) bool { + return context.IsRequired && + (context.RequiredAfter == 0 || signatureTime.After(time.Unix(context.RequiredAfter, 0))) +} + +func findContext(notations []*packet.Notation) (string, error) { + context := "" + for _, notation := range notations { + if notation.Name == constants.SignatureContextName { + if context != "" { + return "", errors.New("gopenpgp: signature has multiple context notations") + } + if !notation.IsHumanReadable { + return "", errors.New("gopenpgp: context notation was not set as human-readable") + } + context = string(notation.Value) + } + } + return context, nil +} + +func (context *VerificationContext) verifyContext(sig *packet.Signature) error { + signatureContext, err := findContext(sig.Notations) + if err != nil { + return err + } + if signatureContext != context.Value { + contextRequired := context.isRequiredAtTime(sig.CreationTime) + if contextRequired { + return errors.New("gopenpgp: signature did not have the required context") + } else if signatureContext != "" { + return errors.New("gopenpgp: signature had a wrong context") + } + } + + return nil +} + // verifySignature verifies if a signature is valid with the entity list. -func verifySignature(pubKeyEntries openpgp.EntityList, origText io.Reader, signature []byte, verifyTime int64) (*packet.Signature, error) { +func verifySignature( + pubKeyEntries openpgp.EntityList, + origText io.Reader, + signature []byte, + verifyTime int64, + verificationContext *VerificationContext, +) (*packet.Signature, error) { config := &packet.Config{} if verifyTime == 0 { config.Time = func() time.Time { @@ -130,32 +218,43 @@ func verifySignature(pubKeyEntries openpgp.EntityList, origText io.Reader, signa return time.Unix(verifyTime+internal.CreationTimeOffset, 0) } } + + if verificationContext != nil { + config.KnownNotations = map[string]bool{constants.SignatureContextName: true} + } signatureReader := bytes.NewReader(signature) sig, signer, err := openpgp.VerifyDetachedSignatureAndHash(pubKeyEntries, origText, signatureReader, allowedHashes, config) if sig != nil && signer != nil && (errors.Is(err, pgpErrors.ErrSignatureExpired) || errors.Is(err, pgpErrors.ErrKeyExpired)) { if verifyTime == 0 { // Expiration check disabled - return sig, nil - } + err = nil + } else { + // Maybe the creation time offset pushed it over the edge + // Retry with the actual verification time + config.Time = func() time.Time { + return time.Unix(verifyTime, 0) + } - // Maybe the creation time offset pushed it over the edge - // Retry with the actual verification time - config.Time = func() time.Time { - return time.Unix(verifyTime, 0) - } + _, err = signatureReader.Seek(0, io.SeekStart) + if err != nil { + return nil, newSignatureFailed() + } - _, err = signatureReader.Seek(0, io.SeekStart) - if err != nil { - return nil, newSignatureFailed() + sig, signer, err = openpgp.VerifyDetachedSignatureAndHash(pubKeyEntries, origText, signatureReader, allowedHashes, config) } - - sig, signer, err = openpgp.VerifyDetachedSignatureAndHash(pubKeyEntries, origText, signatureReader, allowedHashes, config) } if err != nil || sig == nil || signer == nil { return nil, newSignatureFailed() } + if verificationContext != nil { + err := verificationContext.verifyContext(sig) + if err != nil { + return nil, newSignatureFailed() + } + } + return sig, nil } diff --git a/crypto/signature_test.go b/crypto/signature_test.go index ea7a928..eec95f4 100644 --- a/crypto/signature_test.go +++ b/crypto/signature_test.go @@ -13,6 +13,8 @@ import ( "github.com/stretchr/testify/assert" ) +const testMessage = "Hello world!" + const signedPlainText = "Signed message\n" var textSignature, binSignature *PGPSignature @@ -119,7 +121,7 @@ func TestVerifyBinDetachedSig(t *testing.T) { } func Test_KeyRing_GetVerifiedSignatureTimestampSuccess(t *testing.T) { - message := NewPlainMessageFromString("Hello world!") + message := NewPlainMessageFromString(testMessage) var time int64 = 1600000000 pgp.latestServerTime = time defer func() { @@ -218,7 +220,7 @@ func getTimestampOfIssuer(signature *PGPSignature, keyID uint64) int64 { } func Test_KeyRing_GetVerifiedSignatureTimestampError(t *testing.T) { - message := NewPlainMessageFromString("Hello world!") + message := NewPlainMessageFromString(testMessage) var time int64 = 1600000000 pgp.latestServerTime = time defer func() { @@ -234,3 +236,352 @@ func Test_KeyRing_GetVerifiedSignatureTimestampError(t *testing.T) { t.Errorf("Expected an error while parsing the creation time of a wrong signature, got nil") } } + +func Test_SignDetachedWithNonCriticalContext(t *testing.T) { + // given + + context := NewSigningContext( + "test-context", + false, + ) + // when + signature, err := keyRingTestPrivate.SignDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + context, + ) + // then + if err != nil { + t.Fatal(err) + } + p, err := packet.Read(bytes.NewReader(signature.Data)) + if err != nil { + t.Fatal(err) + } + sig, ok := p.(*packet.Signature) + if !ok { + t.Fatal("Packet was not a signature") + } + notations := sig.Notations + if len(notations) != 1 { + t.Fatal("Wrong number of notations") + } + notation := notations[0] + if notation.Name != constants.SignatureContextName { + t.Fatalf("Expected notation name to be %s, got %s", constants.SignatureContextName, notation.Name) + } + if string(notation.Value) != context.Value { + t.Fatalf("Expected notation value to be %s, got %s", context.Value, notation.Value) + } + if notation.IsCritical { + t.Fatal("Expected notation to be non critical") + } + if !notation.IsHumanReadable { + t.Fatal("Expected notation to be human readable") + } +} + +func Test_SignDetachedWithCriticalContext(t *testing.T) { + // given + + context := NewSigningContext( + "test-context", + true, + ) + // when + signature, err := keyRingTestPrivate.SignDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + context, + ) + // then + if err != nil { + t.Fatal(err) + } + p, err := packet.Read(bytes.NewReader(signature.Data)) + if err != nil { + t.Fatal(err) + } + sig, ok := p.(*packet.Signature) + if !ok { + t.Fatal("Packet was not a signature") + } + notations := sig.Notations + if len(notations) != 1 { + t.Fatal("Wrong number of notations") + } + notation := notations[0] + if notation.Name != constants.SignatureContextName { + t.Fatalf("Expected notation name to be %s, got %s", constants.SignatureContextName, notation.Name) + } + if string(notation.Value) != context.Value { + t.Fatalf("Expected notation value to be %s, got %s", context.Value, notation.Value) + } + if !notation.IsCritical { + t.Fatal("Expected notation to be critical") + } + if !notation.IsHumanReadable { + t.Fatal("Expected notation to be human readable") + } +} + +func Test_VerifyDetachedWithUnknownCriticalContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/critical_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + + // when + err = keyRingTestPublic.VerifyDetached( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + ) + // then + if err == nil || !errors.Is(err, newSignatureFailed()) { + t.Fatalf("Expected a verification error") + } +} + +func Test_VerifyDetachedWithUnKnownNonCriticalContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/non_critical_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + // when + err = keyRingTestPublic.VerifyDetached( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + ) + // then + if err != nil { + t.Fatalf("Expected no verification error, got %v", err) + } +} + +func Test_VerifyDetachedWithKnownCriticalContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/critical_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + verificationContext := NewVerificationContext( + "test-context", + false, + 0, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err != nil { + t.Fatalf("Expected no verification error, got %v", err) + } +} + +func Test_VerifyDetachedWithWrongContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/critical_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + verificationContext := NewVerificationContext( + "another-test-context", + false, + 0, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err == nil || !errors.Is(err, newSignatureFailed()) { + t.Fatalf("Expected a verification error") + } +} + +func Test_VerifyDetachedWithMissingNonRequiredContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/no_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + verificationContext := NewVerificationContext( + "test-context", + false, + 0, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err != nil { + t.Fatalf("Expected no verification error, got %v", err) + } +} + +func Test_VerifyDetachedWithMissingRequiredContext(t *testing.T) { + // given + + signatureArmored, err := ioutil.ReadFile("testdata/signature/no_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + verificationContext := NewVerificationContext( + "test-context", + true, + 0, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err == nil || !errors.Is(err, newSignatureFailed()) { + t.Fatalf("Expected a verification error") + } +} + +func Test_VerifyDetachedWithMissingRequiredContextBeforeCutoff(t *testing.T) { + // given + signatureArmored, err := ioutil.ReadFile("testdata/signature/no_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + p, err := packet.Read(bytes.NewReader(sig.Data)) + if err != nil { + t.Fatal(err) + } + sigPacket, ok := p.(*packet.Signature) + if !ok { + t.Fatal("Packet was not a signature") + } + verificationContext := NewVerificationContext( + "test-context", + true, + sigPacket.CreationTime.Unix()+10000, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err != nil { + t.Fatalf("Expected no verification error, got %v", err) + } +} + +func Test_VerifyDetachedWithMissingRequiredContextAfterCutoff(t *testing.T) { + // given + signatureArmored, err := ioutil.ReadFile("testdata/signature/no_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + p, err := packet.Read(bytes.NewReader(sig.Data)) + if err != nil { + t.Fatal(err) + } + sigPacket, ok := p.(*packet.Signature) + if !ok { + t.Fatal("Packet was not a signature") + } + verificationContext := NewVerificationContext( + "test-context", + true, + sigPacket.CreationTime.Unix()-10000, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err == nil || !errors.Is(err, newSignatureFailed()) { + t.Fatalf("Expected a verification error") + } +} + +func Test_VerifyDetachedWithDoubleContext(t *testing.T) { + // given + signatureArmored, err := ioutil.ReadFile("testdata/signature/double_critical_context_detached_sig") + if err != nil { + t.Fatal(err) + } + sig, err := NewPGPSignatureFromArmored(string(signatureArmored)) + if err != nil { + t.Fatal(err) + } + verificationContext := NewVerificationContext( + "test-context", + true, + 0, + ) + // when + err = keyRingTestPublic.VerifyDetachedWithContext( + NewPlainMessage([]byte(testMessage)), + sig, + 0, + verificationContext, + ) + // then + if err == nil || !errors.Is(err, newSignatureFailed()) { + t.Fatalf("Expected a verification error") + } +} diff --git a/crypto/testdata/signature/critical_context_detached_sig b/crypto/testdata/signature/critical_context_detached_sig new file mode 100644 index 0000000..8f78be2 --- /dev/null +++ b/crypto/testdata/signature/critical_context_detached_sig @@ -0,0 +1,14 @@ +-----BEGIN PGP SIGNATURE----- +Version: GopenPGP 2.5.2 +Comment: https://gopenpgp.org + +wsCaBAABCgBOBQJkBdTjCZA+tiWe3yHfJBYhBG6LoimwzMr2li+XlT62JZ7fId8k +JpSAAAAAABEADGNvbnRleHRAcHJvdG9uLmNodGVzdC1jb250ZXh0AACmMwgAmhVy +MIOgqeidOgNUQrOren3m53sA48dO0xmSRMd1HZa4uv5gDDisl+j98l7iawpvnQ1m +GqMvvrxyCV66h3W1efjGCW8lbGMKjaSZL4iUteRrAYCfsBq2l7yMDqFn+Kqns9f5 +c29eh5mSxiGtmJsGSoJVFw7ZfDS+QpIw1yEsdYcyKLqdxmFS5pNQwY8uGuCrPaya +4iHLP52kGRt9pTSQTf8flwjb1bjTTJ/dOd3C2AVXtH7NmOgtLeLuc2bT6WKJFPwd +BYgCnD0r/6bcRqzqdhcV2lK3WtG1AitH0kKweXhPbtv9OGD36//04zGAeZY7BK8+ +4J2lzLNX+pYtHPbnRw== +=XIJE +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/crypto/testdata/signature/double_critical_context_detached_sig b/crypto/testdata/signature/double_critical_context_detached_sig new file mode 100644 index 0000000..a2338fe --- /dev/null +++ b/crypto/testdata/signature/double_critical_context_detached_sig @@ -0,0 +1,15 @@ +-----BEGIN PGP SIGNATURE----- +Version: GopenPGP 2.5.2 +Comment: https://gopenpgp.org + +wsDDBAABCgB3BQJkBd5+CZA+tiWe3yHfJBYhBG6LoimwzMr2li+XlT62JZ7fId8k +JpSAAAAAABEADGNvbnRleHRAcHJvdG9uLmNodGVzdC1jb250ZXh0KJSAAAAAABEA +DmNvbnRleHRAcHJvdG9uLmNodGVzdC1jb250ZXh0LTIAAGnMB/9Z9Bd0z9Q6gvBB +xxh2v/p9PleBwytUbUMaPrL4gzRsfsKF/9kShY9ZCYpzFeTVtTHGG2C8rEiCPLev +01xr3wxVq5N8iyyF9H839qwsAKomkNrqpuAtHHF76uE/vpnqRLQ+2eCiTyOh/BSH +syizwNBRaYeVtabZVXGW5ofWFoq/sgmO4Pr63hPiTmhFbIWDOZVadN1rHOVaLBPW +mlcxb7vK2FUdcyIpwsQMH9ReDNe2FiCLy/lTWyKFYO43/6VnzHtd5Gn1MXLm2tuN +zMTEGii2WFPH8u0e6sQCcmkhJS44J8y2MFv6BKrZKcRpZfzOLPzktBvFg7Q9Pvo3 +1qxluPe5 +=KiYX +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/crypto/testdata/signature/no_context_detached_sig b/crypto/testdata/signature/no_context_detached_sig new file mode 100644 index 0000000..d4ed61a --- /dev/null +++ b/crypto/testdata/signature/no_context_detached_sig @@ -0,0 +1,13 @@ +-----BEGIN PGP SIGNATURE----- +Version: GopenPGP 2.5.2 +Comment: https://gopenpgp.org + +wsBzBAABCgAnBQJkBdkOCZA+tiWe3yHfJBYhBG6LoimwzMr2li+XlT62JZ7fId8k +AAAwOgf/U+wgABHyfI6Bd/1xPdUyy3FTaEY+Nj8NYi/PKez66OmLubgMEj0DfD7M +2P4SL3ZR0Y9iEtCKpncvLtlvA0sss0SZMaXH0bpJZS62cc98gLBuhE9mP1aWUu1u ++1AKVIvJKzhJC+MjKrVwMO03JrEb97ZDJylqoF2UvTeQomIY6qo5l4khDeZRVgsn +wqmq7+FLGHG75bhrW4dSOCKrNdKwodml/3l4/R8OPhRL6882egXfBtF0i0yhnX2s +4watN2OKQE8b9gfkrDWp0vA/hLLXx8IdIiuAkj55Dj6ciVXy6fTfKqcK4/IIX4MO +y5KD4MLQbmTja5KoK82mavsbhwXM7A== +=k4Xq +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/crypto/testdata/signature/non_critical_context_detached_sig b/crypto/testdata/signature/non_critical_context_detached_sig new file mode 100644 index 0000000..b07970e --- /dev/null +++ b/crypto/testdata/signature/non_critical_context_detached_sig @@ -0,0 +1,14 @@ +-----BEGIN PGP SIGNATURE----- +Version: GopenPGP 2.5.2 +Comment: https://gopenpgp.org + +wsCaBAABCgBOBQJkBdcDCZA+tiWe3yHfJBYhBG6LoimwzMr2li+XlT62JZ7fId8k +JhSAAAAAABEADGNvbnRleHRAcHJvdG9uLmNodGVzdC1jb250ZXh0AAAWswgAmtfD +vf7yNlc2umZ4p8ddlcQGhkpwQgiTuaYIeJytAytPtzzSAuMUcACeBCXCTt9iXaak +ImnZULdBW6T5n/o5zVTVO5yGniOeswpXqERnp+Qmsowjd5fU+XRBnkx0cSVIrVo5 +tB4gf5nxAnojusQekELnNINd8nXrWYHiDFM+aos+pTxqzWlcJv32LtQ4yuxWSzIL +9dJMIpqL+1jk2QI6E+6iTM6NkwNhYjJ7emMGJXyzPmXj4pmpJ1lYo50uHRlwirnI +VXcOkUKUwGdibnCjUv+XFoG7Qv2ilDuk/TxTKSjW7ajGjv6KAOde/pOtmpiwcWKi +OzIkiswXw5vOtLkrew== +=Ub8I +-----END PGP SIGNATURE----- \ No newline at end of file