package utils import ( "bufio" "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "io" "math/big" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "unicode" "unicode/utf16" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/term" "github.com/cloudflare/circl/kem/kyber/kyber768" "golang.org/x/crypto/argon2" cc20 "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" "sources.truenas.cloud/code/syscrypt" "sources.truenas.cloud/code/syscrypt/internal/vars" ) //////////////////////////////////////////////////////////////////////////////////////////////////////////////// var BypassList = []string{ //config.MasterPass, } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsAlphaNumeric(s string) bool { if len(s) == 0 { return false } for _, r := range s { if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) { return false } } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsPublicKey(key string) bool { if key != strings.ToLower(key) { return false } prefix := vars.DefaultPrefixLabel return strings.HasPrefix(key, prefix) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsPrivateKey(key string) bool { prefix := vars.PrivateKeyPrefixLabel return strings.HasPrefix(key, prefix) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsValidEncryption(filePath string) bool { fileExists := FileExists(filePath) if !fileExists { return false } _, err := os.Open(filePath) if err != nil { return false } fileRead, err := os.ReadFile(filePath) if err != nil { return false } content := string(fileRead) firstLine, _ := GetFirstLine(filePath) isBase64 := IsValidBase64WithLines(content) if !(firstLine == vars.PrivateKeyHeader || strings.HasPrefix(firstLine, vars.LockVersionLabel) || strings.HasPrefix(firstLine, vars.SyscryptLabel)) { return false } if firstLine == vars.PrivateKeyHeader && !isBase64 { return false } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsValidPath(path string) bool { if path == "" || strings.ContainsRune(path, 0) { return false } if runtime.GOOS == "windows" { badChars := `<>"|?*` checkPath := path if len(path) >= 2 && path[1] == ':' { checkPath = path[2:] } if strings.ContainsAny(checkPath, badChars) { return false } } hasSeperator := strings.ContainsRune(path, filepath.Separator) isRelative := strings.HasPrefix(path, "."+string(filepath.Separator)) hasDrive := runtime.GOOS == "windows" && len(path) >= 2 && path[1] == ':' if !hasSeperator && !isRelative && !hasDrive { return false } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ArgumentMap(selectedArg string) (result string) { arguments := map[string]string{ "-v": "VERSION", "-c": "COMMAND", "-a": "ARMORED", "-k": "KEY", "-i": "INPUT", "-o": "OUTPUT", "-L": "LOCK", "-A": "APIKEY", "-P": "MASTERPASSWORD", "-C": "COMMENT", "-f": "FRIENDLYNAME", "-K": "KEYPATH", } for flag, description := range arguments { if flag == selectedArg { result = description break } } return result } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateAllowedFlags(rules map[string]struct{}) { args := os.Args[1:] seen := make(map[string]bool) for _, arg := range args { if !strings.HasPrefix(arg, "-") { continue } if seen[arg] { fmt.Printf(" ERROR: syscrypt: Flag '%s' is used more than once\n", arg) os.Exit(1) } seen[arg] = true if _, exists := rules[arg]; !exists { fmt.Printf(" ERROR: syscrypt: Unknown or misplaced flag '%s'\n", arg) os.Exit(1) } } } //input := os.Args[1:] //if len(input) == 0 { // return //} //context := input[0] //if len(input) > 1 { // context = input[1] //} //for i := 0; i < len(input); i++ { // // // current := input[i] // needsValue, exists := rules[current] // if !exists { // msg := " ERROR: syscrypt: " + context + ": the flag '" + current + "' is not valid for " + context // HandleFailure(msg, nil) // os.Exit(1) // } // if needsValue { // if i+1 >= len(input) { // msg := " ERROR: syscrypt: " + context + ": requires a value." // HandleFailure(msg, nil) // os.Exit(1) // } // i++ // } // // //} //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CheckFirstFlag() error { if len(os.Args) < 2 { return errors.New("Missing -c COMMAND flag. See usage for details.") } firstArg := os.Args[1] if firstArg == "-v" || firstArg == "-version" { return nil } if firstArg != "-c" { return fmt.Errorf("invalid flag %q: expected -c COMMAND", firstArg) } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CleanIntro(inputflag string) string { cleantext := inputflag cleantext = strings.ReplaceAll(cleantext, "--", "") cleantext = strings.ReplaceAll(cleantext, "\n", "") cleantext = strings.Join(strings.Fields(cleantext), "") return cleantext } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////### func SelectIntro() (string, error) { var isLocked bool var isArmored bool var lockedState string isLockSet, _ := IsFlagPassed("L") isKeySet, _ := IsFlagPassed("k") isInputSet, _ := IsFlagPassed("i") lockValue, _ := GetFlagValue("L") keyValue, _ := GetFlagValue("k") inputValue, _ := GetFlagValue("i") serialFetch, err := FetchFileKey(keyValue, "serial") if err != nil { msg := fmt.Sprintf("unable to parse serial number from key file. %s\n", keyValue) HandleFailure(msg) os.Exit(1) } switch vars.CommandFlag { case "encrypt": isLocked, _ = IsFlagPassed("L") isArmored = false case "decrypt": isLocked, _, _ = IsFileLocked(inputValue) default: msg := "unable to select locked state header.\n" HandleFailure(msg) os.Exit(1) } sLen := len(serialFetch) var prefixLen int if isLocked { prefixLen = 17 } else { prefixLen = 12 } totalRawBytes := prefixLen + sLen + 2 remainder := totalRawBytes % 3 paddingNeeded := 0 if remainder != 0 { paddingNeeded = 3 - remainder } if isLocked { lockedState = vars.LockVersionLabel + vars.EndAnchor } else { lockedState = vars.VersionLabel + vars.EndAnchor } clean := strings.ReplaceAll(lockedState, "[serial]", serialFetch) clean = clean + strings.Repeat(" ", paddingNeeded) /* fmt.Printf("totalRawBytes: %v\n", totalRawBytes) fmt.Printf("len: %v\n", prefixLen) fmt.Printf("serial length: %v\n", sLen) fmt.Printf("command: %s\n", vars.CommandFlag) fmt.Printf("isLockSet: %v\n", isLockSet) fmt.Printf("isKeySet: %v\n", isKeySet) fmt.Printf("isInputSet: %v\n", isInputSet) fmt.Printf("lockValue: %s\n", lockValue) fmt.Printf("keyvalue: %s\n", keyValue) fmt.Printf("inputValue: %s\n", inputValue) fmt.Printf("serial: %s\n", serialFetch) fmt.Printf("islocked: %v\n", isLocked) */ _ = isLockSet _ = isKeySet _ = isInputSet _ = lockValue _ = keyValue _ = inputValue _ = isLocked _ = isArmored _ = lockedState _ = serialFetch _ = isLocked return clean, nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CheckExtraFlags() ([]string, error) { args := os.Args cIndex := -1 for i, arg := range args { if strings.ToLower(arg) == "-c" { cIndex = i break } } if cIndex == -1 || cIndex+1 >= len(args) { return nil, errors.New("missing -c COMMAND flag or its associated value. See Usage for details.") } remaining := args[cIndex+2:] var foundFlags []string for _, arg := range remaining { if strings.HasPrefix(arg, "-") { foundFlags = append(foundFlags, arg) } } if len(foundFlags) == 0 { return nil, fmt.Errorf("error: no additional flags provided after -C COMMAND %s", args[cIndex+1]) } return foundFlags, nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func HasValidPrefix(str, prefix string) bool { return strings.HasPrefix(str, prefix) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateRequiredFlags(required []vars.FlagRequirement, action string) { for _, req := range required { isFlagSet, hasValue := IsFlagPassed(req.Flag[1:]) flag := req.Flag[1:] if !isFlagSet { context := ArgumentMap("-" + flag) msg := fmt.Sprintf("%s: -%s %s: flag is required.\n", vars.CommandFlag, flag, context) HandleFailure(msg) os.Exit(1) } if isFlagSet && req.IsRequired && !hasValue { context := ArgumentMap("-" + flag) msg := fmt.Sprintf("%s: -%s %s: requires a value.", vars.CommandFlag, flag, context) HandleFailure(msg) os.Exit(1) } } /* //func ValidateRequiredFlags(required map[string]bool, action string) { for flag, isRequired := range required { //fmt.Printf("%v\n", flag) //fmt.Printf("%v\n", isRequired) //fmt.Printf("\n\n") //flagtmp := strings.ReplaceAll(flag, "-", "") present, hasValue := IsFlagPassed(flag[1:]) fmt.Printf("%s present: %v\n", flag, present) fmt.Printf("has value: %v\n", hasValue) fmt.Printf("\n\n") if !present { //flagTemp := strings.ReplaceAll(flag, "-", "") context := ArgumentMap(flag) msg := fmt.Sprintf("%s: %s (%s) was not provided.", vars.CommandFlag, flag, context) HandleFailure(msg) os.Exit(1) } if present && isRequired && !hasValue { context := ArgumentMap(flag) msg := fmt.Sprintf("%s: %s (%s) requires a value.", vars.CommandFlag, flag, context) HandleFailure(msg) os.Exit(1) } //if present && isRequired && !hasValue { // fmt.Printf("error") //} //_ = hasValue //flagtmp := strings.ReplaceAll(flag, "-", "") //present, hasValue := IsFlagPassed(flagtmp[1:]) //fmt.Printf("%s\n", flagtmp) //fmt.Printf("%v\n", present) //fmt.Printf("%v\n", hasValue) //if !present && isRequired { // context := ArgumentMap(flag) // //msg := fmt.Printf("%s: %s: The required flag '%s' (%s) was not provided.", vars.CommandFlag, action, flag, context) // msg := fmt.Sprintf("%s: %s: The required flag '%s' (%s) was not provided.", vars.CommandFlag, action, flag, context) // HandleFailure(msg) // os.Exit(1) //} //if present && isRequired && !hasValue { // fmt.Printf("fuck") //} //if !present { // context := ArgumentMap(flag) // msg := " ERROR: syscrypt: " + action + ": the required flag " + flag + " '" + context + "' was not provided." // HandleFailure(msg) // os.Exit(1) //} //if present && !hasValue && isRequired { // context := ArgumentMap(flag) // msg := fmt.Sprintf("%s: %s: The required flag %s. '%s' requires a value.", vars.CommandFlag, action, flag, context) // HandleFailure(msg) // os.Exit(1) //} //_ = hasValue //_ = isRequired } */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ConvertToUTF16(s string) []byte { codePoints := utf16.Encode([]rune(s)) buf := make([]byte, len(codePoints)*2) for i, cp := range codePoints { binary.LittleEndian.PutUint16(buf[i*2:], cp) } return buf } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func RandomCode(length int) (string, error) { pool := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+" result := make([]byte, length) poolLen := big.NewInt(int64(len(pool))) for i := 0; i < length; i++ { num, err := rand.Int(rand.Reader, poolLen) if err != nil { return "", err } result[i] = pool[num.Int64()] } return string(result), nil } func RandomString(length int) string { const charset = "ABCDEFGHJKMNPQRSTUVWXYZ123456789" result := make([]byte, length) _, err := rand.Read(result) if err != nil { fmt.Println(" ERROR: syscrypt: Failed to generate random sequence.") os.Exit(1) } for i := 0; i < length; i++ { result[i] = charset[result[i]%byte(len(charset))] } return string(result) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func NotifyPushover(message string) { /* if config.PushoverActive == "1" { go func() { http.PostForm(config.PushoverEndpoint, url.Values{ "token": {config.PushoverToken}, "user": {config.PushoverUser}, "message": {message}, "title": {"syscrypt Security Alert"}, "priority": {"1"}, }) }() } */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func AbsPath(path string) string { abs, err := filepath.Abs(path) if err != nil { return path } return abs } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func BypassCheck(path string) bool { if path == "" { return false } absInput := AbsPath(path) for _, b := range BypassList { if absInput == AbsPath(b) { return true } } return false } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func BootStrap() bool { //return config.Bootstrap == "1" return false } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CheckLockout() { /* data, err := os.ReadFile(config.LockPath) if err != nil { return } var failures int var lastFail int64 host, _ := os.Hostname() fmt.Sscanf(string(data), "%d %d", &failures, &lastFail) if failures >= config.MaxFailures { remaining := (lastFail + int64(config.LockoutMinutes*60)) - time.Now().Unix() if remaining > 0 { fmt.Fprintf(os.Stderr, " ERROR: syscrypt: System locked. Try again in %d minutes \n", (remaining/60)+1) NotifyPushover(fmt.Sprintf("CRITICAL: host %s is now LOCKED OUT for %d minutes due to repeated failures.", host, config.LockoutMinutes)) os.Exit(403) } else { _ = os.Remove(config.LockPath) } } */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func RecordFailure() { /* var failures int var lastFail int64 data, err := os.ReadFile(config.LockPath) if err == nil { fmt.Sscanf(string(data), "%d %d", &failures, &lastFail) } failures++ _ = os.WriteFile(config.LockPath, []byte(fmt.Sprintf("%d %d", failures, time.Now().Unix())), 0600) host, _ := os.Hostname() if failures >= config.MaxFailures { NotifyPushover(fmt.Sprintf("CRITICAL: Host %s is now LOCKED OUT for %d minutes due to repeated failures.", host, config.LockoutMinutes)) } else { NotifyPushover(fmt.Sprintf("WARNING: Failed access attempt on %s. Strike %d/%d.", host, failures, config.LockoutMinutes)) } */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func RateLimit() { /* now := time.Now().Unix() var tokens int64 var lastUpdate int64 data, err := os.ReadFile(config.StatePath) if err == nil { fmt.Sscanf(string(data), "%d %d", &tokens, &lastUpdate) delta := now - lastUpdate tokens += delta / config.RefillSeconds if tokens > config.MaxTokens { tokens = config.MaxTokens } if delta >= config.RefillSeconds { lastUpdate = now } } else { tokens = config.MaxTokens lastUpdate = now } host, _ := os.Hostname() if tokens <= 0 { _ = os.WriteFile(config.StatePath, []byte(fmt.Sprintf("%d %d", 0, now)), 0600) fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Rate limit exceeded.\n") NotifyPushover(fmt.Sprintf("WARNING: Rate limit exceeded. %s.", host)) } tokens-- _ = os.WriteFile(config.StatePath, []byte(fmt.Sprintf("%d %d", tokens, lastUpdate)), 0600) */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ReadFirstLine(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", err } defer file.Close() scanner := bufio.NewScanner(file) if scanner.Scan() { ScannerText := scanner.Text() ScannerText = strings.TrimSuffix(ScannerText, "--") ScannerText = strings.Join(strings.Fields(ScannerText), "") return ScannerText, nil } if err := scanner.Err(); err != nil { return "", err } return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetFirstLine(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", err } scanner := bufio.NewScanner(file) if scanner.Scan() { ScannerText := scanner.Text() return ScannerText, nil } if err := scanner.Err(); err != nil { return "", err } return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetFirstLineFromString(s string) string { if i := strings.Index(s, "\n"); i >= 0 { return s[:i] } return s } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetLastLine(s string) (string, error) { file, err := os.Open(s) if err != nil { return "", err } defer file.Close() var lineOffsets []int64 lineOffsets = append(lineOffsets, 0) reader := bufio.NewReader(file) var offset int64 for { line, err := reader.ReadBytes('\n') offset += int64(len(line)) if err != nil { // if err == io.EOF { break } return "", err // } lineOffsets = append(lineOffsets, offset) } if len(lineOffsets) == 0 { return "", fmt.Errorf("file is empty") } lastLineOffset := lineOffsets[len(lineOffsets)-1] if lastLineOffset == offset && len(lineOffsets) > 1 { lastLineOffset = lineOffsets[len(lineOffsets)-2] } _, err = file.Seek(lastLineOffset, io.SeekStart) if err != nil { return "", err } finalScanner := bufio.NewScanner(file) if finalScanner.Scan() { return finalScanner.Text(), nil } return "", finalScanner.Err() } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func StripHeaders(s string) string { s = strings.ReplaceAll(s, "-----BEGIN ENCRYPTED DATA-----\n", "") s = strings.ReplaceAll(s, "-----END ENCRYPTED DATA-----\n", "") isBase64 := IsValidBase64WithLines(s) var content string if isBase64 { content = "" /// decode base64 message } else { content = "" // return string untouched } _ = content return s } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CheckHeader(path string, header string) error { file, err := os.Open(path) if err != nil { return fmt.Errorf(" ERROR: syscrypt: CheckHeader: could not open file: %v", err) } defer file.Close() buffer := make([]byte, len(header)) _, err = file.Read(buffer) if err != nil { return fmt.Errorf(" ERROR: syscrypt: checkHeader: could not read file header: %v", err) } if string(buffer) != header { return fmt.Errorf(" ERROR: syscrypt: checkHeader: Invalid file. Header does not match %s", header) } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CheckPermission(path string, want os.FileMode) { st, err := os.Stat(path) if err != nil { return } if !st.Mode().IsRegular() { fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Invalid type for '%s'. Expected 'file' type.\n", path) } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func FolderExists(path string) bool { info, err := os.Stat(path) if err != nil { return false } return info.IsDir() } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func FetchFileKey(path string, object string) (string, error) { if _, err := exec.LookPath("jq"); err != nil { fmt.Fprintf(os.Stderr, "syscrypt: FetchFileKey: jq is not installed.") os.Exit(1) } info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { msg := "syscrypt: FetchFileKey: Unable to open file: " + path + "\n" HandleFailure(msg) os.Exit(1) //fmt.Fprintf(os.Stderr, " ERROR: syscrypt: FetchFileKey: Unable to open file: %s", path+"\n") //os.Exit(1) } } if info.IsDir() { fmt.Fprintf(os.Stderr, "syscrypt: FetchFileKey: Path is a directory. File is expected. %s", path+"\n") os.Exit(1) } cmd := exec.Command("jq", "-e", "-r", ".[] | ."+object, path) var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out cmd.Stderr = &stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "syscrypt: FetchFileKey: Invalid Key file or malformed JSON at %s: %s", path, stderr.String()+"\n") os.Exit(1) } key := strings.TrimSpace(out.String()) if key == "" || key == "null" { fmt.Fprintf(os.Stderr, "syscrypt: No 'key' field found in JSON root at %s\n", path) os.Exit(1) } return key, err } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GenerateSerial(n int) string { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" max := big.NewInt(int64(len(alphabet))) b := make([]byte, n) for idx := 0; idx < n; idx++ { rn, err := rand.Int(rand.Reader, max) if err != nil { msg := fmt.Sprintf(" ERROR: syscrypt-keygen: genSerialUpperAlnum: serial gen failed: %v", err) HandleFailure(msg) os.Exit(1) } b[idx] = alphabet[rn.Int64()] } return string(b) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GenerateAPIKey(salt, password, serial, privateKey string) string { h := hmac.New(sha256.New, []byte(privateKey)) h.Write([]byte("v1")) h.Write([]byte(salt)) h.Write([]byte(password)) h.Write([]byte(serial)) signature := hex.EncodeToString(h.Sum(nil)) return signature } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateAPIKey(apiKey, salt, password, serial, privateKey string) bool { expectedKey := GenerateAPIKey(salt, password, serial, privateKey) if len(apiKey) != len(expectedKey) { return false } result := hmac.Equal([]byte(apiKey), []byte(expectedKey)) return result } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GeneratePassword(length int) (string, error) { const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "!@#$%^&*()-_=+[]{}|;:,.<>?" password := make([]byte, length) for i := range password { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) if err != nil { return "", err } password[i] = charset[num.Int64()] } return string(password), nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GenerateKeys(publicKey interface{}, privateKey interface{}, serial interface{}) (string, error) { privateKeyString := privateKey.(string) publicKeyString := publicKey.(string) serialString := serial.(string) //masterPass := config.MasterPass //fmt.Printf("masterpass: %v\n", masterPass) fmt.Printf("private key: %v\n", privateKeyString) fmt.Printf("public key: %v\n", publicKeyString) fmt.Printf("serial: %v\n", serialString) isOutputSet, _ := IsFlagPassed("o") outputValue, _ := GetFlagValue("o") outputIsValidPath := IsValidPath(outputValue) if !isOutputSet { msg := fmt.Sprintf("%s: -o OUTPUT: is required when using the keygen tool.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } if isOutputSet && outputValue == "" { msg := fmt.Sprintf("%s: -o OUTPUT: a value is required when using the keygen tool.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } if !outputIsValidPath { msg := fmt.Sprintf("%s: -o OUTPUT: a valid path is required when using the keygen tool.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } if privateKeyString == "" { msg := fmt.Sprintf("%s: privateKeyString value is empty.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } isCommentSet, _ := IsFlagPassed("C") commentValue, _ := GetFlagValue("C") if isCommentSet && commentValue == "" { msg := fmt.Sprintf("%s: -C COMMENT: comment flag was set but no value was provided.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } if publicKeyString == "" { msg := fmt.Sprintf("%s: publicKeyString value is empty.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } if serialString == "" { msg := fmt.Sprintf("%s: serialString value is empty.\n", vars.CommandFlag) HandleFailure(msg) os.Exit(1) } isValidPrivateKey := IsPrivateKey(privateKeyString) isValidPublicKey := IsPublicKey(publicKeyString) if !isValidPrivateKey { msg := fmt.Sprintf("%s: Invalid Private Key format. Expected '%s', received: '%s'.\n", vars.CommandFlag, vars.PrivateKeyPrefixLabel+"[your private key]", privateKeyString) HandleFailure(msg) os.Exit(1) } if !isValidPublicKey { msg := fmt.Sprintf("%s: Invalid Public key format. Expected '%s', received: '%s'.\n", vars.CommandFlag, vars.DefaultPrefixLabel+"[your public key]", publicKeyString) HandleFailure(msg) os.Exit(1) } privStruct := syscrypt.PrivateKeyWrapper{} pubStruct := syscrypt.PublicKeyWrapper{} fmt.Printf("--------------------------------------\n") fmt.Printf("privstruct: %s\n", privStruct) fmt.Printf("pubstruct: %s\n", pubStruct) //isKeySet, keyHasValue := utils.IsFlagPassed("k") //keyValue, _ := utils.GetFlagValue("k") //keyIsValidPath := utils.IsValidPath(keyValue) //isInputSet, inputHasValue := utils.IsFlagPassed("i") //inputValue, _ := utils.GetFlagValue("i") //inputIsValidPath := utils.IsValidPath(inputValue) //isOutputSet, outputHasValue := utils.IsFlagPassed("o") //outputValue, _ := utils.GetFlagValue("o") //outputIsValidPath := utils.IsValidPath(outputValue) //isArmoredSet, armoredHasValue := utils.IsFlagPassed("a") //isLockSet, lockHasValue := utils.IsFlagPassed("L") //isAPISet, apiHasValue := utils.IsFlagPassed("A") //apiValue, _ := utils.GetFlagValue("A") //apiIsValidPath := utils.IsValidPath(apiValue) //isPassSet, passHasValue := utils.IsFlagPassed("P") //passValue, _ := utils.GetFlagValue("P") //passIsValidPath := utils.IsValidPath(passValue) return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ExtractSerial(filePath string) (string, error) { fileExists := FileExists(filePath) if !fileExists { return "", fmt.Errorf("Extract Serial: unable to open file '%s'", filePath) } data, err := os.ReadFile(filePath) if err != nil { return "", err } var workingData []byte content := string(data) if strings.Contains(content, vars.PrivateKeyHeader) { content = strings.ReplaceAll(content, vars.PrivateKeyHeader, "") content = strings.ReplaceAll(content, vars.PrivateKeyFooter, "") content = strings.TrimSpace(content) decoded, err := base64.StdEncoding.DecodeString(content) if err != nil { return "", fmt.Errorf("Extract Serial: base64 decode failed: %w", err) } workingData = decoded } else { workingData = data } decodedStr := string(workingData) startAnchor := vars.StartAnchor endAnchor := vars.EndAnchor anchorIdx := strings.Index(decodedStr, startAnchor) if anchorIdx == -1 { return "", fmt.Errorf("Extract Serial: malformed encrypted file: start anchor not found") } serialStart := anchorIdx + len(startAnchor) endIdx := strings.Index(decodedStr[serialStart:], endAnchor) if endIdx == -1 { return "", fmt.Errorf("Extract Serial: malformed encrypted file: end anchor '--' not found") } absoluteEndIdx := serialStart + endIdx serial := decodedStr[serialStart:absoluteEndIdx] return strings.TrimSpace(serial), nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //func GenerateAPIKey(masterKey string) (string, error) { //randomID := make([]byte, 16) //if _, err := rand.Read(randomID); err != nil { // return "", err //} //publicID := hex.EncodeToString(randomID) //h := hmac.New(sha256.New, []byte(masterKey)) //h.Write([]byte(publicID)) //signature := hex.EncodeToString(h.Sum(nil))[:16] //return fmt.Sprintf("%s.%s", publicID, signature), nil //} //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //func GenerateAPIKeyFile(masterKey string) (string, error) { // APIKeyOut, err := os.OpenFile(xxxx, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) // if err != nil { // } //} func ResetDictionary() error { /* var mapPath = config.DictionaryTmp chars := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ " pool := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+" blockSize := 4 if err := os.MkdirAll(filepath.Dir(mapPath), 0755); err != nil { return err } file, err := os.Create(mapPath) if err != nil { return err } defer file.Close() usedCodes := make(map[string]bool) for _, char := range chars { for { code, err := generateRandomBlock(pool, blockSize) if err != nil { return err } if !usedCodes[code] { usedCodes[code] = true _, err := fmt.Fprintf(file, "[%c]:%s\n", char, code) if err != nil { return err } break } } } return nil */ return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func generateRandomBlock(pool string, size int) (string, error) { bytes := make([]byte, size) if _, err := rand.Read(bytes); err != nil { return "", err } for i, b := range bytes { bytes[i] = pool[b%byte(len(pool))] } return string(bytes), nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func Encode(input string) (string, error) { /* binaryPath := config.BinaryPath + "/syscrypt-encode" ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, binaryPath, "-s", input) cmd.Env = append(os.Environ(), "SYSCRYPT_INTERNAL_CALL=1") out, err := cmd.CombinedOutput() if ctx.Err() == context.DeadlineExceeded { return "", fmt.Errorf(" ERROR: syscrypt: Encode: timed out %s", string(out)) } if err != nil { return "", fmt.Errorf(" ERROR: syscrypt: Encode: failed to encode string: %w %s", err, string(out)) } return strings.TrimSpace(string(out)), nil */ return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // func HandleFailure(msg string, err error) error { func HandleFailure(msg string) { //ClearTerminal() isInternal := os.Getenv("SYSCRYPT_INTERNAL_CALL") == "1" bannerStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#000000")). Background(lipgloss.Color("#FFD700")). Padding(0, 2) boxStyle := lipgloss.NewStyle(). //Border(lipgloss.RoundedBorder()). Border(lipgloss.NormalBorder(), true, false, true, false). BorderForeground(lipgloss.Color("#797979")). Padding(1, 1). Margin(1, 1) codeStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#c4b810")). Underline(false) errorStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#FF0000")) fmt.Println("\n" + bannerStyle.Render(" SYSCRYPT ERROR ")) content := msg //if err != nil { // fmt.Println(boxStyle.Render(content)) //} if isInternal { fmt.Println(content) } else { fmt.Println(boxStyle.Render(content)) } //if isInternal { // //return fmt.Errorf("%s", msg) //} else { // //return fmt.Println //} //if isInternal { // return fmt.Errorf("%s", fullMsg) //} _ = errorStyle _ = codeStyle _ = boxStyle _ = isInternal //fullMsg := msg //if err != nil { // Alert("\n") // fullMsg = fmt.Sprintf("%s: %v", msg, err) // Alert("\n") //} //if isInternal { // return fmt.Errorf("%s", fullMsg) //} //fmt.Fprintf(os.Stderr, "%s\n", fullMsg) //os.Exit(1) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func Alert(msg string) { fullMsg := msg if fullMsg == "" { fmt.Println(" ERROR: syscrypt: Alert requires a message.") os.Exit(1) } fmt.Printf("%v\n", fullMsg) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func Decode(input string, key string) (string, error) { /* binaryPath := config.BinaryPath + "/syscrypt-decode" ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, binaryPath, "-s", input) cmd.Env = append(os.Environ(), "SYSCRYPT_INTERNAL_CALL=1") out, err := cmd.CombinedOutput() if ctx.Err() == context.DeadlineExceeded { return "", fmt.Errorf(" ERROR: syscrypt: Decode: timed out %s", string(out)) } if err != nil { return "", fmt.Errorf(" ERROR: syscrypt: Decode: failed to encode string: %w %s", err, string(out)) } return strings.TrimSpace(string(out)), nil */ return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func DecryptFile(path string, password string) (string, error) { /* binaryPath := config.BinaryPath + "/syscrypt" ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, binaryPath, "-d", "-i", config.KeyPath, "-k", password, path) cmd.Env = append(os.Environ(), "SYSCRYPT_INTERNAL_CALL=1") out, err := cmd.CombinedOutput() if ctx.Err() == context.DeadlineExceeded { return "", fmt.Errorf(" ERROR: syscrypt: DecryptFile: timed out: %s", string(out)) } if err != nil { return "", fmt.Errorf(" ERROR: syscrypt: DecryptFile: decrypt failed: %w %s", err, string(out)) } return strings.TrimSpace(string(out)), nil */ return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func EncryptFile(path string, password string) (string, error) { /* binaryPath := config.BinaryPath + "/syscrypt" ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, binaryPath, "-a", "-R", config.KeyPublicPath, "-k", password, path) cmd.Env = append(os.Environ(), "SYSCRYPT_INTERNAL_CALL=1") out, err := cmd.CombinedOutput() if ctx.Err() == context.DeadlineExceeded { return "", fmt.Errorf(" ERROR: syscrypt: EncryptFile: timed out: %s", string(out)) } if err != nil { return "", fmt.Errorf(" ERROR: syscrypt: EncryptFile: encrypt failed: %w %s", err, string(out)) } return strings.TrimSpace(string(out)), nil */ return "", nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func CopyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() destFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } defer destFile.Close() _, err = io.Copy(destFile, sourceFile) return err } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsFileEmpty(path string) bool { info, err := os.Stat(path) if err != nil { return true } return info.Size() == 0 } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func FileExists(path string) bool { _, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return false } } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ConfirmOverwrite(path string) { _, err := os.Stat(path) if os.IsNotExist(err) { return } bannerStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#000000")). Background(lipgloss.Color("#FFD700")). Padding(0, 2) boxStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#d32020")). Padding(1, 4). Margin(1, 0) codeStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#c4b810")). Underline(false) errorStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#FF0000")) fmt.Println("\n" + bannerStyle.Render(" ACTION REQUIRED ")) randomcode := RandomString(6) styledCode := codeStyle.Render(randomcode) fileName := filepath.Base(path) msg := "\n\n WARNING: The output file '" + fileName + "' already exists. \n" + " By proceeding, you may destroy an existing encrypted file or key.\n\n" + " Type " + styledCode + " in the prompt below. \n\n" content := msg fmt.Println(boxStyle.Render(content)) fmt.Print(" Type the code shown above and press ENTER > ") var userInput string fmt.Scanln(&userInput) ClearTerminal() if strings.TrimSpace(userInput) != randomcode { fmt.Printf("\n%s\n", errorStyle.Render(" INVALID CODE. Aborting operation.\n")) os.Exit(1) } //return strings.TrimSpace(userInput) == randomcode //reader := bufio.NewReader(os.Stdin) //input, err := reader.ReadString('\n') //if err != nil { // fmt.Println("\n ERROR: failed to read input") // os.Exit(1) //} //answer := strings.TrimSpace(input) //if answer != randomcode { // fmt.Printf(" ABORTED: Your response did not match the generated code. Action aborted.\n") // os.Exit(1) //} //fmt.Println(msg) //reader := bufio.NewReader(os.Stdin) //input, err := reader.ReadString('\n') //if err != nil { // fmt.Println("\n ERROR: failed to read input") // os.Exit(1) //} //fmt.Println("\t") //answer := strings.TrimSpace(input) //if answer != randomcode { // fmt.Printf(" ABORTED: Your response didnt match the generated code. Action aborted.\n") // os.Exit(1) //} } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ConfirmKeyOverwrite(path string) { _, err := os.Stat(path) if os.IsNotExist(err) { return } bannerStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#000000")). Background(lipgloss.Color("#790000")). Padding(0, 2) boxStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#d32020")). Padding(1, 4). Margin(1, 0) codeStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#ffffff")). Underline(false) errorStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("#FF0000")) fmt.Println("\n" + bannerStyle.Render(" ACTION REQUIRED ")) randomcode := RandomString(3) styledCode := codeStyle.Render(randomcode) fileName := codeStyle.Render(filepath.Base(path)) msg := "\n\n DANGER: THE KEY FILE " + fileName + " ALREADY EXISTS.\n\n" + " BY PROCEEDING, YOU WILL DESTROY YOUR EXISTING KEY PAIRS LINKED TO THIS KEY.\n\n" + " PLEASE BACK UP YOUR KEYS BEFORE PROCEEDING.\n\n" + " OVERWRITTEN KEYS CANNOT BE RECOVERED. Press Ctrl + C to abort. \n\n" + " type " + styledCode + " in the prompt below to proceed. \n\n" content := msg fmt.Println(boxStyle.Render(content)) fmt.Print(" Type the code shown above and press ENTER > ") var userInput string fmt.Scanln(&userInput) ClearTerminal() if strings.TrimSpace(userInput) != randomcode { fmt.Printf("\n%s\n", errorStyle.Render(" INVALID CODE. Aborting operation.\n")) os.Exit(1) } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ClearTerminal() { var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("cls") } else { cmd = exec.Command("clear") } cmd.Stdout = os.Stdout cmd.Run() } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func EmptyVariable(input string, pointer string) error { if input == "" { return fmt.Errorf(" ERROR: syscrypt: variable '%s' is empty.\n", pointer) } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsEmpty(input string) bool { return strings.TrimSpace(input) == "" } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateHeader(path string, expectedHeader string) error { if strings.TrimSpace(path) == "" { return fmt.Errorf(" ERROR: syscrypt: ValidateHeader: path is empty.") } if strings.TrimSpace(expectedHeader) == "" { return fmt.Errorf(" ERROR: syscrypt: ValidateHeader: expectedHeader is empty.") } file, err := os.Open(path) if err != nil { return fmt.Errorf(" ERROR: syscrypt: ValidateHeader: failed to open '%s' '%w'", path, err) } defer file.Close() scanner := bufio.NewScanner(file) if !scanner.Scan() { if err := scanner.Err(); err != nil { return fmt.Errorf(" ERROR: syscrypt: ValidateHeader: '%s' could not be read. %w", path, err) } return fmt.Errorf(" ERROR: syscrypt: ValidateHeader: '%s' is empty or could not be read.", path) } actualHeader := scanner.Text() if strings.TrimSpace(actualHeader) != strings.TrimSpace(expectedHeader) { return fmt.Errorf(" ERROR: syscrypt: ValidateHEader: Header mismatch in '%s'. Expected '%s', got '%s'", path, expectedHeader, actualHeader) } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsFlagPassed(name string) (bool, bool) { //for _, arg := range os.Args { // if arg == "-"+name || arg == "--"+name { // return true // } //} //return false for i, arg := range os.Args { if arg == "-"+name || arg == "--"+name { found := true hasValue := i+1 < len(os.Args) && os.Args[i+1] != "" && os.Args[i+1][0] != '-' return found, hasValue } } return false, false } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PreFlight() { /* // Check Config for empty variables EmptyVariable(config.KeyPath, "KeyPath") EmptyVariable(config.KeyPublicPath, "KeyPublicPath") EmptyVariable(config.ExpectedSerial, "ExpectedSerial") EmptyVariable(config.EncryptKeyPath, "EncryptKeyPath") EmptyVariable(config.EncryptPublicKeyPath, "EncryptPublicKeyPath") if config.Bootstrap == "0" { EmptyVariable(config.ExpectedEncryptSerial, "ExpectedEncryptSerial") } EmptyVariable(config.MasterPass, "MasterPass") EmptyVariable(config.Dictionary, "Dictionary") // Check if files in config exist FileExists(config.KeyPath) FileExists(config.KeyPublicPath) FileExists(config.EncryptKeyPath) FileExists(config.EncryptPublicKeyPath) FileExists(config.MasterPass) FileExists(config.Dictionary) // Check file permissions in config CheckPermission(config.KeyPath, 0600) CheckPermission(config.KeyPublicPath, 0644) CheckPermission(config.EncryptKeyPath, 0600) CheckPermission(config.EncryptPublicKeyPath, 0644) CheckPermission(config.MasterPass, 0600) CheckPermission(config.Dictionary, 0600) // Validate file headers ValidateHeader(config.KeyPath, "SYSCRYPT-KEY-") ValidateHeader(config.KeyPublicPath, "syscrypt") ValidateHeader(config.EncryptKeyPath, "-----BEGIN ENCRYPTED DATA-----") ValidateHeader(config.EncryptPublicKeyPath, "-----BEGIN ENCRYPTED DATA-----") ValidateHeader(config.MasterPass, "-----BEGIN ENCRYPTED DATA-----") ValidateHeader(config.Dictionary, "-----BEGIN ENCRYPTED DATA-----") */ } //// ENCRYPT FUNCTIONS ///////////////////////////////////////////////////////////////////////////////////////// // msg := " ERROR: syscrypt: '-A' API Key flag is only used during decryption.\n" //utils.HandleFailure(msg, nil) ///// DECRYPT FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func AuthorizeOrExit(challengeFlag string, inputPath string) { _disabled := "" _ = _disabled // Validate Master Keys //_storedMasterSerial, err := FetchFileKey(syscryptcfg.KeyPath, "serial") ///if err != nil { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Failed to fetch Master Private Key file.\n") // os.Exit(1) //} //if _storedMasterSerial != syscryptcfg.ExpectedSerial { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: The stored 'Master Primary Key Serial Number' does not match the bound 'Master Primary Key Serial Number'. '%s'.\n", _storedMasterSerial) // os.Exit(1) //} // Validate Encryption Keys //if syscryptcfg.Bootstrap == "0" { // _storedEncryptSerial, err := FetchFileKey(syscryptcfg.EncryptKeyPath, "serial") // if err != nil { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Failed to fetch Encryption Private key file.\n") // } // if _storedEncryptSerial != syscryptcfg.ExpectedEncryptSerial { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: The stored 'Encryption Primary Key Serial Number' does not match the bound 'Encryption Primary Key Serial Number'. '%s'.\n", _storedEncryptSerial) // os.Exit(1) // } //} // Decrypt Master Password //if syscryptcfg.Bootstrap == "0" { // _masterPass, err := DecryptFile(syscryptcfg.MasterPass, "") // if err != nil { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Failed to decrypt Master password.\n") // os.Exit(1) // } // if challengeFlag != _masterPass { // RecordFailure() // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Invalid Master Password.\n") // os.Exit(1) // } //} ///////////////////////////////////////////////////// //if _storedMasterKey != syscryptcfg.ExpectedPrivateKey { // fmt.Fprintf(os.Stderr, " ERROR: The Stored Master Private Key does not match the bound Master Private Key '%s' '%s'.\n", syscryptcfg.ExpectedPrivateKey, _storedMasterKey) // os.Exit(1) //} //if _storedMasterPublicKey != syscryptcfg.ExpectedPublicKey { // fmt.Fprintf(os.Stderr, " ERROR: The Stored Master Public Key does not match the bound Master Public Key '%s' '%s'.\n", syscryptcfg.ExpectedPublicKey, _storedMasterPublicKey) // os.Exit(1) //} // Validate Encryption Keys //if syscryptcfg.Bootstrap == "0" { // _storedEncryptKey, err := FetchFileKey(syscryptcfg.EncryptKeyPath) // if err != nil { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Failed to fetch Encryption Private Key file.\n") // os.Exit(1) // } // if _storedEncryptKey != syscryptcfg.ExpectedEncryptPrivateKey { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: The stored Encryption Private key does not match the bound Encryption Private Key.\n") // os.Exit(1) // } //} // Decrypt Master Password //_masterPass, err := DecryptFile(syscryptcfg.MasterPass, "x") //if err != nil { // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Failed to decrypt Master Password.\n") //} //_ = _masterPass //if syscryptcfg.Bootstrap == "0" { // // if challengeFlag != _masterPass { // RecordFailure() // fmt.Fprintf(os.Stderr, " ERROR: syscrypt: Invalid Master Password.\n") // os.Exit(1) // } //} } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateJSON(path string) (bool, error) { data, err := os.ReadFile(path) if err != nil { return false, fmt.Errorf("failed to read file: %w", err) } var js map[string]any if err := json.Unmarshal(data, &js); err != nil { var jsArray []any if err := json.Unmarshal(data, &jsArray); err != nil { return false, fmt.Errorf("invalid JSON format: %w", err) } } return true, nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidateSerial(blob []byte, keySerial string) bool { if len(blob) < 35 { return false } serialSize := int(blob[1]) if len(blob) < 2+serialSize+32 { return false } maskedSerial := blob[2 : 2+serialSize] ephPubX := blob[2+serialSize : 2+serialSize+32] recoveredBytes := make([]byte, serialSize) for i := 0; i < serialSize; i++ { recoveredBytes[i] = maskedSerial[i] ^ ephPubX[i%32] } recoveredSerial := string(recoveredBytes) if recoveredSerial != keySerial { return false } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func Zeroize(data []byte) { for i := range data { data[i] = 0 } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsFileLocked(filePath string) (isLocked bool, isArmored bool, err error) { file, err := os.Open(filePath) if err != nil { return false, false, err } defer file.Close() buffer := make([]byte, 512) n, err := file.Read(buffer) if err != nil && n == 0 { return false, false, err } preview := string(buffer[:n]) nPreview := strings.ReplaceAll(preview, vars.PrivateKeyHeader+"\n", "") nPreview = strings.ReplaceAll(nPreview, vars.PrivateKeyFooter+"\n", "") isBase64 := IsValidBase64WithLines(preview) if strings.HasPrefix(preview, vars.PrivateKeyHeader) { isArmored = true } else { isArmored = false } var content string if isBase64 { b, err := base64.StdEncoding.DecodeString(nPreview) if err != nil { return false, false, err } content = string(b) } else { content = preview } //fmt.Printf("content: %s\n", content) //if !(strings.HasPrefix(content, vars.SyscryptLabel) || strings.HasPrefix(content, vars.LockedLabel)) { // return false, false, err //} lines := strings.Split(content, "\n") contentFirstLine := strings.TrimSpace(lines[0]) if strings.HasPrefix(contentFirstLine, vars.LockedLabel) { isLocked = true } else if strings.HasPrefix(contentFirstLine, vars.SyscryptLabel) { isLocked = false } else { return false, isArmored, fmt.Errorf("invalid header") } //fmt.Printf("islocked: %v\n", isLocked) //fmt.Printf("prefix default: %v\n\n", strings.HasPrefix(contentFirstLine, vars.SyscryptLabel)) //fmt.Printf("prefix locked: %v\n\n", strings.HasPrefix(contentFirstLine, vars.LockedLabel)) //fmt.Printf("first line: %v\n\n", contentFirstLine) //fmt.Printf("\n------\n") //fmt.Printf("file path: %s\n\n", filePath) //fmt.Printf("content: \n%s\n\n", content) //fmt.Printf("content first line: \n%s\n\n", contentFirstLine) //fmt.Printf("\n\n\n") //fmt.Printf("islocked: %v\n", isLocked) return isLocked, isArmored, err //if !strings.HasPrefix(contentFirstLine, vars.SyscryptLabel) { // isLocked = false //} else { // isLocked = true //} //if !strings.HasPrefix(contentFirstLine, vars.LockedLabel) { // isLocked = false //} else { // isLocked = true //} //return isLocked, isArmored, err } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetFlagValue(flagPrefix string) (string, error) { flagPrefix = "-" + flagPrefix if flagPrefix == "" { return "", fmt.Errorf("GetFlagValue: flag prefix cannot be empty") } if !strings.HasPrefix(flagPrefix, "-") { return "", fmt.Errorf("GetFlagValue: '%s' is an invalid flag", flagPrefix) } if len(flagPrefix) < 2 || !unicode.IsLetter(rune(flagPrefix[1])) { return "", fmt.Errorf("GetFlagValue: '%s' must be followed by a letter", flagPrefix) } for i, arg := range os.Args { if arg == flagPrefix { if i+1 < len(os.Args) { return os.Args[i+1], nil } return "", nil } } return "", nil //for i, arg := range os.Args { // for arg == flagPrefix && i+1 < len(os.Args) { // return os.Args[i+1] // } //} //return "" } func IsValidBase64String(s string) bool { var base64Regex = regexp.MustCompile(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$`) return base64Regex.MatchString(s) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func IsValidBase64WithLines(s string) bool { lines := strings.Split(strings.TrimSpace(s), "\n") validBase64 := false for _, line := range lines { // line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "---") || strings.HasPrefix(line, "->") { continue } if _, err := base64.StdEncoding.DecodeString(line); err != nil { return false } validBase64 = true // } return validBase64 } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func ValidatePassword(s string, priv syscrypt.PrivateKeyWrapper) bool { rawInput, err := os.ReadFile(s) if err != nil { return false } var blob []byte inputStr := string(rawInput) if strings.Contains(inputStr, vars.PrivateKeyHeader) { content := strings.ReplaceAll(inputStr, vars.PrivateKeyHeader, "") content = strings.ReplaceAll(content, vars.PrivateKeyFooter, "") content = strings.ReplaceAll(strings.ReplaceAll(content, "\n", ""), "\r", "") blob, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(content)) } else { blob = rawInput } mode := blob[0] serialSize := int(blob[1]) ephOffset := 2 + serialSize headerOffset := ephOffset + 32 maskedSerial := blob[2:ephOffset] ephPubX := blob[ephOffset:headerOffset] recoveredBytes := make([]byte, serialSize) for i := 0; i < serialSize; i++ { recoveredBytes[i] = maskedSerial[i] ^ ephPubX[i%32] } if string(recoveredBytes) != priv.PrivateKey.Serial { fmt.Printf("Validation failed: This file was not encrypted with the supplied key %s.", maskedSerial) os.Exit(1) } isHybrid := mode == 0x03 || mode == 0x04 isLocked := mode == 0x02 || mode == 0x04 var nonce, ciphertext []byte if isHybrid { nonce = blob[headerOffset+1088 : headerOffset+1100] ciphertext = blob[headerOffset+1100:] } else { nonce = blob[headerOffset : headerOffset+12] ciphertext = blob[headerOffset+12:] } cleanPriv := strings.TrimPrefix(priv.PrivateKey.Key, "SYSCRYPT-PRIVATE-KEY-") myPrivX, _ := hex.DecodeString(cleanPriv) defer Zeroize(myPrivX) sharedX, _ := curve25519.X25519(myPrivX, ephPubX) defer Zeroize(sharedX) var sharedML []byte if isHybrid { scheme := kyber768.Scheme() skBytes, _ := hex.DecodeString(priv.PrivateKey.MLKEMKey) skK, _ := scheme.UnmarshalBinaryPrivateKey(skBytes) sharedML, _ = scheme.Decapsulate(skK, blob[headerOffset:headerOffset+1088]) Zeroize(skBytes) defer Zeroize(sharedML) } combined := append(sharedX, sharedML...) if isLocked { apiValue, _ := GetFlagValue("A") passValue, _ := GetFlagValue("P") apiKey, _ := FetchFileKey(apiValue, "key") if passValue == "" { fmt.Printf("validation failed: file is password protected (-P required).") return false } passKey := argon2.IDKey([]byte(passValue), []byte(apiKey), 1, 64*1024, 4, 32) combined = append(combined, passKey...) Zeroize(passKey) } defer Zeroize(combined) h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid")) symmKey := make([]byte, 32) io.ReadFull(h, symmKey) defer Zeroize(symmKey) aead, _ := cc20.New(symmKey) _, err = aead.Open(nil, nonce, ciphertext, nil) if err != nil { return false // wrong password } return true } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func Base64Decode(s string) (string, error) { data, err := base64.StdEncoding.DecodeString(s) if err != nil { return "", err } return string(data), err } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetFileMode(inputValue string) byte { rawInput, err := os.ReadFile(inputValue) if err != nil { return 0 } var workingBlob []byte if len(rawInput) > 0 && rawInput[0] == 45 { // content := string(rawInput) content = strings.ReplaceAll(content, vars.PrivateKeyHeader, "") content = strings.ReplaceAll(content, vars.PrivateKeyFooter, "") content = strings.ReplaceAll(content, "\n", "") content = strings.ReplaceAll(content, "\r", "") decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(content)) if err != nil { return 0 } workingBlob = decoded } else { workingBlob = rawInput } if len(workingBlob) >= 3 { maskedMode := workingBlob[0] ephKeyByte0 := workingBlob[2] return maskedMode ^ ephKeyByte0 } //if len(workingBlob) > 0 { // return workingBlob[0] //} return 0 } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PromptPassword() string { var styleBox = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("#7158e2")). Padding(1, 4). MarginTop(1). Width(60). Align(lipgloss.Center) var styleTitle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#32ff7e")). Bold(true) var styleBody = lipgloss.NewStyle(). Foreground(lipgloss.Color("#ffffff")) //var styleSubtext = lipgloss.NewStyle(). // Foreground(lipgloss.Color("#808e9b")). // Italic(true) title := styleTitle.Render(" PASSWORD REQUIRED ") body := styleBody.Render("Please enter your Master Password below: ") promptMsg := fmt.Sprintf("%s\n\n%s\n", title, body) fmt.Println(styleBox.Render(promptMsg)) bytePassword, err := term.ReadPassword(uintptr(os.Stdin.Fd())) fmt.Println(" > ") if err != nil { fmt.Printf("Error accessing terminal: %v\n", err) os.Exit(1) } password := string(bytePassword) if len(password) == 0 { errorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#ff4d4d")).Bold(true) fmt.Println(errorStyle.Render("Access Denied: Password cannot be empty.")) os.Exit(1) } return password } func ReadJson(path string, filePath string) (string, error) { cmd := exec.Command("jq", "-r", path, filePath) var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { return "", fmt.Errorf("jq error: %v - %s", err, stderr.String()) } result := bytes.TrimSpace(out.Bytes()) if string(result) == "null" { return "", fmt.Errorf("jq - key not found: %s", path) } return string(result), nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //func DecryptFile(path string, password string) (string, error) { // binaryPath := config.BinaryPath + "/syscrypt" // ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // defer cancel() // cmd := exec.CommandContext(ctx, binaryPath, "-d", "-i", config.KeyPath, "-k", password, path) // cmd.Env = append(os.Environ(), "SYSCRYPT_INTERNAL_CALL=1") // out, err := cmd.CombinedOutput() // if ctx.Err() == context.DeadlineExceeded { // return "", fmt.Errorf(" ERROR: syscrypt: DecryptFile: timed out: %s", string(out)) // } // // if err != nil { // return "", fmt.Errorf(" ERROR: syscrypt: DecryptFile: decrypt failed: %w %s", err, string(out)) // } // return strings.TrimSpace(string(out)), nil //} //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////