node build fixed
This commit is contained in:
330
seanime-2.9.10/internal/goja/goja_bindings/crypto.go
Normal file
330
seanime-2.9.10/internal/goja/goja_bindings/crypto.go
Normal file
@@ -0,0 +1,330 @@
|
||||
package goja_bindings
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type wordArray struct {
|
||||
vm *goja.Runtime
|
||||
iv []byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
func BindCrypto(vm *goja.Runtime) error {
|
||||
|
||||
err := vm.Set("CryptoJS", map[string]interface{}{
|
||||
"AES": map[string]interface{}{
|
||||
"encrypt": cryptoAESEncryptFunc(vm),
|
||||
"decrypt": cryptoAESDecryptFunc(vm),
|
||||
},
|
||||
"enc": map[string]interface{}{
|
||||
"Utf8": map[string]interface{}{
|
||||
"parse": cryptoEncUtf8ParseFunc(vm),
|
||||
"stringify": cryptoEncUtf8StringifyFunc(vm),
|
||||
},
|
||||
"Base64": map[string]interface{}{
|
||||
"parse": cryptoEncBase64ParseFunc(vm),
|
||||
"stringify": cryptoEncBase64StringifyFunc(vm),
|
||||
},
|
||||
"Hex": map[string]interface{}{
|
||||
"parse": cryptoEncHexParseFunc(vm),
|
||||
"stringify": cryptoEncHexStringifyFunc(vm),
|
||||
},
|
||||
"Latin1": map[string]interface{}{
|
||||
"parse": cryptoEncLatin1ParseFunc(vm),
|
||||
"stringify": cryptoEncLatin1StringifyFunc(vm),
|
||||
},
|
||||
"Utf16": map[string]interface{}{
|
||||
"parse": cryptoEncUtf16ParseFunc(vm),
|
||||
"stringify": cryptoEncUtf16StringifyFunc(vm),
|
||||
},
|
||||
"Utf16LE": map[string]interface{}{
|
||||
"parse": cryptoEncUtf16LEParseFunc(vm),
|
||||
"stringify": cryptoEncUtf16LEStringifyFunc(vm),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func newWordArrayGojaValue(vm *goja.Runtime, data []byte, iv []byte) goja.Value {
|
||||
wa := &wordArray{
|
||||
vm: vm,
|
||||
iv: iv,
|
||||
data: data,
|
||||
}
|
||||
// WordArray // Utf8
|
||||
// WordArray.toString(): string // Uses Base64.stringify
|
||||
// WordArray.toString(encoder: Encoder): string
|
||||
obj := vm.NewObject()
|
||||
obj.Prototype().Set("toString", wa.toStringFunc)
|
||||
obj.Set("toString", wa.toStringFunc)
|
||||
obj.Set("iv", iv)
|
||||
return obj
|
||||
}
|
||||
|
||||
func (wa *wordArray) toStringFunc(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) == 0 {
|
||||
return wa.vm.ToValue(base64Stringify(wa.data))
|
||||
}
|
||||
|
||||
encoder, ok := call.Argument(0).Export().(map[string]interface{})
|
||||
if !ok {
|
||||
panic(wa.vm.ToValue("TypeError: encoder parameter must be a CryptoJS.enc object"))
|
||||
}
|
||||
|
||||
var ret string
|
||||
if f, ok := encoder["stringify"]; ok {
|
||||
if stringify, ok := f.(func(functionCall goja.FunctionCall) goja.Value); ok {
|
||||
ret = stringify(goja.FunctionCall{Arguments: []goja.Value{wa.vm.ToValue(wa.data)}}).String()
|
||||
} else {
|
||||
panic(wa.vm.ToValue("TypeError: encoder.stringify must be a function"))
|
||||
}
|
||||
} else {
|
||||
ret = string(wa.data)
|
||||
}
|
||||
|
||||
return wa.vm.ToValue(ret)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// CryptoJS.AES.encrypt(message: string, key: string, cfg?: { iv: ArrayBuffer }): WordArray
|
||||
func cryptoAESEncryptFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) (ret goja.Value) {
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(vm.ToValue("TypeError: AES.encrypt requires at least 2 arguments"))
|
||||
}
|
||||
|
||||
message := call.Argument(0).String()
|
||||
|
||||
var keyBytes []byte
|
||||
switch call.Argument(1).Export().(type) {
|
||||
case string:
|
||||
key := call.Argument(1).String()
|
||||
keyBytes = adjustKeyLength([]byte(key))
|
||||
case []byte:
|
||||
keyBytes = call.Argument(1).Export().([]byte)
|
||||
keyBytes = adjustKeyLength(keyBytes)
|
||||
default:
|
||||
panic(vm.ToValue("TypeError: key parameter must be a string or an ArrayBuffer"))
|
||||
}
|
||||
|
||||
usedRandomIV := false
|
||||
// Check if IV is provided
|
||||
var ivBytes []byte
|
||||
if len(call.Arguments) > 2 {
|
||||
cfg := call.Argument(2).Export().(map[string]interface{})
|
||||
var ok bool
|
||||
iv, ok := cfg["iv"].([]byte)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: iv parameter must be an ArrayBuffer"))
|
||||
}
|
||||
ivBytes = iv
|
||||
if len(ivBytes) != aes.BlockSize {
|
||||
panic(vm.ToValue("TypeError: IV length must be equal to block size (16 bytes for AES)"))
|
||||
}
|
||||
} else {
|
||||
// Generate a random IV
|
||||
ivBytes = make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, ivBytes); err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to generate IV: %v", err)))
|
||||
}
|
||||
usedRandomIV = true
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = vm.ToValue(fmt.Sprintf("Encryption failed: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
// Encrypt the message
|
||||
encryptedMessage := encryptAES(vm, message, keyBytes, ivBytes)
|
||||
|
||||
if usedRandomIV {
|
||||
// Prepend the IV to the encrypted message
|
||||
encryptedMessage = append(ivBytes, encryptedMessage...)
|
||||
}
|
||||
|
||||
return newWordArrayGojaValue(vm, encryptedMessage, ivBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoJS.AES.decrypt(encryptedMessage: string | WordArray, key: string, cfg?: { iv: ArrayBuffer }): WordArray
|
||||
func cryptoAESDecryptFunc(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) (ret goja.Value) {
|
||||
if len(call.Arguments) < 2 {
|
||||
panic(vm.ToValue("TypeError: AES.decrypt requires at least 2 arguments"))
|
||||
}
|
||||
|
||||
// Can be string or WordArray
|
||||
// If WordArray, String() will call WordArray.toString() which will return the base64 encoded string
|
||||
encryptedMessage := call.Argument(0).String()
|
||||
|
||||
var keyBytes []byte
|
||||
var originalPassword []byte
|
||||
switch call.Argument(1).Export().(type) {
|
||||
case string:
|
||||
key := call.Argument(1).String()
|
||||
originalPassword = []byte(key)
|
||||
keyBytes = adjustKeyLength([]byte(key))
|
||||
case []byte:
|
||||
keyBytes = call.Argument(1).Export().([]byte)
|
||||
originalPassword = keyBytes
|
||||
keyBytes = adjustKeyLength(keyBytes)
|
||||
default:
|
||||
panic(vm.ToValue("TypeError: key parameter must be a string or an ArrayBuffer"))
|
||||
}
|
||||
|
||||
var ivBytes []byte
|
||||
var cipherText []byte
|
||||
|
||||
// If IV is provided in the third argument
|
||||
if len(call.Arguments) > 2 {
|
||||
cfg := call.Argument(2).Export().(map[string]interface{})
|
||||
var ok bool
|
||||
iv, ok := cfg["iv"].([]byte)
|
||||
if !ok {
|
||||
panic(vm.ToValue("TypeError: iv parameter must be an ArrayBuffer"))
|
||||
}
|
||||
ivBytes = iv
|
||||
if len(ivBytes) != aes.BlockSize {
|
||||
panic(vm.ToValue("TypeError: IV length must be equal to block size (16 bytes for AES)"))
|
||||
}
|
||||
var err error
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to decode ciphertext: %v", err)))
|
||||
}
|
||||
cipherText = decodedMessage
|
||||
} else {
|
||||
// Decode the base64 encoded string
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("Failed to decode ciphertext: %v", err)))
|
||||
}
|
||||
|
||||
// Check if openssl
|
||||
if len(decodedMessage) >= 16 && string(decodedMessage[:8]) == "Salted__" {
|
||||
salt := decodedMessage[8:16]
|
||||
cipherText = decodedMessage[16:]
|
||||
derivedKey, derivedIV := evpBytesToKey(originalPassword, salt, 32, aes.BlockSize)
|
||||
keyBytes = derivedKey
|
||||
ivBytes = derivedIV
|
||||
} else {
|
||||
// Extract the IV from the beginning of the message
|
||||
ivBytes = decodedMessage[:aes.BlockSize]
|
||||
cipherText = decodedMessage[aes.BlockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt the message
|
||||
decrypted := decryptAES(vm, cipherText, keyBytes, ivBytes)
|
||||
|
||||
return newWordArrayGojaValue(vm, decrypted, ivBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjusts the key length to match AES key length requirements (16, 24, or 32 bytes).
|
||||
// If the key length is not 16, 24, or 32 bytes, it is hashed using SHA-256 and truncated to 32 bytes (AES-256).
|
||||
func adjustKeyLength(keyBytes []byte) []byte {
|
||||
switch len(keyBytes) {
|
||||
case 16, 24, 32:
|
||||
// Valid AES key lengths: 16 bytes (AES-128), 24 bytes (AES-192), 32 bytes (AES-256)
|
||||
return keyBytes
|
||||
default:
|
||||
// Hash the key to 32 bytes (AES-256)
|
||||
hash := sha256.Sum256(keyBytes)
|
||||
return hash[:]
|
||||
}
|
||||
}
|
||||
|
||||
func encryptAES(vm *goja.Runtime, message string, key []byte, iv []byte) (ret []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = nil
|
||||
}
|
||||
}()
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("%v", err)))
|
||||
}
|
||||
|
||||
messageBytes := []byte(message)
|
||||
messageBytes = pkcs7Padding(messageBytes, aes.BlockSize)
|
||||
|
||||
cipherText := make([]byte, len(messageBytes))
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText, messageBytes)
|
||||
|
||||
return cipherText
|
||||
}
|
||||
|
||||
func decryptAES(vm *goja.Runtime, cipherText []byte, key []byte, iv []byte) (ret []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = nil
|
||||
}
|
||||
}()
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(vm.ToValue(fmt.Sprintf("%v", err)))
|
||||
}
|
||||
|
||||
plainText := make([]byte, len(cipherText))
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(plainText, cipherText)
|
||||
|
||||
plainText = pkcs7Trimming(plainText)
|
||||
|
||||
return plainText
|
||||
}
|
||||
|
||||
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs7Trimming(data []byte) []byte {
|
||||
length := len(data)
|
||||
up := int(data[length-1])
|
||||
return data[:(length - up)]
|
||||
}
|
||||
|
||||
func evpBytesToKey(password []byte, salt []byte, keyLen, ivLen int) ([]byte, []byte) {
|
||||
d := make([]byte, 0)
|
||||
dI := make([]byte, 0)
|
||||
|
||||
for len(d) < (keyLen + ivLen) {
|
||||
h := md5.New()
|
||||
h.Write(dI)
|
||||
h.Write(password)
|
||||
h.Write(salt)
|
||||
dI = h.Sum(nil)
|
||||
d = append(d, dI...)
|
||||
}
|
||||
|
||||
return d[:keyLen], d[keyLen : keyLen+ivLen]
|
||||
}
|
||||
Reference in New Issue
Block a user