keychain.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { arrayToB64, b64ToArray } from './utils';
  2. import { decryptStream, encryptStream } from './ece.js';
  3. const encoder = new TextEncoder();
  4. const decoder = new TextDecoder();
  5. export default class Keychain {
  6. constructor(secretKeyB64, nonce) {
  7. this._nonce = nonce || 'yRCdyQ1EMSA3mo4rqSkuNQ==';
  8. if (secretKeyB64) {
  9. this.rawSecret = b64ToArray(secretKeyB64);
  10. } else {
  11. this.rawSecret = crypto.getRandomValues(new Uint8Array(16));
  12. }
  13. this.secretKeyPromise = crypto.subtle.importKey(
  14. 'raw',
  15. this.rawSecret,
  16. 'HKDF',
  17. false,
  18. ['deriveKey']
  19. );
  20. this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
  21. return crypto.subtle.deriveKey(
  22. {
  23. name: 'HKDF',
  24. salt: new Uint8Array(),
  25. info: encoder.encode('metadata'),
  26. hash: 'SHA-256'
  27. },
  28. secretKey,
  29. {
  30. name: 'AES-GCM',
  31. length: 128
  32. },
  33. false,
  34. ['encrypt', 'decrypt']
  35. );
  36. });
  37. this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) {
  38. return crypto.subtle.deriveKey(
  39. {
  40. name: 'HKDF',
  41. salt: new Uint8Array(),
  42. info: encoder.encode('authentication'),
  43. hash: 'SHA-256'
  44. },
  45. secretKey,
  46. {
  47. name: 'HMAC',
  48. hash: { name: 'SHA-256' }
  49. },
  50. true,
  51. ['sign']
  52. );
  53. });
  54. }
  55. get nonce() {
  56. return this._nonce;
  57. }
  58. set nonce(n) {
  59. if (n && n !== this._nonce) {
  60. this._nonce = n;
  61. }
  62. }
  63. setPassword(password, shareUrl) {
  64. this.authKeyPromise = crypto.subtle
  65. .importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [
  66. 'deriveKey'
  67. ])
  68. .then(passwordKey =>
  69. crypto.subtle.deriveKey(
  70. {
  71. name: 'PBKDF2',
  72. salt: encoder.encode(shareUrl),
  73. iterations: 100,
  74. hash: 'SHA-256'
  75. },
  76. passwordKey,
  77. {
  78. name: 'HMAC',
  79. hash: 'SHA-256'
  80. },
  81. true,
  82. ['sign']
  83. )
  84. );
  85. }
  86. setAuthKey(authKeyB64) {
  87. this.authKeyPromise = crypto.subtle.importKey(
  88. 'raw',
  89. b64ToArray(authKeyB64),
  90. {
  91. name: 'HMAC',
  92. hash: 'SHA-256'
  93. },
  94. true,
  95. ['sign']
  96. );
  97. }
  98. async authKeyB64() {
  99. const authKey = await this.authKeyPromise;
  100. const rawAuth = await crypto.subtle.exportKey('raw', authKey);
  101. return arrayToB64(new Uint8Array(rawAuth));
  102. }
  103. async authHeader() {
  104. const authKey = await this.authKeyPromise;
  105. const sig = await crypto.subtle.sign(
  106. {
  107. name: 'HMAC'
  108. },
  109. authKey,
  110. b64ToArray(this.nonce)
  111. );
  112. return `send-v1 ${arrayToB64(new Uint8Array(sig))}`;
  113. }
  114. async encryptMetadata(metadata) {
  115. const metaKey = await this.metaKeyPromise;
  116. const ciphertext = await crypto.subtle.encrypt(
  117. {
  118. name: 'AES-GCM',
  119. iv: new Uint8Array(12),
  120. tagLength: 128
  121. },
  122. metaKey,
  123. encoder.encode(
  124. JSON.stringify({
  125. name: metadata.name,
  126. size: metadata.size,
  127. type: metadata.type || 'application/octet-stream',
  128. manifest: metadata.manifest || {}
  129. })
  130. )
  131. );
  132. return ciphertext;
  133. }
  134. encryptStream(plainStream) {
  135. return encryptStream(plainStream, this.rawSecret);
  136. }
  137. decryptStream(cryptotext) {
  138. return decryptStream(cryptotext, this.rawSecret);
  139. }
  140. async decryptMetadata(ciphertext) {
  141. const metaKey = await this.metaKeyPromise;
  142. const plaintext = await crypto.subtle.decrypt(
  143. {
  144. name: 'AES-GCM',
  145. iv: new Uint8Array(12),
  146. tagLength: 128
  147. },
  148. metaKey,
  149. ciphertext
  150. );
  151. return JSON.parse(decoder.decode(plaintext));
  152. }
  153. }