package pkcs7

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"errors"
	"fmt"
	"math/big"
	"time"

	"golang.org/x/crypto/cryptobyte"
	"golang.org/x/crypto/cryptobyte/asn1"

	encasn1 "encoding/asn1"
)

// OID data we need
var (
	OIDData                        = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
	OIDSignedData                  = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
	OIDDigestAlgorithmSHA256       = encasn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
	OIDDigestAlgorithmSHA384       = encasn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
	OIDDigestAlgorithmSHA512       = encasn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
	OIDEncryptionAlgorithmRSA      = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
	OIDAttributeContentType        = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
	OIDAttributeMessageDigest      = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
	OIDAttributeSigningTime        = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
	OIDAttributeTSTInfo            = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4}
	OIDAttributeRFC3161TimeStamp   = encasn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}
	OIDAttributeMicrosoftTimeStamp = encasn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 3, 3, 1}
)

var digestAlgorithmHashFunction = map[string]crypto.Hash{
	OIDDigestAlgorithmSHA256.String(): crypto.SHA256,
	OIDDigestAlgorithmSHA384.String(): crypto.SHA384,
	OIDDigestAlgorithmSHA512.String(): crypto.SHA512,
}

// Common errors
var (
	// ASN.1 parsing errors
	ErrInteger     = errors.New("expected INTEGER")
	ErrObjectID    = errors.New("expected OBJECT IDENTIFIER")
	ErrNull        = errors.New("expected NULL")
	ErrSequence    = errors.New("expected SEQUENCE")
	ErrOctetString = errors.New("expected OCTET STRING")

	// PKCS7 processing errors
	ErrNoCertificate     = errors.New("no valid certificates")
	ErrTimestampRequest  = errors.New("timestamp request failed")
	ErrTimestampResponse = errors.New("invalid timestamp response")
	ErrTimestampVerify   = errors.New("timestamp verification failed")
)

type Config struct {
	NoAttr                   bool
	NoCerts                  bool
	AdditionalCerts          []*x509.Certificate
	TimestampURL             string
	UseMicrosoftTimestampOID bool
}

type Option func(*Config)

type VerifyConfig struct {
	VerifyTimestamp bool
	TSARoots        []*x509.Certificate // Trusted root CAs for TSA certificates
}

type VerifyOption func(*VerifyConfig)

// Control whether or not the authenticated attributes gets hashed
func NoAttr() Option {
	return func(c *Config) {
		c.NoAttr = true
	}
}

// Control whether or not the certificate gets embedded into the signature
func NoCerts() Option {
	return func(c *Config) {
		c.NoCerts = true
	}
}

// WithAdditionalCerts embeds additional certificates (e.g., intermediate CAs) into the signature
func WithAdditionalCerts(certs []*x509.Certificate) Option {
	return func(c *Config) {
		c.AdditionalCerts = certs
	}
}

// WithAuthenticodeTimestamp enables authenticode timestamping using the specified TSA URL
func WithAuthenticodeTimestamp(tsaURL string) Option {
	return func(c *Config) {
		c.TimestampURL = tsaURL
	}
}

// VerifyTimestamp enables checking the signing certificate for expiry when verifying a signature.
// If the signing certificate is expired, the Authenticode or RFC3161 timestamp is verified.
//
// tsaRoots is the set of trusted root certificates the timestamp signing certificate needs
// to chain up to. If nil or empty, the system roots or the platform verifier are used.
func VerifyTimestamp(tsaRoots []*x509.Certificate) VerifyOption {
	return func(c *VerifyConfig) {
		c.VerifyTimestamp = true
		if tsaRoots != nil || len(tsaRoots) > 0 {
			c.TSARoots = tsaRoots
		}
	}
}

