diff --git a/coordinator/internal/attestation/mda.go b/coordinator/internal/attestation/mda.go index c6cf7302..e2336e5d 100644 --- a/coordinator/internal/attestation/mda.go +++ b/coordinator/internal/attestation/mda.go @@ -202,26 +202,31 @@ func VerifyMDACertChain(certChainPEM []byte, appleRootCA *x509.Certificate) (*MD result := &MDAResult{} - // When a root CA is provided, verify the certificate chain. - // When nil, skip chain verification and just parse OIDs. - if appleRootCA != nil { - roots := x509.NewCertPool() - roots.AddCert(appleRootCA) - - intPool := x509.NewCertPool() - for _, ic := range intermediatesCerts { - intPool.AddCert(ic) - } + // A nil root means no trust anchor is available — return a parsed-only + // result with Valid=false so callers cannot accidentally treat unverified + // chains as trusted. + if appleRootCA == nil { + result.Error = "no root CA provided; chain verification skipped" + result.DeviceSerial = leaf.Subject.SerialNumber + return result, nil + } - opts := x509.VerifyOptions{ - Roots: roots, - Intermediates: intPool, - } + roots := x509.NewCertPool() + roots.AddCert(appleRootCA) - if _, err := leaf.Verify(opts); err != nil { - result.Error = fmt.Sprintf("certificate chain verification failed: %v", err) - return result, nil - } + intPool := x509.NewCertPool() + for _, ic := range intermediatesCerts { + intPool.AddCert(ic) + } + + opts := x509.VerifyOptions{ + Roots: roots, + Intermediates: intPool, + } + + if _, err := leaf.Verify(opts); err != nil { + result.Error = fmt.Sprintf("certificate chain verification failed: %v", err) + return result, nil } result.Valid = true diff --git a/coordinator/internal/attestation/mda_test.go b/coordinator/internal/attestation/mda_test.go index 61984d9a..b2201497 100644 --- a/coordinator/internal/attestation/mda_test.go +++ b/coordinator/internal/attestation/mda_test.go @@ -221,7 +221,8 @@ func TestVerifyMDACertChainWrongRoot(t *testing.T) { } func TestVerifyMDACertChainNilRoot(t *testing.T) { - // Without a root CA, the function should still parse OIDs but skip chain verification. + // Without a root CA no trust anchor is available; the result must be + // invalid so callers cannot treat an unverified chain as trusted. certPEM, _ := createTestMDACert(t, false, true, true) result, err := VerifyMDACertChain(certPEM, nil) @@ -229,18 +230,11 @@ func TestVerifyMDACertChainNilRoot(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - if !result.Valid { - t.Fatalf("expected valid result without root CA, got error: %s", result.Error) - } - - if result.SIPEnabled { - t.Error("expected SIPEnabled = false") - } - if !result.SecureBootEnabled { - t.Error("expected SecureBootEnabled = true") + if result.Valid { + t.Fatal("expected Valid=false when no root CA is provided") } - if !result.ThirdPartyKexts { - t.Error("expected ThirdPartyKexts = true") + if result.Error == "" { + t.Error("expected a non-empty Error explaining why validation was skipped") } }