certinject: NSS improvements, now works on arbitrary NSS cert store directories.

pull/17/head
JeremyRand 6 years ago
parent e5c7c09968
commit 2c8b5fe5ee
No known key found for this signature in database
GPG Key ID: B3F2D165786D6570

@ -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.")
)

@ -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()
}
}

@ -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() {
}

@ -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."
)

@ -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()
}
}

@ -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()
}
}

@ -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
}

@ -0,0 +1,7 @@
// +build !windows
package certinject
var (
nssCertutilName = "certutil"
)

@ -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
}

@ -0,0 +1,5 @@
package certinject
var (
nssCertutilName = "nss-certutil"
)
Loading…
Cancel
Save