// Partially implements RFC2315
func SignPKCS7(signer crypto.Signer, cert *x509.Certificate, oid encasn1.ObjectIdentifier, content []byte, opts ...Option) ([]byte, error) {
	config := &Config{}
	for _, optFunc := range opts {
		optFunc(config)
	}

	var contentInfo cryptobyte.Builder

	h := crypto.SHA256.New()
	h.Write(content)
	contentDigest := h.Sum(nil)

	var attributes []byte
	if !config.NoAttr {
		// Hash authenticated attributes
		// TODO, not needed for Wincert/UEFI. But we do it anyway
		// Original Implementation has:
		// - OIDAttributeContentType
		// - OIDAttributeSigningTime
		// - OIDAttributeMessageDigest
		attrs := &Attributes{
			ContentType:   oid,
			MessageDigest: contentDigest,
			SigningTime:   time.Now().UTC(),
		}

		attributes = attrs.Marshal()
		h = crypto.SHA256.New()
		h.Write(attributes)
	}

	sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
	if err != nil {
		return nil, err
	}

	var timestamp []byte
	if config.TimestampURL != "" {
		sigHash := crypto.SHA256.New()
		sigHash.Write(sig)
		timestamp, err = GetTimestamp(config.TimestampURL, sigHash.Sum(nil))
		if err != nil {
			return nil, fmt.Errorf("failed to get timestamp for unauthenticated attributes: %w", err)
		}
	}

	// ContentInfo ::= SEQUENCE
	contentInfo.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {

		// contentType ContentType,
		b.AddASN1ObjectIdentifier(OIDSignedData)

		// content [0] EXPLICIT DEFINED BY contentType OPTIONAL
		b.AddASN1(asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {

			// SignedData ::= SEQUENCE
			b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {

				// version Version,
				b.AddASN1Int64(1)

				// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
				// digestAlgorithms DigestAlgorithmIdentifiers,
				b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
					// AlgorithmIdentifier  ::=  SEQUENCE
					b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
						//   algorithm               OBJECT IDENTIFIER,
						// NOTE: Only sha256 hardcoded atm
						b.AddASN1ObjectIdentifier(OIDDigestAlgorithmSHA256)
						//   parameters              ANY DEFINED BY algorithm OPTIONAL
						b.AddASN1NULL()
					})
				})

				// contentInfo ContentInfo
				// ContentInfo ::= SEQUENCE
				b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {

					// contentType ContentType
					b.AddASN1ObjectIdentifier(oid)

					// content [0] EXPLICIT DEFINED BY contentType OPTIONAL
					if len(content) > 0 && !oid.Equal(OIDData) {
						b.AddASN1(asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
							if oid.Equal(OIDAttributeTSTInfo) {
								b.AddASN1OctetString(content)
							} else {
								b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
									b.AddBytes(content)
								})
							}
						})
					}
				})

				if !config.NoCerts {
					// certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL
					b.AddASN1(asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
						// Add the signing certificate first
						b.AddBytes(cert.Raw)
						// Add any additional certificates (e.g., intermediate CAs)
						for _, additionalCert := range config.AdditionalCerts {
							b.AddBytes(additionalCert.Raw)
						}
					})
				}

				// Not Used
				// crls [1] IMPLICIT CertificateRevocationLists OPTIONAL

				// signerInfos SignerInfos
				// SignerInfos ::= SET OF SignerInfo
				// We only have one in Authenticode
				b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
					// SignerInfo ::= SEQUENCE
					b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
						// version Version,
						b.AddASN1Int64(1)

						// issuerAndSerialNumber IssuerAndSerialNumber
						// IssuerAndSerialNumber ::= SEQUENCE
						b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
							// issuer Name
							b.AddBytes(cert.RawIssuer)
							// serialNumber CertificateSerialNumber
							b.AddASN1BigInt(cert.SerialNumber)
						})

						// digestAlgorithm DigestAlgorithmIdentifier
						b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
							//   algorithm               OBJECT IDENTIFIER,
							// NOTE: Only sha256 hardcoded atm
							b.AddASN1ObjectIdentifier(OIDDigestAlgorithmSHA256)
							//   parameters              ANY DEFINED BY algorithm OPTIONAL
							b.AddASN1NULL()
						})

						if len(attributes) > 0 {
							// authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL
							b.AddASN1(asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
								attrsOuter := cryptobyte.String(attributes)
								var attrsInner cryptobyte.String
								attrsOuter.ReadASN1(&attrsInner, asn1.SET)
								b.AddBytes(attrsInner)
							})
						}

						// digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier
						b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
							//   algorithm               OBJECT IDENTIFIER,
							b.AddASN1ObjectIdentifier(OIDEncryptionAlgorithmRSA)
							//   parameters              ANY DEFINED BY algorithm OPTIONAL
							b.AddASN1NULL()
						})

						// encryptedDigest EncryptedDigest
						b.AddASN1OctetString(sig)

						// unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
						if len(timestamp) > 0 {
							// Add unauthenticated attributes with timestamp
							b.AddASN1(asn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
								// Single timestamp attribute
								b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
									b.AddASN1ObjectIdentifier(OIDAttributeMicrosoftTimeStamp)
									b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
										b.AddBytes(timestamp)
									})
								})
							})
						}
					})
				})
			})
		})
	})

	return contentInfo.Bytes()
}

