Skip to content

Instantly share code, notes, and snippets.

@CherryDT
Last active May 30, 2026 21:41
Show Gist options
  • Select an option

  • Save CherryDT/629bcccba08746c1341cd4e46e23dfbc to your computer and use it in GitHub Desktop.

Select an option

Save CherryDT/629bcccba08746c1341cd4e46e23dfbc to your computer and use it in GitHub Desktop.
PoC code for testing Windows Hello for Keyring
//go:build windows
// +build windows
package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
"os/user"
"path/filepath"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
const (
passportKSPName = "Microsoft Passport Key Storage Provider"
defaultKeyName = "keyring-winhello-poc"
ncryptRSAAlgorithm = "RSA"
bcryptSHA1Alg = "SHA1"
bcryptSHA256Alg = "SHA256"
ncryptLengthProperty = "Length"
ncryptKeyUsageProperty = "Key Usage"
ncryptNgcCacheTypeProperty = "NgcCacheType"
ncryptNgcCacheTypeLegacyProperty = "NgcCacheTypeProperty"
ncryptPinCacheIsGestureRequiredProperty = "PinCacheIsGestureRequired"
ncryptUseContextProperty = "Use Context"
ncryptWindowHandleProperty = "HWND Handle"
ncryptPadPKCS1Flag = 0x00000002
ncryptPadOAEPFlag = 0x00000004
ncryptSilentFlag = 0x00000040
ncryptAllowDecryptFlag = 0x00000001
ncryptAllowSigningFlag = 0x00000002
ngcCacheTypeAuthMandatory = 0x00000001
passportKeyNameDomain = "ByteNess"
passportKeyNameSubdomain = "keyring-winhello"
createKeyUseContext = "Create keyring winhello PoC key"
)
const (
nteBadKeyset windows.Errno = 0x80090016
nteNoKey windows.Errno = 0x8009000d
nteNotFound windows.Errno = 0x80090011
nteInvalidParameter windows.Errno = 0x80090027
win32InvalidParameter windows.Errno = 87
)
var (
ncryptDLL = windows.NewLazySystemDLL("ncrypt.dll")
user32DLL = windows.NewLazySystemDLL("user32.dll")
kernelDLL = windows.NewLazySystemDLL("kernel32.dll")
procNCryptOpenStorageProvider = ncryptDLL.NewProc("NCryptOpenStorageProvider")
procNCryptOpenKey = ncryptDLL.NewProc("NCryptOpenKey")
procNCryptCreatePersistedKey = ncryptDLL.NewProc("NCryptCreatePersistedKey")
procNCryptFinalizeKey = ncryptDLL.NewProc("NCryptFinalizeKey")
procNCryptSetProperty = ncryptDLL.NewProc("NCryptSetProperty")
procNCryptEncrypt = ncryptDLL.NewProc("NCryptEncrypt")
procNCryptDecrypt = ncryptDLL.NewProc("NCryptDecrypt")
procNCryptDeleteKey = ncryptDLL.NewProc("NCryptDeleteKey")
procNCryptFreeObject = ncryptDLL.NewProc("NCryptFreeObject")
procGetForegroundWindow = user32DLL.NewProc("GetForegroundWindow")
procIsWindowVisible = user32DLL.NewProc("IsWindowVisible")
procGetConsoleWindow = kernelDLL.NewProc("GetConsoleWindow")
)
type ncryptHandle uintptr
type bcryptOAEPPaddingInfo struct {
AlgID *uint16
Label *byte
LabelCB uint32
}
type options struct {
keyName string
useContext string
stateFile string
padding string
cleanupOnly bool
deleteAfter bool
recreate bool
createOnly bool
unwrapOnly bool
silentUnwrap bool
requireGesture bool
}
type persistedState struct {
LogicalKeyName string `json:"logical_key_name"`
PassportKeyName string `json:"passport_key_name"`
WrapAlg string `json:"wrap_alg"`
WrappedCEKBase64 string `json:"wrapped_cek"`
}
type rsaPaddingMode struct {
name string
hashAlg string
flag uint32
paddingInfo func() (unsafe.Pointer, error)
}
var rsaPaddingModes = []rsaPaddingMode{
{
name: "RSA-OAEP-SHA256",
hashAlg: bcryptSHA256Alg,
flag: ncryptPadOAEPFlag,
paddingInfo: func() (unsafe.Pointer, error) {
sha256UTF16, err := windows.UTF16PtrFromString(bcryptSHA256Alg)
if err != nil {
return nil, fmt.Errorf("encode hash algorithm %q: %w", bcryptSHA256Alg, err)
}
paddingInfo := &bcryptOAEPPaddingInfo{
AlgID: sha256UTF16,
Label: nil,
LabelCB: 0,
}
return unsafe.Pointer(paddingInfo), nil
},
},
{
name: "RSA-OAEP-SHA1",
hashAlg: bcryptSHA1Alg,
flag: ncryptPadOAEPFlag,
paddingInfo: func() (unsafe.Pointer, error) {
sha1UTF16, err := windows.UTF16PtrFromString(bcryptSHA1Alg)
if err != nil {
return nil, fmt.Errorf("encode hash algorithm %q: %w", bcryptSHA1Alg, err)
}
paddingInfo := &bcryptOAEPPaddingInfo{
AlgID: sha1UTF16,
Label: nil,
LabelCB: 0,
}
return unsafe.Pointer(paddingInfo), nil
},
},
{
name: "RSA-PKCS1-v1_5",
hashAlg: "",
flag: ncryptPadPKCS1Flag,
paddingInfo: func() (unsafe.Pointer, error) {
return nil, nil
},
},
}
var rsaPaddingModeByName = map[string]rsaPaddingMode{}
func init() {
for _, mode := range rsaPaddingModes {
rsaPaddingModeByName[normalizePaddingName(mode.name)] = mode
}
// Friendly aliases for CLI use.
rsaPaddingModeByName["oaep-sha256"] = rsaPaddingModes[0]
rsaPaddingModeByName["rsaes-oaep-sha256"] = rsaPaddingModes[0]
rsaPaddingModeByName["oaep-sha1"] = rsaPaddingModes[1]
rsaPaddingModeByName["rsaes-oaep-sha1"] = rsaPaddingModes[1]
rsaPaddingModeByName["pkcs1v15"] = rsaPaddingModes[2]
rsaPaddingModeByName["pkcs1-v1_5"] = rsaPaddingModes[2]
rsaPaddingModeByName["pkcs1-v1.5"] = rsaPaddingModes[2]
rsaPaddingModeByName["rsa-pkcs1-v1_5"] = rsaPaddingModes[2]
rsaPaddingModeByName["rsaes-pkcs1-v1_5"] = rsaPaddingModes[2]
}
func main() {
opts := parseFlags()
if err := run(opts); err != nil {
log.Fatal(err)
}
}
func parseFlags() options {
opts := options{}
flag.StringVar(&opts.keyName, "key-name", defaultKeyName, "Persistent key name for this PoC")
flag.StringVar(&opts.useContext, "context", "Unlock keyring secret for winhello PoC", "Use context shown by Windows Hello")
flag.StringVar(&opts.stateFile, "state-file", filepath.Join("_local", "winhello-poc-state.json"), "Path to persisted wrapped-state file for create-only/unwrap-only tests")
flag.StringVar(&opts.padding, "padding", "auto", "Padding mode: auto|oaep-sha256|oaep-sha1|pkcs1v15")
flag.BoolVar(&opts.cleanupOnly, "cleanup", false, "Delete the PoC key and exit")
flag.BoolVar(&opts.deleteAfter, "delete-after", false, "Delete the PoC key after a successful roundtrip")
flag.BoolVar(&opts.recreate, "recreate", false, "Delete and recreate the PoC key before running")
flag.BoolVar(&opts.createOnly, "create-only", false, "Create/reuse the key, wrap a CEK, persist wrapped state, and exit without unwrap")
flag.BoolVar(&opts.unwrapOnly, "unwrap-only", false, "Read persisted wrapped state and only perform unwrap")
flag.BoolVar(&opts.silentUnwrap, "silent-unwrap", false, "Request silent unwrap with NCRYPT_SILENT_FLAG; should fail if UI is required")
flag.BoolVar(&opts.requireGesture, "require-gesture", true, "Set PinCacheIsGestureRequired and Use Context before unwrap")
flag.Parse()
if opts.createOnly && opts.unwrapOnly {
log.Fatal("--create-only and --unwrap-only are mutually exclusive")
}
return opts
}
func run(opts options) error {
provider, err := openStorageProvider(passportKSPName)
if err != nil {
return fmt.Errorf("open Passport KSP %q: %w", passportKSPName, err)
}
defer freeHandle(provider)
if err := setBestEffortWindowHandle(provider); err != nil {
log.Printf("warning: unable to set NCrypt parent window handle on provider: %v", err)
}
passportKeyName, err := passportPersistentKeyName(opts.keyName)
if err != nil {
return err
}
fmt.Printf("logical key name: %s\n", opts.keyName)
fmt.Printf("passport key name: %s\n", passportKeyName)
if opts.cleanupOnly {
deleted, err := deleteKeyByName(provider, passportKeyName)
if err != nil {
return err
}
if deleted {
fmt.Printf("deleted key %q\n", passportKeyName)
} else {
fmt.Printf("key %q not found\n", passportKeyName)
}
if err := removeStateFile(opts.stateFile); err != nil {
return err
}
return nil
}
if opts.unwrapOnly {
return runUnwrapOnly(provider, passportKeyName, opts)
}
if opts.recreate {
deleted, err := deleteKeyByName(provider, passportKeyName)
if err != nil {
return err
}
if deleted {
fmt.Printf("deleted existing key %q\n", passportKeyName)
}
}
key, created, err := openOrCreateKey(provider, passportKeyName)
if err != nil {
return err
}
if created {
fmt.Printf("created persistent key %q\n", passportKeyName)
} else {
fmt.Printf("reused persistent key %q\n", passportKeyName)
}
cek := make([]byte, 32)
if _, err := rand.Read(cek); err != nil {
freeHandle(key)
return fmt.Errorf("generate random 32-byte CEK: %w", err)
}
var (
wrapped []byte
padding rsaPaddingMode
)
if opts.padding == "auto" {
wrapped, padding, err = rsaEncryptWithFallback(key, cek)
} else {
wrapped, padding, err = rsaEncryptWithSelectedMode(key, cek, opts.padding)
}
freeHandle(key)
if err != nil {
return fmt.Errorf("wrap CEK using public-key operation: %w", err)
}
fmt.Printf("wrap mode: %s\n", padding.name)
if opts.createOnly {
state := persistedState{
LogicalKeyName: opts.keyName,
PassportKeyName: passportKeyName,
WrapAlg: padding.name,
WrappedCEKBase64: base64.StdEncoding.EncodeToString(wrapped),
}
if err := writePersistedState(opts.stateFile, state); err != nil {
return err
}
fmt.Printf("persisted wrapped state to %s\n", opts.stateFile)
return nil
}
reopened, err := openKey(provider, passportKeyName)
if err != nil {
return fmt.Errorf("reopen key %q: %w", passportKeyName, err)
}
defer freeHandle(reopened)
if err := prepareDecrypt(reopened, opts.useContext, opts.requireGesture); err != nil {
return fmt.Errorf("prepare Windows Hello decrypt policy: %w", err)
}
unwrapped, err := rsaDecryptWithModeAndFlags(reopened, wrapped, padding, decryptFlags(opts.silentUnwrap))
if err != nil {
return fmt.Errorf("unwrap CEK using private-key operation: %w", err)
}
if !bytes.Equal(cek, unwrapped) {
return fmt.Errorf("roundtrip mismatch: original and unwrapped CEKs differ")
}
fmt.Printf("roundtrip ok (wrapped=%d bytes)\n", len(wrapped))
fmt.Println("If your policy requires a gesture, Windows Hello should appear during unwrap.")
if opts.deleteAfter {
freeHandle(reopened)
reopened = 0
deleted, err := deleteKeyByName(provider, passportKeyName)
if err != nil {
return err
}
if deleted {
fmt.Printf("deleted key %q\n", passportKeyName)
}
}
return nil
}
func runUnwrapOnly(provider ncryptHandle, passportKeyName string, opts options) error {
state, err := readPersistedState(opts.stateFile)
if err != nil {
return err
}
if state.PassportKeyName != "" && state.PassportKeyName != passportKeyName {
log.Printf("warning: state file passport key name %q differs from current derived name %q", state.PassportKeyName, passportKeyName)
}
padding, err := lookupPaddingModeByName(state.WrapAlg)
if err != nil {
return fmt.Errorf("state file wrap_alg %q: %w", state.WrapAlg, err)
}
wrapped, err := base64.StdEncoding.DecodeString(state.WrappedCEKBase64)
if err != nil {
return fmt.Errorf("decode wrapped CEK from %s: %w", opts.stateFile, err)
}
key, err := openKey(provider, passportKeyName)
if err != nil {
return fmt.Errorf("open key %q for unwrap-only: %w", passportKeyName, err)
}
defer freeHandle(key)
if err := prepareDecrypt(key, opts.useContext, opts.requireGesture); err != nil {
return fmt.Errorf("prepare unwrap-only decrypt policy: %w", err)
}
plaintext, err := rsaDecryptWithModeAndFlags(key, wrapped, padding, decryptFlags(opts.silentUnwrap))
if err != nil {
return fmt.Errorf("unwrap-only decrypt using %s: %w", padding.name, err)
}
if len(plaintext) != 32 {
return fmt.Errorf("unwrap-only plaintext length = %d, want 32", len(plaintext))
}
fmt.Printf("unwrap-only ok (mode=%s, silent=%t, require_gesture=%t, plaintext_len=%d)\n", padding.name, opts.silentUnwrap, opts.requireGesture, len(plaintext))
return nil
}
func openStorageProvider(providerName string) (ncryptHandle, error) {
providerNameUTF16, err := windows.UTF16PtrFromString(providerName)
if err != nil {
return 0, fmt.Errorf("encode provider name: %w", err)
}
var provider ncryptHandle
if err := callNCrypt(
procNCryptOpenStorageProvider,
uintptr(unsafe.Pointer(&provider)),
uintptr(unsafe.Pointer(providerNameUTF16)),
0,
); err != nil {
return 0, err
}
return provider, nil
}
func openOrCreateKey(provider ncryptHandle, keyName string) (ncryptHandle, bool, error) {
key, err := openKey(provider, keyName)
if err == nil {
return key, false, nil
}
if isInvalidParameterError(err) {
return 0, false, fmt.Errorf("open key %q: Passport KSP rejected the key identity or parameters: %w", keyName, err)
}
if !isKeyNotFoundError(err) {
return 0, false, fmt.Errorf("open key %q: %w", keyName, err)
}
key, err = createPersistedRSAKey(provider, keyName)
if err != nil {
return 0, false, err
}
return key, true, nil
}
func createPersistedRSAKey(provider ncryptHandle, keyName string) (ncryptHandle, error) {
algorithmUTF16, err := windows.UTF16PtrFromString(ncryptRSAAlgorithm)
if err != nil {
return 0, fmt.Errorf("encode algorithm %q: %w", ncryptRSAAlgorithm, err)
}
keyNameUTF16, err := windows.UTF16PtrFromString(keyName)
if err != nil {
return 0, fmt.Errorf("encode key name: %w", err)
}
key, err := createPersistedKeyWithSpec(provider, algorithmUTF16, keyNameUTF16, 0)
if err != nil {
return 0, fmt.Errorf("create persisted key %q: %w", keyName, err)
}
if err := setUint32Property(key, ncryptLengthProperty, 2048); err != nil {
freeHandle(key)
return 0, fmt.Errorf("set key length property: %w", err)
}
if err := setKeyUsage(key); err != nil {
freeHandle(key)
return 0, fmt.Errorf("set key usage property: %w", err)
}
if err := setNgcCacheType(key); err != nil {
freeHandle(key)
return 0, fmt.Errorf("set NGC cache type property: %w", err)
}
if err := setUseContext(key, createKeyUseContext); err != nil {
freeHandle(key)
return 0, fmt.Errorf("set use context before finalize: %w", err)
}
if err := setBestEffortWindowHandle(key); err != nil {
log.Printf("warning: unable to set NCrypt parent window handle on key creation handle: %v", err)
}
if err := callNCrypt(procNCryptFinalizeKey, uintptr(key), 0); err != nil {
freeHandle(key)
return 0, fmt.Errorf("finalize key %q: %w", keyName, err)
}
return key, nil
}
func createPersistedKeyWithSpec(provider ncryptHandle, algorithmUTF16 *uint16, keyNameUTF16 *uint16, legacyKeySpec uint32) (ncryptHandle, error) {
var key ncryptHandle
if err := callNCrypt(
procNCryptCreatePersistedKey,
uintptr(provider),
uintptr(unsafe.Pointer(&key)),
uintptr(unsafe.Pointer(algorithmUTF16)),
uintptr(unsafe.Pointer(keyNameUTF16)),
uintptr(legacyKeySpec),
0,
); err != nil {
return 0, err
}
return key, nil
}
func openKey(provider ncryptHandle, keyName string) (ncryptHandle, error) {
keyNameUTF16, err := windows.UTF16PtrFromString(keyName)
if err != nil {
return 0, fmt.Errorf("encode key name: %w", err)
}
var key ncryptHandle
if err := callNCrypt(
procNCryptOpenKey,
uintptr(provider),
uintptr(unsafe.Pointer(&key)),
uintptr(unsafe.Pointer(keyNameUTF16)),
0,
0,
); err != nil {
return 0, err
}
return key, nil
}
func deleteKeyByName(provider ncryptHandle, keyName string) (bool, error) {
key, err := openKey(provider, keyName)
if err != nil {
if isKeyNotFoundError(err) {
return false, nil
}
return false, fmt.Errorf("open key %q for deletion: %w", keyName, err)
}
if err := callNCrypt(procNCryptDeleteKey, uintptr(key), 0); err != nil {
freeHandle(key)
return false, fmt.Errorf("delete key %q: %w", keyName, err)
}
return true, nil
}
func setUseContext(key ncryptHandle, context string) error {
contextUTF16, err := windows.UTF16FromString(context)
if err != nil {
return fmt.Errorf("encode use context: %w", err)
}
contextBytes := unsafe.Slice((*byte)(unsafe.Pointer(&contextUTF16[0])), len(contextUTF16)*2)
return setRawProperty(key, ncryptUseContextProperty, contextBytes, 0)
}
func setKeyUsage(key ncryptHandle) error {
return setUint32Property(key, ncryptKeyUsageProperty, ncryptAllowDecryptFlag|ncryptAllowSigningFlag)
}
func setNgcCacheType(key ncryptHandle) error {
err := setUint32Property(key, ncryptNgcCacheTypeProperty, ngcCacheTypeAuthMandatory)
if err == nil {
return nil
}
legacyErr := setUint32Property(key, ncryptNgcCacheTypeLegacyProperty, ngcCacheTypeAuthMandatory)
if legacyErr == nil {
return nil
}
return fmt.Errorf("%s: %w; %s fallback: %v", ncryptNgcCacheTypeProperty, err, ncryptNgcCacheTypeLegacyProperty, legacyErr)
}
func requireGestureForDecrypt(key ncryptHandle, context string) error {
if err := setBestEffortWindowHandle(key); err != nil {
log.Printf("warning: unable to set NCrypt parent window handle on key decrypt handle: %v", err)
}
if err := setUseContext(key, context); err != nil {
return err
}
return setUint32Property(key, ncryptPinCacheIsGestureRequiredProperty, 1)
}
func prepareDecrypt(key ncryptHandle, context string, requireGesture bool) error {
if requireGesture {
return requireGestureForDecrypt(key, context)
}
if err := setBestEffortWindowHandle(key); err != nil {
log.Printf("warning: unable to set NCrypt parent window handle on key decrypt handle: %v", err)
}
return nil
}
func setUint32Property(key ncryptHandle, property string, value uint32) error {
valueBytes := unsafe.Slice((*byte)(unsafe.Pointer(&value)), int(unsafe.Sizeof(value)))
return setRawProperty(key, property, valueBytes, 0)
}
func setBestEffortWindowHandle(handle ncryptHandle) error {
hwnd := bestEffortWindowHandle()
if hwnd == 0 {
return nil
}
hwndBytes := unsafe.Slice((*byte)(unsafe.Pointer(&hwnd)), int(unsafe.Sizeof(hwnd)))
return setRawProperty(handle, ncryptWindowHandleProperty, hwndBytes, 0)
}
func bestEffortWindowHandle() uintptr {
if hwnd, _, _ := procGetConsoleWindow.Call(); hwnd != 0 && isWindowVisible(hwnd) {
return hwnd
}
hwnd, _, _ := procGetForegroundWindow.Call()
return hwnd
}
func isWindowVisible(hwnd uintptr) bool {
visible, _, _ := procIsWindowVisible.Call(hwnd)
return visible != 0
}
func setRawProperty(key ncryptHandle, property string, value []byte, flags uint32) error {
propertyUTF16, err := windows.UTF16PtrFromString(property)
if err != nil {
return fmt.Errorf("encode property %q: %w", property, err)
}
var valuePtr uintptr
if len(value) > 0 {
valuePtr = uintptr(unsafe.Pointer(&value[0]))
}
if err := callNCrypt(
procNCryptSetProperty,
uintptr(key),
uintptr(unsafe.Pointer(propertyUTF16)),
valuePtr,
uintptr(uint32(len(value))),
uintptr(flags),
); err != nil {
return fmt.Errorf("set property %q: %w", property, err)
}
return nil
}
func rsaEncryptWithFallback(key ncryptHandle, plaintext []byte) ([]byte, rsaPaddingMode, error) {
var lastErr error
for _, mode := range rsaPaddingModes {
wrapped, err := rsaEncryptWithMode(key, plaintext, mode)
if err == nil {
return wrapped, mode, nil
}
lastErr = err
log.Printf("warning: encrypt with %s failed: %v", mode.name, err)
}
return nil, rsaPaddingMode{}, fmt.Errorf("all RSA wrap modes failed; last error: %w", lastErr)
}
func rsaEncryptWithSelectedMode(key ncryptHandle, plaintext []byte, modeName string) ([]byte, rsaPaddingMode, error) {
mode, err := lookupPaddingModeByName(modeName)
if err != nil {
return nil, rsaPaddingMode{}, err
}
wrapped, err := rsaEncryptWithMode(key, plaintext, mode)
if err != nil {
return nil, rsaPaddingMode{}, err
}
return wrapped, mode, nil
}
func rsaEncryptWithMode(key ncryptHandle, plaintext []byte, mode rsaPaddingMode) ([]byte, error) {
paddingInfo, err := mode.paddingInfo()
if err != nil {
return nil, err
}
paddingInfoPtr, err := ncryptPaddingInfoPtr(mode.flag, paddingInfo)
if err != nil {
return nil, err
}
var wrappedLen uint32
if err := callNCrypt(
procNCryptEncrypt,
uintptr(key),
uintptr(unsafe.Pointer(slicePtr(plaintext))),
uintptr(uint32(len(plaintext))),
paddingInfoPtr,
0,
0,
uintptr(unsafe.Pointer(&wrappedLen)),
uintptr(mode.flag),
); err != nil {
return nil, err
}
wrapped := make([]byte, wrappedLen)
if err := callNCrypt(
procNCryptEncrypt,
uintptr(key),
uintptr(unsafe.Pointer(slicePtr(plaintext))),
uintptr(uint32(len(plaintext))),
paddingInfoPtr,
uintptr(unsafe.Pointer(slicePtr(wrapped))),
uintptr(uint32(len(wrapped))),
uintptr(unsafe.Pointer(&wrappedLen)),
uintptr(mode.flag),
); err != nil {
return nil, err
}
return wrapped[:wrappedLen], nil
}
func rsaDecryptWithMode(key ncryptHandle, ciphertext []byte, mode rsaPaddingMode) ([]byte, error) {
return rsaDecryptWithModeAndFlags(key, ciphertext, mode, 0)
}
func rsaDecryptWithModeAndFlags(key ncryptHandle, ciphertext []byte, mode rsaPaddingMode, extraFlags uint32) ([]byte, error) {
paddingInfo, err := mode.paddingInfo()
if err != nil {
return nil, err
}
paddingInfoPtr, err := ncryptPaddingInfoPtr(mode.flag, paddingInfo)
if err != nil {
return nil, err
}
var plaintextLen uint32
if err := callNCrypt(
procNCryptDecrypt,
uintptr(key),
uintptr(unsafe.Pointer(slicePtr(ciphertext))),
uintptr(uint32(len(ciphertext))),
paddingInfoPtr,
0,
0,
uintptr(unsafe.Pointer(&plaintextLen)),
uintptr(mode.flag | extraFlags),
); err != nil {
return nil, err
}
plaintext := make([]byte, plaintextLen)
if err := callNCrypt(
procNCryptDecrypt,
uintptr(key),
uintptr(unsafe.Pointer(slicePtr(ciphertext))),
uintptr(uint32(len(ciphertext))),
paddingInfoPtr,
uintptr(unsafe.Pointer(slicePtr(plaintext))),
uintptr(uint32(len(plaintext))),
uintptr(unsafe.Pointer(&plaintextLen)),
uintptr(mode.flag | extraFlags),
); err != nil {
return nil, err
}
return plaintext[:plaintextLen], nil
}
func decryptFlags(silent bool) uint32 {
if silent {
return ncryptSilentFlag
}
return 0
}
func lookupPaddingModeByName(name string) (rsaPaddingMode, error) {
mode, ok := rsaPaddingModeByName[normalizePaddingName(name)]
if !ok {
return rsaPaddingMode{}, fmt.Errorf("unsupported padding mode %q", name)
}
return mode, nil
}
func normalizePaddingName(name string) string {
name = strings.TrimSpace(strings.ToLower(name))
name = strings.ReplaceAll(name, "_", "-")
return name
}
func writePersistedState(path string, state persistedState) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("create state file directory for %s: %w", path, err)
}
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return fmt.Errorf("marshal persisted state: %w", err)
}
if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil {
return fmt.Errorf("write state file %s: %w", path, err)
}
return nil
}
func readPersistedState(path string) (persistedState, error) {
data, err := os.ReadFile(path)
if err != nil {
return persistedState{}, fmt.Errorf("read state file %s: %w", path, err)
}
var state persistedState
if err := json.Unmarshal(data, &state); err != nil {
return persistedState{}, fmt.Errorf("parse state file %s: %w", path, err)
}
if state.WrappedCEKBase64 == "" {
return persistedState{}, fmt.Errorf("state file %s missing wrapped_cek", path)
}
if state.WrapAlg == "" {
return persistedState{}, fmt.Errorf("state file %s missing wrap_alg", path)
}
return state, nil
}
func removeStateFile(path string) error {
err := os.Remove(path)
if err == nil || errors.Is(err, os.ErrNotExist) {
return nil
}
return fmt.Errorf("remove state file %s: %w", path, err)
}
func ncryptPaddingInfoPtr(flag uint32, paddingInfo unsafe.Pointer) (uintptr, error) {
switch flag {
case ncryptPadOAEPFlag:
if paddingInfo == nil {
return 0, fmt.Errorf("OAEP padding requires non-nil padding info")
}
return uintptr(paddingInfo), nil
case ncryptPadPKCS1Flag:
return 0, nil
default:
return 0, fmt.Errorf("unsupported padding flag: 0x%08x", flag)
}
}
func slicePtr(data []byte) *byte {
if len(data) == 0 {
return nil
}
return &data[0]
}
func freeHandle(handle ncryptHandle) {
if handle == 0 {
return
}
if err := callNCrypt(procNCryptFreeObject, uintptr(handle)); err != nil {
log.Printf("warning: NCryptFreeObject failed: %v", err)
}
}
func callNCrypt(proc *windows.LazyProc, args ...uintptr) error {
status, _, _ := proc.Call(args...)
if status == 0 {
return nil
}
return windows.Errno(status)
}
func isInvalidParameterError(err error) bool {
return errors.Is(err, nteInvalidParameter) || errors.Is(err, win32InvalidParameter)
}
func isKeyNotFoundError(err error) bool {
return errors.Is(err, nteBadKeyset) ||
errors.Is(err, nteNoKey) ||
errors.Is(err, nteNotFound)
}
func passportPersistentKeyName(name string) (string, error) {
currentUser, err := user.Current()
if err != nil {
return "", fmt.Errorf("get current user: %w", err)
}
if currentUser.Uid == "" {
return "", fmt.Errorf("current user SID is empty")
}
return fmt.Sprintf("%s//%s/%s/%s", currentUser.Uid, passportKeyNameDomain, passportKeyNameSubdomain, name), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment