mirror of https://github.com/namecoin/ncdns
Use new certinject package
The following defaults have unfortunately changed as a result, and will need to be set in ncdns.conf: capi.physical-store capi.set-magic-name capi.expirable-magic-namepull/149/head
parent
275374c1c2
commit
9c865846be
@ -1,17 +0,0 @@
|
||||
package certinject
|
||||
|
||||
import (
|
||||
"github.com/hlandau/xlog"
|
||||
"gopkg.in/hlandau/easyconfig.v1/cflag"
|
||||
)
|
||||
|
||||
var log, Log = xlog.New("ncdns.certinject")
|
||||
|
||||
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,11 +0,0 @@
|
||||
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."
|
||||
)
|
@ -1,10 +0,0 @@
|
||||
// +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."
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package certinject
|
||||
|
||||
// This package is used to add and remove certificates to the system trust
|
||||
// store.
|
||||
// Currently only supports NSS sqlite3 stores.
|
||||
|
||||
// InjectCert injects the given cert into all configured trust stores.
|
||||
func InjectCert(derBytes []byte) {
|
||||
|
||||
if nssFlag.Value() {
|
||||
injectCertNss(derBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// CleanCerts cleans expired certs from all configured trust stores.
|
||||
func CleanCerts() {
|
||||
|
||||
if nssFlag.Value() {
|
||||
cleanCertsNss()
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package certinject
|
||||
|
||||
import (
|
||||
"gopkg.in/hlandau/easyconfig.v1/cflag"
|
||||
)
|
||||
|
||||
// This package is used to add and remove certificates to the system trust
|
||||
// store.
|
||||
// Currently only supports Windows CryptoAPI and NSS sqlite3 stores.
|
||||
|
||||
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.")
|
||||
)
|
||||
|
||||
// InjectCert injects the given cert into all configured trust stores.
|
||||
func InjectCert(derBytes []byte) {
|
||||
|
||||
if cryptoApiFlag.Value() {
|
||||
injectCertCryptoApi(derBytes)
|
||||
}
|
||||
if nssFlag.Value() {
|
||||
injectCertNss(derBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// CleanCerts cleans expired certs from all configured trust stores.
|
||||
func CleanCerts() {
|
||||
|
||||
if cryptoApiFlag.Value() {
|
||||
cleanCertsCryptoApi()
|
||||
}
|
||||
if nssFlag.Value() {
|
||||
cleanCertsNss()
|
||||
}
|
||||
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
package certinject
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// In 64-bit Windows, this key is shared between 64-bit and 32-bit applications.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253.aspx
|
||||
const cryptoApiCertStoreRegistryBase = registry.LOCAL_MACHINE
|
||||
const cryptoApiCertStoreRegistryKey = `SOFTWARE\Microsoft\EnterpriseCertificates\Root\Certificates`
|
||||
const cryptoApiMagicName = "Namecoin"
|
||||
const cryptoApiMagicValue = 1
|
||||
|
||||
func injectCertCryptoApi(derBytes []byte) {
|
||||
|
||||
// Format documentation of Microsoft's "Certificate Registry Blob":
|
||||
|
||||
// 5c 00 00 00 // propid
|
||||
// 01 00 00 00 // unknown (possibly a version or flags field; value is always the same in my testing)
|
||||
// 04 00 00 00 // size (little endian)
|
||||
// subject public key bit length // data[size]
|
||||
|
||||
// 19 00 00 00
|
||||
// 01 00 00 00
|
||||
// 10 00 00 00
|
||||
// MD5 of ECC pubkey of certificate
|
||||
|
||||
// 0f 00 00 00
|
||||
// 01 00 00 00
|
||||
// 20 00 00 00
|
||||
// Signature Hash
|
||||
|
||||
// 03 00 00 00
|
||||
// 01 00 00 00
|
||||
// 14 00 00 00
|
||||
// Cert SHA1 hash
|
||||
|
||||
// 14 00 00 00
|
||||
// 01 00 00 00
|
||||
// 14 00 00 00
|
||||
// Key Identifier
|
||||
|
||||
// 04 00 00 00
|
||||
// 01 00 00 00
|
||||
// 10 00 00 00
|
||||
// Cert MD5 hash
|
||||
|
||||
// 20 00 00 00
|
||||
// 01 00 00 00
|
||||
// cert length
|
||||
// cert
|
||||
|
||||
// But, guess what? All you need is the "20" record.
|
||||
// Windows will happily regenerate all the others for you, whenever you actually try to use the certificate.
|
||||
// How cool is that?
|
||||
|
||||
// Length of cert
|
||||
certLength := len(derBytes)
|
||||
|
||||
// Header for a stripped Windows Certificate Registry Blob
|
||||
certBlobHeader := []byte{0x20, 0, 0, 0, 0x01, 0, 0, 0, byte((certLength >> 0) & 0xFF), byte((certLength >> 8) & 0xFF), byte((certLength >> 16) & 0xFF), byte((certLength >> 24) & 0xFF)}
|
||||
|
||||
// Construct the Blob
|
||||
certBlob := append(certBlobHeader, derBytes...)
|
||||
|
||||
// Open up the cert store.
|
||||
certStoreKey, err := registry.OpenKey(cryptoApiCertStoreRegistryBase, cryptoApiCertStoreRegistryKey, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't open cert store: %s", err)
|
||||
return
|
||||
}
|
||||
defer certStoreKey.Close()
|
||||
|
||||
// Windows CryptoAPI uses the SHA-1 fingerprint to identify a cert.
|
||||
// This is probably a Bad Thing (TM) since SHA-1 is weak.
|
||||
// However, that's Microsoft's problem to fix, not ours.
|
||||
fingerprint := sha1.Sum(derBytes)
|
||||
|
||||
// Windows CryptoAPI uses a hex string to represent the fingerprint.
|
||||
fingerprintHex := hex.EncodeToString(fingerprint[:])
|
||||
|
||||
// Windows CryptoAPI uses uppercase hex strings
|
||||
fingerprintHexUpper := strings.ToUpper(fingerprintHex)
|
||||
|
||||
// Create the registry key in which we will store the cert.
|
||||
// The 2nd result of CreateKey is openedExisting, which tells us if the cert already existed.
|
||||
// This doesn't matter to us. If true, the "last modified" metadata won't update,
|
||||
// but we delete and recreate the magic value inside it as a workaround.
|
||||
certKey, _, err := registry.CreateKey(certStoreKey, fingerprintHexUpper, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't create registry key for certificate: %s", err)
|
||||
return
|
||||
}
|
||||
defer certKey.Close()
|
||||
|
||||
// Add a magic value which indicates that the certificate is a
|
||||
// Namecoin cert. This will be used for deleting expired certs.
|
||||
// However, we have to delete it before we create it, so that we make sure that the "last modified" metadata gets updated.
|
||||
// If an error occurs during deletion, we ignore it, since it probably just means it wasn't there already.
|
||||
_ = certKey.DeleteValue(cryptoApiMagicName)
|
||||
err = certKey.SetDWordValue(cryptoApiMagicName, cryptoApiMagicValue)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't set magic registry value for certificate: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create the registry value which holds the certificate.
|
||||
err = certKey.SetBinaryValue("Blob", certBlob)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't set blob registry value for certificate: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func cleanCertsCryptoApi() {
|
||||
|
||||
// Open up the cert store.
|
||||
certStoreKey, err := registry.OpenKey(cryptoApiCertStoreRegistryBase, cryptoApiCertStoreRegistryKey, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't open cert store: %s", err)
|
||||
return
|
||||
}
|
||||
defer certStoreKey.Close()
|
||||
|
||||
// get all subkey names in the cert store
|
||||
subKeys, err := certStoreKey.ReadSubKeyNames(0)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't list certs in cert store: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// for all certs in the cert store
|
||||
for _, subKeyName := range subKeys {
|
||||
|
||||
// Check if the cert is expired
|
||||
expired, err := checkCertExpiredCryptoApi(certStoreKey, subKeyName)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't check if cert is expired: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// delete the cert if it's expired
|
||||
if expired {
|
||||
registry.DeleteKey(certStoreKey, subKeyName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkCertExpiredCryptoApi(certStoreKey registry.Key, subKeyName string) (bool, error) {
|
||||
|
||||
// Open the cert
|
||||
certKey, err := registry.OpenKey(certStoreKey, subKeyName, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Couldn't open cert registry key: %s", err)
|
||||
}
|
||||
defer certKey.Close()
|
||||
|
||||
// Check for magic value
|
||||
isNamecoin, _, err := certKey.GetIntegerValue(cryptoApiMagicName)
|
||||
if err != nil {
|
||||
// Magic value wasn't found. Therefore don't consider it expired.
|
||||
return false, nil
|
||||
}
|
||||
if isNamecoin != cryptoApiMagicValue {
|
||||
// Magic value was found but it wasn't the one we recognize. Therefore don't consider it expired.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get metadata about the cert key
|
||||
certKeyInfo, err := certKey.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Couldn't read metadata for cert registry key: %s", err)
|
||||
}
|
||||
|
||||
// Get the last modified time
|
||||
certKeyModTime := certKeyInfo.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(certKeyModTime).Seconds()) > float64(certExpirePeriod.Value())
|
||||
|
||||
return expired, nil
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package certinject
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Injects a certificate by writing to a file. Might be relevant for non-CryptoAPI trust stores.
|
||||
func injectCertFile(derBytes []byte, fileName string) {
|
||||
|
||||
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
err := ioutil.WriteFile(fileName, pemBytes, 0644)
|
||||
if err != nil {
|
||||
log.Errore(err, "Error writing cert!")
|
||||
return
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
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)
|
||||
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func cleanCertsNss() {
|
||||
|
||||
if certDir.Value() == "" {
|
||||
log.Fatal("Empty nsscertdir configuration.")
|
||||
}
|
||||
if nssDir.Value() == "" {
|
||||
log.Fatal("Empty nssdbdir configuration.")
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
// Check if the cert is expired
|
||||
expired, err := checkCertExpiredNss(f)
|
||||
if err != nil {
|
||||
log.Fatalf("Error checking if NSS cert is expired: %s", 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)
|
||||
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
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.Fatalf("Error deleting NSS cert from filesystem: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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(ageSeconds) > float64(certExpirePeriod.Value())
|
||||
|
||||
log.Debugf("Age of certificate: %s = %f seconds; expired = %t", age, ageSeconds, expired)
|
||||
|
||||
return expired, nil
|
||||
|
||||
}
|
||||
|
||||
func nicknameFromFingerprintHexNss(fingerprintHex string) string {
|
||||
return "Namecoin-" + fingerprintHex
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package certinject
|
||||
|
||||
const (
|
||||
nssCertutilName = "certutil"
|
||||
)
|
@ -1,5 +0,0 @@
|
||||
package certinject
|
||||
|
||||
const (
|
||||
nssCertutilName = "nss-certutil"
|
||||
)
|
Loading…
Reference in New Issue