// Parse cryptobyte string to pkix.AlgorithmIdentifier
func ParseAlgorithmIdentifier(der *cryptobyte.String) (*pkix.AlgorithmIdentifier, error) {
	var ident pkix.AlgorithmIdentifier
	var s cryptobyte.String

	if !der.ReadASN1(&s, asn1.SEQUENCE) {
		return nil, errors.New("no algorithmidentifier")
	}

	if !s.ReadASN1ObjectIdentifier(&ident.Algorithm) {
		return nil, errors.New("missing missing algorithmIdentifier")
	}

	if s.Empty() {
		return &ident, nil
	}

	var asn1Null cryptobyte.String
	if !s.ReadASN1(&asn1Null, asn1.NULL) {
		return nil, errors.New("missing missing algorithmIdentifier")
	}
	// TODO: Support paramters, we don't use it currently
	return &ident, nil
}

func hasContentInfo(der *cryptobyte.String) (bool, error) {
	check := *der
	if !check.ReadASN1(&check, asn1.SEQUENCE) {
		return false, errors.New("incorrect input")
	}
	if !check.PeekASN1Tag(asn1.OBJECT_IDENTIFIER) {
		return false, nil
	}
	return true, nil
}

func ParseContentInfo(der *cryptobyte.String) (oid encasn1.ObjectIdentifier, content cryptobyte.String, err error) {
	var s cryptobyte.String

	if !der.ReadASN1(&s, asn1.SEQUENCE) {
		return nil, nil, errors.New("no contentinfo")
	}

	if !s.ReadASN1ObjectIdentifier(&oid) {
		return nil, nil, errors.New("no contentinfo oid")
	}

	if !s.ReadOptionalASN1(&content, nil, asn1.Tag(0).ContextSpecific().Constructed()) {
		return nil, nil, errors.New("no contentinfo content")
	}

	return
}

func parseCertificates(der *cryptobyte.String) ([]*x509.Certificate, []byte, error) {
	var raw cryptobyte.String
	var hasCerts bool
	if !der.ReadOptionalASN1(&raw, &hasCerts, asn1.Tag(0).ContextSpecific().Constructed()) {
		return nil, nil, errors.New("failed to read certificates tag")
	}
	// Certificates are optional in PKCS#7
	if !hasCerts {
		return nil, nil, nil
	}

	// Parse certificates one by one to handle extended certificate types in CertificateSet.
	// RFC 5652 CertificateSet can contain X.509 certificates (untagged SEQUENCE) as well as
	// [0] extended certificates, [1] v1 attribute certificates, and [2] v2 attribute certificates.
	// Some Thales TSS timestamp servers place Time Attribute Certificates (TAC) in the [1] slot
	// ("CertificateChoices1 with ESSCertID compatibility mode"). By parsing cert-by-cert and
	// stopping at non-SEQUENCE data, we extract X.509 certificates and preserve any tagged
	// certificate data (attribute certificates, etc.) for separate handling.
	var certs []*x509.Certificate
	for !raw.Empty() {
		// Save the current state before attempting to read
		rawCopy := raw

		var certDER cryptobyte.String
		// Try to read a SEQUENCE (certificate) - use ReadASN1Element to get the complete DER
		if !raw.ReadASN1Element(&certDER, asn1.SEQUENCE) {
			// Not a certificate SEQUENCE, stop parsing (hit non-certificate data)
			// Restore raw to the state before the failed read
			raw = rawCopy
			break
		}

		// Parse this single certificate
		cert, err := x509.ParseCertificate(certDER)
		if err != nil {
			return nil, nil, fmt.Errorf("failed parsing certificate: %w", err)
		}
		certs = append(certs, cert)
	}

	// Preserve any remaining tagged certificate data (attribute certificates, etc.)
	var extendedCertData []byte
	if !raw.Empty() {
		extendedCertData = []byte(raw)
	}

	return certs, extendedCertData, nil
}

type issuerAndSerialNumber struct {
	RawIssuer    []byte
	SerialNumber *big.Int
}

