Skip to content

Commit

Permalink
adding support for stream encryption in wasm through asynchronous byt…
Browse files Browse the repository at this point in the history
…e chunk exchange
  • Loading branch information
shibme committed Sep 17, 2024
1 parent 190ec02 commit 82cf821
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 122 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
dist/
wasm_exec.js
wasm_exec.js
.ignore/
26 changes: 13 additions & 13 deletions crypto/xcp/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ func (w *Writer) Close() error {
}

type Reader struct {
aead cipher.AEAD
src io.Reader
buf bytes.Buffer
nonce []byte
aead cipher.AEAD
src io.Reader
outBuf bytes.Buffer
nonce []byte
}

// NewDecryptingReader returns a new io.ReadCloser that decrypts src with the cipher
Expand All @@ -104,10 +104,10 @@ func (cipher *SymmetricCipher) NewDecryptingReader(src io.Reader) (io.ReadCloser

func (cipher *SymmetricCipher) newReader(nonce []byte, src io.Reader) (io.ReadCloser, error) {
ciphReader := &Reader{
aead: *cipher.aead,
src: src,
buf: bytes.Buffer{},
nonce: nonce,
aead: *cipher.aead,
src: src,
outBuf: bytes.Buffer{},
nonce: nonce,
}
compressFlag := make([]byte, 1)
if _, err := io.ReadFull(src, compressFlag); err != nil {
Expand All @@ -124,8 +124,8 @@ func (cipher *SymmetricCipher) newReader(nonce []byte, src io.Reader) (io.ReadCl
}

func (r *Reader) Read(p []byte) (int, error) {
if r.buf.Len() > len(p) {
return r.buf.Read(p)
if r.outBuf.Len() > len(p) {
return r.outBuf.Read(p)
}
var block [ctBlockSize]byte
n, err := io.ReadFull(r.src, block[:])
Expand All @@ -134,10 +134,10 @@ func (r *Reader) Read(p []byte) (int, error) {
if err != nil {
return 0, fmt.Errorf("%s: decrypter failed to decrypt", "xipher")
}
r.buf.Write(pt)
return r.buf.Read(p)
r.outBuf.Write(pt)
return r.outBuf.Read(p)
} else if err == io.EOF {
return r.buf.Read(p)
return r.outBuf.Read(p)
} else {
return 0, fmt.Errorf("%s: decrypter failed to read", "xipher")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/cloudflare/circl v1.4.0
github.com/fatih/color v1.17.0
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.26.0
golang.org/x/crypto v0.27.0
golang.org/x/term v0.24.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/commands/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func encryptTextCommand() *cobra.Command {
if err != nil {
exitOnError(err)
}
ct, err := utils.EncryptData(keyPwdStr, input)
ct, err := utils.EncryptData(keyPwdStr, input, true)
if err != nil {
exitOnError(err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/sharedlib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func xipherGetPublicKey(secretKeyOrPassword *C.char, quantumSafe C.int, publicKe

func xipherEncryptData(keyOrPassword *C.char, data *C.char, cipherText **C.char, cipherTextLength *C.int, errMessage **C.char, errLength *C.int) {
dataBytes := C.GoBytes(unsafe.Pointer(data), C.int(len(C.GoString(data))))
if ct, err := utils.EncryptData(C.GoString(keyOrPassword), dataBytes); err != nil {
if ct, err := utils.EncryptData(C.GoString(keyOrPassword), dataBytes, true); err != nil {
*cipherText = nil
*cipherTextLength = 0
*errMessage = C.CString(err.Error())
Expand Down
143 changes: 143 additions & 0 deletions internal/wasm/decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"bytes"
"fmt"
"io"
"sync"
"syscall/js"

"dev.shib.me/xipher/utils"
)

const (
ctMinLegthRequired = 128 * 1024
readableBlockSize = 32 * 1024
)

func decryptStr(args []js.Value) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("supported arguments: secret key or password (required), ciphertext (required)")
}
secretKeyOrPwd := args[0].String()
ciphertext := args[1].String()
message, err := utils.DecryptData(secretKeyOrPwd, ciphertext)
if err != nil {
return nil, err
}
return string(message), nil
}

var (
decrypters = make(map[int]*decrypter)
decryptersMu sync.Mutex
decrypterId int = 1
)

type decrypter struct {
keyOrPwd string
reader io.Reader
src *bytes.Buffer
}

func (d *decrypter) readMax(all bool) ([]byte, error) {
if all {
return io.ReadAll(d.reader)
}
buf := new(bytes.Buffer)
if d.src.Len() >= ctMinLegthRequired {
if d.reader == nil {
reader, err := utils.DecryptingReader(d.keyOrPwd, d.src)
if err != nil {
return nil, err
}
d.reader = reader
}
block := make([]byte, readableBlockSize)
for d.src.Len() >= ctMinLegthRequired {
n, err := d.reader.Read(block)
if err != nil {
return nil, err
}
if n == 0 {
break
}
buf.Write(block[:])
}
}
return buf.Bytes(), nil
}

func (d *decrypter) read(data []byte) ([]byte, error) {
if _, err := d.src.Write(data); err != nil {
return nil, err
}
return d.readMax(false)
}

func (d *decrypter) close() ([]byte, error) {
return d.readMax(true)
}

func newStreamDecrypter(args []js.Value) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("supported arguments: secret key or password (required)")
}
decryptersMu.Lock()
defer decryptersMu.Unlock()
keyOrPwd := args[0].String()
dec := &decrypter{
keyOrPwd: keyOrPwd,
src: new(bytes.Buffer),
}
id := decrypterId
decrypters[id] = dec
decrypterId++
return id, nil
}

func readFromDecrypter(args []js.Value) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("supported arguments: id (required), input (required)")
}
decryptersMu.Lock()
id := args[0].Int()
inputJSArray := args[1]
dec, ok := decrypters[id]
decryptersMu.Unlock()
if !ok {
return nil, fmt.Errorf("decrypter not found for id: %d", id)
}
inputLength := inputJSArray.Get("length").Int()
inputData := make([]byte, inputLength)
js.CopyBytesToGo(inputData, inputJSArray)
outputData, err := dec.read(inputData)
if err != nil {
return nil, err
}
outputJSArray := js.Global().Get("Uint8Array").New(len(outputData))
js.CopyBytesToJS(outputJSArray, outputData)
return outputJSArray, nil
}

func closeDecrypter(args []js.Value) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("supported arguments: id (required)")
}
decryptersMu.Lock()
id := args[0].Int()
dec, ok := decrypters[id]
if !ok {
decryptersMu.Unlock()
return nil, fmt.Errorf("decrypter not found for id: %d", id)
}
delete(decrypters, id)
decryptersMu.Unlock()
outputData, err := dec.close()
if err != nil {
return nil, err
}
outputJSArray := js.Global().Get("Uint8Array").New(len(outputData))
js.CopyBytesToJS(outputJSArray, outputData)
return outputJSArray, nil
}
122 changes: 122 additions & 0 deletions internal/wasm/encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"bytes"
"fmt"
"io"
"sync"
"syscall/js"

"dev.shib.me/xipher/utils"
)

func encryptStr(args []js.Value) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("supported arguments: public key, secret key or password (required), message (required)")
}
keyOrPwd := args[0].String()
message := args[1].String()
ciphertext, err := utils.EncryptData(keyOrPwd, []byte(message), true)
if err != nil {
return nil, err
}
return ciphertext, nil
}

var (
encrypters = make(map[int]*encrypter)
encryptersMu sync.Mutex
encrypterId int = 1
)

type encrypter struct {
writer io.WriteCloser
dst *bytes.Buffer
}

func (e *encrypter) write(data []byte) ([]byte, error) {
_, err := e.writer.Write(data)
if err != nil {
return nil, err
}
if e.dst.Len() > 0 {
return e.dst.Next(e.dst.Len()), nil
}
return nil, nil
}

func (e *encrypter) close() ([]byte, error) {
err := e.writer.Close()
if err != nil {
return nil, err
}
return e.dst.Next(e.dst.Len()), nil
}

func newStreamEncrypter(args []js.Value) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("supported arguments: public key, secret key or password (required), compress (required)")
}
encryptersMu.Lock()
defer encryptersMu.Unlock()
keyOrPwd := args[0].String()
compress := args[1].Bool()
enc := &encrypter{
dst: new(bytes.Buffer),
}
writer, err := utils.EncryptingWriter(keyOrPwd, enc.dst, compress)
if err != nil {
return nil, err
}
enc.writer = writer
id := encrypterId
encrypters[id] = enc
encrypterId++
return id, nil
}

