初版功能完成
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled

This commit is contained in:
qsc
2026-08-29 13:12:17 +08:00
commit 142e5dc7d6
217 changed files with 21313 additions and 0 deletions
@@ -0,0 +1,64 @@
//go:build windows
// Package dpapi protects Windows Node secrets using the operating system DPAPI.
package dpapi
import (
"errors"
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
var entropy = []byte("RemLink-v1-NodeIdentity")
// Protector uses machine-scoped DPAPI so Console/service identities can read
// the package-local identity after Windows restart. Directory ACLs remain required.
type Protector struct{}
func (Protector) Protect(plain []byte) ([]byte, error) {
if len(plain) == 0 {
return nil, errors.New("DPAPI plaintext must not be empty")
}
input := blob(plain)
extra := blob(entropy)
var output windows.DataBlob
name, err := windows.UTF16PtrFromString("RemLink Node Identity")
if err != nil {
return nil, err
}
flags := uint32(windows.CRYPTPROTECT_LOCAL_MACHINE | windows.CRYPTPROTECT_UI_FORBIDDEN)
if err := windows.CryptProtectData(&input, name, &extra, 0, nil, flags, &output); err != nil {
return nil, fmt.Errorf("protect secret with DPAPI: %w", err)
}
return copyAndFree(output)
}
func (Protector) Unprotect(ciphertext []byte) ([]byte, error) {
if len(ciphertext) == 0 {
return nil, errors.New("DPAPI ciphertext must not be empty")
}
input := blob(ciphertext)
extra := blob(entropy)
var output windows.DataBlob
flags := uint32(windows.CRYPTPROTECT_UI_FORBIDDEN)
if err := windows.CryptUnprotectData(&input, nil, &extra, 0, nil, flags, &output); err != nil {
return nil, fmt.Errorf("unprotect secret with DPAPI: %w", err)
}
return copyAndFree(output)
}
func blob(value []byte) windows.DataBlob {
return windows.DataBlob{Size: uint32(len(value)), Data: &value[0]}
}
func copyAndFree(value windows.DataBlob) ([]byte, error) {
if value.Data == nil || value.Size == 0 {
return nil, errors.New("DPAPI returned empty output")
}
defer windows.LocalFree(windows.Handle(uintptr(unsafe.Pointer(value.Data))))
result := make([]byte, int(value.Size))
copy(result, unsafe.Slice(value.Data, int(value.Size)))
return result, nil
}