519423cb0d
- landing/app/mira/wiki: tag=mira-wiki list now shows all wiki paragraphs (depends on KBDB tag filter exposed in matrix/kbdb commit, separate repo) - landing: app/mira hub + feed split + various WIP from prior sessions - registry/components: claude_api / kbdb_create_block / kbdb_get / km_writer / platform_crypto / auth_oauth2 contracts + main.go (accumulated) - .component-builds: pkg-lock updates + index.ts adjustments (WIP) - .agents/specs/arcrun/frontend-redesign: design notes - docs/test_credentials, docs/user_requirements/arcrun-landing-page: WIP docs - cypher-executor: auth-dispatcher / wasi-shim adjustments (WIP) Includes accumulated work from prior sessions plus the wiki UI tag-filter update that surfaces the AI-generated wiki paragraphs at /mira/wiki. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
207 lines
5.7 KiB
Go
207 lines
5.7 KiB
Go
// platform_crypto — Arcrun 平台內部 crypto primitive
|
||
//
|
||
// Actions:
|
||
// generate_api_key — HMAC-SHA256(email, ENCRYPTION_KEY) → ak_{hex[:32]}
|
||
// encrypt — AES-GCM(plaintext, ENCRYPTION_KEY) → {encrypted, iv}(base64)
|
||
// random_token — crypto random bytes → hex string
|
||
//
|
||
// ENCRYPTION_KEY 由 host 持有,永不進入 WASM。
|
||
//
|
||
// Host imports:
|
||
// u6u.crypto_hmac_sha256 — HMAC-SHA256(data, key=ENCRYPTION_KEY) → raw bytes
|
||
// u6u.crypto_aes_encrypt — AES-GCM(plaintext, key=ENCRYPTION_KEY) → encrypted_b64 + iv_b64
|
||
// u6u.crypto_random_bytes — crypto-random bytes → hex string
|
||
//
|
||
//go:build tinygo
|
||
|
||
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"os"
|
||
"strings"
|
||
"unsafe"
|
||
)
|
||
|
||
// ── host function 宣告 ───────────────────────────────────────────────────────
|
||
|
||
// crypto_hmac_sha256(dataPtr, dataLen, outPtr, outLenPtr) → 0 成功
|
||
// key = host 的 ENCRYPTION_KEY,output = raw bytes(hex encode 由 WASM 做)
|
||
//
|
||
//go:wasmimport u6u crypto_hmac_sha256
|
||
func hostCryptoHmacSha256(
|
||
dataPtr uintptr, dataLen uint32,
|
||
outPtr uintptr, outLenPtr uintptr,
|
||
) uint32
|
||
|
||
// crypto_aes_encrypt(plaintextPtr, plaintextLen, outEncPtr, outEncLenPtr, outIvPtr, outIvLenPtr) → 0 成功
|
||
// output: encrypted(base64)放 outEnc,iv(base64)放 outIv
|
||
//
|
||
//go:wasmimport u6u crypto_aes_encrypt
|
||
func hostCryptoAesEncrypt(
|
||
plaintextPtr uintptr, plaintextLen uint32,
|
||
outEncPtr uintptr, outEncLenPtr uintptr,
|
||
outIvPtr uintptr, outIvLenPtr uintptr,
|
||
) uint32
|
||
|
||
// crypto_random_bytes(numBytes, outPtr, outLenPtr) → 0 成功
|
||
// output: hex string
|
||
//
|
||
//go:wasmimport u6u crypto_random_bytes
|
||
func hostCryptoRandomBytes(
|
||
numBytes uint32,
|
||
outPtr uintptr, outLenPtr uintptr,
|
||
) uint32
|
||
|
||
// ── 型別 ─────────────────────────────────────────────────────────────────────
|
||
|
||
type Input struct {
|
||
Action string `json:"action"`
|
||
Email string `json:"email,omitempty"`
|
||
Plaintext string `json:"plaintext,omitempty"`
|
||
Bytes int `json:"bytes,omitempty"`
|
||
}
|
||
|
||
// ── main ─────────────────────────────────────────────────────────────────────
|
||
|
||
func main() {
|
||
raw, err := io.ReadAll(os.Stdin)
|
||
if err != nil {
|
||
writeError("failed to read stdin: " + err.Error())
|
||
return
|
||
}
|
||
|
||
var input Input
|
||
if err := json.Unmarshal(raw, &input); err != nil {
|
||
writeError("invalid input JSON: " + err.Error())
|
||
return
|
||
}
|
||
|
||
switch input.Action {
|
||
case "generate_api_key":
|
||
if input.Email == "" {
|
||
writeError("email 必填")
|
||
return
|
||
}
|
||
sig, ok := hmacSha256([]byte(input.Email))
|
||
if !ok {
|
||
writeError("HMAC-SHA256 失敗")
|
||
return
|
||
}
|
||
apiKey := "ak_" + hex(sig)[:32]
|
||
out, _ := json.Marshal(map[string]interface{}{
|
||
"success": true,
|
||
"api_key": apiKey,
|
||
})
|
||
os.Stdout.Write(out)
|
||
|
||
case "encrypt":
|
||
if input.Plaintext == "" {
|
||
writeError("plaintext 必填")
|
||
return
|
||
}
|
||
encB64, ivB64, ok := aesEncrypt([]byte(input.Plaintext))
|
||
if !ok {
|
||
writeError("AES-GCM 加密失敗")
|
||
return
|
||
}
|
||
out, _ := json.Marshal(map[string]interface{}{
|
||
"success": true,
|
||
"encrypted": encB64,
|
||
"iv": ivB64,
|
||
})
|
||
os.Stdout.Write(out)
|
||
|
||
case "random_token":
|
||
n := input.Bytes
|
||
if n <= 0 {
|
||
n = 32
|
||
}
|
||
token, ok := randomBytes(uint32(n))
|
||
if !ok {
|
||
writeError("random bytes 失敗")
|
||
return
|
||
}
|
||
out, _ := json.Marshal(map[string]interface{}{
|
||
"success": true,
|
||
"token": token,
|
||
})
|
||
os.Stdout.Write(out)
|
||
|
||
default:
|
||
writeError("不支援的 action: " + input.Action)
|
||
}
|
||
}
|
||
|
||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||
|
||
func writeError(msg string) {
|
||
out, _ := json.Marshal(map[string]interface{}{
|
||
"success": false,
|
||
"error": msg,
|
||
})
|
||
os.Stdout.Write(out)
|
||
}
|
||
|
||
func hmacSha256(data []byte) ([]byte, bool) {
|
||
if len(data) == 0 {
|
||
return nil, false
|
||
}
|
||
outBuf := make([]byte, 64) // SHA-256 = 32 bytes raw
|
||
var outLen uint32
|
||
status := hostCryptoHmacSha256(
|
||
uintptr(unsafe.Pointer(&data[0])), uint32(len(data)),
|
||
uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
|
||
)
|
||
if status != 0 {
|
||
return nil, false
|
||
}
|
||
return outBuf[:outLen], true
|
||
}
|
||
|
||
func aesEncrypt(plaintext []byte) (string, string, bool) {
|
||
if len(plaintext) == 0 {
|
||
return "", "", false
|
||
}
|
||
encBuf := make([]byte, 65536)
|
||
ivBuf := make([]byte, 64)
|
||
var encLen, ivLen uint32
|
||
status := hostCryptoAesEncrypt(
|
||
uintptr(unsafe.Pointer(&plaintext[0])), uint32(len(plaintext)),
|
||
uintptr(unsafe.Pointer(&encBuf[0])), uintptr(unsafe.Pointer(&encLen)),
|
||
uintptr(unsafe.Pointer(&ivBuf[0])), uintptr(unsafe.Pointer(&ivLen)),
|
||
)
|
||
if status != 0 {
|
||
return "", "", false
|
||
}
|
||
return string(encBuf[:encLen]), string(ivBuf[:ivLen]), true
|
||
}
|
||
|
||
func randomBytes(n uint32) (string, bool) {
|
||
outBuf := make([]byte, n*2+4) // hex = 2 chars per byte
|
||
var outLen uint32
|
||
status := hostCryptoRandomBytes(
|
||
n,
|
||
uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
|
||
)
|
||
if status != 0 {
|
||
return "", false
|
||
}
|
||
return string(outBuf[:outLen]), true
|
||
}
|
||
|
||
// hex encodes raw bytes to lowercase hex string
|
||
func hex(b []byte) string {
|
||
const hexChars = "0123456789abcdef"
|
||
out := make([]byte, len(b)*2)
|
||
for i, v := range b {
|
||
out[i*2] = hexChars[v>>4]
|
||
out[i*2+1] = hexChars[v&0xf]
|
||
}
|
||
return string(out)
|
||
}
|
||
|
||
// strings import 只為了 strings.Builder(interpolate 用,這裡不需要但 import 要保留給未來)
|
||
var _ = strings.Builder{}
|