Initial: fixing internal package metadata

This commit is contained in:
root
2026-04-02 14:24:29 +01:00
commit d8773e7349
12 changed files with 4835 additions and 0 deletions

97
cmd/syscrypt/main.go Executable file
View File

@@ -0,0 +1,97 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"sources.truenas.cloud/code/syscrypt/internal/actions/decrypt"
"sources.truenas.cloud/code/syscrypt/internal/actions/encrypt"
"sources.truenas.cloud/code/syscrypt/internal/actions/keygen"
"sources.truenas.cloud/code/syscrypt/internal/config"
"sources.truenas.cloud/code/syscrypt/internal/utils"
"sources.truenas.cloud/code/syscrypt/internal/vars"
)
func main() {
fs := flag.NewFlagSet("syscrypt", flag.ContinueOnError)
fs.Usage = func() {}
fs.SetOutput(io.Discard)
fs.BoolVar(&vars.VersionFlag, "v", false, "prints the version")
fs.BoolVar(&vars.VersionFlag, "version", false, "prints the version")
fs.StringVar(&vars.CommandFlag, "c", "", "command flag")
fs.BoolVar(&vars.ArmoredFlag, "a", false, "generates an armored encrypted file")
fs.StringVar(&vars.KeyFlag, "k", "", "public or private key file")
fs.StringVar(&vars.InputFlag, "i", "", "input file")
fs.StringVar(&vars.OutputFlag, "o", "", "output file")
fs.BoolVar(&vars.LockFlag, "L", false, "lock the encrypted file to API Key and Master Password")
fs.StringVar(&vars.ApiFlag, "A", "", "API key for your locked encrypted file")
fs.StringVar(&vars.MasterPass, "P", "", "Master password value")
fs.StringVar(&vars.Comment, "C", "", "Commend for API Key or Public/Private Keys")
fs.StringVar(&vars.StringFlag, "s", "", "String instead of a file")
fs.BoolVar(&vars.PQFlag, "PQ", false, "sets the keys to post-quantum mode")
fs.StringVar(&vars.KeyPath, "K", "", "key path (optional)")
fs.StringVar(&vars.FriendlyName, "f", "", "Friendly Name")
_ = fs.Parse(os.Args[1:])
if vars.VersionFlag {
fmt.Printf("%s version: %s\nBuild Date: %s\n", config.BuildProject, config.BuildVersion, config.BuildDate)
os.Exit(1)
}
if err := utils.CheckFirstFlag(); err != nil {
msg := fmt.Sprintf("%s: %s", vars.CommandFlag, err)
utils.HandleFailure(msg)
os.Exit(1)
}
firstFlag := vars.CommandFlag
if firstFlag != "firstrun" {
_, err := utils.CheckExtraFlags()
if err != nil {
msg := fmt.Sprintf("%s: %s", vars.CommandFlag, err)
utils.HandleFailure(msg)
os.Exit(1)
}
}
switch vars.CommandFlag {
case "firstrun":
keygen.FirstRun()
case "apikey":
case "dictionary":
case "encrypt":
encrypt.Validate()
case "decrypt":
decrypt.Validate()
case "keygen":
defaultKey := config.KeyFolder + "/" + config.MasterKey
defaultKeyExists := utils.FileExists(defaultKey)
fmt.Printf("default key: %s\n", defaultKey)
os.Exit(1)
if !defaultKeyExists {
msg := fmt.Sprintf("%s: No default keys found. \nPlease run 'syscrypt -c firstrun -PQ' before running any other command.\n"+
"This will generate your default keys.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
} else {
keygen.Validate()
}
default:
fmt.Printf("\nUnrecognized command '%s' \n\n", vars.CommandFlag)
fmt.Printf("Allowed commands: firstrun, encrypt, decrypt, encode, decode, keygen, apikeygen, reset, resetpassword\n\n")
os.Exit(1)
}
}

25
go.mod Normal file
View File

@@ -0,0 +1,25 @@
module sources.truenas.cloud/code/syscrypt
go 1.25.5
require github.com/charmbracelet/lipgloss v1.1.0
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.2
github.com/cloudflare/circl v1.6.3
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.49.0
golang.org/x/sys v0.42.0 // indirect
)
replace sources.truenas.cloud/code/syscrypt => ./

34
go.sum Normal file
View File

@@ -0,0 +1,34 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

View File

@@ -0,0 +1 @@
package apikey

View File