func parseIssuerAndSerialNumber(der *cryptobyte.String) (*issuerAndSerialNumber, error) {
	// TODO: We don't really use it yet. Expose error
	var s cryptobyte.String
	var ias issuerAndSerialNumber
	var bi big.Int

	var issuer cryptobyte.String

	if !der.ReadASN1(&s, asn1.SEQUENCE) {
		return nil, errors.New("no issuer and serial number")
	}
	if !s.ReadASN1Element(&issuer, asn1.SEQUENCE) {
		return nil, errors.New("not a raw issuer")
	}
	ias.RawIssuer = issuer
	if !s.ReadASN1Integer(&bi) {
		return nil, errors.New("no serial number")
	}
	ias.SerialNumber = &bi
	return &ias, nil
}

func parseAttributes(der *cryptobyte.String, tag int) (*Attributes, error) {
	var attributes Attributes
	var attrs cryptobyte.String
	var hasAttrs bool

	if !der.ReadOptionalASN1(&attrs, &hasAttrs, asn1.Tag(tag).ContextSpecific().Constructed()) {
		return nil, errors.New("malformed attributes")
	}

	if !hasAttrs {
		return nil, nil
	}

	// Store the original raw bytes for signature verification
	if tag == 1 {
		// For unauthenticated attributes (tag 1) we use the raw bytes directly
		attributes.RawBytes = []byte(attrs)
	} else {
		// For authenticated attributes (tag 0) we need to wrap in a SET
		// This is because the IMPLICIT [0] tag is not used for DER encoding
		// See https://tools.ietf.org/html/rfc2315#section-9.3
		b := cryptobyte.NewBuilder(nil)
		b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
			b.AddBytes(attrs)
		})
		attributes.RawBytes = b.BytesOrPanic()
	}

	var contentType cryptobyte.String
	var contentOID encasn1.ObjectIdentifier

	for !attrs.Empty() {
		if !attrs.ReadASN1(&contentType, asn1.SEQUENCE) {
			return nil, errors.New("malformed content type")
		}

		if !contentType.ReadASN1ObjectIdentifier(&contentOID) {
			return nil, errors.New("malformed content type oid")
		}

		if !contentType.ReadASN1(&contentType, asn1.SET) {
			return nil, errors.New("content type set")
		}

		switch {
		case contentOID.Equal(OIDAttributeMessageDigest):
			var digest cryptobyte.String
			if !contentType.ReadASN1(&digest, asn1.OCTET_STRING) {
				return nil, errors.New("could not parse message digest")
			}
			attributes.MessageDigest = digest
		case contentOID.Equal(OIDAttributeContentType):
			var contentTypeOID encasn1.ObjectIdentifier
			if !contentType.ReadASN1ObjectIdentifier(&contentTypeOID) {
				return nil, errors.New("could not parse Content Type")
			}
			attributes.ContentType = contentTypeOID
		case contentOID.Equal(OIDAttributeSigningTime):
			if !contentType.ReadASN1UTCTime(&attributes.SigningTime) {
				return nil, errors.New("could not parse Signing Time")
			}
		case contentOID.Equal(OIDAttributeMicrosoftTimeStamp):
			attributes.TimestampToken = contentType
		default:
			// Save the bytes for any attributes we are not parsing.
			attributes.Other = append(attributes.Other, &unparsedAttribute{
				Type:  contentOID,
				Bytes: contentType,
			})
		}
	}
	return &attributes, nil
}

func parseEncryptedDigest(der *cryptobyte.String) ([]byte, error) {
	var encryptedDigest cryptobyte.String
	if !der.ReadASN1(&encryptedDigest, asn1.OCTET_STRING) {
		return nil, fmt.Errorf("encrypted digest: %w", ErrOctetString)
	}
	return encryptedDigest, nil
}

