ra2.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import { encodeUTF8 } from './util/strings.js';
  2. import EventTargetMixin from './util/eventtarget.js';
  3. import legacyCrypto from './crypto/crypto.js';
  4. class RA2Cipher {
  5. constructor() {
  6. this._cipher = null;
  7. this._counter = new Uint8Array(16);
  8. }
  9. async setKey(key) {
  10. this._cipher = await legacyCrypto.importKey(
  11. "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
  12. }
  13. async makeMessage(message) {
  14. const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
  15. const encrypted = await legacyCrypto.encrypt({
  16. name: "AES-EAX",
  17. iv: this._counter,
  18. additionalData: ad,
  19. }, this._cipher, message);
  20. for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
  21. const res = new Uint8Array(message.length + 2 + 16);
  22. res.set(ad);
  23. res.set(encrypted, 2);
  24. return res;
  25. }
  26. async receiveMessage(length, encrypted) {
  27. const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
  28. const res = await legacyCrypto.decrypt({
  29. name: "AES-EAX",
  30. iv: this._counter,
  31. additionalData: ad,
  32. }, this._cipher, encrypted);
  33. for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
  34. return res;
  35. }
  36. }
  37. export default class RSAAESAuthenticationState extends EventTargetMixin {
  38. constructor(sock, getCredentials) {
  39. super();
  40. this._hasStarted = false;
  41. this._checkSock = null;
  42. this._checkCredentials = null;
  43. this._approveServerResolve = null;
  44. this._sockReject = null;
  45. this._credentialsReject = null;
  46. this._approveServerReject = null;
  47. this._sock = sock;
  48. this._getCredentials = getCredentials;
  49. }
  50. _waitSockAsync(len) {
  51. return new Promise((resolve, reject) => {
  52. const hasData = () => !this._sock.rQwait('RA2', len);
  53. if (hasData()) {
  54. resolve();
  55. } else {
  56. this._checkSock = () => {
  57. if (hasData()) {
  58. resolve();
  59. this._checkSock = null;
  60. this._sockReject = null;
  61. }
  62. };
  63. this._sockReject = reject;
  64. }
  65. });
  66. }
  67. _waitApproveKeyAsync() {
  68. return new Promise((resolve, reject) => {
  69. this._approveServerResolve = resolve;
  70. this._approveServerReject = reject;
  71. });
  72. }
  73. _waitCredentialsAsync(subtype) {
  74. const hasCredentials = () => {
  75. if (subtype === 1 && this._getCredentials().username !== undefined &&
  76. this._getCredentials().password !== undefined) {
  77. return true;
  78. } else if (subtype === 2 && this._getCredentials().password !== undefined) {
  79. return true;
  80. }
  81. return false;
  82. };
  83. return new Promise((resolve, reject) => {
  84. if (hasCredentials()) {
  85. resolve();
  86. } else {
  87. this._checkCredentials = () => {
  88. if (hasCredentials()) {
  89. resolve();
  90. this._checkCredentials = null;
  91. this._credentialsReject = null;
  92. }
  93. };
  94. this._credentialsReject = reject;
  95. }
  96. });
  97. }
  98. checkInternalEvents() {
  99. if (this._checkSock !== null) {
  100. this._checkSock();
  101. }
  102. if (this._checkCredentials !== null) {
  103. this._checkCredentials();
  104. }
  105. }
  106. approveServer() {
  107. if (this._approveServerResolve !== null) {
  108. this._approveServerResolve();
  109. this._approveServerResolve = null;
  110. }
  111. }
  112. disconnect() {
  113. if (this._sockReject !== null) {
  114. this._sockReject(new Error("disconnect normally"));
  115. this._sockReject = null;
  116. }
  117. if (this._credentialsReject !== null) {
  118. this._credentialsReject(new Error("disconnect normally"));
  119. this._credentialsReject = null;
  120. }
  121. if (this._approveServerReject !== null) {
  122. this._approveServerReject(new Error("disconnect normally"));
  123. this._approveServerReject = null;
  124. }
  125. }
  126. async negotiateRA2neAuthAsync() {
  127. this._hasStarted = true;
  128. // 1: Receive server public key
  129. await this._waitSockAsync(4);
  130. const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
  131. const serverKeyLength = this._sock.rQshift32();
  132. if (serverKeyLength < 1024) {
  133. throw new Error("RA2: server public key is too short: " + serverKeyLength);
  134. } else if (serverKeyLength > 8192) {
  135. throw new Error("RA2: server public key is too long: " + serverKeyLength);
  136. }
  137. const serverKeyBytes = Math.ceil(serverKeyLength / 8);
  138. await this._waitSockAsync(serverKeyBytes * 2);
  139. const serverN = this._sock.rQshiftBytes(serverKeyBytes);
  140. const serverE = this._sock.rQshiftBytes(serverKeyBytes);
  141. const serverRSACipher = await legacyCrypto.importKey(
  142. "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
  143. const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
  144. serverPublickey.set(serverKeyLengthBuffer);
  145. serverPublickey.set(serverN, 4);
  146. serverPublickey.set(serverE, 4 + serverKeyBytes);
  147. // verify server public key
  148. let approveKey = this._waitApproveKeyAsync();
  149. this.dispatchEvent(new CustomEvent("serververification", {
  150. detail: { type: "RSA", publickey: serverPublickey }
  151. }));
  152. await approveKey;
  153. // 2: Send client public key
  154. const clientKeyLength = 2048;
  155. const clientKeyBytes = Math.ceil(clientKeyLength / 8);
  156. const clientRSACipher = (await legacyCrypto.generateKey({
  157. name: "RSA-PKCS1-v1_5",
  158. modulusLength: clientKeyLength,
  159. publicExponent: new Uint8Array([1, 0, 1]),
  160. }, true, ["encrypt"])).privateKey;
  161. const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
  162. const clientN = clientExportedRSAKey.n;
  163. const clientE = clientExportedRSAKey.e;
  164. const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
  165. clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
  166. clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
  167. clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
  168. clientPublicKey[3] = clientKeyLength & 0xff;
  169. clientPublicKey.set(clientN, 4);
  170. clientPublicKey.set(clientE, 4 + clientKeyBytes);
  171. this._sock.sQpushBytes(clientPublicKey);
  172. this._sock.flush();
  173. // 3: Send client random
  174. const clientRandom = new Uint8Array(16);
  175. window.crypto.getRandomValues(clientRandom);
  176. const clientEncryptedRandom = await legacyCrypto.encrypt(
  177. { name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
  178. const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
  179. clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
  180. clientRandomMessage[1] = serverKeyBytes & 0xff;
  181. clientRandomMessage.set(clientEncryptedRandom, 2);
  182. this._sock.sQpushBytes(clientRandomMessage);
  183. this._sock.flush();
  184. // 4: Receive server random
  185. await this._waitSockAsync(2);
  186. if (this._sock.rQshift16() !== clientKeyBytes) {
  187. throw new Error("RA2: wrong encrypted message length");
  188. }
  189. const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
  190. const serverRandom = await legacyCrypto.decrypt(
  191. { name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
  192. if (serverRandom === null || serverRandom.length !== 16) {
  193. throw new Error("RA2: corrupted server encrypted random");
  194. }
  195. // 5: Compute session keys and set ciphers
  196. let clientSessionKey = new Uint8Array(32);
  197. let serverSessionKey = new Uint8Array(32);
  198. clientSessionKey.set(serverRandom);
  199. clientSessionKey.set(clientRandom, 16);
  200. serverSessionKey.set(clientRandom);
  201. serverSessionKey.set(serverRandom, 16);
  202. clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
  203. clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
  204. serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
  205. serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
  206. const clientCipher = new RA2Cipher();
  207. await clientCipher.setKey(clientSessionKey);
  208. const serverCipher = new RA2Cipher();
  209. await serverCipher.setKey(serverSessionKey);
  210. // 6: Compute and exchange hashes
  211. let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
  212. let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
  213. serverHash.set(serverPublickey);
  214. serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
  215. clientHash.set(clientPublicKey);
  216. clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
  217. serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
  218. clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
  219. serverHash = new Uint8Array(serverHash);
  220. clientHash = new Uint8Array(clientHash);
  221. this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
  222. this._sock.flush();
  223. await this._waitSockAsync(2 + 20 + 16);
  224. if (this._sock.rQshift16() !== 20) {
  225. throw new Error("RA2: wrong server hash");
  226. }
  227. const serverHashReceived = await serverCipher.receiveMessage(
  228. 20, this._sock.rQshiftBytes(20 + 16));
  229. if (serverHashReceived === null) {
  230. throw new Error("RA2: failed to authenticate the message");
  231. }
  232. for (let i = 0; i < 20; i++) {
  233. if (serverHashReceived[i] !== serverHash[i]) {
  234. throw new Error("RA2: wrong server hash");
  235. }
  236. }
  237. // 7: Receive subtype
  238. await this._waitSockAsync(2 + 1 + 16);
  239. if (this._sock.rQshift16() !== 1) {
  240. throw new Error("RA2: wrong subtype");
  241. }
  242. let subtype = (await serverCipher.receiveMessage(
  243. 1, this._sock.rQshiftBytes(1 + 16)));
  244. if (subtype === null) {
  245. throw new Error("RA2: failed to authenticate the message");
  246. }
  247. subtype = subtype[0];
  248. let waitCredentials = this._waitCredentialsAsync(subtype);
  249. if (subtype === 1) {
  250. if (this._getCredentials().username === undefined ||
  251. this._getCredentials().password === undefined) {
  252. this.dispatchEvent(new CustomEvent(
  253. "credentialsrequired",
  254. { detail: { types: ["username", "password"] } }));
  255. }
  256. } else if (subtype === 2) {
  257. if (this._getCredentials().password === undefined) {
  258. this.dispatchEvent(new CustomEvent(
  259. "credentialsrequired",
  260. { detail: { types: ["password"] } }));
  261. }
  262. } else {
  263. throw new Error("RA2: wrong subtype");
  264. }
  265. await waitCredentials;
  266. let username;
  267. if (subtype === 1) {
  268. username = encodeUTF8(this._getCredentials().username).slice(0, 255);
  269. } else {
  270. username = "";
  271. }
  272. const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
  273. const credentials = new Uint8Array(username.length + password.length + 2);
  274. credentials[0] = username.length;
  275. credentials[username.length + 1] = password.length;
  276. for (let i = 0; i < username.length; i++) {
  277. credentials[i + 1] = username.charCodeAt(i);
  278. }
  279. for (let i = 0; i < password.length; i++) {
  280. credentials[username.length + 2 + i] = password.charCodeAt(i);
  281. }
  282. this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
  283. this._sock.flush();
  284. }
  285. get hasStarted() {
  286. return this._hasStarted;
  287. }
  288. set hasStarted(s) {
  289. this._hasStarted = s;
  290. }
  291. }