mirror of https://github.com/aptly-dev/aptly
238 lines
6.0 KiB
Go
238 lines
6.0 KiB
Go
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
|
|
}
|