2392 lines
60 KiB
Go
Executable File
2392 lines
60 KiB
Go
Executable File
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
|
|
//}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|