-
Notifications
You must be signed in to change notification settings - Fork 484
feat(vault): import saved MobaXterm passwords from .mobaconf #3045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ export { | |
| import { parseQuickConnectInput } from "./quickConnect"; | ||
| import { findExactHeaderIndex, findHeaderIndex, parseCsv } from "./vaultImport/csvUtils"; | ||
| import { decodeCsvKeyPath, decodeCsvPassphrase } from "./vaultImport/csvCredentialFields"; | ||
| import { attachMobaXtermPasswords } from "./vaultImport/mobaXtermPasswords"; | ||
| export { | ||
| exportHostsToCsvWithStats, | ||
| getVaultCsvTemplate, | ||
|
|
@@ -1104,13 +1105,20 @@ const importFromSecureCrt = (text: string, fileName?: string): VaultImportResult | |
| }; | ||
| }; | ||
|
|
||
| const importFromMobaXterm = (text: string): VaultImportResult => { | ||
| const importFromMobaXterm = ( | ||
| text: string, | ||
| options?: { masterPassword?: string }, | ||
| ): VaultImportResult => { | ||
| const issues: VaultImportIssue[] = []; | ||
| const lines = text.split(/\r?\n/); | ||
|
|
||
| type Entry = { section: string; key: string; value: string }; | ||
| const entries: Entry[] = []; | ||
| const sectionGroups = new Map<string, string | undefined>(); | ||
| const passwordEntries = new Map<string, string>(); | ||
| const credentialEntries = new Map<string, { username: string; ciphertext: string }>(); | ||
| const misc = new Map<string, string>(); | ||
| let hasSesspass = false; | ||
|
|
||
| let section = ""; | ||
| for (const line of lines) { | ||
|
|
@@ -1128,13 +1136,36 @@ const importFromMobaXterm = (text: string): VaultImportResult => { | |
| if (!mKv) continue; | ||
| const key = mKv[1].trim(); | ||
| const value = mKv[2].trim(); | ||
| const isBookmarkSection = /^bookmarks(?:_\d+)?$/i.test(section.trim()); | ||
| const sectionName = section.trim(); | ||
| const isBookmarkSection = /^bookmarks(?:_\d+)?$/i.test(sectionName); | ||
|
|
||
| if (isBookmarkSection && key.toLowerCase() === "subrep") { | ||
| sectionGroups.set(section, normalizeGroupPath(value)); | ||
| continue; | ||
| } | ||
| if (isBookmarkSection && key.toLowerCase() === "imgnum") continue; | ||
| if (/^passwords$/i.test(sectionName) && key && value) { | ||
| passwordEntries.set(key, value); | ||
| continue; | ||
| } | ||
| if (/^credentials$/i.test(sectionName) && key && value) { | ||
| const colon = value.indexOf(":"); | ||
| if (colon > 0) { | ||
| credentialEntries.set(key, { | ||
| username: value.slice(0, colon), | ||
| ciphertext: value.slice(colon + 1), | ||
| }); | ||
| } | ||
| continue; | ||
| } | ||
| if (/^misc$/i.test(sectionName)) { | ||
| misc.set(key.toLowerCase(), value); | ||
| continue; | ||
| } | ||
| if (/^sesspass$/i.test(sectionName)) { | ||
| hasSesspass = true; | ||
| continue; | ||
|
Comment on lines
+1165
to
+1167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a user mistypes the master password, this reduces Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| entries.push({ section, key, value }); | ||
| } | ||
|
|
@@ -1253,7 +1284,18 @@ const importFromMobaXterm = (text: string): VaultImportResult => { | |
| ); | ||
| } | ||
|
|
||
| const { hosts, duplicates } = dedupeHosts(parsedHosts); | ||
| const { hosts: uniqueHosts, duplicates } = dedupeHosts(parsedHosts); | ||
| const attached = attachMobaXtermPasswords(uniqueHosts, { | ||
| passwords: passwordEntries, | ||
| credentials: credentialEntries, | ||
| sessionP: misc.get("sessionp"), | ||
| sysUsername: misc.get("mpsetaccount"), | ||
| sysHostname: misc.get("mpsetcomputer"), | ||
| passwordsInRegistry: misc.get("passwordsinregistry") === "1", | ||
| hasSesspass, | ||
| }, { masterPassword: options?.masterPassword }); | ||
| issues.push(...attached.issues); | ||
| const hosts = attached.hosts; | ||
| const groups = uniq(hosts.map((h) => h.group).filter(Boolean) as string[]); | ||
| return { | ||
| hosts, | ||
|
|
@@ -1266,7 +1308,7 @@ const importFromMobaXterm = (text: string): VaultImportResult => { | |
| export const importVaultHostsFromText = ( | ||
| format: VaultImportFormat, | ||
| text: string, | ||
| options?: { fileName?: string }, | ||
| options?: { fileName?: string; masterPassword?: string }, | ||
| ): VaultImportResult => { | ||
| const input = text ?? ""; | ||
| switch (format) { | ||
|
|
@@ -1279,7 +1321,7 @@ export const importVaultHostsFromText = ( | |
| case "securecrt": | ||
| return importFromSecureCrt(input, options?.fileName); | ||
| case "mobaxterm": | ||
| return importFromMobaXterm(input); | ||
| return importFromMobaXterm(input, options); | ||
| default: { | ||
| const _exhaustive: never = format; | ||
| return _exhaustive; | ||
|
|
@@ -1301,9 +1343,12 @@ export function detectVaultImportFormat(text: string): VaultImportFormat | null | |
| const hasMobaBookmarkSection = /^\[Bookmarks(?:_\d+)?\]\s*$/im.test(input); | ||
| const hasMobaBookmarkMetadata = /^SubRep=.*$/im.test(input) && /^ImgNum=\d+\s*$/im.test(input); | ||
| const hasMobaSessionLine = /^[^=\r\n]+=\s*(?:; logout)?\s*#\d+#\d+%[^%\r\n]+%\d+/im.test(input); | ||
| const hasMobaFullConfig = /^\[Misc\]\s*$/im.test(input) | ||
| && (/^SessionP=/im.test(input) || /^\[Passwords\]\s*$/im.test(input) || /^\[Credentials\]\s*$/im.test(input)); | ||
| if ( | ||
| /\[MobaXterm\]/i.test(input) | ||
| || (hasMobaBookmarkSection && (hasMobaBookmarkMetadata || hasMobaSessionLine)) | ||
| || (hasMobaBookmarkSection && (hasMobaBookmarkMetadata || hasMobaSessionLine || hasMobaFullConfig)) | ||
| || (hasMobaFullConfig && hasMobaBookmarkSection) | ||
| ) { | ||
| return "mobaxterm"; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a MobaXterm master password intentionally begins or ends with whitespace, trimming it here changes the bytes used to derive the SHA-512/AES key, so every saved credential fails to decrypt. Pass the password through exactly as entered and only treat the truly empty string as absent; the domain helper currently trims it again as well.
Useful? React with 👍 / 👎.