Skip to content

Commit

Permalink
Merge pull request AlessandroZ#62 from righettod/master
Browse files Browse the repository at this point in the history
Add new extractors
  • Loading branch information
AlessandroZ authored Aug 1, 2016
2 parents 40b584b + f5fea84 commit 7ecbb84
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 1 deletion.
10 changes: 9 additions & 1 deletion Windows/src/LaZagne/config/manageModules.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
from softwares.sysadmin.winscp import WinSCP
from softwares.sysadmin.coreftp import CoreFTP
from softwares.sysadmin.ftpnavigator import FtpNavigator
from softwares.sysadmin.apachedirectorystudio import ApacheDirectoryStudio
from softwares.sysadmin.opensshforwindows import OpenSSHForWindows
# svn
from softwares.svn.tortoise import Tortoise
# git
from softwares.git.gitforwindows import GitForWindows
# maven
from softwares.maven.mavenrepositories import MavenRepositories
# chats
from softwares.chats.skype import Skype
from softwares.chats.pidgin import Pidgin
Expand All @@ -44,6 +48,7 @@ def get_categories():
'database': {'help': 'SQL clients supported'},
'svn': {'help': 'SVN clients supported'},
'git': {'help': 'GIT clients supported'},
'maven': {'help': 'Maven java build tool'},
'mails': {'help': 'Email clients supported'},
'wifi': {'help': 'Wifi'},
'browsers': {'help': 'Web browsers supported'},
Expand Down Expand Up @@ -81,6 +86,9 @@ def get_modules():
Wifi(),
WifiPass(),
WinSCP(),
GitForWindows()
GitForWindows(),
MavenRepositories(),
ApacheDirectoryStudio(),
OpenSSHForWindows()
]
return moduleNames
Empty file.
137 changes: 137 additions & 0 deletions Windows/src/LaZagne/softwares/maven/mavenrepositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import os
from config.write_output import print_output, print_debug
from config.constant import *
from config.header import Header
from config.moduleInfo import ModuleInfo
import xml.etree.ElementTree as ET

class MavenRepositories(ModuleInfo):

def __init__(self):
options = {'command': '-mvn', 'action': 'store_true', 'dest': 'mavenrepositories', 'help': 'Maven repositories'}
ModuleInfo.__init__(self, 'mavenrepositories', 'maven', options)
# Interesting XML nodes in Maven repository configuration
self.nodes_to_extract = ["id", "username", "password", "privateKey", "passphrase"]
self.settings_namespace = "{http://maven.apache.org/SETTINGS/1.0.0}"

def extract_master_password(self):
"""
Detect if a Master password exists and then extract it.
See https://maven.apache.org/guides/mini/guide-encryption.html#How_to_create_a_master_password
:return: The master password value or None if no master password exists.
"""
master_password = None
master_password_file_location = os.environ.get("USERPROFILE") + "\\.m2\\settings-security.xml"
if os.path.isfile(master_password_file_location):
try:
config = ET.parse(master_password_file_location).getroot()
master_password_node = config.find(".//master")
if master_password_node is not None:
master_password = master_password_node.text
except Exception as e:
print_debug("ERROR", "Cannot retrieve master password '%s'" % e)
master_password = None

return master_password


def extract_repositories_credentials(self):
"""
Extract all repositories's credentials.
See https://maven.apache.org/settings.html#Servers
:return: List of dict in which one dict contains all information for a repository.
"""
repos_creds = []
maven_settings_file_location = os.environ.get("USERPROFILE") + "\\.m2\\settings.xml"
if os.path.isfile(maven_settings_file_location):
try:
settings = ET.parse(maven_settings_file_location).getroot()
server_nodes = settings.findall(".//%sserver" % self.settings_namespace)
for server_node in server_nodes:
creds = {}
for child_node in server_node:
tag_name = child_node.tag.replace(self.settings_namespace, "")
if tag_name in self.nodes_to_extract:
creds[tag_name] = child_node.text.strip()
if len(creds) > 0:
repos_creds.append(creds)
except Exception as e:
print_debug("ERROR", "Cannot retrieve repositories credentials '%s'" % e)
pass

