Files
syscrypt/internal/utils/utils.go

2392 lines
60 KiB
Go
Raw Permalink Normal View History

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
//}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////