Skip to content

Commit a9c0e0a

Browse files
committed
feat(v2): Allow to include external signatures in messages
1 parent 55193c9 commit a9c0e0a

File tree

1 file changed

+95
-17
lines changed

1 file changed

+95
-17
lines changed

openpgp/v2/write.go

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package v2
66

77
import (
8+
"bytes"
89
"crypto"
10+
goerrors "errors"
911
"hash"
1012
"io"
1113
"strconv"
@@ -221,6 +223,11 @@ type EncryptParams struct {
221223
// SessionKey provides a session key to be used for encryption.
222224
// If nil, a one-time session key is generated
223225
SessionKey []byte
226+
// OutsideSig allows to set a signature that should be included
227+
// in the message to encrypt.
228+
// Should only be used for exceptional cases.
229+
// If nil, ignored.
230+
OutsideSig []byte
224231
// Config provides the config to be used.
225232
// If Config is nil, sensible defaults will be used.
226233
Config *packet.Config
@@ -383,8 +390,46 @@ func Encrypt(ciphertext io.Writer, to, toHidden []*Entity, signers []*Entity, hi
383390
// that aids the recipients in processing the message. The resulting
384391
// WriteCloser must be closed after the contents of the file have been
385392
// written. If config is nil, sensible defaults will be used.
386-
func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntities []*Entity, hints *FileHints, sigType packet.SignatureType, intendedRecipients []*packet.Recipient, config *packet.Config) (plaintext io.WriteCloser, err error) {
393+
func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntities []*Entity, hints *FileHints, sigType packet.SignatureType, intendedRecipients []*packet.Recipient, outsideSig []byte, config *packet.Config) (plaintext io.WriteCloser, err error) {
387394
var signers []*signatureContext
395+
var numberOfOutsideSigs int
396+
397+
if outsideSig != nil {
398+
outSigPacket, err := parseOutsideSig(outsideSig)
399+
if err != nil {
400+
return nil, err
401+
}
402+
opsVersion := 3
403+
if outSigPacket.Version == 6 {
404+
opsVersion = 6
405+
}
406+
opsOutside := &packet.OnePassSignature{
407+
Version: opsVersion,
408+
SigType: outSigPacket.SigType,
409+
Hash: outSigPacket.Hash,
410+
PubKeyAlgo: outSigPacket.PubKeyAlgo,
411+
KeyId: *outSigPacket.IssuerKeyId,
412+
IsLast: len(signEntities) == 0,
413+
}
414+
sigContext := signatureContext{
415+
outsideSig: outSigPacket,
416+
}
417+
if outSigPacket.Version == 6 {
418+
opsOutside.KeyFingerprint = outSigPacket.IssuerFingerprint
419+
sigContext.salt = outSigPacket.Salt()
420+
opsOutside.Salt = outSigPacket.Salt()
421+
}
422+
sigContext.h, sigContext.wrappedHash, err = hashForSignature(outSigPacket.Hash, sigType, sigContext.salt)
423+
if err != nil {
424+
return nil, err
425+
}
426+
if err := opsOutside.Serialize(payload); err != nil {
427+
return nil, err
428+
}
429+
signers = append([]*signatureContext{&sigContext}, signers...)
430+
numberOfOutsideSigs = 1
431+
}
432+
388433
for signEntityIdx, signEntity := range signEntities {
389434
if signEntity == nil {
390435
continue
@@ -442,7 +487,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie
442487
signers = append([]*signatureContext{&sigContext}, signers...)
443488
}
444489

445-
if signEntities != nil && len(signers) < 1 {
490+
if signEntities != nil && len(signEntities)+numberOfOutsideSigs != len(signers) {
446491
return nil, errors.InvalidArgumentError("no valid signing key")
447492
}
448493

@@ -451,7 +496,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie
451496
}
452497

453498
w := payload
454-
if signers != nil {
499+
if signers != nil || numberOfOutsideSigs > 0 {
455500
// If we need to write a signature packet after the literal
456501
// data then we need to stop literalData from closing
457502
// encryptedData.
@@ -467,7 +512,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie
467512
return nil, err
468513
}
469514

470-
if signers != nil {
515+
if signers != nil || numberOfOutsideSigs > 0 {
471516
metadata := &packet.LiteralData{
472517
Format: 'b',
473518
FileName: hints.FileName,
@@ -640,7 +685,7 @@ func encryptDataAndSign(
640685
if err != nil {
641686
return nil, err
642687
}
643-
return writeAndSign(payload, candidateHashes, params.Signers, params.Hints, sigType, intendedRecipients, params.Config)
688+
return writeAndSign(payload, candidateHashes, params.Signers, params.Hints, sigType, intendedRecipients, params.OutsideSig, params.Config)
644689
}
645690

646691
type SignParams struct {
@@ -649,6 +694,11 @@ type SignParams struct {
649694
Hints *FileHints
650695
// TextSig indicates if signatures of type SigTypeText should be produced
651696
TextSig bool
697+
// OutsideSig allows to set a signature that should be included
698+
// in an inline signed message.
699+
// Should only be used for exceptional cases.
700+
// If nil, ignored.
701+
OutsideSig []byte
652702
// Config provides the config to be used.
653703
// If Config is nil, sensible defaults will be used.
654704
Config *packet.Config
@@ -661,7 +711,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in
661711
if params == nil {
662712
params = &SignParams{}
663713
}
664-
if len(signers) < 1 {
714+
if len(signers) < 1 && params.OutsideSig == nil {
665715
return nil, errors.InvalidArgumentError("no signer provided")
666716
}
667717
var candidateHashesPerSignature [][]uint8
@@ -708,7 +758,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in
708758
if err != nil {
709759
return nil, err
710760
}
711-
return writeAndSign(payload, candidateHashesPerSignature, signers, params.Hints, sigType, nil, params.Config)
761+
return writeAndSign(payload, candidateHashesPerSignature, signers, params.Hints, sigType, nil, params.OutsideSig, params.Config)
712762
}
713763

714764
// Sign signs a message. The resulting WriteCloser must be closed after the
@@ -742,6 +792,7 @@ type signatureContext struct {
742792
h hash.Hash
743793
salt []byte // v6 only
744794
signer *packet.PrivateKey
795+
outsideSig *packet.Signature
745796
}
746797

747798
func (s signatureWriter) Write(data []byte) (int, error) {
@@ -764,16 +815,21 @@ func (s signatureWriter) Close() error {
764815
return err
765816
}
766817
for _, ctx := range s.signatureContexts {
767-
sig := createSignaturePacket(&ctx.signer.PublicKey, s.sigType, s.config)
768-
sig.Hash = ctx.hashType
769-
sig.Metadata = s.metadata
770-
sig.IntendedRecipients = s.intendedRecipients
771-
772-
if err := sig.SetSalt(ctx.salt); err != nil {
773-
return err
774-
}
775-
if err := sig.Sign(ctx.h, ctx.signer, s.config); err != nil {
776-
return err
818+
var sig *packet.Signature
819+
if ctx.outsideSig != nil {
820+
// Signature that was supplied outside
821+
sig = ctx.outsideSig
822+
} else {
823+
sig = createSignaturePacket(&ctx.signer.PublicKey, s.sigType, s.config)
824+
sig.Hash = ctx.hashType
825+
sig.Metadata = s.metadata
826+
sig.IntendedRecipients = s.intendedRecipients
827+
if err := sig.SetSalt(ctx.salt); err != nil {
828+
return err
829+
}
830+
if err := sig.Sign(ctx.h, ctx.signer, s.config); err != nil {
831+
return err
832+
}
777833
}
778834
if err := sig.Serialize(s.encryptedData); err != nil {
779835
return err
@@ -872,3 +928,25 @@ func selectHash(candidateHashes []byte, configuredHash crypto.Hash) (hash crypto
872928
}
873929
return
874930
}
931+
932+
func parseOutsideSig(outsideSig []byte) (outSigPacket *packet.Signature, err error) {
933+
var p packet.Packet
934+
packets := packet.NewReader(bytes.NewReader(outsideSig))
935+
p, err = packets.Next()
936+
if goerrors.Is(err, io.EOF) {
937+
return nil, errors.ErrUnknownIssuer
938+
}
939+
if err != nil {
940+
return nil, err
941+
}
942+
943+
var ok bool
944+
outSigPacket, ok = p.(*packet.Signature)
945+
if !ok {
946+
return nil, errors.StructuralError("non signature packet found")
947+
}
948+
if outSigPacket.IssuerKeyId == nil {
949+
return nil, errors.StructuralError("signature doesn't have an issuer")
950+
}
951+
return outSigPacket, nil
952+
}

0 commit comments

Comments
 (0)