Commit aa241580 authored by Filippo Valsorda's avatar Filippo Valsorda Committed by Filippo Valsorda

crypto/x509: fix root CA extraction on macOS (no-cgo path)

Certificates without any trust settings might still be in the keychain
(for example if they used to have some, or if they are intermediates for
offline verification), but they are not to be trusted. The only ones we
can trust unconditionally are the ones in the system roots store.

Moreover, the verify-cert invocation was not specifying the ssl policy,
defaulting instead to the basic one. We have no way of communicating
different usages in a CertPool, so stick to the WebPKI use-case as the
primary one for crypto/x509.

Updates #24652

Change-Id: Ife8b3d2f4026daa1223aa81fac44aeeb4f96528a
Reviewed-on: https://go-review.googlesource.com/c/128116Reviewed-by: default avatarAdam Langley <agl@google.com>
Reviewed-by: default avatarAdam Langley <agl@golang.org>
parent f6be1cf1
...@@ -39,8 +39,8 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate ...@@ -39,8 +39,8 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
// //
// 1. Run "security trust-settings-export" and "security // 1. Run "security trust-settings-export" and "security
// trust-settings-export -d" to discover the set of certs with some // trust-settings-export -d" to discover the set of certs with some
// user-tweaked trust policy. We're too lazy to parse the XML (at // user-tweaked trust policy. We're too lazy to parse the XML
// least at this stage of Go 1.8) to understand what the trust // (Issue 26830) to understand what the trust
// policy actually is. We just learn that there is _some_ policy. // policy actually is. We just learn that there is _some_ policy.
// //
// 2. Run "security find-certificate" to dump the list of system root // 2. Run "security find-certificate" to dump the list of system root
...@@ -58,21 +58,20 @@ func execSecurityRoots() (*CertPool, error) { ...@@ -58,21 +58,20 @@ func execSecurityRoots() (*CertPool, error) {
return nil, err return nil, err
} }
if debugDarwinRoots { if debugDarwinRoots {
println(fmt.Sprintf("crypto/x509: %d certs have a trust policy", len(hasPolicy))) fmt.Printf("crypto/x509: %d certs have a trust policy\n", len(hasPolicy))
} }
args := []string{"find-certificate", "-a", "-p", keychains := []string{"/Library/Keychains/System.keychain"}
"/System/Library/Keychains/SystemRootCertificates.keychain",
"/Library/Keychains/System.keychain",
}
// Note that this results in trusting roots from $HOME/... (the environment
// variable), which might not be expected.
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
if debugDarwinRoots { if debugDarwinRoots {
println(fmt.Sprintf("crypto/x509: can't get user home directory: %v", err)) fmt.Printf("crypto/x509: can't get user home directory: %v\n", err)
} }
} else { } else {
args = append(args, keychains = append(keychains,
filepath.Join(home, "/Library/Keychains/login.keychain"), filepath.Join(home, "/Library/Keychains/login.keychain"),
// Fresh installs of Sierra use a slightly different path for the login keychain // Fresh installs of Sierra use a slightly different path for the login keychain
...@@ -80,21 +79,19 @@ func execSecurityRoots() (*CertPool, error) { ...@@ -80,21 +79,19 @@ func execSecurityRoots() (*CertPool, error) {
) )
} }
cmd := exec.Command("/usr/bin/security", args...) type rootCandidate struct {
data, err := cmd.Output() c *Certificate
if err != nil { system bool
return nil, err
} }
var ( var (
mu sync.Mutex mu sync.Mutex
roots = NewCertPool() roots = NewCertPool()
numVerified int // number of execs of 'security verify-cert', for debug stats numVerified int // number of execs of 'security verify-cert', for debug stats
wg sync.WaitGroup
verifyCh = make(chan rootCandidate)
) )
blockCh := make(chan *pem.Block)
var wg sync.WaitGroup
// Using 4 goroutines to pipe into verify-cert seems to be // Using 4 goroutines to pipe into verify-cert seems to be
// about the best we can do. The verify-cert binary seems to // about the best we can do. The verify-cert binary seems to
// just RPC to another server with coarse locking anyway, so // just RPC to another server with coarse locking anyway, so
...@@ -108,31 +105,62 @@ func execSecurityRoots() (*CertPool, error) { ...@@ -108,31 +105,62 @@ func execSecurityRoots() (*CertPool, error) {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
for block := range blockCh { for cert := range verifyCh {
cert, err := ParseCertificate(block.Bytes) sha1CapHex := fmt.Sprintf("%X", sha1.Sum(cert.c.Raw))
if err != nil {
continue
}
sha1CapHex := fmt.Sprintf("%X", sha1.Sum(block.Bytes))
valid := true var valid bool
verifyChecks := 0 verifyChecks := 0
if hasPolicy[sha1CapHex] { if hasPolicy[sha1CapHex] {
verifyChecks++ verifyChecks++
if !verifyCertWithSystem(block, cert) { valid = verifyCertWithSystem(cert.c)
valid = false } else {
} // Certificates not in SystemRootCertificates without user
// or admin trust settings are not trusted.
valid = cert.system
} }
mu.Lock() mu.Lock()
numVerified += verifyChecks numVerified += verifyChecks
if valid { if valid {
roots.AddCert(cert) roots.AddCert(cert.c)
} }
mu.Unlock() mu.Unlock()
} }
}() }()
} }
err = forEachCertInKeychains(keychains, func(cert *Certificate) {
verifyCh <- rootCandidate{c: cert, system: false}
})
if err != nil {
close(verifyCh)
return nil, err
}
err = forEachCertInKeychains([]string{
"/System/Library/Keychains/SystemRootCertificates.keychain",
}, func(cert *Certificate) {
verifyCh <- rootCandidate{c: cert, system: true}
})
if err != nil {
close(verifyCh)
return nil, err
}
close(verifyCh)
wg.Wait()
if debugDarwinRoots {
fmt.Printf("crypto/x509: ran security verify-cert %d times\n", numVerified)
}
return roots, nil
}
func forEachCertInKeychains(paths []string, f func(*Certificate)) error {
args := append([]string{"find-certificate", "-a", "-p"}, paths...)
cmd := exec.Command("/usr/bin/security", args...)
data, err := cmd.Output()
if err != nil {
return err
}
for len(data) > 0 { for len(data) > 0 {
var block *pem.Block var block *pem.Block
block, data = pem.Decode(data) block, data = pem.Decode(data)
...@@ -142,22 +170,19 @@ func execSecurityRoots() (*CertPool, error) { ...@@ -142,22 +170,19 @@ func execSecurityRoots() (*CertPool, error) {
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue continue
} }
blockCh <- block cert, err := ParseCertificate(block.Bytes)
if err != nil {
continue
} }
close(blockCh) f(cert)
wg.Wait()
if debugDarwinRoots {
mu.Lock()
defer mu.Unlock()
println(fmt.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified))
} }
return nil
return roots, nil
} }
func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool { func verifyCertWithSystem(cert *Certificate) bool {
data := pem.EncodeToMemory(block) data := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
})
f, err := ioutil.TempFile("", "cert") f, err := ioutil.TempFile("", "cert")
if err != nil { if err != nil {
...@@ -173,19 +198,19 @@ func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool { ...@@ -173,19 +198,19 @@ func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool {
fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err) fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
return false return false
} }
cmd := exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l", "-L") cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
var stderr bytes.Buffer var stderr bytes.Buffer
if debugDarwinRoots { if debugDarwinRoots {
cmd.Stderr = &stderr cmd.Stderr = &stderr
} }
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
if debugDarwinRoots { if debugDarwinRoots {
println(fmt.Sprintf("crypto/x509: verify-cert rejected %s: %q", cert.Subject, bytes.TrimSpace(stderr.Bytes()))) fmt.Printf("crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
} }
return false return false
} }
if debugDarwinRoots { if debugDarwinRoots {
println(fmt.Sprintf("crypto/x509: verify-cert approved %s", cert.Subject)) fmt.Printf("crypto/x509: verify-cert approved %s\n", cert.Subject)
} }
return true return true
} }
...@@ -218,7 +243,7 @@ func getCertsWithTrustPolicy() (map[string]bool, error) { ...@@ -218,7 +243,7 @@ func getCertsWithTrustPolicy() (map[string]bool, error) {
// localized on macOS, just interpret any failure to mean that // localized on macOS, just interpret any failure to mean that
// there are no trust settings. // there are no trust settings.
if debugDarwinRoots { if debugDarwinRoots {
println(fmt.Sprintf("crypto/x509: exec %q: %v, %s", cmd.Args, err, stderr.Bytes())) fmt.Printf("crypto/x509: exec %q: %v, %s\n", cmd.Args, err, stderr.Bytes())
} }
return nil return nil
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment