246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"filippo.io/age"
|
|
"filippo.io/age/armor"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"syscall/js"
|
|
"encoding/base64"
|
|
)
|
|
|
|
|
|
// Generate a new age key pair with public and secret part.
|
|
// Returns both parts in an object
|
|
func acusGenerateKey() js.Func {
|
|
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
k, err := age.GenerateX25519Identity()
|
|
if err != nil {
|
|
fmt.Println("Key could not be generated")
|
|
return nil
|
|
}
|
|
newKey := make(map[string]interface{})
|
|
newKey["pubkey"] = k.Recipient().String()
|
|
newKey["secret"] = k.String()
|
|
return newKey
|
|
})
|
|
}
|
|
|
|
|
|
// Decrypt age blocks contained in email bodies
|
|
func acusDecryptText() js.Func {
|
|
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
// args must be one single object
|
|
obj := args[0]
|
|
if obj.Type() != js.TypeObject {
|
|
return nil
|
|
}
|
|
|
|
ciphertext := obj.Get("ciphertext")
|
|
if ciphertext.Type() != js.TypeString {
|
|
fmt.Println("ciphertext is not a string")
|
|
return nil
|
|
}
|
|
|
|
privateKey := obj.Get("privateKey")
|
|
if privateKey.Type() != js.TypeString {
|
|
fmt.Println("privateKey is not a string")
|
|
return nil
|
|
}
|
|
|
|
identity, err := age.ParseX25519Identity(privateKey.String())
|
|
if err != nil {
|
|
fmt.Printf("Failed to load private key: %s\n", err)
|
|
return nil
|
|
}
|
|
|
|
out := &bytes.Buffer{}
|
|
reader := strings.NewReader(ciphertext.String())
|
|
armorReader := armor.NewReader(reader)
|
|
|
|
r, err := age.Decrypt(armorReader, identity)
|
|
if err != nil {
|
|
fmt.Println("age.Decrypt failed: ", err)
|
|
return nil
|
|
}
|
|
|
|
if _, err := io.Copy(out, r); err != nil {
|
|
fmt.Println("reading encrypted text failed: ", err)
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("File contents: %q\n", out.Bytes())
|
|
|
|
result := make(map[string]interface{})
|
|
result["cleartext"] = out.String()
|
|
return result
|
|
})
|
|
}
|
|
|
|
// Encrypt plain text for one or multiple age receipient keys.
|
|
// Input is an object with the keys:
|
|
// Array: receipients
|
|
// String: cleartext
|
|
// Returns the resulting multiline string.
|
|
func acusEncryptText() js.Func {
|
|
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
// Deferring a recover() func does not work here
|
|
|
|
var recipients []age.Recipient
|
|
|
|
if len(args) != 1 {
|
|
fmt.Println("First arg should be one single object")
|
|
return nil
|
|
}
|
|
|
|
obj := args[0]
|
|
if obj.Type() != js.TypeObject {
|
|
return nil
|
|
}
|
|
|
|
// Recipients
|
|
recs := obj.Get("recipients")
|
|
if recs.Type() != js.TypeObject || recs.Length() == 0 {
|
|
fmt.Println("Something is wrong with 'recipients'. Maybe not an array?");
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < recs.Length(); i++ {
|
|
entry := recs.Index(i) // entry.Type() == "string"
|
|
|
|
// entry.String() breaks in TinyGo, most probably due to the missing
|
|
// implementation of kind "interface" in src/reflect/value.go for "reflect.Value.Elem".
|
|
// Error message: "panic: reflect: call of reflect.Value.Elem on invalid type".
|
|
// https://github.com/tinygo-org/tinygo/blob/138befd8a25d2a5c65e2e740038a8c3a44e95222/src/reflect/value.go#L373
|
|
|
|
r, err := age.ParseX25519Recipient(entry.String())
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil
|
|
}
|
|
recipients = append(recipients, r)
|
|
}
|
|
|
|
fmt.Println(recipients)
|
|
|
|
if len(recipients) == 0 {
|
|
fmt.Println("No recipients to work with..")
|
|
return nil
|
|
}
|
|
|
|
// Cleartext
|
|
text := obj.Get("cleartext")
|
|
if text.IsNull() || text.IsUndefined() {
|
|
fmt.Println("cleartext is not given")
|
|
return nil
|
|
}
|
|
fmt.Println("Cleartext: ", text.String())
|
|
|
|
cleartextStringReader := strings.NewReader(text.String())
|
|
|
|
fmt.Println(cleartextStringReader.Len())
|
|
|
|
out := new(strings.Builder)
|
|
outArmor := armor.NewWriter(out)
|
|
|
|
fmt.Println(out.Len(), out.Cap())
|
|
|
|
encryptedWriter, err := age.Encrypt(outArmor, recipients...)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if _, err := io.Copy(encryptedWriter, cleartextStringReader); err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if err := encryptedWriter.Close(); err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if err := outArmor.Close(); err != nil {
|
|
fmt.Printf("Failed to close armor: %v\n", err)
|
|
}
|
|
|
|
fmt.Println(out.Len(), out.Cap())
|
|
|
|
return out.String()
|
|
})
|
|
}
|
|
|
|
func acusEncryptBytes() js.Func {
|
|
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
var recipients []age.Recipient
|
|
|
|
obj := args[0]
|
|
if obj.Type() != js.TypeObject {
|
|
return nil
|
|
}
|
|
|
|
recs := obj.Get("recipients")
|
|
|
|
for i := 0; i < recs.Length(); i++ {
|
|
entry := recs.Index(i) // entry.Type() == "string"
|
|
r, err := age.ParseX25519Recipient(entry.String())
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil
|
|
}
|
|
recipients = append(recipients, r)
|
|
}
|
|
|
|
if len(recipients) == 0 {
|
|
fmt.Println("No recipients to work with..")
|
|
return nil
|
|
}
|
|
|
|
clearbytes := obj.Get("clearbytes")
|
|
fmt.Println(clearbytes.Type()) // base64 string
|
|
|
|
cb_array, err := base64.StdEncoding.DecodeString(clearbytes.String())
|
|
if err != nil {
|
|
fmt.Printf("Some error occured during base64 decode. Error %s\n", err.Error())
|
|
return nil
|
|
}
|
|
fmt.Printf("Length of cb_array: %+v\n", len(cb_array))
|
|
|
|
clearbytesReader := bytes.NewReader(cb_array)
|
|
|
|
out := new(strings.Builder)
|
|
outArmor := armor.NewWriter(out)
|
|
|
|
encryptedWriter, err := age.Encrypt(outArmor, recipients...)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if _, err := io.Copy(encryptedWriter, clearbytesReader); err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if err := encryptedWriter.Close(); err != nil {
|
|
fmt.Printf("Error: %v", err)
|
|
}
|
|
|
|
if err := outArmor.Close(); err != nil {
|
|
fmt.Printf("Failed to close armor: %v\n", err)
|
|
}
|
|
|
|
return out.String()
|
|
})
|
|
}
|
|
|
|
// Main entry point function for wasm
|
|
func main() {
|
|
// export public functions to JS main "window"
|
|
js.Global().Set("acusGenerateKey", acusGenerateKey())
|
|
js.Global().Set("acusEncryptText", acusEncryptText())
|
|
js.Global().Set("acusEncryptBytes", acusEncryptBytes())
|
|
js.Global().Set("acusDecryptText", acusDecryptText())
|
|
|
|
// keep program running for JS to call functions
|
|
<-make(chan bool)
|
|
}
|