return repos_creds

def use_key_auth(self, creds_dict):
"""
Utility function to determine if a repository use private key authentication.
:param creds_dict: Repository credentials dict
:return: True only if the repositry use private key authentication
"""
state = False
if "privateKey" in creds_dict:
pk_file_location = creds_dict["privateKey"]
pk_file_location = pk_file_location.replace("${user.home}", os.environ.get("USERPROFILE"))
state = os.path.isfile(pk_file_location)

return state


def run(self):
"""
Main function:
- For encrypted password, provides the encrypted version of the password with the master password in order
to allow "LaZagne run initiator" the use the encryption parameter associated with the version of Maven because
encryption parameters can change between version of Maven.
- "LaZagne run initiator" can also use the encrypted password and the master password "AS IS"
in a Maven distribution to access repositories.
See https://github.com/jelmerk/maven-settings-decoder
See https://github.com/sonatype/plexus-cipher/blob/master/src/main/java/org/sonatype/plexus/components/cipher/PBECipher.java
"""
# Print title
title = "MavenRepositories"
Header().title_info(title)

# Extract the master password
master_password = self.extract_master_password()

# Extract all available repositories credentials
repos_creds = self.extract_repositories_credentials()

# Parse and process the list of repositories's credentials
# 3 cases are handled:
# => Authentication using password protected with the master password (encrypted)
# => Authentication using password not protected with the master password (plain text)
# => Authentication using private key
pwd_found = []
for creds in repos_creds:
values = {}
values["Id"] = creds["id"]
values["Username"] = creds["username"]
if not self.use_key_auth(creds):
pwd = creds["password"].strip()
# Case for authentication using password protected with the master password
if pwd.startswith("{") and pwd.endswith("}"):
values["SymetricEncryptionKey"] = master_password
values["PasswordEncrypted"] = pwd
else:
values["Password"] = pwd
else:
# Case for authentication using private key
pk_file_location = creds["privateKey"]
pk_file_location = pk_file_location.replace("${user.home}", os.environ.get("USERPROFILE"))
with open(pk_file_location, "r") as pk_file:
values["PrivateKey"] = pk_file.read()
if "passphrase" in creds:
values["Passphrase"] = creds["passphrase"]
pwd_found.append(values)

# Print the results
print_output(title, pwd_found)
66 changes: 66 additions & 0 deletions Windows/src/LaZagne/softwares/sysadmin/apachedirectorystudio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import os
from config.write_output import print_output, print_debug
from config.constant import *
from config.header import Header
from config.moduleInfo import ModuleInfo
import xml.etree.ElementTree as ET

class ApacheDirectoryStudio(ModuleInfo):

def __init__(self):
options = {'command': '-ads', 'action': 'store_true', 'dest': 'apachedirectorystudio', 'help': 'Apache Directory Studio'}
ModuleInfo.__init__(self, 'apachedirectorystudio', 'sysadmin', options)
# Interesting XML attributes in ADS connection configuration
self.attr_to_extract = ["host", "port", "bindPrincipal", "bindPassword", "authMethod"]


def extract_connections_credentials(self):
"""
Extract all connection's credentials.
:return: List of dict in which one dict contains all information for a connection.
"""
repos_creds = []
connection_file_location = os.environ.get("USERPROFILE") + "\\.ApacheDirectoryStudio\\.metadata\\.plugins\\org.apache.directory.studio.connection.core\\connections.xml"
if os.path.isfile(connection_file_location):
try:
connections = ET.parse(connection_file_location).getroot()
connection_nodes = connections.findall(".//connection")
for connection_node in connection_nodes:
creds = {}
for connection_attr_name in connection_node.attrib:
if connection_attr_name in self.attr_to_extract:
creds[connection_attr_name] = connection_node.attrib[connection_attr_name].strip()
if len(creds) > 0:
repos_creds.append(creds)
except Exception as e:
print_debug("ERROR", "Cannot retrieve connections credentials '%s'" % e)
pass

