This commit is contained in:
aet
2021-07-11 19:26:33 -04:00
parent 69fd89ad6b
commit 99fa963fc0
4 changed files with 98 additions and 26 deletions

38
repl.ts
View File

@ -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
View 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
}

View File

@ -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

View File

@ -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 */