diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2019-02-25 18:45:32 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2019-02-28 08:05:02 +0100 |
commit | 019ce9f2815cd21756be4f11702fcb02b5453fdc (patch) | |
tree | 43070181e30db403dfad69f3e67a566ba589df4e /conf/dpapi | |
parent | Initial scaffolding (diff) | |
download | wireguard-windows-019ce9f2815cd21756be4f11702fcb02b5453fdc.tar.xz wireguard-windows-019ce9f2815cd21756be4f11702fcb02b5453fdc.zip |
conf: introduce configuration management
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'conf/dpapi')
-rw-r--r-- | conf/dpapi/dpapi_windows.go | 107 | ||||
-rw-r--r-- | conf/dpapi/dpapi_windows_test.go | 79 | ||||
-rw-r--r-- | conf/dpapi/mksyscall.go | 8 | ||||
-rw-r--r-- | conf/dpapi/zdpapi_windows.go | 68 |
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 +} |