Update
This commit is contained in:
38
repl.ts
38
repl.ts
@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env ts-node-transpile-only
|
#!/usr/bin/env ts-node-transpile-only
|
||||||
|
import fs from "fs"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import prompts from "prompts"
|
import prompts from "prompts"
|
||||||
import { OnePassword } from "./src/index"
|
import { OnePassword } from "./src/index"
|
||||||
@ -7,24 +8,35 @@ async function main(args: string[]) {
|
|||||||
const instance = new OnePassword({ path: args[0] })
|
const instance = new OnePassword({ path: args[0] })
|
||||||
const profiles = await instance.getProfileNames()
|
const profiles = await instance.getProfileNames()
|
||||||
|
|
||||||
const { profile } = await prompts({
|
// const { profile } = await prompts({
|
||||||
type: "select",
|
// type: "select",
|
||||||
name: "profile",
|
// name: "profile",
|
||||||
choices: profiles.map(t => ({ title: t, value: t })),
|
// choices: profiles.map(t => ({ title: t, value: t })),
|
||||||
message: "Which vault?",
|
// message: "Which vault?",
|
||||||
})
|
// })
|
||||||
|
|
||||||
console.log(chalk`You have chosen {green ${profile}}.`)
|
// console.log(chalk`You have chosen {green ${profile}}.`)
|
||||||
|
const profile = "default"
|
||||||
|
|
||||||
const vault = await instance.getProfile(profile)
|
const vault = await instance.getProfile(profile)
|
||||||
const { password } = await prompts({
|
// const { password } = await prompts({
|
||||||
type: "invisible",
|
// type: "invisible",
|
||||||
name: "password",
|
// name: "password",
|
||||||
message: "Master Password?",
|
// message: "Master Password?",
|
||||||
})
|
// })
|
||||||
|
const password = "freddy"
|
||||||
|
|
||||||
vault.unlock(password)
|
vault.unlock(password)
|
||||||
console.log(vault.overviews.values())
|
|
||||||
|
const d = vault.decryptAttachment(
|
||||||
|
fs.readFileSync(
|
||||||
|
"./freddy-2013-12-04.opvault/default/1C7D72EFA19A4EE98DB7A9661D2F5732_3B94A1F475014E27BFB00C99A42214DF.attachment"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.writeFileSync("./test", d)
|
||||||
|
|
||||||
|
// console.log(vault.overviews.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
main(process.argv.slice(2))
|
main(process.argv.slice(2))
|
||||||
|
27
src/crypto.ts
Normal file
27
src/crypto.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { webcrypto } from "crypto"
|
||||||
|
|
||||||
|
declare module "crypto" {
|
||||||
|
export const webcrypto: Crypto
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pbkdf2(password: string, salt: string, iterations = 1000, length = 256) {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const key = await webcrypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
encoder.encode(password),
|
||||||
|
"PBKDF2",
|
||||||
|
false,
|
||||||
|
["deriveBits"]
|
||||||
|
)
|
||||||
|
const bits = await webcrypto.subtle.deriveBits(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
hash: "SHA-512",
|
||||||
|
salt: encoder.encode(salt),
|
||||||
|
iterations,
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
length
|
||||||
|
)
|
||||||
|
return bits
|
||||||
|
}
|
26
src/types.ts
26
src/types.ts
@ -1,32 +1,46 @@
|
|||||||
import type { FieldType } from "./models"
|
import type { FieldType } from "./models"
|
||||||
|
|
||||||
|
type integer = number
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
lastUpdatedBy: "Dropbox"
|
lastUpdatedBy: "Dropbox"
|
||||||
/** Unix seconds */
|
/** Unix seconds */
|
||||||
updatedAt: number
|
updatedAt: integer
|
||||||
profileName: string
|
profileName: string
|
||||||
salt: string // base64
|
salt: string // base64
|
||||||
masterKey: string // base64
|
masterKey: string // base64
|
||||||
iterations: number // 50000
|
iterations: integer // 50000
|
||||||
uuid: string // 32 chars
|
uuid: string // 32 chars
|
||||||
overviewKey: string // "b3B...IMO52D"
|
overviewKey: string // "b3B...IMO52D"
|
||||||
createdAt: number // Unix seconds
|
createdAt: integer // Unix seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncryptedItem {
|
export interface EncryptedItem {
|
||||||
category: string // "001"
|
category: string // "001"
|
||||||
/** Unix seconds */
|
/** Unix seconds */
|
||||||
created: number
|
created: integer
|
||||||
d: string // "b3BkYXRhMbt"
|
d: string // "b3BkYXRhMbt"
|
||||||
folder: string // 32 chars
|
folder: string // 32 chars
|
||||||
hmac: string // base64
|
hmac: string // base64
|
||||||
k: string // base64
|
k: string // base64
|
||||||
o: string // base64
|
o: string // base64
|
||||||
tx: number // Unix seconds
|
tx: integer // Unix seconds
|
||||||
updated: number // Unix seconds
|
updated: integer // Unix seconds
|
||||||
uuid: string // 32 chars
|
uuid: string // 32 chars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AttachmentMetadata {
|
||||||
|
itemUUID: string
|
||||||
|
contentSize: integer
|
||||||
|
external: boolean
|
||||||
|
updatedAt: integer
|
||||||
|
txTimestamp: integer
|
||||||
|
/** Base64 encoded OPData */
|
||||||
|
overview: string
|
||||||
|
createdAt: integer
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
|
||||||
export type TextField = {
|
export type TextField = {
|
||||||
type: FieldType.Text
|
type: FieldType.Text
|
||||||
value: string
|
value: string
|
||||||
|
33
src/vault.ts
33
src/vault.ts
@ -4,7 +4,14 @@ import type { IFileSystem } from "./fs"
|
|||||||
import { OnePasswordFileManager } from "./fs"
|
import { OnePasswordFileManager } from "./fs"
|
||||||
import { i18n } from "./i18n"
|
import { i18n } from "./i18n"
|
||||||
|
|
||||||
import type { Profile, Band, Overview, EncryptedItem, Item } from "./types"
|
import type {
|
||||||
|
Profile,
|
||||||
|
Band,
|
||||||
|
Overview,
|
||||||
|
EncryptedItem,
|
||||||
|
Item,
|
||||||
|
AttachmentMetadata,
|
||||||
|
} from "./types"
|
||||||
|
|
||||||
type FoldersMap = { [uuid: string]: Band }
|
type FoldersMap = { [uuid: string]: Band }
|
||||||
|
|
||||||
@ -89,11 +96,11 @@ export class Vault {
|
|||||||
unlock(masterPassword: string) {
|
unlock(masterPassword: string) {
|
||||||
const profile = this.#profile
|
const profile = this.#profile
|
||||||
const derivedKey = crypto.pbkdf2Sync(
|
const derivedKey = crypto.pbkdf2Sync(
|
||||||
masterPassword,
|
/* password */ masterPassword,
|
||||||
toBuffer(profile.salt),
|
/* salt */ toBuffer(profile.salt),
|
||||||
profile.iterations,
|
/* iterations */ profile.iterations,
|
||||||
/* keylen */ 64,
|
/* keylen */ 64,
|
||||||
"sha512"
|
/* digest */ "sha512"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cipher = splitPlainText(derivedKey)
|
const cipher = splitPlainText(derivedKey)
|
||||||
@ -113,6 +120,10 @@ export class Vault {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decryptAttachment(buffer: Buffer) {
|
||||||
|
return decryptAttachment(buffer, this.#master!)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove derived keys stored within the class instance.
|
* Remove derived keys stored within the class instance.
|
||||||
*/
|
*/
|
||||||
@ -175,7 +186,10 @@ function decryptItem(item: EncryptedItem, master: Cipher): Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decryptAttachment(item: Buffer, master: Cipher) {
|
function decryptAttachment(item: Buffer, master: Cipher) {
|
||||||
invariant(item.slice(0, 7).toString("utf-8") === "OPCLDA")
|
invariant(
|
||||||
|
item.slice(0, 6).toString("utf-8") === "OPCLDA",
|
||||||
|
"Attachment must start with OPCLDA"
|
||||||
|
)
|
||||||
invariant(
|
invariant(
|
||||||
item.readIntLE(7, 1) === 1,
|
item.readIntLE(7, 1) === 1,
|
||||||
"The version for this attachment file format is not supported."
|
"The version for this attachment file format is not supported."
|
||||||
@ -184,8 +198,13 @@ function decryptAttachment(item: Buffer, master: Cipher) {
|
|||||||
const metadataSize = item.readIntLE(8, 2)
|
const metadataSize = item.readIntLE(8, 2)
|
||||||
const iconSize = item.readIntLE(12, 3)
|
const iconSize = item.readIntLE(12, 3)
|
||||||
|
|
||||||
const metadata = JSON.parse(item.slice(16, 16 + metadataSize).toString("utf-8"))
|
const metadata: AttachmentMetadata = JSON.parse(
|
||||||
|
item.slice(16, 16 + metadataSize).toString("utf-8")
|
||||||
|
)
|
||||||
const icondata = item.slice(16 + metadataSize, 16 + metadataSize + iconSize)
|
const icondata = item.slice(16 + metadataSize, 16 + metadataSize + iconSize)
|
||||||
|
console.log(icondata.slice(0, 8).toString())
|
||||||
|
const iconData = decryptOPData(icondata, master)
|
||||||
|
return iconData
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Encryption and MAC */
|
/** Encryption and MAC */
|
||||||
|
Reference in New Issue
Block a user