checking in before i do some major restructuring of wifi stuff in the xml/xsd
This commit is contained in:
parent
3e33abe0a6
commit
edc78ea18e
10
aif.xsd
10
aif.xsd
@ -347,8 +347,6 @@
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="auto" type="xs:boolean" use="optional" default="true"/>
|
||||
<xs:attribute name="defaultGateway" type="xs:boolean"
|
||||
use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv4_route">
|
||||
<xs:selector xpath="aif:route"/>
|
||||
@ -377,8 +375,6 @@
|
||||
<!-- https://datatracker.ietf.org/doc/draft-ietf-mif-dhcpv6-route-option/
|
||||
expired. Shame, that. -->
|
||||
<xs:attribute name="auto" type="xs:boolean" use="optional" default="true"/>
|
||||
<xs:attribute name="defaultGateway" type="xs:boolean"
|
||||
use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniq_ipv6_route">
|
||||
<xs:selector xpath="aif:route"/>
|
||||
@ -443,6 +439,12 @@
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:token">
|
||||
<xs:attribute name="type" use="optional" default="psk">
|
||||
<!-- TODO: change this to sub-elements. <psk> or a <radius> thinger. -->
|
||||
<!-- <psk raw="false">PSK_HERE</psk> -->
|
||||
<!-- or e.g. wpa_passphrase test testingpsk -->
|
||||
<!-- <psk raw="true">
|
||||
124153ff24015a16d1993323b1840f3e6309ae24c07df7007d9fff8cff22f74c
|
||||
</psk> -->
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="psk"/>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import binascii
|
||||
import ipaddress
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
##
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
from pyroute2 import IPDB
|
||||
##
|
||||
import aif.utils
|
||||
@ -47,7 +49,22 @@ def convertIpTuples(addr_xmlobj):
|
||||
return((addr, net, gw))
|
||||
|
||||
|
||||
def convertWifiCrypto(crypto_xmlobj):
|
||||
def convertPSK(ssid, passphrase):
|
||||
try:
|
||||
passphrase = passphrase.encode('utf-8').decode('ascii').strip('\r').strip('\n')
|
||||
except UnicodeDecodeError:
|
||||
raise ValueError('passphrase must be an ASCII string')
|
||||
if len(ssid) > 32:
|
||||
raise ValueError('ssid must be <= 32 characters')
|
||||
if not 7 < len(passphrase) < 64:
|
||||
raise ValueError('passphrase must be >= 8 and <= 32 characters')
|
||||
raw_psk = pbkdf2_hmac('sha1', str(passphrase), str(ssid), 4096, 32)
|
||||
hex_psk = binascii.hexlify(raw_psk)
|
||||
str_psk = hex_psk.decode('utf-8')
|
||||
return(str_psk)
|
||||
|
||||
|
||||
def convertWifiCrypto(crypto_xmlobj, ssid):
|
||||
crypto = {'type': crypto_xmlobj.find('type').text.strip()}
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
@ -61,7 +78,8 @@ def convertWifiCrypto(crypto_xmlobj):
|
||||
creds = crypto_xmlobj.find('creds')
|
||||
crypto['auth'] = {'type': creds.attrib.get('type', 'psk').strip()}
|
||||
if crypto['auth']['type'] == 'psk':
|
||||
crypto['auth']['psk'] = creds.text
|
||||
crypto['auth']['passphrase'] = creds.text.strip('\r').strip('\n')
|
||||
crypto['auth']['psk'] = convertPSK(ssid, creds.text)
|
||||
# TODO: enterprise support
|
||||
return(crypto)
|
||||
|
||||
@ -211,3 +229,8 @@ class BaseConnection(object):
|
||||
if addrset not in self.routes[addrtype]:
|
||||
self.routes[addrtype].append(addrset)
|
||||
return()
|
||||
|
||||
def _writeConnCfg(self, chroot_base = None):
|
||||
# Dummy method.
|
||||
pass
|
||||
return()
|
||||
|
@ -274,6 +274,7 @@ class Wireless(Connection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.connection_type = 'wireless'
|
||||
self.packages.add('wpa_supplicant')
|
||||
self._initCfg()
|
||||
self._initConnCfg()
|
||||
|
||||
@ -291,8 +292,7 @@ class Wireless(Connection):
|
||||
self._cfg['BASE']['AP'] = bssid
|
||||
crypto = self.xml.find('encryption')
|
||||
if crypto:
|
||||
self.packages.add('wpa_supplicant')
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto)
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto, self.xml.attrib['essid'])
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
# TODO: WPA2 enterprise
|
||||
|
19
aif/network/networkd.conf.j2
Normal file
19
aif/network/networkd.conf.j2
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by AIF-NG.
|
||||
{%- for section_name, section_items in cfg.items() %}
|
||||
{%- if section_items|isList %}
|
||||
{#- We *only* use lists-of-dicts because they should always render to their own sections.
|
||||
INI doesn't support nesting, thankfully. #}
|
||||
{%- for i in section_items %}
|
||||
[{{ section_name }}]
|
||||
{%- for k, v in i.items() %}
|
||||
{{ k }}={{ v }}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
{%- else %}
|
||||
{#- It's a single-level dict. #}
|
||||
[{{ section_name }}]
|
||||
{%- for k, v in section_items.items() %}
|
||||
{{ k }}={{ v }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{% endfor %}
|
@ -1,8 +1,157 @@
|
||||
import ipaddress
|
||||
import socket
|
||||
import os
|
||||
##
|
||||
# We have to use Jinja2 because while there are ways to *parse* an INI with duplicate keys
|
||||
# (https://stackoverflow.com/a/38286559/733214), there's no way to *write* an INI with them using configparser.
|
||||
# So we use Jinja2 logic.
|
||||
import jinja2
|
||||
##
|
||||
import aif.utils
|
||||
import aif.network._common
|
||||
|
||||
|
||||
class Connection(aif.network._common.BaseConnection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.provider_type = 'systemd-networkd'
|
||||
self.packages = set()
|
||||
self.services = {
|
||||
('/usr/lib/systemd/system/systemd-networkd.service'): ('etc/systemd/system/'
|
||||
'multi-user.target.wants/'
|
||||
'systemd-networkd.service'),
|
||||
('/usr/lib/systemd/system/systemd-networkd.service'): ('etc/systemd/system/'
|
||||
'dbus-org.freedesktop.network1.service'),
|
||||
('/usr/lib/systemd/system/systemd-networkd.socket'): ('etc/systemd/system/'
|
||||
'sockets.target.wants/systemd-networkd.socket'),
|
||||
('/usr/lib/systemd/system/systemd-networkd.socket'): ('etc/systemd/system/'
|
||||
'network-online.target.wants/'
|
||||
'systemd-networkd-wait-online.service'),
|
||||
# We include these *even if* self.auto['resolvers'][*] are false.
|
||||
('/usr/lib/systemd/system/systemd-resolved.service'): ('etc/systemd/system/'
|
||||
'dbus-org.freedesktop.resolve1.service'),
|
||||
('/usr/lib/systemd/system/systemd-resolved.service'): ('etc/systemd/'
|
||||
'system/multi-user.target.wants/'
|
||||
'systemd-resolved.service')}
|
||||
self._wpasupp = {}
|
||||
self._initJ2()
|
||||
|
||||
def _initCfg(self):
|
||||
if self.device == 'auto':
|
||||
self.device = aif.network._common.getDefIface(self.connection_type)
|
||||
self._cfg = {'Match': {'Name': self.device},
|
||||
'Network': {'Description': ('A {0} profile for {1} '
|
||||
'(generated by AIF-NG)').format(self.connection_type,
|
||||
self.device),
|
||||
'DefaultRouteOnDevice': ('true' if self.is_defroute else 'false'),
|
||||
# This (may) get modified by logic below.
|
||||
'IPv6AcceptRA': 'false',
|
||||
'LinkLocalAddressing': 'no'}}
|
||||
if self.domain:
|
||||
self._cfg['Network']['Domains'] = self.domain
|
||||
if self.resolvers:
|
||||
self._cfg['Network']['DNS'] = [str(ip) for ip in self.resolvers]
|
||||
if all((self.auto['addresses']['ipv4'], self.auto['addresses']['ipv6'])):
|
||||
self._cfg['Network']['IPv6AcceptRA'] = 'true'
|
||||
self._cfg['Network']['LinkLocalAddressing'] = 'ipv6'
|
||||
self._cfg['Network']['DHCP'] = 'yes'
|
||||
elif self.auto['addresses']['ipv4'] and not self.auto['addresses']['ipv6']:
|
||||
self._cfg['Network']['DHCP'] = 'ipv4'
|
||||
elif (not self.auto['addresses']['ipv4']) and self.auto['addresses']['ipv6']:
|
||||
self._cfg['Network']['IPv6AcceptRA'] = 'true'
|
||||
self._cfg['Network']['LinkLocalAddressing'] = 'ipv6'
|
||||
self._cfg['Network']['DHCP'] = 'ipv6'
|
||||
else:
|
||||
self._cfg['Network']['DHCP'] = 'no'
|
||||
if any((self.auto['addresses']['ipv4'], self.auto['routes']['ipv4'], self.auto['resolvers']['ipv4'])):
|
||||
t = 'ipv4'
|
||||
self._cfg['DHCPv4'] = {'UseDNS': ('true' if self.auto['resolvers'][t] else 'false'),
|
||||
'UseRoutes': ('true' if self.auto['routes'][t] else 'false')}
|
||||
if any((self.auto['addresses']['ipv6'], self.auto['routes']['ipv6'], self.auto['resolvers']['ipv6'])):
|
||||
t = 'ipv6'
|
||||
self._cfg['Network']['IPv6AcceptRA'] = 'true'
|
||||
self._cfg['DHCPv6'] = {'UseDNS': ('true' if self.auto['resolvers'][t] else 'false')}
|
||||
for t in ('ipv4', 'ipv6'):
|
||||
if self.addrs[t]:
|
||||
if t == 'ipv6':
|
||||
self._cfg['Network']['LinkLocalAddressing'] = 'ipv6'
|
||||
if 'Address' not in self._cfg.keys():
|
||||
self._cfg['Address'] = []
|
||||
for addr, net, gw in self.addrs[t]:
|
||||
a = {'Address': '{0}/{1}'.format(str(addr), str(net.prefixlen))}
|
||||
self._cfg['Address'].append(a)
|
||||
if self.routes[t]:
|
||||
if 'Route' not in self._cfg.keys():
|
||||
self._cfg['Route'] = []
|
||||
for route, net, gw in self.routes[t]:
|
||||
r = {'Gateway': str(gw),
|
||||
'Destination': '{0}/{1}'.format(str(route), str(net.prefixlen))}
|
||||
self._cfg['Route'].append(r)
|
||||
if self._cfg['Network']['IPv6AcceptRA'] == 'true':
|
||||
self._cfg['Network']['LinkLocalAddressing'] = 'ipv6'
|
||||
if 'IPv6AcceptRA' not in self._cfg.keys():
|
||||
self._cfg['IPv6AcceptRA'] = {'UseDNS': ('true' if self.auto['resolvers']['ipv6'] else 'false')}
|
||||
self._initConnCfg()
|
||||
return()
|
||||
|
||||
def _initJ2(self):
|
||||
self.j2_env = jinja2.Environment(loader = jinja2.FileSystemLoader(searchpath = './'))
|
||||
self.j2_env.filters.update(aif.utils.j2_filters)
|
||||
self.j2_tpl = self.j2_env.get_template('networkd.conf.j2')
|
||||
return()
|
||||
|
||||
def writeConf(self, chroot_base):
|
||||
cfgroot = os.path.join(chroot_base, 'etc', 'systemd', 'network')
|
||||
cfgfile = os.path.join(cfgroot, self.id)
|
||||
os.makedirs(cfgroot, exist_ok = True)
|
||||
os.chown(cfgroot, 0, 0)
|
||||
os.chmod(cfgroot, 0o0755)
|
||||
with open(cfgfile, 'w') as fh:
|
||||
fh.write(self.j2_tpl.render(cfg = self._cfg))
|
||||
os.chmod(cfgfile, 0o0644)
|
||||
os.chown(cfgfile, 0, 0)
|
||||
self._writeConnCfg(chroot_base)
|
||||
return()
|
||||
|
||||
|
||||
class Ethernet(Connection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.connection_type = 'ethernet'
|
||||
self._initCfg()
|
||||
|
||||
|
||||
class Wireless(Connection):
|
||||
def __init__(self, iface_xml):
|
||||
super().__init__(iface_xml)
|
||||
self.connection_type = 'wireless'
|
||||
self.packages.add('wpa_supplicant')
|
||||
self.services['src'] = 'dest'
|
||||
self._initCfg()
|
||||
|
||||
def _initConnCfg(self):
|
||||
self._wpasupp['ssid'] = self.xml.attrib['essid']
|
||||
hidden = aif.utils.xmlBool(self.xml.attrib.get('hidden', 'false'))
|
||||
if hidden:
|
||||
self._wpasupp['scan_ssid'] = 1
|
||||
try:
|
||||
bssid = self.xml.attrib.get('bssid').strip()
|
||||
except AttributeError:
|
||||
bssid = None
|
||||
if bssid:
|
||||
bssid = aif.network._common.canonizeEUI(bssid)
|
||||
self._cfg['BASE']['AP'] = bssid
|
||||
crypto = self.xml.find('encryption')
|
||||
if crypto:
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto, self._cfg['BASE']['ESSID'])
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
# TODO: WPA2 enterprise
|
||||
self._cfg['BASE']['Security'] = 'wpa'
|
||||
# if crypto['type'] in ('wep', 'wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
self._cfg['BASE']['Key'] = crypto['auth']['psk']
|
||||
return()
|
||||
|
||||
def _writeConnCfg(self, chroot_base):
|
||||
cfgroot = os.path.join(chroot_base, 'etc', 'wpa_supplicant')
|
||||
cfgbase = os.path.join(cfgroot, 'wpa_supplicant.conf')
|
||||
cfgfile = os.path.join(cfgroot, self.id)
|
||||
|
@ -145,7 +145,7 @@ class Wireless(Connection):
|
||||
if crypto:
|
||||
self.packages.add('wpa_supplicant')
|
||||
self._cfg['wifi-security'] = {}
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto)
|
||||
crypto = aif.network._common.convertWifiCrypto(crypto, self._cfg['wifi']['ssid'])
|
||||
# if crypto['type'] in ('wpa', 'wpa2', 'wpa3'):
|
||||
if crypto['type'] in ('wpa', 'wpa2'):
|
||||
# TODO: WPA2 enterprise
|
||||
|
14
aif/utils.py
14
aif/utils.py
@ -57,6 +57,20 @@ def isPowerofTwo(n):
|
||||
return(isPowerOf2)
|
||||
|
||||
|
||||
# custom Jinja2 filters
|
||||
def j2_isDict(value):
|
||||
return(isinstance(value, dict))
|
||||
|
||||
|
||||
def j2_isList(value):
|
||||
return(isinstance(value, list))
|
||||
|
||||
|
||||
j2_filters = {'isDict': j2_isDict,
|
||||
'isList': j2_isList}
|
||||
# end custom Jinja2 filters
|
||||
|
||||
|
||||
def kernelCmdline(chroot_base = '/'):
|
||||
cmds = {}
|
||||
chroot_base = pathlib.PosixPath(chroot_base)
|
||||
|
@ -574,32 +574,35 @@ LVM (LVs, in particular), however, aren't consecutive. There *is* no concept of
|
||||
=== "How do I specify packages from the AUR?"
|
||||
You'd have to https://wiki.archlinux.org/index.php/Makepkg[build the package(s)^], https://wiki.archlinux.org/index.php/Pacman/Tips_and_tricks#Custom_local_repository[set up a repository^], serve it via e.g. https://www.nginx.com/[nginx^], and add it as a repo (`/aif/pacman/repos/repo`) first. Then you can specify the package as normal as a `/aif/pacman/software/package` item.
|
||||
|
||||
=== "Why aren't the network settings in <network> being applied during install?"
|
||||
=== "Why can't the network settings in <network> be applied during install?"
|
||||
Simply put, a logical race condition. In order for probably 90+% of AIF-NG deploys to bootstrap, they fetch their XML configuration via a network URI (rather than a file URI). This means it needs a network connection that pre-exists in the *install environment* (LiveCD, LiveUSB, PXE/iPXE, etc.) before it even knows what network configuration you want the *persistent environment* to have.
|
||||
|
||||
Granted, this is a moot point if you're using a *`file://`* URI for the XML configuration, but this is not a very flexible means regardless. If demand increases for this, future releases may include this functionality.
|
||||
Granted, this is a moot point if you're using a *`file://`* URI for the XML configuration, but this is not a very flexible means regardless. The installation host itself is outside the scope of AIF-NG.
|
||||
|
||||
If you desire the configuration to be applied *during* the install, you can do it yourself in an `/aif/scripts/pre/script` or `/aif/scripts/pkg/script` script. The fetched XML file can be found at `/var/tmp/AIF.xml` in the install environment. (Alternatively, configure the network yourself procedurally using one of those scripts).
|
||||
If you desire the configuration to be applied *during* the install, you can do it yourself in an `/aif/scripts/pre/script` or `/aif/scripts/pkg/script` script. The fetched XML file can be found at `/var/tmp/AIF.xml` in the install environment.
|
||||
|
||||
If you wish to SSH into the install environment to check the status/progress of the install, it is recommended that you set up a static lease (if using DHCP) or use SLAAC (if using IPv6) beforehand and configure your install environment beforehand. Remember, AIF-NG only *installs* Arch Linux; it tries very hard to *not* interact with the install environment.
|
||||
|
||||
=== "Why isn't enabling/disabling automatic DNS resolvers working?"
|
||||
This is going to be highly unpredictable based on the networking provider you choose. This is a limitation of underlying network provider intercompatibility, resolver libraries, and technology architecture. This may be changed in the future, but because of how DNS servers are handled via DHCP/RDNSS and glibc (and the fact that IPv4 resolver addresses can serve IPv6 -- e.g. AAAA -- records and vice versa) and inherent limitations in some network providers like netctl, I wouldn't hold your breath.
|
||||
=== "Why isn't enabling/disabling automatic DNS resolvers/routes/addresses working?"
|
||||
This is going to be highly unpredictable based on the networking provider you choose. This is a limitation of underlying network provider intercompatibility, resolver libraries, there being no way to tell DHCP/DHCP6/SLAAC clients to *only* fetch information about a network and *not* assign a lease, and technology architecture. This may be changed in the future, but because of how DNS servers are handled via DHCP/RDNSS and glibc (and the fact that IPv4 resolver addresses can serve IPv6 -- e.g. AAAA -- records and vice versa) and inherent limitations in some network providers like netctl, I wouldn't hold your breath.
|
||||
|
||||
=== "I'm using netctl as my network provider, and-"
|
||||
I'ma let you finish, but netctl is a *really* simple network provider. I mean REALLY simple. As such, a lot of things (like mixing auto DNS and non-auto addressing) don't work at all feasibly, and probably might not ever. It's great for simple and flat configurations (i.e. all static everything, all automatic everything, etc.) and I even use it on my own machines where I can, but it just simply doesn't make allowances for more complex setups. (This is why init scripts were replaced by systemd for init, remember? Script-and-shell-based utilities, such as netctl -- seriously, the entire thing's written in Bash -- just can't handle more complex jobs reliably.)
|
||||
I'ma let you finish, but netctl is a *really* simple network provider. I mean REALLY simple. As such, a lot of things don't work at all feasibly, and probably might not ever. It's great for simple and flat configurations (i.e. all static everything, all automatic everything, etc.) and I even use it on my own machines where I can, but it just simply doesn't make allowances for more complex setups. (This is why init scripts were replaced by systemd for init, remember? Script-and-shell-based utilities, such as netctl -- seriously, the entire thing's written in Bash -- just can't handle more complex jobs reliably.)
|
||||
|
||||
If you need more advanced functionality but don't want a lot of cruft or bloat, I recommend `networkd` as your network provider. It requires no extra packages (other than wpa_supplicant, if you're using wireless) because it's part of the systemd package (which is part of the most basic install of Arch) and handles more advanced configurations a lot more reliably.
|
||||
|
||||
=== "How do I specify WEP for a wireless network?"
|
||||
You can't. WEP's pretty broken. I understand some legacy networks may still use it, but I'm incredibly uncomfortable supporting it.
|
||||
|
||||
If absolutely necessary, you can manually configure it yourself via a `/aif/scripts/post/script` script.
|
||||
If absolutely necessary, you can manually configure it yourself via a `/aif/scripts/post/script` script (or just configure it once you boot the newly-installed system).
|
||||
|
||||
=== "How do I connect to a WPA2 Enterprise network?"
|
||||
==== "Then why do you allow connecting to open wireless networks in the config?"
|
||||
Because captive portals are a thing. *Authing* to them, however; that's out of my scope.
|
||||
|
||||
=== "How do I configure connecting to a WPA2 Enterprise network?"
|
||||
You can't, currently; support is only stubbed out for now. If absolutely necessary, you can manually configure it yourself via a `/aif/scripts/post/script` script.
|
||||
|
||||
This hopefully will be changed in the future, however, as I'm interested in adding support. For now, WPA/WPA2 PSK only are considered supported.
|
||||
This hopefully will be changed in the future, however, as I'm interested in adding support. For now, open and WPA/WPA2 PSK only are considered supported.
|
||||
|
||||
== Bug Reports/Feature Requests
|
||||
NOTE: It is possible to submit a bug or feature request without registering in my bugtracker. One of my pet peeves is needing to create an account/register on a bugtracker simply to report a bug! The following links only require an email address to file a bug (which is necessary in case I need any further clarification from you or to keep you updated on the status of the bug/feature request -- so please be sure to use a valid email address).
|
||||
|
@ -130,11 +130,11 @@
|
||||
</ipv6>
|
||||
</addresses>
|
||||
<routes>
|
||||
<ipv4 defaultGateway="true" auto="true">
|
||||
<ipv4 auto="true">
|
||||
<route gateway="192.168.1.1">10.1.1.0/24</route>
|
||||
<route gateway="10.1.1.4">172.16.1.20/32</route>
|
||||
</ipv4>
|
||||
<ipv6 defaultGateway="false" auto="true"/>
|
||||
<ipv6 auto="true"/>
|
||||
</routes>
|
||||
<resolvers>
|
||||
<ipv4 auto="false"/>
|
||||
|
82
extras/genPSK.py
Executable file
82
extras/genPSK.py
Executable file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import getpass
|
||||
import sys
|
||||
##
|
||||
# from passlib.utils import pbkdf2 # deprecated
|
||||
from passlib.crypto.digest import pbkdf2_hmac
|
||||
|
||||
|
||||
def pskGen(ssid, passphrase):
|
||||
# raw_psk = pbkdf2.pbkdf2(str(passphrase), str(ssid), 4096, 32) # deprecated
|
||||
raw_psk = pbkdf2_hmac('sha1', str(passphrase), str(ssid), 4096, 32)
|
||||
hex_psk = binascii.hexlify(raw_psk)
|
||||
str_psk = hex_psk.decode('utf-8')
|
||||
return(str_psk)
|
||||
|
||||
|
||||
def parseArgs():
|
||||
def essidchk(essid):
|
||||
essid = str(essid)
|
||||
if len(essid) > 32:
|
||||
raise argparse.ArgumentTypeError('The maximum length of an ESSID is 32 characters')
|
||||
return(essid)
|
||||
|
||||
def passphrasechk(passphrase):
|
||||
if passphrase:
|
||||
is_piped = False
|
||||
passphrase = str(passphrase)
|
||||
if passphrase == '-':
|
||||
if sys.stdin.isatty():
|
||||
raise argparse.ArgumentTypeError(('[STDIN] You specified a passphrase to be entered but did not '
|
||||
'provide one via a pipe.'))
|
||||
else:
|
||||
is_piped = True
|
||||
try:
|
||||
# WPA-PSK only accepts ASCII for passphrase.
|
||||
raw_pass = sys.stdin.read().encode('utf-8').decode('ascii').strip('\r').strip('\n')
|
||||
except UnicodeDecodeError:
|
||||
raise argparse.ArgumentTypeError('[STDIN] WPA-PSK passphrases must be an ASCII string')
|
||||
if not 7 < len(passphrase) < 64:
|
||||
raise argparse.ArgumentTypeError(('{0}WPA-PSK passphrases must be no shorter than 8 characters'
|
||||
' and no longer than 63 characters. '
|
||||
'Please ensure you have provided the '
|
||||
'correct passphrase.').format(('[STDIN] ' if is_piped else '')))
|
||||
return(passphrase)
|
||||
|
||||
args = argparse.ArgumentParser(description = 'Generate a PSK from a passphrase')
|
||||
args.add_argument('-p', '--passphrase',
|
||||
dest = 'passphrase',
|
||||
default = None,
|
||||
type = passphrasechk,
|
||||
help = ('If specified, use this passphrase (otherwise securely interactively prompt for it). '
|
||||
'If "-" (without quotes), read from stdin (via a pipe). '
|
||||
'WARNING: THIS OPTION IS INSECURE AND MAY EXPOSE THE PASSPHRASE GIVEN '
|
||||
'TO OTHER PROCESSES ON THIS SYSTEM'))
|
||||
args.add_argument('ssid',
|
||||
metavar = 'ESSID',
|
||||
type = essidchk,
|
||||
help = ('The ESSID (network name) to use for this passphrase. '
|
||||
'(This is required because WPA-PSK uses it to salt the key derivation)'))
|
||||
return(args)
|
||||
|
||||
|
||||
def main():
|
||||
args = parseArgs().parse_args()
|
||||
if not args.passphrase:
|
||||
args.passphrase = getpass.getpass(('Please enter the passphrase for '
|
||||
'network "{0}" (will NOT echo back): ').format(args.ssid))
|
||||
args.passphrase = args.passphrase.encode('utf-8').decode('ascii').strip('\r').strip('\n')
|
||||
if not 7 < len(args.passphrase) < 64:
|
||||
raise ValueError(('WPA-PSK passphrases must be no shorter than 8 characters'
|
||||
' and no longer than 63 characters. '
|
||||
'Please ensure you have provided the correct passphrase.'))
|
||||
psk = pskGen(args.ssid, args.passphrase)
|
||||
print('PSK for network "{0}": {1}'.format(args.ssid, psk))
|
||||
return()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user