def gen_putty(self): # set filenames file_key = self.filenames["private"].format(self.tmpfile) file_putty = self.filenames["private_putty"].format(self.tmpfile) # merge arguments and command cmd = [ self.opts.get('sshkey_bin_puttygen'), "{}".format(file_key), "-O", "private", "--old-passphrase", "{}".format(self.pwdfile), "--new-passphrase", "{}".format(self.pwdfile), "-o", "{}".format(file_putty), "-C", "{}".format(self.opts.get('sshkey_comment')) ] proc = Process("puttygen", cmd).run() # set result result = {} result['private_putty'] = hlp.get_file_contents(file_putty) # generate putty fingerprint cmd = [ self.opts.get('sshkey_bin_puttygen'), "{}".format(file_putty), "-O", "fingerprint" ] proc = Process("puttygen", cmd).run() stdout = proc.getstdout() result['fingerprint_putty'] = "\n".join(stdout) # return result return result
def gen_fingerprints(self, fptype, pubfile): # generate fingerprints - output sent to stdout cmd = [ self.opts.get('sshhostkey_bin_keygen'), # full path to binary "-l", # list fingerprint "-v", # list visual fingerprint "-E{}".format(fptype), # fingerprint hash algorithm "-f{}".format(pubfile) ] # full path to public key # run process and catch stdout proc = Process("ssh-keygen", cmd).run() stdout = proc.getstdout() fpline = stdout.pop(0) # set results result = {} result['fingerprint_{}'.format(fptype)] = fpline result['fingerprint_{}_clean'.format( fptype)] = hlp.extract_fingerprint(fpline) result['fingerprint_{}_art'.format(fptype)] = "\n".join(stdout) # return return result
def check_versions(self): msg.vvvv("checking gnupg and libgcrypt versions") # set command cmd = [self.opts.get('gpgkey_bin')] args = ["--version"] cmd += args # run subprocess proc = Process("gpg", cmd).run() stdout = proc.getstdout() # find gpg version regex_gpg = r"gpg\s+\(GnuPG\)\s+(\d+\.\d+\.?\d*)$" match_gpg = re.match(regex_gpg, stdout[0]) # sanity check if re.compile(regex_gpg).groups < 1: msg.fail( "could not find a valid gpg version number in string [{}]". format(stdout[0])) # find libgcrypt version regex_libgcrypt = r"libgcrypt\s+(\d+\.\d+\.?\d*)$" match_libgcrypt = re.match(regex_libgcrypt, stdout[1]) # sanity check if re.compile(regex_libgcrypt).groups < 1: msg.fail( "could not find a valid libgcrypt version number in string [{}]" .format(stdout[1])) # check versions versions = { 'gpg': match_gpg.group(1), 'libgcrypt': match_libgcrypt.group(1), } req_gpg = '2.1.17' req_libgcrypt = '1.8.1' # sanity check if version.parse( versions['gpg']) < version.parse(req_gpg) or version.parse( versions['libgcrypt']) < version.parse(req_libgcrypt): msg.fail( "gpg version [{}] and libgcrypt version [{}] are required; [{}] and [{}] given" .format(req_gpg, req_libgcrypt, versions['gpg'], versions['libgcrypt'])) else: msg.vvvv("gnupg version [{}] and libgcrypt version [{}] detected". format(versions['gpg'], versions['libgcrypt'])) return True
def gen_pkcs8(self): # set filenames file_key = self.filenames["private"].format(self.tmpfile) file_pub = self.filenames["public"].format(self.tmpfile) file_pkcs8 = self.filenames["private_pkcs8"].format(self.tmpfile) # create password file # BEWARE: when supplying the SAME password file for both a -passin file:filename and -passout file:filename parameter, # openssl will use the first line as the passin password and the second line as the passout parameter # if the password file does not contain a second line, openssl will fail with the following message: # Error reading password from BIO # Error getting passwords # to avoid this, we write the same password to the password file twice. Since we're only using openssl to convert a # single key into multiple formats, this does not pose a security risk # to avoid confusing and prevent potential error with other processes requiring the password file, we generate a seperate one file_pwd = hlp.create_pwd_file( self.opts.get_tmp_filename(), "{}\n{}".format(self.passphrase, self.passphrase)) # merge arguments and command cmd = [self.opts.get('sshkey_bin_openssl')] args = [ "pkcs8", "-topk8", "-v2", "des3", "-in", "{}".format(file_key), "-passin", "file:{}".format(file_pwd), "-out", "{}".format(file_pkcs8), "-passout", "file:{}".format(file_pwd) ] cmd += args # run subprocess proc = Process("openssl", cmd).run() # read file contents result = {} result['private_pkcs8'] = hlp.get_file_contents(file_pkcs8) # generate public key cmd = [ self.opts.get('sshkey_bin_keygen'), "-e", "-mPKCS8", "-f{}".format(file_pub) ] proc = Process("ssh-keygen", cmd).run() stdout = proc.getstdout() result['public_pkcs8'] = "\n".join(stdout) # return result return result
def gen_pem(self): # set filenames file_pub = self.filenames["public"].format(self.tmpfile) # merge arguments and command cmd = [ self.opts.get('sshkey_bin_keygen'), "-e", "-m", "PEM", "-f{}".format(file_pub) ] proc = Process("ssh-keygen", cmd).run() # set result result = {} stdout = proc.getstdout() result['public_pem'] = "\n".join(stdout) # return result return result
def gen_rfc4716(self): # set filenames file_pub = self.filenames["public"].format(self.tmpfile) # merge arguments and command # setting a custom comment for this keytype is not supported cmd = [ self.opts.get('sshkey_bin_keygen'), "-e", "-m", "RFC4716", "-f{}".format(file_pub) ] proc = Process("ssh-keygen", cmd).run() # set result result = {} stdout = proc.getstdout() result['public_rfc4716'] = "\n".join(stdout) # return result return result
def check_versions(self): # only check puttygen version if puttygen is enabled if self.opts.get('sshkey_putty_enabled'): msg.vvv("checking puttygen version") # merge arguments and command cmd = [ self.opts.get('sshkey_bin_puttygen'), "--version", ] proc = Process("puttygen", cmd).run() # regex version from first line stdout = proc.getstdout() regex_putty = r"^.+(\d\.\d+)" match_putty = re.match(regex_putty, stdout[0]) # sanity check if re.compile(regex_putty).groups < 1: msg.fail( "could not find a valid puttygen version number in string [{}]" .format(stdout[0])) # check versions versions = {'puttygen': match_putty.group(1)} req_puttygen = '0.72' # sanity check if version.parse( versions['puttygen']) < version.parse(req_puttygen): msg.fail( "puttygen version [{}] is required; [{}] given".format( req_puttygen, versions['puttygen'])) else: msg.vvv("puttygen version [{}] detected".format( versions['puttygen'])) return True
def gen_bubblebabble(self, pubfile): # generate fingerprints - output sent to stdout cmd = [ self.opts.get('sshhostkey_bin_keygen'), # full path to binary "-B", # list bubble babble fingerprint "-f{}".format(pubfile) ] # full path to public key # run process and catch stdout proc = Process("ssh-keygen", cmd).run() stdout = proc.getstdout() bbline = stdout.pop(0) # set results result = {} result['fingerprint_bubblebabble'] = bbline result['fingerprint_bubblebabble_clean'] = hlp.extract_bubblebabble( bbline) # return return result
def gen_dns(self, pubfile): # generate dns records - output sent to stdout cmd = [ self.opts.get('sshhostkey_bin_keygen'), # full path to binary "-r{}".format( self.opts.get('hostname')), # hostname of dns records "-f{}".format(pubfile) ] # full path to public key # # SSHFP records consist of three things: # # Algorithm # 1 - RSA # 2 - DSA # 3 - ECDSA # 4 - Ed25519 # Fingerprint type # 1 - SHA-1 # 2 - SHA-256 # Fingerprint (in hex) # # EXAMPLE: # [root@localhost ~]# ssh-keygen -r my.domain.com -f ./mykey.pub # example.com IN SSHFP 4 1 de3dec1fb5eadf130396a60607f5baa6ace831e8 # example.com IN SSHFP 4 2 a0a4d61227b08addd0d685ded8e475c396831e6d91ab3ac1adf536425fab431f # # run process and catch stdout proc = Process("ssh-keygen", cmd).run() stdout = proc.getstdout() # set known algorithms and fingerprint types algorithms = { "1": "rsa", "2": "dsa", "3": "ecdsa", "4": "ed25519", } fptypes = { "1": "sha1", "2": "sha256", } # find algorithm and fptype for each output line result = {} for l in stdout: # split items on space and extract info items = l.split(" ") alg = algorithms[ items[3]] # not added to key, as that is done elsewhere fpt = fptypes[items[4]] clean = items[3:] dnsname = "dns_{}".format(fpt) dnsclean = "dns_{}_clean".format(fpt) # set dns records result[dnsname] = " ".join(items) result[dnsclean] = " ".join(clean) # return return result
def get_key_info(self, keytype, usage, mapping, fpr=None): msg.vvvv( "attempt to extract key info from generated keys [{}] with usage [{}]" .format(keytype, usage)) # create the command to generate a new master key cmd = [self.opts.get('gpgkey_bin')] listargs = self.get_seclist_args( ) if keytype == 'secret' else self.get_publist_args() cmd += listargs # run subprocess; catch the output but don't fail on stderr as gnupg outputs key creation details to stderr instead of stdout proc = Process("gpg", cmd, failonstderr=False).run() # # SAMPLE DATA # # sec:u:256:22:41343326127FD34F:1566067845:::u:::cC:::+::ed25519:::0: # fpr:::::::::0D18E4B6B2698560729D00CE41343326127FD34F: # grp:::::::::54AA357FD85BA4D4B7CE86016A3734F00B1BDD07: # uid:u::::1566067845::00B9F0DC33EE293CC1E687FFA54A5EA805FD78F8::testing145 (TESTINGCOMM) <*****@*****.**>::::::::::0: # # # line types # # *** Field 1 - Type of record # # - pub :: Public key # - crt :: X.509 certificate # - crs :: X.509 certificate and private key available # - sub :: Subkey (secondary key) # - sec :: Secret key # - ssb :: Secret subkey (secondary key) # - uid :: User id # - uat :: User attribute (same as user id except for field 10). # - sig :: Signature # - rev :: Revocation signature # - rvs :: Revocation signature (standalone) [since 2.2.9] # - fpr :: Fingerprint (fingerprint is in field 10) # - pkd :: Public key data [*] # - grp :: Keygrip # - rvk :: Revocation key # - tfs :: TOFU statistics [*] # - tru :: Trust database information [*] # - spk :: Signature subpacket [*] # - cfg :: Configuration data [*] # # Records marked with an asterisk are described at [[*Special%20field%20formats][*Special fields]]. # # # # *** Field 12 - Key capabilities # # The defined capabilities are: # # - e :: Encrypt # - s :: Sign # - c :: Certify # - a :: Authentication # - ? :: Unknown capability # # A key may have any combination of them in any order. In addition # to these letters, the primary key has uppercase versions of the # letters to denote the _usable_ capabilities of the entire key, and # a potential letter 'D' to indicate a disabled key. # # determine which codes to look for if keytype == 'secret' and usage == 'cert': ltype = 'sec' # secret master key elif keytype == 'secret': ltype = 'ssb' elif keytype == 'public' and usage == 'cert': ltype = 'pub' else: ltype = 'sub' if usage == 'cert': lcapb = 'c' elif usage == 'sign': lcapb = 's' elif usage == 'auth': lcapb = 'a' else: lcapb = 'e' # # FIELD TYPES: # # - Field 1 - Type of record # - Field 2 - Validity # - Field 3 - Key length # - Field 4 - Public key algorithm # - Field 5 - KeyID # - Field 6 - Creation date # - Field 7 - Expiration date # - Field 8 - Certificate S/N, UID hash, trust signature info # - Field 9 - Ownertrust # - Field 10 - User-ID # - Field 11 - Signature class # - Field 12 - Key capabilities # - Field 13 - Issuer certificate fingerprint or other info # - Field 14 - Flag field # - Field 15 - S/N of a token # - Field 16 - Hash algorithm # - Field 17 - Curve name # - Field 18 - Compliance flags # - Field 19 - Last update # - Field 20 - Origin # - Field 21 - Comment # # set empty result tmpresult = {} # determine the correct line correct_line = False main_lines = ['sec', 'ssb', 'pub', 'sub'] follow_lines = ['fpr', 'grp', 'uid'] # indexes start at 0 # main parts are for main_lines only mainparts = { 'type': 0, 'key_length': 2, 'pubkey_algorithm': 3, 'keyid': 4, 'creationdate': 5, 'expirationdate': 6, 'key_capabilities': 11, 'hash_algorithm': 15, 'curve_name': 16, } # indexes start at 0 # follow parts for follow_lines only followparts = { 'type': 0, 'userid': 9, # this is the fingerprint for fpr records and the keygrip for grp records } # # 9.1. Public-Key Algorithms # # ID Algorithm # -- --------- # 1 - RSA (Encrypt or Sign) [HAC] # 2 - RSA Encrypt-Only [HAC] # 3 - RSA Sign-Only [HAC] # 16 - Elgamal (Encrypt-Only) [ELGAMAL] [HAC] # 17 - DSA (Digital Signature Algorithm) [FIPS186] [HAC] # 18 - Reserved for Elliptic Curve # 19 - Reserved for ECDSA # 20 - Reserved (formerly Elgamal Encrypt or Sign) # 21 - Reserved for Diffie-Hellman (X9.42, # as defined for IETF-S/MIME) # 22 - Ed25519 # 100 to 110 - Private/Experimental algorithm # pubkeys = { '1': 'RSA (Encrypt or Sign)', '2': 'RSA Encrypt-Only', '3': 'RSA Sign-Only', '16': 'Elgamal (Encrypt-Only)', '17': 'DSA [FIPS186]', '18': 'Cv25519', '22': 'Ed25519', } msg.vvvv("looping through key details") # loop through lines for l in proc.getstdout(): #print("line: {}".format(l)) # split line into pieces pieces = l.split(":") # get current type ctype = pieces[mainparts.get('type')] # check for usage/capabilities if ctype in main_lines: ccapb = pieces[mainparts.get('key_capabilities')] else: ccapb = "" # check if capabilities OK if lcapb in ccapb: capbok = True else: capbok = False #print("usage: {} | ctype: {} | ccapb: {} | lcapb: {} | capbok: {}".format(usage,ctype,ccapb,lcapb,capbok)) # check for main lines if ctype in main_lines and capbok and not correct_line: # we must be on the correct line #print("we are now on the correct line") correct_line = True current_line = ctype for x in mainparts.keys(): # skip the type if x == 'type': continue # add the other info to the tmpresult array y = '{}_{}_{}'.format(usage, ctype, x) if x == 'pubkey_algorithm': p = pieces[mainparts.get(x)] z = pubkeys.get(p) if p is not None else '' else: z = pieces[mainparts.get(x)] tmpresult[y] = z # check for follow lines elif correct_line and ctype in follow_lines: for x in followparts.keys(): # skip the type if x == 'type': continue # add the other info to the tmpresult array y = '{}_{}_{}_{}'.format(usage, current_line, ctype, x) if x == 'pubkey_algorithm': p = pieces[followparts.get(x)] z = pubkeys.get(p) if p is not None else '' else: z = pieces[followparts.get(x)] tmpresult[y] = z # if not, we have reached a new key or the end else: #print("we are no longer on the correct line") correct_line = False msg.vvvv("renaming tmpresult keys; usage [{}]".format(usage)) # # MAPPING #ssb_fpr_userid sec_fpr_userid master_cert_sec_fingerprint keymap = { 'regular': { 'cert_sec_fpr_userid': 'master_cert_sec_fingerprint', 'cert_sec_curve_name': 'master_cert_sec_keycurve', 'cert_sec_grp_userid': 'master_cert_sec_keygrip', 'cert_sec_key_length': 'master_cert_sec_keybits', 'cert_sec_creationdate': 'master_cert_sec_creationdate', 'cert_sec_keyid': 'master_cert_sec_keyid', 'cert_sec_expirationdate': 'master_cert_sec_expirationdate', 'sign_ssb_fpr_userid': 'subkey_sign_sec_fingerprint', 'sign_ssb_curve_name': 'subkey_sign_sec_keycurve', 'sign_ssb_grp_userid': 'subkey_sign_sec_keygrip', 'sign_ssb_key_length': 'subkey_sign_sec_keybits', 'sign_ssb_creationdate': 'subkey_sign_sec_creationdate', 'sign_ssb_keyid': 'subkey_sign_sec_keyid', 'sign_ssb_expirationdate': 'subkey_sign_sec_expirationdate', 'encr_ssb_fpr_userid': 'subkey_encr_sec_fingerprint', 'encr_ssb_curve_name': 'subkey_encr_sec_keycurve', 'encr_ssb_grp_userid': 'subkey_encr_sec_keygrip', 'encr_ssb_key_length': 'subkey_encr_sec_keybits', 'encr_ssb_creationdate': 'subkey_encr_sec_creationdate', 'encr_ssb_keyid': 'subkey_encr_sec_keyid', 'encr_ssb_expirationdate': 'subkey_encr_sec_expirationdate', 'auth_ssb_fpr_userid': 'subkey_auth_sec_fingerprint', 'auth_ssb_curve_name': 'subkey_auth_sec_keycurve', 'auth_ssb_grp_userid': 'subkey_auth_sec_keygrip', 'auth_ssb_key_length': 'subkey_auth_sec_keybits', 'auth_ssb_creationdate': 'subkey_auth_sec_creationdate', 'auth_ssb_keyid': 'subkey_auth_sec_keyid', 'auth_ssb_expirationdate': 'subkey_auth_sec_expirationdate', }, 'backup_sign': { 'cert_sec_fpr_userid': 'sign_master_cert_sec_fingerprint', 'cert_sec_curve_name': 'sign_master_cert_sec_keycurve', 'cert_sec_grp_userid': 'sign_master_cert_sec_keygrip', 'cert_sec_key_length': 'sign_master_cert_sec_keybits', 'cert_sec_creationdate': 'sign_master_cert_sec_creationdate', 'cert_sec_keyid': 'sign_master_cert_sec_keyid', 'cert_sec_expirationdate': 'sign_master_cert_sec_expirationdate', 'sign_ssb_fpr_userid': 'sign_subkey_sign_sec_fingerprint', 'sign_ssb_curve_name': 'sign_subkey_sign_sec_keycurve', 'sign_ssb_grp_userid': 'sign_subkey_sign_sec_keygrip', 'sign_ssb_key_length': 'sign_subkey_sign_sec_keybits', 'sign_ssb_creationdate': 'sign_subkey_sign_sec_creationdate', 'sign_ssb_keyid': 'sign_subkey_sign_sec_keyid', 'sign_ssb_expirationdate': 'sign_subkey_sign_sec_expirationdate', }, 'backup_encr': { 'cert_sec_fpr_userid': 'encr_master_cert_sec_fingerprint', 'cert_sec_curve_name': 'encr_master_cert_sec_keycurve', 'cert_sec_grp_userid': 'encr_master_cert_sec_keygrip', 'cert_sec_key_length': 'encr_master_cert_sec_keybits', 'cert_sec_creationdate': 'encr_master_cert_sec_creationdate', 'cert_sec_keyid': 'encr_master_cert_sec_keyid', 'cert_sec_expirationdate': 'encr_master_cert_sec_expirationdate', 'encr_ssb_fpr_userid': 'encr_subkey_encr_sec_fingerprint', 'encr_ssb_curve_name': 'encr_subkey_encr_sec_keycurve', 'encr_ssb_grp_userid': 'encr_subkey_encr_sec_keygrip', 'encr_ssb_key_length': 'encr_subkey_encr_sec_keybits', 'encr_ssb_creationdate': 'encr_subkey_encr_sec_creationdate', 'encr_ssb_keyid': 'encr_subkey_encr_sec_keyid', 'encr_ssb_expirationdate': 'encr_subkey_encr_sec_expirationdate', }, } # # rename result keys / only use the relevant ones from the mapping # result = {} resultiterator = tmpresult.copy() km = keymap.get(mapping) for k, v in resultiterator.items(): # only for keys which exist in the mapping dict if k in km: nk = km.get(k) result[nk] = tmpresult.pop(k) # # return results # return result