This commit updates to images v0.117.0 so that the cross-distro.sh test works again (images removed fedora-39.json in main but the uses the previous version of images that includes fedora-39 so there is a mismatch (we should look into if there is a way to get github.com/osbuild/images@latest instead of main in the cross-arch test). It also updates all the vendor stuff that got pulled via the new images release (which is giantonormous). This update requires updating the Go version to 1.22.8
365 lines
12 KiB
Go
365 lines
12 KiB
Go
package pkcs7
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Verify is a wrapper around VerifyWithChain() that initializes an empty
|
|
// trust store, effectively disabling certificate verification when validating
|
|
// a signature.
|
|
func (p7 *PKCS7) Verify() (err error) {
|
|
return p7.VerifyWithChain(nil)
|
|
}
|
|
|
|
// VerifyWithChain checks the signatures of a PKCS7 object.
|
|
//
|
|
// If truststore is not nil, it also verifies the chain of trust of
|
|
// the end-entity signer cert to one of the roots in the
|
|
// truststore. When the PKCS7 object includes the signing time
|
|
// authenticated attr verifies the chain at that time and UTC now
|
|
// otherwise.
|
|
func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) {
|
|
if len(p7.Signers) == 0 {
|
|
return errors.New("pkcs7: Message has no signers")
|
|
}
|
|
for _, signer := range p7.Signers {
|
|
if err := verifySignature(p7, signer, truststore); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VerifyWithChainAtTime checks the signatures of a PKCS7 object.
|
|
//
|
|
// If truststore is not nil, it also verifies the chain of trust of
|
|
// the end-entity signer cert to a root in the truststore at
|
|
// currentTime. It does not use the signing time authenticated
|
|
// attribute.
|
|
func (p7 *PKCS7) VerifyWithChainAtTime(truststore *x509.CertPool, currentTime time.Time) (err error) {
|
|
if len(p7.Signers) == 0 {
|
|
return errors.New("pkcs7: Message has no signers")
|
|
}
|
|
for _, signer := range p7.Signers {
|
|
if err := verifySignatureAtTime(p7, signer, truststore, currentTime); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SigningTimeNotValidError is returned when the signing time attribute
|
|
// falls outside of the signer certificate validity.
|
|
type SigningTimeNotValidError struct {
|
|
SigningTime time.Time
|
|
NotBefore time.Time // NotBefore of signer
|
|
NotAfter time.Time // NotAfter of signer
|
|
}
|
|
|
|
func (e *SigningTimeNotValidError) Error() string {
|
|
return fmt.Sprintf("pkcs7: signing time %q is outside of certificate validity %q to %q",
|
|
e.SigningTime.Format(time.RFC3339),
|
|
e.NotBefore.Format(time.RFC3339),
|
|
e.NotAfter.Format(time.RFC3339))
|
|
}
|
|
|
|
func verifySignatureAtTime(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool, currentTime time.Time) (err error) {
|
|
signedData := p7.Content
|
|
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
|
if ee == nil {
|
|
return errors.New("pkcs7: No certificate for signer")
|
|
}
|
|
if len(signer.AuthenticatedAttributes) > 0 {
|
|
// TODO(fullsailor): First check the content type match
|
|
var (
|
|
digest []byte
|
|
signingTime time.Time
|
|
)
|
|
err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h := hash.New()
|
|
h.Write(p7.Content)
|
|
computed := h.Sum(nil)
|
|
if subtle.ConstantTimeCompare(digest, computed) != 1 {
|
|
return &MessageDigestMismatchError{
|
|
ExpectedDigest: digest,
|
|
ActualDigest: computed,
|
|
}
|
|
}
|
|
signedData, err = marshalAttributes(signer.AuthenticatedAttributes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime)
|
|
if err == nil {
|
|
// signing time found, performing validity check
|
|
if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) {
|
|
return &SigningTimeNotValidError{
|
|
SigningTime: signingTime,
|
|
NotBefore: ee.NotBefore,
|
|
NotAfter: ee.NotAfter,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if truststore != nil {
|
|
_, err = verifyCertChain(ee, p7.Certificates, truststore, currentTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
|
|
}
|
|
|
|
func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) {
|
|
signedData := p7.Content
|
|
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
|
if ee == nil {
|
|
return errors.New("pkcs7: No certificate for signer")
|
|
}
|
|
signingTime := time.Now().UTC()
|
|
if len(signer.AuthenticatedAttributes) > 0 {
|
|
// TODO(fullsailor): First check the content type match
|
|
var digest []byte
|
|
err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h := hash.New()
|
|
h.Write(p7.Content)
|
|
computed := h.Sum(nil)
|
|
if subtle.ConstantTimeCompare(digest, computed) != 1 {
|
|
return &MessageDigestMismatchError{
|
|
ExpectedDigest: digest,
|
|
ActualDigest: computed,
|
|
}
|
|
}
|
|
signedData, err = marshalAttributes(signer.AuthenticatedAttributes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime)
|
|
if err == nil {
|
|
// signing time found, performing validity check
|
|
if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) {
|
|
return &SigningTimeNotValidError{
|
|
SigningTime: signingTime,
|
|
NotBefore: ee.NotBefore,
|
|
NotAfter: ee.NotAfter,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if truststore != nil {
|
|
_, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
|
|
}
|
|
|
|
// GetOnlySigner returns an x509.Certificate for the first signer of the signed
|
|
// data payload. If there are more or less than one signer, nil is returned
|
|
func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {
|
|
if len(p7.Signers) != 1 {
|
|
return nil
|
|
}
|
|
signer := p7.Signers[0]
|
|
return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
|
}
|
|
|
|
// UnmarshalSignedAttribute decodes a single attribute from the signer info
|
|
func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {
|
|
sd, ok := p7.raw.(signedData)
|
|
if !ok {
|
|
return errors.New("pkcs7: payload is not signedData content")
|
|
}
|
|
if len(sd.SignerInfos) < 1 {
|
|
return errors.New("pkcs7: payload has no signers")
|
|
}
|
|
attributes := sd.SignerInfos[0].AuthenticatedAttributes
|
|
return unmarshalAttribute(attributes, attributeType, out)
|
|
}
|
|
|
|
func parseSignedData(data []byte) (*PKCS7, error) {
|
|
var sd signedData
|
|
asn1.Unmarshal(data, &sd)
|
|
certs, err := sd.Certificates.Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// fmt.Printf("--> Signed Data Version %d\n", sd.Version)
|
|
|
|
var compound asn1.RawValue
|
|
var content unsignedData
|
|
|
|
// The Content.Bytes maybe empty on PKI responses.
|
|
if len(sd.ContentInfo.Content.Bytes) > 0 {
|
|
if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Compound octet string
|
|
if compound.IsCompound {
|
|
if compound.Tag == 4 {
|
|
for len(compound.Bytes) > 0 {
|
|
var cdata asn1.RawValue
|
|
if _, err = asn1.Unmarshal(compound.Bytes, &cdata); err != nil {
|
|
return nil, err
|
|
}
|
|
content = append(content, cdata.Bytes...)
|
|
compound.Bytes = compound.Bytes[len(cdata.FullBytes):]
|
|
}
|
|
} else {
|
|
content = compound.Bytes
|
|
}
|
|
} else {
|
|
// assuming this is tag 04
|
|
content = compound.Bytes
|
|
}
|
|
return &PKCS7{
|
|
Content: content,
|
|
Certificates: certs,
|
|
CRLs: sd.CRLs,
|
|
Signers: sd.SignerInfos,
|
|
raw: sd}, nil
|
|
}
|
|
|
|
// verifyCertChain takes an end-entity certs, a list of potential intermediates and a
|
|
// truststore, and built all potential chains between the EE and a trusted root.
|
|
//
|
|
// When verifying chains that may have expired, currentTime can be set to a past date
|
|
// to allow the verification to pass. If unset, currentTime is set to the current UTC time.
|
|
func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) {
|
|
intermediates := x509.NewCertPool()
|
|
for _, intermediate := range certs {
|
|
intermediates.AddCert(intermediate)
|
|
}
|
|
verifyOptions := x509.VerifyOptions{
|
|
Roots: truststore,
|
|
Intermediates: intermediates,
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
CurrentTime: currentTime,
|
|
}
|
|
chains, err = ee.Verify(verifyOptions)
|
|
if err != nil {
|
|
return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// MessageDigestMismatchError is returned when the signer data digest does not
|
|
// match the computed digest for the contained content
|
|
type MessageDigestMismatchError struct {
|
|
ExpectedDigest []byte
|
|
ActualDigest []byte
|
|
}
|
|
|
|
func (err *MessageDigestMismatchError) Error() string {
|
|
return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)
|
|
}
|
|
|
|
func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) {
|
|
switch {
|
|
case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1):
|
|
return x509.ECDSAWithSHA1, nil
|
|
case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256):
|
|
return x509.ECDSAWithSHA256, nil
|
|
case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384):
|
|
return x509.ECDSAWithSHA384, nil
|
|
case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512):
|
|
return x509.ECDSAWithSHA512, nil
|
|
case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512):
|
|
switch {
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1), digest.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1):
|
|
return x509.SHA1WithRSA, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256), digest.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256):
|
|
return x509.SHA256WithRSA, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384), digest.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384):
|
|
return x509.SHA384WithRSA, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512), digest.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512):
|
|
return x509.SHA512WithRSA, nil
|
|
default:
|
|
return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",
|
|
digest.Algorithm.String(), digestEncryption.Algorithm.String())
|
|
}
|
|
case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA),
|
|
digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1):
|
|
switch {
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1):
|
|
return x509.DSAWithSHA1, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256):
|
|
return x509.DSAWithSHA256, nil
|
|
default:
|
|
return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",
|
|
digest.Algorithm.String(), digestEncryption.Algorithm.String())
|
|
}
|
|
case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384),
|
|
digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521):
|
|
switch {
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1):
|
|
return x509.ECDSAWithSHA1, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256):
|
|
return x509.ECDSAWithSHA256, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384):
|
|
return x509.ECDSAWithSHA384, nil
|
|
case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512):
|
|
return x509.ECDSAWithSHA512, nil
|
|
default:
|
|
return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",
|
|
digest.Algorithm.String(), digestEncryption.Algorithm.String())
|
|
}
|
|
default:
|
|
return -1, fmt.Errorf("pkcs7: unsupported algorithm %q",
|
|
digestEncryption.Algorithm.String())
|
|
}
|
|
}
|
|
|
|
func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {
|
|
for _, cert := range certs {
|
|
if isCertMatchForIssuerAndSerial(cert, ias) {
|
|
return cert
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {
|
|
for _, attr := range attrs {
|
|
if attr.Type.Equal(attributeType) {
|
|
_, err := asn1.Unmarshal(attr.Value.Bytes, out)
|
|
return err
|
|
}
|
|
}
|
|
return errors.New("pkcs7: attribute type not in attributes")
|
|
}
|