aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/conf/dpapi
diff options
context:
space:
mode:
Diffstat (limited to 'conf/dpapi')
-rw-r--r--conf/dpapi/dpapi_windows.go107
-rw-r--r--conf/dpapi/dpapi_windows_test.go79
-rw-r--r--conf/dpapi/mksyscall.go8
-rw-r--r--conf/dpapi/zdpapi_windows.go68
4 files changed, 262 insertions, 0 deletions
diff --git a/conf/dpapi/dpapi_windows.go b/conf/dpapi/dpapi_windows.go
new file mode 100644
index 00000000..03a5d8a3
--- /dev/null
+++ b/conf/dpapi/dpapi_windows.go
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package dpapi
+
+import (
+ "errors"
+ "golang.org/x/sys/windows"
+ "runtime"
+ "unsafe"
+)
+
+const (
+ dpCRYPTPROTECT_UI_FORBIDDEN uint32 = 0x1
+ dpCRYPTPROTECT_LOCAL_MACHINE uint32 = 0x4
+ dpCRYPTPROTECT_CRED_SYNC uint32 = 0x8
+ dpCRYPTPROTECT_AUDIT uint32 = 0x10
+ dpCRYPTPROTECT_NO_RECOVERY uint32 = 0x20
+ dpCRYPTPROTECT_VERIFY_PROTECTION uint32 = 0x40
+ dpCRYPTPROTECT_CRED_REGENERATE uint32 = 0x80
+)
+
+type dpBlob struct {
+ len uint32
+ data uintptr
+}
+
+func bytesToBlob(bytes []byte) *dpBlob {
+ blob := &dpBlob{}
+ blob.len = uint32(len(bytes))
+ if len(bytes) > 0 {
+ blob.data = uintptr(unsafe.Pointer(&bytes[0]))
+ }
+ return blob
+}
+
+//sys cryptProtectData(dataIn *dpBlob, name *uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) = crypt32.CryptProtectData
+
+func Encrypt(data []byte, name string) ([]byte, error) {
+ out := dpBlob{}
+ err := cryptProtectData(bytesToBlob(data), windows.StringToUTF16Ptr(name), nil, 0, 0, dpCRYPTPROTECT_UI_FORBIDDEN, &out)
+ if err != nil {
+ return nil, errors.New("Unable to encrypt DPAPI protected data: " + err.Error())
+ }
+
+ outSlice := *(*[]byte)(unsafe.Pointer(&(struct {
+ addr uintptr
+ len int
+ cap int
+ }{out.data, int(out.len), int(out.len)})))
+ ret := make([]byte, len(outSlice))
+ copy(ret, outSlice)
+ windows.LocalFree(windows.Handle(out.data))
+
+ return ret, nil
+}
+
+//sys cryptUnprotectData(dataIn *dpBlob, name **uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) = crypt32.CryptUnprotectData
+
+func Decrypt(data []byte, name string) ([]byte, error) {
+ out := dpBlob{}
+ var outName *uint16
+ utf16Name, err := windows.UTF16PtrFromString(name)
+ if err != nil {
+ return nil, err
+ }
+
+ err = cryptUnprotectData(bytesToBlob(data), &outName, nil, 0, 0, dpCRYPTPROTECT_UI_FORBIDDEN, &out)
+ if err != nil {
+ return nil, errors.New("Unable to decrypt DPAPI protected data: " + err.Error())
+ }
+
+ outSlice := *(*[]byte)(unsafe.Pointer(&(struct {
+ addr uintptr
+ len int
+ cap int
+ }{out.data, int(out.len), int(out.len)})))
+ ret := make([]byte, len(outSlice))
+ copy(ret, outSlice)
+ windows.LocalFree(windows.Handle(out.data))
+
+ // Note: this ridiculous open-coded strcmp is not constant time.
+ different := false
+ a := outName
+ b := utf16Name
+ for {
+ if *a != *b {
+ different = true
+ break
+ }
+ if *a == 0 || *b == 0 {
+ break
+ }
+ a = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(a)) + 2))
+ b = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(b)) + 2))
+ }
+ runtime.KeepAlive(utf16Name)
+ windows.LocalFree(windows.Handle(unsafe.Pointer(outName)))
+
+ if different {
+ return nil, errors.New("The input name does not match the stored name")
+ }
+
+ return ret, nil
+}
diff --git a/conf/dpapi/dpapi_windows_test.go b/conf/dpapi/dpapi_windows_test.go
new file mode 100644
index 00000000..e0e9b42d
--- /dev/null
+++ b/conf/dpapi/dpapi_windows_test.go
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package dpapi
+
+import (
+ "bytes"
+ "golang.org/x/sys/windows"
+ "testing"
+ "unsafe"
+)
+
+func TestRoundTrip(t *testing.T) {
+ name := "golang test"
+ original := []byte("The quick brown fox jumped over the lazy dog")
+
+ e, err := Encrypt(original, name)
+ if err != nil {
+ t.Errorf("Error encrypting: %s", err.Error())
+ }
+
+ if len(e) < len(original) {
+ t.Error("Encrypted data is smaller than original data.")
+ }
+
+ d, err := Decrypt(e, name)
+ if err != nil {
+ t.Errorf("Error decrypting: %s", err.Error())
+ }
+
+ if !bytes.Equal(d, original) {
+ t.Error("Decrypted content does not match original")
+ }
+
+ _, err = Decrypt(e, "bad name")
+ if err == nil {
+ t.Error("Decryption failed to notice ad mismatch")
+ }
+
+ eCorrupt := make([]byte, len(e))
+ copy(eCorrupt, e)
+ eCorrupt[len(original)-1] = 7
+ _, err = Decrypt(eCorrupt, name)
+ if err == nil {
+ t.Error("Decryption failed to notice ciphertext corruption")
+ }
+
+ copy(eCorrupt, e)
+ nameUtf16, err := windows.UTF16FromString(name)
+ if err != nil {
+ t.Errorf("Unable to get utf16 chars for name: %s", err)
+ }
+ nameUtf16Bytes := *(*[]byte)(unsafe.Pointer(&struct {
+ addr *byte
+ len int
+ cap int
+ }{(*byte)(unsafe.Pointer(&nameUtf16[0])), len(nameUtf16) * 2, cap(nameUtf16) * 2}))
+ i := bytes.Index(eCorrupt, nameUtf16Bytes)
+ if i == -1 {
+ t.Error("Unable to find ad in blob")
+ } else {
+ eCorrupt[i] = 7
+ _, err = Decrypt(eCorrupt, name)
+ if err == nil {
+ t.Error("Decryption failed to notice ad corruption")
+ }
+ }
+
+ // BUG: Actually, Windows doesn't report length extension of the buffer, unfortunately.
+ //
+ // eCorrupt = make([]byte, len(e)+1)
+ // copy(eCorrupt, e)
+ // _, err = Decrypt(eCorrupt, name)
+ // if err == nil {
+ // t.Error("Decryption failed to notice length extension")
+ // }
+}
diff --git a/conf/dpapi/mksyscall.go b/conf/dpapi/mksyscall.go
new file mode 100644
index 00000000..f80c3fd2
--- /dev/null
+++ b/conf/dpapi/mksyscall.go
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package dpapi
+
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zdpapi_windows.go dpapi_windows.go
diff --git a/conf/dpapi/zdpapi_windows.go b/conf/dpapi/zdpapi_windows.go
new file mode 100644
index 00000000..e48d36b2
--- /dev/null
+++ b/conf/dpapi/zdpapi_windows.go
@@ -0,0 +1,68 @@
+// Code generated by 'go generate'; DO NOT EDIT.
+
+package dpapi
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var _ unsafe.Pointer
+
+// Do the interface allocations only once for common
+// Errno values.
+const (
+ errnoERROR_IO_PENDING = 997
+)
+
+var (
+ errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+)
+
+// errnoErr returns common boxed Errno values, to prevent
+// allocations at runtime.
+func errnoErr(e syscall.Errno) error {
+ switch e {
+ case 0:
+ return nil
+ case errnoERROR_IO_PENDING:
+ return errERROR_IO_PENDING
+ }
+ // TODO: add more here, after collecting data on the common
+ // error values see on Windows. (perhaps when running
+ // all.bat?)
+ return e
+}
+
+var (
+ modcrypt32 = windows.NewLazySystemDLL("crypt32.dll")
+
+ procCryptProtectData = modcrypt32.NewProc("CryptProtectData")
+ procCryptUnprotectData = modcrypt32.NewProc("CryptUnprotectData")
+)
+
+func cryptProtectData(dataIn *dpBlob, name *uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) {
+ r1, _, e1 := syscall.Syscall9(procCryptProtectData.Addr(), 7, uintptr(unsafe.Pointer(dataIn)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(optionalEntropy)), uintptr(reserved), uintptr(promptStruct), uintptr(flags), uintptr(unsafe.Pointer(dataOut)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func cryptUnprotectData(dataIn *dpBlob, name **uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) {
+ r1, _, e1 := syscall.Syscall9(procCryptUnprotectData.Addr(), 7, uintptr(unsafe.Pointer(dataIn)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(optionalEntropy)), uintptr(reserved), uintptr(promptStruct), uintptr(flags), uintptr(unsafe.Pointer(dataOut)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}