// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin
// +build darwin

// Command macOS-roots-test runs crypto/x509.TestSystemRoots as a
// stand-alone binary for crowdsourced testing.
package main

import (
	"crypto/x509"
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"
	"unsafe"
)

type CertPool struct {
	bySubjectKeyId map[string][]int
	byName         map[string][]int
	certs          []*x509.Certificate
}

func (s *CertPool) contains(cert *x509.Certificate) bool {
	if s == nil {
		return false
	}

	candidates := s.byName[string(cert.RawSubject)]
	for _, c := range candidates {
		if s.certs[c].Equal(cert) {
			return true
		}
	}

	return false
}

func main() {
	var failed bool

	t0 := time.Now()
	sysRootsExt, err := loadSystemRoots() // actual system roots
	sysRootsDuration := time.Since(t0)

	if err != nil {
		log.Fatalf("failed to read system roots (cgo): %v", err)
	}
	sysRoots := (*CertPool)(unsafe.Pointer(sysRootsExt))

	t1 := time.Now()
	execRootsExt, err := execSecurityRoots() // non-cgo roots
	execSysRootsDuration := time.Since(t1)

	if err != nil {
		log.Fatalf("failed to read system roots (nocgo): %v", err)
	}
	execRoots := (*CertPool)(unsafe.Pointer(execRootsExt))

	fmt.Printf("    cgo sys roots: %v\n", sysRootsDuration)
	fmt.Printf("non-cgo sys roots: %v\n", execSysRootsDuration)

	// On Mavericks, there are 212 bundled certs, at least there was at
	// one point in time on one machine. (Maybe it was a corp laptop
	// with extra certs?) Other OS X users report 135, 142, 145...
	// Let's try requiring at least 100, since this is just a sanity
	// check.
	if want, have := 100, len(sysRoots.certs); have < want {
		failed = true
		fmt.Printf("want at least %d system roots, have %d\n", want, have)
	}

	// Check that the two cert pools are the same.
	sysPool := make(map[string]*x509.Certificate, len(sysRoots.certs))
	for _, c := range sysRoots.certs {
		sysPool[string(c.Raw)] = c
	}
	for _, c := range execRoots.certs {
		if _, ok := sysPool[string(c.Raw)]; ok {
			delete(sysPool, string(c.Raw))
		} else {
			// verify-cert lets in certificates that are not trusted roots, but are
			// signed by trusted roots. This should not be a problem, so confirm that's
			// the case and skip them.
			if _, err := c.Verify(x509.VerifyOptions{
				Roots:         sysRootsExt,
				Intermediates: execRootsExt, // the intermediates for EAP certs are stored in the keychain
				KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
			}); err != nil {
				failed = true
				fmt.Printf("certificate only present in non-cgo pool: %v (verify error: %v)\n", c.Subject, err)
			} else {
				fmt.Printf("signed certificate only present in non-cgo pool (acceptable): %v\n", c.Subject)
			}
		}
	}
	for _, c := range sysPool {
		failed = true
		fmt.Printf("certificate only present in cgo pool: %v\n", c.Subject)
	}

	if failed && debugDarwinRoots {
		cmd := exec.Command("security", "dump-trust-settings")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		cmd = exec.Command("security", "dump-trust-settings", "-d")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
	}

	if failed {
		fmt.Printf("\n\n!!! The test failed!\n\nPlease report *the whole output* at https://github.com/golang/go/issues/24652 wrapping it in ``` a code block ```\nThank you!\n")
	} else {
		fmt.Printf("\n\nThe test passed, no need to report the output. Thank you.\n")
	}
}
