// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: MPL-2.0

package certutil

import (
	"bytes"
	"fmt"
	"time"

	ctx509 "github.com/google/certificate-transparency-go/x509"
	"github.com/hashicorp/errwrap"
)

func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOptions) error {
	// If private key exists, check if it matches the public key of cert
	if parsedBundle.PrivateKey != nil && parsedBundle.Certificate != nil {
		equal, err := ComparePublicKeys(parsedBundle.Certificate.PublicKey, parsedBundle.PrivateKey.Public())
		if err != nil {
			return errwrap.Wrapf("could not compare public and private keys: {{err}}", err)
		}
		if !equal {
			return fmt.Errorf("public key of certificate does not match private key")
		}
	}

	rootCertPool := ctx509.NewCertPool()
	intermediateCertPool := ctx509.NewCertPool()

	for index, certificate := range parsedBundle.CAChain {
		cert, err := convertCertificate(certificate.Bytes)
		if err != nil {
			return fmt.Errorf("could not parse certificate number %v in chain: %w", index, err)
		}
		if index > 0 && !cert.IsCA {
			// Sometimes the leaf certificate is contained inside the bundle
			return fmt.Errorf("certificate %v is not a CA certificate", index)
		}
		if bytes.Equal(cert.RawIssuer, cert.RawSubject) {
			// Occasionally verify is called with a self-signed certificate that is not a CA;
			// We don't break that use case here
			rootCertPool.AddCert(cert)
		} else {
			intermediateCertPool.AddCert(cert)
		}
	}

	if len(rootCertPool.Subjects()) < 1 {
		// Alright, this is weird, since we don't have the root CA, we'll treat the intermediate as
		// the root, otherwise we'll get a "x509: certificate signed by unknown authority" error.
		rootCertPool, intermediateCertPool = intermediateCertPool, rootCertPool
	}

	// Note that we use github.com/google/certificate-transparency-go/x509 to perform certificate verification,
	// since that library provides options to disable checks that the standard library does not.

	options.Roots = rootCertPool
	options.Intermediates = intermediateCertPool
	options.CurrentTime = time.Now()

	certificate, err := convertCertificate(parsedBundle.CertificateBytes)
	if err != nil {
		return err
	}

	_, err = certificate.Verify(options)
	return err
}

func convertCertificate(certBytes []byte) (*ctx509.Certificate, error) {
	ret, err := ctx509.ParseCertificate(certBytes)
	if err != nil {
		return nil, fmt.Errorf("cannot convert certificate for validation: %w", err)
	}
	return ret, nil
}
