peridot/vendor/github.com/cavaliergopher/rpm/signature.go
2022-07-07 22:13:21 +02:00

204 lines
5.3 KiB
Go

package rpm
import (
"bytes"
"crypto/md5"
"fmt"
"io"
"os"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
)
var (
// ErrMD5CheckFailed indicates that an rpm package failed MD5 checksum
// validation.
ErrMD5CheckFailed = fmt.Errorf("MD5 checksum validation failed")
// ErrGPGCheckFailed indicates that an rpm package failed GPG signature
// validation.
ErrGPGCheckFailed = fmt.Errorf("GPG signature validation failed")
)
// in order of precedence
var gpgTags = []int{
1002, // RPMSIGTAG_PGP
1006, // RPMSIGTAG_PGP5
1005, // RPMSIGTAG_GPG
}
// see: https://github.com/rpm-software-management/rpm/blob/3b1f4b0c6c9407b08620a5756ce422df10f6bd1a/rpmio/rpmpgp.c#L51
var gpgPubkeyTbl = map[packet.PublicKeyAlgorithm]string{
packet.PubKeyAlgoRSA: "RSA",
packet.PubKeyAlgoRSASignOnly: "RSA(Sign-Only)",
packet.PubKeyAlgoRSAEncryptOnly: "RSA(Encrypt-Only)",
packet.PubKeyAlgoElGamal: "Elgamal",
packet.PubKeyAlgoDSA: "DSA",
packet.PubKeyAlgoECDH: "Elliptic Curve",
packet.PubKeyAlgoECDSA: "ECDSA",
}
// Map Go hashes to rpm info name
// See: https://golang.org/src/crypto/crypto.go?s=#L23
// https://github.com/rpm-software-management/rpm/blob/3b1f4b0c6c9407b08620a5756ce422df10f6bd1a/rpmio/rpmpgp.c#L88
var gpgHashTbl = []string{
"Unknown hash algorithm",
"MD4",
"MD5",
"SHA1",
"SHA224",
"SHA256",
"SHA384",
"SHA512",
"MD5SHA1",
"RIPEMD160",
"SHA3_224",
"SHA3_256",
"SHA3_384",
"SHA3_512",
"SHA512_224",
"SHA512_256",
}
// GPGSignature is the raw byte representation of a package's signature.
type GPGSignature []byte
func (b GPGSignature) String() string {
pkt, err := packet.Read(bytes.NewReader(b))
if err != nil {
return ""
}
switch sig := pkt.(type) {
case *packet.SignatureV3:
algo, ok := gpgPubkeyTbl[sig.PubKeyAlgo]
if !ok {
algo = "Unknown public key algorithm"
}
hasher := gpgHashTbl[0]
if int(sig.Hash) < len(gpgHashTbl) {
hasher = gpgHashTbl[sig.Hash]
}
ctime := sig.CreationTime.UTC().Format(TimeFormat)
return fmt.Sprintf("%v/%v, %v, Key ID %x", algo, hasher, ctime, sig.IssuerKeyId)
}
return ""
}
// readSigHeader reads the lead and signature header of a rpm package and stops
// the reader at the beginning of the header header.
func readSigHeader(r io.Reader) (*Header, error) {
lead, err := readLead(r)
if err != nil {
return nil, err
}
if lead.SignatureType != 5 { // RPMSIGTYPE_HEADERSIG
return nil, errorf("unknown signature type: %x", lead.SignatureType)
}
sig, err := readHeader(r, true)
if err != nil {
return nil, err
}
return sig, nil
}
// GPGCheck validates the integrity of an rpm package file. Public keys in the
// given keyring are used to validate the package signature.
//
// If validation fails, ErrGPGCheckFailed is returned.
func GPGCheck(r io.Reader, keyring openpgp.KeyRing) (string, error) {
sig, err := readSigHeader(r)
if err != nil {
return "", err
}
var sigval []byte
for _, tag := range gpgTags {
if sigval = sig.GetTag(tag).Bytes(); sigval != nil {
break
}
}
if sigval == nil {
return "", errorf("package signature not found")
}
signer, err := openpgp.CheckDetachedSignature(keyring, r, bytes.NewReader(sigval))
if err == errors.ErrUnknownIssuer {
return "", ErrGPGCheckFailed
} else if err != nil {
return "", err
}
for id := range signer.Identities {
return id, nil
}
return "", errorf("no identity found in public key")
}
// MD5Check validates the integrity of an rpm package file. The MD5 checksum is
// computed for the package payload and compared with the checksum specified in
// the package header.
//
// If validation fails, ErrMD5CheckFailed is returned.
func MD5Check(r io.Reader) error {
sigheader, err := readSigHeader(r)
if err != nil {
return err
}
payloadSize := sigheader.GetTag(1000).Int64() // RPMSIGTAG_SIZE
if payloadSize == 0 {
return errorf("tag not found: RPMSIGTAG_SIZE")
}
expect := sigheader.GetTag(1004).Bytes() // RPMSIGTAG_MD5
if expect == nil {
return errorf("tag not found: RPMSIGTAG_MD5")
}
h := md5.New()
if n, err := io.Copy(h, r); err != nil {
return err
} else if n != payloadSize {
return ErrMD5CheckFailed
}
actual := h.Sum(nil)
if !bytes.Equal(expect, actual) {
return ErrMD5CheckFailed
}
return nil
}
// ReadKeyRing reads a openpgp.KeyRing from the given io.Reader which may then
// be used to validate GPG keys in rpm packages.
func ReadKeyRing(r io.Reader) (openpgp.KeyRing, error) {
// decode gpgkey file
p, err := armor.Decode(r)
if err != nil {
return nil, err
}
// extract keys
return openpgp.ReadKeyRing(p.Body)
}
func openKeyRing(name string) (openpgp.KeyRing, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return ReadKeyRing(f)
}
// KeyRingFromFiles reads a openpgp.KeyRing from the given file paths which may
// then be used to validate GPG keys in rpm packages.
//
// This function might typically be used to read all keys in /etc/pki/rpm-gpg.
func OpenKeyRing(name ...string) (openpgp.KeyRing, error) {
entityList := make(openpgp.EntityList, 0)
for _, path := range name {
keyring, err := openKeyRing(path)
if err != nil {
return nil, err
}
entityList = append(entityList, keyring.(openpgp.EntityList)...)
}
return entityList, nil
}