blob: ac55961c74850e2a3f723e91b3a9bffe3469e26f [file] [log] [blame]
# Copyright 2022 The Fuchsia Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import string
from enum import Enum, unique
from typing import Dict, Union
from antlion.controllers.ap_lib import hostapd_constants
from antlion.typing import StrEnum
class SecurityModeInt(int, Enum):
"""Possible values for hostapd's "wpa" config option.
The int value is a bit field that can enable WPA and/or WPA2.
bit0 = enable WPA defined by IEEE 802.11i/D3.0
bit1 = enable RNA (WPA2) defined by IEEE 802.11i/RSN
bit2 = enable WAPI (rejected/withdrawn)
bit3 = enable OSEN (ENT)
"""
WEP = 0
WPA1 = 1
WPA2 = 2
WPA3 = 2 # same as wpa2 and wpa2/wpa3; distinguished by wpa_key_mgmt
MIXED = 3 # applies to wpa/wpa2 and wpa/wpa2/wpa3; distinguished by wpa_key_mgmt
ENT = 8
def __str__(self):
return str(self.value)
@unique
class KeyManagement(StrEnum):
SAE = "SAE"
WPA_PSK = "WPA-PSK"
WPA_PSK_SAE = "WPA-PSK SAE"
ENT = "WPA-EAP"
# TODO(http://b/286584981): This is currently only being used for OpenWRT.
# Investigate whether we can replace KeyManagement with OpenWRTEncryptionMode.
@unique
class OpenWRTEncryptionMode(StrEnum):
"""Combination of Wi-Fi encryption mode and ciphers.
Only used by OpenWRT.
Besides the encryption mode, the encryption option also specifies the group and peer
ciphers to use. To override the cipher, the value of encryption must be given in the
form "mode+cipher". This enum contains all possible combinations.
See https://openwrt.org/docs/guide-user/network/wifi/basic#encryption_modes.
"""
NONE = "none"
"""No authentication, no ciphers"""
SAE = "sae"
"""WPA3 Personal (SAE) using CCMP cipher"""
SAE_MIXED = "sae-mixed"
"""WPA2/WPA3 Personal (PSK/SAE) mixed mode using CCMP cipher"""
PSK2_TKIP_CCMP = "psk2+tkip+ccmp"
"""WPA2 Personal (PSK) using TKIP and CCMP ciphers"""
PSK2_TKIP_AES = "psk2+tkip+aes"
"""WPA2 Personal (PSK) using TKIP and AES ciphers"""
PSK2_TKIP = "psk2+tkip"
"""WPA2 Personal (PSK) using TKIP cipher"""
PSK2_CCMP = "psk2+ccmp"
"""WPA2 Personal (PSK) using CCMP cipher"""
PSK2_AES = "psk2+aes"
"""WPA2 Personal (PSK) using AES cipher"""
PSK2 = "psk2"
"""WPA2 Personal (PSK) using CCMP cipher"""
PSK_TKIP_CCMP = "psk+tkip+ccmp"
"""WPA Personal (PSK) using TKIP and CCMP ciphers"""
PSK_TKIP_AES = "psk+tkip+aes"
"""WPA Personal (PSK) using TKIP and AES ciphers"""
PSK_TKIP = "psk+tkip"
"""WPA Personal (PSK) using TKIP cipher"""
PSK_CCMP = "psk+ccmp"
"""WPA Personal (PSK) using CCMP cipher"""
PSK_AES = "psk+aes"
"""WPA Personal (PSK) using AES cipher"""
PSK = "psk"
"""WPA Personal (PSK) using CCMP cipher"""
PSK_MIXED_TKIP_CCMP = "psk-mixed+tkip+ccmp"
"""WPA/WPA2 Personal (PSK) mixed mode using TKIP and CCMP ciphers"""
PSK_MIXED_TKIP_AES = "psk-mixed+tkip+aes"
"""WPA/WPA2 Personal (PSK) mixed mode using TKIP and AES ciphers"""
PSK_MIXED_TKIP = "psk-mixed+tkip"
"""WPA/WPA2 Personal (PSK) mixed mode using TKIP cipher"""
PSK_MIXED_CCMP = "psk-mixed+ccmp"
"""WPA/WPA2 Personal (PSK) mixed mode using CCMP cipher"""
PSK_MIXED_AES = "psk-mixed+aes"
"""WPA/WPA2 Personal (PSK) mixed mode using AES cipher"""
PSK_MIXED = "psk-mixed"
"""WPA/WPA2 Personal (PSK) mixed mode using CCMP cipher"""
WEP = "wep"
"""defaults to “open system” authentication aka wep+open using RC4 cipher"""
WEP_OPEN = "wep+open"
"""“open system” authentication using RC4 cipher"""
WEP_SHARED = "wep+shared"
"""“shared key” authentication using RC4 cipher"""
WPA3 = "wpa3"
"""WPA3 Enterprise using CCMP cipher"""
WPA3_MIXED = "wpa3-mixed"
"""WPA3/WPA2 Enterprise using CCMP cipher"""
WPA2_TKIP_CCMP = "wpa2+tkip+ccmp"
"""WPA2 Enterprise using TKIP and CCMP ciphers"""
WPA2_TKIP_AES = "wpa2+tkip+aes"
"""WPA2 Enterprise using TKIP and AES ciphers"""
WPA2_CCMP = "wpa2+ccmp"
"""WPA2 Enterprise using CCMP cipher"""
WPA2_AES = "wpa2+aes'"
"""WPA2 Enterprise using AES cipher"""
WPA2 = "wpa2"
"""WPA2 Enterprise using CCMP cipher"""
WPA2_TKIP = "wpa2+tkip"
"""WPA2 Enterprise using TKIP cipher"""
WPA_TKIP_CCMP = "wpa+tkip+ccmp"
"""WPA Enterprise using TKIP and CCMP ciphers"""
WPA_TKIP_AES = "wpa+tkip+aes"
"""WPA Enterprise using TKIP and AES ciphers"""
WPA_CCMP = "wpa+ccmp"
"""WPA Enterprise using CCMP cipher"""
WPA_AES = "wpa+aes"
"""WPA Enterprise using AES cipher"""
WPA_TKIP = "wpa+tkip"
"""WPA Enterprise using TKIP cipher"""
WPA = "wpa"
"""WPA Enterprise using CCMP cipher"""
WPA_MIXED_TKIP_CCMP = "wpa-mixed+tkip+ccmp"
"""WPA/WPA2 Enterprise mixed mode using TKIP and CCMP ciphers"""
WPA_MIXED_TKIP_AES = "wpa-mixed+tkip+aes"
"""WPA/WPA2 Enterprise mixed mode using TKIP and AES ciphers"""
WPA_MIXED_TKIP = "wpa-mixed+tkip"
"""WPA/WPA2 Enterprise mixed mode using TKIP cipher"""
WPA_MIXED_CCMP = "wpa-mixed+ccmp"
"""WPA/WPA2 Enterprise mixed mode using CCMP cipher"""
WPA_MIXED_AES = "wpa-mixed+aes"
"""WPA/WPA2 Enterprise mixed mode using AES cipher"""
WPA_MIXED = "wpa-mixed"
"""WPA/WPA2 Enterprise mixed mode using CCMP cipher"""
OWE = "owe"
"""Opportunistic Wireless Encryption (OWE) using CCMP cipher"""
@unique
class FuchsiaSecurityType(StrEnum):
"""Fuchsia supported security types.
Defined by the fuchsia.wlan.policy.SecurityType FIDL.
https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.wlan.policy/types.fidl
"""
NONE = "none"
WEP = "wep"
WPA = "wpa"
WPA2 = "wpa2"
WPA3 = "wpa3"
@unique
class SecurityMode(StrEnum):
OPEN = "open"
WEP = "wep"
WPA = "wpa"
WPA2 = "wpa2"
WPA_WPA2 = "wpa/wpa2"
WPA3 = "wpa3"
WPA2_WPA3 = "wpa2/wpa3"
WPA_WPA2_WPA3 = "wpa/wpa2/wpa3"
ENT = "ent"
def security_mode_int(self) -> SecurityModeInt:
match self:
case SecurityMode.OPEN:
raise TypeError("Open security doesn't have a SecurityModeInt")
case SecurityMode.WEP:
return SecurityModeInt.WEP
case SecurityMode.WPA:
return SecurityModeInt.WPA1
case SecurityMode.WPA2:
return SecurityModeInt.WPA2
case SecurityMode.WPA_WPA2:
return SecurityModeInt.MIXED
case SecurityMode.WPA3:
return SecurityModeInt.WPA3
case SecurityMode.WPA2_WPA3:
return SecurityModeInt.WPA3
case SecurityMode.WPA_WPA2_WPA3:
return SecurityModeInt.MIXED
case SecurityMode.ENT:
return SecurityModeInt.ENT
def key_management(self) -> KeyManagement | None:
match self:
case SecurityMode.OPEN:
return None
case SecurityMode.WEP:
return None
case SecurityMode.WPA:
return KeyManagement.WPA_PSK
case SecurityMode.WPA2:
return KeyManagement.WPA_PSK
case SecurityMode.WPA_WPA2:
return KeyManagement.WPA_PSK
case SecurityMode.WPA3:
return KeyManagement.SAE
case SecurityMode.WPA2_WPA3:
return KeyManagement.WPA_PSK_SAE
case SecurityMode.WPA_WPA2_WPA3:
return KeyManagement.WPA_PSK_SAE
case SecurityMode.ENT:
return KeyManagement.ENT
def fuchsia_security_type(self) -> FuchsiaSecurityType:
match self:
case SecurityMode.OPEN:
return FuchsiaSecurityType.NONE
case SecurityMode.WEP:
return FuchsiaSecurityType.WEP
case SecurityMode.WPA:
return FuchsiaSecurityType.WPA
case SecurityMode.WPA2:
return FuchsiaSecurityType.WPA2
case SecurityMode.WPA_WPA2:
return FuchsiaSecurityType.WPA2
case SecurityMode.WPA3:
return FuchsiaSecurityType.WPA3
case SecurityMode.WPA2_WPA3:
return FuchsiaSecurityType.WPA3
case SecurityMode.WPA_WPA2_WPA3:
return FuchsiaSecurityType.WPA3
case SecurityMode.ENT:
raise NotImplementedError(
f'Fuchsia has not implemented support for security mode "{self}"'
)
def is_wpa3(self) -> bool:
match self:
case SecurityMode.OPEN:
return False
case SecurityMode.WEP:
return False
case SecurityMode.WPA:
return False
case SecurityMode.WPA2:
return False
case SecurityMode.WPA_WPA2:
return False
case SecurityMode.WPA3:
return True
case SecurityMode.WPA2_WPA3:
return True
case SecurityMode.WPA_WPA2_WPA3:
return True
case SecurityMode.ENT:
return False
raise TypeError("Unknown security mode")
class Security(object):
"""The Security class for hostapd representing some of the security
settings that are allowed in hostapd. If needed more can be added.
"""
def __init__(
self,
security_mode: SecurityMode = SecurityMode.OPEN,
password: str | None = None,
wpa_cipher: str | None = hostapd_constants.WPA_DEFAULT_CIPHER,
wpa2_cipher: str | None = hostapd_constants.WPA2_DEFAULT_CIPER,
wpa_group_rekey: int = hostapd_constants.WPA_GROUP_KEY_ROTATION_TIME,
wpa_strict_rekey: bool = hostapd_constants.WPA_STRICT_REKEY_DEFAULT,
wep_default_key: int = hostapd_constants.WEP_DEFAULT_KEY,
radius_server_ip: str | None = None,
radius_server_port: int | None = None,
radius_server_secret: str | None = None,
) -> None:
"""Gather all of the security settings for WPA-PSK. This could be
expanded later.
Args:
security_mode: Type of security mode.
password: The PSK or passphrase for the security mode.
wpa_cipher: The cipher to be used for wpa.
Options: TKIP, CCMP, TKIP CCMP
Default: TKIP
wpa2_cipher: The cipher to be used for wpa2.
Options: TKIP, CCMP, TKIP CCMP
Default: CCMP
wpa_group_rekey: How often to refresh the GTK regardless of network
changes.
Options: An integer in seconds, None
Default: 600 seconds
wpa_strict_rekey: Whether to do a group key update when client
leaves the network or not.
Options: True, False
Default: True
wep_default_key: The wep key number to use when transmitting.
radius_server_ip: Radius server IP for Enterprise auth.
radius_server_port: Radius server port for Enterprise auth.
radius_server_secret: Radius server secret for Enterprise auth.
"""
self.security_mode = security_mode
self.wpa_cipher = wpa_cipher
self.wpa2_cipher = wpa2_cipher
self.wpa_group_rekey = wpa_group_rekey
self.wpa_strict_rekey = wpa_strict_rekey
self.wep_default_key = wep_default_key
self.radius_server_ip = radius_server_ip
self.radius_server_port = radius_server_port
self.radius_server_secret = radius_server_secret
if password:
if self.security_mode is SecurityMode.WEP:
if len(password) in hostapd_constants.WEP_STR_LENGTH:
self.password = f'"{password}"'
elif len(password) in hostapd_constants.WEP_HEX_LENGTH and all(
c in string.hexdigits for c in password
):
self.password = password
else:
raise ValueError(
"WEP key must be a hex string of %s characters"
% hostapd_constants.WEP_HEX_LENGTH
)
else:
if (
len(password) < hostapd_constants.MIN_WPA_PSK_LENGTH
or len(password) > hostapd_constants.MAX_WPA_PSK_LENGTH
):
raise ValueError(
"Password must be a minumum of %s characters and a maximum of %s"
% (
hostapd_constants.MIN_WPA_PSK_LENGTH,
hostapd_constants.MAX_WPA_PSK_LENGTH,
)
)
else:
self.password = password
def generate_dict(self) -> Dict[str, Union[str, int]]:
"""Returns: an ordered dictionary of settings"""
if self.security_mode is SecurityMode.OPEN:
return {}
settings: Dict[str, Union[str, int]] = collections.OrderedDict()
if self.security_mode is SecurityMode.WEP:
settings["wep_default_key"] = self.wep_default_key
settings[f"wep_key{self.wep_default_key}"] = self.password
elif self.security_mode == SecurityMode.ENT:
if self.radius_server_ip is not None:
settings["auth_server_addr"] = self.radius_server_ip
if self.radius_server_port is not None:
settings["auth_server_port"] = self.radius_server_port
if self.radius_server_secret is not None:
settings["auth_server_shared_secret"] = self.radius_server_secret
settings["wpa_key_mgmt"] = hostapd_constants.ENT_KEY_MGMT
settings["ieee8021x"] = hostapd_constants.IEEE8021X
settings["wpa"] = hostapd_constants.WPA2
else:
settings["wpa"] = self.security_mode.security_mode_int().value
if len(self.password) == hostapd_constants.MAX_WPA_PSK_LENGTH:
settings["wpa_psk"] = self.password
else:
settings["wpa_passphrase"] = self.password
# For wpa, wpa/wpa2, and wpa/wpa2/wpa3, add wpa_pairwise
if self.wpa_cipher and (
self.security_mode is SecurityMode.WPA
or self.security_mode is SecurityMode.WPA_WPA2
or self.security_mode is SecurityMode.WPA_WPA2_WPA3
):
settings["wpa_pairwise"] = self.wpa_cipher
# For wpa/wpa2, wpa2, wpa3, and wpa2/wpa3, and wpa/wpa2, wpa3, add rsn_pairwise
if self.wpa2_cipher and (
self.security_mode is SecurityMode.WPA_WPA2
or self.security_mode is SecurityMode.WPA2
or self.security_mode is SecurityMode.WPA2_WPA3
or self.security_mode is SecurityMode.WPA3
):
settings["rsn_pairwise"] = self.wpa2_cipher
# Add wpa_key_mgmt based on security mode string
wpa_key_mgmt = self.security_mode.key_management()
if wpa_key_mgmt is not None:
settings["wpa_key_mgmt"] = str(wpa_key_mgmt)
if self.wpa_group_rekey:
settings["wpa_group_rekey"] = self.wpa_group_rekey
if self.wpa_strict_rekey:
settings["wpa_strict_rekey"] = hostapd_constants.WPA_STRICT_REKEY
return settings