package pgp import ( "bytes" "crypto" "crypto/dsa" //nolint:staticcheck "crypto/ecdsa" "crypto/rsa" "hash" "io" "strconv" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/packet" ) // hashForSignature returns a pair of hashes that can be used to verify a // signature. The signature may specify that the contents of the signed message // should be preprocessed (i.e. to normalize line endings). Thus this function // returns two hashes. The second should be used to hash the message itself and // performs any needed preprocessing. func hashForSignature(hashID crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) { if !hashID.Available() { return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashID))) } h := hashID.New() switch sigType { case packet.SigTypeBinary: return h, h, nil case packet.SigTypeText: return h, openpgp.NewCanonicalTextHash(h), nil } return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) } type signatureResult struct { CreationTime time.Time IssuerKeyID uint64 PubKeyAlgo packet.PublicKeyAlgorithm Entity *openpgp.Entity } // checkDetachedSignature takes a signed file and a detached signature and // returns the signer if the signature is valid. If the signer isn't known, // ErrUnknownIssuer is returned. // // This is extended version of golang.org/x/crypto/openpgp to support multiple signers and returns multiple // signers, plus keeps track of "missing" keys func checkDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader) (signers []signatureResult, missingKeys int, err error) { var p packet.Packet signedBuf := &bytes.Buffer{} if _, e := io.Copy(signedBuf, signed); e != nil && e != io.EOF { return nil, 0, e } packets := packet.NewReader(signature) for { p, err = packets.Next() if err == io.EOF { if len(signers) == 0 || missingKeys > 0 { err = errors.ErrUnknownIssuer } else { err = nil } return } if err != nil { return nil, 0, err } var issuerKeyID uint64 var hashFunc crypto.Hash var sigType packet.SignatureType var creationTime time.Time var pubKeyAlgo packet.PublicKeyAlgorithm var keys []openpgp.Key switch sig := p.(type) { case *packet.Signature: if sig.IssuerKeyId == nil { return nil, 0, errors.StructuralError("signature doesn't have an issuer") } issuerKeyID = *sig.IssuerKeyId hashFunc = sig.Hash sigType = sig.SigType creationTime = sig.CreationTime pubKeyAlgo = sig.PubKeyAlgo default: return nil, 0, errors.StructuralError("non signature packet found") } keys = keyring.KeysByIdUsage(issuerKeyID, packet.KeyFlagSign) if len(keys) == 0 { signers = append(signers, signatureResult{ CreationTime: creationTime, IssuerKeyID: issuerKeyID, PubKeyAlgo: pubKeyAlgo, }) missingKeys++ continue } h, wrappedHash, err := hashForSignature(hashFunc, sigType) if err != nil { return nil, 0, err } if _, e := io.Copy(wrappedHash, bytes.NewReader(signedBuf.Bytes())); e != nil && e != io.EOF { return nil, 0, e } allFailed := true for _, key := range keys { switch sig := p.(type) { case *packet.Signature: err = key.PublicKey.VerifySignature(h, sig) default: panic("unreachable") } if err == nil { signers = append(signers, signatureResult{ CreationTime: creationTime, IssuerKeyID: issuerKeyID, PubKeyAlgo: pubKeyAlgo, Entity: key.Entity, }) allFailed = false } } if allFailed { return nil, 0, err } } } // readArmored reads an armored block with the given type. func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) { block, err := armor.Decode(r) if err != nil { return } if block.Type != expectedType { return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type) } return block.Body, nil } // checkArmoredDetachedSignature performs the same actions as // CheckDetachedSignature but expects the signature to be armored. // // This is extended version of golang.org/x/crypto/openpgp to support multiple signers and returns multiple // signers, plus keeps track of "missing" keys func checkArmoredDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader) (signers []signatureResult, missingKeys int, err error) { body, err := readArmored(signature, openpgp.SignatureType) if err != nil { return } return checkDetachedSignature(keyring, signed, body) } func pubkeyAlgorithmName(algorithm packet.PublicKeyAlgorithm) string { switch algorithm { case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoRSASignOnly: return "RSA" case packet.PubKeyAlgoElGamal: return "ElGamal" case packet.PubKeyAlgoDSA: return "DSA" case packet.PubKeyAlgoECDH: return "EDCH" case packet.PubKeyAlgoECDSA: return "ECDSA" case packet.PubKeyAlgoEdDSA: return "EdDSA" } return "unknown" } func keyBits(key interface{}) string { switch k := key.(type) { case *rsa.PublicKey: return strconv.Itoa(k.N.BitLen()) case *dsa.PublicKey: return strconv.Itoa(k.P.BitLen()) case *ecdsa.PublicKey: return strconv.Itoa(k.Curve.Params().BitSize) default: return "?" } } func validEntity(entity *openpgp.Entity) bool { var selfSig *packet.Signature for _, ident := range entity.Identities { if selfSig == nil { selfSig = ident.SelfSignature } else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId { selfSig = ident.SelfSignature break } } if selfSig == nil { return false } if len(entity.Revocations) > 0 { return false } if selfSig.RevocationReason != nil { return false } if !selfSig.FlagsValid { return false } if selfSig.KeyLifetimeSecs != nil && selfSig.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs)*time.Second).Before(time.Now()) { return false } return true }