mirror of
https://github.com/lbr77/SideImpactor.git
synced 2026-05-06 03:04:01 -04:00
more refactor
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "wasm/libcurl-wasm"]
|
||||
path = wasm/libcurl-wasm
|
||||
url = https://github.com/lbr77/libcurl.js
|
||||
[submodule "wasm/openssl-wasm"]
|
||||
path = wasm/openssl-wasm
|
||||
url = https://github.com/jedisct1/openssl-wasm
|
||||
|
||||
29
README.md
29
README.md
@@ -1,30 +1,13 @@
|
||||
# sideload.js
|
||||
# Sideload.js
|
||||
|
||||
This repository is organized as a Bun workspace with four top-level areas:
|
||||
|
||||
- `frontend/`: browser signing UI
|
||||
- `backend/`: Cloudflare Workers Wisp proxy demo
|
||||
- `dependencies/webmuxd/`: the publishable `webmuxd` package consumed by the frontend
|
||||
- `wasm/openssl/`: the Rust/WASM OpenSSL bridge whose build artifacts are copied into `dependencies/webmuxd/lib/openssl-wasm`
|
||||
A pure frontend signing infrastructure.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
## Validate
|
||||
<!-- TODO: deploy to cf flow -->
|
||||
Deploy to Cloudflare worker.
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
bun run lint
|
||||
bun run test
|
||||
cd frontend && bun run build
|
||||
cd backend && bun run check
|
||||
```
|
||||
## Technology
|
||||
|
||||
## Workspace Notes
|
||||
|
||||
- `frontend/src/main.ts` must consume `webmuxd` package exports instead of duplicating usbmux/lockdown/AFC/InstProxy logic.
|
||||
- Changes to device communication, pairing, or TLS behavior belong in `dependencies/webmuxd/src/` first.
|
||||
- `wasm/openssl/pkg` is treated as a build artifact source for the package copy step.
|
||||
1. when signing
|
||||
29
README.zh.md
Normal file
29
README.zh.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Sideload.js
|
||||
|
||||
纯前端ipa签名工具
|
||||
|
||||
## 安装/部署
|
||||
|
||||
|
||||
<!-- TODO: deploy to cf flow -->
|
||||
Deploy to Cloudflare worker.
|
||||
|
||||
|
||||
本地部署(With Dockerfile)
|
||||
|
||||
|
||||
## 功能
|
||||
|
||||
1. apple客户端模拟(by unicorn over wasm)
|
||||
2. Team,证书管理(获取,删除,注册)
|
||||
3. zsign wasm(ipa解包,签名)
|
||||
4. usbmuxd over webusb + lockdownd SSL service握手
|
||||
|
||||
|
||||
|
||||
## 感谢
|
||||
|
||||
1. libimobiledevice
|
||||
2. https://github.com/hack-different/webmuxd
|
||||
3. zsign
|
||||
4. openssl-wasm
|
||||
30
bun.lock
30
bun.lock
@@ -41,10 +41,11 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@lbr77/anisette-js": "0.1.3",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "^0.1.5",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "workspace:*",
|
||||
"altsign.js": "^0.1.2",
|
||||
"fflate": "^0.8.2",
|
||||
"jszip": "^3.10.1",
|
||||
"libcurl.js": "workspace:*",
|
||||
"node-forge": "^1.3.3",
|
||||
"webmuxd": "workspace:*",
|
||||
},
|
||||
@@ -55,6 +56,25 @@
|
||||
"vite": "^7.3.1",
|
||||
},
|
||||
},
|
||||
"wasm/libcurl-wasm": {
|
||||
"name": "libcurl.js",
|
||||
"version": "0.7.4",
|
||||
},
|
||||
"wasm/openssl": {
|
||||
"name": "@lbr77/openssl-wasm",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"wasm/zsign-wasm": {
|
||||
"name": "@lbr77/zsign-wasm-resigner-wrapper",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"jszip": "^3.10.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "~5.9.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.16.0", "", { "dependencies": { "@babel/highlight": "^7.16.0" } }, "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA=="],
|
||||
@@ -295,7 +315,9 @@
|
||||
|
||||
"@lbr77/anisette-js": ["@lbr77/anisette-js@0.1.3", "", { "dependencies": { "@types/node": "^25.3.2" } }, "sha512-QzZQB/XzMV68ykaj6yU2aPGmDrhE0wJEmSfMJu0VQ5zhfhrv7G2zrHJGUum226jZ//lYJeMSrzmDaI/NgFVIgg=="],
|
||||
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": ["@lbr77/zsign-wasm-resigner-wrapper@0.1.5", "", { "dependencies": { "@types/node": "^25.3.2", "jszip": "^3.10.1" } }, "sha512-oVmRgkrSccDXpS9rlBKc0LWQzqcEFiTQod5ZaKIcyIp3rtwROgc1Tqwrr/7oLVQNdq8jI79lbnCoa1J34AaUqw=="],
|
||||
"@lbr77/openssl-wasm": ["@lbr77/openssl-wasm@workspace:wasm/openssl"],
|
||||
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": ["@lbr77/zsign-wasm-resigner-wrapper@workspace:wasm/zsign-wasm"],
|
||||
|
||||
"@mercuryworkshop/wisp-js": ["@mercuryworkshop/wisp-js@0.4.1", "", { "dependencies": { "bufferutil": "^4.0.9", "commander": "^14.0.2", "ipaddr.js": "^2.3.0", "ws": "^8.18.3" }, "bin": { "wisp-js-server": "src/bin/server_cli.mjs" } }, "sha512-104LwiXiuhti/e32gmv0Da0u0yuLFDHX8JawCzleTPWJ5t5qTX4EYi4E7/ucbjBPN9wwVPWHE5g5yGqzl/NzQA=="],
|
||||
|
||||
@@ -923,6 +945,8 @@
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"libcurl.js": ["libcurl.js@workspace:wasm/libcurl-wasm"],
|
||||
|
||||
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
@@ -1411,6 +1435,8 @@
|
||||
|
||||
"acorn-globals/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="],
|
||||
|
||||
"altsign.js/@lbr77/zsign-wasm-resigner-wrapper": ["@lbr77/zsign-wasm-resigner-wrapper@0.1.5", "", { "dependencies": { "@types/node": "^25.3.2", "jszip": "^3.10.1" } }, "sha512-oVmRgkrSccDXpS9rlBKc0LWQzqcEFiTQod5ZaKIcyIp3rtwROgc1Tqwrr/7oLVQNdq8jI79lbnCoa1J34AaUqw=="],
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.0", "", {}, "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="],
|
||||
|
||||
3
dependencies/webmuxd/package.json
vendored
3
dependencies/webmuxd/package.json
vendored
@@ -25,7 +25,8 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && bun run copy:openssl-wasm",
|
||||
"build": "bun run build:openssl-wasm && tsc && bun run copy:openssl-wasm",
|
||||
"build:openssl-wasm": "cd ../../wasm/openssl && bun run build",
|
||||
"copy:openssl-wasm": "bun scripts/copy-openssl-wasm.mjs",
|
||||
"prepare": "bun run build",
|
||||
"prepublishOnly": "bun run test && bun run lint",
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { mkdir, copyFile } from "node:fs/promises"
|
||||
import { cp, mkdir, rm } from "node:fs/promises"
|
||||
import { dirname, resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
||||
const packageDir = resolve(scriptDir, "..")
|
||||
const workspaceRootDir = resolve(packageDir, "../..")
|
||||
const sourceDir = resolve(workspaceRootDir, "wasm/openssl/pkg")
|
||||
const sourceDir = resolve(workspaceRootDir, "wasm/openssl")
|
||||
const targetDir = resolve(packageDir, "lib/openssl-wasm")
|
||||
const directoriesToCopy = ["dist", "binary"]
|
||||
|
||||
const filesToCopy = [
|
||||
"openssl_wasm.js",
|
||||
"openssl_wasm.d.ts",
|
||||
"openssl_wasm_bg.wasm",
|
||||
"openssl_wasm_bg.wasm.d.ts",
|
||||
]
|
||||
|
||||
await rm(targetDir, { recursive: true, force: true })
|
||||
await mkdir(targetDir, { recursive: true })
|
||||
|
||||
for (const fileName of filesToCopy) {
|
||||
await copyFile(resolve(sourceDir, fileName), resolve(targetDir, fileName))
|
||||
for (const directoryName of directoriesToCopy) {
|
||||
await cp(resolve(sourceDir, directoryName), resolve(targetDir, directoryName), {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
19
dependencies/webmuxd/src/browser.ts
vendored
Normal file
19
dependencies/webmuxd/src/browser.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export { CONSOLE_LOGGER, NULL_LOGGER, type Logger } from "./logger"
|
||||
export { WebUsbTransport, type WebUsbTransportOptions } from "./core/webusb-transport"
|
||||
export {
|
||||
DirectUsbMuxClient,
|
||||
LOCKDOWN_PORT,
|
||||
installIpaViaInstProxy,
|
||||
sanitizeIpaFileName,
|
||||
createHostId,
|
||||
createSystemBuid,
|
||||
encodeStoredPairRecord,
|
||||
decodeStoredPairRecord,
|
||||
type PairRecord,
|
||||
type StoredPairRecordPayload,
|
||||
type WebUsbTransportInstance,
|
||||
} from "./core/imobiledevice-client"
|
||||
export {
|
||||
createOpenSslWasmTlsFactory,
|
||||
generatePairRecordWithOpenSslWasm,
|
||||
} from "./core/openssl-wasm-browser"
|
||||
112
dependencies/webmuxd/src/core/openssl-wasm-browser.ts
vendored
Normal file
112
dependencies/webmuxd/src/core/openssl-wasm-browser.ts
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { TlsConnection, TlsConnectionFactory } from "./imobiledevice-client"
|
||||
import type {
|
||||
OpenSslWasmConnectionRequest,
|
||||
OpenSslWasmPairRecordRequest,
|
||||
} from "./openssl-wasm"
|
||||
|
||||
interface OpenSslWasmBrowserModule {
|
||||
default(input?: unknown): Promise<unknown>
|
||||
OpensslClient: new (
|
||||
serverName: string,
|
||||
caCertificatePem: string,
|
||||
certificatePem: string,
|
||||
privateKeyPem: string,
|
||||
) => TlsConnection
|
||||
libimobiledevice_generate_pair_record(
|
||||
devicePublicKey: Uint8Array,
|
||||
hostId: string,
|
||||
systemBuid: string,
|
||||
): string
|
||||
}
|
||||
|
||||
const OPENSSL_WASM_MODULE_URL = new URL(
|
||||
"../openssl-wasm/dist/index.mjs",
|
||||
import.meta.url,
|
||||
)
|
||||
|
||||
let opensslWasmModule: OpenSslWasmBrowserModule | null = null
|
||||
let opensslWasmModulePromise: Promise<OpenSslWasmBrowserModule> | null = null
|
||||
let opensslWasmInitPromise: Promise<void> | null = null
|
||||
|
||||
const toOpenSslWasmModule = (moduleValue: unknown): OpenSslWasmBrowserModule => {
|
||||
if (!moduleValue || typeof moduleValue !== "object") {
|
||||
throw new Error("OpenSSL wasm module did not return an object")
|
||||
}
|
||||
|
||||
const candidate = moduleValue as Record<string, unknown>
|
||||
if (typeof candidate.default !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing its default initializer")
|
||||
}
|
||||
if (typeof candidate.OpensslClient !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing OpensslClient")
|
||||
}
|
||||
if (typeof candidate.libimobiledevice_generate_pair_record !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing pair record generation")
|
||||
}
|
||||
|
||||
return candidate as unknown as OpenSslWasmBrowserModule
|
||||
}
|
||||
|
||||
const loadOpenSslWasmModule = async (): Promise<OpenSslWasmBrowserModule> => {
|
||||
if (!opensslWasmModulePromise) {
|
||||
opensslWasmModulePromise = import(
|
||||
/* @vite-ignore */
|
||||
OPENSSL_WASM_MODULE_URL.href
|
||||
).then((moduleValue) => {
|
||||
const loadedModule = toOpenSslWasmModule(moduleValue)
|
||||
opensslWasmModule = loadedModule
|
||||
return loadedModule
|
||||
})
|
||||
}
|
||||
|
||||
return await opensslWasmModulePromise
|
||||
}
|
||||
|
||||
const requireOpenSslWasmModule = (): OpenSslWasmBrowserModule => {
|
||||
if (!opensslWasmModule) {
|
||||
throw new Error("OpenSSL wasm is not ready. Call ensureOpenSslWasmReady() first.")
|
||||
}
|
||||
|
||||
return opensslWasmModule
|
||||
}
|
||||
|
||||
export const ensureOpenSslWasmReady = async (): Promise<void> => {
|
||||
if (!opensslWasmInitPromise) {
|
||||
opensslWasmInitPromise = loadOpenSslWasmModule().then(async (moduleValue) => {
|
||||
await moduleValue.default()
|
||||
})
|
||||
}
|
||||
|
||||
await opensslWasmInitPromise
|
||||
}
|
||||
|
||||
export const createOpenSslWasmConnection = (
|
||||
request: OpenSslWasmConnectionRequest,
|
||||
): TlsConnection => {
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return new moduleValue.OpensslClient(
|
||||
request.serverName,
|
||||
request.caCertificatePem,
|
||||
request.certificatePem,
|
||||
request.privateKeyPem,
|
||||
)
|
||||
}
|
||||
|
||||
export const createOpenSslWasmTlsFactory = (): TlsConnectionFactory => {
|
||||
return {
|
||||
ensureReady: ensureOpenSslWasmReady,
|
||||
createConnection: createOpenSslWasmConnection,
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePairRecordWithOpenSslWasm = async (
|
||||
request: OpenSslWasmPairRecordRequest,
|
||||
): Promise<string> => {
|
||||
await ensureOpenSslWasmReady()
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return moduleValue.libimobiledevice_generate_pair_record(
|
||||
new Uint8Array(request.devicePublicKey),
|
||||
request.hostId,
|
||||
request.systemBuid,
|
||||
)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ interface OpenSslWasmModule {
|
||||
): string
|
||||
}
|
||||
|
||||
const OPENSSL_WASM_MODULE_SPECIFIER = "../openssl-wasm/openssl_wasm.js"
|
||||
const OPENSSL_WASM_MODULE_SPECIFIER = "../openssl-wasm/dist/index.mjs"
|
||||
|
||||
/**
|
||||
* Keep native `import()` intact in the CommonJS build so bundlers can defer the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CONSOLE_LOGGER, Logger, NULL_LOGGER } from "../webmuxd"
|
||||
import { CONSOLE_LOGGER, Logger, NULL_LOGGER } from "../logger"
|
||||
import { DataHandler, DisconnectHandler, UsbMuxTransport } from "./transport"
|
||||
|
||||
const USBMUX_CLASS = 255
|
||||
|
||||
38
dependencies/webmuxd/src/logger.ts
vendored
Normal file
38
dependencies/webmuxd/src/logger.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
type LogLevel = "debug" | "info" | "warn" | "error"
|
||||
|
||||
export interface Logger {
|
||||
log(level: LogLevel, message: string): void
|
||||
}
|
||||
|
||||
export const NULL_LOGGER = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
log: (level: LogLevel, message: string): void => {
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
export const CONSOLE_LOGGER = {
|
||||
log: (level: LogLevel, message: string): void => {
|
||||
switch (level) {
|
||||
case "info":
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message)
|
||||
break
|
||||
case "warn":
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message)
|
||||
break
|
||||
case "error":
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message)
|
||||
break
|
||||
case "debug":
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message)
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Unknown log level ${level}: ${message}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
2
dependencies/webmuxd/src/openssl-wasm/dist/index.mjs
vendored
Normal file
2
dependencies/webmuxd/src/openssl-wasm/dist/index.mjs
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "../../../../../wasm/openssl/dist/index.mjs"
|
||||
export * from "../../../../../wasm/openssl/dist/index.mjs"
|
||||
40
dependencies/webmuxd/src/webmuxd.ts
vendored
40
dependencies/webmuxd/src/webmuxd.ts
vendored
@@ -1,47 +1,11 @@
|
||||
import { NULL_LOGGER, type Logger } from "./logger"
|
||||
|
||||
const USBMUX_USB_FILTER = [{ vendorId: 0x5ac, productId: 0x12a8 }];
|
||||
const USBMUX_CLASS = 255;
|
||||
const USBMUX_SUBCLASS = 254;
|
||||
const USBMUX_PROTOCOL = 2;
|
||||
|
||||
type LogLevel = "debug" | "info" | "warn" | "error"
|
||||
|
||||
export interface Logger {
|
||||
log(level: LogLevel, message: string): void
|
||||
}
|
||||
|
||||
export const NULL_LOGGER = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
log: (level: LogLevel, message: string): void => {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export const CONSOLE_LOGGER = {
|
||||
log: (level: LogLevel, message: string): void => {
|
||||
switch (level) {
|
||||
case "info":
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message)
|
||||
break
|
||||
case "warn":
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message)
|
||||
break
|
||||
case "error":
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message)
|
||||
break
|
||||
case "debug":
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message)
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Unknown log level ${level}: ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
export { CONSOLE_LOGGER, NULL_LOGGER, type Logger } from "./logger"
|
||||
|
||||
export default class MobileDevice {
|
||||
static logger: Logger = NULL_LOGGER
|
||||
|
||||
8
dependencies/webmuxd/tsconfig.json
vendored
8
dependencies/webmuxd/tsconfig.json
vendored
@@ -9,5 +9,11 @@
|
||||
"types": ["node", "w3c-web-usb"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/__tests__/*",
|
||||
"src/browser.ts",
|
||||
"src/core/openssl-wasm-browser.ts",
|
||||
"src/openssl-wasm/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@lbr77/anisette-js": "0.1.3",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "^0.1.5",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "workspace:*",
|
||||
"altsign.js": "^0.1.2",
|
||||
"fflate": "^0.8.2",
|
||||
"jszip": "^3.10.1",
|
||||
"libcurl.js": "workspace:*",
|
||||
"node-forge": "^1.3.3",
|
||||
"webmuxd": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,9 +1,11 @@
|
||||
import type { HttpClient } from "@lbr77/anisette-js"
|
||||
import { initLibcurl, libcurl } from "./anisette-libcurl-init"
|
||||
import { initLibcurl } from "./anisette-libcurl-init"
|
||||
import { requireLibcurl } from "./wasm/libcurl"
|
||||
|
||||
export class LibcurlHttpClient implements HttpClient {
|
||||
async get(url: string, headers: Record<string, string>): Promise<Uint8Array> {
|
||||
await initLibcurl()
|
||||
const libcurl = requireLibcurl()
|
||||
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: "GET",
|
||||
@@ -21,6 +23,7 @@ export class LibcurlHttpClient implements HttpClient {
|
||||
|
||||
async post(url: string, body: string, headers: Record<string, string>): Promise<Uint8Array> {
|
||||
await initLibcurl()
|
||||
const libcurl = requireLibcurl()
|
||||
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @ts-ignore
|
||||
import { libcurl } from "../public/anisette/libcurl_full.mjs"
|
||||
import { loadLibcurl, libcurl } from "./wasm/libcurl"
|
||||
|
||||
let initialized = false
|
||||
let initPromise: Promise<void> | null = null
|
||||
@@ -13,10 +12,11 @@ export async function initLibcurl(): Promise<void> {
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
const loadedLibcurl = await loadLibcurl()
|
||||
const wsProto = location.protocol === "https:" ? "wss:" : "ws:"
|
||||
const wsUrl = `${wsProto}//${location.host}/wisp/`
|
||||
libcurl.set_websocket(wsUrl)
|
||||
await libcurl.load_wasm()
|
||||
loadedLibcurl.set_websocket(wsUrl)
|
||||
await loadedLibcurl.load_wasm()
|
||||
initialized = true
|
||||
})()
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { strFromU8, unzipSync } from "fflate"
|
||||
import {
|
||||
type AnisetteData,
|
||||
type AppleAPI,
|
||||
type AppID,
|
||||
type Certificate,
|
||||
type Device,
|
||||
type Team,
|
||||
import type {
|
||||
AnisetteData,
|
||||
AppleAPI,
|
||||
AppID,
|
||||
Certificate,
|
||||
Device,
|
||||
Team,
|
||||
} from "altsign.js"
|
||||
import { initLibcurl, libcurl } from "./anisette-libcurl-init"
|
||||
import { initLibcurl } from "./anisette-libcurl-init"
|
||||
import { requireLibcurl } from "./wasm/libcurl"
|
||||
|
||||
const SIGNING_IDENTITY_STORAGE_KEY = "webmuxd:signing-identities"
|
||||
const PRIMARY_APP_INFO_PLIST_RE = /^Payload\/[^/]+\.app\/Info\.plist$/
|
||||
@@ -129,6 +130,7 @@ async function getAppleApi(): Promise<AppleAPI> {
|
||||
}
|
||||
const { AppleAPI, Fetch } = await loadAltsignModule()
|
||||
const appleFetch = new Fetch(initLibcurl, async (url, options) => {
|
||||
const libcurl = requireLibcurl()
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export function setupCounter(element: HTMLButtonElement) {
|
||||
let counter = 0
|
||||
const setCounter = (count: number) => {
|
||||
counter = count
|
||||
element.innerHTML = `count is ${counter}`
|
||||
}
|
||||
element.addEventListener('click', () => setCounter(counter + 1))
|
||||
setCounter(0)
|
||||
}
|
||||
@@ -1,121 +1,35 @@
|
||||
import "./style.css"
|
||||
import * as webmuxdModule from "webmuxd"
|
||||
import {
|
||||
initAnisette,
|
||||
getAnisetteData,
|
||||
provisionAnisette,
|
||||
type AnisetteData,
|
||||
} from "./anisette-service"
|
||||
import {
|
||||
loginAppleDeveloperAccount,
|
||||
refreshAppleDeveloperContext,
|
||||
signIpaWithAppleContext,
|
||||
type AppleDeveloperContext,
|
||||
} from "./apple-signing"
|
||||
DirectUsbMuxClient,
|
||||
LOCKDOWN_PORT,
|
||||
WebUsbTransport,
|
||||
createHostId,
|
||||
createOpenSslWasmTlsFactory,
|
||||
createSystemBuid,
|
||||
decodeStoredPairRecord,
|
||||
encodeStoredPairRecord,
|
||||
generatePairRecordWithOpenSslWasm,
|
||||
installIpaViaInstProxy,
|
||||
sanitizeIpaFileName,
|
||||
type PairRecord,
|
||||
type StoredPairRecordPayload,
|
||||
} from "webmuxd"
|
||||
import type { AnisetteData } from "./anisette-service"
|
||||
import type { AppleDeveloperContext } from "./apple-signing"
|
||||
|
||||
interface WebUsbTransportInstance {
|
||||
readonly isOpen: boolean
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
type WasmPairRecordPayload = Pick<
|
||||
PairRecord,
|
||||
| "hostId"
|
||||
| "systemBuid"
|
||||
| "hostCertificatePem"
|
||||
| "hostPrivateKeyPem"
|
||||
| "rootCertificatePem"
|
||||
| "rootPrivateKeyPem"
|
||||
| "deviceCertificatePem"
|
||||
>
|
||||
|
||||
interface WebUsbTransportCtor {
|
||||
supported(): boolean
|
||||
requestAppleDevice(): Promise<WebUsbTransportInstance>
|
||||
}
|
||||
|
||||
interface PairRecord {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: Uint8Array
|
||||
escrowBag?: Uint8Array
|
||||
}
|
||||
|
||||
interface StoredPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: string
|
||||
escrowBag: string | null
|
||||
}
|
||||
|
||||
interface StartSessionResult {
|
||||
sessionId: string
|
||||
enableSessionSsl: boolean
|
||||
}
|
||||
|
||||
interface DirectUsbMuxClient {
|
||||
readonly isHandshakeComplete: boolean
|
||||
readonly isLockdownConnected: boolean
|
||||
readonly isSessionStarted: boolean
|
||||
readonly isSessionSslEnabled: boolean
|
||||
readonly isTlsActive: boolean
|
||||
readonly isPaired: boolean
|
||||
loadPairRecord(record: PairRecord | null): void
|
||||
openAndHandshake(): Promise<void>
|
||||
connectLockdown(port?: number): Promise<void>
|
||||
getOrFetchDeviceUdid(): Promise<string>
|
||||
getOrFetchDeviceName(): Promise<string | null>
|
||||
pairDevice(hostId: string, systemBuid: string): Promise<PairRecord>
|
||||
startSession(hostId: string, systemBuid: string): Promise<StartSessionResult>
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
interface DirectUsbMuxClientCtor {
|
||||
new (
|
||||
transport: WebUsbTransportInstance,
|
||||
options?: {
|
||||
log?: (message: string) => void
|
||||
onStateChange?: () => void
|
||||
lockdownLabel?: string
|
||||
tlsFactory?: {
|
||||
ensureReady?: () => Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
}
|
||||
pairRecordFactory?: {
|
||||
createPairRecord(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<PairRecord>
|
||||
}
|
||||
},
|
||||
): DirectUsbMuxClient
|
||||
}
|
||||
|
||||
interface WasmPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
}
|
||||
type AnisetteServiceModule = typeof import("./anisette-service")
|
||||
type AppleSigningModule = typeof import("./apple-signing")
|
||||
|
||||
interface PairedDeviceInfo {
|
||||
udid: string
|
||||
@@ -162,8 +76,6 @@ interface StoredAccountSessionPayload {
|
||||
|
||||
type AppPage = "login" | "sign"
|
||||
|
||||
const LOCKDOWN_PORT = 62078
|
||||
|
||||
const HOST_ID_STORAGE_KEY = "webmuxd:host-id"
|
||||
const SYSTEM_BUID_STORAGE_KEY = "webmuxd:system-buid"
|
||||
const PAIR_RECORDS_STORAGE_KEY = "webmuxd:pair-records-by-udid"
|
||||
@@ -177,67 +89,24 @@ const SELECTED_DEVICE_UDID_STORAGE_KEY = "webmuxd:selected-device-udid"
|
||||
const LOGIN_PAGE_HASH = "#/login"
|
||||
const SIGN_PAGE_HASH = "#/sign"
|
||||
|
||||
const webmuxdModuleValue = webmuxdModule as unknown as Record<string, unknown>
|
||||
let anisetteServicePromise: Promise<AnisetteServiceModule> | null = null
|
||||
let appleSigningModulePromise: Promise<AppleSigningModule> | null = null
|
||||
|
||||
const WebUsbTransport = resolveWebmuxdExport<WebUsbTransportCtor>(
|
||||
webmuxdModuleValue,
|
||||
"WebUsbTransport",
|
||||
)
|
||||
const WebmuxdDirectUsbMuxClient = resolveWebmuxdExport<DirectUsbMuxClientCtor>(
|
||||
webmuxdModuleValue,
|
||||
"DirectUsbMuxClient",
|
||||
)
|
||||
const webmuxdInstallIpaViaInstProxy = resolveWebmuxdExport<
|
||||
(
|
||||
client: DirectUsbMuxClient,
|
||||
ipaData: Uint8Array,
|
||||
fileName: string,
|
||||
onLog?: (message: string) => void,
|
||||
) => Promise<void>
|
||||
>(webmuxdModuleValue, "installIpaViaInstProxy")
|
||||
const webmuxdSanitizeIpaFileName = resolveWebmuxdExport<(fileName: string) => string>(
|
||||
webmuxdModuleValue,
|
||||
"sanitizeIpaFileName",
|
||||
)
|
||||
const webmuxdCreateHostId = resolveWebmuxdExport<() => string>(
|
||||
webmuxdModuleValue,
|
||||
"createHostId",
|
||||
)
|
||||
const webmuxdCreateSystemBuid = resolveWebmuxdExport<() => string>(
|
||||
webmuxdModuleValue,
|
||||
"createSystemBuid",
|
||||
)
|
||||
const webmuxdEncodeStoredPairRecord = resolveWebmuxdExport<
|
||||
(record: PairRecord) => StoredPairRecordPayload
|
||||
>(webmuxdModuleValue, "encodeStoredPairRecord")
|
||||
const webmuxdDecodeStoredPairRecord = resolveWebmuxdExport<
|
||||
(payload: StoredPairRecordPayload) => PairRecord | null
|
||||
>(webmuxdModuleValue, "decodeStoredPairRecord")
|
||||
const webmuxdCreateOpenSslWasmTlsFactory = resolveWebmuxdExport<
|
||||
() => {
|
||||
ensureReady?(): Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
const loadAnisetteService = async (): Promise<AnisetteServiceModule> => {
|
||||
if (!anisetteServicePromise) {
|
||||
anisetteServicePromise = import("./anisette-service")
|
||||
}
|
||||
>(webmuxdModuleValue, "createOpenSslWasmTlsFactory")
|
||||
const webmuxdGeneratePairRecordWithOpenSslWasm = resolveWebmuxdExport<
|
||||
(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}) => Promise<string>
|
||||
>(webmuxdModuleValue, "generatePairRecordWithOpenSslWasm")
|
||||
|
||||
return await anisetteServicePromise
|
||||
}
|
||||
|
||||
const loadAppleSigningModule = async (): Promise<AppleSigningModule> => {
|
||||
if (!appleSigningModulePromise) {
|
||||
appleSigningModulePromise = import("./apple-signing")
|
||||
}
|
||||
|
||||
return await appleSigningModulePromise
|
||||
}
|
||||
|
||||
const app = document.querySelector<HTMLDivElement>("#app")
|
||||
if (!app) {
|
||||
@@ -586,11 +455,11 @@ const ensureClientSelected = async (): Promise<DirectUsbMuxClient> => {
|
||||
}
|
||||
|
||||
const transport = await WebUsbTransport.requestAppleDevice()
|
||||
directClient = new WebmuxdDirectUsbMuxClient(transport, {
|
||||
directClient = new DirectUsbMuxClient(transport, {
|
||||
log: addLog,
|
||||
onStateChange: refreshUi,
|
||||
lockdownLabel: "webmuxd.frontend",
|
||||
tlsFactory: webmuxdCreateOpenSslWasmTlsFactory(),
|
||||
tlsFactory: createOpenSslWasmTlsFactory(),
|
||||
pairRecordFactory: {
|
||||
createPairRecord: async (request) => {
|
||||
return await createPairRecord(request.devicePublicKey, request.hostId, request.systemBuid)
|
||||
@@ -681,19 +550,20 @@ const ensureAnisetteData = async (): Promise<AnisetteData> => {
|
||||
return anisetteData
|
||||
}
|
||||
|
||||
const anisette = await initAnisette()
|
||||
const anisetteService = await loadAnisetteService()
|
||||
const anisette = await anisetteService.initAnisette()
|
||||
const alreadyProvisioned = anisette.isProvisioned
|
||||
anisetteProvisioned = alreadyProvisioned
|
||||
if (alreadyProvisioned) {
|
||||
addLog("login: anisette already provisioned")
|
||||
} else {
|
||||
addLog("login: preparing anisette environment...")
|
||||
await provisionAnisette()
|
||||
await anisetteService.provisionAnisette()
|
||||
anisetteProvisioned = true
|
||||
addLog("login: anisette provisioned")
|
||||
}
|
||||
|
||||
anisetteData = await getAnisetteData()
|
||||
anisetteData = await anisetteService.getAnisetteData()
|
||||
addLog(`login: anisette ready (${shortToken(anisetteData.machineID)})`)
|
||||
refreshUi()
|
||||
return anisetteData
|
||||
@@ -701,7 +571,8 @@ const ensureAnisetteData = async (): Promise<AnisetteData> => {
|
||||
|
||||
const syncAnisetteProvisionedStatus = async (): Promise<void> => {
|
||||
try {
|
||||
const anisette = await initAnisette()
|
||||
const anisetteService = await loadAnisetteService()
|
||||
const anisette = await anisetteService.initAnisette()
|
||||
anisetteProvisioned = anisette.isProvisioned
|
||||
refreshUi()
|
||||
} catch (error) {
|
||||
@@ -727,7 +598,8 @@ const loginAndSignFlow = async (): Promise<void> => {
|
||||
|
||||
const anisette = await ensureAnisetteData()
|
||||
addLog("login: authenticating Apple account...")
|
||||
const context = await loginAppleDeveloperAccount({
|
||||
const appleSigning = await loadAppleSigningModule()
|
||||
const context = await appleSigning.loginAppleDeveloperAccount({
|
||||
anisetteData: anisette,
|
||||
credentials: { appleId, password },
|
||||
onLog: addLog,
|
||||
@@ -736,7 +608,7 @@ const loginAndSignFlow = async (): Promise<void> => {
|
||||
},
|
||||
})
|
||||
|
||||
loginContext = await refreshAppleDeveloperContext(context, addLog)
|
||||
loginContext = await appleSigning.refreshAppleDeveloperContext(context, addLog)
|
||||
accountContextMap.set(accountKey(loginContext.appleId, loginContext.team.identifier), loginContext)
|
||||
persistAccountSummary(loginContext)
|
||||
if (rememberSessionInput.checked) {
|
||||
@@ -781,7 +653,8 @@ const signSelectedIpa = async (): Promise<File> => {
|
||||
},
|
||||
}
|
||||
|
||||
const refreshed = await refreshAppleDeveloperContext(loginContext, addLog)
|
||||
const appleSigning = await loadAppleSigningModule()
|
||||
const refreshed = await appleSigning.refreshAppleDeveloperContext(loginContext, addLog)
|
||||
loginContext = refreshed
|
||||
accountContextMap.set(accountKey(refreshed.appleId, refreshed.team.identifier), refreshed)
|
||||
persistAccountSummary(refreshed)
|
||||
@@ -790,7 +663,7 @@ const signSelectedIpa = async (): Promise<File> => {
|
||||
}
|
||||
|
||||
addLog("sign: preparing ipa...")
|
||||
const result = await signIpaWithAppleContext({
|
||||
const result = await appleSigning.signIpaWithAppleContext({
|
||||
ipaFile: selectedIpaFile,
|
||||
context: refreshed,
|
||||
deviceUdid: targetUdid,
|
||||
@@ -868,8 +741,8 @@ const installFlow = async (): Promise<void> => {
|
||||
|
||||
addLog("install: uploading and installing...")
|
||||
const bytes = new Uint8Array(await upload.arrayBuffer())
|
||||
const safeName = webmuxdSanitizeIpaFileName(upload.name)
|
||||
await webmuxdInstallIpaViaInstProxy(client, bytes, safeName, addLog)
|
||||
const safeName = sanitizeIpaFileName(upload.name)
|
||||
await installIpaViaInstProxy(client, bytes, safeName, addLog)
|
||||
addLog("install: complete")
|
||||
setInstallProgress(100, "complete")
|
||||
} catch (error) {
|
||||
@@ -1405,7 +1278,7 @@ function getOrCreateHostId(): string {
|
||||
if (existing && existing.trim().length > 0) {
|
||||
return existing
|
||||
}
|
||||
const created = webmuxdCreateHostId()
|
||||
const created = createHostId()
|
||||
saveText(HOST_ID_STORAGE_KEY, created)
|
||||
return created
|
||||
}
|
||||
@@ -1415,7 +1288,7 @@ function getOrCreateSystemBuid(): string {
|
||||
if (existing && existing.trim().length > 0) {
|
||||
return existing
|
||||
}
|
||||
const created = webmuxdCreateSystemBuid()
|
||||
const created = createSystemBuid()
|
||||
saveText(SYSTEM_BUID_STORAGE_KEY, created)
|
||||
return created
|
||||
}
|
||||
@@ -1446,7 +1319,7 @@ function savePairRecordForUdid(udid: string, record: PairRecord): void {
|
||||
return
|
||||
}
|
||||
const map = readPairRecordMap()
|
||||
map[normalizedUdid] = webmuxdEncodeStoredPairRecord(record)
|
||||
map[normalizedUdid] = encodeStoredPairRecord(record)
|
||||
writePairRecordMap(map)
|
||||
}
|
||||
|
||||
@@ -1457,7 +1330,7 @@ function loadLegacyPairRecord(): PairRecord | null {
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(text) as StoredPairRecordPayload
|
||||
return webmuxdDecodeStoredPairRecord(parsed)
|
||||
return decodeStoredPairRecord(parsed)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -1473,7 +1346,7 @@ function loadPairRecordForUdid(udid: string): PairRecord | null {
|
||||
const fromMap = map[normalizedUdid]
|
||||
if (fromMap) {
|
||||
try {
|
||||
return webmuxdDecodeStoredPairRecord(fromMap)
|
||||
return decodeStoredPairRecord(fromMap)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -1492,7 +1365,7 @@ async function createPairRecord(
|
||||
hostId: string,
|
||||
systemBuid: string,
|
||||
): Promise<PairRecord> {
|
||||
const payloadText = await webmuxdGeneratePairRecordWithOpenSslWasm({
|
||||
const payloadText = await generatePairRecordWithOpenSslWasm({
|
||||
devicePublicKey: devicePublicKeyBytes,
|
||||
hostId,
|
||||
systemBuid,
|
||||
@@ -1763,20 +1636,3 @@ function formatError(error: unknown): string {
|
||||
}
|
||||
return String(error)
|
||||
}
|
||||
|
||||
function resolveWebmuxdExport<T>(moduleValue: Record<string, unknown>, key: string): T {
|
||||
const direct = moduleValue[key]
|
||||
if (direct !== undefined) {
|
||||
return direct as T
|
||||
}
|
||||
|
||||
const defaultValue = moduleValue.default
|
||||
if (defaultValue && typeof defaultValue === "object") {
|
||||
const fromDefault = (defaultValue as Record<string, unknown>)[key]
|
||||
if (fromDefault !== undefined) {
|
||||
return fromDefault as T
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`webmuxd export ${key} is unavailable`)
|
||||
}
|
||||
|
||||
2
frontend/src/node-forge.d.ts
vendored
2
frontend/src/node-forge.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
declare module "node-forge"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
6
frontend/src/wasm/libcurl-entry.d.ts
vendored
Normal file
6
frontend/src/wasm/libcurl-entry.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export const libcurl: {
|
||||
fetch(input: string | URL, init?: Record<string, unknown>): Promise<Response>
|
||||
load_wasm(url?: string): Promise<void>
|
||||
set_websocket(url: string): void
|
||||
readonly ready?: boolean
|
||||
}
|
||||
1
frontend/src/wasm/libcurl-entry.js
Normal file
1
frontend/src/wasm/libcurl-entry.js
Normal file
@@ -0,0 +1 @@
|
||||
export { libcurl } from "libcurl.js/bundled"
|
||||
53
frontend/src/wasm/libcurl.ts
Normal file
53
frontend/src/wasm/libcurl.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export interface LibcurlApi {
|
||||
fetch(input: string | URL, init?: Record<string, unknown>): Promise<Response>
|
||||
load_wasm(url?: string): Promise<void>
|
||||
set_websocket(url: string): void
|
||||
readonly ready?: boolean
|
||||
}
|
||||
|
||||
let libcurlModulePromise: Promise<LibcurlApi> | null = null
|
||||
export let libcurl: LibcurlApi
|
||||
|
||||
const toLibcurl = (moduleValue: unknown): LibcurlApi => {
|
||||
if (!moduleValue || typeof moduleValue !== "object") {
|
||||
throw new Error("libcurl module did not return an object")
|
||||
}
|
||||
|
||||
const candidate = moduleValue as Record<string, unknown>
|
||||
if (!candidate.libcurl || typeof candidate.libcurl !== "object") {
|
||||
throw new Error("libcurl module is missing the libcurl export")
|
||||
}
|
||||
|
||||
const loadedLibcurl = candidate.libcurl as Partial<LibcurlApi>
|
||||
if (typeof loadedLibcurl.fetch !== "function") {
|
||||
throw new Error("libcurl export is missing fetch")
|
||||
}
|
||||
if (typeof loadedLibcurl.load_wasm !== "function") {
|
||||
throw new Error("libcurl export is missing load_wasm")
|
||||
}
|
||||
if (typeof loadedLibcurl.set_websocket !== "function") {
|
||||
throw new Error("libcurl export is missing set_websocket")
|
||||
}
|
||||
|
||||
return loadedLibcurl as LibcurlApi
|
||||
}
|
||||
|
||||
export const loadLibcurl = async (): Promise<LibcurlApi> => {
|
||||
if (!libcurlModulePromise) {
|
||||
libcurlModulePromise = import("./libcurl-entry.js").then((moduleValue) => {
|
||||
const loadedLibcurl = toLibcurl(moduleValue)
|
||||
libcurl = loadedLibcurl
|
||||
return loadedLibcurl
|
||||
})
|
||||
}
|
||||
|
||||
return await libcurlModulePromise
|
||||
}
|
||||
|
||||
export const requireLibcurl = (): LibcurlApi => {
|
||||
if (!libcurl) {
|
||||
throw new Error("libcurl is not ready. Call initLibcurl() first.")
|
||||
}
|
||||
|
||||
return libcurl
|
||||
}
|
||||
75
frontend/src/wasm/openssl-webmuxd.js
Normal file
75
frontend/src/wasm/openssl-webmuxd.js
Normal file
@@ -0,0 +1,75 @@
|
||||
let opensslWasmModule = null
|
||||
let opensslWasmModulePromise = null
|
||||
let opensslWasmInitPromise = null
|
||||
|
||||
const loadOpenSslWasmModule = async () => {
|
||||
if (!opensslWasmModulePromise) {
|
||||
opensslWasmModulePromise = import("../../../wasm/openssl/dist/index.mjs").then(
|
||||
(moduleValue) => {
|
||||
if (!moduleValue || typeof moduleValue !== "object") {
|
||||
throw new Error("OpenSSL wasm module did not return an object")
|
||||
}
|
||||
const candidate = moduleValue
|
||||
if (typeof candidate.default !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing its default initializer")
|
||||
}
|
||||
if (typeof candidate.OpensslClient !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing OpensslClient")
|
||||
}
|
||||
if (typeof candidate.libimobiledevice_generate_pair_record !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing pair record generation")
|
||||
}
|
||||
|
||||
opensslWasmModule = candidate
|
||||
return candidate
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return await opensslWasmModulePromise
|
||||
}
|
||||
|
||||
const requireOpenSslWasmModule = () => {
|
||||
if (!opensslWasmModule) {
|
||||
throw new Error("OpenSSL wasm is not ready. Call ensureOpenSslWasmReady() first.")
|
||||
}
|
||||
|
||||
return opensslWasmModule
|
||||
}
|
||||
|
||||
export const ensureOpenSslWasmReady = async () => {
|
||||
if (!opensslWasmInitPromise) {
|
||||
opensslWasmInitPromise = loadOpenSslWasmModule().then(async (moduleValue) => {
|
||||
await moduleValue.default()
|
||||
})
|
||||
}
|
||||
|
||||
await opensslWasmInitPromise
|
||||
}
|
||||
|
||||
export const createOpenSslWasmConnection = (request) => {
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return new moduleValue.OpensslClient(
|
||||
request.serverName,
|
||||
request.caCertificatePem,
|
||||
request.certificatePem,
|
||||
request.privateKeyPem,
|
||||
)
|
||||
}
|
||||
|
||||
export const createOpenSslWasmTlsFactory = () => {
|
||||
return {
|
||||
ensureReady: ensureOpenSslWasmReady,
|
||||
createConnection: createOpenSslWasmConnection,
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePairRecordWithOpenSslWasm = async (request) => {
|
||||
await ensureOpenSslWasmReady()
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return moduleValue.libimobiledevice_generate_pair_record(
|
||||
new Uint8Array(request.devicePublicKey),
|
||||
request.hostId,
|
||||
request.systemBuid,
|
||||
)
|
||||
}
|
||||
120
frontend/src/webmuxd-browser.d.ts
vendored
Normal file
120
frontend/src/webmuxd-browser.d.ts
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
declare module "webmuxd" {
|
||||
export interface WebUsbTransportInstance {
|
||||
readonly isOpen: boolean
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
|
||||
export interface PairRecord {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: Uint8Array
|
||||
escrowBag?: Uint8Array
|
||||
}
|
||||
|
||||
export interface StoredPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: string
|
||||
escrowBag: string | null
|
||||
}
|
||||
|
||||
export interface TlsConnection {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
|
||||
export interface TlsConnectionFactory {
|
||||
ensureReady?(): Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): TlsConnection
|
||||
}
|
||||
|
||||
export class WebUsbTransport implements WebUsbTransportInstance {
|
||||
constructor(device: unknown, options?: { logger?: unknown; transferSize?: number })
|
||||
readonly isOpen: boolean
|
||||
static supported(): boolean
|
||||
static requestAppleDevice(logger?: unknown): Promise<WebUsbTransport>
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
|
||||
export class DirectUsbMuxClient {
|
||||
constructor(
|
||||
transport: WebUsbTransportInstance,
|
||||
options?: {
|
||||
log?: (message: string) => void
|
||||
onStateChange?: () => void
|
||||
lockdownLabel?: string
|
||||
tlsFactory?: TlsConnectionFactory
|
||||
pairRecordFactory?: {
|
||||
createPairRecord(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<PairRecord>
|
||||
}
|
||||
},
|
||||
)
|
||||
readonly isHandshakeComplete: boolean
|
||||
readonly isLockdownConnected: boolean
|
||||
readonly isSessionStarted: boolean
|
||||
readonly isSessionSslEnabled: boolean
|
||||
readonly isTlsActive: boolean
|
||||
readonly isPaired: boolean
|
||||
loadPairRecord(record: PairRecord | null): void
|
||||
openAndHandshake(): Promise<void>
|
||||
connectLockdown(port?: number): Promise<void>
|
||||
getOrFetchDeviceUdid(): Promise<string>
|
||||
getOrFetchDeviceName(): Promise<string | null>
|
||||
pairDevice(hostId: string, systemBuid: string): Promise<PairRecord>
|
||||
startSession(hostId: string, systemBuid: string): Promise<{
|
||||
sessionId: string
|
||||
enableSessionSsl: boolean
|
||||
}>
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
export const LOCKDOWN_PORT: number
|
||||
export function installIpaViaInstProxy(
|
||||
client: DirectUsbMuxClient,
|
||||
ipaData: Uint8Array,
|
||||
fileName: string,
|
||||
onLog?: (message: string) => void,
|
||||
): Promise<void>
|
||||
export function sanitizeIpaFileName(fileName: string): string
|
||||
export function createHostId(): string
|
||||
export function createSystemBuid(): string
|
||||
export function encodeStoredPairRecord(record: PairRecord): StoredPairRecordPayload
|
||||
export function decodeStoredPairRecord(parsed: StoredPairRecordPayload): PairRecord | null
|
||||
export function createOpenSslWasmTlsFactory(): TlsConnectionFactory
|
||||
export function generatePairRecordWithOpenSslWasm(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<string>
|
||||
}
|
||||
15
frontend/src/webmuxd-browser.js
Normal file
15
frontend/src/webmuxd-browser.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export { WebUsbTransport } from "../../dependencies/webmuxd/src/core/webusb-transport.ts"
|
||||
export {
|
||||
DirectUsbMuxClient,
|
||||
LOCKDOWN_PORT,
|
||||
installIpaViaInstProxy,
|
||||
sanitizeIpaFileName,
|
||||
createHostId,
|
||||
createSystemBuid,
|
||||
encodeStoredPairRecord,
|
||||
decodeStoredPairRecord,
|
||||
} from "../../dependencies/webmuxd/src/core/imobiledevice-client.ts"
|
||||
export {
|
||||
createOpenSslWasmTlsFactory,
|
||||
generatePairRecordWithOpenSslWasm,
|
||||
} from "./wasm/openssl-webmuxd.js"
|
||||
4
frontend/src/webmuxd-shim.d.ts
vendored
4
frontend/src/webmuxd-shim.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare module "webmuxd" {
|
||||
const webmuxd: Record<string, unknown>
|
||||
export default webmuxd
|
||||
}
|
||||
@@ -18,16 +18,22 @@ export default defineConfig({
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
webmuxd: resolve(repoRootDir, "dependencies/webmuxd/lib/webmuxd.js"),
|
||||
webmuxd: resolve(frontendDir, "src/webmuxd-browser.js"),
|
||||
},
|
||||
preserveSymlinks: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["@lbr77/anisette-js/browser"],
|
||||
exclude: [
|
||||
"altsign.js",
|
||||
"@lbr77/anisette-js",
|
||||
"@lbr77/anisette-js/browser",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper",
|
||||
"libcurl.js",
|
||||
"libcurl.js/bundled",
|
||||
],
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/, /\/lib\/webmuxd\.js/, /\/lib\/core\/.*\.js/],
|
||||
include: [/node_modules/],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
"workspaces": [
|
||||
"frontend",
|
||||
"backend",
|
||||
"dependencies/webmuxd"
|
||||
"dependencies/webmuxd",
|
||||
"wasm/openssl",
|
||||
"wasm/libcurl-wasm",
|
||||
"wasm/zsign-wasm"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "cd dependencies/webmuxd && bun run build",
|
||||
"build:webmuxd": "cd dependencies/webmuxd && bun run build",
|
||||
"build:frontend": "cd frontend && bun run build",
|
||||
"build:backend": "cd backend && bun run check",
|
||||
"build:wasm": "bun run build:wasm:openssl && bun run build:wasm:libcurl && bun run build:wasm:zsign",
|
||||
"build:wasm:openssl": "bash scripts/build-wasm-openssl.sh",
|
||||
"build:wasm:libcurl": "bash scripts/build-wasm-libcurl.sh",
|
||||
"build:wasm:zsign": "bash scripts/build-wasm-zsign.sh",
|
||||
"lint": "cd dependencies/webmuxd && bun run lint",
|
||||
"test": "cd dependencies/webmuxd && bun run test"
|
||||
}
|
||||
|
||||
17
scripts/build-wasm-libcurl.sh
Normal file
17
scripts/build-wasm-libcurl.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./wasm-common.sh
|
||||
source "$SCRIPT_DIR/wasm-common.sh"
|
||||
|
||||
activate_emscripten
|
||||
|
||||
if [[ "$(uname -s)" != "Linux" ]]; then
|
||||
echo "libcurl-wasm upstream only supports Linux builds." >&2
|
||||
echo "Run this script on Linux, or use orb to enter a Linux environment first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT/wasm/libcurl-wasm"
|
||||
bun run build
|
||||
16
scripts/build-wasm-openssl.sh
Normal file
16
scripts/build-wasm-openssl.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./wasm-common.sh
|
||||
source "$SCRIPT_DIR/wasm-common.sh"
|
||||
|
||||
OPENSSL_PRECOMPILED="$(resolve_openssl_precompiled_dir || true)"
|
||||
if [[ -z "${OPENSSL_PRECOMPILED}" ]]; then
|
||||
echo "OpenSSL precompiled directory not found." >&2
|
||||
echo "Set OPENSSL_PRECOMPILED_DIR or OPENSSL_ROOT to a directory containing include/ and lib/." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT/wasm/openssl"
|
||||
OPENSSL_ROOT="$OPENSSL_PRECOMPILED" bun run build
|
||||
18
scripts/build-wasm-zsign.sh
Normal file
18
scripts/build-wasm-zsign.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./wasm-common.sh
|
||||
source "$SCRIPT_DIR/wasm-common.sh"
|
||||
|
||||
activate_emscripten
|
||||
|
||||
OPENSSL_PRECOMPILED="$(resolve_openssl_precompiled_dir || true)"
|
||||
if [[ -z "${OPENSSL_PRECOMPILED}" ]]; then
|
||||
echo "OpenSSL precompiled directory not found." >&2
|
||||
echo "Set OPENSSL_PRECOMPILED_DIR or OPENSSL_WASM to a directory containing include/ and lib/." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT/wasm/zsign-wasm"
|
||||
OPENSSL_WASM="$OPENSSL_PRECOMPILED" bun run build
|
||||
43
scripts/wasm-common.sh
Normal file
43
scripts/wasm-common.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
resolve_openssl_precompiled_dir() {
|
||||
local candidate
|
||||
|
||||
for candidate in \
|
||||
"${OPENSSL_WASM:-}" \
|
||||
"${OPENSSL_ROOT:-}" \
|
||||
"${OPENSSL_PRECOMPILED_DIR:-}" \
|
||||
"$REPO_ROOT/openssl-wasm/precompiled" \
|
||||
"$REPO_ROOT/wasm/openssl/precompiled" \
|
||||
"$REPO_ROOT/wasm/vendor/openssl-wasm/precompiled" \
|
||||
"$REPO_ROOT/wasm/openssl-wasm/precompiled"
|
||||
do
|
||||
if [[ -n "$candidate" && -d "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
activate_emscripten() {
|
||||
if [[ -n "${EMSDK_ENV:-}" ]]; then
|
||||
if [[ ! -f "${EMSDK_ENV}" ]]; then
|
||||
echo "EMSDK_ENV does not exist: ${EMSDK_ENV}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "${EMSDK_ENV}"
|
||||
fi
|
||||
|
||||
if ! command -v emcc >/dev/null 2>&1 || ! command -v em++ >/dev/null 2>&1; then
|
||||
echo "Emscripten is not available. Install it or set EMSDK_ENV to emsdk_env.sh." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
2
wasm/openssl/.gitignore
vendored
2
wasm/openssl/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/target/
|
||||
/binary/
|
||||
/dist/
|
||||
|
||||
40
wasm/openssl/Cargo.lock
generated
40
wasm/openssl/Cargo.lock
generated
@@ -16,9 +16,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -53,15 +53,15 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -79,9 +79,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
@@ -150,9 +150,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -237,9 +237,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -250,9 +250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -260,9 +260,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -273,18 +273,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
||||
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
||||
@@ -5,18 +5,19 @@ edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "rust-src/lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[dependencies]
|
||||
js-sys = "0.3"
|
||||
js-sys = "0.3.94"
|
||||
openssl = "0.10.75"
|
||||
openssl-sys = { path = "vendor/openssl-sys" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
wasm-bindgen = "0.2.100"
|
||||
web-sys = { version = "0.3", features = ["Window", "Crypto"] }
|
||||
wasm-bindgen = "0.2.117"
|
||||
web-sys = { version = "0.3.94", features = ["Window", "Crypto"] }
|
||||
|
||||
[patch.crates-io]
|
||||
openssl-sys = { path = "vendor/openssl-sys" }
|
||||
|
||||
11
wasm/openssl/build.mjs
Normal file
11
wasm/openssl/build.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { cp, mkdir, rm } from "node:fs/promises"
|
||||
import { dirname, resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const packageDir = dirname(fileURLToPath(import.meta.url))
|
||||
const sourceDir = resolve(packageDir, "src")
|
||||
const distDir = resolve(packageDir, "dist")
|
||||
|
||||
await rm(distDir, { recursive: true, force: true })
|
||||
await mkdir(distDir, { recursive: true })
|
||||
await cp(sourceDir, distDir, { recursive: true })
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("src/c_shim/vsnprintf_shim.c")
|
||||
.file("rust-src/c_shim/vsnprintf_shim.c")
|
||||
.flag_if_supported("-std=c99")
|
||||
.compile("vsnprintf_shim");
|
||||
}
|
||||
|
||||
@@ -4,8 +4,42 @@ set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
OPENSSL_ROOT="/Users/libr/Desktop/Life/browser-apple/openssl-wasm/precompiled"
|
||||
LLVM_BIN="/opt/homebrew/opt/llvm/bin"
|
||||
OPENSSL_ROOT="${OPENSSL_ROOT:-${OPENSSL_PRECOMPILED_DIR:-$ROOT_DIR/precompiled}}"
|
||||
LLVM_BIN="${LLVM_BIN:-}"
|
||||
|
||||
if [[ ! -d "$OPENSSL_ROOT" ]]; then
|
||||
echo "OpenSSL precompiled directory not found: $OPENSSL_ROOT" >&2
|
||||
echo "Set OPENSSL_ROOT or OPENSSL_PRECOMPILED_DIR to a directory containing include/ and lib/." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$LLVM_BIN" ]] && command -v brew >/dev/null 2>&1; then
|
||||
BREW_LLVM_PREFIX="$(brew --prefix llvm 2>/dev/null || true)"
|
||||
if [[ -n "$BREW_LLVM_PREFIX" && -x "$BREW_LLVM_PREFIX/bin/clang" ]]; then
|
||||
LLVM_BIN="$BREW_LLVM_PREFIX/bin"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$LLVM_BIN" ]]; then
|
||||
CLANG_BIN="$LLVM_BIN/clang"
|
||||
LLVM_AR_BIN="$LLVM_BIN/llvm-ar"
|
||||
LLVM_RANLIB_BIN="$LLVM_BIN/llvm-ranlib"
|
||||
else
|
||||
CLANG_BIN="${CLANG_BIN:-$(command -v clang || true)}"
|
||||
LLVM_AR_BIN="${LLVM_AR_BIN:-$(command -v llvm-ar || true)}"
|
||||
LLVM_RANLIB_BIN="${LLVM_RANLIB_BIN:-$(command -v llvm-ranlib || true)}"
|
||||
fi
|
||||
|
||||
if [[ -z "$CLANG_BIN" || -z "$LLVM_AR_BIN" || -z "$LLVM_RANLIB_BIN" ]]; then
|
||||
echo "LLVM tools for wasm32-unknown-unknown are not available." >&2
|
||||
echo "Set LLVM_BIN or CLANG_BIN/LLVM_AR_BIN/LLVM_RANLIB_BIN." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v wasm-bindgen >/dev/null 2>&1; then
|
||||
echo "Missing wasm-bindgen CLI. Install it with: cargo install wasm-bindgen-cli" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export OPENSSL_NO_VENDOR=1
|
||||
export OPENSSL_STATIC=1
|
||||
@@ -14,22 +48,18 @@ export OPENSSL_LIB_DIR="$OPENSSL_ROOT/lib"
|
||||
export OPENSSL_INCLUDE_DIR="$OPENSSL_ROOT/include"
|
||||
export OPENSSL_LIBS="ssl:crypto"
|
||||
|
||||
export CC_wasm32_unknown_unknown="$LLVM_BIN/clang --target=wasm32-unknown-unknown"
|
||||
export AR_wasm32_unknown_unknown="$LLVM_BIN/llvm-ar"
|
||||
export RANLIB_wasm32_unknown_unknown="$LLVM_BIN/llvm-ranlib"
|
||||
export CC_wasm32_unknown_unknown="$CLANG_BIN --target=wasm32-unknown-unknown"
|
||||
export AR_wasm32_unknown_unknown="$LLVM_AR_BIN"
|
||||
export RANLIB_wasm32_unknown_unknown="$LLVM_RANLIB_BIN"
|
||||
|
||||
echo "[1/2] Building wasm32-unknown-unknown with openssl-rs..."
|
||||
cargo build --release --target wasm32-unknown-unknown
|
||||
|
||||
if command -v wasm-bindgen >/dev/null 2>&1; then
|
||||
echo "[2/2] Generating JS bindings with wasm-bindgen..."
|
||||
mkdir -p pkg
|
||||
wasm-bindgen \
|
||||
target/wasm32-unknown-unknown/release/openssl_wasm.wasm \
|
||||
--out-dir pkg \
|
||||
--target web
|
||||
echo "Done: pkg/ generated."
|
||||
else
|
||||
echo "[2/2] wasm-bindgen CLI not found; skipped JS binding generation."
|
||||
echo "Install via: cargo install wasm-bindgen-cli"
|
||||
fi
|
||||
echo "[2/2] Generating JS bindings with wasm-bindgen..."
|
||||
rm -rf binary
|
||||
mkdir -p binary
|
||||
wasm-bindgen \
|
||||
target/wasm32-unknown-unknown/release/openssl_wasm.wasm \
|
||||
--out-dir binary \
|
||||
--target web
|
||||
echo "Done: binary/ generated."
|
||||
|
||||
28
wasm/openssl/package.json
Normal file
28
wasm/openssl/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@lbr77/openssl-wasm",
|
||||
"version": "0.1.0",
|
||||
"description": "OpenSSL WebAssembly package with stable src/dist/binary layout.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"binary/**/*",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build:binary": "bash ./build.sh",
|
||||
"build:dist": "bun ./build.mjs",
|
||||
"build": "bun run build:binary && bun run build:dist",
|
||||
"clean": "rm -rf dist binary",
|
||||
"prepack": "bun run build"
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,15 @@ static COUNT_STAT: AtomicUsize = AtomicUsize::new(0);
|
||||
static COUNT_OPENDIR: AtomicUsize = AtomicUsize::new(0);
|
||||
static ARC4_FALLBACK_SEED: AtomicUsize = AtomicUsize::new(0x9E37_79B9);
|
||||
|
||||
#[no_mangle]
|
||||
pub static mut errno: c_int = 0;
|
||||
|
||||
#[no_mangle]
|
||||
pub static CLOCK_REALTIME: c_int = 0;
|
||||
|
||||
#[no_mangle]
|
||||
pub static _CLOCK_REALTIME: c_int = 0;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PairRecordWasmOut {
|
||||
3
wasm/openssl/src/index.d.ts
vendored
Normal file
3
wasm/openssl/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "../binary/openssl_wasm.js"
|
||||
|
||||
export default function ensureOpenSslWasmModuleReady(input?: unknown): Promise<void>
|
||||
17
wasm/openssl/src/index.mjs
Normal file
17
wasm/openssl/src/index.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
import initOpenSslWasm from "../binary/openssl_wasm.js"
|
||||
|
||||
export * from "../binary/openssl_wasm.js"
|
||||
|
||||
let initPromise = null
|
||||
|
||||
/**
|
||||
* Keep a stable ESM wrapper in `src/` so consumers always import the package
|
||||
* entry, while the raw wasm-bindgen output stays isolated in `binary/`.
|
||||
*/
|
||||
export default async function ensureOpenSslWasmModuleReady(input) {
|
||||
if (!initPromise) {
|
||||
initPromise = Promise.resolve(initOpenSslWasm(input)).then(() => undefined)
|
||||
}
|
||||
|
||||
await initPromise
|
||||
}
|
||||
Reference in New Issue
Block a user