Modern encryption in web and mail with WebAssembly! https://bitkeks.eu/blog/2021/12/thunderbird-encryption-wasm-webassembly-acus.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

245 lines
5.9 KiB

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)
}