You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ncdns/certinject/cryptoapi_windows.go

192 lines
5.8 KiB
Go

package certinject
import (
"golang.org/x/sys/windows/registry"
"crypto/sha1"
"encoding/hex"
"fmt"
"strings"
"math"
"time"
)
// 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
}