Start working on attachment decryption
This commit is contained in:
@ -3,7 +3,7 @@
|
|||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget -qO- https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz | tar xvz -
|
wget -qO- https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz | tar xvz
|
||||||
mv onepassword_data freddy-2013-12-04.opvault
|
mv onepassword_data freddy-2013-12-04.opvault
|
||||||
pnpm run test
|
pnpm run test
|
||||||
```
|
```
|
||||||
|
11
src/errors.ts
Normal file
11
src/errors.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export abstract class OPVaultError extends Error {}
|
||||||
|
|
||||||
|
export class AssertionError extends OPVaultError {}
|
||||||
|
|
||||||
|
export class HMACAssertionError extends AssertionError {}
|
||||||
|
|
||||||
|
export function invariant(condition: any, message?: string): asserts condition {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError(message)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import invariant from "tiny-invariant"
|
import { invariant } from "./errors"
|
||||||
|
|
||||||
export enum Category {
|
export enum Category {
|
||||||
Login = 1,
|
Login = 1,
|
||||||
|
47
src/vault.ts
47
src/vault.ts
@ -1,4 +1,5 @@
|
|||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
|
import { HMACAssertionError, invariant } from "./errors"
|
||||||
import type { IFileSystem } from "./fs"
|
import type { IFileSystem } from "./fs"
|
||||||
import { OnePasswordFileManager } from "./fs"
|
import { OnePasswordFileManager } from "./fs"
|
||||||
import { i18n } from "./i18n"
|
import { i18n } from "./i18n"
|
||||||
@ -44,7 +45,9 @@ export class Vault {
|
|||||||
*/
|
*/
|
||||||
static async of(path: string, profileName = "default", fs: IFileSystem) {
|
static async of(path: string, profileName = "default", fs: IFileSystem) {
|
||||||
const files = new OnePasswordFileManager(fs, path, profileName)
|
const files = new OnePasswordFileManager(fs, path, profileName)
|
||||||
const profile = JSON.parse(stripText(await files.getProfile(), "var profile=", ";"))
|
const profile = JSON.parse(
|
||||||
|
stripText(await files.getProfile(), /^var profile\s*=/, ";")
|
||||||
|
)
|
||||||
const folders = JSON.parse(stripText(await files.getFolders(), "loadFolders(", ");"))
|
const folders = JSON.parse(stripText(await files.getFolders(), "loadFolders(", ");"))
|
||||||
const bands = new Map<string, Band>()
|
const bands = new Map<string, Band>()
|
||||||
|
|
||||||
@ -111,7 +114,7 @@ export class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove derived keys within the class instance.
|
* Remove derived keys stored within the class instance.
|
||||||
*/
|
*/
|
||||||
lock() {
|
lock() {
|
||||||
this.#master = null!
|
this.#master = null!
|
||||||
@ -171,6 +174,20 @@ function decryptItem(item: EncryptedItem, master: Cipher): Item {
|
|||||||
return JSON.parse(detail.toString("utf-8"))
|
return JSON.parse(detail.toString("utf-8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decryptAttachment(item: Buffer, master: Cipher) {
|
||||||
|
invariant(item.slice(0, 7).toString("utf-8") === "OPCLDA")
|
||||||
|
invariant(
|
||||||
|
item.readIntLE(7, 1) === 1,
|
||||||
|
"The version for this attachment file format is not supported."
|
||||||
|
)
|
||||||
|
|
||||||
|
const metadataSize = item.readIntLE(8, 2)
|
||||||
|
const iconSize = item.readIntLE(12, 3)
|
||||||
|
|
||||||
|
const metadata = JSON.parse(item.slice(16, 16 + metadataSize).toString("utf-8"))
|
||||||
|
const icondata = item.slice(16 + metadataSize, 16 + metadataSize + iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
/** Encryption and MAC */
|
/** Encryption and MAC */
|
||||||
interface Cipher {
|
interface Cipher {
|
||||||
/** Encryption key */
|
/** Encryption key */
|
||||||
@ -179,9 +196,27 @@ interface Cipher {
|
|||||||
hmac: Buffer
|
hmac: Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripText(text: string, prefix: string, suffix: string) {
|
function stripText(text: string, prefix: string | RegExp, suffix: string | RegExp) {
|
||||||
if (text.startsWith(prefix)) text = text.slice(prefix.length)
|
if (typeof prefix === "string") {
|
||||||
if (text.endsWith(suffix)) text = text.slice(0, -suffix.length)
|
if (text.startsWith(prefix)) {
|
||||||
|
text = text.slice(prefix.length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const prefixMatch = text.match(prefix)
|
||||||
|
if (prefixMatch) {
|
||||||
|
text = text.slice(prefixMatch[0].length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof suffix === "string") {
|
||||||
|
if (text.endsWith(suffix)) {
|
||||||
|
text = text.slice(0, -suffix.length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const suffixMatch = text.match(suffix)
|
||||||
|
if (suffixMatch) {
|
||||||
|
text = text.slice(0, -suffixMatch[0].length)
|
||||||
|
}
|
||||||
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +225,6 @@ const splitPlainText = (derivedKey: Buffer): Cipher => ({
|
|||||||
hmac: derivedKey.slice(32, 64),
|
hmac: derivedKey.slice(32, 64),
|
||||||
})
|
})
|
||||||
|
|
||||||
class HMACAssertionError extends Error {}
|
|
||||||
|
|
||||||
function decryptOPData(cipherText: Buffer, cipher: Cipher) {
|
function decryptOPData(cipherText: Buffer, cipher: Cipher) {
|
||||||
const key = cipherText.slice(0, -32)
|
const key = cipherText.slice(0, -32)
|
||||||
assertHMac(key, cipher.hmac, cipherText.slice(-32))
|
assertHMac(key, cipher.hmac, cipherText.slice(-32))
|
||||||
|
Reference in New Issue
Block a user