return repos_creds


def run(self):
"""
Main function
"""
# Print title
title = "ApacheDirectoryStudio"
Header().title_info(title)

# Extract all available connections credentials
repos_creds = self.extract_connections_credentials()

# Parse and process the list of connections credentials
pwd_found = []
for creds in repos_creds:
values = {}
values["Host"] = creds["host"]
values["Port"] = creds["port"]
values["BindPrincipal"] = creds["bindPrincipal"]
values["BindPassword"] = creds["bindPassword"]
values["AuthenticationMethod"] = creds["authMethod"]
pwd_found.append(values)

# Print the results
print_output(title, pwd_found)
94 changes: 94 additions & 0 deletions Windows/src/LaZagne/softwares/sysadmin/opensshforwindows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from os import environ, walk
from os.path import isdir, isfile, join
from config.write_output import print_output, print_debug
from config.constant import *
from config.header import Header
from config.moduleInfo import ModuleInfo
from Crypto.PublicKey import RSA
from Crypto.PublicKey import DSA

class OpenSSHForWindows(ModuleInfo):

def __init__(self):
options = {'command': '-winssh', 'action': 'store_true', 'dest': 'opensshforwindows', 'help': 'OpenSSH for Windows'}
ModuleInfo.__init__(self, 'opensshforwindows', 'sysadmin', options)
self.key_files_location = environ.get("USERPROFILE") + "\\.ssh"

def is_private_key_unprotected(self, key_content_encoded, key_algorithm):
"""
Check if the private key can be loaded without specifying any passphrase.
PyCrypto >= 2.6.1 required in order to have the method importKey() in DSA class.
:param key_content_encoded: Encoded content of the private key to test
:param key_algorithm: Algorithm of the key (RSA or DSA)
:return: True only if the key can be successfuly loaded and is usable
"""
state = False
try:
# Try to load it
if key_algorithm == "RSA":
key = RSA.importKey(key_content_encoded)
else:
key = DSA.importKey(key_content_encoded)
# Validate loading
state = (key is not None and key.can_sign() and key.has_private())
except Exception as e:
print_debug("ERROR", "Cannot validate key protection '%s'" % e)
state = False
pass

return state

def extract_private_keys_unprotected(self):
"""
Extract all DSA/RSA private keys that are not protected with a passphrase.
:return: List of encoded key (key file content)
"""
keys = []
if isdir(self.key_files_location):
for (dirpath, dirnames, filenames) in walk(self.key_files_location, followlinks=True):
for f in filenames:
key_file_path = join(dirpath, f)
if isfile(key_file_path):
try:
# Read encoded content of the key
with open(key_file_path, "r") as key_file:
key_content_encoded = key_file.read()
# Determine the type of the key (public/private) and what is it algorithm
if "DSA PRIVATE KEY" in key_content_encoded:
key_algorithm = "DSA"
elif "RSA PRIVATE KEY" in key_content_encoded:
key_algorithm = "RSA"
else:
key_algorithm = None
# Check if the key can be loaded (used) without passphrase
if key_algorithm is not None and self.is_private_key_unprotected(key_content_encoded,
key_algorithm):
keys.append(key_content_encoded)
except Exception as e:
print_debug("ERROR", "Cannot load key file '%s' '%s'" % (key_file_path, e))
pass

return keys

def run(self):
"""
Main function
"""
# Print title
title = "OpenSSHForWindows"
Header().title_info(title)

# Extract all DSA/RSA private keys that are not protected with a passphrase
unprotected_private_keys = self.extract_private_keys_unprotected()

# Parse and process the list of keys
key_found = []
for key in unprotected_private_keys:
values = {"PrivateKey": key}
key_found.append(values)

# Print the results
print_output(title, key_found)

0 comments on commit 7ecbb84

Please sign in to comment.