import Foundation
import SwiftASN1
import X509

@_spi(CMS) import X509

/// Verifies the detached `Mdm-Signature` CMS a device attaches to each check-in/command request when
/// the MDM payload sets `SignMessage=true` (docs/MACOS_VM_MDM.md §4). The signature is a detached CMS
/// `SignedData` over the raw request body, signed by the device identity we issued; validity means it
/// verifies **and** the signer chains to our local CA — the auth boundary that replaces TLS client
/// certs.
///
/// Cross-platform (swift-certificates works on Linux), so it is unit-testable without a live guest:
/// sign a body with a CA-issued identity, verify it passes; tamper with the body, verify it fails.
public enum MDMSignatureVerifier {
    /// True iff `detachedCMS` is a valid CMS signature over `body` whose signer chains to `ca`.
    public static func verify(
        body: Data, detachedCMS: [UInt8], ca: MDMCertificateAuthority
    ) async -> Bool {
        let result = await CMS.isValidSignature(
            dataBytes: Array(body),
            signatureBytes: detachedCMS,
            trustRoots: CertificateStore([ca.rootCertificate]),
            policy: { AcceptAllPolicy() })
        switch result {
        case .success: return true
        case .failure: return false
        }
    }

    /// Sign `body` as a detached CMS `SignedData` with an identity (test/spike helper — mirrors what
    /// mdmclient does on the guest).
    public static func sign(body: Data, identity: MDMDeviceIdentity) throws -> [UInt8] {
        try CMS.sign(
            body,
            signatureAlgorithm: .sha256WithRSAEncryption,
            certificate: identity.certificate,
            privateKey: Certificate.PrivateKey(identity.privateKey),
            detached: true)
    }
}

/// The chain-to-CA check is done by `trustRoots`; this policy adds no further constraints (we do not
/// want RFC5280 date/EKU rejection to interfere with a self-owned, short-lived device identity). It
/// must, however, declare the critical extensions our own certs carry (BasicConstraints, KeyUsage) as
/// "handled" — otherwise chain verification rejects them as unrecognized critical extensions.
private struct AcceptAllPolicy: VerifierPolicy {
    let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
        .X509ExtensionID.basicConstraints,
        .X509ExtensionID.keyUsage,
        .X509ExtensionID.extendedKeyUsage,
        .X509ExtensionID.subjectAlternativeName,
    ]
    func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult {
        .meetsPolicy
    }
}
