MB-X Bilibili Pipeline
6 days ago 856d836cce5b57bda9d5aa9313cc9040ea56f02f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
const encoder = new TextEncoder();
 
function stable(value) {
  if (Array.isArray(value)) return value.map(stable);
  if (value && typeof value === "object") {
    return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
  }
  return value;
}
 
export function canonicalJson(value) {
  return JSON.stringify(stable(value));
}
 
async function hmacHex(secretHex, value) {
  const secret = Uint8Array.from(secretHex.match(/../gu) || [], (item) => Number.parseInt(item, 16));
  const key = await crypto.subtle.importKey("raw", secret, {name: "HMAC", hash: "SHA-256"}, false, ["sign"]);
  const result = await crypto.subtle.sign("HMAC", key, encoder.encode(canonicalJson(value)));
  return [...new Uint8Array(result)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
 
async function verify(secret, message) {
  if (!message || typeof message !== "object" || typeof message.hmac !== "string") throw new Error("E_PROTOCOL");
  const unsigned = {...message};
  const supplied = unsigned.hmac;
  delete unsigned.hmac;
  if (!/^[0-9a-f]{64}$/u.test(supplied)) throw new Error("E_HMAC");
  const rawSecret = Uint8Array.from(secret.match(/../gu) || [], (item) => Number.parseInt(item, 16));
  const signature = Uint8Array.from(supplied.match(/../gu) || [], (item) => Number.parseInt(item, 16));
  const key = await crypto.subtle.importKey("raw", rawSecret, {name: "HMAC", hash: "SHA-256"}, false, ["verify"]);
  const valid = await crypto.subtle.verify("HMAC", key, signature, encoder.encode(canonicalJson(unsigned)));
  if (!valid) throw new Error("E_HMAC");
}
 
export class TrustedRuntimeSession {
  constructor(api, hello) {
    this.api = api;
    this.hello = Object.freeze({...hello});
    this.secret = null;
    this.runId = null;
    this.requestId = null;
    this.sequence = 0;
    this.prepared = null;
    this.dispatched = false;
    this.actionResult = null;
  }
 
  helloFrame() {
    return {schema_version: 1, type: "EXTENSION_HELLO", sequence: 1, ...this.hello};
  }
 
  async accept(message) {
    if (message?.type === "HOST_CHALLENGE") {
      if (this.secret !== null || !/^[0-9a-f]{64}$/u.test(String(message.secret || ""))) throw new Error("E_CHALLENGE");
      this.secret = message.secret;
      await verify(this.secret, message);
      this.runId = message.run_id;
      this.requestId = message.request_id;
      this.sequence = 2;
      return this.sign("EXTENSION_CHALLENGE_ACCEPTED", 3, {challenge_id: message.challenge_id});
    }
    if (!this.secret) throw new Error("E_CHALLENGE_REQUIRED");
    await verify(this.secret, message);
    if (message.run_id !== this.runId || message.request_id !== this.requestId || message.sequence !== this.sequence + 2) {
      throw new Error("E_SEQUENCE");
    }
    this.sequence = message.sequence;
    if (message.type === "HOST_ACTION_PREPARE") {
      if (this.prepared) throw new Error("E_REPLAY");
      this.prepared = await this.api.prepare(message.action);
      return this.sign("EXTENSION_READY_TO_DISPATCH", message.sequence + 1, {
        action_id: message.action.action_id,
        prepared: this.prepared
      });
    }
    if (message.type === "HOST_DISPATCH_PERMIT") {
      if (!this.prepared || this.dispatched || message.permit.action_id !== this.prepared.action_id) throw new Error("E_PERMIT");
      if (Date.now() > Date.parse(message.permit.deadline_at)) throw new Error("E_DEADLINE");
      this.dispatched = true;
      this.actionResult = await this.api.dispatch(this.prepared);
      return this.sign("EXTENSION_ACTION_RESULT", message.sequence + 1, {
        permit_id: message.permit.permit_id,
        result: this.actionResult
      });
    }
    if (message.type === "HOST_OBSERVATION_REQUEST") {
      if (!this.dispatched || !this.actionResult) throw new Error("E_ACTION_RESULT_REQUIRED");
      const observation = await this.api.observe(this.actionResult);
      return this.sign("EXTENSION_OBSERVATION", message.sequence + 1, {observation});
    }
    if (message.type === "HOST_COMMIT_RESULT") {
      return null;
    }
    throw new Error("E_MESSAGE_TYPE");
  }
 
  async sign(type, sequence, fields) {
    const unsigned = {schema_version: 1, type, run_id: this.runId, request_id: this.requestId, sequence, ...fields};
    return {...unsigned, hmac: await hmacHex(this.secret, unsigned)};
  }
}