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-name
pull/149/head
Jeremy Rand 2 years ago
parent 275374c1c2
commit 9c865846be
No known key found for this signature in database
GPG Key ID: EB03139A459DD06E

@ -19,11 +19,22 @@ task:
fetch_script:
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- GOOS=windows go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- go generate github.com/namecoin/certinject/...
- go generate github.com/namecoin/x509-compressed/...
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- env:
GOLANGCI_MODULES_ARGS: ""
MODULES_NAME: ""
certinject_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
- git clone https://github.com/namecoin/certinject.git
- cd certinject
#- go mod init github.com/namecoin/certinject
#- go mod tidy
- go generate ./...
#- go mod tidy
#- go install -v ./...
x509_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
@ -37,7 +48,7 @@ task:
fetch_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod init github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/certinject=../certinject -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod tidy
lint_script:
- cd $(go env GOPATH)/src/github.com/$CIRRUS_REPO_FULL_NAME/
@ -100,10 +111,21 @@ task:
fetch_script:
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- GOOS=windows go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- go generate github.com/namecoin/certinject/...
- go generate github.com/namecoin/x509-compressed/...
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- env:
MODULES_NAME: ""
certinject_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
- git clone https://github.com/namecoin/certinject.git
- cd certinject
#- go mod init github.com/namecoin/certinject
#- go mod tidy
- go generate ./...
#- go mod tidy
#- go install -v ./...
x509_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
@ -117,7 +139,7 @@ task:
fetch_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod init github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/certinject=../certinject -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod tidy
# Get the test suite
- mkdir -p $(go env GOPATH)/src/github.com/hlandau
@ -156,10 +178,21 @@ task:
fetch_script:
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- GOOS=windows go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- go generate github.com/namecoin/certinject/...
- go generate github.com/namecoin/x509-compressed/...
- go get -tags "$GOX_TAGS" -d -v -t github.com/$CIRRUS_REPO_FULL_NAME/...
- env:
MODULES_NAME: ""
certinject_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
- git clone https://github.com/namecoin/certinject.git
- cd certinject
#- go mod init github.com/namecoin/certinject
#- go mod tidy
- go generate ./...
#- go mod tidy
#- go install -v ./...
x509_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- cd ../
@ -173,7 +206,7 @@ task:
fetch_script:
- cd $(go env GOPATH)/src/github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod init github.com/"$CIRRUS_REPO_FULL_NAME"
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/certinject=../certinject -replace github.com/namecoin/x509-compressed=../x509-compressed
- go mod tidy
build_script:
- rm -rf idist

@ -101,23 +101,29 @@ Option A: Using Go build commands without Go modules (works on any platform with
3. Run `go get -d -t -u github.com/namecoin/ncdns/...`. The ncdns source code will be
retrieved automatically.
4. Run `go generate github.com/namecoin/x509-compressed/...`. The compressed public key patch will be applied.
4. Run `go generate github.com/namecoin/certinject/...`. The ReactOS property list will be parsed.
5. Run `go get -t -u github.com/namecoin/ncdns/...`. ncdns will be built. The binaries will be at `$GOPATH/bin/ncdns`.
5. Run `go generate github.com/namecoin/x509-compressed/...`. The compressed public key patch will be applied.
6. Run `go get -t -u github.com/namecoin/ncdns/...`. ncdns will be built. The binaries will be at `$GOPATH/bin/ncdns`.
Option B: Using Go build commands with Go modules (works on any platform with Bash; Go 1.15+:
1. Install [x509-compressed](https://github.com/namecoin/x509-compressed) according to its "with Go modules" instructions. Clone ncdns to a sibling directory of x509-compressed.
1. Clone [certinject](https://github.com/namecoin/certinject), [x509-compressed](https://github.com/namecoin/x509-compressed), and ncdns to sibling directories.
2. Install `certinject` according to its instructions.
3. Install `x509-compressed` according to its "with Go modules" instructions.
2. Run the following in the ncdns directory to set up Go modules:
4. Run the following in the ncdns directory to set up Go modules:
~~~
go mod init github.com/namecoin/ncdns
go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/x509-compressed=../x509-compressed
go mod edit -replace github.com/coreos/go-systemd=github.com/coreos/go-systemd/v22@latest -replace github.com/namecoin/certinject=../certinject -replace github.com/namecoin/x509-compressed=../x509-compressed
go mod tidy
~~~
3. Run `go install ./...`. ncdns will be built. The binaries will be at `$GOPATH/bin/ncdns`.
5. Run `go install ./...`. ncdns will be built. The binaries will be at `$GOPATH/bin/ncdns`.
Option C: Using Makefile (non-Windows platforms):

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

@ -5,8 +5,8 @@ package tlshook
import (
"github.com/hlandau/xlog"
"github.com/namecoin/certinject"
"github.com/namecoin/ncdns/certdehydrate"
"github.com/namecoin/ncdns/certinject"
"github.com/namecoin/ncdns/ncdomain"
)

Loading…
Cancel
Save