func parseSignerInfos(der *cryptobyte.String) (*signerinfo, error) {
	var signerInfo cryptobyte.String
	var si signerinfo

	if !der.ReadASN1(&signerInfo, asn1.SEQUENCE) {
		return nil, errors.New("no signer info")
	}

	var version int64
	if !signerInfo.ReadASN1Integer(&version) {
		return nil, errors.New("no version")
	}
	si.Version = version

	ias, err := parseIssuerAndSerialNumber(&signerInfo)
	if err != nil {
		return nil, fmt.Errorf("failed parsing issuer and serial number: %w", err)
	}
	si.IssuerAndSerialnumber = ias

	//digestAlgo
	algid, err := ParseAlgorithmIdentifier(&signerInfo)
	if err != nil {
		return nil, fmt.Errorf("failed parsing digest algorithm: %w", err)
	}
	si.DigestAlgorithm = algid

	// authenticated attributes - tag 0
	attrs, err := parseAttributes(&signerInfo, 0)
	if err != nil {
		return nil, fmt.Errorf("failed parsing attributes: %w", err)
	}
	si.AuthenticatedAttributes = attrs

	// digest encrypted algorithm
	algid, err = ParseAlgorithmIdentifier(&signerInfo)
	if err != nil {
		return nil, fmt.Errorf("failed parsing encrypted digest algorithm: %w", err)
	}
	si.EncryptedDigestAlgorithm = algid

	digest, err := parseEncryptedDigest(&signerInfo)
	if err != nil {
		return nil, fmt.Errorf("failed parsing encrypted digest: %w", err)
	}
	si.EncryptedDigest = digest

	// unauthenticated attributes - tag 1
	unauthAttrs, err := parseAttributes(&signerInfo, 1)
	if err != nil {
		return nil, fmt.Errorf("failed parsing unauthenticated attributes: %w", err)
	}
	si.UnauthenticatedAttributes = unauthAttrs

	return &si, nil
}

type signerinfo struct {
	Version                   int64
	EncryptedDigest           []byte
	DigestAlgorithm           *pkix.AlgorithmIdentifier
	AuthenticatedAttributes   *Attributes
	EncryptedDigestAlgorithm  *pkix.AlgorithmIdentifier
	IssuerAndSerialnumber     *issuerAndSerialNumber
	UnauthenticatedAttributes *Attributes
	TimestampToken            []byte // Store timestamp for unauthenticated attributes
}

