package util

import (
	"bytes"
	"errors"
	"fmt"

	"github.com/zmap/zcrypto/cryptobyte"
	cryptobyte_asn1 "github.com/zmap/zcrypto/cryptobyte/asn1"
	"github.com/zmap/zcrypto/encoding/asn1"
	"github.com/zmap/zcrypto/x509"
)

// additional OIDs not provided by the x509 package.
var (
	// 1.2.840.10045.4.3.1 is SHA224withECDSA
	OidSignatureSHA224withECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 1}
)

// RSAAlgorithmIDToDER contains DER representations of pkix.AlgorithmIdentifier for different RSA OIDs with Parameters as asn1.NULL.
var RSAAlgorithmIDToDER = map[string][]byte{
	// rsaEncryption
	"1.2.840.113549.1.1.1": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0},
	// md2WithRSAEncryption
	"1.2.840.113549.1.1.2": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x2, 0x5, 0x0},
	// md5WithRSAEncryption
	"1.2.840.113549.1.1.4": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x4, 0x5, 0x0},
	// sha-1WithRSAEncryption
	"1.2.840.113549.1.1.5": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x5, 0x5, 0x0},
	// sha224WithRSAEncryption
	"1.2.840.113549.1.1.14": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xe, 0x5, 0x0},
	// sha256WithRSAEncryption
	"1.2.840.113549.1.1.11": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0},
	// sha384WithRSAEncryption
	"1.2.840.113549.1.1.12": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xc, 0x5, 0x0},
	// sha512WithRSAEncryption
	"1.2.840.113549.1.1.13": {0x30, 0x0d, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xd, 0x5, 0x0},
}

// CheckAlgorithmIDParamNotNULL parses an AlgorithmIdentifier with algorithm OID rsaEncryption to check the Param field is asn1.NULL
// Expects DER-encoded AlgorithmIdentifier including tag and length.
//
//nolint:cyclop
func CheckAlgorithmIDParamNotNULL(algorithmIdentifier []byte, requiredAlgoID asn1.ObjectIdentifier) error {
	expectedAlgoIDBytes, ok := RSAAlgorithmIDToDER[requiredAlgoID.String()]
	if !ok {
		return errors.New("error algorithmID to check is not RSA")
	}

	algorithmSequence := cryptobyte.String(algorithmIdentifier)

	// byte comparison of algorithm sequence and checking no trailing data is present
	var algorithmBytes []byte
	if algorithmSequence.ReadBytes(&algorithmBytes, len(expectedAlgoIDBytes)) {
		if bytes.Equal(algorithmBytes, expectedAlgoIDBytes) && algorithmSequence.Empty() {
			return nil
		}
	}

	// re-parse to get an error message detailing what did not match in the byte comparison
	algorithmSequence = cryptobyte.String(algorithmIdentifier)
	var algorithm cryptobyte.String
	if !algorithmSequence.ReadASN1(&algorithm, cryptobyte_asn1.SEQUENCE) {
		return errors.New("error reading algorithm")
	}

	encryptionOID := asn1.ObjectIdentifier{}
	if !algorithm.ReadASN1ObjectIdentifier(&encryptionOID) {
		return errors.New("error reading algorithm OID")
	}

	if !encryptionOID.Equal(requiredAlgoID) {
		return fmt.Errorf("algorithm OID is not equal to %s", requiredAlgoID.String())
	}

	if algorithm.Empty() {
		return errors.New("RSA algorithm identifier missing required NULL parameter")
	}

	var nullValue cryptobyte.String
	if !algorithm.ReadASN1(&nullValue, cryptobyte_asn1.NULL) {
		return errors.New("RSA algorithm identifier with non-NULL parameter")
	}

	if len(nullValue) != 0 {
		return errors.New("RSA algorithm identifier with NULL parameter containing data")
	}

	// ensure algorithm is empty and no trailing data is present
	if !algorithm.Empty() {
		return errors.New("RSA algorithm identifier with trailing data")
	}

	return errors.New("RSA algorithm appears correct, but didn't match byte-wise comparison")
}

