m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Pizzolli <daniele.pizzolli@create-net.org>2016-01-02 16:23:45 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2016-02-05 21:09:56 +0100
commit3220fd7dec896a6ccdc16e857c102237209107ea (patch)
treea396dbb548a2ac859a1cf37fc3ad9193a8c894be
parente646fdd9c797e1a1d625ae2dfcc22372a37b63e2 (diff)
Add importer for Password Exporter for Firefox
To assist the migration from the default Firefox password store to passff. Add also some basic tests. More info at: - <https://addons.mozilla.org/en-US/firefox/addon/password-exporter> - <https://addons.mozilla.org/en-US/firefox/addon/passff>
-rwxr-xr-xcontrib/importers/password-exporter2pass.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/contrib/importers/password-exporter2pass.py b/contrib/importers/password-exporter2pass.py
new file mode 100755
index 0000000..135feda
--- /dev/null
+++ b/contrib/importers/password-exporter2pass.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016 Daniele Pizzolli <daniele.pizzolli@create-net.org>
+#
+# This file is licensed under GPLv2+. Please see COPYING for more
+# information.
+
+"""Import password(s) exported by Password Exporter for Firefox in
+csv format to pass format. Supports Password Exporter format 1.1.
+"""
+
+import argparse
+import base64
+import csv
+import sys
+import subprocess
+
+
+PASS_PROG = 'pass'
+DEFAULT_USERNAME = 'login'
+
+
+def main():
+ "Parse the arguments and run the passimport with appropriate arguments."
+ description = """\
+ Import password(s) exported by Password Exporter for Firefox in csv
+ format to pass format. Supports Password Exporter format 1.1.
+
+ Check the first line of your exported file.
+
+ Must start with:
+
+ # Generated by Password Exporter; Export format 1.1;
+
+ Support obfuscated export (wrongly called encrypted by Password Exporter).
+
+ It should help you to migrate from the default Firefox password
+ store to passff.
+
+ Please note that Password Exporter or passff may have problem with
+ fields containing characters like " or :.
+
+ More info at:
+ <https://addons.mozilla.org/en-US/firefox/addon/password-exporter>
+ <https://addons.mozilla.org/en-US/firefox/addon/passff>
+ """
+ parser = argparse.ArgumentParser(description=description)
+ parser.add_argument(
+ "filepath", type=str,
+ help="The password Exporter generated file")
+ parser.add_argument(
+ "-p", "--prefix", type=str,
+ help="Prefix for pass store path, you may want to use: sites")
+ parser.add_argument(
+ "-d", "--force", action="store_true",
+ help="Call pass with --force option")
+ parser.add_argument(
+ "-v", "--verbose", action="store_true",
+ help="Show pass output")
+ parser.add_argument(
+ "-q", "--quiet", action="store_true",
+ help="No output")
+
+ args = parser.parse_args()
+
+ passimport(args.filepath, prefix=args.prefix, force=args.force,
+ verbose=args.verbose, quiet=args.quiet)
+
+
+def passimport(filepath, prefix=None, force=False, verbose=False, quiet=False):
+ "Import the password from filepath to pass"
+ with open(filepath, 'rb') as csvfile:
+ # Skip the first line if starts with a comment, as usually are
+ # file exported with Password Exporter
+ first_line = csvfile.readline()
+
+ if not first_line.startswith(
+ '# Generated by Password Exporter; Export format 1.1;'):
+ sys.exit('Input format not supported')
+
+ # Auto detect if the file is obfuscated
+ obfuscation = False
+ if first_line.startswith(
+ ('# Generated by Password Exporter; '
+ 'Export format 1.1; Encrypted: true')):
+ obfuscation = True
+
+ if not first_line.startswith('#'):
+ csvfile.seek(0)
+
+ reader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
+ for row in reader:
+ try:
+ username = row['username']
+ password = row['password']
+
+ if obfuscation:
+ username = base64.b64decode(row['username'])
+ password = base64.b64decode(row['password'])
+
+ # Not sure if some fiel can be empty, anyway tries to be
+ # reasonably safe
+ text = '{}\n'.format(password)
+ if row['passwordField']:
+ text += '{}: {}\n'.format(row['passwordField'], password)
+ if username:
+ text += '{}: {}\n'.format(
+ row.get('usernameField', DEFAULT_USERNAME), username)
+ if row['hostname']:
+ text += 'Hostname: {}\n'.format(row['hostname'])
+ if row['httpRealm']:
+ text += 'httpRealm: {}\n'.format(row['httpRealm'])
+ if row['formSubmitURL']:
+ text += 'formSubmitURL: {}\n'.format(row['formSubmitURL'])
+
+ # Remove the protocol prefix for http(s)
+ simplename = row['hostname'].replace(
+ 'https://', '').replace('http://', '')
+
+ # Rough protection for fancy username like ā€œ; rm -Rf /\nā€
+ userpath = "".join(x for x in username if x.isalnum())
+ # TODO add some escape/protection also to the hostname
+ storename = '{}@{}'.format(userpath, simplename)
+ storepath = storename
+
+ if prefix:
+ storepath = '{}/{}'.format(prefix, storename)
+
+ cmd = [PASS_PROG, 'insert', '--multiline']
+
+ if force:
+ cmd.append('--force')
+
+ cmd.append(storepath)
+
+ proc = subprocess.Popen(
+ cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = proc.communicate(text)
+ retcode = proc.wait()
+
+ # TODO: please note that sometimes pass does not return an
+ # error
+ #
+ # After this command:
+ #
+ # pass git config --bool --add pass.signcommits true
+ #
+ # pass import will fail with:
+ #
+ # gpg: skipped "First Last <user@example.com>":
+ # secret key not available
+ # gpg: signing failed: secret key not available
+ # error: gpg failed to sign the data
+ # fatal: failed to write commit object
+ #
+ # But the retcode is still 0.
+ #
+ # Workaround: add the first signing key id explicitly with:
+ #
+ # SIGKEY=$(gpg2 --list-keys --with-colons user@example.com | \
+ # awk -F : '/:s:$/ {printf "0x%s\n", $5; exit}')
+ # pass git config --add user.signingkey "${SIGKEY}"
+
+ if retcode:
+ print 'command {}" failed with exit code {}: {}'.format(
+ " ".join(cmd), retcode, stdout + stderr)
+
+ if not quiet:
+ print 'Imported {}'.format(storepath)
+
+ if verbose:
+ print stdout + stderr
+ except:
+ print 'Error: corrupted line: {}'.format(row)
+
+if __name__ == '__main__':
+ main()