func (s *signerinfo) verify(cert *x509.Certificate) (bool, error) {
	sigdata := s.AuthenticatedAttributes.RawBytes
	err := cert.CheckSignature(x509.SHA256WithRSA, sigdata, s.EncryptedDigest)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (s *signerinfo) isCertificate(cert *x509.Certificate) bool {
	if !bytes.Equal(cert.RawIssuer, s.IssuerAndSerialnumber.RawIssuer) {
		return false
	}
	if cert.SerialNumber.Cmp(s.IssuerAndSerialnumber.SerialNumber) != 0 {
		return false
	}
	return true
}

func (s *signerinfo) verifyTimestamp(cert *x509.Certificate, opts ...VerifyOption) (bool, error) {
	if s.UnauthenticatedAttributes == nil || len(s.UnauthenticatedAttributes.TimestampToken) == 0 {
		return false, errors.New("no timestamp token found in unauthenticated attributes")
	}

	h := digestAlgorithmHashFunction[s.DigestAlgorithm.Algorithm.String()].New()
	h.Write(s.EncryptedDigest) // message imprint inside the timestamp token
	imprintHash := h.Sum(nil)

	err := VerifyTimestampBytes(s.UnauthenticatedAttributes.TimestampToken, imprintHash, cert, opts...)
	if err != nil {
		return false, fmt.Errorf("failed to verify timestamp: %w", err)
	}
	return true, nil
}

type PKCS7 struct {
	OID                 encasn1.ObjectIdentifier
	SignerInfo          []*signerinfo
	ContentInfo         []byte
	Certs               []*x509.Certificate
	AlgorithmIdentifier *pkix.AlgorithmIdentifier

	// ExtendedCertData contains any non-X.509 certificate data found in the certificates section.
	// Per RFC 5652, CertificateSet can include [0] extended certificates, [1] v1 attribute
	// certificates (e.g., Time Attribute Certificates from Thales TSS), and [2] v2 attribute
	// certificates. This field preserves that tagged data for inspection or specialized parsing.
	ExtendedCertData []byte
}

func (p *PKCS7) Verify(cert *x509.Certificate, opts ...VerifyOption) (bool, error) {
	c := &VerifyConfig{}
	for _, optFunc := range opts {
		optFunc(c)
	}

	for _, si := range p.SignerInfo {
		if !si.isCertificate(cert) {
			continue
		}
		ok, err := si.verify(cert)
		if err != nil {
			return false, fmt.Errorf("failed validating signature: %w", err)
		}
		if !ok {
			continue
		}
		if cert.NotAfter.Before(time.Now()) && c.VerifyTimestamp {
			return si.verifyTimestamp(cert, opts...)
		}
		return true, nil
	}
	return false, nil
}

func (p *PKCS7) HasCertificate(cert *x509.Certificate) bool {
	for _, si := range p.SignerInfo {
		if si.isCertificate(cert) {
			return true
		}
	}
	return false
}

func ParsePKCS7(b []byte) (*PKCS7, error) {
	var pkcs PKCS7

	contentInfo := cryptobyte.String(b)

	var oid encasn1.ObjectIdentifier
	if ok, err := hasContentInfo(&contentInfo); ok {
		oid, contentInfo, err = ParseContentInfo(&contentInfo)
		if err != nil {
			return nil, fmt.Errorf("failed parsing contenting info: %w", err)
		}
	} else if err != nil {
		return nil, fmt.Errorf("failed checking content info: %w", err)
	}

	pkcs.OID = oid

	var signedData cryptobyte.String
	if !contentInfo.ReadASN1(&signedData, asn1.SEQUENCE) {
		return nil, errors.New("no signed data")
	}

	var version int64
	if !signedData.ReadASN1Integer(&version) {
		return nil, errors.New("no version")
	}

	var digest cryptobyte.String
	if !signedData.ReadASN1(&digest, asn1.SET) {
		return nil, errors.New("no digest crypto")
	}

	algid, err := ParseAlgorithmIdentifier(&digest)
	if err != nil {
		return nil, fmt.Errorf("failed algorithm identifier: %w", err)
	}
	pkcs.AlgorithmIdentifier = algid

	oid, content, err := ParseContentInfo(&signedData)
	if err != nil {
		return nil, fmt.Errorf("failed parsing content info: %w", err)
	}
	pkcs.OID = oid
	pkcs.ContentInfo = content

	certs, extendedCertData, err := parseCertificates(&signedData)
	if err != nil {
		return nil, fmt.Errorf("failed parsing certificates: %w", err)
	}
	pkcs.Certs = certs
	pkcs.ExtendedCertData = extendedCertData

	// Skip optional CRLs [1] IMPLICIT - we don't use them but need to skip past them
	var crls cryptobyte.String
	signedData.ReadOptionalASN1(&crls, nil, asn1.Tag(1).ContextSpecific().Constructed())

	var signerInfo cryptobyte.String

	if !signedData.ReadASN1(&signerInfo, asn1.SET) {
		return nil, errors.New("no signer info")
	}
	for !signerInfo.Empty() {
		si, err := parseSignerInfos(&signerInfo)
		if err != nil {
			return nil, fmt.Errorf("failed parsing signer info: %w", err)
		}
		pkcs.SignerInfo = append(pkcs.SignerInfo, si)
	}

	return &pkcs, nil
}

type unparsedAttribute struct {
	Type  encasn1.ObjectIdentifier
	Bytes []byte
}

type Attributes struct {
	ContentType    encasn1.ObjectIdentifier
	MessageDigest  []byte
	SigningTime    time.Time
	TimestampToken []byte
	Other          []*unparsedAttribute
	RawBytes       []byte // Store the original DER bytes for signature verification
}

func (a *Attributes) Marshal() []byte {
	b := cryptobyte.NewBuilder(nil)
	// Attributes := SET OF Attribute
	b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
		// Add the content type
		b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
			b.AddASN1ObjectIdentifier(OIDAttributeContentType)
			b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
				b.AddASN1ObjectIdentifier(a.ContentType)
			})
		})
		if !a.SigningTime.IsZero() {
			b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
				b.AddASN1ObjectIdentifier(OIDAttributeSigningTime)
				b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
					b.AddASN1UTCTime(a.SigningTime)
				})
			})
		}
		// Digest from Authenticode
		b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
			b.AddASN1ObjectIdentifier(OIDAttributeMessageDigest)
			b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
				b.AddASN1OctetString(a.MessageDigest)
			})
		})
		for _, attr := range a.Other {
			b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
				b.AddASN1ObjectIdentifier(attr.Type)
				b.AddASN1(asn1.SET, func(b *cryptobyte.Builder) {
					b.AddBytes(attr.Bytes)
				})
			})
		}
	})
	return b.BytesOrPanic()
}
