confgen is done. messy, but done.
This commit is contained in:
parent
f4f131890d
commit
1d9b40a597
@ -625,7 +625,7 @@ class ConfGenerator(object):
|
||||
'created that can be used to serve iPXE)'),
|
||||
'tftp': ('the TFTP directory (where a TFTP/'
|
||||
'traditional PXE root is created)'),
|
||||
'ssl': ('the SSL/TLS PKI directory (where we store '
|
||||
'pki': ('the SSL/TLS PKI directory (where we store '
|
||||
'the PKI structure we use/re-use - MAKE SURE '
|
||||
'it is in a path that is well-protected!)')}
|
||||
has_paths = False
|
||||
@ -840,7 +840,7 @@ class ConfGenerator(object):
|
||||
'then trying the built-in ~/.gnupg directory).'
|
||||
'\nGPG Home Directory: '))
|
||||
if _gpghome.strip() != '':
|
||||
gpg.attrib['gnupghome'] == _gpghome
|
||||
gpg.attrib['gnupghome'] = _gpghome
|
||||
else:
|
||||
_gpghome = 'none'
|
||||
print('\n++ GPG || KEYSERVER PUSHING ++')
|
||||
@ -935,12 +935,12 @@ class ConfGenerator(object):
|
||||
'\n\t'.join(_choices)
|
||||
))).strip().lower()
|
||||
if _export_type.startswith('a'):
|
||||
_export_type == 'asc'
|
||||
_export_type = 'asc'
|
||||
elif _export_type.startswith('b'):
|
||||
_export_type == 'bin'
|
||||
_export_type = 'bin'
|
||||
else:
|
||||
print('Using the default.')
|
||||
_export_type == 'asc'
|
||||
_export_type = 'asc'
|
||||
elem.attrib['format'] = _export_type
|
||||
_path = None
|
||||
while not _path:
|
||||
|
116
bdisk/prompt_strings.py
Normal file
116
bdisk/prompt_strings.py
Normal file
@ -0,0 +1,116 @@
|
||||
# These are *key* ciphers, for encrypting exported keys.
|
||||
openssl_ciphers = ['aes128', 'aes192', 'aes256', 'bf', 'blowfish',
|
||||
'camellia128', 'camellia192', 'camellia256', 'cast', 'des',
|
||||
'des3', 'idea', 'rc2', 'seed']
|
||||
# These are *hash algorithms* for cert digests.
|
||||
openssl_digests = ['blake2b512', 'blake2s256', 'gost', 'md4', 'md5', 'mdc2',
|
||||
'rmd160', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
|
||||
|
||||
class PromptStrings(object):
|
||||
gpg = {
|
||||
'attribs': {
|
||||
'algo': {
|
||||
'text': 'the subkey\'s encryption type/algorithm',
|
||||
'choices': ['rsa', 'dsa'],
|
||||
'default': 'rsa'
|
||||
},
|
||||
'keysize': {
|
||||
'text': 'the subkey\'s key size (in bits)',
|
||||
'choices': {
|
||||
'rsa': ['1024', '2048', '4096'],
|
||||
'dsa': ['768', '2048', '3072']
|
||||
},
|
||||
'default': {
|
||||
'rsa': '4096',
|
||||
'dsa': '3072'
|
||||
}
|
||||
}
|
||||
},
|
||||
'params': ['name', 'email', 'comment']
|
||||
}
|
||||
ssl = {
|
||||
'attribs': {
|
||||
'cert': {
|
||||
'hash_algo': {
|
||||
'text': ('What hashing algorithm do you want to use? '
|
||||
'(Default is sha512.)'),
|
||||
'prompt': 'Hashing algorithm: ',
|
||||
'options': openssl_digests,
|
||||
'default': 'aes256'
|
||||
}
|
||||
},
|
||||
'key': {
|
||||
'cipher': {
|
||||
'text': ('What encryption algorithm/cipher do you want to '
|
||||
'use? (Default is aes256.) Use "none" to specify '
|
||||
'a key without a passphrase.'),
|
||||
'prompt': 'Cipher: ',
|
||||
'options': openssl_ciphers + ['none'],
|
||||
'default': 'aes256'
|
||||
},
|
||||
'keysize': {
|
||||
'text': ('What keysize/length (in bits) do you want the '
|
||||
'key to be? (Default is 4096; much higher values '
|
||||
'are possible but are untested and thus not '
|
||||
'supported by this tool; feel free to edit the '
|
||||
'generated configuration by hand.) (If the key '
|
||||
'cipher is "none", this is ignored.)'),
|
||||
'prompt': 'Keysize: ',
|
||||
# TODO: do all openssl_ciphers support these sizes?
|
||||
'options': ['1024', '2048', '4096'],
|
||||
'default': 'aes256'
|
||||
},
|
||||
'passphrase': {
|
||||
'text': ('What passphrase do you want to use for the key? '
|
||||
'If you specified the cipher as "none", this is '
|
||||
'ignored (you can just hit enter).'),
|
||||
'prompt': 'Passphrase (will not echo back): ',
|
||||
'options': None,
|
||||
'default': ''
|
||||
}
|
||||
}
|
||||
},
|
||||
'paths': {
|
||||
'cert': '(or read from) the certificate',
|
||||
'key': '(or read from) the key',
|
||||
'csr': ('(or read from) the certificate signing request (if '
|
||||
'blank, we won\'t write to disk - the operation will '
|
||||
'occur entirely in memory assuming we need to generate/'
|
||||
'sign)')
|
||||
},
|
||||
'paths_ca': {
|
||||
'index': ('(or read from) the CA (Certificate Authority) Database '
|
||||
'index file (if left blank, one will not be used)'),
|
||||
'serial': ('(or read from) the CA (Certificate Authority) '
|
||||
'Database serial file (if left blank, one will not be '
|
||||
'used)'),
|
||||
},
|
||||
'subject': {
|
||||
'countryName': {
|
||||
'text': ('the 2-letter country abbreviation (must conform to '
|
||||
'ISO3166 ALPHA-2)?\n'
|
||||
'Country code: ')
|
||||
},
|
||||
'localityName': {
|
||||
'text': ('the city/town/borough/locality name?\n'
|
||||
'Locality: ')
|
||||
},
|
||||
'stateOrProvinceName': {
|
||||
'text': ('the state/region name (full string)?\n'
|
||||
'Region: ')
|
||||
},
|
||||
'organization': {
|
||||
'text': ('your organization\'s name?\n'
|
||||
'Organization: ')
|
||||
},
|
||||
'organizationalUnitName': {
|
||||
'text': ('your department/role/team/department name?\n'
|
||||
'Organizational Unit: ')
|
||||
},
|
||||
'emailAddress': {
|
||||
'text': ('the email address to be associated with this '
|
||||
'certificate/PKI object?\n'
|
||||
'Email: ')
|
||||
}
|
||||
}
|
||||
}
|
221
bdisk/utils.py
221
bdisk/utils.py
@ -1,11 +1,14 @@
|
||||
import _io
|
||||
import copy
|
||||
import crypt
|
||||
import GPG
|
||||
import getpass
|
||||
import hashid
|
||||
import hashlib
|
||||
import iso3166
|
||||
import os
|
||||
import pprint
|
||||
import prompt_strings
|
||||
import re
|
||||
import string
|
||||
import uuid
|
||||
@ -33,12 +36,7 @@ crypt_map = {'sha512': crypt.METHOD_SHA512,
|
||||
'md5': crypt.METHOD_MD5,
|
||||
'des': crypt.METHOD_CRYPT}
|
||||
|
||||
# These are *key* ciphers, for encrypting exported keys.
|
||||
openssl_ciphers = ['aes128', 'aes192', 'aes256', 'bf', 'blowfish',
|
||||
'camellia128', 'camellia192', 'camellia256', 'cast', 'des',
|
||||
'des3', 'idea', 'rc2', 'seed']
|
||||
openssl_digests = ['blake2b512', 'blake2s256', 'gost', 'md4', 'md5', 'mdc2',
|
||||
'rmd160', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
|
||||
|
||||
|
||||
class XPathFmt(string.Formatter):
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
@ -162,7 +160,10 @@ class generate(object):
|
||||
|
||||
class prompts(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
self.promptstr = prompt_strings.PromptStrings()
|
||||
|
||||
# TODO: these strings should be indexed in a separate module and
|
||||
# sourced/imported. we should generally just find a cleaner way to do this.
|
||||
|
||||
def confirm_or_no(self, prompt = '', invert = False,
|
||||
usage = '{0} to confirm, otherwise {1}...\n'):
|
||||
@ -193,6 +194,62 @@ class prompts(object):
|
||||
return(False)
|
||||
return(True)
|
||||
|
||||
def gpg_keygen_attribs(self):
|
||||
_strs = self.promptstr.gpg
|
||||
gpg_vals = {'attribs': {},
|
||||
'params': {}}
|
||||
_checks = {
|
||||
'params': {
|
||||
'name': {'error': 'name cannot be empty',
|
||||
'check': valid().nonempty_str},
|
||||
'email': {'error': 'not a valid email address',
|
||||
'check': valid().email}
|
||||
}
|
||||
}
|
||||
for a in _strs['attribs']:
|
||||
_a = None
|
||||
while not _a:
|
||||
if 'algo' in gpg_vals['attribs'] and a == 'keysize':
|
||||
_algo = gpg_vals['attribs']['algo']
|
||||
_choices = _strs['attribs']['keysize']['choices'][_algo]
|
||||
_dflt = _strs['attribs']['keysize']['default'][_algo]
|
||||
else:
|
||||
_choices = _strs['attribs'][a]['choices']
|
||||
_dflt = _strs['attribs'][a]['default']
|
||||
_a = (input(
|
||||
('\nWhat should be {0}? (Default is {1}.)\nChoices:\n'
|
||||
'\n\t{2}\n\n{3}: ').format(
|
||||
_strs['attribs'][a]['text'],
|
||||
_dflt,
|
||||
'\n\t'.join(_choices),
|
||||
a.title()
|
||||
)
|
||||
)).strip().lower()
|
||||
if _a == '':
|
||||
_a = _dflt
|
||||
elif _a not in _choices:
|
||||
print(
|
||||
('Invalid selection; choosing default '
|
||||
'({0})').format(_dflt)
|
||||
)
|
||||
_a = _dflt
|
||||
gpg_vals['attribs'][a] = _a
|
||||
for p in _strs['params']:
|
||||
_p = input(
|
||||
('\nWhat is the {0} for the subkey?\n'
|
||||
'{1}: ').format(p, p.title())
|
||||
)
|
||||
if p in _checks['params']:
|
||||
while not _checks['params'][p]['check'](_p):
|
||||
print(
|
||||
('Invalid entry ({0}). Please retry.').format(
|
||||
_checks['params'][p]['error']
|
||||
)
|
||||
)
|
||||
_p = input('{0}: '.format(_p.title()))
|
||||
gpg_vals['params'][p] = _p
|
||||
return(gpg_vals)
|
||||
|
||||
def hash_select(self, prompt = '',
|
||||
hash_types = generate().hashlib_names()):
|
||||
_hash_types = hash_types
|
||||
@ -240,116 +297,81 @@ class prompts(object):
|
||||
ssl_vals = {'paths': {},
|
||||
'attribs': {},
|
||||
'subject': {}}
|
||||
_checks = {
|
||||
'subject': {
|
||||
'countryName': valid().country_abbrev,
|
||||
'emailAddress': valid().email
|
||||
}
|
||||
}
|
||||
_strs = copy.deepcopy(self.promptstr.ssl)
|
||||
# pki_role should be 'ca' or 'client'
|
||||
if pki_role not in ('ca', 'client'):
|
||||
raise ValueError('pki_role must be either "ca" or "client"')
|
||||
_attribs = {'cert': {'hash_algo': {'text': ('What hashing algorithm '
|
||||
'do you want to use? (Default is sha512.)'),
|
||||
'prompt': 'Hashing algorithm: ',
|
||||
'options': openssl_digests,
|
||||
'default': 'sha512'}},
|
||||
'key': {'cipher': {'text': ('What encryption algorithm/'
|
||||
'cipher do you want to use? (Default is '
|
||||
'aes256.)'),
|
||||
'prompt': 'Cipher: ',
|
||||
'options': openssl_ciphers,
|
||||
'default': 'aes256'},
|
||||
# This can actually theoretically be anywhere from
|
||||
# 512 to... who knows how high. I couldn't find the
|
||||
# upper bound. So we just set it to sensible
|
||||
# defaults. If they want something higher, they can
|
||||
# edit the XML when they're done.
|
||||
'keysize': {'text': ('What keysize/length (in '
|
||||
'bits) do you want the key to be? (Default is '
|
||||
'4096; much higher values are possible but '
|
||||
'are untested and thus not supported by this '
|
||||
'tool; feel free to edit the generated '
|
||||
'configuration by hand.)'),
|
||||
'prompt': 'Keysize: ',
|
||||
'options': ['1024', '2048', '4096'],
|
||||
'default': '4096'}}}
|
||||
_paths = {'cert': '(or read from) the certificate',
|
||||
'key': '(or read from) the key',
|
||||
'csr': ('(or read from) the certificate signing request (if '
|
||||
'blank, we won\'t write to disk - the operation '
|
||||
'will occur entirely in memory assuming we need to '
|
||||
'generate/sign)')}
|
||||
# NOTE: need to validate US and email
|
||||
if pki_role == 'ca':
|
||||
_paths['index'] = ('(or read from) the CA DB index file (if left '
|
||||
'blank, one will not be used)')
|
||||
_paths['serial'] = ('(or read from) the CA DB serial file (if '
|
||||
'left blank, one will not be used)')
|
||||
for a in _attribs:
|
||||
# this is getting triggered for clients?
|
||||
_strs['paths'].update(_strs['paths_ca'])
|
||||
for a in _strs['attribs']:
|
||||
ssl_vals['attribs'][a] = {}
|
||||
for x in _attribs[a]:
|
||||
for x in _strs['attribs'][a]:
|
||||
ssl_vals['attribs'][a][x] = None
|
||||
for p in _paths:
|
||||
for p in _strs['paths']:
|
||||
if p == 'csr':
|
||||
_allow_empty = True
|
||||
else:
|
||||
_allow_empty = False
|
||||
ssl_vals['paths'][p] = self.path(_paths[p],
|
||||
ssl_vals['paths'][p] = self.path(_strs['paths'][p],
|
||||
empty_passthru = _allow_empty)
|
||||
print()
|
||||
if ssl_vals['paths'][p] == '':
|
||||
ssl_vals['paths'][p] = None
|
||||
if p in _attribs:
|
||||
for x in _attribs[p]:
|
||||
if p in _strs['attribs']:
|
||||
for x in _strs['attribs'][p]:
|
||||
while not ssl_vals['attribs'][p][x]:
|
||||
ssl_vals['attribs'][p][x] = (input(
|
||||
('\n{0}\n\n\t{1}\n\n{2}').format(
|
||||
_attribs[p][x]['text'],
|
||||
'\n\t'.join(_attribs[p][x]['options']),
|
||||
_attribs[p][x]['prompt'])
|
||||
)).strip().lower()
|
||||
if ssl_vals['attribs'][p][x] not in \
|
||||
_attribs[p][x]['options']:
|
||||
print(('\nInvalid selection; setting default '
|
||||
'({0}).').format(_attribs[p][x]['default']))
|
||||
ssl_vals['attribs'][p][x] = \
|
||||
_attribs[p][x]['default']
|
||||
_subject = {'countryName': {'text': ('the 2-letter country '
|
||||
'abbreviation (must conform to '
|
||||
'ISO3166 ALPHA-2)?\nCountry '
|
||||
'code: '),
|
||||
'check': 'func',
|
||||
'func': valid().country_abbrev},
|
||||
'localityName': {'text': ('the city/town/borough/locality '
|
||||
'name?\nLocality: '),
|
||||
'check': None},
|
||||
'stateOrProvinceName': {'text': ('the state/region '
|
||||
'name (full string)?'
|
||||
'\nRegion: '),
|
||||
'check': None},
|
||||
'organization': {'text': ('your organization\'s name?'
|
||||
'\nOrganization: '),
|
||||
'check': None},
|
||||
'organizationalUnitName': {'text': ('your department/role/'
|
||||
'team/department name?'
|
||||
'\nOrganizational '
|
||||
'Unit: '),
|
||||
'check': None},
|
||||
'emailAddress': {'text': ('the email address to be '
|
||||
'associated with this '
|
||||
'certificate/PKI object?\n'
|
||||
'Email: '),
|
||||
'check': 'func',
|
||||
'func': valid().email}}
|
||||
for s in _subject:
|
||||
# cipher attrib is prompted for before this.
|
||||
if p == 'key' and x == 'passphrase':
|
||||
if ssl_vals['attribs']['key']['cipher'] == 'none':
|
||||
ssl_vals['attribs'][p][x] = 'none'
|
||||
continue
|
||||
ssl_vals['attribs'][p][x] = getpass.getpass(
|
||||
('{0}\n{1}').format(
|
||||
_strs['attribs'][p][x]['text'],
|
||||
_strs['attribs'][p][x]['prompt'])
|
||||
)
|
||||
if ssl_vals['attribs'][p][x] == '':
|
||||
ssl_vals['attribs'][p][x] = 'none'
|
||||
else:
|
||||
ssl_vals['attribs'][p][x] = (input(
|
||||
('\n{0}\n\n\t{1}\n\n{2}').format(
|
||||
_strs['attribs'][p][x]['text'],
|
||||
'\n\t'.join(
|
||||
_strs['attribs'][p][x]['options']),
|
||||
_strs['attribs'][p][x]['prompt']))
|
||||
).strip().lower()
|
||||
if ssl_vals['attribs'][p][x] not in \
|
||||
_strs['attribs'][p][x]['options']:
|
||||
print(
|
||||
('\nInvalid selection; setting default '
|
||||
'({0}).').format(
|
||||
_strs['attribs'][p][x]['default']
|
||||
)
|
||||
)
|
||||
ssl_vals['attribs'][p][x] = \
|
||||
_strs['attribs'][p][x]['default']
|
||||
for s in _strs['subject']:
|
||||
ssl_vals['subject'][s] = None
|
||||
for s in _subject:
|
||||
for s in _strs['subject']:
|
||||
while not ssl_vals['subject'][s]:
|
||||
_input = (input(
|
||||
('\nWhat is {0}').format(_subject[s]['text'])
|
||||
('\nWhat is {0}').format(
|
||||
_strs['subject'][s]['text'])
|
||||
)).strip()
|
||||
_chk = _subject[s]['check']
|
||||
if _chk:
|
||||
if _chk == 'func':
|
||||
_chk = _subject[s]['func'](_input)
|
||||
if not _chk:
|
||||
print('Invalid value; retrying.')
|
||||
continue
|
||||
print()
|
||||
if s in _checks['subject']:
|
||||
if not _checks['subject'][s](_input):
|
||||
print('Invalid entry; try again.')
|
||||
ssl_vals['subject'][s] = None
|
||||
continue
|
||||
ssl_vals['subject'][s] = _input
|
||||
_url = transform().url_to_dict(cn_url, no_None = True)
|
||||
ssl_vals['subject']['commonName'] = _url['host']
|
||||
@ -594,6 +616,11 @@ class valid(object):
|
||||
return(False)
|
||||
return()
|
||||
|
||||
def nonempty_str(self, str_in):
|
||||
if str_in.strip() == '':
|
||||
return(False)
|
||||
return(True)
|
||||
|
||||
def password(self, passwd):
|
||||
# https://en.wikipedia.org/wiki/ASCII#Printable_characters
|
||||
# https://serverfault.com/a/513243/103116
|
||||
|
@ -156,7 +156,7 @@
|
||||
publish="no"
|
||||
prompt_passphrase="no">
|
||||
<!-- The below is only used if we are generating a key (i.e. keyid="none"). -->
|
||||
<key type="rsa" keysize="4096" expire="0">
|
||||
<key algo="rsa" keysize="4096" expire="0">
|
||||
<name>{xpath%../../../../meta/dev/author/text()}</name>
|
||||
<email>{xpath%../../../../meta/dev/email/text()}</email>
|
||||
<comment>for {xpath%../../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../../meta/uri/text()} | {xpath%../../../../meta/desc/text()}</comment>
|
||||
|
Loading…
Reference in New Issue
Block a user