From e5c7c09968bbc5a2c156569371f72cdf783fe1b0 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Sat, 10 Jun 2017 12:37:52 +0000 Subject: [PATCH 1/5] certinject: add support for the shared NSS trust store on GNU/Linux systems. --- certinject/certinject_linux.go | 34 +++++++++++++ certinject/certinject_misc.go | 2 +- certinject/nss_shared_linux.go | 89 ++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 certinject/certinject_linux.go create mode 100644 certinject/nss_shared_linux.go diff --git a/certinject/certinject_linux.go b/certinject/certinject_linux.go new file mode 100644 index 0000000..71ddb30 --- /dev/null +++ b/certinject/certinject_linux.go @@ -0,0 +1,34 @@ +package certinject + +import "gopkg.in/hlandau/easyconfig.v1/cflag" +import "github.com/hlandau/xlog" + + +// This package is used to add and remove certificates to the system trust +// store. +// Currently only supports the system NSS store. + +var log, Log = xlog.New("ncdns.certinject") + +var ( + flagGroup = cflag.NewGroup(nil, "certstore") + nssSharedFlag = cflag.Bool(flagGroup, "nss-shared", false, "Synchronize TLS certs to the NSS shared trust store? This enables HTTPS to work with Chromium/Chrome. Only use if you've set up null HPKP in Chromium/Chrome as per documentation. If you haven't set up null HPKP, or if you access ncdns from browsers not based on Chromium, this is unsafe and should not be used.") + certExpirePeriod = cflag.Int(flagGroup, "expire", 60 * 30, "Duration (in seconds) after which TLS certs will be removed from the trust store. Making this smaller than the DNS TTL (default 600) may cause TLS errors.") +) + +// Injects the given cert into all configured trust stores. +func InjectCert(derBytes []byte) { + + if nssSharedFlag.Value() { + injectCertNssShared(derBytes) + } +} + +// Cleans expired certs from all configured trust stores. +func CleanCerts() { + + if nssSharedFlag.Value() { + cleanCertsNssShared() + } + +} diff --git a/certinject/certinject_misc.go b/certinject/certinject_misc.go index ff40bc5..c017530 100644 --- a/certinject/certinject_misc.go +++ b/certinject/certinject_misc.go @@ -1,4 +1,4 @@ -// +build !windows +// +build !windows,!linux package certinject diff --git a/certinject/nss_shared_linux.go b/certinject/nss_shared_linux.go new file mode 100644 index 0000000..1d60885 --- /dev/null +++ b/certinject/nss_shared_linux.go @@ -0,0 +1,89 @@ +package certinject + +import "crypto/sha256" +import "encoding/hex" +import "io/ioutil" +import "os" +import "os/exec" +import "strings" +import "math" +import "time" + +var homeDir = os.Getenv("HOME") + +var certDir = homeDir + "/.ncdns/certs" +var nssDir = "sql:" + homeDir + "/.pki/nssdb" + +func injectCertNssShared(derBytes []byte) { + + fingerprint := sha256.Sum256(derBytes) + + fingerprintHex := hex.EncodeToString(fingerprint[:]) + + path := certDir + "/" + fingerprintHex + ".pem" + + injectCertFile(derBytes, path) + + nickname := nicknameFromFingerprintHexNssShared(fingerprintHex) + + cmd := exec.Command("certutil", "-d", nssDir, "-A", "-t", "CP,,", "-n", nickname, "-a", "-i", path) + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + +} + +func cleanCertsNssShared() { + + certFiles, _ := ioutil.ReadDir(certDir + "/") + + // for all Namecoin certs in the folder + for _, f := range certFiles { + + // Check if the cert is expired + expired, err := checkCertExpiredNssShared(f) + if err != nil { + log.Fatal(err) + } + + // delete the cert if it's expired + if expired { + + filename := f.Name() + + fingerprintHex := strings.Replace(filename, ".pem", "", -1) + + nickname := nicknameFromFingerprintHexNssShared(fingerprintHex) + + // Delete the cert from NSS + cmd := exec.Command("certutil", "-d", nssDir, "-D", "-n", nickname) + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + + // Also delete the cert from the filesystem + err = os.Remove(certDir + "/" + filename) + } + } + +} + +func checkCertExpiredNssShared(certFile os.FileInfo) (bool, error) { + + // Get the last modified time + certFileModTime := certFile.ModTime() + + // If the cert's last modified timestamp differs too much from the current time in either direction, consider it expired + expired := math.Abs( time.Since(certFileModTime).Seconds() ) > float64(certExpirePeriod.Value()) + + return expired, nil + +} + +func nicknameFromFingerprintHexNssShared(fingerprintHex string) string { + return "Namecoin-" + fingerprintHex +} From 2c8b5fe5eedb2377530ac3156daa56b08390a84a Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Sat, 10 Feb 2018 06:17:24 +0000 Subject: [PATCH 2/5] certinject: NSS improvements, now works on arbitrary NSS cert store directories. --- certinject/certinject.go | 11 +++ certinject/certinject_linux.go | 31 +-------- certinject/certinject_misc.go | 15 ---- certinject/certinject_notlinux.go | 7 ++ certinject/certinject_notwindows.go | 28 ++++++++ certinject/certinject_windows.go | 12 ++-- certinject/nss.go | 103 ++++++++++++++++++++++++++++ certinject/nss_notwindows.go | 7 ++ certinject/nss_shared_linux.go | 89 ------------------------ certinject/nss_windows.go | 5 ++ 10 files changed, 170 insertions(+), 138 deletions(-) create mode 100644 certinject/certinject.go delete mode 100644 certinject/certinject_misc.go create mode 100644 certinject/certinject_notlinux.go create mode 100644 certinject/certinject_notwindows.go create mode 100644 certinject/nss.go create mode 100644 certinject/nss_notwindows.go delete mode 100644 certinject/nss_shared_linux.go create mode 100644 certinject/nss_windows.go diff --git a/certinject/certinject.go b/certinject/certinject.go new file mode 100644 index 0000000..513a9a0 --- /dev/null +++ b/certinject/certinject.go @@ -0,0 +1,11 @@ +package certinject + +import ( + "gopkg.in/hlandau/easyconfig.v1/cflag" +) + +var ( + flagGroup = cflag.NewGroup(nil, "certstore") + nssFlag = cflag.Bool(flagGroup, "nss", false, nssExplain) + certExpirePeriod = cflag.Int(flagGroup, "expire", 60 * 30, "Duration (in seconds) after which TLS certs will be removed from the trust store. Making this smaller than the DNS TTL (default 600) may cause TLS errors.") +) diff --git a/certinject/certinject_linux.go b/certinject/certinject_linux.go index 71ddb30..fb175f4 100644 --- a/certinject/certinject_linux.go +++ b/certinject/certinject_linux.go @@ -1,34 +1,5 @@ package certinject -import "gopkg.in/hlandau/easyconfig.v1/cflag" -import "github.com/hlandau/xlog" - - -// This package is used to add and remove certificates to the system trust -// store. -// Currently only supports the system NSS store. - -var log, Log = xlog.New("ncdns.certinject") - var ( - flagGroup = cflag.NewGroup(nil, "certstore") - nssSharedFlag = cflag.Bool(flagGroup, "nss-shared", false, "Synchronize TLS certs to the NSS shared trust store? This enables HTTPS to work with Chromium/Chrome. Only use if you've set up null HPKP in Chromium/Chrome as per documentation. If you haven't set up null HPKP, or if you access ncdns from browsers not based on Chromium, this is unsafe and should not be used.") - certExpirePeriod = cflag.Int(flagGroup, "expire", 60 * 30, "Duration (in seconds) after which TLS certs will be removed from the trust store. Making this smaller than the DNS TTL (default 600) may cause TLS errors.") + nssExplain = "Synchronize TLS certs to an NSS sqlite3 trust store? This enables HTTPS to work with NSS web browsers such as Chromium/Chrome. Only use if you've set up NUMS HPKP in Chromium/Chrome as per documentation. If you haven't set up NUMS HPKP, or if you access the configured NSS sqlite3 trust store from browsers not based on Chromium, this is unsafe and should not be used." ) - -// Injects the given cert into all configured trust stores. -func InjectCert(derBytes []byte) { - - if nssSharedFlag.Value() { - injectCertNssShared(derBytes) - } -} - -// Cleans expired certs from all configured trust stores. -func CleanCerts() { - - if nssSharedFlag.Value() { - cleanCertsNssShared() - } - -} diff --git a/certinject/certinject_misc.go b/certinject/certinject_misc.go deleted file mode 100644 index c017530..0000000 --- a/certinject/certinject_misc.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !windows,!linux - -package certinject - -import "github.com/hlandau/xlog" - -var log, Log = xlog.New("ncdns.certinject") - -func InjectCert(derBytes []byte) { - -} - -func CleanCerts() { - -} diff --git a/certinject/certinject_notlinux.go b/certinject/certinject_notlinux.go new file mode 100644 index 0000000..5368a65 --- /dev/null +++ b/certinject/certinject_notlinux.go @@ -0,0 +1,7 @@ +// +build !linux + +package certinject + +var ( + nssExplain = "(Unsafe and experimental!) Synchronize TLS certs to an NSS sqlite3 trust store? This enables HTTPS to work with some NSS-based software. This is currently unsafe and should not be used." +) diff --git a/certinject/certinject_notwindows.go b/certinject/certinject_notwindows.go new file mode 100644 index 0000000..23f7f51 --- /dev/null +++ b/certinject/certinject_notwindows.go @@ -0,0 +1,28 @@ +// +build !windows + +package certinject + +import "github.com/hlandau/xlog" + +// This package is used to add and remove certificates to the system trust +// store. +// Currently only supports NSS sqlite3 stores. + +var log, Log = xlog.New("ncdns.certinject") + +// Injects the given cert into all configured trust stores. +func InjectCert(derBytes []byte) { + + if nssFlag.Value() { + injectCertNss(derBytes) + } +} + +// Cleans expired certs from all configured trust stores. +func CleanCerts() { + + if nssFlag.Value() { + cleanCertsNss() + } + +} diff --git a/certinject/certinject_windows.go b/certinject/certinject_windows.go index 8811560..288b8ad 100644 --- a/certinject/certinject_windows.go +++ b/certinject/certinject_windows.go @@ -7,14 +7,12 @@ import ( // This package is used to add and remove certificates to the system trust // store. -// Currently only supports Windows CryptoAPI. +// Currently only supports Windows CryptoAPI and NSS sqlite3 stores. var log, Log = xlog.New("ncdns.certinject") var ( - flagGroup = cflag.NewGroup(nil, "certstore") - cryptoApiFlag = cflag.Bool(flagGroup, "cryptoapi", false, "Synchronize TLS certs to the CryptoAPI trust store? This enables HTTPS to work with Chromium/Chrome. Only use if you've set up null HPKP in Chromium/Chrome as per documentation. If you haven't set up null HPKP, or if you access ncdns from browsers not based on Chromium or Firefox, this is unsafe and should not be used.") - certExpirePeriod = cflag.Int(flagGroup, "expire", 60*30, "Duration (in seconds) after which TLS certs will be removed from the trust store. Making this smaller than the DNS TTL (default 600) may cause TLS errors.") + cryptoApiFlag = cflag.Bool(flagGroup, "cryptoapi", false, "Synchronize TLS certs to the CryptoAPI trust store? This enables HTTPS to work with Chromium/Chrome. Only use if you've set up NUMS HPKP in Chromium/Chrome as per documentation. If you haven't set up NUMS HPKP, or if you access ncdns from browsers not based on Chromium or Firefox, this is unsafe and should not be used.") ) // Injects the given cert into all configured trust stores. @@ -23,6 +21,9 @@ func InjectCert(derBytes []byte) { if cryptoApiFlag.Value() { injectCertCryptoApi(derBytes) } + if nssFlag.Value() { + injectCertNss(derBytes) + } } // Cleans expired certs from all configured trust stores. @@ -31,5 +32,8 @@ func CleanCerts() { if cryptoApiFlag.Value() { cleanCertsCryptoApi() } + if nssFlag.Value() { + cleanCertsNss() + } } diff --git a/certinject/nss.go b/certinject/nss.go new file mode 100644 index 0000000..a6c54ed --- /dev/null +++ b/certinject/nss.go @@ -0,0 +1,103 @@ +package certinject + +import "crypto/sha256" +import "encoding/hex" +import "io/ioutil" +import "os" +import "os/exec" +import "strings" +import "math" +import "time" +import "gopkg.in/hlandau/easyconfig.v1/cflag" + +var certDir = cflag.String(flagGroup, "nsscertdir", "", "Directory to store certificate files. Only use a directory that only ncdns can write to. (Required if nss is set.)") +var nssDir = cflag.String(flagGroup, "nssdbdir", "", "Directory that contains NSS's cert9.db. (Required if nss is set.)") + +func injectCertNss(derBytes []byte) { + + if certDir.Value() == "" { + log.Fatal("Empty nsscertdir configuration.") + } + if nssDir.Value() == "" { + log.Fatal("Empty nssdbdir configuration.") + } + + fingerprint := sha256.Sum256(derBytes) + + fingerprintHex := hex.EncodeToString(fingerprint[:]) + + path := certDir.Value() + "/" + fingerprintHex + ".pem" + + injectCertFile(derBytes, path) + + nickname := nicknameFromFingerprintHexNss(fingerprintHex) + + // TODO: check whether we can replace CP with just P. + cmd := exec.Command(nssCertutilName, "-d", "sql:" + nssDir.Value(), "-A", "-t", "CP,,", "-n", nickname, "-a", "-i", path) + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + +} + +func cleanCertsNss() { + + if certDir.Value() == "" { + log.Fatal("Empty nsscertdir configuration.") + } + if nssDir.Value() == "" { + log.Fatal("Empty nssdbdir configuration.") + } + + certFiles, _ := ioutil.ReadDir(certDir.Value() + "/") + + // for all Namecoin certs in the folder + for _, f := range certFiles { + + // Check if the cert is expired + expired, err := checkCertExpiredNss(f) + if err != nil { + log.Fatal(err) + } + + // delete the cert if it's expired + if expired { + + filename := f.Name() + + fingerprintHex := strings.Replace(filename, ".pem", "", -1) + + nickname := nicknameFromFingerprintHexNss(fingerprintHex) + + // Delete the cert from NSS + cmd := exec.Command(nssCertutilName, "-d", "sql:" + nssDir.Value(), "-D", "-n", nickname) + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + + // Also delete the cert from the filesystem + err = os.Remove(certDir.Value() + "/" + filename) + } + } + +} + +func checkCertExpiredNss(certFile os.FileInfo) (bool, error) { + + // Get the last modified time + certFileModTime := certFile.ModTime() + + // If the cert's last modified timestamp differs too much from the current time in either direction, consider it expired + expired := math.Abs( time.Since(certFileModTime).Seconds() ) > float64(certExpirePeriod.Value()) + + return expired, nil + +} + +func nicknameFromFingerprintHexNss(fingerprintHex string) string { + return "Namecoin-" + fingerprintHex +} diff --git a/certinject/nss_notwindows.go b/certinject/nss_notwindows.go new file mode 100644 index 0000000..930bf90 --- /dev/null +++ b/certinject/nss_notwindows.go @@ -0,0 +1,7 @@ +// +build !windows + +package certinject + +var ( + nssCertutilName = "certutil" +) diff --git a/certinject/nss_shared_linux.go b/certinject/nss_shared_linux.go deleted file mode 100644 index 1d60885..0000000 --- a/certinject/nss_shared_linux.go +++ /dev/null @@ -1,89 +0,0 @@ -package certinject - -import "crypto/sha256" -import "encoding/hex" -import "io/ioutil" -import "os" -import "os/exec" -import "strings" -import "math" -import "time" - -var homeDir = os.Getenv("HOME") - -var certDir = homeDir + "/.ncdns/certs" -var nssDir = "sql:" + homeDir + "/.pki/nssdb" - -func injectCertNssShared(derBytes []byte) { - - fingerprint := sha256.Sum256(derBytes) - - fingerprintHex := hex.EncodeToString(fingerprint[:]) - - path := certDir + "/" + fingerprintHex + ".pem" - - injectCertFile(derBytes, path) - - nickname := nicknameFromFingerprintHexNssShared(fingerprintHex) - - cmd := exec.Command("certutil", "-d", nssDir, "-A", "-t", "CP,,", "-n", nickname, "-a", "-i", path) - - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - -} - -func cleanCertsNssShared() { - - certFiles, _ := ioutil.ReadDir(certDir + "/") - - // for all Namecoin certs in the folder - for _, f := range certFiles { - - // Check if the cert is expired - expired, err := checkCertExpiredNssShared(f) - if err != nil { - log.Fatal(err) - } - - // delete the cert if it's expired - if expired { - - filename := f.Name() - - fingerprintHex := strings.Replace(filename, ".pem", "", -1) - - nickname := nicknameFromFingerprintHexNssShared(fingerprintHex) - - // Delete the cert from NSS - cmd := exec.Command("certutil", "-d", nssDir, "-D", "-n", nickname) - - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - - // Also delete the cert from the filesystem - err = os.Remove(certDir + "/" + filename) - } - } - -} - -func checkCertExpiredNssShared(certFile os.FileInfo) (bool, error) { - - // Get the last modified time - certFileModTime := certFile.ModTime() - - // If the cert's last modified timestamp differs too much from the current time in either direction, consider it expired - expired := math.Abs( time.Since(certFileModTime).Seconds() ) > float64(certExpirePeriod.Value()) - - return expired, nil - -} - -func nicknameFromFingerprintHexNssShared(fingerprintHex string) string { - return "Namecoin-" + fingerprintHex -} diff --git a/certinject/nss_windows.go b/certinject/nss_windows.go new file mode 100644 index 0000000..3a5c223 --- /dev/null +++ b/certinject/nss_windows.go @@ -0,0 +1,5 @@ +package certinject + +var ( + nssCertutilName = "nss-certutil" +) From 145d1e32cd39e4674e1c311f5b394db759bfee7d Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 14 Feb 2018 05:03:04 +0000 Subject: [PATCH 3/5] certinject: Fix various issues found by static analysis. --- certinject/certinject.go | 11 +++++++---- certinject/certinject_linux.go | 8 +++++++- certinject/certinject_notlinux.go | 5 ++++- certinject/certinject_notwindows.go | 18 +++++++++--------- certinject/certinject_windows.go | 21 ++++++++++++++------- certinject/cryptoapi_windows.go | 3 ++- certinject/nss.go | 28 ++++++++++++++++++++-------- certinject/nss_notwindows.go | 2 +- certinject/nss_windows.go | 2 +- 9 files changed, 65 insertions(+), 33 deletions(-) diff --git a/certinject/certinject.go b/certinject/certinject.go index 513a9a0..c4da3be 100644 --- a/certinject/certinject.go +++ b/certinject/certinject.go @@ -1,11 +1,14 @@ package certinject import ( - "gopkg.in/hlandau/easyconfig.v1/cflag" + "gopkg.in/hlandau/easyconfig.v1/cflag" ) var ( - flagGroup = cflag.NewGroup(nil, "certstore") - nssFlag = cflag.Bool(flagGroup, "nss", false, nssExplain) - certExpirePeriod = cflag.Int(flagGroup, "expire", 60 * 30, "Duration (in seconds) after which TLS certs will be removed from the trust store. Making this smaller than the DNS TTL (default 600) may cause TLS errors.") + flagGroup = cflag.NewGroup(nil, "certstore") + nssFlag = cflag.Bool(flagGroup, "nss", false, nssExplain) + certExpirePeriod = cflag.Int(flagGroup, "expire", 60*30, "Duration "+ + "(in seconds) after which TLS certs will be removed from the "+ + "trust store. Making this smaller than the DNS TTL (default "+ + "600) may cause TLS errors.") ) diff --git a/certinject/certinject_linux.go b/certinject/certinject_linux.go index fb175f4..ede3d1c 100644 --- a/certinject/certinject_linux.go +++ b/certinject/certinject_linux.go @@ -1,5 +1,11 @@ package certinject var ( - nssExplain = "Synchronize TLS certs to an NSS sqlite3 trust store? This enables HTTPS to work with NSS web browsers such as Chromium/Chrome. Only use if you've set up NUMS HPKP in Chromium/Chrome as per documentation. If you haven't set up NUMS HPKP, or if you access the configured NSS sqlite3 trust store from browsers not based on Chromium, this is unsafe and should not be used." + nssExplain = "Synchronize TLS certs to an NSS sqlite3 trust store? " + + "This enables HTTPS to work with NSS web browsers such as " + + "Chromium/Chrome. Only use if you've set up NUMS HPKP in " + + "Chromium/Chrome as per documentation. If you haven't set " + + "up NUMS HPKP, or if you access the configured NSS sqlite3 " + + "trust store from browsers not based on Chromium, this is " + + "unsafe and should not be used." ) diff --git a/certinject/certinject_notlinux.go b/certinject/certinject_notlinux.go index 5368a65..28d7f19 100644 --- a/certinject/certinject_notlinux.go +++ b/certinject/certinject_notlinux.go @@ -3,5 +3,8 @@ package certinject var ( - nssExplain = "(Unsafe and experimental!) Synchronize TLS certs to an NSS sqlite3 trust store? This enables HTTPS to work with some NSS-based software. This is currently unsafe and should not be used." + nssExplain = "(Unsafe and experimental!) Synchronize TLS certs to " + + "an NSS sqlite3 trust store? This enables HTTPS to work " + + "with some NSS-based software. This is currently unsafe " + + "and should not be used." ) diff --git a/certinject/certinject_notwindows.go b/certinject/certinject_notwindows.go index 23f7f51..ee70a3e 100644 --- a/certinject/certinject_notwindows.go +++ b/certinject/certinject_notwindows.go @@ -4,25 +4,25 @@ package certinject import "github.com/hlandau/xlog" -// This package is used to add and remove certificates to the system trust +// This package is used to add and remove certificates to the system trust // store. // Currently only supports NSS sqlite3 stores. var log, Log = xlog.New("ncdns.certinject") -// Injects the given cert into all configured trust stores. +// InjectCert injects the given cert into all configured trust stores. func InjectCert(derBytes []byte) { - if nssFlag.Value() { - injectCertNss(derBytes) - } + if nssFlag.Value() { + injectCertNss(derBytes) + } } -// Cleans expired certs from all configured trust stores. +// CleanCerts cleans expired certs from all configured trust stores. func CleanCerts() { - if nssFlag.Value() { - cleanCertsNss() - } + if nssFlag.Value() { + cleanCertsNss() + } } diff --git a/certinject/certinject_windows.go b/certinject/certinject_windows.go index 288b8ad..2670ff6 100644 --- a/certinject/certinject_windows.go +++ b/certinject/certinject_windows.go @@ -12,28 +12,35 @@ import ( var log, Log = xlog.New("ncdns.certinject") var ( - cryptoApiFlag = cflag.Bool(flagGroup, "cryptoapi", false, "Synchronize TLS certs to the CryptoAPI trust store? This enables HTTPS to work with Chromium/Chrome. Only use if you've set up NUMS HPKP in Chromium/Chrome as per documentation. If you haven't set up NUMS HPKP, or if you access ncdns from browsers not based on Chromium or Firefox, this is unsafe and should not be used.") + cryptoApiFlag = cflag.Bool(flagGroup, "cryptoapi", false, + "Synchronize TLS certs to the CryptoAPI trust store? This "+ + "enables HTTPS to work with Chromium/Chrome. Only "+ + "use if you've set up NUMS HPKP in Chromium/Chrome "+ + "as per documentation. If you haven't set up NUMS "+ + "HPKP, or if you access ncdns from browsers not "+ + "based on Chromium or Firefox, this is unsafe and "+ + "should not be used.") ) -// Injects the given cert into all configured trust stores. +// InjectCert injects the given cert into all configured trust stores. func InjectCert(derBytes []byte) { if cryptoApiFlag.Value() { injectCertCryptoApi(derBytes) } if nssFlag.Value() { - injectCertNss(derBytes) - } + injectCertNss(derBytes) + } } -// Cleans expired certs from all configured trust stores. +// CleanCerts cleans expired certs from all configured trust stores. func CleanCerts() { if cryptoApiFlag.Value() { cleanCertsCryptoApi() } if nssFlag.Value() { - cleanCertsNss() - } + cleanCertsNss() + } } diff --git a/certinject/cryptoapi_windows.go b/certinject/cryptoapi_windows.go index 5451e50..5e52106 100644 --- a/certinject/cryptoapi_windows.go +++ b/certinject/cryptoapi_windows.go @@ -4,10 +4,11 @@ import ( "crypto/sha1" "encoding/hex" "fmt" - "golang.org/x/sys/windows/registry" "math" "strings" "time" + + "golang.org/x/sys/windows/registry" ) // In 64-bit Windows, this key is shared between 64-bit and 32-bit applications. diff --git a/certinject/nss.go b/certinject/nss.go index a6c54ed..63a4e8d 100644 --- a/certinject/nss.go +++ b/certinject/nss.go @@ -10,8 +10,11 @@ import "math" import "time" import "gopkg.in/hlandau/easyconfig.v1/cflag" -var certDir = cflag.String(flagGroup, "nsscertdir", "", "Directory to store certificate files. Only use a directory that only ncdns can write to. (Required if nss is set.)") -var nssDir = cflag.String(flagGroup, "nssdbdir", "", "Directory that contains NSS's cert9.db. (Required if nss is set.)") +var certDir = cflag.String(flagGroup, "nsscertdir", "", "Directory to store "+ + "certificate files. Only use a directory that only ncdns can write "+ + "to. (Required if nss is set.)") +var nssDir = cflag.String(flagGroup, "nssdbdir", "", "Directory that "+ + "contains NSS's cert9.db. (Required if nss is set.)") func injectCertNss(derBytes []byte) { @@ -33,7 +36,8 @@ func injectCertNss(derBytes []byte) { nickname := nicknameFromFingerprintHexNss(fingerprintHex) // TODO: check whether we can replace CP with just P. - cmd := exec.Command(nssCertutilName, "-d", "sql:" + nssDir.Value(), "-A", "-t", "CP,,", "-n", nickname, "-a", "-i", path) + cmd := exec.Command(nssCertutilName, "-d", "sql:"+nssDir.Value(), "-A", + "-t", "CP,,", "-n", nickname, "-a", "-i", path) err := cmd.Run() if err != nil { @@ -67,12 +71,15 @@ func cleanCertsNss() { filename := f.Name() - fingerprintHex := strings.Replace(filename, ".pem", "", -1) + fingerprintHex := strings.Replace(filename, ".pem", "", + -1) - nickname := nicknameFromFingerprintHexNss(fingerprintHex) + nickname := nicknameFromFingerprintHexNss( + fingerprintHex) // Delete the cert from NSS - cmd := exec.Command(nssCertutilName, "-d", "sql:" + nssDir.Value(), "-D", "-n", nickname) + cmd := exec.Command(nssCertutilName, "-d", "sql:"+ + nssDir.Value(), "-D", "-n", nickname) err := cmd.Run() if err != nil { @@ -81,6 +88,9 @@ func cleanCertsNss() { // Also delete the cert from the filesystem err = os.Remove(certDir.Value() + "/" + filename) + if err != nil { + log.Fatal(err) + } } } @@ -91,8 +101,10 @@ func checkCertExpiredNss(certFile os.FileInfo) (bool, error) { // Get the last modified time certFileModTime := certFile.ModTime() - // If the cert's last modified timestamp differs too much from the current time in either direction, consider it expired - expired := math.Abs( time.Since(certFileModTime).Seconds() ) > float64(certExpirePeriod.Value()) + // If the cert's last modified timestamp differs too much from the + // current time in either direction, consider it expired + expired := math.Abs(time.Since(certFileModTime).Seconds()) > + float64(certExpirePeriod.Value()) return expired, nil diff --git a/certinject/nss_notwindows.go b/certinject/nss_notwindows.go index 930bf90..d242f5f 100644 --- a/certinject/nss_notwindows.go +++ b/certinject/nss_notwindows.go @@ -2,6 +2,6 @@ package certinject -var ( +const ( nssCertutilName = "certutil" ) diff --git a/certinject/nss_windows.go b/certinject/nss_windows.go index 3a5c223..b24bef3 100644 --- a/certinject/nss_windows.go +++ b/certinject/nss_windows.go @@ -1,5 +1,5 @@ package certinject -var ( +const ( nssCertutilName = "nss-certutil" ) From ead7a2078a62d431f8995915a37c4eec68669537 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Mon, 26 Feb 2018 11:55:13 +0000 Subject: [PATCH 4/5] certinject: NSS: Improve error handling. --- certinject/nss.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/certinject/nss.go b/certinject/nss.go index 63a4e8d..0f2a348 100644 --- a/certinject/nss.go +++ b/certinject/nss.go @@ -39,9 +39,15 @@ func injectCertNss(derBytes []byte) { cmd := exec.Command(nssCertutilName, "-d", "sql:"+nssDir.Value(), "-A", "-t", "CP,,", "-n", nickname, "-a", "-i", path) - err := cmd.Run() + stdoutStderr, err := cmd.CombinedOutput() if err != nil { - log.Fatal(err) + if strings.Contains(string(stdoutStderr), "SEC_ERROR_PKCS11_GENERAL_ERROR") { + log.Warn("Temporary SEC_ERROR_PKCS11_GENERAL_ERROR injecting certificate to NSS database; retrying in 1ms...") + time.Sleep(1 * time.Millisecond) + injectCertNss(derBytes) + } else { + log.Errorf("Error injecting cert to NSS database: %s\n%s", err, stdoutStderr) + } } } @@ -55,7 +61,10 @@ func cleanCertsNss() { log.Fatal("Empty nssdbdir configuration.") } - certFiles, _ := ioutil.ReadDir(certDir.Value() + "/") + certFiles, err := ioutil.ReadDir(certDir.Value() + "/") + if err != nil { + log.Fatalf("Error enumerating files in cert directory: %s", err) + } // for all Namecoin certs in the folder for _, f := range certFiles { @@ -63,7 +72,7 @@ func cleanCertsNss() { // Check if the cert is expired expired, err := checkCertExpiredNss(f) if err != nil { - log.Fatal(err) + log.Fatalf("Error checking if NSS cert is expired: %s", err) } // delete the cert if it's expired @@ -81,15 +90,23 @@ func cleanCertsNss() { cmd := exec.Command(nssCertutilName, "-d", "sql:"+ nssDir.Value(), "-D", "-n", nickname) - err := cmd.Run() + stdoutStderr, err := cmd.CombinedOutput() if err != nil { - log.Fatal(err) + if strings.Contains(string(stdoutStderr), "SEC_ERROR_UNRECOGNIZED_OID") { + log.Warn("Tried to delete certificate from NSS database, but the certificate was already not present in NSS database") + } else if strings.Contains(string(stdoutStderr), "SEC_ERROR_PKCS11_GENERAL_ERROR") { + log.Warn("Temporary SEC_ERROR_PKCS11_GENERAL_ERROR deleting certificate from NSS database; retrying in 1ms...") + time.Sleep(1 * time.Millisecond) + cleanCertsNss() + } else { + log.Fatalf("Error deleting cert from NSS database: %s\n%s", err, stdoutStderr) + } } // Also delete the cert from the filesystem err = os.Remove(certDir.Value() + "/" + filename) if err != nil { - log.Fatal(err) + log.Fatalf("Error deleting NSS cert from filesystem: %s", err) } } } @@ -101,10 +118,14 @@ func checkCertExpiredNss(certFile os.FileInfo) (bool, error) { // Get the last modified time certFileModTime := certFile.ModTime() + age := time.Since(certFileModTime) + ageSeconds := age.Seconds() + // If the cert's last modified timestamp differs too much from the // current time in either direction, consider it expired - expired := math.Abs(time.Since(certFileModTime).Seconds()) > - float64(certExpirePeriod.Value()) + expired := math.Abs(ageSeconds) > float64(certExpirePeriod.Value()) + + log.Debugf("Age of certificate: %s = %f seconds; expired = %t", age, ageSeconds, expired) return expired, nil From 375ff45a4d116866b7ef3bd0ff73d3ff2bd12989 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Tue, 27 Feb 2018 09:53:42 +0000 Subject: [PATCH 5/5] certinject: NSS: Add an internal test. --- certinject/nss_internal_test.go | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 certinject/nss_internal_test.go diff --git a/certinject/nss_internal_test.go b/certinject/nss_internal_test.go new file mode 100644 index 0000000..2a6dc16 --- /dev/null +++ b/certinject/nss_internal_test.go @@ -0,0 +1,48 @@ +package certinject + +import ( + "os" + "testing" + "time" +) + +func TestCheckCertExpired(t *testing.T) { + testFilename := "test_cert_file.pem" + + certExpirePeriod.SetValue(5.0) + + bytesDummy := []byte(`TEST DATA`) + + injectCertFile(bytesDummy, testFilename) + defer os.Remove(testFilename) + + info1, err := os.Stat(testFilename) + if err != nil { + t.Errorf("Error getting file info 1: %s", err) + } + + expired1, err := checkCertExpiredNss(info1) + if err != nil { + t.Errorf("Error checking if file info 1 expired: %s", err) + } + + if expired1 { + t.Errorf("Cert expired instantly") + } + + time.Sleep(10 * time.Second) + + info2, err := os.Stat(testFilename) + if err != nil { + t.Errorf("Error getting file info 2: %s", err) + } + + expired2, err := checkCertExpiredNss(info2) + if err != nil { + t.Errorf("Error checking if file info 2 expired: %s", err) + } + + if !expired2 { + t.Errorf("Cert never expired") + } +}