Skip to content
Snippets Groups Projects
root_darwin.go 3 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright 2013 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:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
    
    import (
    	"bytes"
    	"encoding/pem"
    	"fmt"
    	"io/ioutil"
    	"os"
    	"os/exec"
    	"strconv"
    	"sync"
    	"syscall"
    )
    
    func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
    	return nil, nil
    }
    
    
    // This code is only used when compiling without cgo.
    // It is here, instead of root_nocgo_darwin.go, so that tests can check it
    // even if the tests are run with cgo enabled.
    // The linker will not include these unused functions in binaries built with cgo enabled.
    
    
    func execSecurityRoots() (*CertPool, error) {
    	cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
    	data, err := cmd.Output()
    	if err != nil {
    		return nil, err
    
    	var (
    		mu    sync.Mutex
    		roots = NewCertPool()
    	)
    	add := func(cert *Certificate) {
    		mu.Lock()
    		defer mu.Unlock()
    		roots.AddCert(cert)
    	}
    	blockCh := make(chan *pem.Block)
    	var wg sync.WaitGroup
    	for i := 0; i < 4; i++ {
    		wg.Add(1)
    		go func() {
    			defer wg.Done()
    			for block := range blockCh {
    				verifyCertWithSystem(block, add)
    			}
    		}()
    	}
    	for len(data) > 0 {
    		var block *pem.Block
    		block, data = pem.Decode(data)
    		if block == nil {
    			break
    		}
    		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
    			continue
    		}
    		blockCh <- block
    	}
    	close(blockCh)
    	wg.Wait()
    
    
    func verifyCertWithSystem(block *pem.Block, add func(*Certificate)) {
    	data := pem.EncodeToMemory(block)
    	var cmd *exec.Cmd
    	if needsTmpFiles() {
    		f, err := ioutil.TempFile("", "cert")
    		if err != nil {
    			fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
    			return
    		}
    		defer os.Remove(f.Name())
    		if _, err := f.Write(data); err != nil {
    			fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
    			return
    		}
    		if err := f.Close(); err != nil {
    			fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
    			return
    		}
    		cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l")
    	} else {
    		cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", "/dev/stdin", "-l")
    		cmd.Stdin = bytes.NewReader(data)
    	}
    	if cmd.Run() == nil {
    		// Non-zero exit means untrusted
    		cert, err := ParseCertificate(block.Bytes)
    		if err != nil {
    			return
    		}
    
    		add(cert)
    	}
    }
    
    var versionCache struct {
    	sync.Once
    	major int
    }
    
    // needsTmpFiles reports whether the OS is <= 10.11 (which requires real
    // files as arguments to the security command).
    func needsTmpFiles() bool {
    	versionCache.Do(func() {
    		release, err := syscall.Sysctl("kern.osrelease")
    		if err != nil {
    			return
    		}
    		for i, c := range release {
    			if c == '.' {
    				release = release[:i]
    				break
    			}
    		}
    		major, err := strconv.Atoi(release)
    		if err != nil {
    			return
    		}
    		versionCache.major = major
    	})
    	return versionCache.major <= 15
    }