package protocol

import (
	"bytes"
	"crypto/sha256"
	"crypto/x509"
	"encoding/asn1"
	"time"

	"github.com/go-webauthn/webauthn/metadata"
)

// attestationFormatValidationHandlerAppleAnonymous is the handler for the Apple Anonymous Attestation Statement Format.
//
// The syntax of an Apple attestation statement is defined as follows:
//
// $$attStmtType //= (
//
//	    fmt: "apple",
//	    attStmt: appleStmtFormat
//	)
//
//	appleStmtFormat = {
//	                      x5c: [ credCert: bytes, * (caCert: bytes) ]
//	                  }
//
// Specification: §8.8. Apple Anonymous Attestation Statement Format
//
// See : https://www.w3.org/TR/webauthn/#sctn-apple-anonymous-attestation
func attestationFormatValidationHandlerAppleAnonymous(att AttestationObject, clientDataHash []byte, _ metadata.Provider) (attestationType string, x5cs []any, err error) {
	// Step 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it
	// to extract the contained fields.
	var (
		x5c   []any
		certs []*x509.Certificate
	)

	if x5c, certs, err = attStatementParseX5CS(att.AttStatement, stmtX5C); err != nil {
		return "", nil, err
	}

	if len(certs) == 0 {
		return "", nil, ErrInvalidAttestation.WithDetails("No certificates in x5c")
	}

	credCert := certs[0]

	if _, err = attStatementCertChainVerify(certs, attAppleHardwareRootsCertPool, true, time.Now().Add(time.Hour*8760).UTC()); err != nil {
		return "", nil, ErrInvalidAttestation.WithDetails("Error validating x5c cert chain").WithError(err)
	}

	// Step 2. Concatenate authenticatorData and clientDataHash to form nonceToHash.
	nonceToHash := append(att.RawAuthData, clientDataHash...) //nolint:gocritic // This is intentional.

	// Step 3. Perform SHA-256 hash of nonceToHash to produce nonce.
	nonce := sha256.Sum256(nonceToHash)

	// Step 4. Verify that nonce equals the value of the extension with OID 1.2.840.113635.100.8.2 in credCert.
	var attExtBytes []byte

	for _, ext := range credCert.Extensions {
		if ext.Id.Equal(oidExtensionAppleAnonymousAttestation) {
			attExtBytes = ext.Value
		}
	}

	if len(attExtBytes) == 0 {
		return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.2.840.113635.100.8.2")
	}

	decoded := AppleAnonymousAttestation{}

	if _, err = asn1.Unmarshal(attExtBytes, &decoded); err != nil {
		return "", nil, ErrAttestationFormat.WithDetails("Unable to parse apple attestation certificate extensions").WithError(err)
	}

	if !bytes.Equal(decoded.Nonce, nonce[:]) {
		return "", nil, ErrInvalidAttestation.WithDetails("Attestation certificate does not contain expected nonce")
	}

	// Step 5. Verify that the credential public key equals the Subject Public Key of credCert.
	if _, err = verifyAttestationECDSAPublicKeyMatch(att, credCert); err != nil {
		return "", nil, err
	}

	// Step 6. If successful, return implementation-specific values representing attestation type Anonymization CA and
	// attestation trust path x5c.
	return string(metadata.AnonCA), x5c, nil
}

// AppleAnonymousAttestation represents the attestation format for Apple, who have not yet published a schema for the
// extension (as of JULY 2021.)
type AppleAnonymousAttestation struct {
	Nonce []byte `asn1:"tag:1,explicit"`
}

var (
	attAppleHardwareRootsCertPool *x509.CertPool
)

func init() {
	RegisterAttestationFormat(AttestationFormatApple, attestationFormatValidationHandlerAppleAnonymous)
}
