Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions keeperapi/src/browser/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const browserPlatform: Platform = class {
static async importKey(keyId: string, key: Uint8Array, storage?: KeyStorage, canExport?: boolean): Promise<void> {
// An AES key for one of our Keeper objects can be used for either CBC or GCM operations.
// Since CryptoKeys are bound to a particular algorithm, we need to keep a copy for each.
const extractable = !!canExport
const extractable = typeof storage?.saveKeyBytes === 'function' ? true : !!canExport
const cbcKey = await this.aesCbcImportKey(key, extractable)
const gcmKey = await this.aesGcmImportKey(key, extractable)
cryptoKeysCache['cbc'][keyId] = cbcKey
Expand All @@ -96,7 +96,8 @@ export const browserPlatform: Platform = class {
if (storage.saveObject) {
await storage.saveObject(this.getStorageKeyId(keyId, 'cbc'), cbcKey)
await storage.saveObject(this.getStorageKeyId(keyId, 'gcm'), gcmKey)
} else {
}
if (storage.saveKeyBytes) {
await storage.saveKeyBytes(keyId, key)
}
}
Expand All @@ -114,7 +115,8 @@ export const browserPlatform: Platform = class {
if (storage) {
if (storage.saveObject) {
await storage.saveObject(this.getStorageKeyId(keyId, 'ecc'), key)
} else {
}
if (storage.saveKeyBytes) {
const jwk = await crypto.subtle.exportKey('jwk', key)
const keyBytes = this.stringToBytes(JSON.stringify(jwk))
await storage.saveKeyBytes(keyId, keyBytes)
Expand Down Expand Up @@ -168,10 +170,9 @@ export const browserPlatform: Platform = class {
if (storage?.getObject) {
const storageKeyId = this.getStorageKeyId(keyId, keyType)
const storedKey = await storage.getObject<CryptoKey>(storageKeyId)
if (!storedKey) {
throw new Error('Unable to load crypto key ' + keyId)
if (storedKey) {
return storedKey
}
return storedKey
}

const keyBytes = await this.loadKeyBytes(keyId, storage)
Expand Down Expand Up @@ -212,6 +213,20 @@ export const browserPlatform: Platform = class {
return key
}

static async ensureAesKeyLoaded(keyId: string, storage?: KeyStorage): Promise<boolean> {
if (cryptoKeysCache['cbc'][keyId] && cryptoKeysCache['gcm'][keyId]) {
return true
}
if (storage?.getKeyBytes) {
const keyBytes = await storage.getKeyBytes(keyId)
if (keyBytes) {
await this.importKey(keyId, keyBytes, storage, true)
return true
}
}
return false
}

static async unwrapKeys(keys: UnwrapKeyMap, storage?: KeyStorage): Promise<void> {
if (workerPool) {
try {
Expand Down Expand Up @@ -288,17 +303,15 @@ export const browserPlatform: Platform = class {
await this.unwrapRSAKey(key, keyId, unwrappingKeyId, encryptionType, storage)
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,
// or if a record belongs to multiple shared folders. So, short circuit when possible for a performance improvement
if (await this.ensureAesKeyLoaded(keyId, storage)) {
return
}

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]) {
if (cryptoKeysCache['ecc'][keyId]) {
return
}

Expand Down Expand Up @@ -365,7 +378,8 @@ export const browserPlatform: Platform = class {
break
}

const canExtract: boolean = storage?.saveObject ? !!canExport : true
const mustPersistKeyBytes = typeof storage?.saveKeyBytes === 'function'
const canExtract: boolean = mustPersistKeyBytes ? true : storage?.saveObject ? !!canExport : true
const keyUsages: KeyUsage[] = ['encrypt', 'decrypt', 'unwrapKey', 'wrapKey']

const gcmKey = await crypto.subtle.unwrapKey(
Expand Down Expand Up @@ -394,7 +408,8 @@ export const browserPlatform: Platform = class {
if (storage.saveObject) {
await storage.saveObject(this.getStorageKeyId(keyId, 'cbc'), cbcKey)
await storage.saveObject(this.getStorageKeyId(keyId, 'gcm'), gcmKey)
} else {
}
if (storage.saveKeyBytes) {
const keyBuffer = await crypto.subtle.exportKey('raw', gcmKey)
await storage.saveKeyBytes(keyId, new Uint8Array(keyBuffer))
}
Expand Down
138 changes: 138 additions & 0 deletions keeperapi/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,25 @@ export const enterpriseAllocateIdsCommand = (
): RestCommand<EnterpriseAllocateIdsRequest, KeeperResponse & { base_id: number; number_allocated: number }> =>
createCommand(request, 'enterprise_allocate_ids')

export type NodeEditRequest = {
node_id: number
parent_id?: number
encrypted_data: string
}

export const nodeAddCommand = (request: NodeEditRequest): RestCommand<NodeEditRequest, KeeperResponse> =>
createCommand(request, 'node_add')

export const nodeUpdateCommand = (request: NodeEditRequest): RestCommand<NodeEditRequest, KeeperResponse> =>
createCommand(request, 'node_update')

export type NodeDeleteRequest = {
node_id: number
}

export const nodeDeleteCommand = (request: NodeDeleteRequest): RestCommand<NodeDeleteRequest, KeeperResponse> =>
createCommand(request, 'node_delete')

export type RoleEditRequest = {
role_id: number
node_id: number
Expand All @@ -232,6 +251,115 @@ export type RoleDeleteRequest = {
export const roleDeleteCommand = (request: RoleDeleteRequest): RestCommand<RoleDeleteRequest, KeeperResponse> =>
createCommand(request, 'role_delete')

export type RoleUserAddRequest = {
role_id: number
enterprise_user_id: number
tree_key?: string
tree_key_type?: string | number
role_admin_key?: string
role_admin_key_type?: string | number
}

export const roleUserAddCommand = (request: RoleUserAddRequest): RestCommand<RoleUserAddRequest, KeeperResponse> =>
createCommand(request, 'role_user_add')

export type RoleUserRemoveRequest = {
role_id: number
enterprise_user_id: number
}

export const roleUserRemoveCommand = (
request: RoleUserRemoveRequest
): RestCommand<RoleUserRemoveRequest, KeeperResponse> => createCommand(request, 'role_user_remove')

export type RoleManagedNodeTreeKey = {
enterprise_user_id: number
tree_key: string
tree_key_type?: string | number
}

export type RoleManagedNodeAddResponse = KeeperResponse & {
missing_tree_keys?: Record<string, string>
missing_ecc_tree_keys?: Record<string, string>
}

export type RoleManagedNodeAddRequest = {
role_id: number
managed_node_id: number
cascade_node_management?: boolean
tree_keys?: RoleManagedNodeTreeKey[]
}

export const roleManagedNodeAddCommand = (
request: RoleManagedNodeAddRequest
): RestCommand<RoleManagedNodeAddRequest, RoleManagedNodeAddResponse> => createCommand(request, 'role_managed_node_add')

export type RoleManagedNodeUpdateRequest = {
role_id: number
managed_node_id: number
cascade_node_management?: boolean
tree_keys?: RoleManagedNodeTreeKey[]
}

export const roleManagedNodeUpdateCommand = (
request: RoleManagedNodeUpdateRequest
): RestCommand<RoleManagedNodeUpdateRequest, KeeperResponse> => createCommand(request, 'role_managed_node_update')

export type RoleManagedNodeRemoveRequest = {
role_id: number
managed_node_id: number
}

export const roleManagedNodeRemoveCommand = (
request: RoleManagedNodeRemoveRequest
): RestCommand<RoleManagedNodeRemoveRequest, KeeperResponse> => createCommand(request, 'role_managed_node_remove')

export type ManagedNodeRoleKey = {
enterprise_user_id: number
role_key: string
tree_key_type?: string | number
}

export type ManagedNodeMspKey = {
mc_enterprise_id: number
tree_key: string
}

export type ManagedNodePrivilegeAddRequest = {
role_id: number
managed_node_id: number
privilege: string
role_key_enc_with_tree_key?: string
role_public_key?: string
role_private_key?: string
role_keys?: ManagedNodeRoleKey[]
msp_keys?: ManagedNodeMspKey[]
}

export type ManagedNodePrivilegeAddResponse = KeeperResponse & {
status?: {
missing_keys?: Record<string, string>
invalid_users?: number[]
cant_be_pending?: number[]
}
}

export const managedNodePrivilegeAddCommand = (
request: ManagedNodePrivilegeAddRequest
): RestCommand<ManagedNodePrivilegeAddRequest, ManagedNodePrivilegeAddResponse> =>
createCommand(request, 'managed_node_privilege_add')

export type ManagedNodePrivilegeRemoveRequest = {
role_id: number
managed_node_id: number
privilege: string
}

export const managedNodePrivilegeRemoveCommand = (
request: ManagedNodePrivilegeRemoveRequest
): RestCommand<ManagedNodePrivilegeRemoveRequest, KeeperResponse> =>
createCommand(request, 'managed_node_privilege_remove')

export type MoveRequest = {
to_type: 'user_folder' | 'shared_folder' | 'shared_folder_folder'
to_uid?: string
Expand Down Expand Up @@ -379,6 +507,16 @@ export const teamEnterpriseUserRemoveCommand = (
request: TeamUserCommandRequest
): RestCommand<TeamUserCommandRequest, KeeperResponse> => createCommand(request, 'team_enterprise_user_remove')

export type TeamEnterpriseUserUpdateRequest = {
team_uid: string
enterprise_user_id: number
user_type: number
}

export const teamEnterpriseUserUpdateCommand = (
request: TeamEnterpriseUserUpdateRequest
): RestCommand<TeamEnterpriseUserUpdateRequest, KeeperResponse> => createCommand(request, 'team_enterprise_user_update')

export type TeamGetKeysRequest = {
teams: string[]
}
Expand Down
5 changes: 5 additions & 0 deletions keeperapi/src/restMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,11 @@ export const enterpriseUsersLockMessage = (
): RestMessage<Enterprise.ILockUsersRequest, Enterprise.ILockUsersResponse> =>
createMessage(data, 'enterprise/enterprise_users_lock', Enterprise.LockUsersRequest, Enterprise.LockUsersResponse)

export const disableTwoFaMessage = (
data: Enterprise.IEnterpriseUserIds
): RestInMessage<Enterprise.IEnterpriseUserIds> =>
createInMessage(data, 'enterprise/disable_two_fa', Enterprise.EnterpriseUserIds)

export const setV2AlternatePasswordMessage = (
data: Authentication.IUserAuthRequest
): RestInMessage<Authentication.IUserAuthRequest> =>
Expand Down
7 changes: 3 additions & 4 deletions keeperapi/src/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1597,15 +1597,14 @@ export const syncDown = async (options: SyncDownOptions): Promise<SyncResult> =>
const dToken = await storage.get('continuationToken')
let continuationToken = dToken ? platform.base64ToBytes(dToken.token) : undefined

await platform.importKey('data', auth.dataKey!, undefined, true)
await platform.importKey('data', auth.dataKey!, storage, true)
await platform.importKeyEC(
'pk_ecc',
new Uint8Array(auth.eccPrivateKey!),
new Uint8Array(auth.eccPublicKey!),
undefined,
true
storage
)
await platform.importKeyRSA('pk_rsa', auth.privateKey!, undefined, true)
await platform.importKeyRSA('pk_rsa', auth.privateKey!, storage)

while (true) {
const msg = syncDownMessage({
Expand Down
Loading