123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import { arrayToB64, b64ToArray } from './utils';
- import ECE from './ece.js';
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
- export default class Keychain {
- constructor(secretKeyB64, nonce, ivB64) {
- this._nonce = nonce || 'yRCdyQ1EMSA3mo4rqSkuNQ==';
- if (ivB64) {
- this.iv = b64ToArray(ivB64);
- } else {
- this.iv = crypto.getRandomValues(new Uint8Array(12));
- }
- if (secretKeyB64) {
- this.rawSecret = b64ToArray(secretKeyB64);
- } else {
- this.rawSecret = crypto.getRandomValues(new Uint8Array(16));
- }
- this.secretKeyPromise = crypto.subtle.importKey(
- 'raw',
- this.rawSecret,
- 'HKDF',
- false,
- ['deriveKey']
- );
- this.encryptKeyPromise = this.secretKeyPromise.then(function(secretKey) {
- return crypto.subtle.deriveKey(
- {
- name: 'HKDF',
- salt: new Uint8Array(),
- info: encoder.encode('encryption'),
- hash: 'SHA-256'
- },
- secretKey,
- {
- name: 'AES-GCM',
- length: 128
- },
- false,
- ['encrypt', 'decrypt']
- );
- });
- this.metaKeyPromise = this.secretKeyPromise.then(function(secretKey) {
- return crypto.subtle.deriveKey(
- {
- name: 'HKDF',
- salt: new Uint8Array(),
- info: encoder.encode('metadata'),
- hash: 'SHA-256'
- },
- secretKey,
- {
- name: 'AES-GCM',
- length: 128
- },
- false,
- ['encrypt', 'decrypt']
- );
- });
- this.authKeyPromise = this.secretKeyPromise.then(function(secretKey) {
- return crypto.subtle.deriveKey(
- {
- name: 'HKDF',
- salt: new Uint8Array(),
- info: encoder.encode('authentication'),
- hash: 'SHA-256'
- },
- secretKey,
- {
- name: 'HMAC',
- hash: { name: 'SHA-256' }
- },
- true,
- ['sign']
- );
- });
- }
- get nonce() {
- return this._nonce;
- }
- set nonce(n) {
- if (n && n !== this._nonce) {
- this._nonce = n;
- }
- }
- setIV(ivB64) {
- this.iv = b64ToArray(ivB64);
- }
- setPassword(password, shareUrl) {
- this.authKeyPromise = crypto.subtle
- .importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [
- 'deriveKey'
- ])
- .then(passwordKey =>
- crypto.subtle.deriveKey(
- {
- name: 'PBKDF2',
- salt: encoder.encode(shareUrl),
- iterations: 100,
- hash: 'SHA-256'
- },
- passwordKey,
- {
- name: 'HMAC',
- hash: 'SHA-256'
- },
- true,
- ['sign']
- )
- );
- }
- setAuthKey(authKeyB64) {
- this.authKeyPromise = crypto.subtle.importKey(
- 'raw',
- b64ToArray(authKeyB64),
- {
- name: 'HMAC',
- hash: 'SHA-256'
- },
- true,
- ['sign']
- );
- }
- async authKeyB64() {
- const authKey = await this.authKeyPromise;
- const rawAuth = await crypto.subtle.exportKey('raw', authKey);
- return arrayToB64(new Uint8Array(rawAuth));
- }
- async authHeader() {
- const authKey = await this.authKeyPromise;
- const sig = await crypto.subtle.sign(
- {
- name: 'HMAC'
- },
- authKey,
- b64ToArray(this.nonce)
- );
- return `send-v1 ${arrayToB64(new Uint8Array(sig))}`;
- }
- async encryptFile(plaintext) {
- const encryptKey = await this.encryptKeyPromise;
- const ciphertext = await crypto.subtle.encrypt(
- {
- name: 'AES-GCM',
- iv: this.iv,
- tagLength: 128
- },
- encryptKey,
- plaintext
- );
- return ciphertext;
- }
- async encryptMetadata(metadata) {
- const metaKey = await this.metaKeyPromise;
- const ciphertext = await crypto.subtle.encrypt(
- {
- name: 'AES-GCM',
- iv: new Uint8Array(12),
- tagLength: 128
- },
- metaKey,
- encoder.encode(
- JSON.stringify({
- iv: arrayToB64(this.iv),
- name: metadata.name,
- type: metadata.type || 'application/octet-stream'
- })
- )
- );
- return ciphertext;
- }
- encryptStream(plaintext) {
- const ece = new ECE(plaintext, this.rawSecret, 'encrypt');
- return {
- stream: ece.transform(),
- streamInfo: ece.info()
- };
- }
- decryptStream(cryptotext) {
- const ece = new ECE(cryptotext, this.rawSecret, 'decrypt');
- return ece.transform();
- }
- async decryptFile(ciphertext) {
- const encryptKey = await this.encryptKeyPromise;
- const plaintext = await crypto.subtle.decrypt(
- {
- name: 'AES-GCM',
- iv: this.iv,
- tagLength: 128
- },
- encryptKey,
- ciphertext
- );
- return plaintext;
- }
- async decryptMetadata(ciphertext) {
- const metaKey = await this.metaKeyPromise;
- const plaintext = await crypto.subtle.decrypt(
- {
- name: 'AES-GCM',
- iv: new Uint8Array(12),
- tagLength: 128
- },
- metaKey,
- ciphertext
- );
- return JSON.parse(decoder.decode(plaintext));
- }
- }
|