mirror of
https://github.com/rwinkhart/cmutton.git
synced 2026-08-27 20:36:31 -04:00
Significant enhancements to secure data handling (especially on iOS)
This commit is contained in:
@@ -43,12 +43,13 @@ func getCPascalStringArrayFromStringSlice(goSlice []string) C.PascalStringArray
|
||||
}
|
||||
|
||||
// getStringSliceFromCPascalStringArray converts a C.PascalStringArray to a go slice.
|
||||
func getStringSliceFromCPascalStringArray(pascalStringArray C.PascalStringArray) []string {
|
||||
func getStringSliceFromCPascalStringArrayAndFree(pascalStringArray C.PascalStringArray) []string {
|
||||
var goSlice []string
|
||||
cArr := (*[1 << 30]C.PascalString)(unsafe.Pointer(pascalStringArray.data))[:pascalStringArray.len:pascalStringArray.len]
|
||||
for i := 0; i < int(pascalStringArray.len); i++ {
|
||||
goSlice = append(goSlice, C.GoStringN(cArr[i].data, cArr[i].len))
|
||||
}
|
||||
FreePascalArray(pascalStringArray)
|
||||
return goSlice
|
||||
}
|
||||
|
||||
@@ -96,4 +97,10 @@ func safeBoolDeref(b *bool) bool {
|
||||
return *b
|
||||
}
|
||||
|
||||
// securePtrOverwrite securely overwrites the memory at the input pointer and then frees it.
|
||||
func securePtrOverwriteAndFree(input unsafe.Pointer, length C.int) {
|
||||
C.explicit_bzero(input, C.size_t(length))
|
||||
C.free(input)
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
@@ -65,61 +65,3 @@ int main() {
|
||||
FreeArray(result.r1);
|
||||
}
|
||||
```
|
||||
|
||||
# Progress
|
||||
- [X] age
|
||||
- [X] AllPasswordEntries(forceReage bool, rcwPassword []byte) error
|
||||
- [X] Entry(vanityPath string, timestamp int64) error
|
||||
- [X] TranslateAgeTimestamp(timestamp *int64) uint8
|
||||
- [X] clip
|
||||
- [X] ~~ClearArgument() error~~ (clipclear argument not supported)
|
||||
- [X] ClearProcess(assignedContents string) error
|
||||
- [X] CopyShortcut(realPath string, field int) error
|
||||
- [X] CopyString(clearClipboardAutomatically bool, copySubject string) error
|
||||
- [X] ~~LaunchClearProcess(copySubject string)~~ (clipclear argument not supported)
|
||||
- [X] ~~TOTPCopier(secret string, errorChan chan<- error, done <-chan bool)~~ (not supported due to complexity)
|
||||
- [X] config
|
||||
- [X] Write(cfg *CfgT, appendMode bool) error
|
||||
- [X] Load() (*CfgT, error)
|
||||
- [X] core
|
||||
- [X] EntryAddPrecheck(realPath string) (uint8, error)
|
||||
- [X] EntryIsNotEmpty(entryData []string) bool
|
||||
- [X] EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) error
|
||||
- [X] GenTOTP(secret string, time time.Time) (string, error)
|
||||
- [X] GetOldEntryData(realPath string, field int, rcwPassword []byte) ([]string, error)
|
||||
- [X] ~~LibmuttonInit(inputCB func(prompt string) string, rcwPassword []byte, ...) error~~ (should be performed manually)
|
||||
- [X] RCWSanityCheckGen(password []byte) error
|
||||
- [X] VerifyEntries(rcwPassword []byte) error
|
||||
- [X] WriteEntry(realPath string, decSlice []string, passwordIsNew bool, rcwPassword []byte) error
|
||||
- [X] crypt
|
||||
- [X] ~~VAR: RetryPassword bool~~ (RCWD not supported)
|
||||
- [X] DecryptFileToSlice(realPath string) ([]string, error)
|
||||
- [X] EncryptBytes(decBytes []byte) []byte
|
||||
- [X] ~~RCWDArgument()~~ (RCWD not supported)
|
||||
- [X] global
|
||||
- [X] ~~VAR (CB func): GetPassword~~ (RCWD not supported)
|
||||
- [X] VAR LibmuttonVersion (GetVersion())
|
||||
- [X] DirInit(preserveOldCfgDir bool) (string, error)
|
||||
- [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
|
||||
- [X] ~~GetSysProcAttr() *syscall.SysProcAttr~~ (not for use outside of libmutton)
|
||||
- [X] GetVanityPath(realPath string) string
|
||||
- [X] syncclient
|
||||
- [X] AddFolderRemote(vanityPath string) error
|
||||
- [X] GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error)
|
||||
- [X] ~~GetSSHClient() (*ssh.Client, bool, *bool, *string, *string, error)~~ (not for use outside of libmutton)
|
||||
- [X] ~~GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) ([]byte, error)~~ (not for use outside of libmutton)
|
||||
- [X] RenameRemote(oldVanityPath, newVanityPath string) error
|
||||
- [X] ShearRemote(vanityPath string, onlyShearAgeFile bool) error
|
||||
- [X] RunJob() (*syncListsT, error)
|
||||
- [X] synccommon
|
||||
- [X] ~~AddFolderLocal(vanityPath string) error~~ (not for use outside of libmutton)
|
||||
- [X] ~~RenameLocal(oldVanityPath, newVanityPath string) error~~ (not for use outside of libmutton)
|
||||
- [X] ~~ShearAgeFileLocal(vanityPath string) error~~ (not for use outside of libmutton)
|
||||
- [X] ~~ShearLocal(vanityPath, clientDeviceID string, onlyShearAgeFile bool) (string, bool, error)~~ (not for use outside of libmutton)
|
||||
- [X] WalkEntryDir() ([]string, []string, error)
|
||||
- [X] GetAllEntryData() (EntryMapT, error)
|
||||
- [X] syncserver
|
||||
- [X] ~~GetRemoteDataFromServer(clientDeviceID string)~~ (not for use outside of libmutton)
|
||||
|
||||
@@ -4,6 +4,8 @@ package main
|
||||
// #include "types.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/rwinkhart/libmutton/age"
|
||||
)
|
||||
|
||||
@@ -23,7 +25,7 @@ func AgeEntry(vanityPath C.PascalString, timestamp int64) *C.char {
|
||||
//
|
||||
//export AgeAllPasswordEntries
|
||||
func AgeAllPasswordEntries(forceReage bool, rcwPassword C.PascalString) *C.char {
|
||||
if err := age.AllPasswordEntries(forceReage, []byte(C.GoStringN(rcwPassword.data, rcwPassword.len))); err != nil {
|
||||
if err := age.AllPasswordEntries(forceReage, C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len)); err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,8 @@ package main
|
||||
// #include "types.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/rwinkhart/libmutton/clip"
|
||||
)
|
||||
|
||||
@@ -27,7 +29,7 @@ func ClearProcess(assignedContents C.PascalString) *C.char {
|
||||
//
|
||||
//export CopyShortcut
|
||||
func CopyShortcut(realPath C.PascalString, field int, rcwPassword C.PascalString) *C.char {
|
||||
if err := clip.CopyShortcut(C.GoStringN(realPath.data, realPath.len), field, []byte(C.GoStringN(rcwPassword.data, rcwPassword.len))); err != nil {
|
||||
if err := clip.CopyShortcut(C.GoStringN(realPath.data, realPath.len), field, C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len)); err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ package main
|
||||
import "C"
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rwinkhart/libmutton/core"
|
||||
)
|
||||
@@ -28,7 +29,7 @@ func EntryAddPrecheck(realPath C.PascalString) (*C.char, uint8) {
|
||||
//
|
||||
//export EntryIsNotEmpty
|
||||
func EntryIsNotEmpty(entryData C.PascalStringArray) bool {
|
||||
notEmpty := core.EntryIsNotEmpty(getStringSliceFromCPascalStringArray(entryData))
|
||||
notEmpty := core.EntryIsNotEmpty(getStringSliceFromCPascalStringArrayAndFree(entryData))
|
||||
return notEmpty
|
||||
}
|
||||
|
||||
@@ -37,7 +38,7 @@ func EntryIsNotEmpty(entryData C.PascalStringArray) bool {
|
||||
//
|
||||
//export EntryRefresh
|
||||
func EntryRefresh(oldRCWPassword, newRCWPassword C.PascalString, removeOldDir bool) *C.char {
|
||||
err := core.EntryRefresh([]byte(C.GoStringN(oldRCWPassword.data, oldRCWPassword.len)), []byte(C.GoStringN(newRCWPassword.data, newRCWPassword.len)), removeOldDir)
|
||||
err := core.EntryRefresh(C.GoBytes(unsafe.Pointer(oldRCWPassword.data), oldRCWPassword.len), C.GoBytes(unsafe.Pointer(newRCWPassword.data), newRCWPassword.len), removeOldDir)
|
||||
if err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func GenTOTP(secret C.PascalString, unixTimestamp int64) (*C.char, C.PascalStrin
|
||||
//
|
||||
//export GetOldEntryData
|
||||
func GetOldEntryData(realPath C.PascalString, field int, rcwPassword C.PascalString) (*C.char, C.PascalStringArray) {
|
||||
lines, err := core.GetOldEntryData(C.GoStringN(realPath.data, realPath.len), field, []byte(C.GoStringN(rcwPassword.data, rcwPassword.len)))
|
||||
lines, err := core.GetOldEntryData(C.GoStringN(realPath.data, realPath.len), field, C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len))
|
||||
if err != nil {
|
||||
return C.CString(err.Error()), C.PascalStringArray{}
|
||||
}
|
||||
@@ -77,7 +78,7 @@ func GetOldEntryData(realPath C.PascalString, field int, rcwPassword C.PascalStr
|
||||
//
|
||||
//export RCWSanityCheckGen
|
||||
func RCWSanityCheckGen(password C.PascalString) *C.char {
|
||||
err := core.RCWSanityCheckGen([]byte(C.GoStringN(password.data, password.len)))
|
||||
err := core.RCWSanityCheckGen(C.GoBytes(unsafe.Pointer(password.data), password.len))
|
||||
if err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
@@ -89,7 +90,7 @@ func RCWSanityCheckGen(password C.PascalString) *C.char {
|
||||
//
|
||||
//export VerifyEntries
|
||||
func VerifyEntries(rcwPassword C.PascalString) *C.char {
|
||||
err := core.VerifyEntries([]byte(C.GoStringN(rcwPassword.data, rcwPassword.len)))
|
||||
err := core.VerifyEntries(C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len))
|
||||
if err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
@@ -101,7 +102,7 @@ func VerifyEntries(rcwPassword C.PascalString) *C.char {
|
||||
//
|
||||
//export WriteEntry
|
||||
func WriteEntry(realPath C.PascalString, decSlice C.PascalStringArray, passwordIsNew bool, rcwPassword C.PascalString) *C.char {
|
||||
if err := core.WriteEntry(C.GoStringN(realPath.data, realPath.len), getStringSliceFromCPascalStringArray(decSlice), passwordIsNew, []byte(C.GoStringN(rcwPassword.data, rcwPassword.len))); err != nil {
|
||||
if err := core.WriteEntry(C.GoStringN(realPath.data, realPath.len), getStringSliceFromCPascalStringArrayAndFree(decSlice), passwordIsNew, C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len)); err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -4,6 +4,8 @@ package main
|
||||
// #include "types.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/rwinkhart/libmutton/crypt"
|
||||
"github.com/rwinkhart/rcw/wrappers"
|
||||
)
|
||||
@@ -15,7 +17,7 @@ import (
|
||||
//
|
||||
//export DecryptFileToSlice
|
||||
func DecryptFileToSlice(realPath, rcwPassword C.PascalString) (*C.char, C.PascalStringArray) {
|
||||
lines, err := crypt.DecryptFileToSlice(C.GoStringN(realPath.data, realPath.len), []byte(C.GoStringN(rcwPassword.data, rcwPassword.len)))
|
||||
lines, err := crypt.DecryptFileToSlice(C.GoStringN(realPath.data, realPath.len), C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len))
|
||||
if err != nil {
|
||||
return C.CString(err.Error()), C.PascalStringArray{}
|
||||
}
|
||||
@@ -33,6 +35,6 @@ func DecryptFileToSlice(realPath, rcwPassword C.PascalString) (*C.char, C.Pascal
|
||||
//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)))
|
||||
encBytes := wrappers.Encrypt(C.GoBytes(unsafe.Pointer(decBytes.data), decBytes.len), C.GoBytes(unsafe.Pointer(rcwPassword.data), rcwPassword.len))
|
||||
return getPascalStringFromBytes(encBytes)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ module cmutton
|
||||
go 1.26rc3
|
||||
|
||||
require (
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208003646-25edd344c861
|
||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208040332-7bfbc7ef309e
|
||||
github.com/rwinkhart/rcw v0.2.5
|
||||
)
|
||||
|
||||
@@ -13,7 +14,6 @@ require (
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/pkg/sftp v1.13.10 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/rwinkhart/go-boilerplate v0.2.2 // indirect
|
||||
github.com/rwinkhart/peercred-mini v0.1.2 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
|
||||
@@ -14,10 +14,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/rwinkhart/go-boilerplate v0.2.2 h1:SVHTAQU+HWFivtUnDBcfrgClJV5ZmHyFS7/uERh7NKU=
|
||||
github.com/rwinkhart/go-boilerplate v0.2.2/go.mod h1:/NVRKGslU20E5xU5YOgXzWxA6aa94BMtv5MtHRTb5Ek=
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208003646-25edd344c861 h1:eDEH43Ch0/tSoAU+t2c4QYK9B3d4gMi9LVqUfr8ET5E=
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208003646-25edd344c861/go.mod h1:Z1QDejdTmI/ydQtnu5jUYZszAeT30AtXPDm0wkowAu0=
|
||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781 h1:kMAM0fkWGPa8WK7A4a9rMX2oD2Lv198zJoD9LCUAzOA=
|
||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781/go.mod h1:QcUlr4SMgONRMKD965m8oTuCa6ZJVAc17w27Qopyr+s=
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208040332-7bfbc7ef309e h1:Hl/LG+vc7RkU4tHVq+rMt15zg4t3utDZN6HY5RfPDPY=
|
||||
github.com/rwinkhart/libmutton v0.4.3-0.20260208040332-7bfbc7ef309e/go.mod h1:G9EvjsHvTyOvUOs1VZ8aRKRpRRgzrrdZSCGheL/t72I=
|
||||
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.5 h1:3GJeii9sDZsZNL+HRkfHrnbgxnaB8a9kejiMIfPXnpE=
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <string.h>
|
||||
// #include "types.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/privkey"
|
||||
)
|
||||
|
||||
// SetPrivKeyDataAndEraseFreeInput is meant to be called on iOS
|
||||
// prior to any server connections to ensure libmutton can
|
||||
// retrieve the correct SSH private key contents (enables
|
||||
// loading from keychain).
|
||||
//
|
||||
//export SetPrivKeyDataAndEraseFreeInput
|
||||
func SetPrivKeyDataAndEraseFreeInput(privKeyData C.PascalString) {
|
||||
privKeyDataPtr := unsafe.Pointer(privKeyData.data)
|
||||
privkey.SetBytes(C.GoBytes(privKeyDataPtr, privKeyData.len))
|
||||
securePtrOverwriteAndFree(privKeyDataPtr, privKeyData.len)
|
||||
FreePascalString(privKeyData)
|
||||
}
|
||||
|
||||
// GetPrivKeyDataAndEraseGoCopy will return nil on non-iOS platforms.
|
||||
// On iOS, it will return whatever was set in
|
||||
// SetPrivKeyDataAndSecurelyFreeInput.
|
||||
//
|
||||
// Don't forget to call EraseFreePrivKeyPascal on the output
|
||||
// of this function as soon as it is no longer needed.
|
||||
//
|
||||
//export GetPrivKeyDataAndEraseGoCopy
|
||||
func GetPrivKeyDataAndEraseGoCopy() C.PascalString {
|
||||
privKeyBytes, _ := privkey.GetBytes(nil)
|
||||
privKeyPascal := getPascalStringFromBytes(privKeyBytes)
|
||||
back.EraseBytesSecurely(privKeyBytes)
|
||||
return privKeyPascal
|
||||
}
|
||||
|
||||
// EraseFreePrivKeyPascal is meant to be called on
|
||||
// iOS after any server connections to ensure the cached SSH private key
|
||||
// set by SetPrivKeyData is securely erased. It also erases and frees
|
||||
// the input privKeyData C.PascalString.
|
||||
//
|
||||
//export EraseFreePrivKeyPascal
|
||||
func EraseFreePrivKeyPascal(privKeyPascal C.PascalString) {
|
||||
securePtrOverwriteAndFree(unsafe.Pointer(privKeyPascal.data), privKeyPascal.len)
|
||||
FreePascalString(privKeyPascal)
|
||||
}
|
||||
Reference in New Issue
Block a user