pax_global_header00006660000000000000000000000064137037143240014516gustar00rootroot0000000000000052 comment=23dd8829443eb0fd7dbb4e43bc22d2ab7a1affa3 truststore-0.9.6/000077500000000000000000000000001370371432400137705ustar00rootroot00000000000000truststore-0.9.6/.gitignore000066400000000000000000000004211370371432400157550ustar00rootroot00000000000000# Binaries for programs and plugins /bin/truststore *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Others *.swp .travis-releases coverage.txt output vendor step truststore-0.9.6/LICENSE000066400000000000000000000261351370371432400150040ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. truststore-0.9.6/Makefile000066400000000000000000000001371370371432400154310ustar00rootroot00000000000000all: go build -o bin/truststore cmd/truststore/main.go clean: rm -rf bin .PHONY: all clean truststore-0.9.6/README.md000066400000000000000000000006051370371432400152500ustar00rootroot00000000000000# truststore [![GoDoc](https://godoc.org/github.com/smallstep/truststore?status.svg)](https://godoc.org/github.com/smallstep/truststore) [![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/truststore)](https://goreportcard.com/report/github.com/smallstep/truststore) Package to locally install development certificates. Based on https://github.com/FiloSottile/mkcert truststore-0.9.6/cmd/000077500000000000000000000000001370371432400145335ustar00rootroot00000000000000truststore-0.9.6/cmd/truststore/000077500000000000000000000000001370371432400167715ustar00rootroot00000000000000truststore-0.9.6/cmd/truststore/main.go000066400000000000000000000032671370371432400202540ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. package main import ( "flag" "fmt" "os" "github.com/smallstep/truststore" ) func usage() { fmt.Fprintf(os.Stderr, "Usage:\n\t%s [-uninstall] rootCA.pem\n", os.Args[0]) flag.PrintDefaults() } func main() { var uninstall, help, verbose bool var java, firefox, noSystem, all bool flag.Usage = usage flag.BoolVar(&uninstall, "uninstall", false, "uninstall the given certificate") flag.BoolVar(&java, "java", false, "install or uninstall on the Java truststore") flag.BoolVar(&firefox, "firefox", false, "install or uninstall on the Firefox truststore") flag.BoolVar(&noSystem, "no-system", false, "disables the install or uninstall on the system truststore") flag.BoolVar(&all, "all", false, "install or uninstall on the system, Firefox and Java truststores") flag.BoolVar(&verbose, "v", false, "be verbose") flag.BoolVar(&help, "help", false, "show help") flag.Parse() if help { flag.Usage() os.Exit(0) } if len(flag.Args()) != 1 { flag.Usage() os.Exit(1) } var opts []truststore.Option if all { opts = append(opts, truststore.WithJava(), truststore.WithFirefox()) } else { if java { opts = append(opts, truststore.WithJava()) } if firefox { opts = append(opts, truststore.WithFirefox()) } } if noSystem { opts = append(opts, truststore.WithNoSystem()) } if verbose { opts = append(opts, truststore.WithDebug()) } if uninstall { if err := truststore.UninstallFile(flag.Arg(0), opts...); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } } else { if err := truststore.InstallFile(flag.Arg(0), opts...); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } } } truststore-0.9.6/errors.go000066400000000000000000000034601370371432400156360ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. package truststore import ( "errors" "fmt" "os/exec" "path/filepath" ) var ( // ErrNotSupported is the error to indicate that the install of the // certificate is not supported on the system. ErrNotSupported = errors.New("install is not supported on this system") // ErrNotFound is the error to indicate that a cert was not found. ErrNotFound = errors.New("no certs found") // ErrInvalidCertificate is the error to indicate that a cert contains bad data. ErrInvalidCertificate = errors.New("invalid PEM data") // ErrTrustExists is the error returned when a trust already exists. ErrTrustExists = errors.New("trust already exists") // ErrTrustNotFound is the error returned when a trust does not exists. ErrTrustNotFound = errors.New("trust does not exists") // ErrTrustNotSupported is the error returned when a trust is not supported. ErrTrustNotSupported = errors.New("trust not supported") ) // CmdError is the error used when an executable fails. type CmdError struct { err error cmd *exec.Cmd out []byte } // NewCmdError creates a new CmdError. func NewCmdError(err error, cmd *exec.Cmd, out []byte) *CmdError { return &CmdError{ err: err, cmd: cmd, out: out, } } // Error implements the error interface. func (e *CmdError) Error() string { name := filepath.Base(e.cmd.Path) return fmt.Sprintf("failed to execute %s: %v", name, e.err) } // Err returns the internal error. func (e *CmdError) Err() error { return e.err } // Cmd returns the command executed. func (e *CmdError) Cmd() *exec.Cmd { return e.cmd } // Out returns the output of the command. func (e *CmdError) Out() []byte { return e.out } func wrapError(err error, msg string) error { if err == nil { return nil } return fmt.Errorf("%s: %s", msg, err) } truststore-0.9.6/go.mod000066400000000000000000000001551370371432400150770ustar00rootroot00000000000000module github.com/smallstep/truststore go 1.13 require howett.net/plist v0.0.0-20181124034731-591f970eefbb truststore-0.9.6/go.sum000066400000000000000000000015051370371432400151240ustar00rootroot00000000000000github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= truststore-0.9.6/truststore.go000066400000000000000000000131761370371432400165650ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. package truststore import ( "bytes" "crypto/x509" "encoding/pem" "io" "io/ioutil" "log" "os" ) var prefix = "" var enableDebug bool func debug(format string, args ...interface{}) { if enableDebug { log.Printf(format, args...) } } // Trust is the interface that non-system trustores implement to add and remove // a certificate on its trustore. Right now we there are two implementations of // trust NSS (Firefox) and Java. type Trust interface { Name() string Install(filename string, cert *x509.Certificate) error Uninstall(filename string, cert *x509.Certificate) error Exists(cert *x509.Certificate) bool PreCheck() error } // Install installs the given certificate into the system truststore, and // optionally to the Firefox and Java trustores. func Install(cert *x509.Certificate, opts ...Option) error { filename, fn, err := saveTempCert(cert) defer fn() if err != nil { return err } return installCertificate(filename, cert, opts) } // InstallFile will read the certificate in the given file and install it to the // system truststore, and optionally to the Firefox and Java truststores. func InstallFile(filename string, opts ...Option) error { cert, err := ReadCertificate(filename) if err != nil { return err } return installCertificate(filename, cert, opts) } func installCertificate(filename string, cert *x509.Certificate, opts []Option) error { o := newOptions(opts) for _, t := range o.trusts { if err := t.PreCheck(); err != nil { debug(err.Error()) continue } if !t.Exists(cert) { if err := t.Install(filename, cert); err != nil { return err } } } if o.withNoSystem { return nil } return installPlatform(filename, cert) } // Uninstall removes the given certificate from the system truststore, and // optionally from the Firefox and Java truststres. func Uninstall(cert *x509.Certificate, opts ...Option) error { filename, fn, err := saveTempCert(cert) defer fn() if err != nil { return err } return uninstallCertificate(filename, cert, opts) } // UninstallFile reads the certificate in the given file and removes it from the // system truststore, and optionally to the Firefox and Java truststores. func UninstallFile(filename string, opts ...Option) error { cert, err := ReadCertificate(filename) if err != nil { return err } return uninstallCertificate(filename, cert, opts) } func uninstallCertificate(filename string, cert *x509.Certificate, opts []Option) error { o := newOptions(opts) for _, t := range o.trusts { if err := t.PreCheck(); err != nil { debug(err.Error()) continue } if err := t.Uninstall(filename, cert); err != nil { return err } } if o.withNoSystem { return nil } return uninstallPlatform(filename, cert) } // ReadCertificate reads a certificate file and returns a x509.Certificate struct. func ReadCertificate(filename string) (*x509.Certificate, error) { b, err := ioutil.ReadFile(filename) if err != nil { return nil, err } // PEM format if bytes.HasPrefix(b, []byte("-----BEGIN ")) { b, err = ioutil.ReadFile(filename) if err != nil { return nil, err } block, _ := pem.Decode(b) if block == nil || block.Type != "CERTIFICATE" { return nil, ErrInvalidCertificate } b = block.Bytes } // DER format (binary) crt, err := x509.ParseCertificate(b) return crt, wrapError(err, "error parsing "+filename) } // SaveCertificate saves the given x509.Certificate with the given filename. func SaveCertificate(filename string, cert *x509.Certificate) error { block := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, } return ioutil.WriteFile(filename, pem.EncodeToMemory(block), 0600) } type options struct { withNoSystem bool trusts map[string]Trust } func newOptions(opts []Option) *options { o := &options{ trusts: make(map[string]Trust), } for _, fn := range opts { fn(o) } return o } // Option is the type used to pass custom options. type Option func(*options) // WithTrust enables the given trust. func WithTrust(t Trust) Option { return func(o *options) { o.trusts[t.Name()] = t } } // WithJava enables the install or uninstall of a certificate in the Java // truststore. func WithJava() Option { t, _ := NewJavaTrust() return WithTrust(t) } // WithFirefox enables the install or uninstall of a certificate in the Firefox // truststore. func WithFirefox() Option { t, _ := NewNSSTrust() return WithTrust(t) } // WithNoSystem disables the install or uninstall of a certificate in the system // truststore. func WithNoSystem() Option { return func(o *options) { o.withNoSystem = true } } // WithDebug enables debug logging messages. func WithDebug() Option { return func(o *options) { enableDebug = true } } // WithPrefix sets a custom prefix for the truststore name. func WithPrefix(s string) Option { return func(o *options) { prefix = s } } func uniqueName(cert *x509.Certificate) string { switch { case prefix != "": return prefix + cert.SerialNumber.String() case cert.Subject.CommonName != "": return cert.Subject.CommonName + " " + cert.SerialNumber.String() default: return "Truststore Development CA " + cert.SerialNumber.String() } } func saveTempCert(cert *x509.Certificate) (string, func(), error) { f, err := ioutil.TempFile(os.TempDir(), "truststore.*.pem") if err != nil { return "", func() {}, err } name := f.Name() clean := func() { os.Remove(name) } data := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }) n, err := f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if err1 := f.Close(); err == nil { err = err1 } return name, clean, err } truststore-0.9.6/truststore_darwin.go000066400000000000000000000071371370371432400201310ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. // Copyright (c) 2018 The mkcert Authors. All rights reserved. package truststore import ( "bytes" "crypto/x509" "encoding/asn1" "fmt" "io/ioutil" "os" "os/exec" plist "howett.net/plist" ) var ( // NSSProfile is the path of the Firefox profiles. NSSProfile = os.Getenv("HOME") + "/Library/Application Support/Firefox/Profiles/*" // CertutilInstallHelp is the command to run on macOS to add NSS support. CertutilInstallHelp = "brew install nss" ) // https://github.com/golang/go/issues/24652#issuecomment-399826583 var trustSettings []interface{} var _, _ = plist.Unmarshal(trustSettingsData, &trustSettings) var trustSettingsData = []byte(` kSecTrustSettingsPolicy KoZIhvdjZAED kSecTrustSettingsPolicyName sslServer kSecTrustSettingsResult 1 kSecTrustSettingsPolicy KoZIhvdjZAEC kSecTrustSettingsPolicyName basicX509 kSecTrustSettingsResult 1 `) func installPlatform(filename string, cert *x509.Certificate) error { cmd := exec.Command("sudo", "security", "add-trusted-cert", "-d", "-k", "/Library/Keychains/System.keychain", filename) out, err := cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } // Make trustSettings explicit, as older Go does not know the defaults. // https://github.com/golang/go/issues/24652 plistFile, err := ioutil.TempFile("", "trust-settings") if err != nil { return wrapError(err, "failed to create temp file") } defer os.Remove(plistFile.Name()) cmd = exec.Command("sudo", "security", "trust-settings-export", "-d", plistFile.Name()) out, err = cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } plistData, err := ioutil.ReadFile(plistFile.Name()) if err != nil { return wrapError(err, "failed to read trust settings") } var plistRoot map[string]interface{} _, err = plist.Unmarshal(plistData, &plistRoot) if err != nil { return wrapError(err, "failed to parse trust settings") } if v, ok := plistRoot["trustVersion"].(uint64); v != 1 || !ok { return fmt.Errorf("unsupported trust settings version: %v", plistRoot["trustVersion"]) } trustList := plistRoot["trustList"].(map[string]interface{}) rootSubjectASN1, _ := asn1.Marshal(cert.Subject.ToRDNSequence()) for key := range trustList { entry := trustList[key].(map[string]interface{}) if _, ok := entry["issuerName"]; !ok { continue } issuerName := entry["issuerName"].([]byte) if !bytes.Equal(rootSubjectASN1, issuerName) { continue } entry["trustSettings"] = trustSettings break } plistData, err = plist.MarshalIndent(plistRoot, plist.XMLFormat, "\t") if err != nil { return wrapError(err, "failed to serialize trust settings") } err = ioutil.WriteFile(plistFile.Name(), plistData, 0600) if err != nil { return wrapError(err, "failed to write trust settings") } cmd = exec.Command("sudo", "security", "trust-settings-import", "-d", plistFile.Name()) out, err = cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } debug("certificate installed properly in macOS keychain") return nil } func uninstallPlatform(filename string, cert *x509.Certificate) error { cmd := exec.Command("sudo", "security", "remove-trusted-cert", "-d", filename) out, err := cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } debug("certificate uninstalled properly from macOS keychain") return nil } truststore-0.9.6/truststore_java.go000066400000000000000000000104441370371432400175610ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. // Copyright (c) 2018 The mkcert Authors. All rights reserved. package truststore import ( "bytes" "crypto/sha1" "crypto/sha256" "crypto/x509" "encoding/hex" "fmt" "hash" "os" "os/exec" "path/filepath" "runtime" "strings" ) // JavaStorePass is the default store password of the keystore. var JavaStorePass = "changeit" // JavaTrust implements a Trust for the Java runtime. type JavaTrust struct { keytoolPath string cacertsPath string } // NewJavaTrust initializes a new JavaTrust if the environment has java installed. func NewJavaTrust() (*JavaTrust, error) { home := os.Getenv("JAVA_HOME") if home == "" { return nil, ErrTrustNotFound } var keytoolPath, cacertsPath string if runtime.GOOS == "windows" { keytoolPath = filepath.Join(home, "bin", "keytool.exe") } else { keytoolPath = filepath.Join(home, "bin", "keytool") } if _, err := os.Stat(keytoolPath); err != nil { return nil, ErrTrustNotFound } _, err := os.Stat(filepath.Join(home, "lib", "security", "cacerts")) if err == nil { cacertsPath = filepath.Join(home, "lib", "security", "cacerts") } _, err = os.Stat(filepath.Join(home, "jre", "lib", "security", "cacerts")) if err == nil { cacertsPath = filepath.Join(home, "jre", "lib", "security", "cacerts") } return &JavaTrust{ keytoolPath: keytoolPath, cacertsPath: cacertsPath, }, nil } // Name implement the Trust interface. func (t *JavaTrust) Name() string { return "java" } // Install implements the Trust interface. func (t *JavaTrust) Install(filename string, cert *x509.Certificate) error { args := []string{ "-importcert", "-noprompt", "-keystore", t.cacertsPath, "-storepass", JavaStorePass, "-file", filename, "-alias", uniqueName(cert), } cmd := exec.Command(t.keytoolPath, args...) if out, err := execKeytool(cmd); err != nil { return NewCmdError(err, cmd, out) } debug("certificate installed properly in Java keystore") return nil } // Uninstall implements the Trust interface. func (t *JavaTrust) Uninstall(filename string, cert *x509.Certificate) error { args := []string{ "-delete", "-alias", uniqueName(cert), "-keystore", t.cacertsPath, "-storepass", JavaStorePass, } cmd := exec.Command(t.keytoolPath, args...) out, err := execKeytool(cmd) if bytes.Contains(out, []byte("does not exist")) { return nil } if err != nil { return NewCmdError(err, cmd, out) } debug("certificate uninstalled properly from the Java keystore") return nil } // Exists implements the Trust interface. func (t *JavaTrust) Exists(cert *x509.Certificate) bool { if t == nil { return false } // exists returns true if the given x509.Certificate's fingerprint // is in the keytool -list output exists := func(c *x509.Certificate, h hash.Hash, keytoolOutput []byte) bool { h.Write(c.Raw) fp := strings.ToUpper(hex.EncodeToString(h.Sum(nil))) return bytes.Contains(keytoolOutput, []byte(fp)) } cmd := exec.Command(t.keytoolPath, "-list", "-keystore", t.cacertsPath, "-storepass", JavaStorePass) keytoolOutput, err := cmd.CombinedOutput() if err != nil { debug("failed to execute \"keytool -list\": %s\n\n%s", err, keytoolOutput) return false } // keytool outputs SHA1 and SHA256 (Java 9+) certificates in uppercase hex // with each octet pair delimitated by ":". Drop them from the keytool output keytoolOutput = bytes.Replace(keytoolOutput, []byte(":"), nil, -1) // pre-Java 9 uses SHA1 fingerprints s1, s256 := sha1.New(), sha256.New() return exists(cert, s1, keytoolOutput) || exists(cert, s256, keytoolOutput) } // PreCheck implements the Trust interface. func (t *JavaTrust) PreCheck() error { if t != nil { return nil } return fmt.Errorf("define JAVA_HOME environment variable to use the Java trust") } // execKeytool will execute a "keytool" command and if needed re-execute // the command wrapped in 'sudo' to work around file permissions. func execKeytool(cmd *exec.Cmd) ([]byte, error) { out, err := cmd.CombinedOutput() if err != nil && bytes.Contains(out, []byte("java.io.FileNotFoundException")) && runtime.GOOS != "windows" { origArgs := cmd.Args[1:] cmd = exec.Command("sudo", cmd.Path) cmd.Args = append(cmd.Args, origArgs...) cmd.Env = []string{ "JAVA_HOME=" + os.Getenv("JAVA_HOME"), } out, err = cmd.CombinedOutput() } return out, err } truststore-0.9.6/truststore_linux.go000066400000000000000000000056431370371432400200040ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. // Copyright (c) 2018 The mkcert Authors. All rights reserved. package truststore import ( "bytes" "crypto/x509" "fmt" "io/ioutil" "os" "os/exec" "strings" ) var ( // NSSProfile is the path of the Firefox profiles. NSSProfile = os.Getenv("HOME") + "/.mozilla/firefox/*" // CertutilInstallHelp is the command to run on linux to add NSS support. CertutilInstallHelp = `apt install libnss3-tools" or "yum install nss-tools` // SystemTrustFilename is the format used to name the root certificates. SystemTrustFilename string // SystemTrustCommand is the command used to update the system truststore. SystemTrustCommand []string ) func init() { if pathExists("/etc/pki/ca-trust/source/anchors/") { SystemTrustFilename = "/etc/pki/ca-trust/source/anchors/%s.pem" SystemTrustCommand = []string{"update-ca-trust", "extract"} } else if pathExists("/usr/local/share/ca-certificates/") { SystemTrustFilename = "/usr/local/share/ca-certificates/%s.crt" SystemTrustCommand = []string{"update-ca-certificates"} } else if pathExists("/etc/ca-certificates/trust-source/anchors/") { SystemTrustFilename = "/etc/ca-certificates/trust-source/anchors/%s.crt" SystemTrustCommand = []string{"trust", "extract-compat"} } if SystemTrustCommand != nil { _, err := exec.LookPath(SystemTrustCommand[0]) if err != nil { SystemTrustCommand = nil } } } func pathExists(path string) bool { _, err := os.Stat(path) return err == nil } func systemTrustFilename(cert *x509.Certificate) string { return fmt.Sprintf(SystemTrustFilename, strings.Replace(uniqueName(cert), " ", "_", -1)) } func installPlatform(filename string, cert *x509.Certificate) error { if SystemTrustCommand == nil { return ErrNotSupported } data, err := ioutil.ReadFile(filename) if err != nil { return err } cmd := CommandWithSudo("tee", systemTrustFilename(cert)) cmd.Stdin = bytes.NewReader(data) out, err := cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } cmd = CommandWithSudo(SystemTrustCommand...) out, err = cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } debug("certificate installed properly in linux trusts") return nil } func uninstallPlatform(filename string, cert *x509.Certificate) error { if SystemTrustCommand == nil { return ErrNotSupported } cmd := CommandWithSudo("rm", "-f", systemTrustFilename(cert)) out, err := cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } cmd = CommandWithSudo(SystemTrustCommand...) out, err = cmd.CombinedOutput() if err != nil { return NewCmdError(err, cmd, out) } debug("certificate uninstalled properly from linux trusts") return nil } func CommandWithSudo(cmd ...string) *exec.Cmd { if _, err := exec.LookPath("sudo"); err != nil { return exec.Command(cmd[0], cmd[1:]...) } return exec.Command("sudo", append([]string{"--"}, cmd...)...) } truststore-0.9.6/truststore_nss.go000066400000000000000000000100411370371432400174340ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. // Copyright (c) 2018 The mkcert Authors. All rights reserved. package truststore import ( "crypto/x509" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" ) var nssDB = filepath.Join(os.Getenv("HOME"), ".pki/nssdb") // NSSTrust implements a Trust for Firefox or other NSS based applications. type NSSTrust struct { certutilPath string } // NewNSSTrust creates a new NSSTrust. func NewNSSTrust() (*NSSTrust, error) { var err error var certutilPath string switch runtime.GOOS { case "darwin": certutilPath, err = exec.LookPath("certutil") if err != nil { cmd := exec.Command("brew", "--prefix", "nss") out, err1 := cmd.Output() if err1 != nil { return nil, NewCmdError(err1, cmd, out) } certutilPath = filepath.Join(strings.TrimSpace(string(out)), "bin", "certutil") if _, err = os.Stat(certutilPath); err != nil { return nil, err } } case "linux": if certutilPath, err = exec.LookPath("certutil"); err != nil { return nil, err } default: return nil, ErrTrustNotSupported } return &NSSTrust{ certutilPath: certutilPath, }, nil } // Name implements the Trust interface. func (t *NSSTrust) Name() string { return "nss" } // Install implements the Trust interface. func (t *NSSTrust) Install(filename string, cert *x509.Certificate) error { // install certificate in all profiles if forEachNSSProfile(func(profile string) { cmd := exec.Command(t.certutilPath, "-A", "-d", profile, "-t", "C,,", "-n", uniqueName(cert), "-i", filename) out, err := cmd.CombinedOutput() if err != nil { debug("failed to execute \"certutil -A\": %s\n\n%s", err, out) } }) == 0 { return fmt.Errorf("not NSS security databases found") } // check for the cert in all profiles if !t.Exists(cert) { return fmt.Errorf("certificate cannot be installed in NSS security databases") } debug("certificate installed properly in NSS security databases") return nil } // Uninstall implements the Trust interface. func (t *NSSTrust) Uninstall(filename string, cert *x509.Certificate) (err error) { forEachNSSProfile(func(profile string) { if err != nil { return } // skip if not found if err := exec.Command(t.certutilPath, "-V", "-d", profile, "-u", "L", "-n", uniqueName(cert)).Run(); err != nil { return } // delete certificate cmd := exec.Command(t.certutilPath, "-D", "-d", profile, "-n", uniqueName(cert)) out, err1 := cmd.CombinedOutput() if err1 != nil { err = NewCmdError(err1, cmd, out) } }) if err == nil { debug("certificate uninstalled properly from NSS security databases") } return } // Exists implements the Trust interface. Exists checks if the certificate is // already installed. func (t *NSSTrust) Exists(cert *x509.Certificate) bool { success := true if forEachNSSProfile(func(profile string) { err := exec.Command(t.certutilPath, "-V", "-d", profile, "-u", "L", "-n", uniqueName(cert)).Run() if err != nil { success = false } }) == 0 { success = false } return success } // PreCheck implements the Trust interface. func (t *NSSTrust) PreCheck() error { if t != nil { if forEachNSSProfile(func(_ string) {}) == 0 { return fmt.Errorf("not NSS security databases found") } return nil } if CertutilInstallHelp == "" { return fmt.Errorf("Note: NSS support is not available on your platform") } return fmt.Errorf(`Warning: "certutil" is not available, install "certutil" with "%s" and try again`, CertutilInstallHelp) } func forEachNSSProfile(f func(profile string)) (found int) { profiles, _ := filepath.Glob(NSSProfile) if _, err := os.Stat(nssDB); err == nil { profiles = append(profiles, nssDB) } if len(profiles) == 0 { return } for _, profile := range profiles { if stat, err := os.Stat(profile); err != nil || !stat.IsDir() { continue } if _, err := os.Stat(filepath.Join(profile, "cert9.db")); err == nil { f("sql:" + profile) found++ continue } if _, err := os.Stat(filepath.Join(profile, "cert8.db")); err == nil { f("dbm:" + profile) found++ } } return } truststore-0.9.6/truststore_others.go000066400000000000000000000006761370371432400201520ustar00rootroot00000000000000// +build !linux,!darwin,!windows package truststore import "crypto/x509" var ( // NSSProfile is the path of the Firefox profiles. NSSProfile = "" // CertutilInstallHelp is the command to add NSS support. CertutilInstallHelp = "" ) func installPlatform(filename string, cert *x509.Certificate) error { return ErrTrustNotSupported } func uninstallPlatform(filename string, cert *x509.Certificate) error { return ErrTrustNotSupported } truststore-0.9.6/truststore_windows.go000066400000000000000000000111461370371432400203320ustar00rootroot00000000000000// Copyright (c) 2018 The truststore Authors. All rights reserved. // Copyright (c) 2018 The mkcert Authors. All rights reserved. package truststore import ( "crypto/x509" "fmt" "math/big" "os" "syscall" "unsafe" ) var ( // NSSProfile is the path of the Firefox profiles. NSSProfile = os.Getenv("USERPROFILE") + "\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles" // CertutilInstallHelp is the command to run on windows to add NSS support. // Certutils is not supported on Windows. CertutilInstallHelp = "" ) var ( modcrypt32 = syscall.NewLazyDLL("crypt32.dll") procCertAddEncodedCertificateToStore = modcrypt32.NewProc("CertAddEncodedCertificateToStore") procCertCloseStore = modcrypt32.NewProc("CertCloseStore") procCertDeleteCertificateFromStore = modcrypt32.NewProc("CertDeleteCertificateFromStore") procCertDuplicateCertificateContext = modcrypt32.NewProc("CertDuplicateCertificateContext") procCertEnumCertificatesInStore = modcrypt32.NewProc("CertEnumCertificatesInStore") procCertOpenSystemStoreW = modcrypt32.NewProc("CertOpenSystemStoreW") ) func installPlatform(filename string, cert *x509.Certificate) error { // Open root store store, err := openWindowsRootStore() if err != nil { return wrapError(err, "open root store failed") } defer store.close() // Add cert if err := store.addCert(cert.Raw); err != nil { return wrapError(err, "add cert failed") } debug("certificate installed properly in windows trusts") return nil } func uninstallPlatform(filename string, cert *x509.Certificate) error { // We'll just remove all certs with the same serial number // Open root store store, err := openWindowsRootStore() if err != nil { return wrapError(err, "open root store failed") } defer store.close() // Do the deletion deletedAny, err := store.deleteCertsWithSerial(cert.SerialNumber) if err != nil { return wrapError(err, "delete cert failed") } if !deletedAny { return ErrNotFound } debug("certificate uninstalled properly from windows trusts") return nil } type windowsRootStore uintptr func openWindowsRootStore() (windowsRootStore, error) { store, _, err := procCertOpenSystemStoreW.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ROOT")))) if store != 0 { return windowsRootStore(store), nil } return 0, fmt.Errorf("cannot open windows root store: %v", err) } func (w windowsRootStore) close() error { ret, _, err := procCertCloseStore.Call(uintptr(w), 0) if ret != 0 { return nil } return fmt.Errorf("cannot close windows root store: %v", err) } func (w windowsRootStore) addCert(cert []byte) error { // TODO: ok to always overwrite? ret, _, err := procCertAddEncodedCertificateToStore.Call( uintptr(w), // HCERTSTORE hCertStore uintptr(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING), // DWORD dwCertEncodingType uintptr(unsafe.Pointer(&cert[0])), // const BYTE *pbCertEncoded uintptr(len(cert)), // DWORD cbCertEncoded 3, // DWORD dwAddDisposition (CERT_STORE_ADD_REPLACE_EXISTING is 3) 0, // PCCERT_CONTEXT *ppCertContext ) if ret != 0 { return nil } return fmt.Errorf("Failed adding cert: %v", err) } func (w windowsRootStore) deleteCertsWithSerial(serial *big.Int) (bool, error) { // Go over each, deleting the ones we find var cert *syscall.CertContext deletedAny := false for { // Next enum certPtr, _, err := procCertEnumCertificatesInStore.Call(uintptr(w), uintptr(unsafe.Pointer(cert))) if cert = (*syscall.CertContext)(unsafe.Pointer(certPtr)); cert == nil { if errno, ok := err.(syscall.Errno); ok && errno == 0x80092004 { break } return deletedAny, fmt.Errorf("Failed enumerating certs: %v", err) } // Parse cert certBytes := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length] parsedCert, err := x509.ParseCertificate(certBytes) // We'll just ignore parse failures for now if err == nil && parsedCert.SerialNumber != nil && parsedCert.SerialNumber.Cmp(serial) == 0 { // Duplicate the context so it doesn't stop the enum when we delete it dupCertPtr, _, err := procCertDuplicateCertificateContext.Call(uintptr(unsafe.Pointer(cert))) if dupCertPtr == 0 { return deletedAny, fmt.Errorf("Failed duplicating context: %v", err) } if ret, _, err := procCertDeleteCertificateFromStore.Call(dupCertPtr); ret == 0 { return deletedAny, fmt.Errorf("Failed deleting certificate: %v", err) } deletedAny = true } } return deletedAny, nil }