func writeToEncrypter(args []js.Value) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("supported arguments: id (required), input (required)")
}
encryptersMu.Lock()
id := args[0].Int()
inputJSArray := args[1]
enc, ok := encrypters[id]
encryptersMu.Unlock()
if !ok {
return nil, fmt.Errorf("encrypter not found for id: %d", id)
}
inputLength := inputJSArray.Get("length").Int()
inputData := make([]byte, inputLength)
js.CopyBytesToGo(inputData, inputJSArray)
outputData, err := enc.write(inputData)
if err != nil {
return nil, err
}
outputJSArray := js.Global().Get("Uint8Array").New(len(outputData))
js.CopyBytesToJS(outputJSArray, outputData)
return outputJSArray, nil
}

func closeEncrypter(args []js.Value) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("supported arguments: id (required)")
}
encryptersMu.Lock()
id := args[0].Int()
enc, ok := encrypters[id]
if !ok {
encryptersMu.Unlock()
return nil, fmt.Errorf("encrypter not found for id: %d", id)
}
delete(encrypters, id)
encryptersMu.Unlock()
outputData, err := enc.close()
if err != nil {
return nil, err
}
outputJSArray := js.Global().Get("Uint8Array").New(len(outputData))
js.CopyBytesToJS(outputJSArray, outputData)
return outputJSArray, nil
}
Loading

0 comments on commit 82cf821

Please sign in to comment.