atm-droid 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/api.js +109 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atm-droid",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "ATM Token Manager CLI - 跨平台 Factory Token 管理工具",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -4,19 +4,35 @@ const { config, getDeviceId } = require('./config');
4
4
 
5
5
  const getServerUrl = () => config.get('serverUrl');
6
6
 
7
- // 生成加密密钥(与服务器一致)
8
- function getEncryptionKey() {
9
- const key = Buffer.alloc(32);
10
- const base = Buffer.from([
11
- 0x4a, 0x7b, 0x2c, 0x9d, 0x1e, 0x5f, 0x8a, 0x3b,
12
- 0x6c, 0x0d, 0x4e, 0x9f, 0x2a, 0x7b, 0x1c, 0x5d,
13
- 0x8e, 0x3f, 0x6a, 0x0b, 0x4c, 0x9d, 0x2e, 0x7f,
14
- 0x1a, 0x5b, 0x8c, 0x3d, 0x6e, 0x0f, 0x4a, 0x9b,
15
- ]);
16
- for (let i = 0; i < 32; i++) {
17
- key[i] = base[i] ^ ((i * 0x17) & 0xff);
7
+ // 解密静态字节(与 Rust 客户端一致)
8
+ function decryptStaticBytes(encrypted, keySeed) {
9
+ const result = [];
10
+ let key = keySeed;
11
+
12
+ for (let i = 0; i < encrypted.length; i++) {
13
+ const byte = encrypted[i];
14
+ let k1 = ((key * 0x1B) + i) & 0xff;
15
+ let k2 = ((k1 << 3) | (k1 >>> 5)) & 0xff; // rotate_left(3)
16
+ k2 = k2 ^ 0xA5;
17
+ const decrypted = byte ^ k2;
18
+ result.push(decrypted);
19
+ key = (((key + decrypted) & 0xff) * 0x3D) & 0xff;
18
20
  }
19
- return key;
21
+
22
+ return Buffer.from(result);
23
+ }
24
+
25
+ // 加密的通信密钥(与 Rust 客户端一致)
26
+ const ENC_COMM_KEY = Buffer.from([
27
+ 0x11, 0x18, 0xFC, 0x4A, 0xF9, 0xB8, 0x1A, 0x9C,
28
+ 0x4F, 0xA4, 0xD1, 0x44, 0x47, 0xE5, 0x03, 0x57,
29
+ 0xA3, 0x2B, 0x8E, 0x38, 0x3B, 0x99, 0x8F, 0x8C,
30
+ 0x2B, 0x91, 0x60, 0xC1, 0x15, 0x07, 0x21, 0x53,
31
+ ]);
32
+
33
+ // 生成加密密钥(与客户端一致)
34
+ function getEncryptionKey() {
35
+ return decryptStaticBytes(ENC_COMM_KEY, 0x8D);
20
36
  }
21
37
 
22
38
  // 生成签名
@@ -28,6 +44,40 @@ function generateSignature(data, timestamp, deviceId) {
28
44
  return hmac.digest('hex');
29
45
  }
30
46
 
47
+ // 解密服务器返回的数据(返回字符串)
48
+ function decryptPayload(encryptedData, ivHex, tagHex) {
49
+ try {
50
+ const key = getEncryptionKey();
51
+ const iv = Buffer.from(ivHex, 'hex');
52
+ const tag = Buffer.from(tagHex, 'hex');
53
+ const encrypted = Buffer.from(encryptedData, 'hex');
54
+
55
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
56
+ decipher.setAuthTag(tag);
57
+
58
+ let decrypted = decipher.update(encrypted, null, 'utf8');
59
+ decrypted += decipher.final('utf8');
60
+
61
+ return decrypted;
62
+ } catch (e) {
63
+ console.error('解密失败:', e.message);
64
+ return null;
65
+ }
66
+ }
67
+
68
+ // 解密服务器返回的 JSON 数据
69
+ function decryptPayloadJson(encryptedData, ivHex, tagHex) {
70
+ const decrypted = decryptPayload(encryptedData, ivHex, tagHex);
71
+ if (decrypted) {
72
+ try {
73
+ return JSON.parse(decrypted);
74
+ } catch (e) {
75
+ return null;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
31
81
  // 激活码登录
32
82
  async function activate(code) {
33
83
  const deviceId = getDeviceId();
@@ -51,19 +101,27 @@ async function getTokens() {
51
101
  throw new Error('未登录');
52
102
  }
53
103
 
104
+ const timestamp = Math.floor(Date.now() / 1000);
105
+ const signature = generateSignature(sessionToken, timestamp, deviceId);
106
+
54
107
  const response = await fetch(`${getServerUrl()}/api/v1/tokens`, {
55
108
  headers: {
56
109
  'Authorization': `Bearer ${sessionToken}`,
57
- 'X-Device-ID': deviceId
110
+ 'X-Device-ID': deviceId,
111
+ 'X-Timestamp': timestamp.toString(),
112
+ 'X-Signature': signature
58
113
  }
59
114
  });
60
115
 
61
116
  const result = await response.json();
62
117
 
63
118
  // 如果返回加密数据,需要解密
64
- if (result.success && result.data && result.iv) {
65
- // 简化版:假设服务器返回明文(或添加解密逻辑)
66
- return result;
119
+ if (result.success && result.data && result.iv && result.tag) {
120
+ const decrypted = decryptPayloadJson(result.data, result.iv, result.tag);
121
+ if (decrypted) {
122
+ return { success: true, data: decrypted };
123
+ }
124
+ return { success: false, error: '解密失败' };
67
125
  }
68
126
 
69
127
  return result;
@@ -73,18 +131,40 @@ async function getTokens() {
73
131
  async function activateToken(tokenId) {
74
132
  const sessionToken = config.get('sessionToken');
75
133
  const deviceId = getDeviceId();
134
+ const timestamp = Math.floor(Date.now() / 1000);
135
+ const signature = generateSignature(sessionToken, timestamp, deviceId);
76
136
 
77
137
  const response = await fetch(`${getServerUrl()}/api/v1/tokens/activate`, {
78
138
  method: 'POST',
79
139
  headers: {
80
140
  'Content-Type': 'application/json',
81
141
  'Authorization': `Bearer ${sessionToken}`,
82
- 'X-Device-ID': deviceId
142
+ 'X-Device-ID': deviceId,
143
+ 'X-Timestamp': timestamp.toString(),
144
+ 'X-Signature': signature
83
145
  },
84
146
  body: JSON.stringify({ token_id: tokenId })
85
147
  });
86
148
 
87
- return response.json();
149
+ const result = await response.json();
150
+
151
+ // 解密 access_token 和 refresh_token
152
+ if (result.success && result.access_token && result.access_iv && result.access_tag) {
153
+ const accessToken = decryptPayload(result.access_token, result.access_iv, result.access_tag);
154
+ const refreshToken = decryptPayload(result.refresh_token, result.refresh_iv, result.refresh_tag);
155
+
156
+ if (accessToken && refreshToken) {
157
+ return {
158
+ success: true,
159
+ email: result.email,
160
+ access_token: accessToken,
161
+ refresh_token: refreshToken
162
+ };
163
+ }
164
+ return { success: false, error: '解密失败' };
165
+ }
166
+
167
+ return result;
88
168
  }
89
169
 
90
170
  // 心跳
@@ -94,12 +174,17 @@ async function heartbeat() {
94
174
 
95
175
  if (!sessionToken) return { valid: false };
96
176
 
177
+ const timestamp = Math.floor(Date.now() / 1000);
178
+ const signature = generateSignature(sessionToken, timestamp, deviceId);
179
+
97
180
  const response = await fetch(`${getServerUrl()}/api/v1/auth/heartbeat`, {
98
181
  method: 'POST',
99
182
  headers: {
100
183
  'Content-Type': 'application/json',
101
184
  'Authorization': `Bearer ${sessionToken}`,
102
- 'X-Device-ID': deviceId
185
+ 'X-Device-ID': deviceId,
186
+ 'X-Timestamp': timestamp.toString(),
187
+ 'X-Signature': signature
103
188
  }
104
189
  });
105
190
 
@@ -110,13 +195,17 @@ async function heartbeat() {
110
195
  async function unbind() {
111
196
  const sessionToken = config.get('sessionToken');
112
197
  const deviceId = getDeviceId();
198
+ const timestamp = Math.floor(Date.now() / 1000);
199
+ const signature = generateSignature(sessionToken, timestamp, deviceId);
113
200
 
114
201
  const response = await fetch(`${getServerUrl()}/api/v1/auth/unbind`, {
115
202
  method: 'POST',
116
203
  headers: {
117
204
  'Content-Type': 'application/json',
118
205
  'Authorization': `Bearer ${sessionToken}`,
119
- 'X-Device-ID': deviceId
206
+ 'X-Device-ID': deviceId,
207
+ 'X-Timestamp': timestamp.toString(),
208
+ 'X-Signature': signature
120
209
  }
121
210
  });
122
211