601 lines
16 KiB
Go
601 lines
16 KiB
Go
|
|
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
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
}
|