diff --git a/keeperapi/src/browser/asn1hex.ts b/keeperapi/src/browser/asn1hex.ts index f9b92db..7bf1420 100644 --- a/keeperapi/src/browser/asn1hex.ts +++ b/keeperapi/src/browser/asn1hex.ts @@ -262,7 +262,7 @@ function _asnhex_getDecendantHexVByNthList(h, currentIndex, nthList) { return _asnhex_getHexOfV_AtObj(h, idx); } -// RSA TAGGED +// RSA TAGGED - no changes here function _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey) { var a = new Array(); var v1 = _asnhex_getStartPosOfV_AtObj(hPrivateKey, 0); @@ -278,7 +278,7 @@ function _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey) { return a; } -// RSA TAGGED +// RSA TAGGED - no changes here export function _rsapem_getHexValueArrayOfChildrenFromHex(hPrivateKey) { var posArray = _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey); diff --git a/keeperapi/src/browser/platform.ts b/keeperapi/src/browser/platform.ts index 0a99a72..3256276 100644 --- a/keeperapi/src/browser/platform.ts +++ b/keeperapi/src/browser/platform.ts @@ -23,7 +23,7 @@ import { CryptoWorkerPoolConfig } from '../cryptoWorker'; -// RSA TAGGED +// RSA TAGGED - just naming the algo const rsaAlgorithmName: string = "RSASSA-PKCS1-v1_5"; const CBC_IV_LENGTH = 16 const GCM_IV_LENGTH = 12 @@ -119,7 +119,7 @@ export const browserPlatform: Platform = class { } } - // RSA TAGGED + // RSA TAGGED - done, there is already an ecc version static async importKeyRSA(keyId: string, key: Uint8Array, storage?: KeyStorage): Promise { keyBytesCache[keyId] = key @@ -209,9 +209,14 @@ export const browserPlatform: Platform = class { await this.importKey(keyId, keyBytes, storage, true) break case 'rsa': - // RSA TAGGED + // RSA TAGGED - this seems to be a read operation, keep for now. + // the key should change to ecc at some point up stream instead of here await this.importKeyRSA(keyId, keyBytes, storage) break + // TODO: add something like this, need to find pub/priv key pair + // case 'ecc': + // await this.importKeyEC(keyId, keyBytes, keys[keyId].publicKey, storage) + // break default: throw new Error(`unable to import ${unwrappedType} key`) } @@ -244,14 +249,14 @@ export const browserPlatform: Platform = class { static async unwrapKey(key: Uint8Array, keyId: string, unwrappingKeyId: string, encryptionType: EncryptionType, unwrappedKeyType: UnwrappedKeyType, storage?: KeyStorage, canExport?: boolean): Promise { switch (unwrappedKeyType) { case 'rsa': - // RSA TAGGED + // RSA TAGGED - added another item for ecc if (keyBytesCache[keyId]) { // Skip redundant RSA key decryption return } await this.unwrapRSAKey(key, keyId, unwrappingKeyId, encryptionType, storage) - break + break case 'aes': if (cryptoKeysCache['gcm'][keyId]) { // Keeperapp sometimes provides redundant key data, for example, like if you own a record in a shared folder, @@ -261,6 +266,14 @@ export const browserPlatform: Platform = class { await this.unwrapAesKey(key, keyId, unwrappingKeyId, encryptionType, storage, canExport) break + // TODO: add something like this, need to find pub/priv key pair + // case 'ecc': + // if (cryptoKeysCache['gcm'][keyId]) { + // return + // } + + // await this.unwrapECCKey(key, keyId, unwrappingKeyId, encryptionType, storage, canExport) + // break default: throw new Error('Unable to unwrap key type ' + unwrappedKeyType) } @@ -272,7 +285,7 @@ export const browserPlatform: Platform = class { let algoParams: AesCbcParams | AesGcmParams switch (encryptionType) { case 'rsa': - // RSA TAGGED + // RSA TAGGED - ecc already an option, but might need to fallback to ecc here const rsaKey = await this.loadKeyBytes(unwrappingKeyId, storage) const keyBytes = this.privateDecrypt(key, rsaKey) await this.importKey(keyId, keyBytes, storage, canExport) @@ -329,12 +342,18 @@ export const browserPlatform: Platform = class { } } - // RSA TAGGED + // RSA TAGGED - created copy with ECC involved, unwrapping the private key instead static async unwrapRSAKey(key: Uint8Array, keyId: string, unwrappingKeyId: string, encryptionType: EncryptionType, storage?: KeyStorage): Promise { const rsaKey = await this.decrypt(key, unwrappingKeyId, encryptionType, storage) await this.importKeyRSA(keyId, rsaKey, storage) } + // keyId: string, privateKey: Uint8Array, publicKey: Uint8Array, storage?: KeyStorage + static async unwrapECCKey(privateKey: Uint8Array, publicKey: Uint8Array, keyId: string, unwrappingKeyId: string, encryptionType: EncryptionType, storage?: KeyStorage): Promise { + const decryptedPrivateKey = await this.decrypt(privateKey, unwrappingKeyId, encryptionType, storage) + await this.importKeyEC(keyId, decryptedPrivateKey, publicKey, storage) + } + static async decrypt(data: Uint8Array, keyId: string, encryptionType: EncryptionType, storage?: KeyStorage): Promise { switch (encryptionType) { case 'cbc': { @@ -346,7 +365,7 @@ export const browserPlatform: Platform = class { return this.aesGcmDecryptWebCrypto(data, key) } case 'rsa': { - // RSA TAGGED + // RSA TAGGED - ecc already an option, but might need to fallback to ecc here const key = await this.loadKeyBytes(keyId, storage) return this.privateDecrypt(data, key) } @@ -359,7 +378,7 @@ export const browserPlatform: Platform = class { } } - // RSA TAGGED + // RSA TAGGED - ecc copy already below static async generateRSAKeyPair(): Promise<{privateKey: Uint8Array; publicKey: Uint8Array}> { let keyPair = await crypto.subtle.generateKey({ name: rsaAlgorithmName, @@ -403,7 +422,7 @@ export const browserPlatform: Platform = class { return await this.mainPublicEncryptEC(messageBytes, pubKey, id, true) } - // RSA TAGGED + // RSA TAGGED - ecc copy already below static publicEncrypt(data: Uint8Array, key: string): Uint8Array { let publicKeyHex = base64ToHex(key); const pos = _asnhex_getPosArrayOfChildren_AtObj(publicKeyHex, 0); @@ -458,7 +477,7 @@ export const browserPlatform: Platform = class { return await this.mainPublicEncryptEC(data, key, id) } - // RSA TAGGED + // RSA TAGGED - ecc copy already below static privateDecrypt(data: Uint8Array, key: Uint8Array): Uint8Array { let pkh = bytesToHex(key); const rsa = new RSAKey(); @@ -546,7 +565,7 @@ export const browserPlatform: Platform = class { } // TODO Not tested - // RSA TAGGED + // RSA TAGGED - unused static async privateSign(data: Uint8Array, key: string): Promise { let _key = await crypto.subtle.importKey("pkcs8", browserPlatform.base64ToBytes(key), @@ -575,7 +594,7 @@ export const browserPlatform: Platform = class { } case 'rsa': { - // RSA TAGGED + // RSA TAGGED - ecc already an option, but might need to fallback to ecc here const publicKey = await this.loadKeyBytes(keyId + '_pub') return this.publicEncrypt(data, this.bytesToBase64(publicKey)) } @@ -969,7 +988,7 @@ type CryptoKeyCache = { } // Web crypto supports aes gcm, aes cbc with padding, and ecc -// RSA TAGGED +// RSA TAGGED - just removes rsa type type CryptoKeyType = Exclude const cryptoKeysCache: CryptoKeyCache = { diff --git a/keeperapi/src/node/platform.ts b/keeperapi/src/node/platform.ts index 3e3bf42..e508b09 100644 --- a/keeperapi/src/node/platform.ts +++ b/keeperapi/src/node/platform.ts @@ -71,7 +71,7 @@ export const nodePlatform: Platform = class { this.importKey(keyId, privateKey, storage) } - // RSA TAGGED + // RSA TAGGED - ecc version above static async importKeyRSA(keyId: string, key: Uint8Array, storage?: KeyStorage): Promise { this.importKey(keyId, key, storage) } @@ -101,7 +101,7 @@ export const nodePlatform: Platform = class { unwrappedKey = await nodePlatform.aesGcmDecrypt(key, unwrappingKey) break; case 'rsa': - // RSA TAGGED + // RSA TAGGED - ecc version already here, might need to fallback to ecc here unwrappedKey = await nodePlatform.privateDecrypt(key, unwrappingKey) break; case 'ecc': @@ -130,7 +130,7 @@ export const nodePlatform: Platform = class { decrypted = await nodePlatform.aesGcmDecrypt(data, key) break; case 'rsa': - // RSA TAGGED + // RSA TAGGED - ecc version already here, might need to fallback to ecc here decrypted = await nodePlatform.privateDecrypt(data, key) break; case 'ecc': @@ -142,7 +142,7 @@ export const nodePlatform: Platform = class { return decrypted } - // RSA TAGGED + // RSA TAGGED - ecc version already here static async generateRSAKeyPair(): Promise<{ privateKey: Uint8Array; publicKey: Uint8Array}> { const rsaKeys = new NodeRSA({b: 2048}); const rsaPublicKey: Buffer = rsaKeys.exportKey('public-der'); @@ -181,7 +181,7 @@ export const nodePlatform: Platform = class { encrypted = await nodePlatform.aesGcmEncrypt(data, key) break; case 'rsa': - // RSA TAGGED + // RSA TAGGED - ecc version already here, might need to fallback to ecc here encrypted = nodePlatform.publicEncrypt(data, this.bytesToBase64(key)) break; case 'ecc': @@ -198,6 +198,8 @@ export const nodePlatform: Platform = class { return this.encrypt(key, wrappingKeyId, encryptionType, storage) } + + // RSA TAGGED - ecc version below static publicEncrypt(data: Uint8Array, key: string): Uint8Array { let publicKey = key[0] === '-' // PEM or DER? ? key @@ -206,7 +208,6 @@ export const nodePlatform: Platform = class { type: 'pkcs1', format: 'der' }) - // RSA TAGGED return crypto.publicEncrypt({ key: publicKey, padding: RSA_PKCS1_PADDING @@ -228,8 +229,8 @@ export const nodePlatform: Platform = class { return await this.mainPublicEncryptEC(data, key, id) } + // RSA TAGGED - ecc version below static privateDecrypt(data: Uint8Array, key: Uint8Array): Uint8Array { - // RSA TAGGED return crypto.privateDecrypt({ key: crypto.createPrivateKey({ key: Buffer.from(key), diff --git a/keeperapi/src/platform.ts b/keeperapi/src/platform.ts index 1c7d1c0..ab24d03 100644 --- a/keeperapi/src/platform.ts +++ b/keeperapi/src/platform.ts @@ -115,7 +115,7 @@ export class KeyWrapper { } } -export type UnwrappedKeyType = 'aes' | 'rsa' +export type UnwrappedKeyType = 'aes' | 'rsa' | 'ecc' export type EncryptionType = 'cbc' | 'gcm' | 'rsa' | 'ecc' diff --git a/keeperapi/src/vaultx.ts b/keeperapi/src/vaultx.ts index ba1d1d1..c9a7288 100644 --- a/keeperapi/src/vaultx.ts +++ b/keeperapi/src/vaultx.ts @@ -292,15 +292,40 @@ const processTeams = async (teams: NN[], storage: VaultStorage, dependenc } } - // RSA TAGGED - teamPrivateKeys[teamUid + '_priv'] = { - data: team.teamPrivateKey, - dataId: teamUid + '_priv', - keyId: teamUid, - encryptionType: 'cbc', - unwrappedType: 'rsa', + switch (team.teamKeyType) { + case Records.RecordKeyType.ENCRYPTED_BY_DATA_KEY: + teamPrivateKeys[teamUid + '_priv'] = { + data: team.teamPrivateKey, + dataId: teamUid + '_priv', + keyId: teamUid, + encryptionType: 'cbc', + unwrappedType: 'rsa', + } + break + // RSA TAGGED - this essentially changes the unwrapped type to ecc. make sure this is fine + case Records.RecordKeyType.ENCRYPTED_BY_PUBLIC_KEY_ECC: + teamPrivateKeys[teamUid + '_priv'] = { + data: team.teamPrivateKey, + dataId: teamUid + '_priv', + keyId: 'pk_ecc', + encryptionType: 'ecc', + unwrappedType: 'aes', + } + break + default: + console.error(`Key ${team.teamKeyType} type for team folder private key ${teamUid} is not supported for team folder decryption`) + break } + // RSA TAGGED - fix is the switch case above. need to confirm the encryptionType and unwrappedType are correct + // teamPrivateKeys[teamUid + '_priv'] = { + // data: team.teamPrivateKey, + // dataId: teamUid + '_priv', + // keyId: teamUid, + // encryptionType: 'cbc', + // unwrappedType: 'rsa', + // } + for (const folderKey of team.sharedFolderKeys as NN[]) { // Empty if team being removed from shared folder if (!folderKey.sharedFolderKey.byteLength) continue @@ -319,7 +344,7 @@ const processTeams = async (teams: NN[], storage: VaultStorage, dependenc unwrappedType: 'aes', } break - // RSA TAGGED + // RSA TAGGED - done, since this is a read operation, we only need to add ecc read below case Records.RecordKeyType.ENCRYPTED_BY_PUBLIC_KEY: teamSharedFolderKeys[folderUid] = { data: folderKey.sharedFolderKey, @@ -329,6 +354,15 @@ const processTeams = async (teams: NN[], storage: VaultStorage, dependenc unwrappedType: 'aes', } break + case Records.RecordKeyType.ENCRYPTED_BY_PUBLIC_KEY_ECC: + teamSharedFolderKeys[folderUid] = { + data: folderKey.sharedFolderKey, + dataId: folderUid, + keyId: 'pk_ecc', + encryptionType: 'ecc', + unwrappedType: 'aes', + } + break default: console.error(`Key ${folderKey.keyType} type for team folder key ${teamUid}/${folderUid} is not supported for team folder decryption`) break @@ -949,7 +983,7 @@ export const syncDown = async (options: SyncDownOptions): Promise => await platform.importKey('data', auth.dataKey!, undefined, true) await platform.importKeyEC('pk_ecc', new Uint8Array(auth.eccPrivateKey!), new Uint8Array(auth.eccPublicKey!), undefined, true) - // RSA TAGGED + // RSA TAGGED - keep here for read purposes await platform.importKeyRSA('pk_rsa', auth.privateKey!, undefined, true) while (true) {