Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

239 lines
8.8 KiB
Swift

import Foundation
import JavaScriptCore
import CryptoKit
import CommonCrypto
/// Implements the `crypto_*` host functions from `BRIDGE_CONTRACT.md`.
///
/// SHA/HMAC/AES-GCM go through CryptoKit. PBKDF2 falls through to
/// CommonCrypto's `CCKeyDerivationPBKDF` because CryptoKit doesn't expose
/// PBKDF2 directly.
public final class CryptoBridge {
public init() {}
public func install(into ctx: JSContext) {
ctx.installBridgeFunction(name: "crypto_random_bytes") { args in
guard let len = args.first?.toNumber()?.intValue, len > 0 else {
return ctx.newUint8Array(Data())
}
var bytes = [UInt8](repeating: 0, count: len)
let status = SecRandomCopyBytes(kSecRandomDefault, len, &bytes)
if status != errSecSuccess {
// Fall back to arc4random which can't fail.
for i in 0..<len { bytes[i] = UInt8.random(in: 0...255) }
}
return ctx.newUint8Array(Data(bytes))
}
ctx.installBridgeFunction(name: "crypto_random_uuid") { _ in
return UUID().uuidString.lowercased()
}
ctx.installBridgeFunction(name: "crypto_hash") { args in
guard args.count >= 2,
let algo = args[0].toString(),
let data = args[1].toData() else {
return NSNull()
}
let out = Self.hash(algo: algo, data: data)
guard let out = out else { return NSNull() }
return ctx.newUint8Array(out)
}
ctx.installBridgeFunction(name: "crypto_hmac") { args in
guard args.count >= 3,
let algo = args[0].toString(),
let key = args[1].toData(),
let data = args[2].toData() else {
return NSNull()
}
let out = Self.hmac(algo: algo, key: key, data: data)
guard let out = out else { return NSNull() }
return ctx.newUint8Array(out)
}
ctx.installBridgeFunction(name: "crypto_pbkdf2") { args in
guard args.count >= 5,
let password = args[0].toData(),
let salt = args[1].toData(),
let iter = args[2].toNumber()?.uint32Value,
let keyLen = args[3].toNumber()?.intValue,
let digest = args[4].toString() else {
return NSNull()
}
let out = Self.pbkdf2(
password: password,
salt: salt,
iterations: iter,
keyLength: keyLen,
digest: digest
)
guard let out = out else { return NSNull() }
return ctx.newUint8Array(out)
}
ctx.installBridgeFunction(name: "crypto_aes_gcm_encrypt") { args in
guard args.count >= 3,
let key = args[0].toData(),
let nonce = args[1].toData(),
let plaintext = args[2].toData() else {
return NSNull()
}
let aad: Data? = args.count >= 4 ? args[3].toData() : nil
guard let sealed = Self.aesGcmEncrypt(key: key, nonce: nonce, plaintext: plaintext, aad: aad) else {
return NSNull()
}
return [
"ciphertext": ctx.newUint8Array(sealed.ciphertext),
"tag": ctx.newUint8Array(sealed.tag),
] as [String: Any]
}
ctx.installBridgeFunction(name: "crypto_aes_gcm_decrypt") { args in
guard args.count >= 4,
let key = args[0].toData(),
let nonce = args[1].toData(),
let ciphertext = args[2].toData(),
let tag = args[3].toData() else {
return NSNull()
}
let aad: Data? = args.count >= 5 ? args[4].toData() : nil
guard let plain = Self.aesGcmDecrypt(key: key, nonce: nonce, ciphertext: ciphertext, tag: tag, aad: aad) else {
return NSNull()
}
return ctx.newUint8Array(plain)
}
}
// MARK: - Hashes
static func hash(algo: String, data: Data) -> Data? {
switch algo.lowercased() {
case "sha256":
return Data(SHA256.hash(data: data))
case "sha512":
return Data(SHA512.hash(data: data))
case "sha1":
return Data(Insecure.SHA1.hash(data: data))
case "md5":
return Data(Insecure.MD5.hash(data: data))
default:
return nil
}
}
static func hmac(algo: String, key: Data, data: Data) -> Data? {
let symmetricKey = SymmetricKey(data: key)
switch algo.lowercased() {
case "sha256":
let mac = HMAC<SHA256>.authenticationCode(for: data, using: symmetricKey)
return Data(mac)
case "sha512":
let mac = HMAC<SHA512>.authenticationCode(for: data, using: symmetricKey)
return Data(mac)
case "sha1":
let mac = HMAC<Insecure.SHA1>.authenticationCode(for: data, using: symmetricKey)
return Data(mac)
default:
return nil
}
}
static func pbkdf2(
password: Data,
salt: Data,
iterations: UInt32,
keyLength: Int,
digest: String
) -> Data? {
let prf: CCPseudoRandomAlgorithm
switch digest.lowercased() {
case "sha256": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
case "sha512": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512)
case "sha1": prf = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1)
default: return nil
}
var derived = Data(count: keyLength)
let status = derived.withUnsafeMutableBytes { (derivedRaw: UnsafeMutableRawBufferPointer) -> Int32 in
guard let derivedBase = derivedRaw.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return Int32(kCCParamError)
}
return password.withUnsafeBytes { (pwRaw: UnsafeRawBufferPointer) -> Int32 in
guard let pwBase = pwRaw.baseAddress?.assumingMemoryBound(to: Int8.self) else {
return Int32(kCCParamError)
}
return salt.withUnsafeBytes { (saltRaw: UnsafeRawBufferPointer) -> Int32 in
guard let saltBase = saltRaw.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return Int32(kCCParamError)
}
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
pwBase, password.count,
saltBase, salt.count,
prf,
iterations,
derivedBase, keyLength
)
}
}
}
if status != 0 { return nil }
return derived
}
// MARK: - AES-GCM
struct SealedBlob {
let ciphertext: Data
let tag: Data
}
static func aesGcmEncrypt(key: Data, nonce: Data, plaintext: Data, aad: Data?) -> SealedBlob? {
guard key.count == 16 || key.count == 32 else { return nil }
guard nonce.count == 12 else { return nil }
let symmetric = SymmetricKey(data: key)
guard let aesNonce = try? AES.GCM.Nonce(data: nonce) else { return nil }
do {
let sealed: AES.GCM.SealedBox
if let aad = aad {
sealed = try AES.GCM.seal(plaintext, using: symmetric, nonce: aesNonce, authenticating: aad)
} else {
sealed = try AES.GCM.seal(plaintext, using: symmetric, nonce: aesNonce)
}
return SealedBlob(ciphertext: sealed.ciphertext, tag: sealed.tag)
} catch {
return nil
}
}
static func aesGcmDecrypt(key: Data, nonce: Data, ciphertext: Data, tag: Data, aad: Data?) -> Data? {
guard key.count == 16 || key.count == 32 else { return nil }
guard nonce.count == 12 else { return nil }
let symmetric = SymmetricKey(data: key)
guard let aesNonce = try? AES.GCM.Nonce(data: nonce),
let sealed = try? AES.GCM.SealedBox(nonce: aesNonce, ciphertext: ciphertext, tag: tag) else {
return nil
}
do {
if let aad = aad {
return try AES.GCM.open(sealed, using: symmetric, authenticating: aad)
} else {
return try AES.GCM.open(sealed, using: symmetric)
}
} catch {
return nil
}
}
}
// MARK: - JSValue numeric helpers used by the bridge
extension JSValue {
/// Returns a Swift NSNumber for numeric JSValues. Nil for non-numbers.
func toNumber() -> NSNumber? {
guard isNumber else { return nil }
return NSNumber(value: toDouble())
}
}