Instead of swallowing the cause of verification errors, we use error wrapping to communicate the cause to the caller.
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"mime"
|
|
"net/textproto"
|
|
|
|
pgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
"github.com/ProtonMail/gopenpgp/v2/internal"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
gomime "github.com/ProtonMail/go-mime"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// SignatureCollector structure.
|
|
type SignatureCollector struct {
|
|
config *packet.Config
|
|
keyring openpgp.KeyRing
|
|
target gomime.VisitAcceptor
|
|
signature string
|
|
verified error
|
|
}
|
|
|
|
func newSignatureCollector(
|
|
targetAcceptor gomime.VisitAcceptor, keyring openpgp.KeyRing, config *packet.Config,
|
|
) *SignatureCollector {
|
|
return &SignatureCollector{
|
|
target: targetAcceptor,
|
|
config: config,
|
|
keyring: keyring,
|
|
}
|
|
}
|
|
|
|
// Accept collects the signature.
|
|
func (sc *SignatureCollector) Accept(
|
|
part io.Reader, header textproto.MIMEHeader,
|
|
hasPlainSibling, isFirst, isLast bool,
|
|
) (err error) {
|
|
parentMediaType, params, _ := mime.ParseMediaType(header.Get("Content-Type"))
|
|
|
|
if parentMediaType != "multipart/signed" {
|
|
sc.verified = newSignatureNotSigned()
|
|
return sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast)
|
|
}
|
|
|
|
newPart, rawBody := gomime.GetRawMimePart(part, "--"+params["boundary"])
|
|
multiparts, multipartHeaders, err := gomime.GetMultipartParts(newPart, params)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
hasPlainChild := false
|
|
for _, header := range multipartHeaders {
|
|
mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type"))
|
|
hasPlainChild = (mediaType == "text/plain")
|
|
}
|
|
if len(multiparts) != 2 {
|
|
sc.verified = newSignatureNotSigned()
|
|
// Invalid multipart/signed format just pass along
|
|
if _, err = ioutil.ReadAll(rawBody); err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in reading raw message body")
|
|
}
|
|
|
|
for i, p := range multiparts {
|
|
if err = sc.target.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// actual multipart/signed format
|
|
err = sc.target.Accept(multiparts[0], multipartHeaders[0], hasPlainChild, true, true)
|
|
if err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in parsing body")
|
|
}
|
|
|
|
partData, err := ioutil.ReadAll(multiparts[1])
|
|
if err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in ready part data")
|
|
}
|
|
|
|
decodedPart := gomime.DecodeContentEncoding(
|
|
bytes.NewReader(partData),
|
|
multipartHeaders[1].Get("Content-Transfer-Encoding"))
|
|
|
|
buffer, err := ioutil.ReadAll(decodedPart)
|
|
if err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in reading decoded data")
|
|
}
|
|
mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type"))
|
|
buffer, err = gomime.DecodeCharset(buffer, mediaType, params)
|
|
if err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in decoding charset")
|
|
}
|
|
sc.signature = string(buffer)
|
|
str, _ := ioutil.ReadAll(rawBody)
|
|
canonicalizedBody := internal.Canonicalize(internal.TrimEachLine(string(str)))
|
|
rawBody = bytes.NewReader([]byte(canonicalizedBody))
|
|
if sc.keyring != nil {
|
|
_, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), sc.config)
|
|
|
|
switch {
|
|
case err == nil:
|
|
sc.verified = nil
|
|
case errors.Is(err, pgpErrors.ErrUnknownIssuer):
|
|
sc.verified = newSignatureNoVerifier()
|
|
default:
|
|
sc.verified = newSignatureFailed(err)
|
|
}
|
|
} else {
|
|
sc.verified = newSignatureNoVerifier()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSignature collected by Accept.
|
|
func (sc SignatureCollector) GetSignature() string {
|
|
return sc.signature
|
|
}
|