storj/satellite/console/wasm/main.go
Cameron 98fed4bc30 {satellite/console,web/satellite}: get project salt from satellite
Add getSalt to projects api. Add action, GET_SALT, on Store
Projects module to make the api request and return the salt
string everywhere in the web app that generates an access grant.
The Wasm code which is used to create the access grant has been
changed to decode the salt as a base64 encoded string. The names
of the function calls in the changed Wasm code have also been
changed to ensure that access grant creation fails if JS access
grant worker code and Wasm code are not the same version.

https://github.com/storj/storj-private/issues/64

Change-Id: Ia2bc4cbadad84b066ca1882b042a3f0bb13c783a
2022-10-12 19:06:27 +00:00

216 lines
6.3 KiB
Go

//go:build js && wasm
// +build js,wasm
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"crypto/aes"
"crypto/sha256"
"encoding/json"
"syscall/js"
"github.com/zeebo/errs"
"storj.io/common/base58"
console "storj.io/storj/satellite/console/consolewasm"
)
func main() {
js.Global().Set("deriveAndAESEncryptRootKey", deriveAndEncryptRootKey())
js.Global().Set("generateNewAccessGrant", generateAccessGrant())
js.Global().Set("setAPIKeyPermission", setAPIKeyPermission())
js.Global().Set("newPermission", newPermission())
js.Global().Set("restrictGrant", restrictGrant())
<-make(chan bool)
}
// deriveAndEncryptRootKey derives the root key portion of the access grant and then encrypts it using the provided aes
// key. To ensure the key used in encryption is a proper length, we take a sha256 of the provided key and use it instead
// of using the key directly. Then we base58 encode the result and return it to the caller.
func deriveAndEncryptRootKey() js.Func {
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
if len(args) < 3 {
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: encryption passphrase, project ID, and AES Key.", len(args))
}
encryptionPassphrase := args[0].String()
projectSalt := args[1].String()
aesKey := args[2].String()
rootKey, err := console.DeriveRootKey(encryptionPassphrase, projectSalt)
if err != nil {
return nil, err
}
key := sha256.Sum256([]byte(aesKey))
cipher, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}
value := *(rootKey.Raw())
out := make([]byte, len(value))
cipher.Encrypt(out, value[:])
return base58.Encode(out), nil
}))
}
// generateAccessGrant creates a new access grant with the provided api key and encryption passphrase.
func generateAccessGrant() js.Func {
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
if len(args) < 4 {
return nil, errs.New("not enough arguments. Need 4, but only %d supplied. The order of arguments are: satellite Node URL, API key, encryption passphrase, and project ID.", len(args))
}
satelliteNodeURL := args[0].String()
apiKey := args[1].String()
encryptionPassphrase := args[2].String()
projectSalt := args[3].String()
access, err := console.GenAccessGrant(satelliteNodeURL,
apiKey,
encryptionPassphrase,
projectSalt,
)
if err != nil {
return nil, err
}
return access, nil
}))
}
// setAPIKeyPermission creates a new api key with specific permissions.
func setAPIKeyPermission() js.Func {
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
if len(args) < 3 {
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: API key, bucket names, and permission object.", len(args))
}
apiKey := args[0].String()
// convert array of bucket names to go []string type
buckets := args[1]
if ok := buckets.InstanceOf(js.Global().Get("Array")); !ok {
return nil, errs.New("invalid data type. Expect Array, Got %s", buckets.Type().String())
}
bucketNames, err := parseArrayOfStrings(buckets)
if err != nil {
return nil, err
}
// convert js permission to go permission type
permissionJS := args[2]
if permissionJS.Type() != js.TypeObject {
return nil, errs.New("invalid argument type. Expect %s, Got %s", js.TypeObject.String(), permissionJS.Type().String())
}
permission, err := parsePermission(permissionJS)
if err != nil {
return nil, err
}
restrictedKey, err := console.SetPermission(apiKey, bucketNames, permission)
if err != nil {
return nil, err
}
return restrictedKey.Serialize(), nil
}))
}
// restrictGrant restricts an access grant with the permissions and paths and returns a new access grant.
func restrictGrant() js.Func {
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
if len(args) < 3 {
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: access grant, paths, and permission object.", len(args))
}
accessGrant := args[0].String()
// convert array of paths to go []string type
pathsObj := args[1]
if ok := pathsObj.InstanceOf(js.Global().Get("Array")); !ok {
return nil, errs.New("invalid data type. Expect Array, Got %s", pathsObj.Type().String())
}
paths, err := parseArrayOfStrings(pathsObj)
if err != nil {
return nil, err
}
// convert js permission to go permission type
permissionJS := args[2]
if permissionJS.Type() != js.TypeObject {
return nil, errs.New("invalid argument type. Expect %s, Got %s", js.TypeObject.String(), permissionJS.Type().String())
}
permission, err := parsePermission(permissionJS)
if err != nil {
return nil, err
}
return console.RestrictGrant(accessGrant, paths, permission)
}))
}
// newPermission creates a new permission object.
func newPermission() js.Func {
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
p, err := json.Marshal(console.Permission{})
if err != nil {
return nil, err
}
var jsObj map[string]interface{}
if err = json.Unmarshal(p, &jsObj); err != nil {
return nil, err
}
return jsObj, nil
}))
}
func parsePermission(arg js.Value) (console.Permission, error) {
var permission console.Permission
// convert javascript object to a json string
jsJSON := js.Global().Get("JSON")
p := jsJSON.Call("stringify", arg)
if err := json.Unmarshal([]byte(p.String()), &permission); err != nil {
return permission, err
}
return permission, nil
}
func parseArrayOfStrings(arg js.Value) ([]string, error) {
data := make([]string, arg.Length())
for i := 0; i < arg.Length(); i++ {
data[i] = arg.Index(i).String()
}
return data, nil
}
type result struct {
value interface{}
err error
}
func (r result) ToJS() map[string]interface{} {
var errMsg string
if r.err != nil {
errMsg = r.err.Error()
}
return map[string]interface{}{
"value": js.ValueOf(r.value),
"error": errMsg,
}
}
func responseHandler(fn func(this js.Value, args []js.Value) (value interface{}, err error)) func(js.Value, []js.Value) interface{} {
return func(this js.Value, args []js.Value) interface{} {
value, err := fn(this, args)
return result{value, err}.ToJS()
}
}