@@ -0,0 +1,600 @@
package decrypt
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"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/utils"
"sources.truenas.cloud/code/syscrypt/internal/vars"
)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func Validate() {
utils.ClearTerminal()
decryptAllowedFlags := map[string]struct{}{
"-C": {},
"-k": {},
"-i": {},
"-c": {},
"-o": {},
"-A": {},
"-P": {},
}
utils.ValidateAllowedFlags(decryptAllowedFlags)
decryptRequiredFlags := map[string]bool{
"-k": true,
"-i": true,
}
_ = decryptRequiredFlags
//utils.ValidateRequiredFlags(decryptRequiredFlags, "decrypt")
// -- Keys
isKeySet, keyHasValue := utils.IsFlagPassed("k")
keyValue, _ := utils.GetFlagValue("k")
keyIsValidPath := utils.IsValidPath(keyValue)
keyExists := utils.FileExists(keyValue)
keyIsValidJson, _ := utils.ValidateJSON(keyValue)
if isKeySet && !keyHasValue {
msg := fmt.Sprintf("%s: -k KEY: requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isKeySet && !keyIsValidPath {
msg := fmt.Sprintf("%s: -k KEY: requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isKeySet && !keyExists {
msg := fmt.Sprintf("%s: -k KEY: key file does not exist.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if !keyIsValidJson {
msg := fmt.Sprintf("%s: -k KEY: Invalid JSON format.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
var privateKeyPrefix string
privateKey, _ := utils.FetchFileKey(keyValue, "key")
privateKeyParts := strings.Split(privateKey, "--")
if len(privateKeyParts) != 2 {
msg := fmt.Sprintf("%s: -k KEY: Mismatch: Invalid Prefix '%s'. Invalid Length.\n", vars.CommandFlag, privateKeyParts[0]+vars.EndAnchor)
utils.HandleFailure(msg)
os.Exit(1)
}
privateKeyStart := privateKeyParts[0]
privateKeyPrefix = strings.ReplaceAll(vars.PrivateKeyPrefixLabel, "--", "")
var match bool
if privateKeyStart == privateKeyPrefix {
match = true
} else {
match = false
}
if !match {
msg := fmt.Sprintf("%s: -k KEY: Mismatch: Invalid Prefix '%s'\n", vars.CommandFlag, privateKeyStart+vars.EndAnchor)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Input
isInputSet, inputHasValue := utils.IsFlagPassed("i")
inputValue, _ := utils.GetFlagValue("i")
inputIsValidPath := utils.IsValidPath(inputValue)
inputExists := utils.FileExists(inputValue)
if isInputSet && !inputHasValue {
msg := fmt.Sprintf("%s: -i INPUT: Requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isInputSet && !inputIsValidPath {
msg := fmt.Sprintf("%s: -i INPUT: Requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if !inputExists {
msg := fmt.Sprintf("%s: -i INPUT: File does not exist.\n%s", vars.CommandFlag, inputValue)
utils.HandleFailure(msg)
os.Exit(1)
}
inputFileMode := utils.GetFileMode(inputValue)
if inputFileMode == 0 {
msg := fmt.Sprintf("%s: -i INPUT: File does not appear to be a valid encrypted file.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
var apiValue string
var apiIsValidPath bool
var apiExists bool
var apiIsValidJson bool
var password string
isAPISet, apiHasValue := utils.IsFlagPassed("A")
if isAPISet {
apiValue, _ = utils.GetFlagValue("A")
apiIsValidPath = utils.IsValidPath(apiValue)
apiExists = utils.FileExists(apiValue)
apiIsValidJson, _ = utils.ValidateJSON(apiValue)
}
_ = apiHasValue
_ = apiExists
_ = apiIsValidJson
var lockValidationRequired bool
switch inputFileMode {
case 1: // classic - do nothing
lockValidationRequired = false
case 2: // classic locked - apikey and password required
lockValidationRequired = true
case 3: // hybrid - do nothing
lockValidationRequired = false
case 4: // hybrid locked - apikey and password required
lockValidationRequired = true
default:
lockValidationRequired = false
msg := fmt.Sprintf("%s: -i INPUT: Unable to determine encryption mode. Invalid File.\n%s\n", vars.CommandFlag, inputValue)
utils.HandleFailure(msg)
os.Exit(1)
}
if isAPISet && lockValidationRequired {
//
if !isAPISet {
msg := fmt.Sprintf("%s: LOCKED FILE: -A APIKEY: flag is required.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isAPISet && !apiHasValue {
msg := fmt.Sprintf("%s: LOCKED FILE: -A APIKEY: requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isAPISet && !apiIsValidPath {
msg := fmt.Sprintf("%s: LOCKED FILE: -A APIKEY: requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
password := utils.PromptPassword()
_ = password
//
}
_ = apiIsValidPath
_ = password
// -- Output
isOutputSet, outputHasValue := utils.IsFlagPassed("o")
outputValue, _ := utils.GetFlagValue("o")
outputIsValidPath := utils.IsValidPath(outputValue)
outputExists := utils.FileExists(outputValue)
if isOutputSet && !outputHasValue {
msg := fmt.Sprintf("%s: -o OUTPUT: requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isOutputSet && !outputIsValidPath {
msg := fmt.Sprintf("%s: -o OUTPUT: requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
// confirm overwrite.....
if isOutputSet && outputExists {
utils.ConfirmOverwrite(outputValue)
}
// Get Private Key
privateKey, err := utils.FetchFileKey(keyValue, "key")
if err != nil {
msg := fmt.Sprintf("%s: -k KEY: Error: %s", vars.CommandFlag, err)
utils.HandleFailure(msg)
os.Exit(1)
}
keyBytes, err := os.ReadFile(keyValue)
if err != nil {
msg := fmt.Sprintf("%s: -k KEY: Could not read key file.\n%s\n", vars.CommandFlag, err)
utils.HandleFailure(msg)
os.Exit(1)
}
var pKey syscrypt.PrivateKeyWrapper
err = json.Unmarshal(keyBytes, &pKey)
if err != nil {
msg := fmt.Sprintf("%s: -k KEY: File is not a valid syscrypt key file. %v\n", vars.CommandFlag, err)
utils.HandleFailure(msg)
os.Exit(1)
}
DecryptFile(outputValue, inputValue, pKey)
_ = err
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func validateFile(inputValue string) (encryptionType bool, isLocked bool, isArmored bool) {
//switch mode {
//case 0x01:
// fmt.Printf("classic")
//case 0x02:
// fmt.Printf("classic with lock")
//case 0x03:
// fmt.Printf("post quantum")
//case 0x04:
// fmt.Printf("post quantum with lock")
//default:
// fmt.Printf("invalid")
//}
return false, false, false
//file, err := os.Open(inputValue)
//if err != nil {
// fmt.Printf("Error: cannot open %s\n", inputValue)
// os.Exit(1)
//}
//defer file.Close()
//header := make([]byte, 1)
//_, err = file.Read(header)
//if err != nil {
// fmt.Println("Error: file is empty or unreadable")
// os.Exit(1)
//}
//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
//}
//mode := header[0]
//switch mode {
//case 0x01:
// return 1 // Classical Encryption
//case 0x02:
// return 2 // Classical Encryption [with lock]
//case 0x03:
// return 3 // Post Quantum Encryption
//case 0x04:
// return 4 // Post Quantum Encryption [with lock]
//default:
// return 5 // Invalid or Armored
//}
}
func DecryptFile(out string, input string, priv syscrypt.PrivateKeyWrapper) {
outputValue, _ := utils.GetFlagValue("o")
inputValue, _ := utils.GetFlagValue("i")
isOutputSet, outputHasValue := utils.IsFlagPassed("o")
isOutputValidPath := utils.IsValidPath(outputValue)
outputExists := utils.FileExists(outputValue)
passValue, _ := utils.GetFlagValue("P")
apiValue, _ := utils.GetFlagValue("A")
raw, _ := os.ReadFile(input)
var blob []byte
if strings.Contains(string(raw), vars.PrivateKeyHeader) {
c := strings.ReplaceAll(string(raw), vars.PrivateKeyHeader, "")
c = strings.ReplaceAll(c, vars.PrivateKeyFooter, "")
c = strings.ReplaceAll(c, "\n", "")
c = strings.ReplaceAll(c, "\r", "")
blob, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(c))
} else {
blob = raw
}
// unblind header
ephPubX := blob[2:34]
mode := blob[0] ^ ephPubX[0]
serialSize := int(blob[1] ^ ephPubX[1])
serialOffset := 34
dataPtr := serialOffset + serialSize
recoveredSerial := make([]byte, serialSize)
for i := 0; i < serialSize; i++ {
recoveredSerial[i] = blob[serialOffset+i] ^ ephPubX[(i+2)%32]
}
if priv.PrivateKey.Serial != string(recoveredSerial) {
fmt.Printf("Access Denied: Serial Mismatch or invalid key type.\n")
os.Exit(1)
}
isHybrid := (mode == 0x03 || mode == 0x04)
isLocked := (mode == 0x02 || mode == 0x04)
// Password Verification for Locked files
var passKey []byte
if isLocked {
//
apiKey, err := utils.FetchFileKey(apiValue, "key")
if err != nil {
fmt.Printf("Error: %s", err)
os.Exit(1)
}
passKey = argon2.IDKey([]byte(passValue), []byte(apiKey), 1, 64*1024, 4, 32)
vH := hkdf.New(sha256.New, passKey, nil, []byte("syscrypt-pass-verify"))
vK := make([]byte, 32)
io.ReadFull(vH, vK)
vAead, _ := cc20.New(vK)
res, err := vAead.Open(nil, make([]byte, 12), blob[dataPtr:dataPtr+vars.PassTagSize], nil)
if err != nil || !bytes.Equal(res, []byte("SYSC-PASS-OK")) {
fmt.Printf("Invalid Master Password.")
os.Exit(1)
}
dataPtr += vars.PassTagSize
//
}
// Slice Hybrid & Data
var kyberCT []byte
if isHybrid {
kyberCT = blob[dataPtr : dataPtr+vars.KyberCTSize]
dataPtr += vars.KyberCTSize
}
nonce := blob[dataPtr : dataPtr+12]
ciphertext := blob[dataPtr+12:]
// Final Key Reconstruction
cleanPrivX := strings.TrimPrefix(priv.PrivateKey.Key, vars.PrivateKeyPrefixLabel)
myPrivX, _ := hex.DecodeString(cleanPrivX)
sharedX, _ := curve25519.X25519(myPrivX, ephPubX)
var sharedML []byte
if isHybrid {
scheme := kyber768.Scheme()
cleanML := strings.TrimPrefix(priv.PrivateKey.MLKEMKey, vars.PQPrivateKeyPrefixLabel)
skBytes, _ := hex.DecodeString(cleanML)
skK, _ := scheme.UnmarshalBinaryPrivateKey(skBytes)
sharedML, _ = scheme.Decapsulate(skK, kyberCT)
}
combined := append(sharedX, sharedML...)
if isLocked {
combined = append(combined, passKey...)
}
h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid"))
symmK := make([]byte, 32)
io.ReadFull(h, symmK)
aead, _ := cc20.New(symmK)
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
fmt.Printf("Decryption failed")
os.Exit(1)
}
if isOutputSet {
os.WriteFile(outputValue, plaintext, 0644)
fmt.Printf("Successfully decrypted file to: %s\n", outputValue)
}
if !isOutputSet {
fmt.Printf("%s\n", plaintext)
}
_ = inputValue
_ = isOutputSet
_ = outputHasValue
_ = isOutputValidPath
_ = outputExists
_ = dataPtr
_ = kyberCT
_ = nonce
_ = ciphertext
_ = sharedX
//outputValue, _ := utils.GetFlagValue("o")
//inputValue, _ := utils.GetFlagValue("i")
//isOutputSet, outputHasValue := utils.IsFlagPassed("o")
//outputValue, _ := utils.GetFlagValue("k")
//outputIsValidPath := utils.IsValidPath(outValue)
//outputExists := utils.FileExists(outputValue)
/*
rawInput, err := os.ReadFile(inputValue)
if err != nil {
fmt.Printf("Error: Unable to read %s\n", inputValue)
os.Exit(1)
}
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(content, "\n", "")
content = strings.ReplaceAll(content, "\r", "")
content = strings.TrimSpace(content)
blob, err = base64.StdEncoding.DecodeString(content)
if err != nil {
fmt.Printf("Error: Malformed Armored Base64 data.\n")
os.Exit(1)
}
} else {
blob = rawInput
}
if len(blob) < 35 {
fmt.Printf("Error: -i INPUT: File is too small to be a valid syscrypt encrypted message.\n")
os.Exit(1)
}
mode := blob[0]
serialSize := int(blob[1])
ephOffset := 2 + serialSize
headerOffset := ephOffset + 32
if len(blob) < headerOffset {
fmt.Printf("Error: Header truncated or invalid serial length.\n")
}
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]
}
fileSerial := string(recoveredBytes)
if fileSerial != priv.PrivateKey.Serial {
fmt.Printf("Error: File requires key [%s], but you provided [%s]\n", fileSerial, priv.PrivateKey.Serial)
os.Exit(1)
}
var kyberCT, nonce, ciphertext []byte
isHybrid := mode == 0x03 || mode == 0x04
isLocked := mode == 0x02 || mode == 0x04
if isHybrid {
kyberCT = blob[headerOffset : headerOffset+1088]
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, vars.PrivateKeyPrefixLabel)
myPrivX, _ := hex.DecodeString(cleanPriv)
sharedX, _ := curve25519.X25519(myPrivX, ephPubX)
var sharedML []byte
if isHybrid {
scheme := kyber768.Scheme()
skBytes, _ := hex.DecodeString(priv.PrivateKey.MLKEMKey)
skK, _ := scheme.UnmarshalBinaryPrivateKey(skBytes)
sharedML, _ = scheme.Decapsulate(skK, kyberCT)
}
combined := append(sharedX, sharedML...)
if isLocked {
apiValue, _ := utils.GetFlagValue("A")
passValue, _ := utils.GetFlagValue("P")
passKey := argon2.IDKey([]byte(passValue), []byte(apiKey), 1, 64*1024, 4, 32)
combined = append(combined, passKey...)
defer utils.Zeroize(passKey)
}
h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid"))
symmKey := make([]byte, 32)
io.ReadFull(h, symmKey)
defer utils.Zeroize(symmKey)
aead, _ := cc20.New(symmKey)
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
fmt.Printf("Error: Decryption failed. Potentional data corruption or wrong password.")
os.Exit(1)
}
if isOutputSet && outputIsValidPath {
err = os.WriteFile(outputValue, plaintext, 0644)
if err != nil {
fmt.Printf("Error: Unable to write decrypted file %s\n", outputValue)
os.Exit(1)
}
} else {
/// do stuff that isnt output
}
*/
}

View File

@@ -0,0 +1,829 @@
package encrypt
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"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/utils"
"sources.truenas.cloud/code/syscrypt/internal/vars"
)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func Validate() {
encryptAllowedFlags := map[string]struct{}{
"-C": {},
"-L": {},
"-a": {},
"-k": {},
"-i": {},
"-c": {},
"-o": {},
"-A": {},
"-P": {},
"-PQ": {},
}
for _, arg := range os.Args {
if arg == "-L" {
encryptAllowedFlags["-A"] = struct{}{}
encryptAllowedFlags["-P"] = struct{}{}
break
}
}
utils.ValidateAllowedFlags(encryptAllowedFlags)
encryptRequiredFlags := map[string]bool{
"-k": true,
"-i": true,
"-o": true,
}
for _, arg := range os.Args {
if arg == "-L" {
encryptRequiredFlags["-A"] = true
encryptRequiredFlags["-P"] = true
break
}
}
//utils.ValidateRequiredFlags(encryptRequiredFlags, "encrypt")
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)
outputFileExists := utils.FileExists(outputValue)
isArmoredSet, armoredHasValue := utils.IsFlagPassed("a")
isLockSet, lockHasValue := utils.IsFlagPassed("L")
lockValue, _ := utils.GetFlagValue("L")
_ = lockValue
isAPISet, _ := utils.IsFlagPassed("A")
apiValue, _ := utils.GetFlagValue("A")
apiIsValidPath := utils.IsValidPath(apiValue)
apiFileExists := utils.FileExists(apiValue)
//isPassSet, _ := utils.IsFlagPassed("P")
//passValue, _ := utils.GetFlagValue("P")
//passIsValidPath := utils.IsValidPath(passValue)
///////////////////////////////////////////////////////////
// -- Key
if isKeySet && !keyHasValue {
msg := fmt.Sprintf("%s: -k KEY: Requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isKeySet && !keyIsValidPath {
msg := fmt.Sprintf("%s: -k KEY: Requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
keyExists := utils.FileExists(keyValue)
if !keyExists {
msg := fmt.Sprintf("%s: -k KEY: Key file does not exist.\n%s\n", vars.CommandFlag, keyValue)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Input
if isInputSet && !inputHasValue {
msg := fmt.Sprintf("%s: -i INPUT: Requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isInputSet && !inputIsValidPath {
msg := fmt.Sprintf("%s: -i INPUT: Requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
inputExists := utils.FileExists(inputValue)
if !inputExists {
msg := fmt.Sprintf("%s: -i INPUT: Input file does not exist \n%s\n", vars.CommandFlag, inputValue)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Output
if isOutputSet && !outputHasValue {
msg := fmt.Sprintf("%s: -o OUTPUT: Requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isOutputSet && !outputIsValidPath {
msg := fmt.Sprintf("%s: -o OUTPUT: Requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Armored
if isArmoredSet && armoredHasValue {
msg := fmt.Sprintf("%s: -a ARMORED: Armored does not support a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Lock
if isLockSet && lockHasValue {
msg := fmt.Sprintf("%s: -L LOCK: Lock does not support a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isLockSet && !isAPISet {
msg := fmt.Sprintf("%s: -L LOCK: Lock requires the -A APIKEY flag and value to be set.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
//if isLockSet && !isPassSet {
// msg := fmt.Sprintf("%s: -L LOCK: Lock requires the -P MASTER PASSWORD flag and value to be set.", vars.CommandFlag)
// utils.HandleFailure(msg)
// os.Exit(1)
//}
// -- API
if isLockSet && apiValue == "" {
msg := fmt.Sprintf("%s: -A APIKEY: Requires a value.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isLockSet && !apiIsValidPath {
msg := fmt.Sprintf("%s: -A APIKEY: Requires a valid file path.\n", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isLockSet && !apiFileExists {
msg := fmt.Sprintf("%s: -A APIKEY: Key file does not exist.\n%s\n", vars.CommandFlag, apiValue)
utils.HandleFailure(msg)
os.Exit(1)
}
// -- Password
//if isLockSet && passValue == "" {
// msg := fmt.Sprintf("%s: -P MASTER PASSWORD: Requires a value.\n", vars.CommandFlag)
// utils.HandleFailure(msg)
// os.Exit(1)
//}
if isLockSet {
//masterPass := config.MasterPass
//masterPassIsValidPath := utils.IsValidPath(masterPass)
//if masterPass == "" {
// msg := fmt.Sprintf("%s: Unable to determine location of the Master Password file.\nUnable to continue.\n"+
// "Please run syscrypt -c keygen again to generate the default keys.", vars.CommandFlag)
// utils.HandleFailure(msg)
// os.Exit(1)
//}
//if !masterPassIsValidPath {
// msg := fmt.Sprintf("%s: Invalid Master Password location.\n%s"+
// "Please run syscrypt -c keygen again to generate the default keys.", vars.CommandFlag, masterPass)
// utils.HandleFailure(msg)
// os.Exit(1)
//}
}
//if isLockSet && !passIsValidPath {
// msg := fmt.Sprintf("%s: -P MASTER PASSWORD: Requires a valid file path.\n", vars.CommandFlag)
// utils.HandleFailure(msg)
// os.Exit(1)
//}
if outputFileExists {
utils.ConfirmOverwrite(outputValue)
}
///////////////////////////////////////////////////////////
fmt.Printf("\n\n")
fmt.Printf("keyset: %v\n", isKeySet)
fmt.Printf("keyvalue: %v\n", keyValue)
fmt.Printf("keyvalidpath: %v\n", keyIsValidPath)
fmt.Printf("\n")
fmt.Printf("inputset: %v\n", isInputSet)
fmt.Printf("inputvalue: %v\n", inputValue)
fmt.Printf("inputvalidpath: %v\n", inputIsValidPath)
fmt.Printf("\n")
fmt.Printf("outputset: %v\n", isOutputSet)
fmt.Printf("outputvalue: %v\n", outputValue)
fmt.Printf("outputvalidpath: %v\n", outputIsValidPath)
fmt.Printf("\n")
// Open Files
keyBytes, err := os.ReadFile(keyValue)
if err != nil {
fmt.Printf("Error: Could not read key file at %s\n", keyValue)
}
var pub syscrypt.PublicKeyWrapper
err = json.Unmarshal(keyBytes, &pub)
if err != nil {
fmt.Printf("Error: key file is not a valid syscrypt JSON: %v\n", err)
os.Exit(1)
}
EncryptFile(outputValue, inputValue, pub)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func zeroize(data []byte) {
for i := range data {
data[i] = 0
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const PassTagSize = 28
// func EncryptFile(dst io.Writer, src io.Reader, pub syscrypt.PublicKeyWrapper) {
func EncryptFile(outputValue string, inputValue string, pub syscrypt.PublicKeyWrapper) {
// Get Values
isLockSet, _ := utils.IsFlagPassed("L")
isArmoredSet, _ := utils.IsFlagPassed("a")
keyValue, _ := utils.GetFlagValue("k")
passValue, _ := utils.GetFlagValue("P")
apiIsSet, _ := utils.IsFlagPassed("A")
apiValue, _ := utils.GetFlagValue("A")
serialKey, _ := utils.FetchFileKey(keyValue, "serial")
src, _ := os.Open(inputValue)
plaintext, _ := io.ReadAll(src)
src.Close()
// Keys
cleanPubX := strings.TrimPrefix(pub.PublicKey.Key, vars.DefaultPrefixLabel)
recipientPubX, _ := hex.DecodeString(cleanPubX)
ephPrivX := make([]byte, 32)
io.ReadFull(rand.Reader, ephPrivX)
ephPubX, _ := curve25519.X25519(ephPrivX, curve25519.Basepoint)
sharedX, _ := curve25519.X25519(ephPrivX, recipientPubX)
var kyberCT []byte
var sharedML []byte
isHybrid := pub.PublicKey.MLKEMKey != ""
if isHybrid {
scheme := kyber768.Scheme()
cleanML := strings.TrimPrefix(pub.PublicKey.MLKEMKey, vars.PQPublicKeyPrefixLabel)
pkBytes, err := hex.DecodeString(cleanML)
if err != nil {
fmt.Printf("Hex Decode Error: %v\n", err)
return
}
pkK, err := scheme.UnmarshalBinaryPublicKey(pkBytes)
if err != nil {
fmt.Printf("Error: kyber key unmarshal fail: %v\n", err)
return
}
kyberCT, sharedML, err = scheme.Encapsulate(pkK)
if err != nil {
fmt.Printf("Error: Kyber encapsulation failed: %v\n", err)
}
}
// Password Tag Generation
var passKey, passVerifyTag []byte
if isLockSet {
apiKey, _ := utils.FetchFileKey(apiValue, "key")
passKey = argon2.IDKey([]byte(passValue), []byte(apiKey), 1, 64*1024, 4, 32)
vH := hkdf.New(sha256.New, passKey, nil, []byte("syscrypt-pass-verify"))
vKey := make([]byte, 32)
io.ReadFull(vH, vKey)
vAead, _ := cc20.New(vKey)
vNonce := make([]byte, 12)
passVerifyTag = vAead.Seal(nil, vNonce, []byte("SYSC-PASS-OK"), nil)
}
// Final Symmetric Key
combined := append(sharedX, sharedML...)
if isLockSet {
combined = append(combined, passKey...)
}
h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid"))
symmKey := make([]byte, 32)
io.ReadFull(h, symmKey)
aead, _ := cc20.New(symmKey)
for i := range symmKey {
symmKey[i] = 0
}
nonce := make([]byte, 12)
io.ReadFull(rand.Reader, nonce)
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
// Blinded Header Assembly
serialBytes := []byte(serialKey)
serialSize := len(serialBytes)
var mode byte
if isHybrid && isLockSet {
mode = 0x04
} else if isHybrid {
mode = 0x03
} else if isLockSet {
mode = 0x02
} else {
mode = 0x01
}
var finalBlob []byte
finalBlob = append(finalBlob, mode^ephPubX[0])
finalBlob = append(finalBlob, byte(serialSize)^ephPubX[1])
finalBlob = append(finalBlob, ephPubX...)
for i := 0; i < serialSize; i++ {
finalBlob = append(finalBlob, serialBytes[i]^ephPubX[(i+2)%32])
}
if isLockSet {
finalBlob = append(finalBlob, passVerifyTag...)
}
if isHybrid {
finalBlob = append(finalBlob, kyberCT...)
}
finalBlob = append(finalBlob, nonce...)
finalBlob = append(finalBlob, ciphertext...)
dst, err := os.Create(outputValue)
if err != nil {
fmt.Printf("Error: Unable to create output file %s", outputValue)
}
defer dst.Close()
if isArmoredSet {
dst.WriteString(vars.PrivateKeyHeader + "\n")
enc := base64.StdEncoding.EncodeToString(finalBlob)
for i := 0; i < len(enc); i += 64 {
end := i + 64
if end > len(enc) {
end = len(enc)
}
dst.WriteString(enc[i:end] + "\n")
}
dst.WriteString(vars.PrivateKeyFooter + "\n")
} else {
dst.Write(finalBlob)
}
_ = apiIsSet
fmt.Printf("Success: file saved at: %s\n", outputValue)
/*
// 1. Setup Flags
isLockSet, _ := utils.IsFlagPassed("L")
isAPISet, _ := utils.IsFlagPassed("A")
isArmoredSet, _ := utils.IsFlagPassed("a")
keyValue, _ := utils.GetFlagValue("k")
apiValue, _ := utils.GetFlagValue("A")
passValue, _ := utils.GetFlagValue("P")
// 2. Read Plaintext
src, _ := os.Open(inputValue)
plaintext, _ := io.ReadAll(src)
src.Close()
// 3. Classical Key Exchange (X25519)
cleanPubX := strings.TrimPrefix(pub.PublicKey.Key, "syscrypt-")
recipientPubX, _ := hex.DecodeString(cleanPubX)
ephPrivX := make([]byte, 32)
io.ReadFull(rand.Reader, ephPrivX)
ephPubX, _ := curve25519.X25519(ephPrivX, curve25519.Basepoint)
sharedX, _ := curve25519.X25519(ephPrivX, recipientPubX)
// 4. Post-Quantum Key Exchange (Kyber768)
//var kyberCT, sharedML []byte
//isHybrid := pub.PublicKey.MLKEMKey != ""
var kyberCT []byte
var sharedML []byte
isHybrid := pub.PublicKey.MLKEMKey != ""
if isHybrid {
scheme := kyber768.Scheme()
cleanML := strings.TrimPrefix(pub.PublicKey.MLKEMKey, "syscrypt-pq-")
pkBytes, err := hex.DecodeString(cleanML)
if err != nil {
fmt.Printf("Hex Decode Error: %v\n", err)
return
}
pkK, err := scheme.UnmarshalBinaryPublicKey(pkBytes)
if err != nil {
fmt.Printf("Error: kyber key unmarshal failed: %v\n", err)
return
}
var encapErr error
kyberCT, sharedML, encapErr = scheme.Encapsulate(pkK)
if encapErr != nil {
fmt.Printf("Error: Kyber encapsulation failed: %v\n", encapErr)
return
}
}
// 5. Entropy Binding
combined := append(sharedX, sharedML...)
if isLockSet {
var apiKey string
if isAPISet {
apiKey, _ = utils.FetchFileKey(apiValue, "key")
}
passKey := argon2.IDKey([]byte(passValue), []byte(apiKey), 1, 64*1024, 4, 32)
combined = append(combined, passKey...)
}
// 6. Key Derivation & Encryption
h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid"))
symmetricKey := make([]byte, 32)
io.ReadFull(h, symmetricKey)
aead, _ := cc20.New(symmetricKey)
for i := range symmetricKey {
symmetricKey[i] = 0
}
nonce := make([]byte, aead.NonceSize())
io.ReadFull(rand.Reader, nonce)
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
// 7. Metadata/Serial Masking
serialKey, _ := utils.FetchFileKey(keyValue, "serial")
serialBytes := []byte(serialKey)
serialSize := len(serialBytes)
maskedSerial := make([]byte, serialSize)
for i := 0; i < serialSize; i++ {
maskedSerial[i] = serialBytes[i] ^ ephPubX[i%32]
}
// 8. Determine Mode
var mode byte
if isHybrid && isLockSet {
mode = 0x04
} else if isHybrid {
mode = 0x03
} else if isLockSet {
mode = 0x02
} else {
mode = 0x01
}
// 9. Final Assembly
var finalBlob []byte
finalBlob = append(finalBlob, mode)
finalBlob = append(finalBlob, byte(serialSize))
finalBlob = append(finalBlob, maskedSerial...)
finalBlob = append(finalBlob, ephPubX...)
if isHybrid {
if len(kyberCT) != 1088 {
fmt.Printf("CRITICAL: kyberCT is %d bytes, expected 1088\n", len(kyberCT))
return
}
finalBlob = append(finalBlob, kyberCT...)
}
finalBlob = append(finalBlob, nonce...)
finalBlob = append(finalBlob, ciphertext...)
// 10. Write output
dst, _ := os.Create(outputValue)
defer dst.Close()
if isArmoredSet {
dst.WriteString(vars.PrivateKeyHeader + "\n")
encoded := base64.StdEncoding.EncodeToString(finalBlob)
for i := 0; i < len(encoded); i += 64 {
end := i + 64
if end > len(encoded) {
end = len(encoded)
}
dst.WriteString(encoded[i:end] + "\n")
}
dst.WriteString(vars.PrivateKeyFooter + "\n")
} else {
dst.Write(finalBlob)
}
fmt.Printf("File created: %s (mode %d, %d bytes)\n", outputValue, mode, len(finalBlob))
*/
/*
isLockSet, _ := utils.IsFlagPassed("L")
isAPISet, _ := utils.IsFlagPassed("A")
isPassSet, _ := utils.IsFlagPassed("P")
isArmoredSet, _ := utils.IsFlagPassed("a")
/////////
isKeyPassed, _ := utils.IsFlagPassed("k")
keyValue, _ := utils.GetFlagValue("k")
_ = isKeyPassed
//////////
// set values
apiValue, _ := utils.GetFlagValue("A")
passValue, _ := utils.GetFlagValue("P")
_ = passValue
var apiKey string
var pass string
if isAPISet {
apiKey, _ = utils.FetchFileKey(apiValue, "key")
}
if isPassSet {
pass = ""
}
//isInputSet, inputHasValue := utils.IsFlagPassed("i")
//inputValue, _ := utils.GetFlagValue("i")
src, _ := os.Open(inputValue)
plaintext, _ := io.ReadAll(src)
cleanPub := strings.TrimPrefix(pub.PublicKey.Key, "syscrypt")
recipientPubX, _ := hex.DecodeString(cleanPub)
ephPrivX := make([]byte, 32)
io.ReadFull(rand.Reader, ephPrivX)
ephPubX, _ := curve25519.X25519(ephPrivX, curve25519.Basepoint)
sharedX, _ := curve25519.X25519(ephPrivX, recipientPubX)
var kyberCT, sharedML []byte
isHybrid := pub.PublicKey.MLKEMKey != ""
if isHybrid {
scheme := kyber768.Scheme()
pkBytes, _ := hex.DecodeString(pub.PublicKey.MLKEMKey)
pkK, _ := scheme.UnmarshalBinaryPublicKey(pkBytes)
sharedML, kyberCT, _ = scheme.Encapsulate(pkK)
}
combined := append(sharedX, sharedML...)
if isLockSet {
passKey := argon2.IDKey([]byte(pass), []byte(apiKey), 1, 64*1024, 4, 32)
combined = append(combined, passKey...)
zeroize(passKey)
}
h := hkdf.New(sha256.New, combined, nil, []byte("syscrypt-v1-hybrid"))
symmetricKey := make([]byte, 32)
io.ReadFull(h, symmetricKey)
zeroize(symmetricKey)
aead, _ := cc20.New(symmetricKey)
nonce := make([]byte, aead.NonceSize())
io.ReadFull(rand.Reader, nonce)
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
var mode byte
switch {
case isHybrid && isLockSet:
mode = 0x04 // Post-Quantum + Password Protected
case isHybrid:
mode = 0x03 // Post-Quantum only
case isLockSet:
mode = 0x02 // Classical + Password Protected
default:
mode = 0x01 // Classical Only
}
serialKey, err := utils.FetchFileKey(keyValue, "serial")
if err != nil {
msg := "unable to fetch key serial. Unable to proceed."
utils.HandleFailure(msg)
os.Exit(1)
}
serialBytes := []byte(serialKey)
serialSize := len(serialBytes)
maskedSerial := make([]byte, serialSize)
for i := 0; i < serialSize; i++ {
maskedSerial[i] = serialBytes[i] ^ ephPubX[i%32]
}
var finalBlob []byte
finalBlob = append(finalBlob, mode)
finalBlob = append(finalBlob, byte(serialSize))
finalBlob = append(finalBlob, maskedSerial...)
finalBlob = append(finalBlob, ephPubX...)
if isHybrid {
finalBlob = append(finalBlob, kyberCT...)
}
finalBlob = append(finalBlob, nonce...)
finalBlob = append(finalBlob, ciphertext...)
dst, err := os.Create(outputValue)
if err != nil {
fmt.Printf("error: unable to create %s\n", outputValue)
os.Exit(1)
}
defer dst.Close()
if isArmoredSet {
dst.WriteString(vars.PrivateKeyHeader + "\n")
encoded := base64.StdEncoding.EncodeToString(finalBlob)
for i := 0; i < len(encoded); i += 64 {
end := i + 64
if end > len(encoded) {
end = len(encoded)
}
dst.WriteString(encoded[i:end] + "\n")
}
dst.WriteString(vars.PrivateKeyFooter + "\n")
} else {
dst.Write(finalBlob)
}
fmt.Printf("final blob: %v\n", finalBlob)
fmt.Printf("is hybrid: %v\n", isHybrid)
_ = ciphertext
_ = isArmoredSet
_ = ephPubX
_ = kyberCT
_ = mode
_ = serialBytes
//////////////////////////////////////////
//serialKey, err := utils.FetchFileKey(keyValue, "serial")
//if err != nil {
// msg := "Unable to fetch key serial. Unable to proceed."
// utils.HandleFailure(msg)
// os.Exit(1)
//}
//serialBytes := []byte(serialKey)
//serialSize := len(serialBytes)
//maskedSerial := make([]byte, serialSize)
//for i := 0; i < serialSize; i++ {
// maskedSerial[i] = serialBytes[i] ^ ephPubX[i%32]
//}
//fmt.Printf("serial: %s\n", serialKey)
//fmt.Printf("masked: %s\n", maskedSerial)
//os.Exit(1)
///////////////////////////////////////////
//var finalBlob []byte
//finalBlob = append(finalBlob, mode)
//finalBlob = append(finalBlob, ephPubX...)
//if isHybrid {
// finalBlob = append(finalBlob, kyberCT...)
//}
//finalBlob = append(finalBlob, nonce...)
//finalBlob = append(finalBlob, ciphertext...)
//dst, err := os.Create(outputValue)
//if err != nil {
// fmt.Printf("Error: unable to create %s\n", outputValue)
// os.Exit(1)
//}
//defer dst.Close()
//if isArmoredSet {
// dst.WriteString(vars.PrivateKeyHeader + "\n")
// encoded := base64.StdEncoding.EncodeToString(finalBlob)
// for i := 0; i < len(encoded); i += 64 {
// end := i + 64
// if end > len(encoded) {
// end = len(encoded)
// }
// dst.WriteString(encoded[i:end] + "\n")
// }
// dst.WriteString(vars.PrivateKeyFooter + "\n")
//} else {
// dst.Write(finalBlob)
//}
//fmt.Printf("Encryption Complete: %s\n", outputValue)
//dst, err := os.Create(outputValue)
//if err != nil {
// fmt.Printf("Error: unable to create %s\n", outputValue)
// os.Exit(1)
//}
//defer dst.Close()
//dst.Write([]byte{mode})
//dst.Write(ephPubX)
//switch mode {
//case 0x03, 0x04:
// dst.Write(kyberCT)
//}
//dst.Write(nonce)
//dst.Write(ciphertext)
// validate headers
*/
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

504
internal/actions/keygen/keygen.go Executable file
View File

@@ -0,0 +1,504 @@
package keygen
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strconv"
"time"
"sources.truenas.cloud/code/syscrypt/internal/utils"
"sources.truenas.cloud/code/syscrypt/internal/vars"
)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func FirstRun() {
defaultFolder := configuration.ConfigKeyFolder
//fmt.Printf("default folder: %s\n", defaultFolder)
//fmt.Printf("[DEBUG KEYGEN] Value: %s | Addr: %p\n", config.KeyFolder, &config.KeyFolder)
//defaultFolder := config.KeyFolder
//defaultKey := config.KeyFolder + "/" + config.MasterKey
//defaultKeyExists := utils.FileExists(defaultKey)
//defaultFolderExists := utils.FolderExists(defaultFolder)
//fmt.Printf("CONFIG KEY FOLDER: %s\n", config.KeyFolder)
_ = defaultFolder
os.Exit(1)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func Validate() {
keygenAllowedFlags := map[string]struct{}{
"-C": {},
"-i": {},
"-c": {},
"-PQ": {},
"-k": {},
"-f": {},
}
utils.ValidateAllowedFlags(keygenAllowedFlags)
keygenRequiredFlags := []vars.FlagRequirement{
{Flag: "-f", IsRequired: true},
}
// friendly name -f
// keypath -K
// output -o
utils.ValidateRequiredFlags(keygenRequiredFlags, "keygen")
// friendlyname
isFriendlySet, friendlyHasValue := utils.IsFlagPassed("f")
friendlyValue, _ := utils.GetFlagValue("f")
isFriendlyValid := utils.IsAlphaNumeric(friendlyValue)
if isFriendlySet && !friendlyHasValue {
msg := fmt.Sprintf("%s: -f FRIENDLYNAME: requires a value.\n"+
"Do not enter spaces or special characters. e.g. 'masterkey'", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isFriendlySet && friendlyHasValue && !isFriendlyValid {
msg := fmt.Sprintf("%s: -f FRIENDLYNAME: invalid friendly name.\n"+
"The friendly name requires an alphanumeric value. e.g. 'masterkey'\n"+
"Do not enter paths, spaces, special characters or file extensions.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
// check default keys
// keypath
isKeyPathSet, keyPathHasValue := utils.IsFlagPassed("k")
keyPathValue, _ := utils.GetFlagValue("k")
keyPathIsValid := utils.IsValidPath(keyPathValue)
keyPathExists := utils.FolderExists(keyPathValue)
if isKeyPathSet && !keyPathHasValue {
msg := fmt.Sprintf("%s: -k KEYPATH: requires a value.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isKeyPathSet && !keyPathIsValid {
msg := fmt.Sprintf("%s: -k KEYPATH: requires a valid folder path.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
if isKeyPathSet && keyPathIsValid && !keyPathExists {
msg := fmt.Sprintf("%s: -k KEYPATH: folder path '%s' does not exist.", vars.CommandFlag, keyPathValue)
utils.HandleFailure(msg)
os.Exit(1)
}
var keyFolder string
var defaultSet bool
if isKeyPathSet {
defaultSet = false
keyFolder = keyPathValue
} else {
defaultSet = true
keyFolder = config.KeyFolder
}
if defaultSet {
keyPathIsValid = utils.IsValidPath(keyFolder)
}
if defaultSet && !keyPathIsValid {
msg := fmt.Sprintf("%s: DEFAULT KEYPATH '%s' was not found.", vars.CommandFlag, config.KeyFolder)
utils.HandleFailure(msg)
os.Exit(1)
}
// comment
isCommentSet, commentHasValue := utils.IsFlagPassed("C")
if isCommentSet && !commentHasValue {
msg := fmt.Sprintf("%s: -C COMMENT: flag is set but no value was provided.", vars.CommandFlag)
utils.HandleFailure(msg)
os.Exit(1)
}
fmt.Printf("key folder: %s\n", keyFolder)
_ = isKeyPathSet
_ = keyPathHasValue
_ = keyPathIsValid
_ = keyPathExists
_ = keyFolder
// comment
// output
KeyGen()
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func generateRandomSerial() string {
token := make([]byte, 15)
rand.Read(token)
return hex.EncodeToString(token)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func getCurrentDate() string {
return time.Now().Format("2006-01-02 15:04:05")
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func saveKeyFile(filename string, permission string, data interface{}) {
fmt.Printf("%v\n", data)
os.Exit(1)
if permission == "" {
fmt.Printf("error: permission code required.\n")
return
}
fileData, err := json.MarshalIndent(data, "", " ")
if err != nil {
fmt.Printf("error encoding json: %v\n", err)
return
}
err = os.WriteFile(filename, fileData, 0600)
if err != nil {
fmt.Printf("Error writing %s: %v\n", filename, err)
return
}
permUint, _ := strconv.ParseUint(permission, 8, 32)
err = os.Chmod(filename, os.FileMode(permUint))
if err != nil {
fmt.Printf("Error adjusting permissions on %s: %v\n", filename, err)
return
}
fmt.Printf("Success: '%s' saved successfully.\n", filename)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func KeyGen() {
outputFolder, _ := utils.GetFlagValue("o")
isPQSet, _ := utils.IsFlagPassed("PQ")
commentValue, _ := utils.GetFlagValue("C")
if outputFolder == "" {
outputFolder = config.KeyFolder
}
_ = isPQSet
_ = commentValue
//fmt.Printf("%s", config.KeyFolder)
//outputValue, _ := utils.GetFlagValue("o")
//isPQSet, _ := utils.IsFlagPassed("PQ")
//commentValue, _ := utils.GetFlagValue("C")
//pubWrap := syscrypt.PublicKeyWrapper{}
//privWrap := syscrypt.PrivateKeyWrapper{}
//apiWrap := syscrypt.ApiKeyWrapper{}
///
//var privX, pubX [32]byte
//io.ReadFull(rand.Reader, privX[:])
//curve25519.ScalarBaseMult(&pubX, &privX)
//serial := generateRandomSerial()
//date := getCurrentDate()
//password := (generatepassword)
//pubWrap.PublicKey.Key = vars.DefaultPrefixLabel + hex.EncodeToString(pubX[:])
//pubWrap.PublicKey.Serial = serial
//pubWrap.PublicKey.Date = date
//pubWrap.PublicKey.Comment = commentValue
//privWrap.PrivateKey.Key = vars.PrivateKeyPrefixLabel + hex.EncodeToString(privX[:])
//privWrap.PrivateKey.Serial = serial
//privWrap.PrivateKey.Date = date
//privWrap.PrivateKey.Comment = commentValue
//apiWrap.ApiKey.Key = ""
//apiWrap.ApiKey.Serial = serial
//apiWrap.ApiKey.Date = date
//apiWrap.ApiKey.Comment = commentValue
/*
type ApiKeyWrapper struct {
ApiKey struct {
Key string `json:"key"`
Serial string `json:"serial"`
Date string `json:"date"`
Comment string `json:"comment"`
} `json:"APIKey"`
}
*/
//if isPQSet {
// scheme := kyber768.Scheme()
// pkK, skK, err := scheme.GenerateKeyPair()
// if err != nil {
// fmt.Printf("error: keygen failed: %v\n", err)
// return
// }
// pkBytes, _ := pkK.MarshalBinary()
// skBytes, _ := skK.MarshalBinary()
// pubWrap.PublicKey.MLKEMKey = vars.PQPublicKeyPrefixLabel + hex.EncodeToString(pkBytes)
// privWrap.PrivateKey.MLKEMKey = vars.PQPrivateKeyPrefixLabel + hex.EncodeToString(skBytes)
//}
//pubJSON, err := json.MarshalIndent(pubWrap, "", " ")
//if err != nil {
// fmt.Printf("Marshal Error: %v\n", err)
// return
//}
//privJSON, err := json.MarshalIndent(privWrap, "", " ")
//if err != nil {
// fmt.Printf("Marshal Error: %v\n", err)
// return
//}
//MasterFolder := config.KeyFolder
// Public Key File
//pubFullPath := outputValue
//pubDir := filepath.Dir(pubFullPath)
//pubBase := filepath.Base(pubFullPath)
//pubExt := filepath.Ext(pubBase)
//pubFileName := strings.TrimSuffix(pubBase, pubExt)
//pubFilePath := MasterFolder + "/" + pubFileName + ".pub" + pubExt
// API Key File
//apiFullPath := outputValue
//apiDir := filepath.Dir(apiFullPath)
//apiBase := filepath.Base(apiFullPath)
//apiExt := filepath.Ext(apiBase)
//apiFileName := strings.TrimSuffix(apiBase, apiExt)
//apiFilePath := MasterFolder + "/" + apiFileName + ".api" + apiExt
// Master Password
/*
passFullPath := outputValue
passDir := filepath.Dir(passFullPath)
passBase := filepath.Base(passFullPath)
passExt := filepath.Ext(passBase)
passFileName := strings.TrimSuffix(passBase, passExt)
passFilePath := MasterFolder + "/" + passFileName + ".secret" + passExt
fmt.Printf("fullpath: %s\n", passFullPath)
fmt.Printf("dir: %s\n", passDir)
fmt.Printf("base: %s\n", passBase)
fmt.Printf("ext: %s\n", passExt)
fmt.Printf("filename: %s\n", passFileName)
fmt.Printf("path: %s\n", passFilePath)
*/
// Save to disk
//err = os.WriteFile(pubFilePath, pubJSON, 0644)
//if err != nil {
// fmt.Printf("File Error: %v\n", err)
// os.Exit(1)
//}
//err = os.WriteFile(outputValue, privJSON, 0600)
//if err != nil {
// fmt.Printf("File Error: %v\n", err)
// os.Exit(1)
//}
//saveKeyFile(outputValue, "600", privJSON)
//saveKeyFile(filename string, permission string, data interface{})
////pubFullPath := outputValue
////pubDir := filepath.Dir(pubFullPath)
////pubBase := filepath.Base(pubFullPath)
////pubExt := filepath.Ext(pubBase)
////pubClean := strings.TrimSuffix(pubBase, pubExt)
////pubFileOut := pubClean + ".pub" + pubExt
////pubFileOut = pubDir + "/" + pubFileOut
//_ = outputValue
//_ = commentValue
//_ = pubJSON
//_ = privJSON
//_ = pubDir
//_ = pubFilePath
//_ = apiDir
//_ = apiFilePath
//_ = apiWrap
//_ = passDir
//_ = passFilePath
//fmt.Printf("%v\n", pkK)
//fmt.Printf("%v\n", skK)
////outputValue, _ := utils.GetFlagValue("o")
////isPQSet, _ := utils.IsFlagPassed("PQ")
////commentValue, _ := utils.GetFlagValue("C")
////privX := make([]byte, 32)
////rand.Read(privX)
////pubX, _ := curve25519.X25519(privX, curve25519.Basepoint)
////pubWrap := syscrypt.PublicKeyWrapper{}
////privWrap := syscrypt.PrivateKeyWrapper{}
////apiWrap := syscrypt.ApiKeyWrapper{}
////_ = apiWrap
///
////serial := generateRandomSerial()
////date := getCurrentDate()
////if isPQSet {
//// pubK, privK, _ := kyber768.GenerateKeyPair(rand.Reader)
//// pBytes, _ := pubK.MarshalBinary()
//// sBytes, _ := privK.MarshalBinary()
//// pubWrap.PublicKey.MLKEMKey = vars.PQPublicKeyPrefixLabel + hex.EncodeToString(pBytes)
//// privWrap.PrivateKey.MLKEMKey = vars.PQPrivateKeyPrefixLabel + hex.EncodeToString(sBytes)
////}
////pubWrap.PublicKey.Key = vars.DefaultPrefixLabel + hex.EncodeToString(pubX)
////pubWrap.PublicKey.Serial = serial
////pubWrap.PublicKey.Date = date
////pubWrap.PublicKey.Comment = commentValue
////privWrap.PrivateKey.Key = vars.PrivateKeyPrefixLabel + hex.EncodeToString(privX)
////privWrap.PrivateKey.Serial = serial
////privWrap.PrivateKey.Date = date
////privWrap.PrivateKey.Comment = commentValue
//apiWrap.ApiKey.Key = GenerateAPIKey(xxx, )
//apiWrap.ApiKey.Serial = serial
//apiWrap.ApiKey.Date = date
//apiWrap.ApiKey.Comment = commentValue
//type ApiKeyWrapper struct {
// ApiKey struct {
// Key string `json:"key"`
// Serial string `json:"serial"`
// Date string `json:"date"`
// Comment string `json:"comment"`
// } `json:"APIKey"`
//}
////fmt.Printf("public key: %s\n", pubWrap.PublicKey.Key)
////fmt.Printf("private key: %s\n", privWrap.PrivateKey.Key)
////fmt.Printf("pq: %s\n", pubWrap.PublicKey.MLKEMKey)
//preview, _ := json.MarshalIndent(privWrap, "", " ")
//fmt.Println(string(preview))
////pubFullPath := outputValue
////pubDir := filepath.Dir(pubFullPath)
////pubBase := filepath.Base(pubFullPath)
////pubExt := filepath.Ext(pubBase)
////pubClean := strings.TrimSuffix(pubBase, pubExt)
////pubFileOut := pubClean + ".pub" + pubExt
////pubFileOut = pubDir + "/" + pubFileOut
////apiFullPath := outputValue
////apiDir := filepath.Dir(apiFullPath)
////apiBase := filepath.Base(apiFullPath)
////apiExt := filepath.Ext(apiBase)
////apiClean := strings.TrimSuffix(apiBase, apiExt)
////apiFileOut := apiClean + ".api" + apiExt
////apiFileOut = apiDir + "/" + apiFileOut
// Generate Master Password if one doesnt already exist
//mpExists := utils.ReadJson("config.masterpass", config.ConfigPath)
////_ = pubDir
////_ = apiDir
////_ = apiFileOut
////fmt.Printf("public out: %s\n", pubDir+"/"+pubFileOut)
////fmt.Printf("api out: %s\n", apiDir+"/"+apiFileOut)
////saveKeyFile(pubFileOut, "0600", pubWrap)
////saveKeyFile(outputValue, "0644", privWrap)
//saveKeyFile(xxxx, 0600, xxxxxx)
//func saveKeyFile(filename string, permission string, data interface{}) {
//type PublicKeyWrapper struct {
// PublicKey struct {
// Key string `json:"key"`
// MLKEMKey string `json:"mlkem_key,omitempty"`
// Serial string `json:"serial"`
// Date string `json:"date"`
// Comment string `json:"comment"`
// } `json:"PublicKey"`
//}
//privRaw := make([]byte, 32)
//serial := utils.GenerateSerial(27)
//io.ReadFull(rand.Reader, privRaw)
//pubRaw, _ := curve25519.X25519(privRaw, curve25519.Basepoint)
//dateStr := time.Now().Format("2006-01-02 15:04:05")
//privStruct := syscrypt.PrivateKeyWrapper{}
//privStruct.PrivateKey.Key = vars.PrivateKeyPrefixLabel + hex.EncodeToString(privRaw)
//privStruct.PrivateKey.Serial = serial
//privStruct.PrivateKey.Date = dateStr
//privStruct.PrivateKey.Comment = vars.Comment
//pubStruct := syscrypt.PublicKeyWrapper{}
//pubStruct.PublicKey.Key = vars.DefaultPrefixLabel + hex.EncodeToString(pubRaw)
//pubStruct.PublicKey.Serial = serial
//pubStruct.PublicKey.Date = dateStr
//pubStruct.PublicKey.Comment = vars.Comment
//pBytes, _ := json.MarshalIndent(privStruct, "", " ")
//pubBytes, _ := json.MarshalIndent(pubStruct, "", " ")
//fmt.Printf("%s\n", pBytes)
//fmt.Printf("%s\n", pubBytes)
//fmt.Printf("%s\n", pubStruct.PublicKey.Serial)
//utils.GenerateKeys(pubStruct.PublicKey.Key, privStruct.PrivateKey.Key, serial)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,58 @@
package configuration
import (
"fmt"
"os"
)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const (
// BUILD DATA
BuildProject = "syscrypt "
BuildVersion = "1.2.1"
BuildDate = "20260222-183358"
BuildSource = "https://www.github.com/mintsalad/syscrypt"
// JSON
ConfigPath = "/opt/syscrypt/config.json"
// KEYS
MasterExt = ".d"
MasterKey = ".masterkey.d"
MasterPublicKey = ".masterkey.pub.d"
MasterKeySalt = ".register.d"
MasterAPIKey = ".master.api.d"
Dictionary = ".library.d"
DictionaryTmp = ".library.tmp.txt"
StatePath = ".ratelimit"
LockPath = ".lockout"
// LOCKOUT SETTINGS
MaxTokens = 5
RefillSeconds = 2
MaxFailures = 3
LockoutMinutes = 15
)
var KeyFolder string
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func init() {
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
fmt.Printf("config file is missing at %s\n", ConfigPath)
}
//KeyFolder = utils.ReadJSON(".config.keys.Path", ConfigPath)
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//func ReadJson(path string, filePath string) (string, error) {

141
internal/utils/salt.go Executable file
View File

@@ -0,0 +1,141 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
"sources.truenas.cloud/code/syscrypt"
"sources.truenas.cloud/code/syscrypt/internal/configuration"
"sources.truenas.cloud/code/syscrypt/internal/vars"
)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func HashEntry(value, x25519Str string) string {
key := strings.TrimPrefix(x25519Str, vars.PrivateKeyPrefixLabel)
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(value))
return hex.EncodeToString(h.Sum(nil))
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func AddSaltEntry(ref, serial, salt, x25519Key string) error {
var vault syscrypt.Vault
//vaultPath := config.KeyFolder + "/" + config.MasterKeySalt
vaultPath := configuration.KeyFolder + "/" + configuration.MasterKeySalt
if data, err := os.ReadFile(vaultPath); err == nil {
json.Unmarshal(data, &vault)
}
for _, e := range vault.Entries {
if e.Ref == ref {
return fmt.Errorf("reference %s already exists", ref)
}
}
encSalt := salt
newEntry := syscrypt.SaltEntry{
Ref: ref,
Salt: encSalt,
}
vault.Entries = append(vault.Entries, newEntry)
updated, _ := json.MarshalIndent(vault, "", " ")
return os.WriteFile(vaultPath, updated, 0644)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func VerifySaltEntry(ref, x25519Key string) (salt string, exists bool, err error) {
//var vault syscrypt.Vault
//vaultPath := config.KeyFolder + "/" + config.MasterKeySalt
/*
data, err := os.ReadFile(vaultPath)
if err != nil {
return "", false, err
}
json.Unmarshal(data, &vault)
for _, e := range vault.Entries {
if e.Ref == ref {
//
//decSerial, errS := DecryptString(e.Serial, x25519Key)
decSalt, errSalt := DecryptString(e.Salt, x25519Key)
if errSalt != nil {
return "", false, fmt.Errorf("failed to decrypt entry")
}
return decSalt, true, nil
//
//
}
}
*/
//return "", false, fmt.Errorf("reference %s not found", ref)
return "", false, nil
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func EncryptString(plaintext, x25519Key string) (string, error) {
keyBytes := sha256.Sum256([]byte(x25519Key))
block, _ := aes.NewCipher(keyBytes[:])
gcm, _ := cipher.NewGCM(block)
nonce := make([]byte, gcm.NonceSize())
rand.Read(nonce)
return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, []byte(plaintext), nil)), nil
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func DecryptString(cipherText string, x25519Key string) (string, error) {
data, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}
keyBytes := sha256.Sum256([]byte(x25519Key))
block, _ := aes.NewCipher(keyBytes[:])
gcm, _ := cipher.NewGCM(block)
nonceSize := gcm.NonceSize()
nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
plain, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
return string(plain), err
//keyBytes := sha256.Sum256([]byte(x25519Key))
//block, _ := aes.NewCipher(keyBytes[:])
//gcm, _ := cipher.NewGCM(block)
//nonceSize := gcm.NonceSize()
//nonce, cipher := cipherText[:nonceSize], cipherText[nonceSize:]
//plain, err := gcm.Open(nil, nonce, cipher, nil)
//return string(plain), err
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

2391
internal/utils/utils.go Executable file

File diff suppressed because it is too large Load Diff

108
internal/vars/vars.go Executable file
View File

@@ -0,0 +1,108 @@
package vars
var (
// tag
Tag_P256Label = "syscrypt/p256tag" // TagLabel
Tag_MLKem768p256Label = "syscrypt/mlkem768p256tag" // MLKemLabel
Tag_Syscrypt1Tag1 = "syscrypt1tag1" // LabelSyscrypt1Tag1
Tag_MLKem768X25519 = "syscrypt/mlkem768x25519" // MLKem768X25519
Tag_Syscrypt1TagPq1 = "syscrypt1tagpq1" //
// post quantum
Pq_PrivateKeyLabel = "SYSCRYPT-PQ-PRIVATE-KEY-" // PrivateKeyPQPrefix
Pq_PublicKeyLabel = "syscrypt1pq1" // PublicKeyPQPrefix
// plugins
Plugin_Label = "syscrypt-plugin-" // PluginLabel
Plugin_PrivateKeyLabel = "SYSCRYPT-PLUGIN-" //PluginPrivateKeyLabel
// ssh
SSH_Label = "ssh-"
// Github
Github_label = "github:"
// OAEP
OAEP_Label = "syscrypt/base/v1/ssh-rsa" // OAEPLabel
// ED25519
ED25519_Label = "syscrypt/base/v1/ssh-ed25519" // ED25519Label
// X25519
X25519_Label = "syscrypt/base/v1/X25519" // X25519Label
// encrypt, decrypt
DefaultPrefixLabel = "syscrypt--" // DefaultPrefix
PrivateKeyPrefixLabel = "SYSCRYPT-PRIVATE-KEY--" // PrivateKeyPrefix
PublicKeyPrefixLabel = "syscrypt--" // PublicKeyPrefix
DefaultPrivateKeyPrefixLabel = "SYSCRYPT--"
PQPublicKeyPrefixLabel = "syscrypt-pq--"
PQPrivateKeyPrefixLabel = "SYSCRYPT-PRIVATE-KEY-PQ--"
// Other
SyscryptLabel = "syscrypt/base/v1/"
ScryptLabel = "syscrypt/base/v1/scrypt"
PrivateKeyHeader = "-----BEGIN ENCRYPTED DATA-----" // PrivateKeyHeader
PrivateKeyStartTrunc = "-----BEGIN"
PrivateKeyEndTrunc = "-----END"
PrivateKeyFooter = "-----END ENCRYPTED DATA-----" // PrivateKeyFooter
VersionLabel = "syscrypt/base/v1/[serial]"
// Lock
LockPrivateKeyStartTrunc = "-----BEGIN LOCK ENCRYPTED DATA-----"
LockPrivateKeyEndTrunc = "-----END LOCK ENCRYPTED DATA-----"
LockVersionLabel = "syscrypt/lock/v1/[serial]"
LockedLabel = "syscrypt/lock/"
LockedLabelVersion = "syscrypt/lock/v1/"
// Anchors
StartAnchor = "/v1/"
EndAnchor = "--"
BoundaryAnchor = []byte("---")
FooterAnchor = "---"
)
var (
Version string
VersionFlag bool // version
CommandFlag string // -c
ArmoredFlag bool // -a
KeyFlag string // -k
InputFlag string // -i
OutputFlag string // -o
LockFlag bool // -L
ApiFlag string // -A
MasterPass string // -P
Comment string // -COMMENT
StringFlag string // -s
PQFlag bool // post quantum
KeyPath string // -K
FriendlyName string // -f
)
const (
PassTagSize = 28 // 12 bytes nonce/ct + 16 bytes poly1305 tag
KyberCTSize = 1088 // Kyber768 Ciphertext size
)
type FlagRequirement struct {
Flag string
IsRequired bool
}
type ExpectedKeys struct {
File string
}

47
syscrypt.go Executable file
View File

@@ -0,0 +1,47 @@
package syscrypt
import "sources.truenas.cloud/code/syscrypt/internal/vars"
type PrivateKeyWrapper struct {
PrivateKey struct {
Key string `json:"key"`
MLKEMKey string `json:"mlkem_key,omitempty"`
Serial string `json:"serial"`
Salt string `json:"salt"`
Date string `json:"date"`
Comment string `json:"comment"`
} `json:"PrivateKey"`
}
type PublicKeyWrapper struct {
PublicKey struct {
Key string `json:"key"`
MLKEMKey string `json:"mlkem_key,omitempty"`
Serial string `json:"serial"`
Salt string `json:"salt"`
Date string `json:"date"`
Comment string `json:"comment"`
} `json:"PublicKey"`
}
type ApiKeyWrapper struct {
ApiKey struct {
Key string `json:"key"`
Serial string `json:"serial"`
Salt string `json:"salt"`
Date string `json:"date"`
Comment string `json:"comment"`
} `json:"APIKey"`
}
type SaltEntry struct {
Ref string `json:"ref"`
Salt string `json:"salt"`
}
type Vault struct {
Entries []SaltEntry `json:"entries"`
}
var ArmorHeader = vars.PrivateKeyHeader
var ArmorFooter = vars.PrivateKeyFooter