diff --git a/0.go b/0.go index 3d7833c..189fcca 100644 --- a/0.go +++ b/0.go @@ -1,12 +1,10 @@ package main -/* -typedef struct { - char* data; - int len; -} PascalString; -*/ +// #include +// #include +// #include "types.h" import "C" +import "unsafe" // getPascalString returns a pascal // string struct for the input Go string. @@ -28,3 +26,24 @@ func getPascalStringFromBytes(goBytes []byte) C.PascalString { len: C.int(len(goBytes)), } } + +// GetPascalStringFromCString is a helper meant to be used from C. +// +//export GetPascalStringFromCString +func GetPascalStringFromCString(cString *C.char) C.PascalString { + return C.PascalString{ + data: cString, + len: C.int(C.strlen(cString)), + } +} + +// FreeArray frees the memory allocated by a C-allocated array. +// +//export FreeArray +func FreeArray(items *C.PascalString, count C.int) { + linesSlice := (*[1 << 30]C.PascalString)(unsafe.Pointer(items))[:count:count] + for i := 0; i < int(count); i++ { + C.free(unsafe.Pointer(linesSlice[i].data)) + } + C.free(unsafe.Pointer(items)) +} diff --git a/README.md b/README.md index dd8d911..621f91f 100644 --- a/README.md +++ b/README.md @@ -12,23 +12,40 @@ to export to C. All relevant exported libmutton functions have C functions of the same name. Functions with multiple return values all have CGO-generated structs to store the return values. These structs are named `_return`, as per CGO. -All functions continue to perform the same basic operations, with a couple caveats: +All functions perform similar basic operations, with a couple caveats: - Anything that would normally return a Go error now returns a null-terminated *C.char containing the error string. These are safe to null-terminate because their values are much more predictable than other strings. Errors are always the *first* return value, so they can always be referenced with `.r0`. -- Anything that would normally return a Go string or byte slice now returns a C.PascalString struct. +- Many things that would normally require/return a Go string or byte slice now require/return a C.PascalString struct. This is to avoid bugs with null-terminated strings. Additionally, since comments tend to fall out of date, please rely on the [Go documentation for libmutton](https://pkg.go.dev/github.com/rwinkhart/libmutton). Documentation for the CGO bindings present in this repo only specify return values. -### Example (libmutton's global.DirInit) +### Example (decrypting and printing lines of a libmutton entry) Build the following example with `gcc ./cmutton.a`. ```c #include +#include #include "cmutton.h" // import cmutton +static void read_input(const char *prompt, char *buffer, size_t size) { + printf("%s", prompt); + fflush(stdout); + if (fgets(buffer, size, stdin) == NULL) { + fprintf(stderr, "Error reading user input\n"); + exit(1); + } + // remove trailing newline + buffer[strlen(buffer)-1] = '\0'; +} + int main() { + char vanityPath[256]; + char password[256]; + read_input("Enter vanity path: ", vanityPath, sizeof(vanityPath)); + read_input("Enter password: ", password, sizeof(password)); + // use CGO-generated struct to get multiple return values - struct DirInit_return result = DirInit(1); + struct DecryptFileToSlice_return result = DecryptFileToSlice(GetRealPath(vanityPath), GetPascalStringFromCString(password)); // familiar error handling pattern if (result.r0 != NULL) { @@ -36,17 +53,19 @@ int main() { exit(1); // result.r0 not freed since program exits } - // print Pascal string using "%.*s" with printf and supplying both length and data - printf("Old device ID: %.*s\n", result.r1.len, result.r1.data); + for (int i = 0; i < result.r2; i++) { + // print Pascal string using "%.*s" with printf and supplying both length and data + printf("%.*s\n", result.r1[i].len, result.r1[i].data); + } // be sure to free the data! - free(result.r1.data); + FreeArray(result.r1, result.r2); } ``` # Progress - [ ] age - - [ ] AllPasswordEntries(forceReage bool) error + - [ ] AllPasswordEntries(forceReage bool, rcwPassword []byte) error - [ ] Entry(vanityPath string, timestamp int64) error - [ ] TranslateAgeTimestamp(timestamp *int64) uint8 - [ ] clip @@ -65,25 +84,26 @@ int main() { - [ ] EntryIsNotEmpty(entryData []string) bool - [ ] EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) error - [ ] GenTOTP(secret string, time time.Time) (string, error) - - [ ] GetOldEntryData(realPath string, field int) ([]string, error) + - [ ] GetOldEntryData(realPath string, field int, rcwPassword []byte) ([]string, error) - [ ] LibmuttonInit(inputCB func(prompt string) string, rcwPassword []byte, ...) error - [ ] RCWSanityCheckGen(password []byte) error - - [ ] WriteEntry(realPath string, decSlice []string, passwordIsNew bool) error + - [ ] WriteEntry(realPath string, decSlice []string, passwordIsNew bool, rcwPassword []byte) error - [ ] crypt - - [ ] DecryptFileToSlice(realPath string) ([]string, error) - - [ ] EncryptBytes(decBytes []byte) []byte - - [ ] RCWDArgument() - - [ ] VAR: Daemonize bool - - [ ] VAR: RetryPassword bool + - [ ] Address TODOs + - [X] ~~VAR: RetryPassword bool~~ (RCWD not supported) + - [X] DecryptFileToSlice(realPath string) ([]string, error) + - [X] EncryptBytes(decBytes []byte) []byte + - [X] ~~RCWDArgument()~~ (RCWD not supported) - [ ] global + - [ ] Address TODOs + - [X] ~~VAR (CB func): GetPassword~~ (RCWD not supported) - [X] DirInit(preserveOldCfgDir bool) (string, error) - - [ ] ~~GenDeviceIDList() ([]fs.DirEntry, error)~~ (not for use outside of libmutton) + - [X] ~~GenDeviceIDList() ([]fs.DirEntry, error)~~ (not for use outside of libmutton) - [X] GetCurrentDeviceID() (string, error) - [X] GetRealAgePath(vanityPath string) string - [X] GetRealPath(vanityPath string) string - - [ ] ~~GetSysProcAttr() *syscall.SysProcAttr~~ (not for use outside of libmutton) + - [X] ~~GetSysProcAttr() *syscall.SysProcAttr~~ (not for use outside of libmutton) - [X] GetVanityPath(realPath string) string - - [ ] VAR (CB func): GetPassword - [ ] syncclient - [ ] AddFolderRemote(vanityPath string) error - [ ] GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error) diff --git a/crypt.go b/crypt.go new file mode 100644 index 0000000..90614ec --- /dev/null +++ b/crypt.go @@ -0,0 +1,53 @@ +package main + +// #include +// #include "types.h" +import "C" +import ( + "unsafe" + + "github.com/rwinkhart/libmutton/crypt" + "github.com/rwinkhart/rcw/wrappers" +) + +// TODO investigate username+password only entries still having blank lines at the end when decrypted + +// DecryptFileToSlice returns: +// r0: err +// +// r1: decLines (pointer to C-allocated array) +// +// r2: length decLines array +// +//export DecryptFileToSlice +func DecryptFileToSlice(realPath, rcwPassword C.PascalString) (*C.char, *C.PascalString, C.int) { + lines, err := crypt.DecryptFileToSlice(C.GoStringN(realPath.data, realPath.len), []byte(C.GoStringN(rcwPassword.data, rcwPassword.len))) + if err != nil { + return C.CString(err.Error()), nil, 0 + } + + if len(lines) == 0 { + return nil, nil, 0 + } + + // allocate C array for PascalStrings + cLinesPtr := (*C.PascalString)(C.malloc(C.size_t(len(lines)) * C.size_t(C.sizeof_PascalString))) + + // populate the array with the decrypted lines + cLinesSlice := (*[1 << 30]C.PascalString)(unsafe.Pointer(cLinesPtr))[:len(lines):len(lines)] + for i, line := range lines { + cLinesSlice[i] = getPascalString(line) + } + + return nil, cLinesPtr, C.int(len(lines)) +} + +// EncryptBytes returns: +// encBytes +// +//export EncryptBytes +func EncryptBytes(decBytes, rcwPassword C.PascalString) C.PascalString { + // use wrappers.Encrypt directly since C bindings do no support the RCWD daemon + encBytes := wrappers.Encrypt([]byte(C.GoStringN(decBytes.data, decBytes.len)), []byte(C.GoStringN(rcwPassword.data, rcwPassword.len))) + return getPascalStringFromBytes(encBytes) +} diff --git a/global.go b/global.go index 6a41e86..8db68c6 100644 --- a/global.go +++ b/global.go @@ -1,12 +1,7 @@ package main -/* -#include -typedef struct { - char* data; - int len; -} PascalString; -*/ +// #include +// #include "types.h" import "C" import ( "github.com/rwinkhart/libmutton/global" diff --git a/go.mod b/go.mod index 05e5425..dce11c7 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,17 @@ module cmutton go 1.25.6 -require github.com/rwinkhart/libmutton v0.4.3-0.20260111043639-f82b35bc4398 +require ( + github.com/rwinkhart/libmutton v0.4.3-0.20260111043639-f82b35bc4398 + github.com/rwinkhart/rcw v0.2.4 +) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/rwinkhart/go-boilerplate v0.1.1 // indirect + github.com/rwinkhart/peercred-mini v0.1.2 // indirect + golang.org/x/crypto v0.46.0 // indirect golang.org/x/sys v0.40.0 // indirect ) + +replace github.com/rwinkhart/libmutton => ../libmutton diff --git a/go.sum b/go.sum index 1258f49..73e43b6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/rwinkhart/go-boilerplate v0.1.1 h1:C+yyscGWeqvNpR91iw0i91zqX0LcxnCr1VOyT7iM4JY= github.com/rwinkhart/go-boilerplate v0.1.1/go.mod h1:/NVRKGslU20E5xU5YOgXzWxA6aa94BMtv5MtHRTb5Ek= -github.com/rwinkhart/libmutton v0.4.3-0.20260111043639-f82b35bc4398 h1:QufX3nG3H1N986Xh7NZWsVELxmGms1yhNP7/fejBeHY= -github.com/rwinkhart/libmutton v0.4.3-0.20260111043639-f82b35bc4398/go.mod h1:qInUv19lzX1adFNI6Ih/IEVy+pktSTh/yA7cRTdVPH8= +github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo= +github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c= +github.com/rwinkhart/rcw v0.2.4 h1:FrRSsl+ui1WLmDwqjLSROtFPKqSeGLEfF2SPENt4qP0= +github.com/rwinkhart/rcw v0.2.4/go.mod h1:nhW4RAHvEjW2QcOlqEdWqRQOFJLjoOGSPkSRZlMjU3s= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/types.h b/types.h new file mode 100644 index 0000000..c5619c2 --- /dev/null +++ b/types.h @@ -0,0 +1,9 @@ +#ifndef TYPES_H +#define TYPES_H + +typedef struct { + char* data; + int len; +} PascalString; + +#endif