|
| 1 | +import { asCodec, asObject, asOptional, asString, Cleaner } from 'cleaners' |
| 2 | +import { justFolders, navigateDisklet } from 'disklet' |
| 3 | + |
| 4 | +import { fixUsername } from '../../client-side' |
| 5 | +import { asBase32, asEdgeBox, asEdgeSnrp } from '../../types/server-cleaners' |
| 6 | +import { EdgeIo } from '../../types/types' |
| 7 | +import { base58, utf8 } from '../../util/encoding' |
| 8 | +import { makeJsonFile } from '../../util/file-helpers' |
| 9 | +import { userIdSnrp } from '../scrypt/scrypt-selectors' |
| 10 | +import { LoginStash } from './login-stash' |
| 11 | + |
| 12 | +/** |
| 13 | + * Reads legacy Airbitz login stashes from disk. |
| 14 | + */ |
| 15 | +export async function loadAirbitzStashes( |
| 16 | + io: EdgeIo, |
| 17 | + avoidUsernames: Set<string> |
| 18 | +): Promise<LoginStash[]> { |
| 19 | + const out: LoginStash[] = [] |
| 20 | + |
| 21 | + const paths = await io.disklet.list('Accounts').then(justFolders) |
| 22 | + for (const path of paths) { |
| 23 | + const folder = navigateDisklet(io.disklet, path) |
| 24 | + const [ |
| 25 | + carePackage, |
| 26 | + loginPackage, |
| 27 | + otp, |
| 28 | + pin2Key, |
| 29 | + recovery2Key, |
| 30 | + usernameJson |
| 31 | + ] = await Promise.all([ |
| 32 | + await carePackageFile.load(folder, 'CarePackage.json'), |
| 33 | + await loginPackageFile.load(folder, 'LoginPackage.json'), |
| 34 | + await otpFile.load(folder, 'OtpKey.json'), |
| 35 | + await pin2KeyFile.load(folder, 'Pin2Key.json'), |
| 36 | + await recovery2KeyFile.load(folder, 'Recovery2Key.json'), |
| 37 | + await usernameFile.load(folder, 'UserName.json') |
| 38 | + ]) |
| 39 | + |
| 40 | + if (usernameJson == null) continue |
| 41 | + const username = fixUsername(usernameJson.userName) |
| 42 | + if (avoidUsernames.has(username)) continue |
| 43 | + const userId = await io.scrypt( |
| 44 | + utf8.parse(username), |
| 45 | + userIdSnrp.salt_hex, |
| 46 | + userIdSnrp.n, |
| 47 | + userIdSnrp.r, |
| 48 | + userIdSnrp.p, |
| 49 | + 32 |
| 50 | + ) |
| 51 | + |
| 52 | + // Assemble a modern stash object: |
| 53 | + const stash: LoginStash = { |
| 54 | + appId: '', |
| 55 | + loginId: userId, |
| 56 | + pendingVouchers: [], |
| 57 | + username |
| 58 | + } |
| 59 | + if (carePackage != null && loginPackage != null) { |
| 60 | + stash.passwordKeySnrp = carePackage.SNRP2 |
| 61 | + stash.passwordBox = loginPackage.EMK_LP2 |
| 62 | + stash.syncKeyBox = loginPackage.ESyncKey |
| 63 | + stash.passwordAuthBox = loginPackage.ELP1 |
| 64 | + } |
| 65 | + if (otp != null) { |
| 66 | + stash.otpKey = otp.TOTP |
| 67 | + } |
| 68 | + if (pin2Key != null) { |
| 69 | + stash.pin2Key = pin2Key.pin2Key |
| 70 | + } |
| 71 | + if (recovery2Key != null) { |
| 72 | + stash.recovery2Key = recovery2Key.recovery2Key |
| 73 | + } |
| 74 | + |
| 75 | + out.push(stash) |
| 76 | + } |
| 77 | + |
| 78 | + return out |
| 79 | +} |
| 80 | + |
| 81 | +/** |
| 82 | + * A string of base58-encoded binary data. |
| 83 | + */ |
| 84 | +const asBase58: Cleaner<Uint8Array> = asCodec( |
| 85 | + raw => base58.parse(asString(raw)), |
| 86 | + clean => base58.stringify(clean) |
| 87 | +) |
| 88 | + |
| 89 | +const carePackageFile = makeJsonFile( |
| 90 | + asObject({ |
| 91 | + SNRP2: asEdgeSnrp, // passwordKeySnrp |
| 92 | + SNRP3: asOptional(asEdgeSnrp), // recoveryKeySnrp |
| 93 | + SNRP4: asOptional(asEdgeSnrp), // questionKeySnrp |
| 94 | + ERQ: asOptional(asEdgeBox) // questionBox |
| 95 | + }) |
| 96 | +) |
| 97 | + |
| 98 | +const loginPackageFile = makeJsonFile( |
| 99 | + asObject({ |
| 100 | + EMK_LP2: asEdgeBox, // passwordBox |
| 101 | + EMK_LRA3: asOptional(asEdgeBox), // recoveryBox |
| 102 | + |
| 103 | + ESyncKey: asEdgeBox, // syncKeyBox |
| 104 | + ELP1: asEdgeBox // passwordAuthBox |
| 105 | + }) |
| 106 | +) |
| 107 | + |
| 108 | +const otpFile = makeJsonFile(asObject({ TOTP: asBase32 })) |
| 109 | +const pin2KeyFile = makeJsonFile(asObject({ pin2Key: asBase58 })) |
| 110 | +const recovery2KeyFile = makeJsonFile(asObject({ recovery2Key: asBase58 })) |
| 111 | +const usernameFile = makeJsonFile(asObject({ userName: asString })) |
0 commit comments