]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
tests: DPP Connector testing with ECDSA signature in Python
authorJouni Malinen <j@w1.fi>
Sun, 25 Nov 2018 20:01:35 +0000 (22:01 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 25 Nov 2018 20:01:35 +0000 (22:01 +0200)
Implement ECDSA signing functionality in the Python test script for
generating a valid signedConnector. This allows coverage of DPP config
object testing to be increased more easily.

Signed-off-by: Jouni Malinen <j@w1.fi>
tests/hwsim/test_dpp.py

index 1fb993cf1ad37bce5f1fc8edacca095984ef5473..377942171688385292e8941a3a0c9d0d29876454 100644 (file)
@@ -6,8 +6,11 @@
 # See README for more details.
 
 import base64
+import binascii
+import hashlib
 import logging
 logger = logging.getLogger()
+import struct
 import subprocess
 import time
 
@@ -16,6 +19,12 @@ import hwsim_utils
 from utils import HwsimSkip, alloc_fail, fail_test, wait_fail_trigger
 from wpasupplicant import WpaSupplicant
 
+try:
+    import OpenSSL
+    openssl_imported = True
+except ImportError:
+    openssl_imported = False
+
 def check_dpp_capab(dev, brainpool=False):
     if "UNKNOWN COMMAND" in dev.request("DPP_BOOTSTRAP_GET_URI 0"):
         raise HwsimSkip("DPP not supported")
@@ -1207,10 +1216,12 @@ def build_conf_obj(kty="EC", crv="P-256",
 
     return conf
 
-def run_dpp_config_error(dev, apdev, conf):
+def run_dpp_config_error(dev, apdev, conf,
+                         skip_net_access_key_mismatch=True):
     check_dpp_capab(dev[0])
     check_dpp_capab(dev[1])
-    dev[0].set("dpp_ignore_netaccesskey_mismatch", "1")
+    if skip_net_access_key_mismatch:
+        dev[0].set("dpp_ignore_netaccesskey_mismatch", "1")
     dev[1].set("dpp_config_obj_override", conf)
     run_dpp_qr_code_auth_unicast(dev, apdev, "prime256v1",
                                  require_conf_failure=True)
@@ -1403,6 +1414,164 @@ def test_dpp_config_error_legacy_too_short_psk(dev, apdev):
     conf = '{"wi-fi_tech":"infra","discovery":{"ssid":"test"},"cred":{"akm":"psk","psk_hex":"%s"}}' % (31*"12")
     run_dpp_config_error(dev, apdev, conf)
 
+def ecdsa_sign(pkey, message, alg="sha256"):
+    sign = OpenSSL.crypto.sign(pkey, message, alg)
+    a,b = struct.unpack('BB', sign[0:2])
+    if a != 0x30:
+        raise Exception("Invalid DER encoding of ECDSA signature")
+    if b != len(sign) - 2:
+        raise Exception("Invalid length of ECDSA signature")
+    sign = sign[2:]
+
+    a,b = struct.unpack('BB', sign[0:2])
+    if a != 0x02:
+        raise Exception("Invalid DER encoding of ECDSA signature r")
+    if b > len(sign) - 2:
+        raise Exception("Invalid length of ECDSA signature r")
+    sign = sign[2:]
+    if b == 32:
+        r = sign[0:32]
+        sign = sign[32:]
+    elif b == 33:
+        r = sign[1:33]
+        sign = sign[33:]
+    else:
+        raise Exception("Invalid length of ECDSA signature r")
+
+    a,b = struct.unpack('BB', sign[0:2])
+    if a != 0x02:
+        raise Exception("Invalid DER encoding of ECDSA signature s")
+    if b > len(sign) - 2:
+        raise Exception("Invalid length of ECDSA signature s")
+    sign = sign[2:]
+    if b == 32:
+        s = sign[0:32]
+        sign = sign[32:]
+    elif b == 33:
+        s = sign[1:33]
+        sign = sign[33:]
+    else:
+        raise Exception("Invalid length of ECDSA signature s")
+    if len(sign) != 0:
+        raise Exception("Extra data at the end of ECDSA signature")
+
+    raw_sign = r + s
+    return base64.urlsafe_b64encode(raw_sign).rstrip('=')
+
+p256_priv_key = """-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIBVQij9ah629f1pu3tarDQGQvrzHgAkgYd1jHGiLxNajoAoGCCqGSM49
+AwEHoUQDQgAEAC9d2/JirKu72F2qLuv5jEFMD1Cqu9EiyGk7cOzn/2DJ51p2mEoW
+n03N6XRvTC+G7WPol9Ng97NAM2sK57+F/Q==
+-----END EC PRIVATE KEY-----"""
+p256_pub_key_x = binascii.unhexlify("002f5ddbf262acabbbd85daa2eebf98c414c0f50aabbd122c8693b70ece7ff60")
+p256_pub_key_y = binascii.unhexlify("c9e75a76984a169f4dcde9746f4c2f86ed63e897d360f7b340336b0ae7bf85fd")
+
+def run_dpp_config_connector(dev, apdev, expiry=None, payload=None,
+                             skip_net_access_key_mismatch=True):
+    if not openssl_imported:
+        raise HwsimSkip("OpenSSL python method not available")
+    pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM,
+                                          p256_priv_key)
+    x = base64.urlsafe_b64encode(p256_pub_key_x).rstrip('=')
+    y = base64.urlsafe_b64encode(p256_pub_key_y).rstrip('=')
+
+    pubkey = '\04' + p256_pub_key_x + p256_pub_key_y
+    kid = base64.urlsafe_b64encode(hashlib.sha256(pubkey).digest()).rstrip('=')
+
+    prot_hdr = '{"typ":"dppCon","kid":"%s","alg":"ES256"}' % kid
+
+    if not payload:
+        payload = '{"groups":[{"groupId":"*","netRole":"sta"}],"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}'
+        if expiry:
+            payload += ',"expiry":"%s"' % expiry
+        payload += '}'
+    conn = base64.urlsafe_b64encode(prot_hdr).rstrip('=') + '.'
+    conn += base64.urlsafe_b64encode(payload).rstrip('=')
+    sign = ecdsa_sign(pkey, conn)
+    conn += '.' + sign
+    run_dpp_config_error(dev, apdev,
+                         build_conf_obj(x=x, y=y, signed_connector=conn),
+                         skip_net_access_key_mismatch=skip_net_access_key_mismatch)
+
+def test_dpp_config_connector_error_ext_sign(dev, apdev):
+    """DPP Config Object connector error - external signature calculation"""
+    run_dpp_config_connector(dev, apdev)
+
+def test_dpp_config_connector_error_too_short_timestamp(dev, apdev):
+    """DPP Config Object connector error - too short timestamp"""
+    run_dpp_config_connector(dev, apdev, expiry="1")
+
+def test_dpp_config_connector_error_invalid_timestamp(dev, apdev):
+    """DPP Config Object connector error - invalid timestamp"""
+    run_dpp_config_connector(dev, apdev, expiry=19*"1")
+
+def test_dpp_config_connector_error_invalid_timestamp_date(dev, apdev):
+    """DPP Config Object connector error - invalid timestamp date"""
+    run_dpp_config_connector(dev, apdev, expiry="9999-99-99T99:99:99Z")
+
+def test_dpp_config_connector_error_invalid_time_zone(dev, apdev):
+    """DPP Config Object connector error - invalid time zone"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00*")
+
+def test_dpp_config_connector_error_invalid_time_zone_2(dev, apdev):
+    """DPP Config Object connector error - invalid time zone 2"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00+")
+
+def test_dpp_config_connector_error_expired_1(dev, apdev):
+    """DPP Config Object connector error - expired 1"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00")
+
+def test_dpp_config_connector_error_expired_2(dev, apdev):
+    """DPP Config Object connector error - expired 2"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00Z")
+
+def test_dpp_config_connector_error_expired_3(dev, apdev):
+    """DPP Config Object connector error - expired 3"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00+01")
+
+def test_dpp_config_connector_error_expired_4(dev, apdev):
+    """DPP Config Object connector error - expired 4"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00+01:02")
+
+def test_dpp_config_connector_error_expired_5(dev, apdev):
+    """DPP Config Object connector error - expired 5"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00-01")
+
+def test_dpp_config_connector_error_expired_6(dev, apdev):
+    """DPP Config Object connector error - expired 6"""
+    run_dpp_config_connector(dev, apdev, expiry="2018-01-01T00:00:00-01:02")
+
+def test_dpp_config_connector_error_no_groups(dev, apdev):
+    """DPP Config Object connector error - no groups"""
+    payload = '{"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}}'
+    run_dpp_config_connector(dev, apdev, payload=payload)
+
+def test_dpp_config_connector_error_empty_groups(dev, apdev):
+    """DPP Config Object connector error - empty groups"""
+    payload = '{"groups":[],"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}}'
+    run_dpp_config_connector(dev, apdev, payload=payload)
+
+def test_dpp_config_connector_error_missing_group_id(dev, apdev):
+    """DPP Config Object connector error - missing groupId"""
+    payload = '{"groups":[{"netRole":"sta"}],"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}}'
+    run_dpp_config_connector(dev, apdev, payload=payload)
+
+def test_dpp_config_connector_error_missing_net_role(dev, apdev):
+    """DPP Config Object connector error - missing netRole"""
+    payload = '{"groups":[{"groupId":"*"}],"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}}'
+    run_dpp_config_connector(dev, apdev, payload=payload)
+
+def test_dpp_config_connector_error_missing_net_access_key(dev, apdev):
+    """DPP Config Object connector error - missing netAccessKey"""
+    payload = '{"groups":[{"groupId":"*","netRole":"sta"}]}'
+    run_dpp_config_connector(dev, apdev, payload=payload)
+
+def test_dpp_config_connector_error_net_access_key_mismatch(dev, apdev):
+    """DPP Config Object connector error - netAccessKey mismatch"""
+    payload = '{"groups":[{"groupId":"*","netRole":"sta"}],"netAccessKey":{"kty":"EC","crv":"P-256","x":"aTF4JEGIPKSZ0Xv9zdCMjm-tn5XpMsYIVZ9wySAz1gI","y":"QGcHWA_6rbU9XDXAztoX-M5Q3suTnMaqEhULtn7SSXw"}}'
+    run_dpp_config_connector(dev, apdev, payload=payload,
+                             skip_net_access_key_mismatch=False)
+
 def test_dpp_gas_timeout(dev, apdev):
     """DPP and GAS server timeout for a query"""
     check_dpp_capab(dev[0])