// Returns the signature field of the tbsCertificate of this certificate in a DER encoded form or an error
// if the signature field could not be extracted. The encoded form contains the tag and the length.
//
//	TBSCertificate  ::=  SEQUENCE  {
//	    version         [0]  EXPLICIT Version DEFAULT v1,
//	    serialNumber         CertificateSerialNumber,
//	    signature            AlgorithmIdentifier,
//	    issuer               Name,
//	    validity             Validity,
//	    subject              Name,
//	    subjectPublicKeyInfo SubjectPublicKeyInfo,
//	    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
//	                         -- If present, version MUST be v2 or v3
//	    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
//	                         -- If present, version MUST be v2 or v3
//	    extensions      [3]  EXPLICIT Extensions OPTIONAL
//	                         -- If present, version MUST be v3
//	    }
func GetSignatureAlgorithmInTBSEncoded(c *x509.Certificate) ([]byte, error) {
	input := cryptobyte.String(c.RawTBSCertificate)

	var tbsCert cryptobyte.String
	if !input.ReadASN1(&tbsCert, cryptobyte_asn1.SEQUENCE) {
		return nil, errors.New("error reading tbsCertificate")
	}

	if !tbsCert.SkipOptionalASN1(cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) {
		return nil, errors.New("error reading tbsCertificate.version")
	}

	if !tbsCert.SkipASN1(cryptobyte_asn1.INTEGER) {
		return nil, errors.New("error reading tbsCertificate.serialNumber")
	}

	var signatureAlgoID cryptobyte.String
	var tag cryptobyte_asn1.Tag
	// use ReadAnyElement to preserve tag and length octets
	if !tbsCert.ReadAnyASN1Element(&signatureAlgoID, &tag) {
		return nil, errors.New("error reading tbsCertificate.signature")
	}

	return signatureAlgoID, nil
}

// Returns the algorithm field of the SubjectPublicKeyInfo of the certificate or an error
// if the algorithm field could not be extracted.
//
//	SubjectPublicKeyInfo  ::=  SEQUENCE  {
//	    algorithm            AlgorithmIdentifier,
//	    subjectPublicKey     BIT STRING  }
func GetPublicKeyOID(c *x509.Certificate) (asn1.ObjectIdentifier, error) {
	input := cryptobyte.String(c.RawSubjectPublicKeyInfo)

	var publicKeyInfo cryptobyte.String
	if !input.ReadASN1(&publicKeyInfo, cryptobyte_asn1.SEQUENCE) {
		return nil, errors.New("error reading pkixPublicKey")
	}

	var algorithm cryptobyte.String
	if !publicKeyInfo.ReadASN1(&algorithm, cryptobyte_asn1.SEQUENCE) {
		return nil, errors.New("error reading public key algorithm identifier")
	}

	publicKeyOID := asn1.ObjectIdentifier{}
	if !algorithm.ReadASN1ObjectIdentifier(&publicKeyOID) {
		return nil, errors.New("error reading public key OID")
	}

	return publicKeyOID, nil
}

// Returns the algorithm field of the SubjectPublicKeyInfo of the certificate in its encoded form (containing Tag
// and Length) or an error if the algorithm field could not be extracted.
//
//	SubjectPublicKeyInfo  ::=  SEQUENCE  {
//	    algorithm            AlgorithmIdentifier,
//	    subjectPublicKey     BIT STRING  }
func GetPublicKeyAidEncoded(c *x509.Certificate) ([]byte, error) {
	input := cryptobyte.String(c.RawSubjectPublicKeyInfo)
	var spkiContent cryptobyte.String

	if !input.ReadASN1(&spkiContent, cryptobyte_asn1.SEQUENCE) {
		return nil, errors.New("error reading pkixPublicKey")
	}

	var algorithm cryptobyte.String
	var tag cryptobyte_asn1.Tag

	if !spkiContent.ReadAnyASN1Element(&algorithm, &tag) {
		return nil, errors.New("error reading public key algorithm identifier")
	}

	return algorithm, nil
}
