From 32f2f1c0a9afeb64c2004c91fc3604d71406cfaa Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Mon, 13 Jan 2025 20:22:26 +0000 Subject: [PATCH 01/17] Split Key types chapter to improve structure * Overview of key types, accessors, and ECC/DH familes * Definition of unstructured key types * Definition of asymmetric key types --- doc/crypto/api/keys/types.rst | 898 ++++++++++++++++++---------------- 1 file changed, 475 insertions(+), 423 deletions(-) diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index f54f1dfb..95cdaf7c 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -7,10 +7,10 @@ .. _key-types: Key types -========= +--------- Key type encoding ------------------ +~~~~~~~~~~~~~~~~~ .. typedef:: uint16_t psa_key_type_t @@ -41,7 +41,16 @@ Key type encoding Zero is not the encoding of any key type. Key categories --------------- +~~~~~~~~~~~~~~ + +In the |API|, keys are typically used to store secrets that are specific to a set of related cryptographic algorithms. +Keys can also be used to store non-cryptographic secrets or other data. +The key type is used to identify what the key value is, and what can be used for. + +* :secref:`unstructured-keys` --- defines types for non-key data and unstructured symmetric keys. + For example, passwords, key-derivation secrets, or AES keys. +* :secref:`asymmetric-keys` --- defines types for asymmetric keys. + For example, elliptic curve or SPAKE2+ keys. .. macro:: PSA_KEY_TYPE_IS_UNSTRUCTURED :definition: /* specification-defined value */ @@ -54,7 +63,7 @@ Key categories This encompasses both symmetric keys and non-key data. - See :secref:`symmetric-keys` for a list of symmetric key types. + See :secref:`unstructured-keys` for a list of unstructured key types. .. macro:: PSA_KEY_TYPE_IS_ASYMMETRIC :definition: /* specification-defined value */ @@ -86,74 +95,352 @@ Key categories A key type: a value of type `psa_key_type_t`. -.. _symmetric-keys: +Elliptic curve families +~~~~~~~~~~~~~~~~~~~~~~~ -Symmetric keys --------------- +.. typedef:: uint8_t psa_ecc_family_t -.. macro:: PSA_KEY_TYPE_RAW_DATA - :definition: ((psa_key_type_t)0x1001) + .. summary:: + The type of identifiers of an elliptic curve family. + + The curve identifier is required to create a number of key types: + + * ECC keys using `PSA_KEY_TYPE_ECC_KEY_PAIR()` or `PSA_KEY_TYPE_ECC_PUBLIC_KEY()`. + These keys are used in various asymmetric signature, key-encapsulation, and key-agreement algorithms. + * SPAKE2+ keys using the `PSA_KEY_TYPE_SPAKE2P_KEY_PAIR()` or `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY()`. + These keys are used in the SPAKE2+ PAKE algorithms. + * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_ECC_PT()`. + These keys are used in the WPA3-SAE PAKE algorithms. + + The specific ECC curve within a family is identified by the ``key_bits`` attribute of the key. + + The range of elliptic curve family identifier values is divided as follows: + + :code:`0x00` + Reserved. + Not allocated to an ECC family. + :code:`0x01 - 0x7f` + ECC family identifiers defined by this standard. + Unallocated values in this range are reserved for future use. + :code:`0x80 - 0xff` + Invalid. + Values in this range must not be used. + + The least significant bit of a elliptic curve family identifier is a parity bit for the whole key type. + See :secref:`asymmetric-key-encoding` for details of the encoding of asymmetric key types. + + .. admonition:: Implementation note + + To provide other elliptic curve families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. + +.. macro:: PSA_ECC_FAMILY_SECP_K1 + :definition: ((psa_ecc_family_t) 0x17) .. summary:: - Raw data. + SEC Koblitz curves over prime fields. - A "key" of this type cannot be used for any cryptographic operation. Applications can use this type to store arbitrary data in the keystore. + This family comprises the following curves: - The bit size of a raw key must be a non-zero multiple of 8. The maximum size of a raw key is :scterm:`IMPLEMENTATION DEFINED`. + * secp192k1 : ``key_bits = 192`` + * secp224k1 : ``key_bits = 225`` + * secp256k1 : ``key_bits = 256`` - .. subsection:: Compatible algorithms + They are defined in :cite-title:`SEC2`. - A key of this type can also be used as a non-secret input to the following key-derivation algorithms: +.. macro:: PSA_ECC_FAMILY_SECP_R1 + :definition: ((psa_ecc_family_t) 0x12) - .. hlist:: + .. summary:: + SEC random curves over prime fields. - * `PSA_ALG_HKDF` - * `PSA_ALG_HKDF_EXPAND` - * `PSA_ALG_HKDF_EXTRACT` - * `PSA_ALG_SP800_108_COUNTER_HMAC` - * `PSA_ALG_SP800_108_COUNTER_CMAC` - * `PSA_ALG_TLS12_PRF` - * `PSA_ALG_TLS12_PSK_TO_MS` + This family comprises the following curves: - .. subsection:: Key format + * secp192r1 : ``key_bits = 192`` + * secp224r1 : ``key_bits = 224`` + * secp256r1 : ``key_bits = 256`` + * secp384r1 : ``key_bits = 384`` + * secp521r1 : ``key_bits = 521`` - The data format for import and export of the key is the raw bytes of the key. + They are defined in :cite:`SEC2`. - .. subsection:: Key derivation +.. macro:: PSA_ECC_FAMILY_SECP_R2 + :definition: ((psa_ecc_family_t) 0x1b) - A call to `psa_key_derivation_output_key()` will draw :math:`m/8` bytes of output and use these as the key data, where :math:`m` is the bit-size of the key. + .. summary:: + .. warning:: + This family of curves is weak and deprecated. -.. macro:: PSA_KEY_TYPE_HMAC - :definition: ((psa_key_type_t)0x1100) + This family comprises the following curves: + + * secp160r2 : ``key_bits = 160`` *(Deprecated)* + + It is defined in the superseded :cite-title:`SEC2v1`. + +.. macro:: PSA_ECC_FAMILY_SECT_K1 + :definition: ((psa_ecc_family_t) 0x27) .. summary:: - HMAC key. + SEC Koblitz curves over binary fields. - HMAC keys can be used in HMAC, or HMAC-based, algorithms. - Although HMAC is parameterized by a specific hash algorithm, for example SHA-256, the hash algorithm is not specified in the key type. - The permitted-algorithm policy for the key must specify a particular hash algorithm. + This family comprises the following curves: - The bit size of an HMAC key must be a non-zero multiple of 8. - An HMAC key is typically the same size as the output of the underlying hash algorithm. - An HMAC key that is longer than the block size of the underlying hash algorithm will be hashed before use, see :RFC-title:`2104#2`. + * sect163k1 : ``key_bits = 163`` *(Deprecated)* + * sect233k1 : ``key_bits = 233`` + * sect239k1 : ``key_bits = 239`` + * sect283k1 : ``key_bits = 283`` + * sect409k1 : ``key_bits = 409`` + * sect571k1 : ``key_bits = 571`` - It is recommended that an application does not construct HMAC keys that are longer than the block size of the hash algorithm that will be used. - It is :scterm:`implementation defined` whether an HMAC key that is longer than the hash block size is supported. + They are defined in :cite:`SEC2`. - If the application does not control the length of the data used to construct the HMAC key, it is recommended that the application hashes the key data, when it exceeds the hash block length, before constructing the HMAC key. + .. warning:: + The 163-bit curve sect163k1 is weak and deprecated and is only recommended for use in legacy applications. - .. note:: +.. macro:: PSA_ECC_FAMILY_SECT_R1 + :definition: ((psa_ecc_family_t) 0x22) - :code:`PSA_HASH_LENGTH(alg)` provides the output size of hash algorithm ``alg``, in bytes. + .. summary:: + SEC random curves over binary fields. - :code:`PSA_HASH_BLOCK_LENGTH(alg)` provides the block size of hash algorithm ``alg``, in bytes. + This family comprises the following curves: + + * sect163r1 : ``key_bits = 163`` *(Deprecated)* + * sect233r1 : ``key_bits = 233`` + * sect283r1 : ``key_bits = 283`` + * sect409r1 : ``key_bits = 409`` + * sect571r1 : ``key_bits = 571`` + + They are defined in :cite:`SEC2`. + + .. warning:: + The 163-bit curve sect163r1 is weak and deprecated and is only recommended for use in legacy applications. + +.. macro:: PSA_ECC_FAMILY_SECT_R2 + :definition: ((psa_ecc_family_t) 0x2b) + + .. summary:: + SEC additional random curves over binary fields. + + This family comprises the following curves: + + * sect163r2 : ``key_bits = 163`` *(Deprecated)* + + It is defined in :cite:`SEC2`. + + .. warning:: + The 163-bit curve sect163r2 is weak and deprecated and is only recommended for use in legacy applications. + +.. macro:: PSA_ECC_FAMILY_BRAINPOOL_P_R1 + :definition: ((psa_ecc_family_t) 0x30) + + .. summary:: + Brainpool P random curves. + + This family comprises the following curves: + + * brainpoolP160r1 : ``key_bits = 160`` *(Deprecated)* + * brainpoolP192r1 : ``key_bits = 192`` + * brainpoolP224r1 : ``key_bits = 224`` + * brainpoolP256r1 : ``key_bits = 256`` + * brainpoolP320r1 : ``key_bits = 320`` + * brainpoolP384r1 : ``key_bits = 384`` + * brainpoolP512r1 : ``key_bits = 512`` + + They are defined in :rfc-title:`5639`. + + .. warning:: + The 160-bit curve brainpoolP160r1 is weak and deprecated and is only recommended for use in legacy applications. + +.. macro:: PSA_ECC_FAMILY_FRP + :definition: ((psa_ecc_family_t) 0x33) + + .. summary:: + Curve used primarily in France and elsewhere in Europe. + + This family comprises one 256-bit curve: + + * FRP256v1 : ``key_bits = 256`` + + This is defined by :cite-title:`FRP`. + +.. macro:: PSA_ECC_FAMILY_MONTGOMERY + :definition: ((psa_ecc_family_t) 0x41) + + .. summary:: + Montgomery curves. + + This family comprises the following Montgomery curves: + + * Curve25519 : ``key_bits = 255`` + * Curve448 : ``key_bits = 448`` + + Curve25519 is defined in :cite-title:`Curve25519`. Curve448 is defined in :cite-title:`Curve448`. + +.. macro:: PSA_ECC_FAMILY_TWISTED_EDWARDS + :definition: ((psa_ecc_family_t) 0x42) + + .. summary:: + Twisted Edwards curves. + + .. versionadded:: 1.1 + + This family comprises the following twisted Edwards curves: + + * Edwards25519 : ``key_bits = 255``. This curve is birationally equivalent to Curve25519. + * Edwards448 : ``key_bits = 448``. This curve is birationally equivalent to Curve448. + + Edwards25519 is defined in :cite-title:`Ed25519`. Edwards448 is defined in :cite-title:`Curve448`. + + +Diffie-Hellman families +~~~~~~~~~~~~~~~~~~~~~~~ + +.. typedef:: uint8_t psa_dh_family_t + + .. summary:: + The type of identifiers of a finite-field Diffie-Hellman group family. + + The group family identifier is required to create a finite-field Diffie-Hellman key using the `PSA_KEY_TYPE_DH_KEY_PAIR()` or `PSA_KEY_TYPE_DH_PUBLIC_KEY()` macros. + + The specific Diffie-Hellman group within a family is identified by the ``key_bits`` attribute of the key. + + The range of Diffie-Hellman group family identifier values is divided as follows: + + :code:`0x00` + Reserved. + Not allocated to a DH group family. + :code:`0x01 - 0x7f` + DH group family identifiers defined by this standard. + Unallocated values in this range are reserved for future use. + :code:`0x80 - 0xff` + Invalid. + Values in this range must not be used. + + The least significant bit of a Diffie-Hellman group family identifier is a parity bit for the whole key type. + See :secref:`asymmetric-key-encoding` for details of the encoding of asymmetric key types. + + .. admonition:: Implementation note + + To provide other Diffie-Hellman group families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. + +.. macro:: PSA_DH_FAMILY_RFC7919 + :definition: ((psa_dh_family_t) 0x03) + + .. summary:: + Finite-field Diffie-Hellman groups defined for TLS in RFC 7919. + + This family includes groups with the following key sizes (in bits): 2048, 3072, 4096, 6144, 8192. + An implementation can support all of these sizes or only a subset. + + Keys is this group can only be used with the `PSA_ALG_FFDH` key-agreement algorithm. + + These groups are defined by :rfc-title:`7919#A`. + +Attribute accessors +~~~~~~~~~~~~~~~~~~~ + +.. function:: psa_set_key_type + + .. summary:: + Declare the type of a key. + + .. param:: psa_key_attributes_t * attributes + The attribute object to write to. + .. param:: psa_key_type_t type + The key type to write. If this is `PSA_KEY_TYPE_NONE`, the key type in ``attributes`` becomes unspecified. + + .. return:: void + + This function overwrites any key type previously set in ``attributes``. + + .. admonition:: Implementation note + + This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + + +.. function:: psa_get_key_type + + .. summary:: + Retrieve the key type from key attributes. + + .. param:: const psa_key_attributes_t * attributes + The key attribute object to query. + + .. return:: psa_key_type_t + The key type stored in the attribute object. + + .. admonition:: Implementation note + + This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + + +.. function:: psa_get_key_bits + + .. summary:: + Retrieve the key size from key attributes. + + .. param:: const psa_key_attributes_t * attributes + The key attribute object to query. + + .. return:: size_t + The key size stored in the attribute object, in bits. + + .. admonition:: Implementation note + + This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + + +.. function:: psa_set_key_bits + + .. summary:: + Declare the size of a key. + + .. param:: psa_key_attributes_t * attributes + The attribute object to write to. + .. param:: size_t bits + The key size in bits. If this is ``0``, the key size in ``attributes`` becomes unspecified. Keys of size ``0`` are not supported. + + .. return:: void + + This function overwrites any key size previously set in ``attributes``. + + .. admonition:: Implementation note + + This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + +.. _unstructured-keys: + +Unstructured key types +---------------------- + +.. _non-key-data: + +Non-key data +~~~~~~~~~~~~ + +.. macro:: PSA_KEY_TYPE_RAW_DATA + :definition: ((psa_key_type_t)0x1001) + + .. summary:: + Raw data. + + A "key" of this type cannot be used for any cryptographic operation. Applications can use this type to store arbitrary data in the keystore. + + The bit size of a raw key must be a non-zero multiple of 8. The maximum size of a raw key is :scterm:`IMPLEMENTATION DEFINED`. .. subsection:: Compatible algorithms + A key of this type can also be used as a non-secret input to the following key-derivation algorithms: + .. hlist:: - * `PSA_ALG_HMAC` - * `PSA_ALG_SP800_108_COUNTER_HMAC` (secret input) + * `PSA_ALG_HKDF` + * `PSA_ALG_HKDF_EXPAND` + * `PSA_ALG_HKDF_EXTRACT` + * `PSA_ALG_SP800_108_COUNTER_HMAC` + * `PSA_ALG_SP800_108_COUNTER_CMAC` + * `PSA_ALG_TLS12_PRF` + * `PSA_ALG_TLS12_PSK_TO_MS` .. subsection:: Key format @@ -287,6 +574,50 @@ Symmetric keys A call to `psa_key_derivation_output_key()` will draw :math:`m/8` bytes of output and use these as the key data, where :math:`m` is the bit-size of the key. +Symmetric cryptographic keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. macro:: PSA_KEY_TYPE_HMAC + :definition: ((psa_key_type_t)0x1100) + + .. summary:: + HMAC key. + + HMAC keys can be used in HMAC, or HMAC-based, algorithms. + Although HMAC is parameterized by a specific hash algorithm, for example SHA-256, the hash algorithm is not specified in the key type. + The permitted-algorithm policy for the key must specify a particular hash algorithm. + + The bit size of an HMAC key must be a non-zero multiple of 8. + An HMAC key is typically the same size as the output of the underlying hash algorithm. + An HMAC key that is longer than the block size of the underlying hash algorithm will be hashed before use, see :RFC-title:`2104#2`. + + It is recommended that an application does not construct HMAC keys that are longer than the block size of the hash algorithm that will be used. + It is :scterm:`implementation defined` whether an HMAC key that is longer than the hash block size is supported. + + If the application does not control the length of the data used to construct the HMAC key, it is recommended that the application hashes the key data, when it exceeds the hash block length, before constructing the HMAC key. + + .. note:: + + :code:`PSA_HASH_LENGTH(alg)` provides the output size of hash algorithm ``alg``, in bytes. + + :code:`PSA_HASH_BLOCK_LENGTH(alg)` provides the block size of hash algorithm ``alg``, in bytes. + + .. subsection:: Compatible algorithms + + .. hlist:: + + * `PSA_ALG_HMAC` + * `PSA_ALG_SP800_108_COUNTER_HMAC` (secret input) + + .. subsection:: Key format + + The data format for import and export of the key is the raw bytes of the key. + + .. subsection:: Key derivation + + A call to `psa_key_derivation_output_key()` will draw :math:`m/8` bytes of output and use these as the key data, where :math:`m` is the bit-size of the key. + + .. macro:: PSA_KEY_TYPE_AES :definition: ((psa_key_type_t)0x2400) @@ -603,8 +934,15 @@ Symmetric keys .. _asymmetric-keys: -Asymmetric keys ---------------- +Asymmetric key types +-------------------- + +Asymmetric keys come in pairs. +One is a private or secret key, which must be kept confidential. +The other is a public key, which is meant to be shared with other participants in the protocol. + +.. note:: + Depending on the type of cryptographic scheme, the private key can be referred to as the *prover key* or the *decapsulation key*, and the public key can be referred to as the *verifier key* or the *encapsulation key*. The |API| defines the following types of asymmetric key: @@ -613,10 +951,16 @@ The |API| defines the following types of asymmetric key: * :secref:`dh-keys` * :secref:`spake2p-keys` +In the |API|, key objects can either be a key pair, providing both the private and public key, or just a public key. +The difference in the key type values for a key pair and a public key for the same scheme is common across all asymmetric keys. + +The `PSA_KEY_TYPE_KEY_PAIR_OF_PUBLIC_KEY()` and `PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR()` macros convert from one type to the other. + + .. _rsa-keys: RSA keys --------- +~~~~~~~~ .. macro:: PSA_KEY_TYPE_RSA_KEY_PAIR :definition: ((psa_key_type_t)0x7001) @@ -721,10 +1065,11 @@ RSA keys .. param:: type A key type: a value of type `psa_key_type_t`. + .. _ecc-keys: Elliptic Curve keys -------------------- +~~~~~~~~~~~~~~~~~~~ Elliptic curve keys are grouped into families of related curves. A keys for a specific curve is specified by a combination of the elliptic curve family and the bit-size of the key. @@ -762,34 +1107,6 @@ The curve type affects the key format, the key-derivation procedure, and the alg * - Twisted Edwards - `PSA_ECC_FAMILY_TWISTED_EDWARDS` -.. typedef:: uint8_t psa_ecc_family_t - - .. summary:: - The type of identifiers of an elliptic curve family. - - The curve identifier is required to create an ECC key using the `PSA_KEY_TYPE_ECC_KEY_PAIR()` or `PSA_KEY_TYPE_ECC_PUBLIC_KEY()` macros. - - The specific ECC curve within a family is identified by the ``key_bits`` attribute of the key. - - The range of elliptic curve family identifier values is divided as follows: - - :code:`0x00` - Reserved. - Not allocated to an ECC family. - :code:`0x01 - 0x7f` - ECC family identifiers defined by this standard. - Unallocated values in this range are reserved for future use. - :code:`0x80 - 0xff` - Invalid. - Values in this range must not be used. - - The least significant bit of a elliptic curve family identifier is a parity bit for the whole key type. - See :secref:`asymmetric-key-encoding` for details of the encoding of asymmetric key types. - - .. admonition:: Implementation note - - To provide other elliptic curve families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. - .. macro:: PSA_KEY_TYPE_ECC_KEY_PAIR :definition: /* specification-defined value */ @@ -908,256 +1225,98 @@ The curve type affects the key format, the key-derivation procedure, and the alg * Curve25519 (`PSA_ECC_FAMILY_MONTGOMERY`, 255 bits): draw a 32-byte string and process it as specified in :RFC-title:`7748#5`. * Curve448 (`PSA_ECC_FAMILY_MONTGOMERY`, 448 bits): draw a 56-byte string and process it as specified in :RFC:`7748#5`. - * - Twisted Edwards - - Draw a byte string whose length is determined by the curve, and use this as the private key. - That is: - - * Ed25519 (`PSA_ECC_FAMILY_MONTGOMERY`, 255 bits): draw a 32-byte string. - * Ed448 (`PSA_ECC_FAMILY_MONTGOMERY`, 448 bits): draw a 57-byte string. - -.. macro:: PSA_KEY_TYPE_ECC_PUBLIC_KEY - :definition: /* specification-defined value */ - - .. summary:: - Elliptic curve public key. - - .. param:: curve - A value of type `psa_ecc_family_t` that identifies the ECC curve family to be used. - - The size of an elliptic curve public key is the same as the corresponding private key. See `PSA_KEY_TYPE_ECC_KEY_PAIR()` and the documentation of each elliptic curve family for details. - - .. subsection:: Compatible algorithms - - :numref:`tab-ecc-public-key-algorithms` shows the compatible algorithms for each type of elliptic curve public key. - - .. note:: - - For key agreement, the public key of the peer is provided to the |API| as a buffer. - This avoids the need to import the public-key data that is received from the peer, just to carry out the key-agreement algorithm. - - .. list-table:: Compatible algorithms for elliptic curve public keys - :name: tab-ecc-public-key-algorithms - :class: longtable - :widths: 1,4 - :header-rows: 1 - - * - Curve type - - Compatible algorithms - * - Weierstrass - - Weierstrass curve public keys can be used in asymmetric signature and key-encapsulation algorithms. - - `PSA_ALG_DETERMINISTIC_ECDSA` - - `PSA_ALG_ECDSA` - - `PSA_ALG_ECDSA_ANY` - - `PSA_ALG_ECIES_SEC1` - - * - Montgomery - - Montgomery curve public keys can only be used in key-encapsulation algorithms. - - `PSA_ALG_ECIES_SEC1` - - * - Twisted Edwards - - Twisted Edwards curve public keys can only be used in asymmetric signature algorithms. - - `PSA_ALG_PURE_EDDSA` - - `PSA_ALG_ED25519PH` (Edwards25519 only) - - `PSA_ALG_ED448PH` (Edwards448 only) - - .. subsection:: Key format - - The data format for import and export of the public key depends on the type of elliptic curve. - :numref:`tab-ecc-public-key-format` shows the format for each type of elliptic curve public key. - - .. list-table:: Public-key formats for elliptic curve keys - :name: tab-ecc-public-key-format - :class: longtable - :widths: 1,4 - :header-rows: 1 - - * - Curve type - - Public-key format - * - Weierstrass - - The key data is the uncompressed representation of an elliptic curve point as an octet string defined in :cite-title:`SEC1` §2.3.3. - If :math:`m` is the bit size associated with the curve, i.e. the bit size of :math:`q` for a curve over :math:`\mathbb{F}_q`, then the representation of point :math:`P` consists of: - - * The byte ``0x04``; - * :math:`x_P` as a :math:`\lceil{m/8}\rceil`-byte string, big-endian; - * :math:`y_P` as a :math:`\lceil{m/8}\rceil`-byte string, big-endian. - - * - Montgomery - - The key data is the scalar value of the 'public key' in little-endian order as defined by :RFC-title:`7748#6`. - This is a :math:`\lceil{m/8}\rceil`-byte string where :math:`m` is the key size in bits. - - * This is 32 bytes for Curve25519, computed as ``X25519(private_key, 9)``. - * This is 56 bytes for Curve448, computed as ``X448(private_key, 5)``. - - * - Twisted Edwards - - The key data is the public key, as defined by :RFC-title:`8032`. - - This is a 32-byte string for Edwards25519, and a 57-byte string for Edwards448. - -.. macro:: PSA_ECC_FAMILY_SECP_K1 - :definition: ((psa_ecc_family_t) 0x17) - - .. summary:: - SEC Koblitz curves over prime fields. - - This family comprises the following curves: - - * secp192k1 : ``key_bits = 192`` - * secp224k1 : ``key_bits = 225`` - * secp256k1 : ``key_bits = 256`` - - They are defined in :cite-title:`SEC2`. - -.. macro:: PSA_ECC_FAMILY_SECP_R1 - :definition: ((psa_ecc_family_t) 0x12) - - .. summary:: - SEC random curves over prime fields. - - This family comprises the following curves: - - * secp192r1 : ``key_bits = 192`` - * secp224r1 : ``key_bits = 224`` - * secp256r1 : ``key_bits = 256`` - * secp384r1 : ``key_bits = 384`` - * secp521r1 : ``key_bits = 521`` - - They are defined in :cite:`SEC2`. - -.. macro:: PSA_ECC_FAMILY_SECP_R2 - :definition: ((psa_ecc_family_t) 0x1b) - - .. summary:: - .. warning:: - This family of curves is weak and deprecated. - - This family comprises the following curves: - - * secp160r2 : ``key_bits = 160`` *(Deprecated)* - - It is defined in the superseded :cite-title:`SEC2v1`. - -.. macro:: PSA_ECC_FAMILY_SECT_K1 - :definition: ((psa_ecc_family_t) 0x27) - - .. summary:: - SEC Koblitz curves over binary fields. - - This family comprises the following curves: - - * sect163k1 : ``key_bits = 163`` *(Deprecated)* - * sect233k1 : ``key_bits = 233`` - * sect239k1 : ``key_bits = 239`` - * sect283k1 : ``key_bits = 283`` - * sect409k1 : ``key_bits = 409`` - * sect571k1 : ``key_bits = 571`` - - They are defined in :cite:`SEC2`. - - .. warning:: - The 163-bit curve sect163k1 is weak and deprecated and is only recommended for use in legacy applications. - -.. macro:: PSA_ECC_FAMILY_SECT_R1 - :definition: ((psa_ecc_family_t) 0x22) - - .. summary:: - SEC random curves over binary fields. - - This family comprises the following curves: - - * sect163r1 : ``key_bits = 163`` *(Deprecated)* - * sect233r1 : ``key_bits = 233`` - * sect283r1 : ``key_bits = 283`` - * sect409r1 : ``key_bits = 409`` - * sect571r1 : ``key_bits = 571`` - - They are defined in :cite:`SEC2`. + * - Twisted Edwards + - Draw a byte string whose length is determined by the curve, and use this as the private key. + That is: - .. warning:: - The 163-bit curve sect163r1 is weak and deprecated and is only recommended for use in legacy applications. + * Ed25519 (`PSA_ECC_FAMILY_MONTGOMERY`, 255 bits): draw a 32-byte string. + * Ed448 (`PSA_ECC_FAMILY_MONTGOMERY`, 448 bits): draw a 57-byte string. -.. macro:: PSA_ECC_FAMILY_SECT_R2 - :definition: ((psa_ecc_family_t) 0x2b) +.. macro:: PSA_KEY_TYPE_ECC_PUBLIC_KEY + :definition: /* specification-defined value */ .. summary:: - SEC additional random curves over binary fields. + Elliptic curve public key. - This family comprises the following curves: + .. param:: curve + A value of type `psa_ecc_family_t` that identifies the ECC curve family to be used. - * sect163r2 : ``key_bits = 163`` *(Deprecated)* + The size of an elliptic curve public key is the same as the corresponding private key. See `PSA_KEY_TYPE_ECC_KEY_PAIR()` and the documentation of each elliptic curve family for details. - It is defined in :cite:`SEC2`. + .. subsection:: Compatible algorithms - .. warning:: - The 163-bit curve sect163r2 is weak and deprecated and is only recommended for use in legacy applications. + :numref:`tab-ecc-public-key-algorithms` shows the compatible algorithms for each type of elliptic curve public key. -.. macro:: PSA_ECC_FAMILY_BRAINPOOL_P_R1 - :definition: ((psa_ecc_family_t) 0x30) + .. note:: - .. summary:: - Brainpool P random curves. + For key agreement, the public key of the peer is provided to the |API| as a buffer. + This avoids the need to import the public-key data that is received from the peer, just to carry out the key-agreement algorithm. - This family comprises the following curves: + .. list-table:: Compatible algorithms for elliptic curve public keys + :name: tab-ecc-public-key-algorithms + :class: longtable + :widths: 1,4 + :header-rows: 1 - * brainpoolP160r1 : ``key_bits = 160`` *(Deprecated)* - * brainpoolP192r1 : ``key_bits = 192`` - * brainpoolP224r1 : ``key_bits = 224`` - * brainpoolP256r1 : ``key_bits = 256`` - * brainpoolP320r1 : ``key_bits = 320`` - * brainpoolP384r1 : ``key_bits = 384`` - * brainpoolP512r1 : ``key_bits = 512`` + * - Curve type + - Compatible algorithms + * - Weierstrass + - Weierstrass curve public keys can be used in asymmetric signature and key-encapsulation algorithms. - They are defined in :rfc-title:`5639`. + `PSA_ALG_DETERMINISTIC_ECDSA` - .. warning:: - The 160-bit curve brainpoolP160r1 is weak and deprecated and is only recommended for use in legacy applications. + `PSA_ALG_ECDSA` -.. macro:: PSA_ECC_FAMILY_FRP - :definition: ((psa_ecc_family_t) 0x33) + `PSA_ALG_ECDSA_ANY` - .. summary:: - Curve used primarily in France and elsewhere in Europe. + `PSA_ALG_ECIES_SEC1` - This family comprises one 256-bit curve: + * - Montgomery + - Montgomery curve public keys can only be used in key-encapsulation algorithms. - * FRP256v1 : ``key_bits = 256`` + `PSA_ALG_ECIES_SEC1` - This is defined by :cite-title:`FRP`. + * - Twisted Edwards + - Twisted Edwards curve public keys can only be used in asymmetric signature algorithms. -.. macro:: PSA_ECC_FAMILY_MONTGOMERY - :definition: ((psa_ecc_family_t) 0x41) + `PSA_ALG_PURE_EDDSA` - .. summary:: - Montgomery curves. + `PSA_ALG_ED25519PH` (Edwards25519 only) - This family comprises the following Montgomery curves: + `PSA_ALG_ED448PH` (Edwards448 only) - * Curve25519 : ``key_bits = 255`` - * Curve448 : ``key_bits = 448`` + .. subsection:: Key format - Curve25519 is defined in :cite-title:`Curve25519`. Curve448 is defined in :cite-title:`Curve448`. + The data format for import and export of the public key depends on the type of elliptic curve. + :numref:`tab-ecc-public-key-format` shows the format for each type of elliptic curve public key. -.. macro:: PSA_ECC_FAMILY_TWISTED_EDWARDS - :definition: ((psa_ecc_family_t) 0x42) + .. list-table:: Public-key formats for elliptic curve keys + :name: tab-ecc-public-key-format + :class: longtable + :widths: 1,4 + :header-rows: 1 - .. summary:: - Twisted Edwards curves. + * - Curve type + - Public-key format + * - Weierstrass + - The key data is the uncompressed representation of an elliptic curve point as an octet string defined in :cite-title:`SEC1` §2.3.3. + If :math:`m` is the bit size associated with the curve, i.e. the bit size of :math:`q` for a curve over :math:`\mathbb{F}_q`, then the representation of point :math:`P` consists of: - .. versionadded:: 1.1 + * The byte ``0x04``; + * :math:`x_P` as a :math:`\lceil{m/8}\rceil`-byte string, big-endian; + * :math:`y_P` as a :math:`\lceil{m/8}\rceil`-byte string, big-endian. - This family comprises the following twisted Edwards curves: + * - Montgomery + - The key data is the scalar value of the 'public key' in little-endian order as defined by :RFC-title:`7748#6`. + This is a :math:`\lceil{m/8}\rceil`-byte string where :math:`m` is the key size in bits. - * Edwards25519 : ``key_bits = 255``. This curve is birationally equivalent to Curve25519. - * Edwards448 : ``key_bits = 448``. This curve is birationally equivalent to Curve448. + * This is 32 bytes for Curve25519, computed as ``X25519(private_key, 9)``. + * This is 56 bytes for Curve448, computed as ``X448(private_key, 5)``. - Edwards25519 is defined in :cite-title:`Ed25519`. Edwards448 is defined in :cite-title:`Curve448`. + * - Twisted Edwards + - The key data is the public key, as defined by :RFC-title:`8032`. + + This is a 32-byte string for Edwards25519, and a 57-byte string for Edwards448. .. macro:: PSA_KEY_TYPE_IS_ECC :definition: /* specification-defined value */ @@ -1198,38 +1357,11 @@ The curve type affects the key format, the key-derivation procedure, and the alg .. return:: psa_ecc_family_t The elliptic curve family id, if ``type`` is a supported elliptic curve key. Unspecified if ``type`` is not a supported elliptic curve key. + .. _dh-keys: Diffie Hellman keys -------------------- - -.. typedef:: uint8_t psa_dh_family_t - - .. summary:: - The type of identifiers of a finite-field Diffie-Hellman group family. - - The group family identifier is required to create a finite-field Diffie-Hellman key using the `PSA_KEY_TYPE_DH_KEY_PAIR()` or `PSA_KEY_TYPE_DH_PUBLIC_KEY()` macros. - - The specific Diffie-Hellman group within a family is identified by the ``key_bits`` attribute of the key. - - The range of Diffie-Hellman group family identifier values is divided as follows: - - :code:`0x00` - Reserved. - Not allocated to a DH group family. - :code:`0x01 - 0x7f` - DH group family identifiers defined by this standard. - Unallocated values in this range are reserved for future use. - :code:`0x80 - 0xff` - Invalid. - Values in this range must not be used. - - The least significant bit of a Diffie-Hellman group family identifier is a parity bit for the whole key type. - See :secref:`asymmetric-key-encoding` for details of the encoding of asymmetric key types. - - .. admonition:: Implementation note - - To provide other Diffie-Hellman group families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. +~~~~~~~~~~~~~~~~~~~ .. macro:: PSA_KEY_TYPE_DH_KEY_PAIR :definition: /* specification-defined value */ @@ -1286,47 +1418,6 @@ Diffie Hellman keys The data format for export of the public key is the representation of the public key :math:`y = g^x\!\mod p` as a big-endian byte string. The length of the byte string is the length of the base prime :math:`p` in bytes. -.. macro:: PSA_DH_FAMILY_RFC7919 - :definition: ((psa_dh_family_t) 0x03) - - .. summary:: - Finite-field Diffie-Hellman groups defined for TLS in RFC 7919. - - This family includes groups with the following key sizes (in bits): 2048, 3072, 4096, 6144, 8192. - An implementation can support all of these sizes or only a subset. - - Keys is this group can only be used with the `PSA_ALG_FFDH` key-agreement algorithm. - - These groups are defined by :rfc-title:`7919#A`. - -.. macro:: PSA_KEY_TYPE_KEY_PAIR_OF_PUBLIC_KEY - :definition: /* specification-defined value */ - - .. summary:: - The key-pair type corresponding to a public-key type. - - .. param:: type - A public-key type or key-pair type. - - .. return:: - The corresponding key-pair type. If ``type`` is not a public key or a key pair, the return value is undefined. - - If ``type`` is a key-pair type, it will be left unchanged. - -.. macro:: PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR - :definition: /* specification-defined value */ - - .. summary:: - The public-key type corresponding to a key-pair type. - - .. param:: type - A public-key type or key-pair type. - - .. return:: - The corresponding public-key type. If ``type`` is not a public key or a key pair, the return value is undefined. - - If ``type`` is a public-key type, it will be left unchanged. - .. macro:: PSA_KEY_TYPE_IS_DH :definition: /* specification-defined value */ @@ -1366,6 +1457,7 @@ Diffie Hellman keys .. return:: psa_dh_family_t The Diffie-Hellman group family id, if ``type`` is a supported Diffie-Hellman key. Unspecified if ``type`` is not a supported Diffie-Hellman key. + .. _spake2p-keys: SPAKE2+ keys @@ -1527,74 +1619,34 @@ SPAKE2+ keys .. return:: psa_ecc_family_t The elliptic curve family id, if ``type`` is a supported SPAKE2+ key. Unspecified if ``type`` is not a supported SPAKE2+ key. -Attribute accessors -------------------- - -.. function:: psa_set_key_type - - .. summary:: - Declare the type of a key. - - .. param:: psa_key_attributes_t * attributes - The attribute object to write to. - .. param:: psa_key_type_t type - The key type to write. If this is `PSA_KEY_TYPE_NONE`, the key type in ``attributes`` becomes unspecified. - - .. return:: void - - This function overwrites any key type previously set in ``attributes``. - - .. admonition:: Implementation note - - This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. - - -.. function:: psa_get_key_type - - .. summary:: - Retrieve the key type from key attributes. - - .. param:: const psa_key_attributes_t * attributes - The key attribute object to query. - - .. return:: psa_key_type_t - The key type stored in the attribute object. - - .. admonition:: Implementation note - - This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. +Support macros +~~~~~~~~~~~~~~ -.. function:: psa_get_key_bits +.. macro:: PSA_KEY_TYPE_KEY_PAIR_OF_PUBLIC_KEY + :definition: /* specification-defined value */ .. summary:: - Retrieve the key size from key attributes. - - .. param:: const psa_key_attributes_t * attributes - The key attribute object to query. - - .. return:: size_t - The key size stored in the attribute object, in bits. + The key-pair type corresponding to a public-key type. - .. admonition:: Implementation note + .. param:: type + A public-key type or key-pair type. - This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + .. return:: + The corresponding key-pair type. If ``type`` is not a public key or a key pair, the return value is undefined. + If ``type`` is a key-pair type, it will be left unchanged. -.. function:: psa_set_key_bits +.. macro:: PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR + :definition: /* specification-defined value */ .. summary:: - Declare the size of a key. - - .. param:: psa_key_attributes_t * attributes - The attribute object to write to. - .. param:: size_t bits - The key size in bits. If this is ``0``, the key size in ``attributes`` becomes unspecified. Keys of size ``0`` are not supported. - - .. return:: void + The public-key type corresponding to a key-pair type. - This function overwrites any key size previously set in ``attributes``. + .. param:: type + A public-key type or key-pair type. - .. admonition:: Implementation note + .. return:: + The corresponding public-key type. If ``type`` is not a public key or a key pair, the return value is undefined. - This is a simple accessor function that is not required to validate its inputs. It can be efficiently implemented as a ``static inline`` function or a function-like-macro. + If ``type`` is a public-key type, it will be left unchanged. From 688967249361fff835e414f56ddfb80a000923f3 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Mon, 13 Jan 2025 21:48:05 +0000 Subject: [PATCH 02/17] Add key types for WPA3-SAE password tokens * Add a structured key category * Define the WPA3-SAE password token key types --- doc/crypto/api/keys/types.rst | 165 ++++++++++++++++++ doc/crypto/appendix/encodings.rst | 80 +++++++++ doc/crypto/appendix/specdef_values.rst | 18 ++ .../figure/encoding/structured_key.json | 18 ++ .../encoding/structured_key.json.license | 2 + doc/crypto/figure/encoding/structured_key.pdf | Bin 0 -> 7805 bytes .../encoding/structured_key.pdf.license | 2 + doc/crypto/figure/encoding/structured_key.svg | 2 + .../encoding/structured_key.svg.license | 2 + .../figure/encoding/wpa3_sae_dh_key.json | 18 ++ .../encoding/wpa3_sae_dh_key.json.license | 2 + .../figure/encoding/wpa3_sae_dh_key.pdf | Bin 0 -> 7082 bytes .../encoding/wpa3_sae_dh_key.pdf.license | 2 + .../figure/encoding/wpa3_sae_dh_key.svg | 2 + .../encoding/wpa3_sae_dh_key.svg.license | 2 + .../figure/encoding/wpa3_sae_ecc_key.json | 18 ++ .../encoding/wpa3_sae_ecc_key.json.license | 2 + .../figure/encoding/wpa3_sae_ecc_key.pdf | Bin 0 -> 7152 bytes .../encoding/wpa3_sae_ecc_key.pdf.license | 2 + .../figure/encoding/wpa3_sae_ecc_key.svg | 2 + .../encoding/wpa3_sae_ecc_key.svg.license | 2 + doc/crypto/references | 12 ++ 22 files changed, 353 insertions(+) create mode 100644 doc/crypto/figure/encoding/structured_key.json create mode 100644 doc/crypto/figure/encoding/structured_key.json.license create mode 100644 doc/crypto/figure/encoding/structured_key.pdf create mode 100644 doc/crypto/figure/encoding/structured_key.pdf.license create mode 100644 doc/crypto/figure/encoding/structured_key.svg create mode 100644 doc/crypto/figure/encoding/structured_key.svg.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.json create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.json.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.svg create mode 100644 doc/crypto/figure/encoding/wpa3_sae_dh_key.svg.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.json create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.json.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf.license create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg create mode 100644 doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg.license diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index 95cdaf7c..d3908f75 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -49,6 +49,8 @@ The key type is used to identify what the key value is, and what can be used for * :secref:`unstructured-keys` --- defines types for non-key data and unstructured symmetric keys. For example, passwords, key-derivation secrets, or AES keys. +* :secref:`structured-keys` --- defines types for structured symmetric keys. + For example, WPA3-SAE password tokens. * :secref:`asymmetric-keys` --- defines types for asymmetric keys. For example, elliptic curve or SPAKE2+ keys. @@ -336,6 +338,22 @@ Diffie-Hellman families These groups are defined by :rfc-title:`7919#A`. +.. macro:: PSA_DH_FAMILY_RFC3526 + :definition: ((psa_dh_family_t) 0x05) + + .. summary:: + Finite-field Diffie-Hellman groups defined for IKE2 in RFC 3526. + + .. versionadded:: 1.4 + + This family includes groups with the following key sizes (in bits): 2048, 3072, 4096, 6144, 8192. + An implementation can support all of these sizes or only a subset. + + Groups in this family can be used with the `PSA_ALG_FFDH` key-agreement algorithm, or with the `PSA_ALG_WPA3_SAE_FIXED` and `PSA_ALG_WPA3_SAE_GDH` PAKE algorithms. + + These groups are defined by :rfc-title:`3526`. + + Attribute accessors ~~~~~~~~~~~~~~~~~~~ @@ -932,6 +950,153 @@ Symmetric cryptographic keys A call to `psa_key_derivation_output_key()` will draw 32 bytes of output and use these as the key data. + +.. _structured-keys: + +Structured keys +--------------- + +.. _wpa3-sae-keys: + +WPA3-SAE password tokens +~~~~~~~~~~~~~~~~~~~~~~~~ + +The WPA3-SAE PAKE defines two techniques for generating the password element used during the PAKE protocol. +The recommended hash-2-curve method is used to generate an intermediate password token, which is an element of the cyclic group used in the PAKE ciphersuite. +The password token can be stored as a key object, and later used in the PAKE operation when performing the WPA3-SAE protocol. + +WPA3-SAE password tokens are defined for both elliptic curve and finite field groups. + +.. macro:: PSA_KEY_TYPE_WPA3_SAE_ECC_PT + :definition: /* specification-defined value */ + + .. summary:: + WPA3-SAE password token using elliptic curves. + + .. versionadded:: 1.4 + + .. param:: curve + A value of type `psa_ecc_family_t` that identifies the elliptic curve family to be used. + + .. todo:: Consider removing the _PT suffix from the WPA3-SAE key types - the only keys are the password token keys, so not sure the suffix is helpful? + + The bit-size of a WPA3-SAE password token is the bit size associated with the specific curve within the elliptic curve family. + See the documentation of the elliptic curve family for details. + + To construct a WPA3-SAE password token, it must be output from key derivation operation using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + + .. subsection:: Compatible algorithms + + .. hlist:: + + * `PSA_ALG_WPA3_SAE_FIXED` + * `PSA_ALG_WPA3_SAE_GDH` + + .. subsection:: Key format + + The data format for import and export of a WPA3-SAE password token is :scterm:`implementation defined`. + + .. rationale:: + + :issue:`No export/import format is needed for this key type: password tokens should ALWAYS be derived from the password?` + + .. todo:: Decide if we need to define the format for WPA3-SAE password token keys + + .. subsection:: Key derivation + + A elliptic curve-based WPA3-SAE password token can only be derived using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + The call to `psa_key_derivation_output_key()` uses the method defined in :cite-title:`IEEE-802.11` §12.4.4.2.3 to generate the key value. + +.. macro:: PSA_KEY_TYPE_WPA3_SAE_DH_PT + :definition: /* specification-defined value */ + + .. summary:: + WPA3-SAE password token using finite fields. + + .. versionadded:: 1.4 + + .. param:: group + A value of type `psa_dh_family_t` that identifies the finite field Diffie-Hellman family to be used. + + .. todo:: Consider removing the _PT suffix from the WPA3-SAE key types - the only keys are the password token keys, so not sure the suffix is helpful? + + The bit-size of a WPA3-SAE password token is the bit size associated with the specific group within the Diffie-Hellman family. + See the documentation of the Diffie-Hellman family for details. + + To construct a WPA3-SAE password token, it must be output from key derivation operation using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + + .. subsection:: Compatible algorithms + + .. hlist:: + + * `PSA_ALG_WPA3_SAE_FIXED` + * `PSA_ALG_WPA3_SAE_GDH` + + .. subsection:: Key format + + The data format for import and export of a WPA3-SAE password token is :scterm:`implementation defined`. + + .. rationale:: + + :issue:`No export/import format is needed for this key type: password tokens should ALWAYS be derived from the password?` + + .. todo:: Decide if we need to define the format for WPA3-SAE password token keys + + .. subsection:: Key derivation + + A finite field-based WPA3-SAE password token can only be derived using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + The call to `psa_key_derivation_output_key()` uses the method defined in :cite-title:`IEEE-802.11` §12.4.4.3.3 to generate the key value. + +.. macro:: PSA_KEY_TYPE_IS_WPA3_SAE_ECC + :definition: /* specification-defined value */ + + .. summary:: + Whether a key type is a WPA3-SAE password token using elliptic curves. + + .. versionadded:: 1.4 + + .. param:: type + A key type: a value of type `psa_key_type_t`. + +.. macro:: PSA_KEY_TYPE_WPA3_SAE_ECC_GET_FAMILY + :definition: /* specification-defined value */ + + .. summary:: + Extract the curve family from a WPA3-SAE password token using elliptic curves. + + .. versionadded:: 1.4 + + .. param:: type + A WPA3-SAE password token using elliptic curve key type: a value of type `psa_key_type_t` such that :code:`PSA_KEY_TYPE_IS_WPA3_SAE_ECC(type)` is true. + + .. return:: psa_ecc_family_t + The elliptic curve family id, if ``type`` is a supported WPA3-SAE password token using elliptic curve key. Unspecified if ``type`` is not a supported WPA3-SAE password token using elliptic curve key. + +.. macro:: PSA_KEY_TYPE_IS_WPA3_SAE_DH + :definition: /* specification-defined value */ + + .. summary:: + Whether a key type is a WPA3-SAE password token using elliptic curves. + + .. versionadded:: 1.4 + + .. param:: type + A key type: a value of type `psa_key_type_t`. + +.. macro:: PSA_KEY_TYPE_WPA3_SAE_DH_GET_FAMILY + :definition: /* specification-defined value */ + + .. summary:: + Extract the finite field group family from a WPA3-SAE password token using finite fields. + + .. versionadded:: 1.4 + + .. param:: type + A WPA3-SAE password token using finite fields key type: a value of type `psa_key_type_t` such that :code:`PSA_KEY_TYPE_IS_WPA3_SAE_DH(type)` is true. + + .. return:: psa_ecc_family_t + The finite field group family id, if ``type`` is a supported WPA3-SAE password token using finite fields key. Unspecified if ``type`` is not a supported WPA3-SAE password token using finite fields key. + .. _asymmetric-keys: Asymmetric key types diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index 50bfb290..57a94005 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -555,6 +555,7 @@ The A and CAT fields in a key type take the values shown in :numref:`table-key-t None, 0, 0, See `PSA_KEY_TYPE_NONE` Raw data, 0, 1, See :secref:`raw-key-encoding` Symmetric key, 0, 2, See :secref:`symmetric-key-encoding` + Structured key, 0, 3, See :secref:`structured-key-encoding` Asymmetric public key, 1, 0, See :secref:`asymmetric-key-encoding` Asymmetric key pair, 1, 3, See :secref:`asymmetric-key-encoding` @@ -618,6 +619,85 @@ The defined values for BLK, SYM-TYPE and P are shown in :numref:`table-symmetric SM4, 4, 2, 1, `PSA_KEY_TYPE_SM4`, ``0x2405`` ARIA, 4, 3, 0, `PSA_KEY_TYPE_ARIA`, ``0x2406`` + +Structured key encoding +~~~~~~~~~~~~~~~~~~~~~~~ + +The key type for structured keys defined in this specification are encoded as shown in :numref:`fig-structured-key-fields`. + +.. figure:: ../figure/encoding/structured_key.* + :name: fig-structured-key-fields + + Encoding of structured keys + +The defined values for STRUCT-TYPE are shown in :numref:`table-structured-type`. + +The defined values for FAMILY depend on the STRUCT-TYPE value. See the details for each structured key sub-type. + +.. csv-table:: Structured key sub-type values + :name: table-structured-type + :header-rows: 1 + :align: left + :widths: auto + + Structured key type, STRUCT-TYPE, Details + WPA3-SAE password token, "5, 6", See :secref:`wpa3-sae-key-encoding` + +.. _wpa3-sae-key-encoding: + +WPA3-SAE password token encoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +WPA3-SAE can use either elliptic curve or Diffie-Hellman cipher suites. +These use distinct STRUCT-TYPE values, and use the same FAMILY values as elliptic curve and Diffie-Hellman key types. + +.. rubric:: WPA3-SAE password tokens using elliptic curves + +The key type for WPA3-SAE password tokens using elliptic curves defined in this specification are encoded as shown in :numref:`fig-wpa3-sae-ecc-fields`. + +.. figure:: ../figure/encoding/wpa3_sae_ecc_key.* + :name: fig-wpa3-sae-ecc-fields + + Encoding of WPA3-SAE password token using elliptic curves + +The defined values for ECC-FAMILY and P are shown in :numref:`table-wpa3-sae-ecc-type`. + +.. csv-table:: WPA3-SAE password token ECC family values + :name: table-wpa3-sae-ecc-type + :header-rows: 1 + :align: left + :widths: auto + + WPA3-SAE suite, ECC-FAMILY, P, ECC family :sup:`a`, Key value + SECP R1, 0x09, 0, `PSA_ECC_FAMILY_SECP_R1`, ``0x3292`` + Brainpool-P R1, 0x18, 0, `PSA_ECC_FAMILY_BRAINPOOL_P_R1`, ``0x32b0`` + +a. The elliptic curve family values defined in the API also include the parity bit. The password token key type value is constructed from the elliptic curve family using :code:`PSA_KEY_TYPE_WPA3_SAE_ECC_PT(family)`. + +.. rubric:: WPA3-SAE password tokens using finite fields + +The key type for WPA3-SAE password tokens using finite fields defined in this specification are encoded as shown in :numref:`fig-wpa3-sae-dh-fields`. + +.. figure:: ../figure/encoding/wpa3_sae_dh_key.* + :name: fig-wpa3-sae-dh-fields + + Encoding of WPA3-SAE password token using finite fields + +The defined values for DH-FAMILY and P are shown in :numref:`table-wpa3-sae-dh-type`. + +RFC3526 defines a set of FF groups that are recommended for use with WPA3-SAE (those with primes >=3072 bits) + +.. csv-table:: WPA3-SAE password token Diffie-Hellman family values + :name: table-wpa3-sae-dh-type + :header-rows: 1 + :align: left + :widths: auto + + WPA3-SAE suite, DH-FAMILY, P, DH family :sup:`a`, Key value + RFC3526, 0x02, 1, `PSA_DH_FAMILY_RFC3526`, ``0x3305`` + +a. The Diffie Hellman family values defined in the API also include the parity bit. The password token key type value is constructed from the Diffie Hellman family using :code:`PSA_KEY_TYPE_WPA3_SAE_DH_PT(family)`. + .. _asymmetric-key-encoding: Asymmetric key encoding diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index cc331b3c..c2b51902 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -326,6 +326,12 @@ Key type macros #define PSA_KEY_TYPE_IS_UNSTRUCTURED(type) \ (((type) & 0x7000) == 0x1000 || ((type) & 0x7000) == 0x2000) + #define PSA_KEY_TYPE_IS_WPA3_SAE_DH(type) \ + (((type) & 0xff80) == 0x3300) + + #define PSA_KEY_TYPE_IS_WPA3_SAE_ECC(type) \ + (((type) & 0xff80) == 0x3280) + #define PSA_KEY_TYPE_KEY_PAIR_OF_PUBLIC_KEY(type) \ ((psa_key_type_t) ((type) | 0x3000)) @@ -341,6 +347,18 @@ Key type macros #define PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY(curve) \ ((psa_key_type_t) (0x4400 | ((curve) & 0x007f))) + #define PSA_KEY_TYPE_WPA3_SAE_DH_GET_FAMILY(type) \ + ((psa_dh_family_t) ((type) & 0x007f)) + + #define PSA_KEY_TYPE_WPA3_SAE_DH_PT(family) \ + ((psa_key_type_t) (0x3300 | ((family) & 0x007f))) + + #define PSA_KEY_TYPE_WPA3_SAE_ECC_GET_FAMILY(type) \ + ((psa_ecc_family_t) ((type) & 0x007f)) + + #define PSA_KEY_TYPE_WPA3_SAE_ECC_PT(curve) \ + ((psa_key_type_t) (0x3280 | ((curve) & 0x007f))) + Hash suspend state macros ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/crypto/figure/encoding/structured_key.json b/doc/crypto/figure/encoding/structured_key.json new file mode 100644 index 00000000..c43097a8 --- /dev/null +++ b/doc/crypto/figure/encoding/structured_key.json @@ -0,0 +1,18 @@ +{ + "reg": [ + { "name": "P", "bits": 1 }, + { "name": "FAMILY", "bits": 6 }, + { "name": "STRUCT-TYPE", "bits": 5 }, + { "name": "3", "bits": 2 }, + { "name": "0", "bits": 1 }, + { "name": "0", "bits": 1 } + ], + "config": { + "lanes": 1, + "fontfamily": "lato", + "fontsize": 11, + "bits": 16, + "vspace": 52, + "hspace": 300 + } +} diff --git a/doc/crypto/figure/encoding/structured_key.json.license b/doc/crypto/figure/encoding/structured_key.json.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/structured_key.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/structured_key.pdf b/doc/crypto/figure/encoding/structured_key.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a27a2c8abe75167f2d340126092205f1b9024497 GIT binary patch literal 7805 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 literal 0 HcmV?d00001 diff --git a/doc/crypto/figure/encoding/structured_key.pdf.license b/doc/crypto/figure/encoding/structured_key.pdf.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/structured_key.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/structured_key.svg b/doc/crypto/figure/encoding/structured_key.svg new file mode 100644 index 00000000..64e12cf6 --- /dev/null +++ b/doc/crypto/figure/encoding/structured_key.svg @@ -0,0 +1,2 @@ + +01671112131415PFAMILYSTRUCT-TYPE300 \ No newline at end of file diff --git a/doc/crypto/figure/encoding/structured_key.svg.license b/doc/crypto/figure/encoding/structured_key.svg.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/structured_key.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.json b/doc/crypto/figure/encoding/wpa3_sae_dh_key.json new file mode 100644 index 00000000..bdb3eb86 --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_dh_key.json @@ -0,0 +1,18 @@ +{ + "reg": [ + { "name": "P", "bits": 1 }, + { "name": "DH-FAMILY", "bits": 6 }, + { "name": "6", "bits": 5 }, + { "name": "3", "bits": 2 }, + { "name": "0", "bits": 1 }, + { "name": "0", "bits": 1 } + ], + "config": { + "lanes": 1, + "fontfamily": "lato", + "fontsize": 11, + "bits": 16, + "vspace": 52, + "hspace": 300 + } +} diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.json.license b/doc/crypto/figure/encoding/wpa3_sae_dh_key.json.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_dh_key.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf b/doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fbfb90f20924efe833b2cbe32525dcb06d9d43c3 GIT binary patch literal 7082 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 literal 0 HcmV?d00001 diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf.license b/doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_dh_key.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg b/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg new file mode 100644 index 00000000..4387f5ea --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg @@ -0,0 +1,2 @@ + +01671112131415PDH-FAMILY6300 \ No newline at end of file diff --git a/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg.license b/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_dh_key.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json new file mode 100644 index 00000000..60891c1d --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json @@ -0,0 +1,18 @@ +{ + "reg": [ + { "name": "P", "bits": 1 }, + { "name": "ECC-FAMILY", "bits": 6 }, + { "name": "5", "bits": 5 }, + { "name": "3", "bits": 2 }, + { "name": "0", "bits": 1 }, + { "name": "0", "bits": 1 } + ], + "config": { + "lanes": 1, + "fontfamily": "lato", + "fontsize": 11, + "bits": 16, + "vspace": 52, + "hspace": 300 + } +} diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json.license b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf new file mode 100644 index 0000000000000000000000000000000000000000..00cf969085d32c19775c1dc4554b9ae40c56e20e GIT binary patch literal 7152 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 literal 0 HcmV?d00001 diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf.license b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg new file mode 100644 index 00000000..de523792 --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg @@ -0,0 +1,2 @@ + +01671112131415PECC-FAMILY5300 \ No newline at end of file diff --git a/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg.license b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/encoding/wpa3_sae_ecc_key.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/references b/doc/crypto/references index 6c2e31d6..5dc3aad7 100644 --- a/doc/crypto/references +++ b/doc/crypto/references @@ -425,3 +425,15 @@ :author: NIST :publication: December 2012 :url: doi.org/10.6028/NIST.SP.800-38F + +.. reference:: RFC3526 + :title: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE) + :author: IETF + :publication: May 2003 + :url: tools.ietf.org/html/rfc3526.html + +.. reference:: IEEE-802.11 + :title: IEEE 802.11-2024: Wireless LAN Medium Access Control (MAC) and Physical Layer (PHY) Specifications + :author: IEEE + :publication: 2024 + :url: standards.ieee.org/ieee/802.11/10548/ From 040abd08029110ebce3642d25b8719242dd9b8db Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Wed, 15 Jan 2025 19:20:27 +0000 Subject: [PATCH 03/17] Clarify use of 'Diffie-Hellman' Prefix with 'finite field' when describing the cyclic groups and group families. --- doc/crypto/api/keys/types.rst | 71 ++++++++++++++++------------ doc/crypto/api/ops/key-agreement.rst | 10 ++-- doc/crypto/api/ops/pake.rst | 12 ++--- doc/crypto/appendix/encodings.rst | 23 ++++----- 4 files changed, 64 insertions(+), 52 deletions(-) diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index d3908f75..9a58f005 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -105,7 +105,7 @@ Elliptic curve families .. summary:: The type of identifiers of an elliptic curve family. - The curve identifier is required to create a number of key types: + The curve family identifier is required to create a number of key types: * ECC keys using `PSA_KEY_TYPE_ECC_KEY_PAIR()` or `PSA_KEY_TYPE_ECC_PUBLIC_KEY()`. These keys are used in various asymmetric signature, key-encapsulation, and key-agreement algorithms. @@ -114,15 +114,18 @@ Elliptic curve families * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_ECC_PT()`. These keys are used in the WPA3-SAE PAKE algorithms. + Elliptic curve family identifiers are also used to construct PAKE primitives for cipher suites based on elliptic curve groups. + See :secref:`pake-primitive`. + The specific ECC curve within a family is identified by the ``key_bits`` attribute of the key. The range of elliptic curve family identifier values is divided as follows: :code:`0x00` Reserved. - Not allocated to an ECC family. + Not allocated to an elliptic curve family. :code:`0x01 - 0x7f` - ECC family identifiers defined by this standard. + Elliptic curve family identifiers defined by this standard. Unallocated values in this range are reserved for future use. :code:`0x80 - 0xff` Invalid. @@ -294,47 +297,55 @@ Elliptic curve families Edwards25519 is defined in :cite-title:`Ed25519`. Edwards448 is defined in :cite-title:`Curve448`. -Diffie-Hellman families -~~~~~~~~~~~~~~~~~~~~~~~ +Finite field Diffie-Hellman families +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. typedef:: uint8_t psa_dh_family_t .. summary:: - The type of identifiers of a finite-field Diffie-Hellman group family. + The type of identifiers of a finite field Diffie-Hellman group family. + + The group family identifier is required to create a number of key types: + + * Diffie-Hellman keys using `PSA_KEY_TYPE_DH_KEY_PAIR()` or `PSA_KEY_TYPE_DH_PUBLIC_KEY()`. + These keys are used in the FFDH key-agreement algorithm. + * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_DH_PT()`. + These keys are used in the WPA3-SAE PAKE algorithms. - The group family identifier is required to create a finite-field Diffie-Hellman key using the `PSA_KEY_TYPE_DH_KEY_PAIR()` or `PSA_KEY_TYPE_DH_PUBLIC_KEY()` macros. + Finite field Diffie-Hellman group identifiers are also used to construct PAKE primitives for cipher suites based on finite field groups. + See :secref:`pake-primitive`. - The specific Diffie-Hellman group within a family is identified by the ``key_bits`` attribute of the key. + The specific finite field Diffie-Hellman group within a family is identified by the ``key_bits`` attribute of the key. - The range of Diffie-Hellman group family identifier values is divided as follows: + The range of finite field Diffie-Hellman group family identifier values is divided as follows: :code:`0x00` Reserved. - Not allocated to a DH group family. + Not allocated to a Diffie-Hellman group family. :code:`0x01 - 0x7f` - DH group family identifiers defined by this standard. + Diffie-Hellman group family identifiers defined by this standard. Unallocated values in this range are reserved for future use. :code:`0x80 - 0xff` Invalid. Values in this range must not be used. - The least significant bit of a Diffie-Hellman group family identifier is a parity bit for the whole key type. + The least significant bit of a finite field Diffie-Hellman group family identifier is a parity bit for the whole key type. See :secref:`asymmetric-key-encoding` for details of the encoding of asymmetric key types. .. admonition:: Implementation note - To provide other Diffie-Hellman group families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. + To provide other finite field Diffie-Hellman group families, it is recommended that an implementation defines a key type with bit 15 set, which indicates an :scterm:`implementation defined` key type. .. macro:: PSA_DH_FAMILY_RFC7919 :definition: ((psa_dh_family_t) 0x03) .. summary:: - Finite-field Diffie-Hellman groups defined for TLS in RFC 7919. + Finite field Diffie-Hellman groups defined for TLS in RFC 7919. This family includes groups with the following key sizes (in bits): 2048, 3072, 4096, 6144, 8192. An implementation can support all of these sizes or only a subset. - Keys is this group can only be used with the `PSA_ALG_FFDH` key-agreement algorithm. + Groups in this family can be used with the `PSA_ALG_FFDH` key-agreement algorithm. These groups are defined by :rfc-title:`7919#A`. @@ -342,7 +353,7 @@ Diffie-Hellman families :definition: ((psa_dh_family_t) 0x05) .. summary:: - Finite-field Diffie-Hellman groups defined for IKE2 in RFC 3526. + Finite field Diffie-Hellman groups defined for IKE2 in RFC 3526. .. versionadded:: 1.4 @@ -1020,8 +1031,8 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr .. todo:: Consider removing the _PT suffix from the WPA3-SAE key types - the only keys are the password token keys, so not sure the suffix is helpful? - The bit-size of a WPA3-SAE password token is the bit size associated with the specific group within the Diffie-Hellman family. - See the documentation of the Diffie-Hellman family for details. + The bit-size of the WPA3-SAE password token is the bit size associated with the specific group within the finite field Diffie-Hellman family. + See the documentation of the selected Diffie-Hellman family for details. To construct a WPA3-SAE password token, it must be output from key derivation operation using the `PSA_ALG_WPA3_SAE_H2E` algorithm. @@ -1532,10 +1543,10 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Finite-field Diffie-Hellman key pair: both the private key and public key. + Finite field Diffie-Hellman key pair: both the private key and public key. .. param:: group - A value of type `psa_dh_family_t` that identifies the Diffie-Hellman group family to be used. + A value of type `psa_dh_family_t` that identifies the finite field Diffie-Hellman group family to be used. .. subsection:: Compatible algorithms @@ -1554,7 +1565,7 @@ Diffie Hellman keys A call to `psa_key_derivation_output_key()` will use the following process, defined in *Key-Pair Generation by Testing Candidates* in :cite-title:`SP800-56A` §5.6.1.1.4. - A Diffie-Hellman private key is :math:`x \in [1, p - 1]`, where :math:`p` is the group's prime modulus. + A finite field Diffie-Hellman private key is :math:`x \in [1, p - 1]`, where :math:`p` is the group's prime modulus. Let :math:`m` be the bit size of :math:`p`, such that :math:`2^{m-1} \leq p < 2^m`. This function generates the private key using the following process: @@ -1569,14 +1580,14 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Finite-field Diffie-Hellman public key. + Finite field Diffie-Hellman public key. .. param:: group - A value of type `psa_dh_family_t` that identifies the Diffie-Hellman group family to be used. + A value of type `psa_dh_family_t` that identifies the finite field Diffie-Hellman group family to be used. .. subsection:: Compatible algorithms - None: Finite-field Diffie-Hellman public keys are exported to use in a key-agreement algorithm, and the peer key is provided to the `PSA_ALG_FFDH` key-agreement algorithm as a buffer of key data. + None: Finite field Diffie-Hellman public keys are exported to use in a key-agreement algorithm, and the peer key is provided to the `PSA_ALG_FFDH` key-agreement algorithm as a buffer of key data. .. subsection:: Key format @@ -1587,7 +1598,7 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Whether a key type is a Diffie-Hellman key, either a key pair or a public key. + Whether a key type is a finite field Diffie-Hellman key, either a key pair or a public key. .. param:: type A key type: a value of type `psa_key_type_t`. @@ -1596,7 +1607,7 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Whether a key type is a Diffie-Hellman key pair. + Whether a key type is a finite field Diffie-Hellman key pair. .. param:: type A key type: a value of type `psa_key_type_t`. @@ -1605,7 +1616,7 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Whether a key type is a Diffie-Hellman public key. + Whether a key type is a finite field Diffie-Hellman public key. .. param:: type A key type: a value of type `psa_key_type_t`. @@ -1614,13 +1625,13 @@ Diffie Hellman keys :definition: /* specification-defined value */ .. summary:: - Extract the group family from a Diffie-Hellman key type. + Extract the group family from a finite field Diffie-Hellman key type. .. param:: type - A Diffie-Hellman key type: a value of type `psa_key_type_t` such that :code:`PSA_KEY_TYPE_IS_DH(type)` is true. + A finite field Diffie-Hellman key type: a value of type `psa_key_type_t` such that :code:`PSA_KEY_TYPE_IS_DH(type)` is true. .. return:: psa_dh_family_t - The Diffie-Hellman group family id, if ``type`` is a supported Diffie-Hellman key. Unspecified if ``type`` is not a supported Diffie-Hellman key. + The finite field Diffie-Hellman group family id, if ``type`` is a supported finite field Diffie-Hellman key. Unspecified if ``type`` is not a supported finite field Diffie-Hellman key. .. _spake2p-keys: diff --git a/doc/crypto/api/ops/key-agreement.rst b/doc/crypto/api/ops/key-agreement.rst index eb15add6..cba8619e 100644 --- a/doc/crypto/api/ops/key-agreement.rst +++ b/doc/crypto/api/ops/key-agreement.rst @@ -32,7 +32,7 @@ Key-agreement algorithms :definition: ((psa_algorithm_t)0x09010000) .. summary:: - The finite-field Diffie-Hellman (DH) key-agreement algorithm. + The finite field Diffie-Hellman (DH) key-agreement algorithm. This standalone key-agreement algorithm can be used directly in a call to `psa_key_agreement()` or `psa_raw_key_agreement()`, or combined with a key-derivation operation using `PSA_ALG_KEY_AGREEMENT()` for use with `psa_key_derivation_key_agreement()`. @@ -41,7 +41,7 @@ Key-agreement algorithms * In a call to `psa_key_agreement()` or `psa_raw_key_agreement()`, with algorithm `PSA_ALG_FFDH`. * In a call to `psa_key_derivation_key_agreement()`, with any combined key-agreement and key-derivation algorithm constructed with `PSA_ALG_FFDH`. - When used as part of a multi-part key-derivation operation, this implements a Diffie-Hellman key-agreement scheme using a single Diffie-Hellman key pair for each participant. This includes the *dhEphem*, *dhOneFlow*, and *dhStatic* schemes. The input step `PSA_KEY_DERIVATION_INPUT_SECRET` is used when providing the secret and peer keys to the operation. + When used as part of a multi-part key-derivation operation, this implements a Diffie-Hellman key-agreement scheme using a single finite field Diffie-Hellman key pair for each participant. This includes the *dhEphem*, *dhOneFlow*, and *dhStatic* schemes. The input step `PSA_KEY_DERIVATION_INPUT_SECRET` is used when providing the secret and peer keys to the operation. The shared secret produced by this key-agreement algorithm is :math:`g^{ab}` in big-endian format. It is :math:`\lceil{(m / 8)}\rceil` bytes long where :math:`m` is the size of the prime :math:`p` in bits. @@ -226,7 +226,7 @@ Standalone key agreement This key can be used as input to a key-derivation operation using `psa_key_derivation_input_key()`. .. warning:: - The shared secret resulting from a key-agreement algorithm such as finite-field Diffie-Hellman or elliptic curve Diffie-Hellman has biases. This makes it unsuitable for use as key material, for example, as an AES key. Instead, it is recommended that a key-derivation algorithm is applied to the result, to derive unbiased cryptographic keys. + The shared secret resulting from a key-agreement algorithm such as finite field Diffie-Hellman or elliptic curve Diffie-Hellman has biases. This makes it unsuitable for use as key material, for example, as an AES key. Instead, it is recommended that a key-derivation algorithm is applied to the result, to derive unbiased cryptographic keys. .. function:: psa_raw_key_agreement @@ -288,7 +288,7 @@ Standalone key agreement A key-agreement algorithm takes two inputs: a private key ``private_key``, and a public key ``peer_key``. The result of this function is a shared secret, returned in the ``output`` buffer. .. warning:: - The result of a key-agreement algorithm such as finite-field Diffie-Hellman or elliptic curve Diffie-Hellman has biases, and is not suitable for direct use as key material, for example, as an AES key. Instead it is recommended that the result is used as input to a key-derivation algorithm. + The result of a key-agreement algorithm such as finite field Diffie-Hellman or elliptic curve Diffie-Hellman has biases, and is not suitable for direct use as key material, for example, as an AES key. Instead it is recommended that the result is used as input to a key-derivation algorithm. To chain a key agreement with a key derivation, either use `psa_key_agreement()` to obtain the result of the key agreement as a derivation key, or use `psa_key_derivation_key_agreement()` and other functions from the key-derivation interface. @@ -428,7 +428,7 @@ Support macros .. return:: ``1`` if ``alg`` is a finite field Diffie-Hellman algorithm, ``0`` otherwise. This macro can return either ``0`` or ``1`` if ``alg`` is not a supported key-agreement algorithm identifier. - This includes the standalone finite field Diffie-Hellman algorithm, as well as finite-field Diffie-Hellman combined with any supported key-derivation algorithm. + This includes the standalone finite field Diffie-Hellman algorithm, as well as finite field Diffie-Hellman combined with any supported key-derivation algorithm. .. macro:: PSA_ALG_IS_ECDH :definition: /* specification-defined value */ diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index 6fa518b0..cd002ef6 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -114,19 +114,19 @@ A PAKE primitive is required when constructing a PAKE cipher-suite object, `psa_ :definition: ((psa_pake_primitive_type_t)0x02) .. summary:: - The PAKE primitive type indicating the use of Diffie-Hellman groups. + The PAKE primitive type indicating the use of a finite field Diffie-Hellman group. .. versionadded:: 1.1 - The values of the ``family`` and ``bits`` components of the PAKE primitive identify a specific Diffie-Hellman group, using the same mapping that is used for Diffie-Hellman keys. + The values of the ``family`` and ``bits`` components of the PAKE primitive identify a specific finite field Diffie-Hellman group, using the same mapping that is used for finite field Diffie-Hellman keys. See the definition of ``psa_dh_family_t``. Here ``family`` and ``bits`` refer to the values used to construct the PAKE primitive using `PSA_PAKE_PRIMITIVE()`. Input and output during the operation can involve group elements and scalar values: - * The format for group elements is the same as that for public keys in the specific Diffie-Hellman group. + * The format for group elements is the same as that for public keys in the specific finite field Diffie-Hellman group. See *Key format* within the definition of `PSA_KEY_TYPE_DH_PUBLIC_KEY()`. - * The format for scalars is the same as that for private keys in the specific Diffie-Hellman group. + * The format for scalars is the same as that for private keys in the specific finite field Diffie-Hellman group. See *Key format* within the definition of `PSA_KEY_TYPE_DH_PUBLIC_KEY()`. @@ -605,7 +605,7 @@ PAKE step types The format depends on the group as well: * For Montgomery curves, the encoding is little endian. - * For other elliptic curves, and for Diffie-Hellman groups, the encoding is big endian. See :cite:`SEC1` §2.3.8. + * For other elliptic curves, and for finite field Diffie-Hellman groups, the encoding is big endian. See :cite:`SEC1` §2.3.8. In both cases leading zeroes are permitted as long as the length in bytes does not exceed the byte length of the group order. @@ -1293,7 +1293,7 @@ For example, the following code creates a cipher suite to select J-PAKE using P- PSA_ECC_FAMILY_SECP_R1, 256)); psa_pake_cs_set_key_confirmation(&cipher_suite, PSA_PAKE_UNCONFIRMED_KEY); -More information on selecting a specific elliptic curve or Diffie-Hellman field is provided with the `PSA_PAKE_PRIMITIVE_TYPE_ECC` and `PSA_PAKE_PRIMITIVE_TYPE_DH` constants. +More information on selecting a specific elliptic curve or finite field Diffie-Hellman group is provided with the `PSA_PAKE_PRIMITIVE_TYPE_ECC` and `PSA_PAKE_PRIMITIVE_TYPE_DH` constants. .. _jpake-passwords: diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index 57a94005..8ec8f98b 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -619,6 +619,7 @@ The defined values for BLK, SYM-TYPE and P are shown in :numref:`table-symmetric SM4, 4, 2, 1, `PSA_KEY_TYPE_SM4`, ``0x2405`` ARIA, 4, 3, 0, `PSA_KEY_TYPE_ARIA`, ``0x2406`` +.. _structured-key-encoding: Structured key encoding ~~~~~~~~~~~~~~~~~~~~~~~ @@ -648,8 +649,8 @@ The defined values for FAMILY depend on the STRUCT-TYPE value. See the details f WPA3-SAE password token encoding ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -WPA3-SAE can use either elliptic curve or Diffie-Hellman cipher suites. -These use distinct STRUCT-TYPE values, and use the same FAMILY values as elliptic curve and Diffie-Hellman key types. +WPA3-SAE is defined to use either elliptic curve or finite field groups. +These use distinct STRUCT-TYPE values, and use the same FAMILY values as elliptic curve and finite field Diffie-Hellman key types. .. rubric:: WPA3-SAE password tokens using elliptic curves @@ -687,7 +688,7 @@ The defined values for DH-FAMILY and P are shown in :numref:`table-wpa3-sae-dh-t RFC3526 defines a set of FF groups that are recommended for use with WPA3-SAE (those with primes >=3072 bits) -.. csv-table:: WPA3-SAE password token Diffie-Hellman family values +.. csv-table:: WPA3-SAE password token finite field Diffie-Hellman family values :name: table-wpa3-sae-dh-type :header-rows: 1 :align: left @@ -696,7 +697,7 @@ RFC3526 defines a set of FF groups that are recommended for use with WPA3-SAE (t WPA3-SAE suite, DH-FAMILY, P, DH family :sup:`a`, Key value RFC3526, 0x02, 1, `PSA_DH_FAMILY_RFC3526`, ``0x3305`` -a. The Diffie Hellman family values defined in the API also include the parity bit. The password token key type value is constructed from the Diffie Hellman family using :code:`PSA_KEY_TYPE_WPA3_SAE_DH_PT(family)`. +a. The finite field Diffie Hellman family values defined in the API also include the parity bit. The password token key type value is constructed from the finite field Diffie Hellman family using :code:`PSA_KEY_TYPE_WPA3_SAE_DH_PT(family)`. .. _asymmetric-key-encoding: @@ -792,30 +793,30 @@ a. The elliptic curve family values defined in the API also include the parity .. _dh-key-encoding: -Diffie Hellman key encoding -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Finite field Diffie Hellman key encoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The key type for Diffie Hellman keys defined in this specification are encoded as shown in :numref:`fig-dh-key-fields`. +The key type for finite field Diffie Hellman keys defined in this specification are encoded as shown in :numref:`fig-dh-key-fields`. .. figure:: ../figure/encoding/dh_key.* :name: fig-dh-key-fields - Diffie Hellman key encoding + Finite field Diffie Hellman key encoding PAIR is either 0 for a public key, or 3 for a key pair. The defined values for DH-FAMILY and P are shown in :numref:`table-dh-type`. -.. csv-table:: Diffie Hellman key group values +.. csv-table:: Finite field Diffie Hellman key group values :name: table-dh-type :header-rows: 1 :align: left :widths: auto - DH key group, DH-FAMILY, P, DH group :sup:`a`, Public-key value, Key-pair value + DH key group, DH-FAMILY, P, DH family :sup:`a`, Public-key value, Key-pair value RFC7919, 0x01, 1, `PSA_DH_FAMILY_RFC7919`, ``0x4203``, ``0x7203`` -a. The Diffie Hellman family values defined in the API also include the parity bit. The key type value is constructed from the Diffie Hellman family using either :code:`PSA_KEY_TYPE_DH_PUBLIC_KEY(family)` or :code:`PSA_KEY_TYPE_DH_KEY_PAIR(family)` as required. +a. The finite field Diffie Hellman group family values defined in the API also include the parity bit. The key type value is constructed from the finite field Diffie Hellman family using either :code:`PSA_KEY_TYPE_DH_PUBLIC_KEY(family)` or :code:`PSA_KEY_TYPE_DH_KEY_PAIR(family)` as required. .. _spakep2-key-encoding: From 0053dd2d6b826ac9748dc236aec175528ac4d284 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 5 Sep 2025 16:27:34 +0100 Subject: [PATCH 04/17] Add the hash-to-element key derivation for WPA3-SAE password tokens --- doc/crypto/api/ops/key-derivation.rst | 50 ++++++++++++++++++++++++++ doc/crypto/appendix/encodings.rst | 1 + doc/crypto/appendix/specdef_values.rst | 6 ++++ 3 files changed, 57 insertions(+) diff --git a/doc/crypto/api/ops/key-derivation.rst b/doc/crypto/api/ops/key-derivation.rst index 5a1d1250..55ca981c 100644 --- a/doc/crypto/api/ops/key-derivation.rst +++ b/doc/crypto/api/ops/key-derivation.rst @@ -359,6 +359,41 @@ Key-derivation algorithms | `PSA_KEY_TYPE_DERIVE` --- the secret key is extracted from a PAKE operation by calling :code:`psa_pake_get_shared_key()`. +.. macro:: PSA_ALG_WPA3_SAE_H2E + :definition: /* specification-defined value */ + + .. summary:: + The WPA3-SAE hash-to-element password token key-derivation algorithm. + + .. versionadded:: 1.4 + + .. param:: hash_alg + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + + This KDF is defined in :cite-title:`IEEE-802.11` §12.4.4. + This specifies the hash-to-element procedures for deriving a WPA3-SAE password token from a network SSID and password. + The resulting password token is then used during a WPA3-SAE PAKE operation. + + This key-derivation algorithm uses the following inputs, which must be passed in the order given here: + + * `PSA_KEY_DERIVATION_INPUT_SALT` is the network SSID. + * `PSA_KEY_DERIVATION_INPUT_PASSWORD` is the password. + * `PSA_KEY_DERIVATION_INPUT_INFO` is the password identifier. + It is optional. + + This key derivation algorithm can only be used to derive and output a single key, which is obtained by a call to `psa_key_derivation_output_key()`. + The output has to be read as a key of type `PSA_KEY_TYPE_WPA3_SAE_DH_PT` or `PSA_KEY_TYPE_WPA3_SAE_ECC_PT`. + Requesting any other key type, or calling `psa_key_derivation_output_bytes()`, returns an error status. + + The ``hash_alg`` parameter to `PSA_ALG_WPA3_SAE_H2E()` determines the hash function used for the derivation. + The key attributes of the output key indicate the elliptic curve or finite field group used for the derivation. + See :secref:`wpa3-sae-keys` for details of the derivation procedures. + + .. note:: + If the elliptic curve or finite field group specified in the key attributes is not compatible with the hash function used for the derivation, `psa_key_derivation_output_bytes()` returns :code:`PSA_ERROR_INVALID_ARGUMENT`. + + See also :secref:`wpa3-sae-cipher-suites`. + .. macro:: PSA_ALG_PBKDF2_HMAC :definition: /* specification-defined value */ @@ -1323,6 +1358,21 @@ Support macros .. return:: ``1`` if ``alg`` is a PBKDF2-HMAC algorithm, ``0`` otherwise. This macro can return either ``0`` or ``1`` if ``alg`` is not a supported key-derivation algorithm identifier. +.. macro:: PSA_ALG_IS_WPA3_SAE_H2E + :definition: /* specification-defined value */ + + .. summary:: + Whether the specified algorithm is a WPA3-SAE hash-to-element key-derivation algorithm + + .. versionadded:: 1.4 + + .. param:: alg + An algorithm identifier: a value of type `psa_algorithm_t`. + + .. return:: + ``1`` if ``alg`` is a WPA3-SAE hash-to-element algorithm, ``0`` otherwise. + This macro can return either ``0`` or ``1`` if ``alg`` is not a supported key-derivation algorithm identifier. + .. macro:: PSA_KEY_DERIVATION_UNLIMITED_CAPACITY :definition: /* implementation-defined value */ diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index 8ec8f98b..efd4e221 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -335,6 +335,7 @@ The permitted values of HASH-TYPE (see :numref:`table-hash-type`) depend on the SP 800-108 Counter CMAC, 0, ``0x08``, :code:`PSA_ALG_SP800_108_COUNTER_CMAC`, ``0x08000800`` PBKDF2-HMAC, 1, ``0x01``, :code:`PSA_ALG_PBKDF2_HMAC(hash)`, ``0x088001hh`` :sup:`a` PBKDF2-AES-CMAC-PRF-128, 1, ``0x02``, :code:`PSA_ALG_PBKDF2_AES_CMAC_PRF_128`, ``0x08800200`` + WPA3-SAE Hash-to-element, 1, ``0x04``, :code:`PSA_ALG_WPA3_SAE_H2E(hash)`, ``0x088004hh`` :sup:`a` a. ``hh`` is the HASH-TYPE for the hash algorithm, ``hash``, used to construct the key-derivation algorithm. diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index c2b51902..1297e652 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -194,6 +194,9 @@ Algorithm macros (((alg) & 0x7f008000) == 0x03008000) || \ (((alg) & 0x7f008000) == 0x05008000)) + #define PSA_ALG_IS_WPA3_SAE_H2E(alg) \ + (((alg) & ~0x000000ff) == 0x08800400) + #define PSA_ALG_IS_XOF(alg) \ (((alg) & 0x7f000000) == 0x0D000000) @@ -242,6 +245,9 @@ Algorithm macros #define PSA_ALG_TRUNCATED_MAC(mac_alg, mac_length) \ ((psa_algorithm_t) (((mac_alg) & ~0x003f8000) | (((mac_length) & 0x3f) << 16))) + #define PSA_ALG_WPA3_SAE_H2E(hash_alg) \ + ((psa_algorithm_t) (0x08800400 | ((hash_alg) & 0x000000ff))) + #define PSA_PAKE_PRIMITIVE(pake_type, pake_family, pake_bits) \ ((pake_bits & 0xFFFF) != pake_bits) ? 0 : \ ((psa_pake_primitive_t) (((pake_type) << 24 | \ From cd958a7e7267de85d366d97474abb83ab0d65a97 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 5 Sep 2025 16:37:05 +0100 Subject: [PATCH 05/17] Tidy up and minor adjustments in PAKE chapter --- doc/crypto/api/ops/pake.rst | 124 +++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index cd002ef6..014a652b 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -289,7 +289,7 @@ A PAKE cipher suite is required when setting up a PAKE operation in `psa_pake_se - Value * - algorithm - - :code:`PSA_ALG_NONE` --- an invalid algorithm identifier. + - `PSA_ALG_NONE` --- an invalid algorithm identifier. * - primitive - ``0`` --- an invalid PAKE primitive. * - key confirmation @@ -351,7 +351,7 @@ A PAKE cipher suite is required when setting up a PAKE operation in `psa_pake_se .. param:: psa_pake_cipher_suite_t* cipher_suite The cipher suite object to write to. .. param:: psa_algorithm_t alg - The PAKE algorithm to write: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. + The PAKE algorithm to write: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. .. return:: void @@ -565,13 +565,13 @@ PAKE step types :definition: ((psa_pake_step_t)0x01) .. summary:: - The key share being sent to or received from the peer. + A key share being sent to or received from a PAKE participant. .. versionadded:: 1.1 The format for both input and output using this step is the same as the format for public keys on the group specified by the PAKE operation's primitive. - The public-key formats are defined in the documentation for :code:`psa_export_public_key()`. + The public-key formats are defined in the documentation for `psa_export_public_key()`. For information regarding how the group is determined, consult the documentation `PSA_PAKE_PRIMITIVE()`. @@ -579,7 +579,7 @@ PAKE step types :definition: ((psa_pake_step_t)0x02) .. summary:: - A Schnorr NIZKP public key. + A Schnorr NIZKP public key being sent to or received from a PAKE participant. .. versionadded:: 1.1 @@ -587,7 +587,7 @@ PAKE step types The format for both input and output at this step is the same as that for public keys on the group specified by the PAKE operation's primitive. - For more information on the format, consult the documentation of :code:`psa_export_public_key()`. + For more information on the format, consult the documentation of `psa_export_public_key()`. For information regarding how the group is determined, consult the documentation `PSA_PAKE_PRIMITIVE()`. @@ -595,7 +595,7 @@ PAKE step types :definition: ((psa_pake_step_t)0x03) .. summary:: - A Schnorr NIZKP proof. + A Schnorr NIZKP proof being sent to or received from a PAKE participant. .. versionadded:: 1.1 @@ -615,7 +615,7 @@ PAKE step types :definition: ((psa_pake_step_t)0x04) .. summary:: - The key confirmation value. + A key confirmation value being sent to or received from a PAKE participant. .. versionadded:: 1.2 @@ -702,7 +702,7 @@ Multi-part PAKE operations The valid key types depend on the PAKE algorithm, and participant role. Refer to the documentation of individual PAKE algorithms for more information. - The key must permit the usage :code:`PSA_KEY_USAGE_DERIVE`. + The key must permit the usage `PSA_KEY_USAGE_DERIVE`. .. param:: const psa_pake_cipher_suite_t * cipher_suite The cipher suite to use. A PAKE cipher suite fully characterizes a PAKE algorithm, including the PAKE algorithm. @@ -716,11 +716,11 @@ Multi-part PAKE operations The following conditions can result in this error: * The operation state is not valid: it must be inactive. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_HANDLE ``password_key`` is not a valid key identifier. .. retval:: PSA_ERROR_NOT_PERMITTED - ``psssword_key`` does not have the :code:`PSA_KEY_USAGE_DERIVE` flag, or it does not permit the algorithm in ``cipher_suite``. + ``psssword_key`` does not have the `PSA_KEY_USAGE_DERIVE` flag, or it does not permit the algorithm in ``cipher_suite``. .. retval:: PSA_ERROR_INVALID_ARGUMENT The following conditions can result in this error: @@ -793,7 +793,7 @@ Multi-part PAKE operations The following conditions can result in this error: * The operation state is not valid: it must be active, and `psa_pake_set_role()`, `psa_pake_input()`, and `psa_pake_output()` must not have been called yet. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT The following conditions can result in this error: @@ -836,7 +836,7 @@ Multi-part PAKE operations The following conditions can result in this error: * The operation state is not valid: it must be active, and `psa_pake_set_user()`, `psa_pake_input()`, and `psa_pake_output()` must not have been called yet. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT ``user_id`` is not valid for the operation's algorithm and cipher suite. .. retval:: PSA_ERROR_NOT_SUPPORTED @@ -873,7 +873,7 @@ Multi-part PAKE operations * The operation state is not valid: it must be active, and `psa_pake_set_peer()`, `psa_pake_input()`, and `psa_pake_output()` must not have been called yet. * Calling `psa_pake_set_peer()` is invalid with the operation's algorithm. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT ``peer_id`` is not valid for the operation's algorithm and cipher suite. .. retval:: PSA_ERROR_NOT_SUPPORTED @@ -910,7 +910,7 @@ Multi-part PAKE operations * The operation state is not valid: it must be active, and `psa_pake_set_context()`, `psa_pake_input()`, and `psa_pake_output()` must not have been called yet. * Calling `psa_pake_set_context()` is invalid with the operation's algorithm. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT ``context`` is not valid for the operation's algorithm and cipher suite. .. retval:: PSA_ERROR_NOT_SUPPORTED @@ -955,7 +955,7 @@ Multi-part PAKE operations The following conditions can result in this error: * The operation state is not valid: it must be active and fully set up, and this call must conform to the algorithm's requirements for ordering of input and output steps. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT ``step`` is not compatible with the operation's algorithm. .. retval:: PSA_ERROR_NOT_SUPPORTED @@ -1002,7 +1002,7 @@ Multi-part PAKE operations The following conditions can result in this error: * The operation state is not valid: it must be active and fully set up, and this call must conform to the algorithm's requirements for ordering of input and output steps. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_INVALID_ARGUMENT The following conditions can result in this error: @@ -1046,8 +1046,8 @@ Multi-part PAKE operations The following attributes are required for all keys: * The key type. - All PAKE algorithms can output a key of type :code:`PSA_KEY_TYPE_DERIVE` or :code:`PSA_KEY_TYPE_HMAC`. - PAKE algorithms that produce a pseudorandom shared secret, can also output block-cipher key types, for example :code:`PSA_KEY_TYPE_AES`. + All PAKE algorithms can output a key of type `PSA_KEY_TYPE_DERIVE` or `PSA_KEY_TYPE_HMAC`. + PAKE algorithms that produce a pseudorandom shared secret, can also output block-cipher key types, for example `PSA_KEY_TYPE_AES`. Refer to the documentation of individual PAKE algorithms for more information. The following attributes must be set for keys used in cryptographic operations: @@ -1069,7 +1069,7 @@ Multi-part PAKE operations The final attributes of the new key can be queried by calling `psa_get_key_attributes()` with the key's identifier. .. param:: psa_key_id_t * key - On success, an identifier for the newly created key. :code:`PSA_KEY_ID_NULL` on failure. + On success, an identifier for the newly created key. `PSA_KEY_ID_NULL` on failure. .. return:: psa_status_t .. retval:: PSA_SUCCESS @@ -1083,7 +1083,7 @@ Multi-part PAKE operations For an unconfirmed key, this will be when the key-exchange output and input steps are complete, but prior to any key-confirmation output and input steps. For a confirmed key, this will be when all key-exchange and key-confirmation output and input steps are complete. - * The library requires initializing by a call to :code:`psa_crypto_init()`. + * The library requires initializing by a call to `psa_crypto_init()`. .. retval:: PSA_ERROR_ALREADY_EXISTS This is an attempt to create a persistent key, and there is already a persistent key with the given identifier. .. retval:: PSA_ERROR_NOT_SUPPORTED @@ -1140,9 +1140,6 @@ Multi-part PAKE operations Following key confirmation, the PAKE algorithm provides a cryptographic guarantee that the peer used the same password and identity inputs, and has computed the identical shared secret key. - Since the peer is not authenticated, no action should be taken that assumes that the peer is who it claims to be. - For example, do not access restricted resources on the peer's behalf until an explicit authentication has succeeded. - .. note:: Some PAKE algorithms do not include any key-confirmation steps. @@ -1169,7 +1166,7 @@ Multi-part PAKE operations .. retval:: PSA_ERROR_COMMUNICATION_FAILURE .. retval:: PSA_ERROR_CORRUPTION_DETECTED .. retval:: PSA_ERROR_BAD_STATE - The library requires initializing by a call to :code:`psa_crypto_init()`. + The library requires initializing by a call to `psa_crypto_init()`. Aborting an operation frees all associated resources except for the ``operation`` object itself. Once aborted, the operation object can be reused for another operation by calling `psa_pake_setup()` again. @@ -1193,7 +1190,7 @@ PAKE support macros .. versionadded:: 1.1 .. param:: alg - A PAKE algorithm: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. + A PAKE algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. .. param:: primitive A primitive of type `psa_pake_primitive_t` that is compatible with algorithm ``alg``. .. param:: output_step @@ -1230,7 +1227,7 @@ PAKE support macros .. versionadded:: 1.1 .. param:: alg - A PAKE algorithm: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. + A PAKE algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_PAKE(alg)` is true. .. param:: primitive A primitive of type `psa_pake_primitive_t` that is compatible with algorithm ``alg``. .. param:: input_step @@ -1277,7 +1274,7 @@ J-PAKE cipher suites When setting up a PAKE cipher suite to use the J-PAKE protocol: -* Use the :code:`PSA_ALG_JPAKE()` algorithm, parameterized by the required hash algorithm. +* Use the `PSA_ALG_JPAKE()` algorithm, parameterized by the required hash algorithm. * Use a PAKE primitive for the required elliptic curve, or finite field group. * J-PAKE does not confirm the shared secret key that results from the key exchange. @@ -1300,7 +1297,7 @@ More information on selecting a specific elliptic curve or finite field Diffie-H J-PAKE password processing ~~~~~~~~~~~~~~~~~~~~~~~~~~ -The PAKE operation for J-PAKE expects a key of type type :code:`PSA_KEY_TYPE_PASSWORD` or :code:`PSA_KEY_TYPE_PASSWORD_HASH`. +The PAKE operation for J-PAKE expects a key of type type `PSA_KEY_TYPE_PASSWORD` or PSA_KEY_TYPE_PASSWORD_HASH`. The same key value must be provided to the PAKE operation in both participants. The key can be the password text itself, in an agreed character encoding, or some value derived from the password, as required by a higher level protocol. @@ -1335,7 +1332,7 @@ A call to `psa_pake_set_context()` for a J-PAKE operation will fail with :code:` The following steps demonstrate the application code for 'User' in :numref:`fig-jpake`. The code flow for the 'Peer' is the same as for 'User', as J-PAKE is a balanced PAKE. -1. To prepare a J-PAKE operation, initialize and set up a :code:`psa_pake_operation_t` object by calling the following functions: +1. To prepare a J-PAKE operation, initialize and set up a `psa_pake_operation_t` object by calling the following functions: .. code-block:: xref @@ -1360,7 +1357,7 @@ After setup, the key exchange flow for J-PAKE is as follows: The application can either extract the round one output values first, and then provide the round one inputs that are received from the Peer; or provide the peer inputs first, and then extract the outputs. - * To get the first round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: + To get the first round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: .. code-block:: xref @@ -1377,7 +1374,7 @@ After setup, the key exchange flow for J-PAKE is as follows: // Get r2, the ZKP proof for x2 psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); - * To provide the first round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: + To provide the first round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: .. code-block:: xref @@ -1394,11 +1391,11 @@ After setup, the key exchange flow for J-PAKE is as follows: // Set r4, the ZKP proof for x4 psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); -#. Round two. +3. Round two. The application can either extract the round two output values first, and then provide the round two inputs that are received from the Peer; or provide the peer inputs first, and then extract the outputs. - * To get the second round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: + To get the second round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: .. code-block:: xref @@ -1409,7 +1406,7 @@ After setup, the key exchange flow for J-PAKE is as follows: // Get r5, the ZKP proof for x2*s psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); - * To provide the second round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: + To provide the second round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: .. code-block:: xref @@ -1420,7 +1417,11 @@ After setup, the key exchange flow for J-PAKE is as follows: // Set r6, the ZKP proof for x4*s psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); -#. To use the shared secret, extract it as a key-derivation key. For example, to extract a derivation key for HKDF-SHA-256: +Extract shared secret +^^^^^^^^^^^^^^^^^^^^^ + +4. To use the shared secret, extract it as a key-derivation key. + For example, to extract a derivation key for HKDF-SHA-256: .. code-block:: xref @@ -1465,7 +1466,7 @@ J-PAKE algorithms .. versionchanged:: 1.2 Parameterize J-PAKE algorithm by hash. .. param:: hash_alg - A hash algorithm: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. .. return:: A J-PAKE algorithm, parameterized by a specific hash. @@ -1487,8 +1488,8 @@ J-PAKE algorithms .. subsection:: Compatible key types - | :code:`PSA_KEY_TYPE_PASSWORD` - | :code:`PSA_KEY_TYPE_PASSWORD_HASH` + | `PSA_KEY_TYPE_PASSWORD` + | `PSA_KEY_TYPE_PASSWORD_HASH` .. macro:: PSA_ALG_IS_JPAKE :definition: /* specification-defined value */ @@ -1499,7 +1500,7 @@ J-PAKE algorithms .. versionadded:: 1.2 .. param:: alg - An algorithm identifier: a value of type :code:`psa_algorithm_t`. + An algorithm identifier: a value of type `psa_algorithm_t`. .. return:: ``1`` if ``alg`` is a J-PAKE algorithm, ``0`` otherwise. @@ -1536,8 +1537,8 @@ Valid combinations of these parameters are defined in the table of cipher suites When setting up a PAKE cipher suite to use the SPAKE2+ protocol defined in :rfc:`9383`: -* For cipher-suites that use HMAC for key confirmation, use the :code:`PSA_ALG_SPAKE2P_HMAC()` algorithm, parameterized by the required hash algorithm. -* For cipher-suites that use CMAC-AES-128 for key confirmation, use the :code:`PSA_ALG_SPAKE2P_CMAC()` algorithm, parameterized by the required hash algorithm. +* For cipher-suites that use HMAC for key confirmation, use the `PSA_ALG_SPAKE2P_HMAC()` algorithm, parameterized by the required hash algorithm. +* For cipher-suites that use CMAC-AES-128 for key confirmation, use the `PSA_ALG_SPAKE2P_CMAC()` algorithm, parameterized by the required hash algorithm. * Use a PAKE primitive for the required elliptic curve. For example, the following code creates a cipher suite to select SPAKE2+ using edwards25519 with the SHA-256 hash function: @@ -1553,7 +1554,7 @@ For example, the following code creates a cipher suite to select SPAKE2+ using e When setting up a PAKE cipher suite to use the SPAKE2+ protocol used by :cite:`MATTER`: -* Use the :code:`PSA_ALG_SPAKE2P_MATTER` algorithm. +* Use the `PSA_ALG_SPAKE2P_MATTER` algorithm. * Use the :code:`PSA_PAKE_PRIMITIVE(PSA_PAKE_PRIMITIVE_TYPE_ECC, PSA_ECC_FAMILY_SECP_R1, 256)` PAKE primitive. The following code creates a cipher suite to select the :cite:`MATTER` variant of SPAKE2+: @@ -1678,7 +1679,7 @@ In SPAKE2+, the Prover uses the `PSA_PAKE_ROLE_CLIENT` role, and the Verifier us The key passed to the Prover must be a SPAKE2+ key pair, which is derived as recommended in :secref:`spake2p-registration`. The key passed to the Verifier can either be a SPAKE2+ key pair, or a SPAKE2+ public key. -A SPAKE2+ public key is imported from data that is output by calling :code:`psa_export_public_key()` on a SPAKE2+ key pair. +A SPAKE2+ public key is imported from data that is output by calling `psa_export_public_key()` on a SPAKE2+ key pair. Both participants in SPAKE2+ have an optional identity. If no identity value is provided, then a zero-length string is used for that identity in the protocol. @@ -1696,7 +1697,7 @@ If the participants do not supply the same context value to the protocol, the co The following steps demonstrate the application code for both Prover and Verifier in :numref:`fig-spake2p`. **Prover** - To prepare a SPAKE2+ operation for the Prover, initialize and set up a :code:`psa_pake_operation_t` object by calling the following functions: + To prepare a SPAKE2+ operation for the Prover, initialize and set up a `psa_pake_operation_t` object by calling the following functions: .. code-block:: xref @@ -1718,7 +1719,7 @@ The following steps demonstrate the application code for both Prover and Verifie psa_pake_set_context(&spake2p_p, ...); // Optional context **Verifier** - To prepare a SPAKE2+ operation for the Verifier, initialize and set up a :code:`psa_pake_operation_t` object by calling the following functions: + To prepare a SPAKE2+ operation for the Verifier, initialize and set up a `psa_pake_operation_t` object by calling the following functions: .. code-block:: xref @@ -1739,8 +1740,8 @@ The following steps demonstrate the application code for both Prover and Verifie psa_pake_set_peer(&spake2p_v, ...); // Prover identity psa_pake_set_context(&spake2p_v, ...); // Optional context -Key exchange -^^^^^^^^^^^^ +Key exchange and confirmation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ After setup, the key exchange and confirmation flow for SPAKE2+ is as follows. @@ -1800,6 +1801,9 @@ After setup, the key exchange and confirmation flow for SPAKE2+ is as follows. // Set confirmP psa_pake_input(&spake2p_v, PSA_PAKE_STEP_CONFIRM, ...); +Extract shared secret +^^^^^^^^^^^^^^^^^^^^^ + **Prover** To use the shared secret, extract it as a key-derivation key. For example, to extract a derivation key for HKDF-SHA-256: @@ -1848,7 +1852,7 @@ SPAKE2+ algorithms .. versionadded:: 1.2 .. param:: hash_alg - A hash algorithm: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. .. return:: A SPAKE2+ algorithm, using HMAC for key confirmation, parameterized by a specific hash. @@ -1857,7 +1861,7 @@ SPAKE2+ algorithms This is SPAKE2+, as defined by :RFC-title:`9383`, for cipher suites that use HMAC for key confirmation. SPAKE2+ cipher suites are specified in :rfc:`9383#4`. - The cipher suite's hash algorithm is used as input to `PSA_ALG_SPAKE2P_HMAC()`. + See :secref:`spake2p-cipher-suites`. The shared secret that is produced by SPAKE2+ is pseudorandom. Although it can be used directly as an encryption key, it is recommended to use the shared secret as an input to a key-derivation operation to produce additional cryptographic keys. @@ -1866,8 +1870,8 @@ SPAKE2+ algorithms .. subsection:: Compatible key types - | :code:`PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` - | :code:`PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) + | `PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` + | `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) .. macro:: PSA_ALG_SPAKE2P_CMAC :definition: /* specification-defined value */ @@ -1878,7 +1882,7 @@ SPAKE2+ algorithms .. versionadded:: 1.2 .. param:: hash_alg - A hash algorithm: a value of type :code:`psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. .. return:: A SPAKE2+ algorithm, using CMAC for key confirmation, parameterized by a specific hash. @@ -1897,8 +1901,8 @@ SPAKE2+ algorithms .. subsection:: Compatible key types - | :code:`PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` - | :code:`PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) + | `PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` + | `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) .. macro:: PSA_ALG_SPAKE2P_MATTER :definition: ((psa_algoirithm_t)0x0A000609) @@ -1935,8 +1939,8 @@ SPAKE2+ algorithms .. subsection:: Compatible key types - | :code:`PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` - | :code:`PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) + | `PSA_KEY_TYPE_SPAKE2P_KEY_PAIR` + | `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) .. macro:: PSA_ALG_IS_SPAKE2P :definition: /* specification-defined value */ @@ -1947,7 +1951,7 @@ SPAKE2+ algorithms .. versionadded:: 1.2 .. param:: alg - An algorithm identifier: a value of type :code:`psa_algorithm_t`. + An algorithm identifier: a value of type `psa_algorithm_t`. .. return:: ``1`` if ``alg`` is a SPAKE2+ algorithm, ``0`` otherwise. @@ -1964,7 +1968,7 @@ SPAKE2+ algorithms .. versionadded:: 1.2 .. param:: alg - An algorithm identifier: a value of type :code:`psa_algorithm_t`. + An algorithm identifier: a value of type `psa_algorithm_t`. .. return:: ``1`` if ``alg`` is a SPAKE2+ algorithm that uses a HMAC-based key confirmation, ``0`` otherwise. @@ -1981,7 +1985,7 @@ SPAKE2+ algorithms .. versionadded:: 1.2 .. param:: alg - An algorithm identifier: a value of type :code:`psa_algorithm_t`. + An algorithm identifier: a value of type `psa_algorithm_t`. .. return:: ``1`` if ``alg`` is a SPAKE2+ algorithm that uses a CMAC-based key confirmation, ``0`` otherwise. From f7bc46bf2240d56bc9ba4b2d3d26c65290831aa6 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 5 Sep 2025 16:38:07 +0100 Subject: [PATCH 06/17] Add WPA3-SAE PAKE protocol and algorithm identifiers --- doc/crypto/api/ops/pake.rst | 633 ++++++++++++++++-- doc/crypto/appendix/encodings.rst | 2 + doc/crypto/appendix/specdef_values.rst | 15 + doc/crypto/figure/pake/wpa3-sae-pt.pdf | Bin 0 -> 42808 bytes .../figure/pake/wpa3-sae-pt.pdf.license | 2 + doc/crypto/figure/pake/wpa3-sae-pt.puml | 31 + doc/crypto/figure/pake/wpa3-sae-pt.svg | 1 + .../figure/pake/wpa3-sae-pt.svg.license | 2 + doc/crypto/figure/pake/wpa3-sae.pdf | Bin 0 -> 43054 bytes doc/crypto/figure/pake/wpa3-sae.pdf.license | 2 + doc/crypto/figure/pake/wpa3-sae.puml | 55 ++ doc/crypto/figure/pake/wpa3-sae.svg | 1 + doc/crypto/figure/pake/wpa3-sae.svg.license | 2 + 13 files changed, 704 insertions(+), 42 deletions(-) create mode 100644 doc/crypto/figure/pake/wpa3-sae-pt.pdf create mode 100644 doc/crypto/figure/pake/wpa3-sae-pt.pdf.license create mode 100644 doc/crypto/figure/pake/wpa3-sae-pt.puml create mode 100644 doc/crypto/figure/pake/wpa3-sae-pt.svg create mode 100644 doc/crypto/figure/pake/wpa3-sae-pt.svg.license create mode 100644 doc/crypto/figure/pake/wpa3-sae.pdf create mode 100644 doc/crypto/figure/pake/wpa3-sae.pdf.license create mode 100644 doc/crypto/figure/pake/wpa3-sae.puml create mode 100644 doc/crypto/figure/pake/wpa3-sae.svg create mode 100644 doc/crypto/figure/pake/wpa3-sae.svg.license diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index 014a652b..8e80c201 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -18,6 +18,7 @@ This chapter is divided into the following sections: * :secref:`pake-common-api` --- the common interface elements, including the PAKE operation. * :secref:`pake-jpake` --- the J-PAKE protocol, and the associated interface elements. * :secref:`pake-spake2p` --- the SPAKE2+ protocols, and the associated interface elements. +* :secref:`pake-wpa3-sae` --- the WPA3-SAE protocol, and the associated interface elements. .. _pake-common-api: @@ -575,6 +576,16 @@ PAKE step types For information regarding how the group is determined, consult the documentation `PSA_PAKE_PRIMITIVE()`. + .. todo:: + + Decide on how to handle COMMIT-ELEMENT format in WPA3-SAE. + This is an element in the group, using a big-endian scalar value for FFDH (== public key format (I think - no truncation)), and a big-endian encoding of the {x,y} coordinates for ECC. The latter is close to public key format, but there is no :code:```0x04``` prefix. + + 1. Use a WPA3-SAE-specific PAKE step for the COMMIT-ELEMENT, and specify the format (by reference to 802.11). + 2. Make the format of KEY_SHARE step algorithm-specific, and describe it separately for J-PAKE, SPAKE2+, and WPA3-SAE. + 3. Make a COMMIT step for WPA3-SAE that concatenates both the scalar and ELEMENT values (means application has to know the size of the scalar element to split/combine this correctly, and cannot read/write this directly from SAE frame structures in memory). + 4. Stick with KEY_SHARE and the 'public key format', making the application deal with the extra prefix (prevents application directly reading from/writing to the SAE frame structures in memory). + .. macro:: PSA_PAKE_STEP_ZK_PUBLIC :definition: ((psa_pake_step_t)0x02) @@ -619,9 +630,82 @@ PAKE step types .. versionadded:: 1.2 - This value is used during the key confirmation phase of a PAKE protocol. The format of the value depends on the algorithm and cipher suite: + This value is used during the key confirmation phase of a PAKE protocol. + The use of this step, and format of the value depends on the algorithm and cipher suite: + + * For a SPAKE2+ algorithm, the format for both input and output at this step is the same as the output of the MAC algorithm specified in the cipher suite. + See :secref:`spake2p-operation`. + + * For a WPA3-SAE algorithm, the format for both input and output at this step is a 2-byte little-endian *send-confirm* counter, followed by the *confirm* value, which is the output from the hash algorithm specified in the cipher suite. + See :secref:`wpa3-sae-operation`. + +.. macro:: PSA_PAKE_STEP_SALT + :definition: ((psa_pake_step_t)0x05) + + .. summary:: + A salt value used for deriving shared secrets within a PAKE operation. + + .. versionadded:: 1.4 + + This input can be used during the key exchange phase of a PAKE protocol. + The use of this step, and format of the value depends on the algorithm and cipher suite: + + * For a WPA3-SAE algorithm, a salt value must be provided as defined in `[IEEE-802.11]` §12.4.5.4. + See :secref:`wpa3-sae-operation`. + +.. + Oberon's approach + + /** The WPA3-SAE commit step. + * + * The format for both input and output at this step is a 2 byte number + * specifying the group used followed by a scalar and an element of the + * specified group. + */ + #define PSA_PAKE_STEP_COMMIT ((psa_pake_step_t)0x06) + +.. macro:: PSA_PAKE_STEP_COMMIT_SCALAR + :definition: ((psa_pake_step_t)0x06) + + .. summary:: + A scalar value being sent to or received from a PAKE participant. + + .. versionadded:: 1.4 + + This input and output is used during the key exchange phase of a PAKE protocol. + The use of this step, and format of the value depends on the algorithm and cipher suite: - * For :code:`PSA_ALG_SPAKE2P`, the format for both input and output at this step is the same as the output of the MAC algorithm specified in the cipher suite. + * For a WPA3-SAE algorithm, the format for input at this step is a string that encodes the *commit-scalar* or *peer-commit-scalar* values, as defined in `[IEEE-802.11]` §12.4.7.3. + See :secref:`wpa3-sae-operation`. + +.. macro:: PSA_PAKE_STEP_CONFIRM_COUNT + :definition: ((psa_pake_step_t)0x07) + + .. summary:: + A counter used as part of key confirmation. + + .. versionadded:: 1.4 + + This value is input during the key confirmation phase of a PAKE protocol. + It enables multiple confirmation attempts to result in distinct confirmation values. + The use of this step, and format of the value depends on the algorithm and cipher suite: + + * For a WPA3-SAE algorithm, the format for input at this step is the 2-byte little-endian *send-confirm* counter. + See :secref:`wpa3-sae-operation`. + +.. macro:: PSA_PAKE_STEP_KEY_ID + :definition: ((psa_pake_step_t)0x08) + + .. summary:: + A key identifier value from a PAKE operation. + + .. versionadded:: 1.4 + + This value can be output from a PAKE operation following key confirmation. + The use of this step, and format of the value depends on the algorithm and cipher suite: + + * For a WPA3-SAE algorithm, the format of the output at this step is the 16-byte PMKID. + See :secref:`wpa3-sae-operation`. .. _pake-operation: @@ -1359,37 +1443,37 @@ After setup, the key exchange flow for J-PAKE is as follows: To get the first round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: - .. code-block:: xref + .. code-block:: xref - // Get g1 - psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Get V1, the ZKP public key for x1 - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Get r1, the ZKP proof for x1 - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); - // Get g2 - psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Get V2, the ZKP public key for x2 - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Get r2, the ZKP proof for x2 - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Get g1 + psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Get V1, the ZKP public key for x1 + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Get r1, the ZKP proof for x1 + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Get g2 + psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Get V2, the ZKP public key for x2 + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Get r2, the ZKP proof for x2 + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); To provide the first round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: - .. code-block:: xref + .. code-block:: xref - // Set g3 - psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Set V3, the ZKP public key for x3 - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Set r3, the ZKP proof for x3 - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); - // Set g4 - psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Set V4, the ZKP public key for x4 - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Set r4, the ZKP proof for x4 - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Set g3 + psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Set V3, the ZKP public key for x3 + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Set r3, the ZKP proof for x3 + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Set g4 + psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Set V4, the ZKP public key for x4 + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Set r4, the ZKP proof for x4 + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); 3. Round two. @@ -1397,25 +1481,25 @@ After setup, the key exchange flow for J-PAKE is as follows: To get the second round data that needs to be sent to the peer, make the following calls to `psa_pake_output()`, in the order shown: - .. code-block:: xref + .. code-block:: xref - // Get A - psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Get V5, the ZKP public key for x2*s - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Get r5, the ZKP proof for x2*s - psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Get A + psa_pake_output(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Get V5, the ZKP public key for x2*s + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Get r5, the ZKP proof for x2*s + psa_pake_output(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); To provide the second round data received from the peer to the operation, make the following calls to `psa_pake_input()`, in the order shown: - .. code-block:: xref + .. code-block:: xref - // Set B - psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); - // Set V6, the ZKP public key for x4*s - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); - // Set r6, the ZKP proof for x4*s - psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); + // Set B + psa_pake_input(&jpake, PSA_PAKE_STEP_KEY_SHARE, ...); + // Set V6, the ZKP public key for x4*s + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PUBLIC, ...); + // Set r6, the ZKP proof for x4*s + psa_pake_input(&jpake, PSA_PAKE_STEP_ZK_PROOF, ...); Extract shared secret ^^^^^^^^^^^^^^^^^^^^^ @@ -1992,3 +2076,468 @@ SPAKE2+ algorithms This macro can return either ``0`` or ``1`` if ``alg`` is not a supported PAKE algorithm identifier. SPAKE2+ algorithms, using CMAC-based key confirmation, are constructed using :code:`PSA_ALG_SPAKE2P_CMAC(hash_alg)`. + + +.. _pake-wpa3-sae: + +The WPA3-SAE protocol +--------------------- + +WPA3-SAE is a balanced, password-authenticated key exchange protocol, defined by :cite-title:`IEEE-802.11`. +It is used as the authentication and key exchange protocol for WLAN access points and mesh networks. +WPA3-SAE includes confirmation of the shared secret key that results from the key exchange. + +.. _wpa3-sae-cipher-suites: + +WPA3-SAE cipher suites +~~~~~~~~~~~~~~~~~~~~~~ + +WPA3-SAE is instantiated with the following parameters: + +* An elliptic curve group or a finite field cyclic group. +* A cryptographic hash function. + +`[IEEE-802.11]` describes three variants of the WPA3-SAE algorithm. +These differ in the method used to generate a password element (PWE) from the password, and in the size of the key confirmation key (SAE-KCK) and pairwise master key (PMK). + +:numref:`tab-wpa3-sae-variants` summarizes the properties of the different algorithm variants. + +.. list-table:: WPA3-SAE algorithm variants + :name: tab-wpa3-sae-variants + :header-rows: 1 + :widths: 4 3 3 2 2 + + * - Algorithm variant + - PWE method + - Hash algorithm + - SAE-KCK size + - PMK size + + * - Looping + - Looping + - SHA-256 + - 256 + - 256 + * - Hash-to-element + - Hash-to-element + - SHA-256 + + SHA-384 + + SHA-512 + - 256 + + 384 + + 512 + - 256 + + 256 + + 256 + * - Group-dependent-hash + - Hash-to-element + - SHA-256 + + SHA-384 + + SHA-512 + - 256 + + 384 + + 512 + - 256 + + 384 + + 512 + +When setting up a PAKE cipher suite to use the WPA3-SAE protocol: + +* For the looping variant, use the :code:`PSA_ALG_WPA3_SAE_FIXED(PSA_ALG_SHA_256)` algorithm. +* For the hash-to-element variant, use the :code:`PSA_ALG_WPA3_SAE_FIXED(hash_alg)` algorithm, where ``hash_alg`` is the required hash algorithm. +* For the group-dependent-hash variant, use the :code:`PSA_ALG_WPA3_SAE_GDH(hash_alg)` algorithm, where ``hash_alg`` is the required hash algorithm. +* Use a PAKE primitive for the required elliptic curve or finite field group. + +Valid elliptic curves and finite field groups for WPA3-SAE are defined in `[IEEE-802.11]` §12.4.4.1. +For the hash-to-element and group-dependent-hash variants, the required hash algorithm is determined from the size of the prime for the cyclic group. +See Table 12-1 in `[IEEE-802.11]` §12.4.2. + +If the hash algorithm in the cipher suite is not compatible with the WPA3-SAE algorithm and PAKE primitive, the call to `psa_pake_setup()` will fail with :code:`PSA_ERROR_INVALID_ARGUMENT`. + +For example, the following code creates a PAKE cipher suite for WPA3-SAE using hash-to-element over the secp256r1 elliptic curve (IANA group 19): + +.. code-block:: xref + + psa_pake_cipher_suite_t cipher_suite = PSA_PAKE_CIPHER_SUITE_INIT; + + psa_pake_cs_set_algorithm(&cipher_suite, PSA_ALG_WPA3_SAE_FIXED(PSA_ALG_SHA_256)); + psa_pake_cs_set_primitive(&cipher_suite, + PSA_PAKE_PRIMITIVE(PSA_PAKE_PRIMITIVE_TYPE_ECC, + PSA_ECC_FAMILY_SECP_R1, 256)); + + +.. _wpa3-sae-passwords: + +WPA3-SAE password processing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +WPA3-SAE defines the following two methods for deriving the password element (PWE) from the password: + +.. list-table:: + :widths: 1 4 + :class: borderless + + * - Looping method + - Repeatedly sample candidate element values using a hash computed from the password, until a valid element is found. + This derivation occurs as part of the authentication flow. + + * - Hash-to-element method + - Derive a password token (PT) element from the password, using the hash-to-curve procedure for elliptic curve groups, and a direct method for finite field groups. + This derivation can be carried out when the network SSID and password is provisioned to the device, and the PT stored as part of the configuration. + + During authentication, the PWE is derived from the PT. + +The hash-to-element method is recommended, as it is less vulnerable to timing-based attacks, and reduces the authentication time. + +:numref:`fig-wpa3-sae-pt` illustrates the password processing required prior to the WPA3-SAE authentication flow. + +.. figure:: /figure/pake/wpa3-sae-pt.* + :name: fig-wpa3-sae-pt + + WPA3-SAE password processing + +.. rubric:: Looping method + +To use the looping method, import the password into a key of type `PSA_KEY_TYPE_PASSWORD`. +The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. +Provide this key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. + +.. rubric:: Hash-to-element method + +To use the hash-to-element method: + +1. Import the password into a key of type `PSA_KEY_TYPE_PASSWORD`. + The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. + +#. A WPA3-SAE password token (PT) is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. + The `PSA_ALG_WPA3_SAE_H2E()` algorithm is parameterized by the hash used in the required WPA3-SAE cipher suite. + + The PT is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC_PT()` or `PSA_KEY_TYPE_WPA3_SAE_DH_PT()`. + The key type is parameterized by the elliptic curve or finite field Diffie-Hellman group used in the required WPA3-SAE cipher suite. + + The PT key must be protected at least as well as the password. + +#. Pass the PT key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. + +The following steps demonstrate the derivation of a password token for use with the group-dependent-hash variant of WPA3-SAE. +The selected cipher suite in the example is IANA Group 20: ECC using secp384r1, hash function SHA-384. + +1. Allocate and initialize a key-derivation object: + + .. code-block:: xref + + psa_key_derivation_operation_t h2e_kdf = PSA_KEY_DERIVATION_OPERATION_INIT; + +#. Setup the key derivation from the WPA3-SAE password, ``password_key``, with network SSID ``ssid``: + + .. code-block:: xref + + psa_key_derivation_setup(&h2e_kdf, PSA_ALG_WPA3_SAE_H2E(PSA_ALG_SHA_384)); + psa_key_derivation_input_bytes(&h2e_kdf, PSA_KEY_DERIVATION_INPUT_SALT, ssid, ssid_len); + psa_key_derivation_input_key(&h2e_kdf, PSA_KEY_DERIVATION_INPUT_PASSWORD, password_key); + +#. Allocate and initialize a key attributes object: + + .. code-block:: xref + + psa_key_attributes_t pt_att = PSA_KEY_ATTRIBUTES_INIT; + +#. Set the key type, size, and policy: + + .. code-block:: xref + + psa_set_key_type(&pt_att, + PSA_KEY_TYPE_WPA3_SAE_ECC_PT(PSA_ECC_FAMILT_SECP_R1)); + psa_set_key_bits(&pt_att, 384); + psa_set_key_usage_flags(&pt_att, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&pt_att, PSA_ALG_WPA3_SAE_GDH(PSA_ALG_SHA_384)); + +#. Derive the password token key: + + .. code-block:: xref + + psa_key_id_t pt_key; + psa_key_derivation_output_key(&pt_att, &h2e_kdf, &pt_key); + psa_key_derivation_abort(&h2e_kdf); + +See :secref:`wpa3-sae-keys` for details of the key types and key derivation. + +.. _wpa3-sae-operation: + +WPA3-SAE operation +~~~~~~~~~~~~~~~~~~ + +The WPA3-SAE authentication operation follows the protocol shown in :numref:`fig-wpa3-sae`. + +.. figure:: /figure/pake/wpa3-sae.* + :name: fig-wpa3-sae + + The WPA3-SAE authentication and key confirmation protocol + + The variable names *commit-scalar*, *COMMIT-ELEMENT*, *peer-commit-scalar*, and so on, are taken from the description of WPA3-SAE in `[IEEE-802.11]` §12.4.5. + +Setup +^^^^^ + +The type of keys used to set up a PAKE multi-part operation for WPA3-SAE depends on the variant of WPA3-SAE that is required: + +* For the *Looping* variant, use a `PSA_KEY_TYPE_PASSWORD` key containing the secret password. +* For the *Hash-to-element* and *Group-dependent-hash* variants, use a `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` or `PSA_KEY_TYPE_WPA3_SAE_DH_PT` key that is derived from the secret password, as described in :secref:`wpa3-sae-passwords`. + +WPA-SAE does not assign roles to the participants, so it is not necessary to call `psa_pake_set_role()`. + +WPA-SAE requires the MAC addresses of both participants, which are provided to the PAKE multi-part operation as the user and peer identities. + +WPA-SAE does not use a context. +A call to `psa_pake_set_context()` for a WPA-SAE operation will fail with :code:`PSA_ERROR_BAD_STATE`. + +The following steps demonstrate the application code for STA-A in :numref:`fig-wpa3-sae`. +The flow for STA-B is the same as for STA-A, as WPA3-SAE is a balanced PAKE. + +1. To prepare a WPA3-SAE operation, initialize and set up a `psa_pake_operation_t` object by calling the following functions: + + .. code-block:: xref + + psa_pake_operation_t wpa3_sae = PSA_PAKE_OPERATION_INIT; + + psa_pake_setup(&wpa3_sae, pt_key, &cipher_suite); + psa_pake_set_user(&wpa3_sae, &sta_a_mac, mac_length); + psa_pake_set_peer(&wpa3_sae, &sta_b_mac, mac_length); + + See :secref:`wpa3-sae-cipher-suites` and :secref:`wpa3-sae-passwords` for details on the requirements for the cipher suite and key. + +Commit +^^^^^^ + +2. Exchange commitment values to establish shared secret and confirmation keys. + + The application can either extract the commitment values first, and then provide the commitment values that are received from the peer; or provide the peer inputs first, and then extract the outputs. + + To get the commitment values to send to STA-B, call: + + .. code-block:: xref + + // Get commit-scalar + psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_COMMIT_SCALAR, ...); + // Get COMMIT-ELEMENT + psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_KEY_SHARE, ...); + + To provide and validate the commitment values from STA-B, call: + + .. code-block:: xref + + // Set peer-commit-scalar + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_COMMIT_SCALAR, ...); + // Set PEER-COMMIT-ELEMENT + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_KEY_SHARE, ...); + +3. Provide the salt used for shared secret derivation, as described in `[IEEE-802.11]` §12.4.5.4. + For Hash-to-element and Group-dependent-hash variants, this is the list of rejected groups. + + .. code-block:: xref + + // Set salt + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_SALT, ...); + +Confirm +^^^^^^^ + +4. Exchange and verify confirmation values. + + WPA3-SAE can make multiple attempts to confirm key establishment, to mitigate frame losses that can occur. + To prevent replay of confirmation messages, each attempt generates a distinct confirmation value by including a confirmation counter value. + + The application can either extract a confirmation value first, and then provide a confirmation value received from the peer; or provide the peer input first, and then extract the output. + + To get a confirmation value to send to STA-B, the confirmation counter value :math:`send{-}confirm` must be updated before extracting the combined *send-confirm* || *confirm* value, as follows: + + .. code-block:: xref + + // Set send-confirm counter + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_SEND_CONFIRM, ...); + // Get combined send-confirm || confirm value + psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_CONFIRM, ...); + + To verify a confirmation value received from the peer, call: + + .. code-block:: xref + + // Set combined peer-send-confirm || peer-confirm value + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_CONFIRM, ...); + + .. note:: + + The application is permitted to request new confirmation values, or verify additional peer confirmation values, even after a peer confirmation value has been successfully verified. + +Extract shared secret +^^^^^^^^^^^^^^^^^^^^^ + +5. Optionally, to extract the identity of the shared secret key, PMKID, call: + + .. code-block:: xref + + // Get PMKID + psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_KEY_ID, ...); + +6. To use the shared secret, extract it as a key-derivation key. + For example, to extract a derivation key for HKDF-SHA-256: + + .. code-block:: xref + + // Set up the key attributes + psa_key_attributes_t att = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&att, PSA_KEY_TYPE_DERIVE); + psa_set_key_usage_flags(&att, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&att, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + + // Get K_shared + psa_key_id_t shared_key; + psa_pake_get_shared_key(&spake2p_p, &att, &shared_key); + +The shared secret that is produced by WPA3-SAE is pseudorandom. +Although it can be used directly as an encryption key, it is recommended to use the shared secret as an input to a key-derivation operation to produce additional cryptographic keys. + +For more information about the format of the values which are passed for each step, see :secref:`pake-steps`. + +If the validation of a commitment value fails, then the corresponding call to `psa_pake_input()` for the `PSA_PAKE_STEP_KEY_SHARE` or `PSA_PAKE_STEP_COMMIT_SCALAR` step will return :code:`PSA_ERROR_INVALID_ARGUMENT`. +If the verification of a confirmation value fails, then the corresponding call to `psa_pake_input()` for the `PSA_PAKE_STEP_CONFIRM` step will return :code:`PSA_ERROR_INVALID_SIGNATURE`. + +.. _wpa3-sae-algorithms: + +WPA3-SAE algorithms +------------------- + + +.. macro:: PSA_ALG_WPA3_SAE_FIXED + :definition: /* specification-defined value */ + + .. summary:: + Macro to build the WPA3-SAE algorithm, with fixed-sized PMK output key. + + .. versionadded:: 1.4 + + .. param:: hash_alg + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + + .. return:: + A WPA3-SAE algorithm, for the Looping or Hash-to-element variants, parameterized by a specific hash. + + Unspecified if ``hash_alg`` is not a supported hash algorithm. + + This is WPA3-SAE, as defined by :cite-title:`IEEE-802.11` §12.4, using the Looping or Hash-to-element password element derivation procedure, with fixed-sized PMK output key. + + The hash algorithm specified must match one of the supported WPA3-SAE cipher suites. + See :secref:`wpa3-sae-cipher-suites`. + + The shared secret that is produced by WPA3-SAE is pseudorandom. + Although it can be used directly as an encryption key, it is recommended to use the shared secret as an input to a key-derivation operation to produce additional cryptographic keys. + + See :secref:`pake-wpa3-sae` for the WPA3-SAE protocol flow and how to implement it with the |API|. + + .. subsection:: Compatible key types + + | `PSA_KEY_TYPE_PASSWORD` + | `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` + | `PSA_KEY_TYPE_WPA3_SAE_DH_PT` + +.. macro:: PSA_ALG_WPA3_SAE_GDH + :definition: /* specification-defined value */ + + .. summary:: + Macro to build the WPA3-SAE algorithm, with group-dependent size of the PMK output key. + + .. versionadded:: 1.4 + + .. param:: hash_alg + A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + + .. return:: + A WPA3-SAE algorithm, for the group-dependent-hash variant, parameterized by a specific hash. + + Unspecified if ``hash_alg`` is not a supported hash algorithm. + + This is WPA3-SAE, as defined by :cite-title:`IEEE-802.11` §12.4, using the hash-to-element password element derivation procedure, with group-dependent size for the PMK output key. + + The hash algorithm specified must match one of the supported WPA3-SAE cipher suites. + See :secref:`wpa3-sae-cipher-suites`. + + The shared secret that is produced by WPA3-SAE is pseudorandom. + Although it can be used directly as an encryption key, it is recommended to use the shared secret as an input to a key-derivation operation to produce additional cryptographic keys. + + See :secref:`pake-wpa3-sae` for the WPA3-SAE protocol flow and how to implement it with the |API|. + + .. subsection:: Compatible key types + + | `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` + | `PSA_KEY_TYPE_WPA3_SAE_DH_PT` + +.. macro:: PSA_ALG_IS_WPA3_SAE + :definition: /* specification-defined value */ + + .. summary:: + Whether the specified algorithm is a WPA3-SAE algorithm. + + .. versionadded:: 1.4 + + .. param:: alg + An algorithm identifier: a value of type `psa_algorithm_t`. + + .. return:: + ``1`` if ``alg`` is a WPA3-SAE algorithm, ``0`` otherwise. + This macro can return either ``0`` or ``1`` if ``alg`` is not a supported PAKE algorithm identifier. + + WPA3-SAE algorithms are constructed using :code:`PSA_ALG_WPA3_SAE_FIXED(hash_alg)` or :code:`PSA_ALG_WPA3_SAE_GDH(hash_alg)`. + +.. macro:: PSA_ALG_IS_WPA3_SAE_FIXED + :definition: /* specification-defined value */ + + .. summary:: + Whether the specified algorithm is a WPA3-SAE algorithm with a fixed-sized output key. + + .. versionadded:: 1.4 + + .. param:: alg + An algorithm identifier: a value of type `psa_algorithm_t`. + + .. return:: + ``1`` if ``alg`` is a WPA3-SAE algorithm with a fixed-sized output key, ``0`` otherwise. + This macro can return either ``0`` or ``1`` if ``alg`` is not a supported PAKE algorithm identifier. + + WPA3-SAE algorithms with a fixed-sized output key, are constructed using :code:`PSA_ALG_WPA3_SAE_FIXED(hash_alg)`. + +.. macro:: PSA_ALG_IS_WPA3_SAE_GDH + :definition: /* specification-defined value */ + + .. summary:: + Whether the specified algorithm is a WPA3-SAE algorithm with a group-dependent size for the output key. + + .. versionadded:: 1.4 + + .. param:: alg + An algorithm identifier: a value of type `psa_algorithm_t`. + + .. return:: + ``1`` if ``alg`` is a WPA3-SAE algorithm with a group-dependent size for the output key, ``0`` otherwise. + This macro can return either ``0`` or ``1`` if ``alg`` is not a supported PAKE algorithm identifier. + + WPA3-SAE algorithms with a group-dependent size for the output key, are constructed using :code:`PSA_ALG_WPA3_SAE_GDH(hash_alg)`. + +.. todo:: + + Wildcard algorithms are needed: + + 1. A WPA3-SAE password can be derived for use with different cipher suites (ECC vs DH, group size affects hash algorithm selection). Desirable to have a wildcard that does not constrain which WPA3-SAE key derivation algorithm is permitted, so that password can be persisted in key store. + 2. A WPA3-SAE password key to be used in both the key-derivation (to password token), and the `PSA_ALG_IS_WPA3_SAE_FIXED` algorithm (for looping). The same password can be used in different ways that is only discovered at runtime in the device - based on protocol interactions. Workaround: could make copies of the password? + 3. A SPA3-SAE password token key to be used in both the `PSA_ALG_IS_WPA3_SAE_FIXED` and `PSA_ALG_IS_WPA3_SAE_GDH` algorithms? - Less pressing as this differs in AKM selector, and device already has need to store multiple password tokens for a single password, based on cipher suite selection (cyclic group that is used): but GDH/FIXED does not affect PT derivation so wildcard could be helpful. diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index efd4e221..2199adac 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -490,6 +490,8 @@ The permitted values of HASH-TYPE (see :numref:`table-hash-type`) depend on the SPAKE2+ with HMAC, ``0x04``, :code:`PSA_ALG_SPAKE2P_HMAC(hash)`, ``0x0A0004hh`` :sup:`a` SPAKE2+ with CMAC, ``0x05``, :code:`PSA_ALG_SPAKE2P_CMAC(hash)`, ``0x0A0005hh`` :sup:`a` SPAKE2+ for Matter, ``0x06``, :code:`PSA_ALG_SPAKE2P_MATTER`, ``0x0A000609`` + WPA3-SAE, ``0x08``, :code:`PSA_ALG_WPA3_SAE_FIXED(hash)`, ``0x0A0008hh`` :sup:`a` + WPA3-SAE (GDH), ``0x09``, :code:`PSA_ALG_WPA3_SAE_GDH(hash)`, ``0x0A0009hh`` :sup:`a` a. ``hh`` is the HASH-TYPE for the hash algorithm, ``hash``, used to construct the key-derivation algorithm. diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index 1297e652..1dc0d02b 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -194,6 +194,15 @@ Algorithm macros (((alg) & 0x7f008000) == 0x03008000) || \ (((alg) & 0x7f008000) == 0x05008000)) + #define PSA_ALG_IS_WPA3_SAE(alg) \ + (((alg) & ~0x000001ff) == 0x0a000800) + + #define PSA_ALG_IS_WPA3_SAE_FIXED(alg) \ + (((alg) & ~0x000000ff) == 0x0a000800) + + #define PSA_ALG_IS_WPA3_SAE_GDH(alg) \ + (((alg) & ~0x000000ff) == 0x0a000900) + #define PSA_ALG_IS_WPA3_SAE_H2E(alg) \ (((alg) & ~0x000000ff) == 0x08800400) @@ -245,6 +254,12 @@ Algorithm macros #define PSA_ALG_TRUNCATED_MAC(mac_alg, mac_length) \ ((psa_algorithm_t) (((mac_alg) & ~0x003f8000) | (((mac_length) & 0x3f) << 16))) + #define PSA_ALG_WPA3_SAE_FIXED(hash_alg) \ + ((psa_algorithm_t) (0x0a000800 | ((hash_alg) & 0x000000ff))) + + #define PSA_ALG_WPA3_SAE_GDH(hash_alg) \ + ((psa_algorithm_t) (0x0a000900 | ((hash_alg) & 0x000000ff))) + #define PSA_ALG_WPA3_SAE_H2E(hash_alg) \ ((psa_algorithm_t) (0x08800400 | ((hash_alg) & 0x000000ff))) diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.pdf b/doc/crypto/figure/pake/wpa3-sae-pt.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2dc1a4d0dfeed42f25ebba25d368f6a6535db274 GIT binary patch literal 42808 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 literal 0 HcmV?d00001 diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.pdf.license b/doc/crypto/figure/pake/wpa3-sae-pt.pdf.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae-pt.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.puml b/doc/crypto/figure/pake/wpa3-sae-pt.puml new file mode 100644 index 00000000..67ac1389 --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae-pt.puml @@ -0,0 +1,31 @@ +' SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + + !include atg-spec.pumh + + participant "station (STA)" as Participant + + note over Participant: Initial information : cipher suite, //SSID//, //password// [, //password-identifier//] + + alt Hash-to-element generation of password element + + Participant -> Participant: ""psa_key_derivation_setup(WPA3_SAE_H2E)""\n""psa_key_derivation_input_bytes(SALT = SSID)""\n""psa_key_derivation_input_key(PASSWORD = password)"" + opt + Participant -> Participant: ""psa_key_derivation_input_bytes(INFO = password-identifier)"" + end + + Participant -> Participant: ""psa_key_derivation_output_key(WPA3_SAE_XX_PT)"" + + note left: Compute password token //PT// + + note over Participant: Use //PT// for authentication flow + + else Generation of the password element by looping + + note over Participant: Use //password// for authentication flow + + end + +@enduml diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.svg b/doc/crypto/figure/pake/wpa3-sae-pt.svg new file mode 100644 index 00000000..0894911e --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae-pt.svg @@ -0,0 +1 @@ +station (STA)Initial information : cipher suite,SSID,password[,password-identifier]alt[Hash-to-element generation of password element]psa_key_derivation_setup(WPA3_SAE_H2E)psa_key_derivation_input_bytes(SALT = SSID)psa_key_derivation_input_key(PASSWORD = password)optpsa_key_derivation_input_bytes(INFO = password-identifier)psa_key_derivation_output_key(WPA3_SAE_XX_PT)Compute password tokenPTUsePTfor authentication flow[Generation of the password element by looping]Usepasswordfor authentication flow \ No newline at end of file diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.svg.license b/doc/crypto/figure/pake/wpa3-sae-pt.svg.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae-pt.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/pake/wpa3-sae.pdf b/doc/crypto/figure/pake/wpa3-sae.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7e14182169ef9bf4ddafa8ec79bdac0ea39324f6 GIT binary patch literal 43054 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 literal 0 HcmV?d00001 diff --git a/doc/crypto/figure/pake/wpa3-sae.pdf.license b/doc/crypto/figure/pake/wpa3-sae.pdf.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae.pdf.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license diff --git a/doc/crypto/figure/pake/wpa3-sae.puml b/doc/crypto/figure/pake/wpa3-sae.puml new file mode 100644 index 00000000..1815ef1a --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae.puml @@ -0,0 +1,55 @@ +' SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +' SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license + +@startuml + + !include atg-spec.pumh + + participant "STA-A" as STAA + participant "STA-B" as STAB + + note over STAA, STAB + Shared information: cipher suite, //STA-A-MAC//, //STA-B-MAC// + If generating PWE by looping: //password// + If generating PWE by hash-to-element: //PT// + end note + + STAA -> STAA: ""psa_pake_setup()""\n""psa_pake_set_user(STA-A-MAC)""\n""psa_pake_set_peer(STA-B-MAC)"" + note left: Provide either //password// or //PT// to\n""psa_pake_setup()"" depending\non PWE generation method + + STAA -> STAA: ""psa_pake_output()"" for //commit-scalar//, //COMMIT-ELEMENT// + note left: Generate //rand//, //mask//; compute\n//commit-scalar//, //COMMIT-ELEMENT// + + + STAA ->> STAB: //SAE Commit// frame //(commit-scalar, COMMIT-ELEMENT)// + + STAB ->> STAA: //SAE Commit// frame //(peer-commit-scalar, PEER-COMMIT-ELEMENT)// + + STAA -> STAA: ""psa_pake_input()"" for //peer-commit-scalar//, //PEER-COMMIT-ELEMENT// + note left: Validate inputs; compute //k// + + STAA -> STAA: ""psa_pake_input()"" for //salt// + note left: Compute //SAE-KCK//, //PMK// + + loop Until //SAE Confirm// frame is successfully delivered to STA-B + STAA -> STAA: ""psa_pake_input()"" for //send-confirm// counter + + STAA -> STAA: ""psa_pake_output()"" for //send-confirm// || //confirm// + note left: Compute //confirm// + + STAA ->> STAB: //SAE Confirm// frame //(send-confirm, confirm)// + + end + + STAB ->> STAA: //SAE Confirm// frame //(peer-send-confirm, peer-confirm)// + + STAA -> STAA: ""psa_pake_input()"" for //peer-send-confirm// || //peer-confirm// + note left: Compute and validate\n//peer-verify// = //peer-confirm// + + opt + STAA -> STAA: ""psa_pake_output()"" for //PMKID// + end + + STAA -> STAA: ""psa_pake_get_shared_key()"" to extract //PMK// + +@enduml diff --git a/doc/crypto/figure/pake/wpa3-sae.svg b/doc/crypto/figure/pake/wpa3-sae.svg new file mode 100644 index 00000000..a8df3194 --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae.svg @@ -0,0 +1 @@ +STA-ASTA-BSTA-ASTA-BShared information: cipher suite,STA-A-MAC,STA-B-MACIf generating PWE by looping:passwordIf generating PWE by hash-to-element:PTpsa_pake_setup()psa_pake_set_user(STA-A-MAC)psa_pake_set_peer(STA-B-MAC)Provide eitherpasswordorPTtopsa_pake_setup()dependingon PWE generation methodpsa_pake_output()forcommit-scalar,COMMIT-ELEMENTGeneraterand,mask; computecommit-scalar,COMMIT-ELEMENTSAE Commitframe(commit-scalar, COMMIT-ELEMENT)SAE Commitframe(peer-commit-scalar, PEER-COMMIT-ELEMENT)psa_pake_input()forpeer-commit-scalar,PEER-COMMIT-ELEMENTValidate inputs; computekpsa_pake_input()forsaltComputeSAE-KCK,PMKloop[UntilSAE Confirmframe is successfully delivered to STA-B]psa_pake_input()forsend-confirmcounterpsa_pake_output()forsend-confirm||confirmComputeconfirmSAE Confirmframe(send-confirm, confirm)SAE Confirmframe(peer-send-confirm, peer-confirm)psa_pake_input()forpeer-send-confirm||peer-confirmCompute and validatepeer-verify=peer-confirmoptpsa_pake_output()forPMKIDpsa_pake_get_shared_key()to extractPMK \ No newline at end of file diff --git a/doc/crypto/figure/pake/wpa3-sae.svg.license b/doc/crypto/figure/pake/wpa3-sae.svg.license new file mode 100644 index 00000000..b66dc6de --- /dev/null +++ b/doc/crypto/figure/pake/wpa3-sae.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +SPDX-License-Identifier: CC-BY-SA-4.0 AND LicenseRef-Patent-license From 9be3bf408471e145c3eeb3bbae7b0d58822fa522 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 5 Sep 2025 16:39:36 +0100 Subject: [PATCH 07/17] Update history and reference API definition for WPA3-SAE --- doc/crypto/api.db/psa/crypto.h | 20 ++++++++++++++++++++ doc/crypto/appendix/history.rst | 14 +++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index d7f35295..64408c19 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -136,6 +136,10 @@ typedef struct psa_custom_key_parameters_t { #define PSA_ALG_IS_TLS12_PRF(alg) /* specification-defined value */ #define PSA_ALG_IS_TLS12_PSK_TO_MS(alg) /* specification-defined value */ #define PSA_ALG_IS_WILDCARD(alg) /* specification-defined value */ +#define PSA_ALG_IS_WPA3_SAE(alg) /* specification-defined value */ +#define PSA_ALG_IS_WPA3_SAE_FIXED(alg) /* specification-defined value */ +#define PSA_ALG_IS_WPA3_SAE_GDH(alg) /* specification-defined value */ +#define PSA_ALG_IS_WPA3_SAE_H2E(alg) /* specification-defined value */ #define PSA_ALG_IS_XOF(alg) /* specification-defined value */ #define PSA_ALG_JPAKE(hash_alg) /* specification-defined value */ #define PSA_ALG_KEY_AGREEMENT(ka_alg, kdf_alg) \ @@ -186,6 +190,9 @@ typedef struct psa_custom_key_parameters_t { #define PSA_ALG_TLS12_PSK_TO_MS(hash_alg) /* specification-defined value */ #define PSA_ALG_TRUNCATED_MAC(mac_alg, mac_length) \ /* specification-defined value */ +#define PSA_ALG_WPA3_SAE_FIXED(hash_alg) /* specification-defined value */ +#define PSA_ALG_WPA3_SAE_GDH(hash_alg) /* specification-defined value */ +#define PSA_ALG_WPA3_SAE_H2E(hash_alg) /* specification-defined value */ #define PSA_ALG_XCHACHA20_POLY1305 ((psa_algorithm_t)0x05100600) #define PSA_ALG_XTS ((psa_algorithm_t)0x0440ff00) #define PSA_ASYMMETRIC_DECRYPT_OUTPUT_MAX_SIZE \ @@ -219,6 +226,7 @@ typedef struct psa_custom_key_parameters_t { #define PSA_CRYPTO_API_VERSION_MAJOR 1 #define PSA_CRYPTO_API_VERSION_MINOR 4 #define PSA_CUSTOM_KEY_PARAMETERS_INIT { 0 } +#define PSA_DH_FAMILY_RFC3526 ((psa_dh_family_t) 0x05) #define PSA_DH_FAMILY_RFC7919 ((psa_dh_family_t) 0x03) #define PSA_ECC_FAMILY_BRAINPOOL_P_R1 ((psa_ecc_family_t) 0x30) #define PSA_ECC_FAMILY_FRP ((psa_ecc_family_t) 0x33) @@ -317,6 +325,8 @@ typedef struct psa_custom_key_parameters_t { #define PSA_KEY_TYPE_IS_SPAKE2P_PUBLIC_KEY(type) \ /* specification-defined value */ #define PSA_KEY_TYPE_IS_UNSTRUCTURED(type) /* specification-defined value */ +#define PSA_KEY_TYPE_IS_WPA3_SAE_DH(type) /* specification-defined value */ +#define PSA_KEY_TYPE_IS_WPA3_SAE_ECC(type) /* specification-defined value */ #define PSA_KEY_TYPE_KEY_PAIR_OF_PUBLIC_KEY(type) \ /* specification-defined value */ #define PSA_KEY_TYPE_NONE ((psa_key_type_t)0x0000) @@ -333,6 +343,12 @@ typedef struct psa_custom_key_parameters_t { #define PSA_KEY_TYPE_SPAKE2P_KEY_PAIR(curve) /* specification-defined value */ #define PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY(curve) \ /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_DH_GET_FAMILY(type) \ + /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_DH_PT(group) /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_ECC_GET_FAMILY(type) \ + /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_ECC_PT(curve) /* specification-defined value */ #define PSA_KEY_TYPE_XCHACHA20 ((psa_key_type_t)0x2007) #define PSA_KEY_USAGE_CACHE ((psa_key_usage_t)0x00000004) #define PSA_KEY_USAGE_COPY ((psa_key_usage_t)0x00000002) @@ -376,8 +392,12 @@ typedef struct psa_custom_key_parameters_t { #define PSA_PAKE_ROLE_NONE ((psa_pake_role_t)0x00) #define PSA_PAKE_ROLE_SECOND ((psa_pake_role_t)0x02) #define PSA_PAKE_ROLE_SERVER ((psa_pake_role_t)0x12) +#define PSA_PAKE_STEP_COMMIT_SCALAR ((psa_pake_step_t)0x06) #define PSA_PAKE_STEP_CONFIRM ((psa_pake_step_t)0x04) +#define PSA_PAKE_STEP_CONFIRM_COUNT ((psa_pake_step_t)0x07) +#define PSA_PAKE_STEP_KEY_ID ((psa_pake_step_t)0x08) #define PSA_PAKE_STEP_KEY_SHARE ((psa_pake_step_t)0x01) +#define PSA_PAKE_STEP_SALT ((psa_pake_step_t)0x05) #define PSA_PAKE_STEP_ZK_PROOF ((psa_pake_step_t)0x03) #define PSA_PAKE_STEP_ZK_PUBLIC ((psa_pake_step_t)0x02) #define PSA_PAKE_UNCONFIRMED_KEY 1 diff --git a/doc/crypto/appendix/history.rst b/doc/crypto/appendix/history.rst index 8f36ac0e..a06cdf99 100644 --- a/doc/crypto/appendix/history.rst +++ b/doc/crypto/appendix/history.rst @@ -14,6 +14,8 @@ This section provides the detailed changes made between published version of the Changes between *1.3.2* and *1.4.0* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:issue:`TBD` + Changes to the API ~~~~~~~~~~~~~~~~~~ @@ -24,6 +26,15 @@ Changes to the API * Added support for key wrapping using key-wrapping algorithms. See :secref:`key-wrapping`. +* Added support for the WPA3-SAE PAKE: + + - Add `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` and `PSA_KEY_TYPE_WPA3_SAE_DH_PT` key types for WPA3-SAE password tokens. + - Added the `PSA_ALG_WPA3_SAE_H2E()` KDF for generating a WPA3-SAE password token from a password. + - Added WPA3-SAE PAKE algorithms, `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()`. + - Added finite field Diffie-Hellman family `PSA_DH_FAMILY_RFC3526`, which provides cyclic groups used for WPA3-SAE. + + See :secref:`pake-wpa3-sae`. + Clarifications and fixes ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -32,7 +43,8 @@ Clarifications and fixes Other changes ~~~~~~~~~~~~~ -* TBD +* Reorganised the chapter on key types. + See :secref:`key-types`. Changes between *1.3.1* and *1.3.2* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From da81724660374a1f669e74eac061a137088217da Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Thu, 2 Oct 2025 15:09:39 +0100 Subject: [PATCH 08/17] Fix heading levels --- doc/crypto/api/keys/types.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index 9a58f005..bfe04540 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -964,8 +964,8 @@ Symmetric cryptographic keys .. _structured-keys: -Structured keys ---------------- +Structured key types +-------------------- .. _wpa3-sae-keys: @@ -1637,7 +1637,7 @@ Diffie Hellman keys .. _spake2p-keys: SPAKE2+ keys ------------- +~~~~~~~~~~~~ .. macro:: PSA_KEY_TYPE_SPAKE2P_KEY_PAIR :definition: /* specification-defined value */ From b2ce086caf811b570d0d253a5498d49b064ce134 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Thu, 2 Oct 2025 15:10:06 +0100 Subject: [PATCH 09/17] Define export format for WPA3-SAE password tokens --- doc/crypto/api/keys/types.rst | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index bfe04540..6aa6e2b6 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -1005,18 +1005,28 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr .. subsection:: Key format - The data format for import and export of a WPA3-SAE password token is :scterm:`implementation defined`. + The password token is an element of the elliptic curve group, with value :math:`(x,y)`. - .. rationale:: + The data format for import and export of the password token is the concatenation of: + + * :math:`x` encoded as a big-endian :math:`m`-byte string; + * :math:`y` encoded as a big-endian :math:`m`-byte string. + + For an elliptic curve over :math:`\mathbb{F}_p`, :math:`m` is the integer for which :math:`2^{8m-1} \leq p < 2^{8m}`. + + .. note:: + + This is the same format as the one used for group elements in the commit phase of the WPA3-SAE protocol, defined in `[IEEE-802.11]` §12.4.7.2.4. - :issue:`No export/import format is needed for this key type: password tokens should ALWAYS be derived from the password?` + .. rationale:: - .. todo:: Decide if we need to define the format for WPA3-SAE password token keys + There is no protocol use case for exporting or importing the password token. + However, the ability to extract a derived token, or import a known-value token, can help development and testing of an implementation. .. subsection:: Key derivation A elliptic curve-based WPA3-SAE password token can only be derived using the `PSA_ALG_WPA3_SAE_H2E` algorithm. - The call to `psa_key_derivation_output_key()` uses the method defined in :cite-title:`IEEE-802.11` §12.4.4.2.3 to generate the key value. + The call to `psa_key_derivation_output_key()` uses the method defined in `[IEEE-802.11]` §12.4.4.2.3 to generate the key value. .. macro:: PSA_KEY_TYPE_WPA3_SAE_DH_PT :definition: /* specification-defined value */ @@ -1045,18 +1055,23 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr .. subsection:: Key format - The data format for import and export of a WPA3-SAE password token is :scterm:`implementation defined`. + The password token is a finite-field group element :math:`y \in [1, p - 1]`, where :math:`p` is the group's prime modulus. - .. rationale:: + The data format for import and export of the password token is :math:`y` encoded as a big-endian :math:`m`-byte string, where :math:`m` is the integer for which :math:`2^{8m-1} \leq p < 2^{8m}`. + + .. note:: + + This is the same format as the one used for group elements in the commit phase of the WPA3-SAE protocol, defined in `[IEEE-802.11]` §12.4.7.2.4. - :issue:`No export/import format is needed for this key type: password tokens should ALWAYS be derived from the password?` + .. rationale:: - .. todo:: Decide if we need to define the format for WPA3-SAE password token keys + There is no protocol use case for exporting or importing the password token. + However, the ability to extract a derived token, or import a known-value token, can help development and testing of an implementation. .. subsection:: Key derivation A finite field-based WPA3-SAE password token can only be derived using the `PSA_ALG_WPA3_SAE_H2E` algorithm. - The call to `psa_key_derivation_output_key()` uses the method defined in :cite-title:`IEEE-802.11` §12.4.4.3.3 to generate the key value. + The call to `psa_key_derivation_output_key()` uses the method defined in `[IEEE-802.11]` §12.4.4.3.3 to generate the key value. .. macro:: PSA_KEY_TYPE_IS_WPA3_SAE_ECC :definition: /* specification-defined value */ From 05eeae3f3748266f9d75be5cb06f723d478a1fb8 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Thu, 2 Oct 2025 16:16:00 +0100 Subject: [PATCH 10/17] Change commit steps for WPA3-SAE Combine the scalar and group-element values into a single PAKE step for the WPA3-SAE algorithm. --- doc/crypto/api.db/psa/crypto.h | 2 +- doc/crypto/api/ops/pake.rst | 49 ++++++++------------------- doc/crypto/figure/pake/wpa3-sae.pdf | Bin 43054 -> 43068 bytes doc/crypto/figure/pake/wpa3-sae.puml | 4 +-- doc/crypto/figure/pake/wpa3-sae.svg | 2 +- 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index 64408c19..d5086af1 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -392,7 +392,7 @@ typedef struct psa_custom_key_parameters_t { #define PSA_PAKE_ROLE_NONE ((psa_pake_role_t)0x00) #define PSA_PAKE_ROLE_SECOND ((psa_pake_role_t)0x02) #define PSA_PAKE_ROLE_SERVER ((psa_pake_role_t)0x12) -#define PSA_PAKE_STEP_COMMIT_SCALAR ((psa_pake_step_t)0x06) +#define PSA_PAKE_STEP_COMMIT ((psa_pake_step_t)0x06) #define PSA_PAKE_STEP_CONFIRM ((psa_pake_step_t)0x04) #define PSA_PAKE_STEP_CONFIRM_COUNT ((psa_pake_step_t)0x07) #define PSA_PAKE_STEP_KEY_ID ((psa_pake_step_t)0x08) diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index 8e80c201..9637215f 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -576,16 +576,6 @@ PAKE step types For information regarding how the group is determined, consult the documentation `PSA_PAKE_PRIMITIVE()`. - .. todo:: - - Decide on how to handle COMMIT-ELEMENT format in WPA3-SAE. - This is an element in the group, using a big-endian scalar value for FFDH (== public key format (I think - no truncation)), and a big-endian encoding of the {x,y} coordinates for ECC. The latter is close to public key format, but there is no :code:```0x04``` prefix. - - 1. Use a WPA3-SAE-specific PAKE step for the COMMIT-ELEMENT, and specify the format (by reference to 802.11). - 2. Make the format of KEY_SHARE step algorithm-specific, and describe it separately for J-PAKE, SPAKE2+, and WPA3-SAE. - 3. Make a COMMIT step for WPA3-SAE that concatenates both the scalar and ELEMENT values (means application has to know the size of the scalar element to split/combine this correctly, and cannot read/write this directly from SAE frame structures in memory). - 4. Stick with KEY_SHARE and the 'public key format', making the application deal with the extra prefix (prevents application directly reading from/writing to the SAE frame structures in memory). - .. macro:: PSA_PAKE_STEP_ZK_PUBLIC :definition: ((psa_pake_step_t)0x02) @@ -653,31 +643,26 @@ PAKE step types * For a WPA3-SAE algorithm, a salt value must be provided as defined in `[IEEE-802.11]` §12.4.5.4. See :secref:`wpa3-sae-operation`. -.. - Oberon's approach - - /** The WPA3-SAE commit step. - * - * The format for both input and output at this step is a 2 byte number - * specifying the group used followed by a scalar and an element of the - * specified group. - */ - #define PSA_PAKE_STEP_COMMIT ((psa_pake_step_t)0x06) - -.. macro:: PSA_PAKE_STEP_COMMIT_SCALAR +.. macro:: PSA_PAKE_STEP_COMMIT :definition: ((psa_pake_step_t)0x06) .. summary:: - A scalar value being sent to or received from a PAKE participant. + A commitment value being sent to or received from a PAKE participant. .. versionadded:: 1.4 This input and output is used during the key exchange phase of a PAKE protocol. The use of this step, and format of the value depends on the algorithm and cipher suite: - * For a WPA3-SAE algorithm, the format for input at this step is a string that encodes the *commit-scalar* or *peer-commit-scalar* values, as defined in `[IEEE-802.11]` §12.4.7.3. + * For a WPA3-SAE algorithm, the format for input and output at this step is a concatenation of the *commit-scalar* and *COMMIT-ELEMENT* values, as defined in `[IEEE-802.11]` §12.4.7.3. + See :secref:`wpa3-sae-operation`. + .. note:: + + These values are adjacent in the WPA3-SAE Authentication frame defined in `[IEEE-802.11]` §9.3.3.11. + The concatenated value can be output directly to, or input directly from, the frame buffer. + .. macro:: PSA_PAKE_STEP_CONFIRM_COUNT :definition: ((psa_pake_step_t)0x07) @@ -830,7 +815,7 @@ Multi-part PAKE operations 1. Allocate a PAKE operation object which will be passed to all the functions listed here. #. Initialize the operation object with one of the methods described in the documentation for `psa_pake_operation_t`. For example, using `PSA_PAKE_OPERATION_INIT`. - #. Call `psa_pake_setup()` to specify the cipher suite. + #. Call `psa_pake_setup()` to specify the cipher suite and provide the password or password-derived key. #. Call ``psa_pake_set_xxx()`` functions on the operation to complete the setup. The exact sequence of ``psa_pake_set_xxx()`` functions that needs to be called depends on the algorithm in use. @@ -2329,19 +2314,15 @@ Commit .. code-block:: xref - // Get commit-scalar - psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_COMMIT_SCALAR, ...); - // Get COMMIT-ELEMENT - psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_KEY_SHARE, ...); + // Get commit-scalar || COMMIT-ELEMENT + psa_pake_output(&wpa3_sae, PSA_PAKE_STEP_COMMIT, ...); To provide and validate the commitment values from STA-B, call: .. code-block:: xref - // Set peer-commit-scalar - psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_COMMIT_SCALAR, ...); - // Set PEER-COMMIT-ELEMENT - psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_KEY_SHARE, ...); + // Set peer-commit-scalar || PEER-COMMIT-ELEMENT + psa_pake_input(&wpa3_sae, PSA_PAKE_STEP_COMMIT, ...); 3. Provide the salt used for shared secret derivation, as described in `[IEEE-802.11]` §12.4.5.4. For Hash-to-element and Group-dependent-hash variants, this is the list of rejected groups. @@ -2411,7 +2392,7 @@ Although it can be used directly as an encryption key, it is recommended to use For more information about the format of the values which are passed for each step, see :secref:`pake-steps`. -If the validation of a commitment value fails, then the corresponding call to `psa_pake_input()` for the `PSA_PAKE_STEP_KEY_SHARE` or `PSA_PAKE_STEP_COMMIT_SCALAR` step will return :code:`PSA_ERROR_INVALID_ARGUMENT`. +If the validation of a commitment value fails, then the corresponding call to `psa_pake_input()` for the `PSA_PAKE_STEP_COMMIT` step will return :code:`PSA_ERROR_INVALID_ARGUMENT`. If the verification of a confirmation value fails, then the corresponding call to `psa_pake_input()` for the `PSA_PAKE_STEP_CONFIRM` step will return :code:`PSA_ERROR_INVALID_SIGNATURE`. .. _wpa3-sae-algorithms: diff --git a/doc/crypto/figure/pake/wpa3-sae.pdf b/doc/crypto/figure/pake/wpa3-sae.pdf index 7e14182169ef9bf4ddafa8ec79bdac0ea39324f6..a8f84edfa60aa7f475eabbbe77c67ec094dd246f 100644 GIT binary patch delta 10995 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%0DC1CyP(mp7dV`rHXHCi?mI(i3W@?wE} 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 diff --git a/doc/crypto/figure/pake/wpa3-sae.puml b/doc/crypto/figure/pake/wpa3-sae.puml index 1815ef1a..4718e614 100644 --- a/doc/crypto/figure/pake/wpa3-sae.puml +++ b/doc/crypto/figure/pake/wpa3-sae.puml @@ -17,7 +17,7 @@ STAA -> STAA: ""psa_pake_setup()""\n""psa_pake_set_user(STA-A-MAC)""\n""psa_pake_set_peer(STA-B-MAC)"" note left: Provide either //password// or //PT// to\n""psa_pake_setup()"" depending\non PWE generation method - STAA -> STAA: ""psa_pake_output()"" for //commit-scalar//, //COMMIT-ELEMENT// + STAA -> STAA: ""psa_pake_output()"" for //commit-scalar// || //COMMIT-ELEMENT// note left: Generate //rand//, //mask//; compute\n//commit-scalar//, //COMMIT-ELEMENT// @@ -25,7 +25,7 @@ STAB ->> STAA: //SAE Commit// frame //(peer-commit-scalar, PEER-COMMIT-ELEMENT)// - STAA -> STAA: ""psa_pake_input()"" for //peer-commit-scalar//, //PEER-COMMIT-ELEMENT// + STAA -> STAA: ""psa_pake_input()"" for //peer-commit-scalar// || //PEER-COMMIT-ELEMENT// note left: Validate inputs; compute //k// STAA -> STAA: ""psa_pake_input()"" for //salt// diff --git a/doc/crypto/figure/pake/wpa3-sae.svg b/doc/crypto/figure/pake/wpa3-sae.svg index a8df3194..f10f160b 100644 --- a/doc/crypto/figure/pake/wpa3-sae.svg +++ b/doc/crypto/figure/pake/wpa3-sae.svg @@ -1 +1 @@ -STA-ASTA-BSTA-ASTA-BShared information: cipher suite,STA-A-MAC,STA-B-MACIf generating PWE by looping:passwordIf generating PWE by hash-to-element:PTpsa_pake_setup()psa_pake_set_user(STA-A-MAC)psa_pake_set_peer(STA-B-MAC)Provide eitherpasswordorPTtopsa_pake_setup()dependingon PWE generation methodpsa_pake_output()forcommit-scalar,COMMIT-ELEMENTGeneraterand,mask; computecommit-scalar,COMMIT-ELEMENTSAE Commitframe(commit-scalar, COMMIT-ELEMENT)SAE Commitframe(peer-commit-scalar, PEER-COMMIT-ELEMENT)psa_pake_input()forpeer-commit-scalar,PEER-COMMIT-ELEMENTValidate inputs; computekpsa_pake_input()forsaltComputeSAE-KCK,PMKloop[UntilSAE Confirmframe is successfully delivered to STA-B]psa_pake_input()forsend-confirmcounterpsa_pake_output()forsend-confirm||confirmComputeconfirmSAE Confirmframe(send-confirm, confirm)SAE Confirmframe(peer-send-confirm, peer-confirm)psa_pake_input()forpeer-send-confirm||peer-confirmCompute and validatepeer-verify=peer-confirmoptpsa_pake_output()forPMKIDpsa_pake_get_shared_key()to extractPMK \ No newline at end of file +STA-ASTA-BSTA-ASTA-BShared information: cipher suite,STA-A-MAC,STA-B-MACIf generating PWE by looping:passwordIf generating PWE by hash-to-element:PTpsa_pake_setup()psa_pake_set_user(STA-A-MAC)psa_pake_set_peer(STA-B-MAC)Provide eitherpasswordorPTtopsa_pake_setup()dependingon PWE generation methodpsa_pake_output()forcommit-scalar||COMMIT-ELEMENTGeneraterand,mask; computecommit-scalar,COMMIT-ELEMENTSAE Commitframe(commit-scalar, COMMIT-ELEMENT)SAE Commitframe(peer-commit-scalar, PEER-COMMIT-ELEMENT)psa_pake_input()forpeer-commit-scalar||PEER-COMMIT-ELEMENTValidate inputs; computekpsa_pake_input()forsaltComputeSAE-KCK,PMKloop[UntilSAE Confirmframe is successfully delivered to STA-B]psa_pake_input()forsend-confirmcounterpsa_pake_output()forsend-confirm||confirmComputeconfirmSAE Confirmframe(send-confirm, confirm)SAE Confirmframe(peer-send-confirm, peer-confirm)psa_pake_input()forpeer-send-confirm||peer-confirmCompute and validatepeer-verify=peer-confirmoptpsa_pake_output()forPMKIDpsa_pake_get_shared_key()to extractPMK \ No newline at end of file From c6b50520e9c4d7a5d71f017cd06ff07cc30dc1d9 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 11:23:08 +0100 Subject: [PATCH 11/17] Fix some spelling errors --- doc/crypto/api.db/psa/crypto.h | 2 +- doc/crypto/api/keys/types.rst | 4 ++-- doc/crypto/api/ops/pake.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index d5086af1..34ef9733 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -183,7 +183,7 @@ typedef struct psa_custom_key_parameters_t { /* specification-defined value */ #define PSA_ALG_SPAKE2P_CMAC(hash_alg) /* specification-defined value */ #define PSA_ALG_SPAKE2P_HMAC(hash_alg) /* specification-defined value */ -#define PSA_ALG_SPAKE2P_MATTER ((psa_algoirithm_t)0x0A000609) +#define PSA_ALG_SPAKE2P_MATTER ((psa_algorithm_t)0x0A000609) #define PSA_ALG_STREAM_CIPHER ((psa_algorithm_t)0x04800100) #define PSA_ALG_TLS12_ECJPAKE_TO_PMS ((psa_algorithm_t)0x08000609) #define PSA_ALG_TLS12_PRF(hash_alg) /* specification-defined value */ diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index 6aa6e2b6..18aa8778 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -17,7 +17,7 @@ Key type encoding .. summary:: Encoding of a key type. - This is a structured bitfield that identifies the category and type of key. The range of key type values is divided as follows: + This is a structured bit field that identifies the category and type of key. The range of key type values is divided as follows: :code:`PSA_KEY_TYPE_NONE == 0` Reserved as an invalid key type. @@ -973,7 +973,7 @@ WPA3-SAE password tokens ~~~~~~~~~~~~~~~~~~~~~~~~ The WPA3-SAE PAKE defines two techniques for generating the password element used during the PAKE protocol. -The recommended hash-2-curve method is used to generate an intermediate password token, which is an element of the cyclic group used in the PAKE ciphersuite. +The recommended hash-2-curve method is used to generate an intermediate password token, which is an element of the cyclic group used in the PAKE cipher suite. The password token can be stored as a key object, and later used in the PAKE operation when performing the WPA3-SAE protocol. WPA3-SAE password tokens are defined for both elliptic curve and finite field groups. diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index 9637215f..b59c1afa 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -789,7 +789,7 @@ Multi-part PAKE operations .. retval:: PSA_ERROR_INVALID_HANDLE ``password_key`` is not a valid key identifier. .. retval:: PSA_ERROR_NOT_PERMITTED - ``psssword_key`` does not have the `PSA_KEY_USAGE_DERIVE` flag, or it does not permit the algorithm in ``cipher_suite``. + ``password_key`` does not have the `PSA_KEY_USAGE_DERIVE` flag, or it does not permit the algorithm in ``cipher_suite``. .. retval:: PSA_ERROR_INVALID_ARGUMENT The following conditions can result in this error: @@ -1974,7 +1974,7 @@ SPAKE2+ algorithms | `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY` (verification only) .. macro:: PSA_ALG_SPAKE2P_MATTER - :definition: ((psa_algoirithm_t)0x0A000609) + :definition: ((psa_algorithm_t)0x0A000609) .. summary:: The SPAKE2+ algorithm, as used by the Matter v1 specification. From db9b344c9e68a30a77e14f44524e5baad88ead72 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 11:24:27 +0100 Subject: [PATCH 12/17] Remove the _PT suffix on the WPA3-SAE key types --- doc/crypto/api.db/psa/crypto.h | 4 ++-- doc/crypto/api/keys/types.rst | 12 ++++-------- doc/crypto/api/ops/key-derivation.rst | 2 +- doc/crypto/api/ops/pake.rst | 14 +++++++------- doc/crypto/appendix/encodings.rst | 4 ++-- doc/crypto/appendix/history.rst | 2 +- doc/crypto/appendix/specdef_values.rst | 4 ++-- doc/crypto/figure/pake/wpa3-sae-pt.pdf | Bin 42808 -> 33711 bytes doc/crypto/figure/pake/wpa3-sae-pt.puml | 2 +- doc/crypto/figure/pake/wpa3-sae-pt.svg | 2 +- 10 files changed, 21 insertions(+), 25 deletions(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index 34ef9733..db97ae98 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -343,12 +343,12 @@ typedef struct psa_custom_key_parameters_t { #define PSA_KEY_TYPE_SPAKE2P_KEY_PAIR(curve) /* specification-defined value */ #define PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY(curve) \ /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_DH(group) /* specification-defined value */ #define PSA_KEY_TYPE_WPA3_SAE_DH_GET_FAMILY(type) \ /* specification-defined value */ -#define PSA_KEY_TYPE_WPA3_SAE_DH_PT(group) /* specification-defined value */ +#define PSA_KEY_TYPE_WPA3_SAE_ECC(curve) /* specification-defined value */ #define PSA_KEY_TYPE_WPA3_SAE_ECC_GET_FAMILY(type) \ /* specification-defined value */ -#define PSA_KEY_TYPE_WPA3_SAE_ECC_PT(curve) /* specification-defined value */ #define PSA_KEY_TYPE_XCHACHA20 ((psa_key_type_t)0x2007) #define PSA_KEY_USAGE_CACHE ((psa_key_usage_t)0x00000004) #define PSA_KEY_USAGE_COPY ((psa_key_usage_t)0x00000002) diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index 18aa8778..ac05372c 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -111,7 +111,7 @@ Elliptic curve families These keys are used in various asymmetric signature, key-encapsulation, and key-agreement algorithms. * SPAKE2+ keys using the `PSA_KEY_TYPE_SPAKE2P_KEY_PAIR()` or `PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY()`. These keys are used in the SPAKE2+ PAKE algorithms. - * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_ECC_PT()`. + * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_ECC()`. These keys are used in the WPA3-SAE PAKE algorithms. Elliptic curve family identifiers are also used to construct PAKE primitives for cipher suites based on elliptic curve groups. @@ -309,7 +309,7 @@ Finite field Diffie-Hellman families * Diffie-Hellman keys using `PSA_KEY_TYPE_DH_KEY_PAIR()` or `PSA_KEY_TYPE_DH_PUBLIC_KEY()`. These keys are used in the FFDH key-agreement algorithm. - * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_DH_PT()`. + * WPA3-SAE password tokens using `PSA_KEY_TYPE_WPA3_SAE_DH()`. These keys are used in the WPA3-SAE PAKE algorithms. Finite field Diffie-Hellman group identifiers are also used to construct PAKE primitives for cipher suites based on finite field groups. @@ -978,7 +978,7 @@ The password token can be stored as a key object, and later used in the PAKE ope WPA3-SAE password tokens are defined for both elliptic curve and finite field groups. -.. macro:: PSA_KEY_TYPE_WPA3_SAE_ECC_PT +.. macro:: PSA_KEY_TYPE_WPA3_SAE_ECC :definition: /* specification-defined value */ .. summary:: @@ -989,8 +989,6 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr .. param:: curve A value of type `psa_ecc_family_t` that identifies the elliptic curve family to be used. - .. todo:: Consider removing the _PT suffix from the WPA3-SAE key types - the only keys are the password token keys, so not sure the suffix is helpful? - The bit-size of a WPA3-SAE password token is the bit size associated with the specific curve within the elliptic curve family. See the documentation of the elliptic curve family for details. @@ -1028,7 +1026,7 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr A elliptic curve-based WPA3-SAE password token can only be derived using the `PSA_ALG_WPA3_SAE_H2E` algorithm. The call to `psa_key_derivation_output_key()` uses the method defined in `[IEEE-802.11]` §12.4.4.2.3 to generate the key value. -.. macro:: PSA_KEY_TYPE_WPA3_SAE_DH_PT +.. macro:: PSA_KEY_TYPE_WPA3_SAE_DH :definition: /* specification-defined value */ .. summary:: @@ -1039,8 +1037,6 @@ WPA3-SAE password tokens are defined for both elliptic curve and finite field gr .. param:: group A value of type `psa_dh_family_t` that identifies the finite field Diffie-Hellman family to be used. - .. todo:: Consider removing the _PT suffix from the WPA3-SAE key types - the only keys are the password token keys, so not sure the suffix is helpful? - The bit-size of the WPA3-SAE password token is the bit size associated with the specific group within the finite field Diffie-Hellman family. See the documentation of the selected Diffie-Hellman family for details. diff --git a/doc/crypto/api/ops/key-derivation.rst b/doc/crypto/api/ops/key-derivation.rst index 55ca981c..48d6bd23 100644 --- a/doc/crypto/api/ops/key-derivation.rst +++ b/doc/crypto/api/ops/key-derivation.rst @@ -382,7 +382,7 @@ Key-derivation algorithms It is optional. This key derivation algorithm can only be used to derive and output a single key, which is obtained by a call to `psa_key_derivation_output_key()`. - The output has to be read as a key of type `PSA_KEY_TYPE_WPA3_SAE_DH_PT` or `PSA_KEY_TYPE_WPA3_SAE_ECC_PT`. + The output has to be read as a key of type `PSA_KEY_TYPE_WPA3_SAE_DH` or `PSA_KEY_TYPE_WPA3_SAE_ECC`. Requesting any other key type, or calling `psa_key_derivation_output_bytes()`, returns an error status. The ``hash_alg`` parameter to `PSA_ALG_WPA3_SAE_H2E()` determines the hash function used for the derivation. diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index b59c1afa..3fafc584 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -2209,7 +2209,7 @@ To use the hash-to-element method: #. A WPA3-SAE password token (PT) is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. The `PSA_ALG_WPA3_SAE_H2E()` algorithm is parameterized by the hash used in the required WPA3-SAE cipher suite. - The PT is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC_PT()` or `PSA_KEY_TYPE_WPA3_SAE_DH_PT()`. + The PT is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC()` or `PSA_KEY_TYPE_WPA3_SAE_DH()`. The key type is parameterized by the elliptic curve or finite field Diffie-Hellman group used in the required WPA3-SAE cipher suite. The PT key must be protected at least as well as the password. @@ -2244,7 +2244,7 @@ The selected cipher suite in the example is IANA Group 20: ECC using secp384r1, .. code-block:: xref psa_set_key_type(&pt_att, - PSA_KEY_TYPE_WPA3_SAE_ECC_PT(PSA_ECC_FAMILT_SECP_R1)); + PSA_KEY_TYPE_WPA3_SAE_ECC(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&pt_att, 384); psa_set_key_usage_flags(&pt_att, PSA_KEY_USAGE_DERIVE); psa_set_key_algorithm(&pt_att, PSA_ALG_WPA3_SAE_GDH(PSA_ALG_SHA_384)); @@ -2279,7 +2279,7 @@ Setup The type of keys used to set up a PAKE multi-part operation for WPA3-SAE depends on the variant of WPA3-SAE that is required: * For the *Looping* variant, use a `PSA_KEY_TYPE_PASSWORD` key containing the secret password. -* For the *Hash-to-element* and *Group-dependent-hash* variants, use a `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` or `PSA_KEY_TYPE_WPA3_SAE_DH_PT` key that is derived from the secret password, as described in :secref:`wpa3-sae-passwords`. +* For the *Hash-to-element* and *Group-dependent-hash* variants, use a `PSA_KEY_TYPE_WPA3_SAE_ECC` or `PSA_KEY_TYPE_WPA3_SAE_DH` key that is derived from the secret password, as described in :secref:`wpa3-sae-passwords`. WPA-SAE does not assign roles to the participants, so it is not necessary to call `psa_pake_set_role()`. @@ -2430,8 +2430,8 @@ WPA3-SAE algorithms .. subsection:: Compatible key types | `PSA_KEY_TYPE_PASSWORD` - | `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` - | `PSA_KEY_TYPE_WPA3_SAE_DH_PT` + | `PSA_KEY_TYPE_WPA3_SAE_ECC` + | `PSA_KEY_TYPE_WPA3_SAE_DH` .. macro:: PSA_ALG_WPA3_SAE_GDH :definition: /* specification-defined value */ @@ -2461,8 +2461,8 @@ WPA3-SAE algorithms .. subsection:: Compatible key types - | `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` - | `PSA_KEY_TYPE_WPA3_SAE_DH_PT` + | `PSA_KEY_TYPE_WPA3_SAE_ECC` + | `PSA_KEY_TYPE_WPA3_SAE_DH` .. macro:: PSA_ALG_IS_WPA3_SAE :definition: /* specification-defined value */ diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index 2199adac..65976e87 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -676,7 +676,7 @@ The defined values for ECC-FAMILY and P are shown in :numref:`table-wpa3-sae-ecc SECP R1, 0x09, 0, `PSA_ECC_FAMILY_SECP_R1`, ``0x3292`` Brainpool-P R1, 0x18, 0, `PSA_ECC_FAMILY_BRAINPOOL_P_R1`, ``0x32b0`` -a. The elliptic curve family values defined in the API also include the parity bit. The password token key type value is constructed from the elliptic curve family using :code:`PSA_KEY_TYPE_WPA3_SAE_ECC_PT(family)`. +a. The elliptic curve family values defined in the API also include the parity bit. The password token key type value is constructed from the elliptic curve family using :code:`PSA_KEY_TYPE_WPA3_SAE_ECC(family)`. .. rubric:: WPA3-SAE password tokens using finite fields @@ -700,7 +700,7 @@ RFC3526 defines a set of FF groups that are recommended for use with WPA3-SAE (t WPA3-SAE suite, DH-FAMILY, P, DH family :sup:`a`, Key value RFC3526, 0x02, 1, `PSA_DH_FAMILY_RFC3526`, ``0x3305`` -a. The finite field Diffie Hellman family values defined in the API also include the parity bit. The password token key type value is constructed from the finite field Diffie Hellman family using :code:`PSA_KEY_TYPE_WPA3_SAE_DH_PT(family)`. +a. The finite field Diffie Hellman family values defined in the API also include the parity bit. The password token key type value is constructed from the finite field Diffie Hellman family using :code:`PSA_KEY_TYPE_WPA3_SAE_DH(family)`. .. _asymmetric-key-encoding: diff --git a/doc/crypto/appendix/history.rst b/doc/crypto/appendix/history.rst index a06cdf99..878d3882 100644 --- a/doc/crypto/appendix/history.rst +++ b/doc/crypto/appendix/history.rst @@ -28,7 +28,7 @@ Changes to the API * Added support for the WPA3-SAE PAKE: - - Add `PSA_KEY_TYPE_WPA3_SAE_ECC_PT` and `PSA_KEY_TYPE_WPA3_SAE_DH_PT` key types for WPA3-SAE password tokens. + - Add `PSA_KEY_TYPE_WPA3_SAE_ECC` and `PSA_KEY_TYPE_WPA3_SAE_DH` key types for WPA3-SAE password tokens. - Added the `PSA_ALG_WPA3_SAE_H2E()` KDF for generating a WPA3-SAE password token from a password. - Added WPA3-SAE PAKE algorithms, `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()`. - Added finite field Diffie-Hellman family `PSA_DH_FAMILY_RFC3526`, which provides cyclic groups used for WPA3-SAE. diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index 1dc0d02b..2b5e94a5 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -371,13 +371,13 @@ Key type macros #define PSA_KEY_TYPE_WPA3_SAE_DH_GET_FAMILY(type) \ ((psa_dh_family_t) ((type) & 0x007f)) - #define PSA_KEY_TYPE_WPA3_SAE_DH_PT(family) \ + #define PSA_KEY_TYPE_WPA3_SAE_DH(family) \ ((psa_key_type_t) (0x3300 | ((family) & 0x007f))) #define PSA_KEY_TYPE_WPA3_SAE_ECC_GET_FAMILY(type) \ ((psa_ecc_family_t) ((type) & 0x007f)) - #define PSA_KEY_TYPE_WPA3_SAE_ECC_PT(curve) \ + #define PSA_KEY_TYPE_WPA3_SAE_ECC(curve) \ ((psa_key_type_t) (0x3280 | ((curve) & 0x007f))) Hash suspend state macros diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.pdf b/doc/crypto/figure/pake/wpa3-sae-pt.pdf index 2dc1a4d0dfeed42f25ebba25d368f6a6535db274..fc41d3a9ef9edaac76dc690c295171e9681a085d 100644 GIT binary patch literal 33711 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 bZ$~G62PZcNKvM$N%w}PMAtx7=6NC9*#BW6s literal 42808 zcmd42WmsLywk?ViEJ$#72|jUmf;++8-QC^Yf_rdxcXxujySoH6$`wa^llU7+4zF8{;#*o8+MJ z@$qSeP0Z~LY~SzZI`#&F26|Td2GHEx(02B=209kd&S|F@W>!QjXV1SwA6Y?XXQJ+0Or<^z0KF(@J)@v))uwD(O1-{pq#&9B#t2ivJ z2Q{p!DBe%j=09rv%$N!#>(o5aD#18IEpCAPGmc!5l*;T^SGe`;8I4kG$Eg4 z|L-a?dvOlrC>itfEvSvf3i}CCz@;p}p1+|_eok2yhqZf_D!jn`ep5|v^W!USFI0c2 zc)C~p1z`#={sYt3!bah4O}``ugZ zC<7ie=LlPk{fk6FI_`sz_=^jd@YI5z1OO!ddnEE0a#?A(b|TTZ**JqiycyePq#?+k zISb_!lfLrToWJDB)*hxNy{0GK+7c!LZ#RDXj5B8P6HrBE{xW{ZDwg4LP-u`gU2>B1 zNloiptzi$nKB+XMC{Le7b^3%AY>qJhQ*%cF%^C_}Bnun@#0Xp2Zp1S5RhuLvO`tc; zb}kXUimH?K28_Nv4@Zf48u!>}0c+~?I|x<@38B&kG9H_DRvhK42!IxxWiRHj-ER(vw^&?WCv>*Qo$3LyvIBd z3}nj^rSh}AqK7M!JOktDp`ydwiq$wv*mCNVg`MpZ#Cogia{akfM^QUxfQR(g+u>Px zp!;(9Arm;xd%ntoI4*q~3w)wdLD!^vZ`mkR2K^ufw|Y{wA){k~j3ZHx%rYz2*dOdcMKKN680^YT$Tq*2-^wxVwXy>IbDqb@TFGu68vGRO3mvTYJUni+hf3x^GWv zT;JSnoIIM!&+$-GN2CWQwCiz=8W?co?50h7#<0va_kDT*V`Qg?6>&wvk9+0meS59x z0eiRvrnrc0LdL76%jyyfm(AI1e)K=th?UTP4EVT2jc%jbc^fWJRTA7FrD7Qu8}@MZ zfT*HAoLsB?mY~SzS#PN~!aXh`Sj#4OC^ght0v|k#3~-TxiTah*qL#ycl~N^$vf@Oq z%E=hhFJQ^+e+*Iv;ohRKBStTRyR1cVl^r^Pbg75pL5GGOsu;cGdjvPb6_@L_mNH4D z_N7a5YRYvc%J9n$TM|C#Dy5ab4+chBUH#)z>#a4w+>Q%W8?=)eBngO$7FZeB+J#8t z*6pyli-wigx%I2KN0=J^>IOboIYJ}o2DbOJ8xU^HEv7fjN%y0;e%mlMi^cAy^NxBV zWds)!OrpE!FncNSi?>}9AibK&_($FsluVmDf+nJ?NW zrLi|?)YK|aJ&IZu!N+{%lWO!w-U}QC*JXrLvK$ZT3rb&$u5JG%CBesPgl98fxb6gd z5nz8Sl4xATVrkmXN2em^R4aJk+hJX}Za0p--Eqw2lgn{y$+tOK7gzAxrD6~-4V4c+ zmE%ZAmd(xHsJjYg=1T9^A^n3~lP?07=0^{qO7s(iOB%bswqFEJf6dW1K~YlfLtC?6 z`C48Znpt{Yo#f7=OsE#HoLP9!;p?R@Rq^eMg~~zsJpoO?m?R=7Hx1X{FQ1lqR5*YaUE+KIWLUA9xrjFPQ8cPbArcgF{qiO-Pb zu8~gYG{D2?R(C76oh)_yZ<4W_nNs20v6TY#NWYzRSJJ@@kg=|eb>n59jb1`}KRF~{ zEE;l>_IELr7sO?Vg)a%jK#+ZZ)N7jJBKn3M8t7)5o4V5vz-%VOd@^C&!QRXmM#MgO zlRV`qda;Ccoh1e_yo?m;EZr;(zsa9w`2tq~{UZ#@P@#8n`*W%}ngDv6$i?>O7KWit z*c;waF&|`?*tt^Tywc-hwXKd#FO?grk zE}=%HmV*_>)7NcBhx^K=()022b3ChgwA;PAhPkh;5O}U8AOo<_*w+D^fKZq&he?^0 z5wKq)3@^Oik}N+X+ZixhkxbD}qRcD5Zrfcj(c+=iT6bolxN*sZAG!EfV%whZj%@*{ucs6T~;z%LD}*;$3)m)6626u_mji48YAp*e()5x zO{n)XYt==rC+|3|9%bMbFjJHAz=T9{nEB##Xp6o%m{I=dOLw~PT4Q%*?0FlQo7!BY@e_c7j`gF#N0H&*IO`9x5^}cxCU5_as_;2E zKMe0bv^sR}1$Kj<|EC2#{$F)}{iPMQvb6ul zsD{*Z)O7gFe`C6ji43Xf-%Tw4-9(Si`kyAidn>m8G%>uJ=;{8`Mn_GLPf!21?R^#R zzyI1b;NAK+x>RtnHo&Kq(J?Z>C&8!x;{_QVTZ8w-{<|-&q=CMP4!@N%zS?_7I($ZE z78+&%6Fz{Ii3Y&TOa}nqYy9ab@V>|Qo_6>j@bxdO`pwqL!TKNj{%g8F*1sJmk58*$ zt7B5gf z>HoRQe_?3)zZ!jb`+ow_ANIdmzPkS7$;?b%57qy)0oGp00wVVr}D7{owk?I!?%MT&Z6eijhNhTxRuV?Z?uyb}rJU3c_ud3ZK!~`c~zkgMBp-%ICk5->+ z$Z095$!#lRiYqTE3Js)OrSV}1xiw2TCCXj&$5TjoQ+(aP!eN{abrW&e$ze(Rj+9WzxiM>T@2!G3@Nq3lqV<*qIvMxy9}m zYg{qcJs#~-Z0Y8qR24NNHwUV(;3IG#;sLGkDJ>McloUPm1iac5I=}pP$ze);prC{H zT%>YxkN-%ESKkt{E4n# zGyet29zv<%R%XY^t~tnLD9=Ap0>0M6bFU!L)~(e-gS6=<+Yjuudb?$7K%3s)+zYM4 zJcc`slH@=jN-`G1?$G8bM*}<=Rzt>G?J-vRNd&hRg7P9#`FJlS5z}g_QfafBool5- zr)f#>cQEA-qWIWbs`W7>Y9x zMepxd$iAL(4*Rmx>RaFBW{l<_+=^H*5VU0}6;u>4%@VohHM;3mm3YZEV8VEh$0dnU zM^MpaIG!iPCY7oExsj`+t+wZEyv_P;)@~8EW#p_iza7dV!yFuotv@6$(SFi?PDyKi zMtNR#9WhP-W!E!m*<3#u(Ets^(Oz&v5RQ{5*qS)fKsizr6iQZGa0hGzm8zc?95_A) znpKcW2&%l%5~PwPOe|pQQ$7u}rbL|Kyqusj0@MUy3?Uo;Nj`Ba@?DHZ4Kni}N_`74 z3h4MJ{B%MR_rhu5O+j8dxS9SOjhxLolofnfGsnYx6YPd6?HX7&a}VAvE~aB<%kz_^ z7MjKT-nZ6TH=}c9E5CIVMiDGpuh&G#0r@8To(MlmfdMV{Nn{aw4v^(y_F}G>_Eo=C zCri`qIj`3Y5sO}n;|03ucC)?2lJl0`+8Zl%RxTN(Ul?{sisc2JM9W5Wb55I60KvZNLdzrUpS8|hQwq`xF9Nr8iiE^f!f`S6O} zRV(d{o}etrqnmXa`Sr&!UE|p@2SZv`)^&_Ew{8^p?&2;|rbhHiuJUKlqErgZXj=F5 ze%YhwL9U~lNHNf9t?f()lB$N~i%|Up98{soD!Pdm6DIQ@BOMh)D=2?@ab{mqZdfQM zu{9oce|Nv`b~(;Ln}LqbYI8YGHj&|ByE?O^Y<`M70MFxoc$Qni!^wDaas2JP{pR9g zWy;I*vBk>3(cQ(}-4KxbbjDB_;|1CNF!W}KL$^id88u?b5bByIxLk2_N!}LiX^tFA zJt|PDOC{k$5yOcf1(N5fEe#w!&n6F)R~TJtSOy!c0bXl{JOw(BLJm?g`Hd#PJK_`G z=ue;@0@}gJ)ZSgY^o^VlLFn;4MuL-yC5s`J3Jr=f3_GRx>=P$ZspcNEE1uUL4aA1` zILdGNQg>n}k=d8^s|=~-xJJ0_?(5BKCEN*K1$O5Eg~~W+LPNHwkd))1fn9jnif@&_ zkV~Gx>tMJ8gwGl!H_Z2L&F*gl`P-i7%2}2ro<9SeUKpJ&+zf-d_K>jPJMNFyv0L^V zI$Y01;tr}8S1Wct7arUDR(K}yz%P>RFlgW~*|5nW(uHEnj~adF^+g1McZzWDZ<64y z;XJx}9Cg@KC(5jvnuwmPjKbvlY-DR~Tj`|aRcm(j@G?NwQawP6>$>v-cM8!lw%ojV zG3Ez3H)k)|IU}evKht2v^R!2IU%ZN27BRevh1opcFi*O~jEl8=cdA$7XtiWoPo@@k z*%ea0mbzSGrpB_`;juc3<8eIKxkkyWBE{QI8(Wck?_coAhU1(oW?mBl?nf4kwu;Cq zMO~JT*^0egu~tt!gfeeYx>$JmG?1tcXZ zoUY4eD{p;XM`6{*FHJv3njl^e(YWbtyxjTfK7ZLk5wM1!++pMyR6SuX=`%-m4n!W8 zIm{{U%F6eSMi1}WqZ2@5edI(KsY)g!2066El~$Ao@rMjDLu#~*EcAtRz5sm)_MXcV zJ0zeIzz2)l%SY_h%_=h_Dvg+vUy>j)Z(R}=@;n&w)@Sv&UHhe5`&-no537kG{4I&Q zV$lrR`M~*1ND2JyX2r(P;W%)q3utN5`b^_Vcg!gPv$J-@l0{7p-&dL~ph zxWt+D!4<$sC+uJf{ZyiI!^d^8DB#j1=#I%;If-Gq2}-WJ7bPY%+hKQR>ool{WnYm> z9gI-*%Y|H+J2FuCq3UQy@k>&>2 zv;lUBZyZQDRi!Y({KN_>h_KEY%wgogk7~SaIH1_OmxKzzVa0mgOv+4+yyatR&P-?n zdNZi9*0|jXDI7`K$Dbb2-Q5}F zCHs~?Y;9^`MiQ~>IkaZF+m?}yliJ~ImTNSsM{b65+IIWeWT#4H#2x6q_RX`X-xlw( zDgATmP}aZ|kgyiak2ED9>F`$lAFxiXH zX6E$hjP`jI786O5$OF&%i~}aizFB6XHhXv?p7p!#tjQm4QzaMN&%r4?^#Rn3_U7D< z7bx2|?4HjZIq<$Z;Vrn`HfG#kbU<1Z=5|pDU$M(6;e_!^+&-r{j(S3~WQvp}Lte_t zzhO+9o8_RZ}6wq!EP{r!+MNz_MCCm)^JlL94UQ!LgjsW8N|$hXCr!^tH;~o zs0@-t)P3=}yKnVpZ57=djV1st+w;Ad6AO+n$1S^yILlAm^FW8cEc|kVNSB2H24fuU zttfGYEm1)&4fcA+3sbX{<*Id@FFU{S4!BMx@TDtAr41w%r+AZlNUgEOa-pwHSwk{{ z3ejSvjrI)$Ba)kskoT`QU4~q!X`|w$WQzfm@cd%2SIjS{vC^aIVNYbMh>aD#g5I^J0-w;W}UXg#bPM|Iw+{pw?Ay51<7%+L8Zy6IsVcaj zxfzIZS@}p=ew(7SVvSqL7_8EhkG3)(c8Joc@QDkfHgUxt21f7TQJh5wh^7fjo+j!t z$+CM~dfm^+Epu6|73?~xyq(ecdP|%x_o)caH?x>f^k^`#PcMFEt~d5lU#N4tS}lU` zz&?TAiUI>~HWUhZj7Jj88dCXt7#2AyUx;e3M!$)N;R;ekf@UyGv^O!rt;IK=aovVl z2M>|?E4AVyXj|f5RQK=M`Uj9S-*`hXTt*n=DK&PL8t5aiqDl`l)n_+uRHkFfo_{Xg zX*7CCp40D6z8GmpIMiBk3{JTBER*r<6Ck-6pNMYQNogIQM{Rc~lym zFR7kdTAROu-of2LU&cjU)!`)d=mIdE>y?+QLZU&Z5safXw*Y-d%soqrg@7)Pp?Do=Ome?!5Wvcx z?HD|JHOD+@$+i?7g({V@;Po8wE4{`veP8X_s@+PS%We0ls)sD_`Nvxd!@%dy5p}02 zi@j;z9~-IfA=&v~vCbHlYxpbGH=ys?lG3NO>BGn=K&VoxN*;w|u+&Lwl;f3yof#~S zb(XD1meV!`@n;Ym!7!D`1iq(IP=SM+Wzn2)o>joLxX+Gd@9f-NI_LN4)Ftahq$4U3 zhqz@9z4-1ul=O-%&8h|_C`&@zn6@fsMj*}R8nc(~DHis)YJg@UN;z~R%4lno2rh`R z3l#XaU+;T#t^mc+g zHXb6Th9qdwmocrjYGR7u>zCP!CV5r6dW#Vw++GWqGPQ6?a%NwgfMCekc@;<2$n+ zU5w%YR*U(cUctB3BtMT;R=>pWlT9H5OsOS6H}HuD*bsg&2X&G9*--nj9KsgLd8|#Y z4$?UVKe^o~8H6==4w*6wRay7?(k$cNDqbYPF|TdVzs1u!t#3eGyqP*;ItN`!ws(85 z-@goi{Cs2l;k>5*kice-Ssr~?@~!wOck1=)o=wYuUI#k~yda@j_h|0t^E9^M-)$XT z=Tz(buk6?R|8b3JXxSVcW6)bLLGgCN(>0#{UGoTU-OT5W(yKPBrqpW>t`_SMjKf4Onb*D>J)>UYH&d(@o_-pqmDP#dy0J^XT1=aGCOADZoEQwy+IC#K60z0wu#Gqf8))-Uf4~X zx7~J)np*~>fRtyrd|hHe_dd<&+Jg)1xzTP9B{3eQ4 z3T78KRoZ>n3(p7dAY3jiE(~a3Q5LDzeL2zhSO` ze_9Ee5cqtHIWXb%gF`%+A8DFKC}n|&QeWTxURf@i4xgeTid@}YeG{S~TzE&s+EqbL zSXh`y%#n(m;&j0j4=WCNdt-+tAEKq7Pt>GWn25iX8}Ris=4SyJNgpGC9heAdJh^f< zv}`V91MG)_48g6Q-qTVgYn+T=lQiOn_{DO?rxz??l&TI-RQ+|Hse%VbB_QlnZ#Jkz zeF&$o_SOTvymFgYdU=}d_|{cWCf2GMy*-6yarkG_F}~J;zeAz1;>aL@lBCJ)`F*c% z;J0BJciQRu{M%BqjC?ia5Uqg<(S;nza4zz`y(^pCJ?Hg*^ZTLpx5*NYu6ns0bT` zP9JUXW9N3CeMK*A!2GVlm7TUC%Q*sG3F^X|{Q`ZM3|;{#PJ%+umj{*QFlGKT&&qYn zw{wOJMF0e1?28-^6@yRh=6ay4%28l>X``bO7!l$^6MZ)N5sH9@96brKNu7eac!qgW z{-K&M8axEzSQ@@HpiiMd#!64FiZ~B7D^;PCZrrr8^Mn(VIpo|x@gBl;=MDz-C;-I* zstBtPEf^G@bU7}RE9T0H3}pbR#`-E9Xj4L6JD8RS7zjzuT?? zsZWNh+zGCG3H)m_lf7i}Y4sO{N^En1PiPA(W&Vtd=%x>5SyDVyyuD-3fzbD@ybU4d zaq#}9C*IgVPlRM>s)`hu2Tz0vu%m>ZQ|52IG%A}&kEnk8&Oz)BD#s88_xcUQX!}PU z55%-=#%nq#(+_&kHHc|O+{BD<-`-Fa2lQiA+c1(i_z9Uc#~O*mxbVZe3KR!Uccou_ z)*!O?94)#wq!k~OgO+ZG4li({a_AD_$|~Ir*o>Lp?BqWn(ebU! zQ~KqnGAhP(7OxAss6@q}ncT=*I8e5bjUz@b^td8|{V1%SZDIYF)~9wDGT3b>NFT`QuNSJ10*YkF(muR!j`0BF+2S%=0aC@({!&HMU=||e?I6ZPjtY~cbpcr>@7EYqpBB)V_BU_~S+!3~#q|ia z)WAHaQ2CPA>IX4&%Q-%FYWYb)^~)_4(YAJIq}O3_Nt}R?dp~3QsYOrm^}?F}Qn9#9gm3=HN5(E95Gm>kK!@c`>;=j7mc6 zO%*ZFp8h|wct%eMtUy=7Mz4uG`-d=ecse}=rK)LTbu9hU_|(f`mvVQ|U$Cm-tPM9f z#-nQPvW2Xx0O^c4c5dgGj9J4CJs=EwFx8^awd2g`&c`uF6vfuZ_JF&;29o#gT< z-;+%Mn2pRdoMT(W^m^nf6#%(mxw?A+j3=Zxb`g?>#%yKhY)F!e^Am-^9V$*Qf%?wb z@yeBgTlzj8IzcG_&4C)nxz_D)Z{89tndGwFrjfgJ8P~vzgS!DEPiz}BNuLaIWJM)d zqtUzYsx>j;zRr8&wG789c5V5bCbXDyYmRfkTb&MVa4#Ai^eW_0@Fbawo({Bn_=_H| zr@p7)+)2fdR!s-#o9_$EmC{MYV23u19L>mU;sPC3vAj)s=pN1l$BteSI_S8aX54sR zU4laD<`V3zA!E%?u2GVzt=7=1qYSgN5VoYB>fq04Lmm1Sq8aYOac9wWq$Z1apiH}t z&oj8L-|pL&&B7P1ZWiP!ibe$96(VT`$qI+h;8TYU=l{c@{(~}QU}paJnSOAV|4ANx zP@94Vc6zoZ*7jDmALQI0wo^*S;+SwNF^iJgg{yhl)U$i4N8zVk7JuBdy(WGa_XQOBR7vm^uuVZea$7g9|Zh%ks zrxTx@-UoTf#>(=i__3~!GBtpn;g3N;$6Ca|#K`!)2|eo{i@d#o#dmzxzq)@EME)*( zZ0wyUGDbBga1m*tRH!1){n>J@5Dg&L0|v(#Kb^{&&2jl zZNK0DEB!U=-*dmW_|x~DTgGQ%V#8+wFyb>Xe2m7##EcJkzut52wvQYufc}s5qlW3v zsu<}Q@EIB2d$2J4sd=A^nfb3r#}|ymx9aQ~TzG3zIXhv0&RmcjK=80`jDBu-(F<<+1t*TE_o| zW53^j&un&r$mRQrXn$;YdduZ%`iAzTm37^ww(QnXwT|KaYIUGAl*M?($+Vu&IyMEO zk-eRNkrTZ!J#U7A<9Jl;ZqQb(K=>08i$p^&WxToFYPN zKno7RoQQl+T_01$J$a9?CkaOdm|G_-E(r-@)QF}Mq@`1RodSEmkCUR|-5Z|`S#ZB3_S63d zipp1jVF?{TqLTEO_ZPL=gePv0M78-fEG!dmTJaljuA%Q2499@6(J>lRhQ$j5ROBw$ zFM4yI!UA-=g50tPR~J_~TGpNK&D0>9QioX9b;mEOUQb~R3%#iCwr)O{w|jgP-3#AQc_i7)=1+7zR1mror!-7R$VRkXyujPO+M2uOq4 zMF?XJH!$-|H-ad?zLjF}Do{yEJl7yMexV_}aUkh9=V$1poxI?w^wlR*V}p2C^_k&d zCXFG^NLR6m5|mFl$_Y4PZosOLwY9YhAVqy)V{Ipp_~%X=q$m%3iv^_x&X3wq>GsfbQ*0e>cu%g;YWy7zWiSR5);zQ3cU zPDqS|(JL4b7ulvTcdt+cqvb`Zc5-7y3e(vKR$`|(l*l&-8hQN6Ge%OO}q ztCLQX4OpZnQ88wxZc*okT&ZmZYFWDws!`Js%e(N%blVk*SwGh$&_8i>0Y0#>zR60I z9>HZ{kP#UP+b_+zl5_@J^@!-U$=EF6#;AQzZEOGfUC2TJAj{b}s9cB#JHVrX`TSX$@LM*rGVu`xE|7FfLcxnK;)S(MH;rk|AfZpt(x508L6dGtu4bP-Gj;;0%rnToz+92Z1n;r*te*KvE)bKmbF*jV8BmQ?gZB#Z9NZ#Z+97?%i|b&3 zz|)=3R%CadU~QmP9N9*xo7F$@tgV27;eG6!Dp_{tC&FsU3EIvwse9J$N7k8Sw0e|8 zl(Lk=-U{y1<7#+NWok#XqUati8?0A+_r1wR(Nco(*y5~3Lq|ceF&y^-g|kjoNV2iI z11?%2v=z)`IthY6@-e~(07kMqEVI0<7Du|H3RLy2MC&$yv+bw4{0B?()_ zN@6*GnKXtypMt4;nR!CTblZp)NX3S)0vJ-=>AwL!+fOgc!f*YGBV&@~hP6LXJ*vFU zX@=}+RDuuBtNYE5!7Ly?^#&9IQ@ij#z|D`Z-G9Xp^sEd_|HhjiQn&y16%F`65r65+ z{wEpNyEN?I{|liJ-4)t{j}Umy-G{v_KtIF=mUu=I4}?>s{^;`!yxA`W{-njd)O2Pt;9q$2$JazKShh40Y~-@DS(;7@q-5gAF_>KoX8Aa!z4{rAAc z#NOrKRq_tj*5(ElAK}WMVB}vC$Nz@<|D5Gt55E$=8r?gpXJUBA!|wr-hQ>eTkN@+L z|L0~ucKKJ_^MBvv-w_2q?cWQh7Wp&UUm=#h&U>u%?zoRI<6RK?{`ve%6Z#MI{g-I< zALsF3>f66{m-z1x|Nr#S{_x=63Qbl5J_t>q@`+u7=4``xSw$+(q5mZCJG`Br}#yQJGX1f{4_FBEM zh+FJkzBJ16LVC*fSnPQ=Y1(|t^1^+(di86$du!T+qGfyoebN)eN92DHxN&>4P;QV=to+$!{F#M^EG)=bOBu&OgmH;A*UJ9Lr zKv|9vny_Ai6lz?AxTrJ`Jp#fU3l`PS96H?OSLMubUs7yvhzKd`OHNoKYBh$w9zN5P zTbT|WI+U!K*(Q;(SV7%*Wld5;e@XJP0gAbTN$~Vty=HbBb>56>vfT29`7ybK0Ii^3 zq&7%9-g7g=yzui~IHTe>cHPj8#^q6ChdeGhuIbsCMP-$}8i~7p&IrZN-xPDn=2D+g zz4qle$H{2ZQ7!NNi?PK_#f@}Z$*_y>*Ww*i{ZkVAWom1(nN-dw?$w(YVr>;zT-qXL zBe^$|qwej4jotY}49-Abzg*G|LSGpUFa4TDm-9{VjN{Xmn^G;KBsUI%PE!O0E$|n#z$Ahg2 zE6Wa`%8)p-5}*>+_+FLL1p-5BVMYFItpt0e%!hIZYoc+Kv8kU%sp$C4UJCyzR_z~5 znPox1ZEG}qV#9_*>PP&k59cMzI<)y0%U+s{2ibxa`H2>=00MmTdeYt|F=sS1O`7k< zH+f{y3q<6rpVsuEGQQ*ZZobeCk={6xdz@85k$bDe$7%utwT0{ zdg^|q3s-1NHz1XYAkT!-kj?@rLQE!irugDg=L157ji6{$2Ao9gY8D~L)?jDdMqhSk zN7sx7hy>DmRtyI_jA_X-oYx)g6A`7VK8|$A`{MPN08>v=oo0 z9t~s+ef=@aw^*k&zq{};kHJj8Ur!=G+~cbue>?Dbu_D|xk4S_cJorICJp)A>#Mn%J za#p$J)NVBG^<|!9-DuXuKVPBF?DUl?zebrk{@Dy(z>_}a2&Y#3Z2CDEoK*(f9~4Mn zHEdx3D0#mxsN9vy+)UsUit^iFH>1xFK>}f6`ZlHzt^r#vA%un$*AOL^4fP!Qr~|InpunKPo#!7U;5oW^!zR?dr2BE{ozR{ z*$~>Qqj)!o7UY>a;Lf1UJt*8fs77Iy!=->$da0xy@~8oHnt*KWU^41N9r>7og3%ZZzQr;V#7#^X1gh0gAx#{3Jt9M=7V3=-~u3h!3RgEv4NN?hWdV5 zJRUY^FSS{x#4)FR-rk|rnN~{k*PCRXl#k&By6I<|)bRWaS_S8@2th+x>w^{)R=FXl z!B$e#kVI-sdz!7}z_$y>DTY-F#3c8l+^ut0Z@E(eH8Y_(-&=2HR|k?9l!c=#UL;}n zn;nbiA|;Mbn@|nGJwwTxr)Sr0qq~R-#Rw(JUE$bnwZ>Y-4dgCxKzd~hZNhAI(kWky zfTOymmiP^JG|;HCA_4&-#ZI3KA@Jm0yd)rusMdKGwe7*a6I`9UgS-14}ed%Axb{my4b$)0Z)y zJ?P1(MG^NIBPzp~UEHs}ipluCec-&N<4M#arR6Dd8A(4f#PfXnmv|y%yH{%HrUXpM zeK(DC>#4hc(-9sjj`D^zX-?1(_97I{d3)%W4J0_|<&`hSUe@QUgaA6&xDlkUlZ$@* z;O}eQG2gwtE`s7pf04Rm%O-Cmv3VnU%+bCPaX#2pveT$KNvu=snjFJ1I~N~do_$D1dI8br+W4ctIGfKP9dy1UWCU;Lc)5aW#c(6+-V9C`+hP3x$Oh~}Pk$3M!^ z8PGLlx;L_hql84&5~%Ge3Zj+)bo$-Lb;j)a2|N!L#)v!)m|3P)CPt)-1U3GepYJW6 z61cda%7rl-e)Z*Q6|WhkuDb+6qg-`XE&D5t=cXJ+2d~=;V&ASDp{JLEO-p_ zo5kcSmX><6L#P4hgkvF7U>$LF_I9y=FtC}NSQyUFB~x?Ga2m?=iZ!mKif(Lq&i(NA zoUVtGuFp7CRT&~8^-0S!b0=rZaxCNeOTHdzmR`=Qx6c*ww&ypit?kRNJ53ae<@Q&@ zuglXfUbRCViCoF0ZRr9R%*oxKsT;FOzZggeDNG+Fn>Q)>5iiV%z=F#wPH#+k_BQC zPr_VcK_5SUb#!742*3~WiW303FiQZ>>D*@_|KDuSjbgX$)}^N8dYg)Ry3mGZDb4X^@) z`Kv8QIMtUfc(^{(Mu;wNL>%n=M}K8Ul;7VGp=&t3s?zt#+ft2ZDE%md?GgG}kOPL{ zGL-{&WWRfHWow}i8*k=xBDApEQX{$;9&zK7ebjKzh31fMxUi zuzNlA;vUuG_zT+zx6R!VJZ;?q%JyA11-7_t=u<^973B?eJ_aQ{A^H}Up0&Vlbnf}& zS2`Y}A)AM-b0~K+ii~ZvPeWW}+${G#&Qcq8czd{Hf<(of`9sO8p~C%Sj`>8G0l+F8 zJmpZ?V<~jC284xRpbTHCnYpNh|K9O1QYZ^Q9jYY3N|`yB93Ryd{1U21TskVo?;(tvB3n%!-p&HG@yN&q1VDD0 zac+yn0n?P7H5P_&fe^NVz!dXRWze(Yx*44eGe)w^s(O0uRxy;E%wXz?66Okp4eNU1 zC78LN7#*x^AZ3O`i7*U1<@Ld9cjbY5hU=l}!8kn_cTq8fvia(Vi8Cp3jzO-aQxa?_ z6O!grH=dXvJIxc9oS@8_gQ18{HPRV@J04}$^VSR{B*m{ZHSDAa;mgO`Bsv?eGq!*| zHCjvsKXUCxu_z^!(WJ9=)CE+XPy|N&Yjh0LVSq42C}CM=HTWGbxn!WmYv}ioUb-vY zw{>ae+s*N>R^}=u#8tYQ*i7=(GvqAJ6tZAUorI10?!@tK${1l>&Wy;qC_6!X=K(M< zl)_Rjrei8TO)PA(@B6|HcTwTWu>U@;!Gt?c3He%`mgb|Fc3oqukeS4U_&rxgWh5(K z{+M*U8&30c+Acv?=Gi%^8d%0?(ly{uUk;Nm+;a`t!uQVnj zpGa-8Spng}*}kk|NO|_hJt?w>K~0LobmdX1cL&pU_F=7-gZ&|eY5u|E&tkF!*TZo~ z*(^D3LE-D(TwS%s6ZAfGtV2V zw1uuyO6Sda-ZE$VmucCFi59p?#y> zY>l8Irn;#!rlIt-DDaX)eoOAPdw5CF{FvA%vn`0;+Lub75q8YQhr3;Elt_A~w{bx~cc|7Km&ZJK&NoWz`HZ`z4pQY>1=QH1MJ*>wwYtZ*# z5ewBN@{hnDU@P{R5p~sJuHRs<&ts#?2WxiuK|?OQ2vO(wB12eWO0@|RehVON8|tD) z#X0Nnda?R{hSwdb1u-~7)SUv5#?PXy*Y=JeZrj4vPde8cX> z;m)nad_Vb$_K$m0t z(R%w2eE@|un;-o}vh?qG%4b%Ifr;TC*ve;V^FOnt|4S97`={3ae@r>c{(-Huy^!B0 zVc}sxXNe)p#cQ(MW>0&5sa^n=qfNjCd+%snCy96ci5lrN|1ub{CVnu0R1?XPilt=2=2&mau)Y)h~_gqxN0>LB7d+}Ru^y@OO?oIS>@Jn1CEXOMgL+- z7vl}pabrL;?;WihR_ah`P+`SKQ20nQ@RCX5s|wu(yZFOS%h}NV_p5pkKe}{C(xFl0 zL>iPZU+&y#mvtY<3WdY7&ej~bk)h;~Z}Amaqzn__aa@v{JelC;5kjCo#4AY59_AKkZ!__nl6}ocl2R@ekmvde+5`i<>gU>;WzB z=aCx6UYaLn)b;XY3kVBPuWyASA2rwbGdENg&p8SUq`JaL(f6T<+Qqcd*3jc4xlYe3#|MeFAACkp?ee^%yXJY*)hC;{k zuQ&@M)9*d{)1ZKb1&5L0U!VK)J2KMz4O(IR+^ZRwekaO*|Iz(viQmsK($L{Bev(F? zfD9uo4m}Gi4n4!)KAV|__21!=KaLuIo+!G%D_Vat@uK^Oal?NDF8M@~KBxU3QdYX6 zg?XydZ@A=Ei3@{R3Z41N!SZeDxjr@j$vQa|ja_OGZ`Phh-^r(aTq$%pLI%1{K_cbR z+iwe--#ADV2SE30-U%oziq|t3I4rIYf|q7l*!k5lC9=S?&$=mVr5UQOuKW| ziROc}gAUJNxadqx+;mXpjz_@8d#N@deRuHr*Zc8$wZ_VID&7z9=)3d!RBDTl=e@^+ z>U&JQx5|Y^wA2T$*Yy*DE+w7RR+yg8LopZ3o71GUR_9%JXa`{nfCp^L#r^>knt9~9 z_dE6Be0hH;yF~4WB%+U)`@#MEIP95K1mD5rCtZSZy~?bW3x!l9LdZrUD4vK+g`D|1 z=>N2&&Xk_E8;v{ib#Y1IX;roS2W|meD$Z}Xq#zg-jlSSZ!BHNq7{K%`YC+7aC!`y} z2t@zTL2bPG3U2C)UgM!bc>QWT!JRwspItkNin9-DRG=I;m^V^7O4D2cwZK*7J1H+wnB>8~cX{ z1?ad)>iY&t0mO&@`-5J{MZYghi!c~$cKH4f#ECsRolc+Fo>beN&Q%`yms3BLpVf#^ zoWQyZb6~r=EkOkKE5kJ+Y>ez)o*j}6vaOlkk|DZ45?(iiE<2lhXYS4pN2TbfFAD5U zwg`MF;Q05WJ|Fgi$^TPX?3=NgV#hqO1Z z*Hakel=5Xg8WO$*E7$h#Evr)Eir=9mf)FAdJ5asN^%sni%xUe}ih;`y@2snaTiqDe zb{S}w?{|04sq%>0iKW=-QK=%v#+5Iy|E5gxD2IPjCM6=Jw)sS7V)s;R8a5+zEe;T# zExwEqajFjdHODQjNwGo;LMl@=VR<8^l2bB$&dsC+_rxwxjP~?moTO->L5!F~>JhGF z(#4;YiFzVHJ|{Hg7fNEvcplRs+s`w9?p?(%%tJXoKv0jDG%Ucf{1XTF$x5fs|I1X3R253Hs6(7yKr)CXDn1ynTS1L!gv#v zqWTpUA2o}?HT}qTC+CZm@x0StFpoyWHkQ($SkkC(a@BKWzqLI{oXP4*_=8~b8PHiP z){s0rhJ8PiNVrdjcJL& zB2@(U&@aETQ-EiealG*(r5<;8?%7bVuvFeieo>h+I<|j*d1* za8v5#3I90rmEFt7t|)XfKW@Ml=(gE$xoi(luvX>b0Km$07>N{Ss26l_4h0On^Ufwd zP7k*hzT)l2l`2_o-6K+H2A?W!ti90mw^7hR;@lK>HgyOUW4CPri;@lD05}RGPWQ8) zH-n0!B~cNMxNXDCcV1_emRom`ma90xKs{@gBJH#D=#Gs$sUs#`xQW0xYaS=*vrE4t z*kd~o9d=mnD&HJ@x{ob=JNGj?ZQak@csa5I>U$Wn>MS4aeCs!IY}@GE?X+ca{8j^d zhGF8IILr2t6IAQvc}d!4s@4CDUH#7Vc9Z>~GA1HF9y9~E>$IpzoG7G4lsbPkoi#j?(t%&(-7Ubk5!Ku z{Yp5+?I&G>rEr_QZqm`(-)Pn6BxNIGLR>GtSGZrB3|EY97h2|^ks`afe0|2$9m z*9iT8gPHt?QThLuGWlaF|D7`VjQszFGWm0e`=3_zuOO5Eu#W#coA^I6CV!0bzcD6% z?8g5x8Gk>=|5Ff7|2z8s+q(VLx%{6Q6IvQ(x_^zr_gKxW)8-r=)-S4CRZWy$<~usq ztegnXMoQ;ij_NZ!)#!v0^vy3+Jpq_dPT^^&1Cx>WQSP0E&P%|>gxpp6@0RNtY=L$ zqTP5nzc0|6M>{9xlC!w-wHswPN9hb<11T7>kuAO-9UqXNUYresILQP_CV*iWepn1j z72<8%PflN6KTddA2GC&5%AO7>rOH^Sb&Fv*?RpanMLgPXQ#?PrR3*s6M%hF!#w^}) zoe%9@0pSx}bP(u9$H_RsSH4+HfF|`=Zjb%2jDWsM$E0O7*R^e0ny2j*i=~mEN2~E` z*?Qq_j~t84TwXd3HJYi}d<_t^uWu;yN&eJkwiyG_i7}(gHWrW_SaK}5yqLC0Jbhp8 zgbO5B!?8L0HqzHo(8+I|E{-FG@yB1!rp5*8>NOH#3xs05ikA`vYsw}zkZoA-EMB_< z?eu&yWo%oVlNp1Arg`>liK1$}8D@_h0CNE|egMy&fRd1b5gKLz!*7ueFxtW&jQN`# zu*hH;=lb|s1S!?)fU}{IyjpZDVb-1U zWax5@s|pd8O}o}prd)40pmUBd?C4)*r9|Xq!4mLo0RU}nI7R@!rw`f}aYy(Te}w~f zlaUsLb41Bbv1h9!@;Fy@{>gAhh9B}xtQ_%FeP()+%^kS=3a~K4z#pHaG9lEr+tPMr zUWZ2j;zcty8p}waDw2%eobOphVF#BLV}(Z&Pe~(vr)=!oy}+c*p_F zBmeL!`Noz+;A-cQ84^H?5PM-^QQK)HuqOM*B6+=K8tSs^MTesr%NeJ3wJRW&Fv}dGXwMH_7Veg-`;(Oc zH=T2%9MEJv81InC&Virdd725@Ysm?@`CE<0M=rUww)bC8n_udCV0j2(t6m zt)Z#Y4LsL~V`;l5y$7uI zuKNWcjs>H9BvD2C>W6N9Q53on7^()hLDA%onA9Pn@)%ri^NAfBxv2H}4l0r~ot6&Y z**IiUtBN)TjR#v*h2>xhdK3#P#tLo@)(g1A<_w{yKW0;qB?3}Kjtp=>s0fIxT=T5J zuz3xLg@z`AZhVWRYCd&*h2m#jHUlWDALYm| zbsqTZ{Hw~TRGm}R4URL&TOt@Xr{#lJx$gt}gslA4LNc<_qyZy}tS4*IP# zjEhO^nuGPs-`)()_m4zSi+?y|+5(%b8*98S9P zYn?P)jZcKVp1S)&H(uu#I_~ifb$6~6&JVIeNknJ*)#dG=x{b{!*3$0X&J^u{j0wbY z18RiW)Rpylhd^Goaa?#jH)#lYN`A!>lpyhZQQ%hv=1YmM)bBPcaJWRGAFap(k-&iu zR-ICKEXx9BKmD*5FkOu&?_G?8uFkoX`Kef58FQ)r4T}%cq(8>La-EiffrU~$lbOrL zQTw8RDRHTIW7YL`enx}2^LcW7>ZQ}}Ah;I!Iik>v*$VS{G_>(K2d1Z1`mwvibw}M$ z-fpM2=fi%hf8pKU_Ug2F^sc!mVIoVa5SBk|Z?L?X?WeWrBbo35C=K*-);yGeE}@`; zw;^;8+>AN|_c4Z#oh1Xg-_X~YZ6YSz3IY~C?&nLe?s!<-_2SuY7kwm)qA{;z2f!jR z1Zt`HbU!2|nL?+2O^MCT;>lzX;0#ss0PPiP<#8lfRV}#BE00GlrY|xXLF&=IDWVH0eFAwXkUG*0sgX7bppr>(R=qcV6 z?R-rH6P(HPEN|~4Pes0;Lo-=qP}OO8d0O^>%`EgW)jxSUt^GK7N6yG^0NaGH-dVq= zG%*_35`NGFWo5W9SxM_%npWts-teWtx`LJ-LjF1vk(QFJJV}*7`RdN=m~$UmJg_UFD`$B;(csKf;ujf?h5f+RY=j#F$soK!>PJ*uoUSCq*p zkHfPgx?al`ux~E(tSqKJVY(~jZV-;GDM!|<-*<)CTwX~29AyY1DNV|5Fz==PxzR7! z#=V8;&1*Br=?@oPHr6&Iw+9P0pM z3_SI9Gi^CAXQzk9Qu6iJ3uw#H`>B#YOXmuHluOsYB5L1u22AK1+r@qau zjNhJj?qsmvKSZp49AkQ2cUy4Mv+Eu8%P_UsIbQC@-YV-Kw!GaOIkz@`{5o9RlJwr5 zyEPuJhvll6MX`EtwX}8t4lR^k_I$Vn&EakKn(!F>8e75^_x_fD1|`*?KOtZIrdhs2dqkr9;v1Q z7*t0^JvYJ#^;$J!jIkwA56zoLcexe9p*1UQ^GkLvS73S9i%`ewV*-AvyV28kzlWU} z8&|~tqAyKRslnE^5Qq0mullF5~21dHw_+ z0V@z#?pJQm8HebD%efxQ@)v=n}!Pd0r4pkmyFSfQ@b*|I$Q$k9Zu?9fv4;>%F*fU3Dxi)+9b2h9@3X#*qV{PC_cJ z*#tpm?vtgAhu~|MsZQKSgKx^$_7~L+8iI7zrWIg@L^)U~P5QFyKaTWx3|}J_bgd+% z#(BKYfSGrea=2`gnv|)FS~>c>N9{E3W#gzTqrD_!OR-bXzYX8GL;&0-+%UmmTh!wi zxuL&OmFx85>5$!&ja5tC&vgxe%_RG~t&|f2u3T1e-x}bz@5=zfcY6@^(C}x zN&UKiR{ZcV?f`!XzaGQ7&FKir*5p{=IV>XV&6{ivk#SJgDE5@)bcEivm0fM`FFBds za*SeEDi2kvl=SwNDKYv944G^UjsAIr^NzGoE)6gZ2C=sGRv5{FuPk9-DLGf~i0e(E zWU@CYS3LLYeteae)al~7eGTS>#XiDfdz-^|yBL0bh=@hszG>%tVG4NDU=IoHj+=3? z)-`tlrIboB4$9;;WTd-FWiqbgK_lBq?YL|lIBcVj}3t8f~x0ge?6Ov#ng^axKT&J z7*b|I=BT8_^lQMzXg(={ASN+U(RmCtk$dra8GVbS7o|{&lq>%B0y>5Rn|RL*-{_m` z)g882!2*;aF9WJ3bA4MEeWb_H+N?buZMrUPE11v)L4hj5j0tkJo2QmU4XIZr_awHm zs)Xr->Up9}C3Cdmpt@8fx~pULmZ6#4n*g7klZ^M1hlIz?PV-MR_|gPj zSjAHLdHrwB=N+g)0VuS(j;;M z(H*y001p^6cU{~9z>2UBJWWvW(2A-k=wtI&>GbL^!@4x60wHxDiGb3kL zk9Wq$t#yGp7JVbP?eP~ccS7wX){GY~%GNZh4wK6dZz-%&H>VIV#T^&?PxI3CMhQ^{ zsrN0?nWUOj&Np!v^q0G ztYh(JgF=C`e(C3Ab=W07$dUe}rR+Li*>s+Jg}l2mOorRpY0_ z#!Tw$(>C7TzGI87q@{`e@!;hcbp|F@=S8ZO|LczfCyw^p^n~}Mvjp+FtEbB8esN;U zRXe6-a?+g*>r2d|7Mu(mpMpHHVA4)QW`YuF^y|7QfwJ@RY&d-QF8~PU!t1jL{tbr>gn-Z@_gj_62lb;3waW{O=LR& zJ}`$(lDn&Sz{KymILMQJJZK8Wn6y3#x4`5c^uUzq5cOS+N0EyQAB7WiEp{h(GBDS_GfrSG< zKBrR&>89agh1DIXgd4oHH2rCqBu>WWq71hse#xE_gfCAYVo7iXzc?)7sceIF1%Q?4 z_uDW@?*;;sJq}xv)E!j_{yJw+_-1<1N1aD!wm$g#L!pRfRCZfVx8Pnk$s~lR|2|=q zkv{o2k1$q-!m)K7x{w2n7%3iAR@I@k33VvwA@zJ$Bwu zviiYsInscWNr^(*$sE@KAM*4y3}5(FsjNa1|C}AUSL5 ztlvgWv%U|7i2}xuI@tKL(UInJkvafw#7%#(mg)g9NF1sXPxC5WUO$XltyPxBtjZxJ zh5qsYVK>Q}O`M+;YqG#2W}>c#Oppl%C2k7`=>9`Y9HE4^>M+6IAKZnH3PY}l<5dnH zvCC2U7sS9eaDI=MgJO0tKBS3C3@Gx?&~i5N{5cm~CD zE^+6l>Xqpb@bUFVnAJ>mf>}%X(MebWBuEBZxoV>MRV9kzMR>?6Cx%a6Y=_1Ahe_X; zqUaFD_&L}B;B%te9U1r ziGATYO}NbjRwf%r;JzeVD2F^~fkg_)C9K7ZCSq!|D3TMsP-<6PnfMB8EMf+sC zq_%nbq>4FvrAj#k)}nr-A*oTQa64OIJq((WQA8HsO|7~J%%2FoeG-U~n!3+41z*(|X4x6`KMJj`Fz_ioe) z>8Gr|Dk(YM~87S1jOD0Ye9J-jB>f#NIW z??&;fz((l*>9S9%70=I!rD)oR`I;fJb- z{NL0pJ}Yzm|2MSRC%E)!bMy(D(SE{6te^fje=DN>|73IYxkkS?v;U6G(WiyZziL^3 zF24Vd`p#d&W`8{#{~0x-`&;vw|5EMz$0YoZP_uvcIQl30MgQq6^v`?$-CN9`3?B0z z4n+UwCH~FT=yO>{=HJWx{?GW^qv(?*Wc)N9Vq{{$p=16VeD?dAV{`fMynQd-z{p$g%*0(`4G#Pk46mTtPsP1aQ(DPktx?UtG?+UU*%2nYG8R8(oy# zD5Rrxmavgz`+Wl{CZ@CbF;-Ue(&jS5?x(IK6vR+$x{fh$M0-JnlV=8rd)&6%QcOSa zK*}L4;zU6Aka3);)k^Iiisb$MYc*KG+>ak0ZNn!ytMxK8?89`}XRiBfkE7>@>CiYgdl&BYq43$=j=+&# z77iP?gr*)de=Y`iI+6oTdH?{0D5J;_iyD7>_eDlK^}-(~c5VyOra0 zJws=W_5yCz8S2HD#R-g^pdgVTaL7!`0SS+e+{_BC5zjhd!#&GmWh#B~ zA%u5TEoCOOK%iB}rYd8t{-Ua8_V%jBU!JB9Tb-mX+~hmY(8F>wOH&?+wQS`jj++3+ zx9}lfZrfQc=8#35CQ_Y@&?UO)?l>nT(xW=Cm#*u5W?{f5=24Jtb zX35hjwsUcknJ#La8l9N46kAiu0{|-8QP$SjVVou5Y@XKK)T@ZhFhK&4OYYH_(49~S z^%eywkC#2ouahMZJKmCBpz1MRn#NM1x2{Oiv5$_=4{f?85YXy%zQ3vFcNC*i00e!u z93)Gt_z`hu8_7j?GG8})m{gQUpm;-~rbJp$N^x<*Rc36PUwHq7w{agFdLAT^?zwZh z)aT(Qv1B{2Ikyvng%PE|M#Dsw+fZ~nPAi2Lt(i7gXr}CEA+G*O}HE`3&~LzWbNRH=QlF1N_gr_{h5p_1g=TTPvmM(AAt#gIH3C z9uFrwz8};IkRC(mNOSC4gFs{@a(CzNI_}<22(kA*f~dPjbjgvaqilETrF)?bGKKvm z@DYzXm>X~Wx4A>F!5jKhnwM&E;{r;KYz(PFCv`eOu(6;*!BPgk7&mS_#-1ZagvFdU z{^dg*Ia3jEV|z|#?VNgts|T+$jpM_MgzUB@we$s07v%}7!NpUNoW+d(C)ibedz9?X z2V<2SGHIM2eK~0=Af!Ve4yf{a$5|xZ&F($7I#cigzI%8*OWK96M%bH5d>Y6KUGXXC`1CD;o8zn&hNB zdmpPSe-C#PP8z)}7KhYqZ)Wl`8Sz;gyV;+N-2C#$;R1Oh5J-zEN<>y4g?B$CtYmK3g#2*#|-2Gd8CD^uz{V?f@;aJrZ; zms%0Ukj=K%aSh=PZzvV>;#CWB$Sz#azEL%QVzSi~5}j~A>Q56D6-3m26l~C z^y;dlth++=Og)Vsa_QvITmn&E?}-zIc0nwqhj4vmC{_N)X@Bhe{&6`evhNc1^U&ZzwRVN#gOJU?1i%JmqWCb>tjfveJv@+IkT6u_p*cRsm*G7 zYig;wma$on60P|j)DmQ>$`SSn-pl3AD+iym6q%kywGs~209@j36M z2H1H`H=cn zLDjX6M?^8kAFdL~=nSmNL`o&x1cuYG@ufp$v75q9Xm#{M0*JuqOM2xbytVNCW-39YAn;l_xU`eFH6=x;wvjKotDhVrjzCPkKdyOi6NQPqA0-hlwx|} z7DVjyE?)tFU$C5q(*&qVg2oe3NR*CD?{CsX_ua#ezVzus>3&ZEnsF*3MqYcl+HLrH z9!6KSvF~JHWJ&uT>aNR9UsF(hJWSObh|J725MTf4R$yutVzj0dn;{B0v0A77Afw>r z*qepT^(tUpwd`r|M13(6d-h#xk@TWVec8{(1?~#jq1(9l zP&Hzeawi0juG^EQx1bmv-E9k^*2$cADPVbH@Ko`NvH6OnB%c+C{yv2+Wy6uIf(-~* zar@Q3{K3Ds7`vE}L(`hh=|{y$R;(A!A?SZ32@kqKtV&8ans&zJ|C}bFSYMHlr)-ST>TC-KY5=qHlk-Sj}k^4NtdDw z%POy@yK$)oR&%?CF!9v-;FxjuP%(Ytd37>ZQ0uviu#AFbD9qI`(fPdieHqOG%Q3ml za3Ruy0ERDff9>I_#0C-!%51Pw{pe)rA>io=mYc2#kGa}Y=iUQktv>>DC#B(NAE35$ zrlkKNx0Hy>)+~Xyamv`%KpK@JOZ(@Wx8I34s04Lnq?T0(gPDBD1&T%^m*9evJ*8Ep zig9JEaizUn;bRfOW07Ln7t*@=CF`}HdF>2ox7uL*X7-+u>xFW7bK~X|2UG0fIv+By z#M$&wEN~4C9_|$iOJ;<3*7sa{2m3+}i`=g??*}_sHGJxG@MzVlmpfUYq5eW#tcHE> zOK*kiKUOVnJ64Ftu}{#{JF+}^pEh}K?q}HtBCt;|UX9h7C?;3AYt@8;I2{oC*NfP?S>nO>N9G#m4lY`N2k+c3>fz%V1{s4 z_0qexj;an;w`4@S!&Pd|<35~9l`Z%8jfhL-Svgp%HPy{cKh)%7;}!{?mNR8nR=DF` zv!P&fpp0m-Fgi2$G}n=g!fV;AnYgwXWefXWYsG}k6=HJzm(fKTQwS7L0Rxw|9rA8M ze=h0Lf1Dl^_$@I#>5TS&;U*&~kC6%-8ixASbkWu6T?L(`coH|E`9x2dLUZP-_WCoJ zpQvn*1lytNJpio0;FdyN#$8CYDjFGaO?FTG#Esu^x~QvmK=Me-#p~$snUEYpx|&hS zpYi;eyTXRu39TMsXw+<;1^HZkXR*``DoCVJhAkFLw4>QYaO?!kCv+AYRD5~jrSt7+ zdb=3xsKM&+z#jKSbM`9K9sD3hBnI)ARu52Z%fWob@2t8aP!w|kOEW8T@~kMrB6WJ4 zB!bI+S%D~IE)OQs?13KNp)uDpy~bC}j=baj*h;`e@vQS%fI%XLdXH+!L+xlCz45aj zjh4%2P8AE^DADYrWwl{wOG zcYdaM6bijcxf$yDte$K<@}Q%(k^$V zsG^K2H8rRCauZv2f$=kdb2!3;8I%sM`oHFz>kxEEXwf-I|fcQ$Ldi-!)MNuB8`7*cYxk z8l=86^S7z|+bJIiI}5?O-{HyQ15KZut4calov zGH2ZGGz%nQZ~C_1gdrfY?M+Vj2=7&G%BI-v5vSy+HdwC358jnY(Jv)~Trlbku&AG+ z!kpD56)*Lx|K$CS;KG+_z@9(y~hYo)mlE_RTWTu8` z6T!m2fY46?v;sp`q44Csc|{`5y%Lws9!Asej8>{?U&p_4tR#opnsMn=Am0#eNtm5# zHM_@UdmtrwWj-}UYim3ZaJ;H`AqQ-b1x2OS~VL%L+ku!90x~Z_rFk(yd4VEqq9S z77a=$SFu^?ara{njCh=H)6{|wS?bFQa!P-fL5k*jGwGu{OFUzfjyqUQ8L?kBt z(N5uE#fGA35pu=3+6~ae_3K$tHoYVHnn2TKBhW%^UsuA%;9I)%@ET0srG{K{S2`im z)UpLJYsv}dF;(B&de!oL;-&1}WdvSw>M+&kWmrf|&LbuB3NE(LzT`IcW>Rr;a2~mWj zmz76myqwWu#!C<;+tTf+xy3PNmopuqTv72HNm-p^$bReeanqnSi9E=8T_EYXC3tf3nXFC-Rwp&CTJfmvAaj ztNJJ)dudnLm`Tv~)VoAV;ct{ubAQS^7D$BjW;_GRZ=B~4T<8Tgs-=bVu8EX)Z=oY< zYY!JNi*gVM#fP0cRDhpD(Jv2xuf;mRi3J$Qq>xM?6E@t*g*Ck64XNGT`eBx03ptSQ zj?pqqk+5<_%X=lR$3557uxZxit2Mo^y(W8HKq~B0OCbKDVv<`&V4PdHazW^hJaQ_| z;od2RHn2)wjYAYhfs1JmSf1rH*=JvFul&3ky?c1lw0`ANt0oNg(#T!ok340|p@ry* z6?}l2KJoD;coi{mq-T%6=GRG?-P!Z#7TXbgpz*G4_j*<>v((2LhceQwVS2NgZIrzR-;t2z*FcA_jpdcc&p&UUpHJ3eO1785{4PbQx1#@ zKXwQ)O|Agno_tvkCRuntmVF4YCTP{i!2m!-;$0vi#%&P7&j>$&TZ8YM@6^_oT1E66 zzjW|z7ohhQ{Hz%xft;WA!3SH$Tm@?+3Q~N^S|Npz#j70uWGHu+w#K_m$eB_X3lA3m zK6?ByeAlM$digQ@W6Bd@DXhc8$DD9wa%V!@tBw=#!EN+r@#!2VJZPh&*PJ+Wvoaib z$iUYBRb*Tz4aFEZe83o0Y+%~PxZOmIJU)BQ64%bgCq0mcd;nf960N3%Qf^s0c-pYc z!gklAlESRFF<~=vLO`GS3I{W?Y(D=be$-5f6MTVdS zP+l-YY`Q+P3pnFOL<=)+9ptmg?E8{TXj>35KpjNmTD~&3-CQl^$8DYeDdEfD$S%@o z9&!y>$eQ44X&xiuezxzfLoWti;1K$*#wjcpZYE#5=sGTTOyr;};VbG>$}105bmbuF zZO`|^*w|h4Ys#mLG@;kXT?$rVs;tP0tjY@Q{Tfd(UgyLP|HEv70&TGgW62&Xo1B{WF^scjmuW&BSJ$5yXYJj!q8Z zLG>z>TZXy3l{2QZWGXoXtPAl<=U)lp!G4HVyuKnLJ1XGhY?lOG{{)hCSy%?f*b7PC zQ5C1AF#FIx+DllM5;wJh!sVBX!EB}y-W;?}%=MQdyXCD!J127h9w%`v%TW*&`xW+c zCKxedbKxOf%xBY*ar#m-dOL$Ac1CPUW0Q zp|ym3*d3r5Fc@ujH6UOQpp<=bgpp(NWV~}AXPD7jf8lcV=ZgQR(07N9pP;kB$cedI z9K2f^Mp9bn@^B$j1ghf*L@G$^so)d-L?q~9LC>t|WfbIUZF%tCJRaGRftY#I>jG_9 zP9{8LM$#}r#Il5^qV3Mp$!)89`Wo?SSkGgr$mm6t_IU)kckxzTfWxXt^d+hY7{6%+ATr?WoC~ zIFi$*VZ(-{G*RvrQ0CyQt0R36PeCc*GFU0p zow8~sA+;!Z89mx48h(xM1pQ9)1&gpSC(3}6LPg!f z2t#aqv|{+{#SRknQ#&YOtos;|&uct8Ma`1G4JtcyE(EzmMQ%#?pnv`h+OT*sH|`Wl z`{UFX2WWhNAx0>|`6NJ4B}vC$hr@3G5ErB#YtNB*M2rJRS-lvf>^{yaq*esw;DnX7Te1qxzLC-3No(#;G>a}0 z(24*Ga^Zl!oVQU`rbEF(wYXUqzED?s%Zq_7xDGiDb#dP{!6{);H`fw=!V!wKl#ud+ zPqH~P#KePi?vaqua}L&!n&S#quy0DJWXGq~Ib+ZeN0j|?9hgCf+d(6MKS@$!2)H)h zt5#LCLs-V7%Ff?0S#Up4`0fs-2D|i#f6w_TXVq;*!g-^wkq)(R_ou`k27snNX%TOp z<Ip6vIGtYkZ-oJP4wcc6p`|iD;XMSs~ zT2&LnfSM#&i!7herF}8xUE+Z)xi>E^`58gp&tiBTR$W>>b{L<|&l&QK%F@ReUwPB5 z`S92M;%jFjxQ8P8vTjYSbFsile2R5 z>ngUa+_I5gX##!8O@3WRsGzm&7Hes4RE9=&y}YW?v7*K*d-0D+v4PIY&-{O{wM{y3 zgoM1YHYmG4>V6W#{CB+|rUy}bV~AnHTB~ZaPcK;HpleT24<*f;If}*mA3GiMx2nD3GUmXw<_Y8(D`WEHSgPR-u*zV!j#@=^|gWd)>yRLx=$W9Ee}rA@ZPNvsjP6X z>gMW8C6b}D<)$d|rB^OCnxrh_FJAahn$b3+!6L67r&hJgbn5y)Y#49vO^Aomw^mig zWURAw!Z?XBc5ZH`1%4;D+PeyW*-}-hq+Su7*_+XnTE1luQVe?|n+IJk7=Zo6EdVwE ziVmbh3b|R2AjGA7SPV=FAcY1PEBDDUN)a6ya-K?;!`B;o=Vi!_XdjY)uA=Rz8Qg5X z3v=wltwN^$>YD+M$CM^M4tR_)X~efBTwU7ZPqhb+1D%-~p>Y#=D1-Vz>r=%jOVsXB z21cP~bh&76TBVJ`9u1Lwl}kYBvBRyqiRb#X*Em7Ms|V^1{s!s7?94iJVJuS=E)ij_ z3)hRkWS7*(T38?7Z}9#>h-wWA>FC}TJQUI~{>n6abdlTPbB9!qm~6`$gZ5wfSg}*5 z*teIlgJ}>$KBPK`A1VEz@6d}Alk2Pzbxx|g5RSB6+vOLdOM-cKX&Y60$yjs5yI)b5 zOp1lbg7(=|`Lr~wXn^+Fi`RsBSAfb`-0O$UQFylpwM;R=pLZ~G!iR^<9UW`XUe78}+b! zlug!V^%6Ip3l>IugX(q0ZbJ2@V2jmy*JFzp_cF1?s=aR5a|$(Uc`Gu-`l2YTE+(uL zrInfN$#czGXCX46RjTzeu)30xtN^t` zvBsa*khxA@6oyq-sPW;+&{{CPD%e;|STTy7dE892jn<;ttB#FA^kBkvqsWT*U>=j^ zxuhh3r-`k|`bD+GmxsXK$-3aiGog8^mw58DvW%BpTOm@Wd7?{J^TM!uGmX(D4!n1? z{c2&AsM1Uglhwtj4qAjl&k}t<-f5bWy1o<7k*0(W(#PbT~@;?6Jd@tZ~T|_@VeFkM)Za@`nzm zD)?d~n~1QDYyPr;2iWk_wnIs*C0K!mx(bxeZyJWhu#D{zz_;*^!;tu;aHxt$Xgr z4WkX8>qi=TdKUx`tjf*_jLI$v{!>;f&_CNK@+)3%-kED%ww&>3%FVY^(05d@$HdUE zz_6YYNppL-C^Zz;S^0UdFTw-ido#cNfUx*oBHQlFU7_5cgOzDR_k<}X7Y(obvD|C@ zYTX;n9+`Pfjx^dH2`PM-TgVwvGJKxkP_gYv(VuqW6*XUsudVES`aCXFSGW6V;t9(t z=OaazwtE1-an-gBJyKhZF45ud;yXgO$P?k>2DLPuMGy%-^QmFwQb;c zy%?1hUgxVMZ0R4h7XD7rd6CFz`;lJ7O3iA!%m0w@^ez)A%==kv`oM#~o&EF%bA>R9 z?#0)n%2580)SqbE$KNCwT_qf#*xnsnO7~``?rl5DKbJ6RDLlh3megmpMbV2WwyjJ9 zp)y~$Z`4>g#@Fqs&uP2P*X3^gAh@Xs*MJFc|USkr31Ui{jC@RlAT4i!+ zQ6gNphwn~V!Y%~Ld45xvi(RxQ~D-) z0e^Rb<{hTI@GAeef!wQ1A3uM?&3Zy8{}FX;AomG+EyTUQ{h;fI{ue#%*?#UvmCa8W zu~}qiqg|=&p~vW}X)JHLEyXaw{YCqUqx<~GbUHo9Xsfj`*DsJBTxfdT@%MyU_TB;X zEt5PyCE;fJ8h#94pXwZ6%c_k>44`k`Zn@o#w$qGw5g`!_2>Jy5f*!$3LGN<*5y~36 z=ovIh9`*;YL_&yHm{7hT{~56n(h6^a*u(9i1DrbGAb*j6n?jKfV2z93$Zm()aHCEsY$gU8 zE#l|^+a=Bt8A%G;@fuqa&5h;=xkAng?l#~g_Y9y0sE1lsD((6w zeDU!XjTClEj>c`RaPQ^~MoqM1_aibNJr;;W1_2z7BQz z4(>*sueJ(Cr+oWEQeXiekUQLyJ1W)+JU+Z~$XB>xlyW!EyrZlXKIXae^4{UlLO_uF z*U7r1bE(hd&h?SjrZ4XdJ8h;TS7>%S{XNhXo0l1zlGKx!K}pw(VT*HcDX!uv95X}w zi4l@@yQ`Yfkuu!De%V_5di(ui+h?2HJL?8o3v73)Bz$Sgc=%-)_W9(+2czI4G?*ZF z8H$f?-8io8Gho2$IK5Rx@!pzUEy?GKzF39@WH0aAkAH(rrrBPOUkqYGh z81+RkKxJmcB~;0J$x;bZVj^)1)v8{S3=_j*WOe08FfGn4&fkwPvJ$4r*~wi3uprTl zOG+2C0Jq~^GNw><_&!e7`Zre~$C1yW&s8MtZUc}y?laDMzzpyNkS&l{*`MKUoN@;< zAGkk!t%IQtG#5^W`f+YMER8qtK}>M8IC~rve3a18EC(n~S3_>Uj0@C&yGl>18P@1v zghOj6ML>SyZ~=&(&MTw7)i#s@8>5QcbI-$HY%zu zS;(QD=9YfV2m~fgA^k6lnkeP0N*lf-zfY%8{AVKST=M(f96k=!_}IEe7jPif=!c~ax-W4V%ksCLJtB zSn;mHH5TuEbt?C;#obr#*lf%`Q^9`sG2z)*YS7PThj#jXX)Zc%YunN$ypS__nsBqd z+qa!)fq6grddzbut^1sP>f=bVSJJA&U;NH}BE*Db|5|tnZGSSX>*V+?w)rRF#7A+v z_|NoELAA@}ZB7q}rkFF6KT`{TEX+A({;tWExsE{Z89!8U<)?b)pGOiro=3ew$_$hZ zb^6C|BG?~U#?`5D`a^MR2%wk(jk~>Jq&<1FwRacXuiNo$N}ffuNe%6Koi#twUY(!M zKNEgV<@jaFsD6t@b9Zf(dO9^s&U6%=jIfm{HnTk698=Jdccppfi0UuMk%%9&Kj$qq zCDb3bTbw#6pO%@*-6XC$72=v9_a!Gi>6J$^a@S5&SCOR4^e?l|wkgQ2n*7F4NY@n6 z1#0=pF1)AMSGS5Fj{FU`=6%WAx~4N)6?fiaHkP(L{F^B-oBau7n-lT)SsMLxR?XQ7 z@Qp+Mk2LzVUXCuVo-3W=V%c#q4E6#R{cOX(6bvSU8H~&ipT#Om7Yu_ zOxu!nY^Gwc)eLd|Tg70hn*TqSekRo?IGMl36X2!v>xGV(Q8-(E>>p|d|E*L0TQ2?d zNc>k^dMo05zPuFqhJHH&dFgXnq`!6c`%P;0XjXRSeD+K#KNtDCv%j}ASMr<84F0{| zSu4_(xmV31v-_E=7I5vS?`Yq1?Z3an%}j6~Ms(!N%=KVKOjkq$f12;t8R!(31Xg&% zf0xpi_&G962oSyOK#Kd^EwS~nr8qH$FZ)S$G&Er_n zCNnF+i4_T!(Prtba&dNX!QqHh9FF7+ZZsSYEWC^(NjE%rj05*pI2-}oTY~2VPzG$8 z*-Ou*#}rTo+{nbQ8;D7DSUQ4jpl<0#`ZxVj+n^knKMO|!jS{GK(=&a#o6NY_)r`bQ zJ>%8Z1RR)LnE-Z;K%`ibgG|16%WOQA^wBZnSSJQp@H;MgTGOXR zc7lWeJN@;eN3ya4lYT|%&0s`45eIr)`q7L1is1+(5;)iAVMJ;6<+&IRC#_&V7bAdv znume63ep0MLZyJAU~{e|5b)Ibb_o>90=q;URho2f&b4?;OKCmtIT$#0R4{I7E=DCv z6CTgUq^S_+VHENLzfdVuF#2SU93C94`Eq!I6>+`|5L@6=Jb@yOd6}yZ?DGPBmUwBz z&0IN4X~pk(7=<8B2t8MhVks?+J`V%u;6iMHpYc@i(KSyFEFCYcGCoHih>_>pAmB;l z`MxLM$+(63sHFMh1{UjFFkc81a6QhqO92^X8o95^Bi zF{}CgBjUh-#szXDu*&*8eR$%6F(=~5#P4h*vKY*mNY?ZVp&v6fQo2^90cyT+afy1; mN%S>FNm@-)Z~E3ay}sFr3|8Xw>L!8_a(INHp|iIO;(q}Ao~`!) diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.puml b/doc/crypto/figure/pake/wpa3-sae-pt.puml index 67ac1389..596c0102 100644 --- a/doc/crypto/figure/pake/wpa3-sae-pt.puml +++ b/doc/crypto/figure/pake/wpa3-sae-pt.puml @@ -16,7 +16,7 @@ Participant -> Participant: ""psa_key_derivation_input_bytes(INFO = password-identifier)"" end - Participant -> Participant: ""psa_key_derivation_output_key(WPA3_SAE_XX_PT)"" + Participant -> Participant: ""psa_key_derivation_output_key(WPA3_SAE_XX)"" note left: Compute password token //PT// diff --git a/doc/crypto/figure/pake/wpa3-sae-pt.svg b/doc/crypto/figure/pake/wpa3-sae-pt.svg index 0894911e..7287cd9b 100644 --- a/doc/crypto/figure/pake/wpa3-sae-pt.svg +++ b/doc/crypto/figure/pake/wpa3-sae-pt.svg @@ -1 +1 @@ -station (STA)Initial information : cipher suite,SSID,password[,password-identifier]alt[Hash-to-element generation of password element]psa_key_derivation_setup(WPA3_SAE_H2E)psa_key_derivation_input_bytes(SALT = SSID)psa_key_derivation_input_key(PASSWORD = password)optpsa_key_derivation_input_bytes(INFO = password-identifier)psa_key_derivation_output_key(WPA3_SAE_XX_PT)Compute password tokenPTUsePTfor authentication flow[Generation of the password element by looping]Usepasswordfor authentication flow \ No newline at end of file +station (STA)station (STA)Initial information : cipher suite,SSID,password[,password-identifier]alt[Hash-to-element generation of password element]psa_key_derivation_setup(WPA3_SAE_H2E)psa_key_derivation_input_bytes(SALT = SSID)psa_key_derivation_input_key(PASSWORD = password)optpsa_key_derivation_input_bytes(INFO = password-identifier)psa_key_derivation_output_key(WPA3_SAE_XX)Compute password tokenPTUsePTfor authentication flow[Generation of the password element by looping]Usepasswordfor authentication flow \ No newline at end of file From d31ba12c07e2eebe0881ffd1e322c777fd4fba94 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 11:43:21 +0100 Subject: [PATCH 13/17] Fix: missing CCM* wildcard in example macro definitions --- doc/crypto/appendix/specdef_values.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index 2b5e94a5..c002635b 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -190,9 +190,10 @@ Algorithm macros (((alg) & ~0x000000ff) == 0x08000300) #define PSA_ALG_IS_WILDCARD(alg) \ - ((PSA_ALG_GET_HASH(alg) == PSA_ALG_ANY_HASH) || \ - (((alg) & 0x7f008000) == 0x03008000) || \ - (((alg) & 0x7f008000) == 0x05008000)) + (PSA_ALG_GET_HASH(alg) == PSA_ALG_ANY_HASH || \ + ((alg) & 0x7f008000) == 0x03008000 || \ + ((alg) & 0x7f008000) == 0x05008000 || \ + (alg) == PSA_ALG_CCM_STAR_ANY_TAG) #define PSA_ALG_IS_WPA3_SAE(alg) \ (((alg) & ~0x000001ff) == 0x0a000800) From 613b82009dfe25f9a299bc0de0115d57609d0c25 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 12:01:18 +0100 Subject: [PATCH 14/17] Allow hash wildcard in WPA3-SAE H2E KDF --- doc/crypto/api/keys/policy.rst | 1 + doc/crypto/api/ops/key-derivation.rst | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/crypto/api/keys/policy.rst b/doc/crypto/api/keys/policy.rst index 1e312c22..1ae38fe2 100644 --- a/doc/crypto/api/keys/policy.rst +++ b/doc/crypto/api/keys/policy.rst @@ -37,6 +37,7 @@ The following algorithm policies are supported: * An algorithm built from `PSA_ALG_AT_LEAST_THIS_LENGTH_MAC()` permits any MAC algorithm from the same base class (for example, CMAC) which computes or verifies a MAC length greater than or equal to the length encoded in the wildcard algorithm. * An algorithm built from `PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG()` permits any AEAD algorithm from the same base class (for example, CCM) which computes or verifies a tag length greater than or equal to the length encoded in the wildcard algorithm. * The `PSA_ALG_CCM_STAR_ANY_TAG` wildcard algorithm permits the `PSA_ALG_CCM_STAR_NO_TAG` cipher algorithm, the `PSA_ALG_CCM` AEAD algorithm, and the :code:`PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_length)` truncated-tag AEAD algorithm for ``tag_length`` equal to 4, 8 or 16. +* The wildcard key policy :code:`PSA_ALG_WPA3_SAE_H2E(PSA_ALG_ANY_HASH)` permits a password key to be used with any WPA3-SAE cipher suite. When a key is used in a cryptographic operation, the application must supply the algorithm to use for the operation. This algorithm is checked against the key's permitted-algorithm policy. diff --git a/doc/crypto/api/ops/key-derivation.rst b/doc/crypto/api/ops/key-derivation.rst index 48d6bd23..9499c7d3 100644 --- a/doc/crypto/api/ops/key-derivation.rst +++ b/doc/crypto/api/ops/key-derivation.rst @@ -369,6 +369,7 @@ Key-derivation algorithms .. param:: hash_alg A hash algorithm: a value of type `psa_algorithm_t` such that :code:`PSA_ALG_IS_HASH(hash_alg)` is true. + This includes `PSA_ALG_ANY_HASH` when specifying the algorithm in a key policy. This KDF is defined in :cite-title:`IEEE-802.11` §12.4.4. This specifies the hash-to-element procedures for deriving a WPA3-SAE password token from a network SSID and password. @@ -387,12 +388,13 @@ Key-derivation algorithms The ``hash_alg`` parameter to `PSA_ALG_WPA3_SAE_H2E()` determines the hash function used for the derivation. The key attributes of the output key indicate the elliptic curve or finite field group used for the derivation. - See :secref:`wpa3-sae-keys` for details of the derivation procedures. - .. note:: - If the elliptic curve or finite field group specified in the key attributes is not compatible with the hash function used for the derivation, `psa_key_derivation_output_bytes()` returns :code:`PSA_ERROR_INVALID_ARGUMENT`. + If the elliptic curve or finite field group specified in the key attributes is not compatible with the hash function used for the derivation, `psa_key_derivation_output_bytes()` returns :code:`PSA_ERROR_INVALID_ARGUMENT`. + See :secref:`wpa3-sae-cipher-suites`.as specified by the + + :secref:`wpa3-sae-keys` provides details of the derivation procedures. - See also :secref:`wpa3-sae-cipher-suites`. + The wildcard key policy :code:`PSA_ALG_WPA3_SAE_H2E(PSA_ALG_ANY_HASH)` permits a password key to be used with any WPA3-SAE cipher suite. .. macro:: PSA_ALG_PBKDF2_HMAC :definition: /* specification-defined value */ From 4412e1f8d05d7e6e93ba013a7f1c504cf47e7dbe Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 13:46:28 +0100 Subject: [PATCH 15/17] fixup: typo in WPA3 KDF --- doc/crypto/api/ops/key-derivation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/crypto/api/ops/key-derivation.rst b/doc/crypto/api/ops/key-derivation.rst index 9499c7d3..1f4bef13 100644 --- a/doc/crypto/api/ops/key-derivation.rst +++ b/doc/crypto/api/ops/key-derivation.rst @@ -390,7 +390,7 @@ Key-derivation algorithms The key attributes of the output key indicate the elliptic curve or finite field group used for the derivation. If the elliptic curve or finite field group specified in the key attributes is not compatible with the hash function used for the derivation, `psa_key_derivation_output_bytes()` returns :code:`PSA_ERROR_INVALID_ARGUMENT`. - See :secref:`wpa3-sae-cipher-suites`.as specified by the + See :secref:`wpa3-sae-cipher-suites`. :secref:`wpa3-sae-keys` provides details of the derivation procedures. From 81f543ee3401f076e78056c1f929234f281fbca2 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Fri, 3 Oct 2025 13:56:53 +0100 Subject: [PATCH 16/17] Wildcard policies for WPA3-SAE password tokens --- doc/crypto/api.db/psa/crypto.h | 1 + doc/crypto/api/keys/types.rst | 2 ++ doc/crypto/api/ops/pake.rst | 32 ++++++++++++++++---------- doc/crypto/appendix/specdef_values.rst | 14 +++++------ 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index db97ae98..12a565e0 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -190,6 +190,7 @@ typedef struct psa_custom_key_parameters_t { #define PSA_ALG_TLS12_PSK_TO_MS(hash_alg) /* specification-defined value */ #define PSA_ALG_TRUNCATED_MAC(mac_alg, mac_length) \ /* specification-defined value */ +#define PSA_ALG_WPA3_SAE_ANY ((psa_algorithm_t)0x0a000800) #define PSA_ALG_WPA3_SAE_FIXED(hash_alg) /* specification-defined value */ #define PSA_ALG_WPA3_SAE_GDH(hash_alg) /* specification-defined value */ #define PSA_ALG_WPA3_SAE_H2E(hash_alg) /* specification-defined value */ diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index ac05372c..292b7247 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -978,6 +978,8 @@ The password token can be stored as a key object, and later used in the PAKE ope WPA3-SAE password tokens are defined for both elliptic curve and finite field groups. +See :secref:`wpa3-sae-passwords`. + .. macro:: PSA_KEY_TYPE_WPA3_SAE_ECC :definition: /* specification-defined value */ diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index 3fafc584..d3e0d6bf 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -2179,10 +2179,10 @@ WPA3-SAE defines the following two methods for deriving the password element (PW This derivation occurs as part of the authentication flow. * - Hash-to-element method - - Derive a password token (PT) element from the password, using the hash-to-curve procedure for elliptic curve groups, and a direct method for finite field groups. - This derivation can be carried out when the network SSID and password is provisioned to the device, and the PT stored as part of the configuration. + - Derive a password token element PT from the password, using the hash-to-curve procedure for elliptic curve groups, and a direct method for finite field groups. + This derivation can be carried out when the network SSID and password is provisioned to the device, and PT is stored as part of the configuration. - During authentication, the PWE is derived from the PT. + During authentication, PWE is derived from PT. The hash-to-element method is recommended, as it is less vulnerable to timing-based attacks, and reduces the authentication time. @@ -2206,15 +2206,17 @@ To use the hash-to-element method: 1. Import the password into a key of type `PSA_KEY_TYPE_PASSWORD`. The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. -#. A WPA3-SAE password token (PT) is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. +#. A WPA3-SAE password token is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. The `PSA_ALG_WPA3_SAE_H2E()` algorithm is parameterized by the hash used in the required WPA3-SAE cipher suite. - The PT is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC()` or `PSA_KEY_TYPE_WPA3_SAE_DH()`. + The password token is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC()` or `PSA_KEY_TYPE_WPA3_SAE_DH()`. The key type is parameterized by the elliptic curve or finite field Diffie-Hellman group used in the required WPA3-SAE cipher suite. - The PT key must be protected at least as well as the password. + The password token key must be protected at least as well as the password. -#. Pass the PT key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. +#. Pass the password token key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. + +The wildcard key policy `PSA_ALG_WPA3_SAE_ANY` permits a password token key to be used with both the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. The following steps demonstrate the derivation of a password token for use with the group-dependent-hash variant of WPA3-SAE. The selected cipher suite in the example is IANA Group 20: ECC using secp384r1, hash function SHA-384. @@ -2515,10 +2517,16 @@ WPA3-SAE algorithms WPA3-SAE algorithms with a group-dependent size for the output key, are constructed using :code:`PSA_ALG_WPA3_SAE_GDH(hash_alg)`. -.. todo:: +.. macro:: PSA_ALG_WPA3_SAE_ANY + :definition: ((psa_algorithm_t)0x0a000800) + + .. summary:: + A wildcard algorithm that permits a WPA3-SAE password token key to be used in hash-to-element and group-dependent-hash variants of the WPA3-SAE PAKE algorithm. + + .. versionadded:: 1.4 + + If a WPA3-SAE password token key specifies `PSA_ALG_WPA3_SAE_ANY` as its permitted algorithm, then the key can be used with the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. - Wildcard algorithms are needed: + .. todo:: - 1. A WPA3-SAE password can be derived for use with different cipher suites (ECC vs DH, group size affects hash algorithm selection). Desirable to have a wildcard that does not constrain which WPA3-SAE key derivation algorithm is permitted, so that password can be persisted in key store. - 2. A WPA3-SAE password key to be used in both the key-derivation (to password token), and the `PSA_ALG_IS_WPA3_SAE_FIXED` algorithm (for looping). The same password can be used in different ways that is only discovered at runtime in the device - based on protocol interactions. Workaround: could make copies of the password? - 3. A SPA3-SAE password token key to be used in both the `PSA_ALG_IS_WPA3_SAE_FIXED` and `PSA_ALG_IS_WPA3_SAE_GDH` algorithms? - Less pressing as this differs in AKM selector, and device already has need to store multiple password tokens for a single password, based on cipher suite selection (cyclic group that is used): but GDH/FIXED does not affect PT derivation so wildcard could be helpful. + We could extend this wildcard key policy to also cover the cases for password keys being used in the PAKE and KDF algorithms for WPA3-SAE? diff --git a/doc/crypto/appendix/specdef_values.rst b/doc/crypto/appendix/specdef_values.rst index c002635b..235920ff 100644 --- a/doc/crypto/appendix/specdef_values.rst +++ b/doc/crypto/appendix/specdef_values.rst @@ -217,25 +217,25 @@ Algorithm macros ((ka_alg) | (kdf_alg)) #define PSA_ALG_KEY_AGREEMENT_GET_BASE(alg) \ - ((psa_algorithm_t)((alg) & 0xff7f0000)) + ((psa_algorithm_t) ((alg) & 0xff7f0000)) #define PSA_ALG_KEY_AGREEMENT_GET_KDF(alg) \ - ((psa_algorithm_t)((alg) & 0xfe80ffff)) + ((psa_algorithm_t) ((alg) & 0xfe80ffff)) #define PSA_ALG_PBKDF2_HMAC(hash_alg) \ - ((psa_algorithm_t)(0x08800100 | ((hash_alg) & 0x000000ff))) + ((psa_algorithm_t) (0x08800100 | ((hash_alg) & 0x000000ff))) #define PSA_ALG_RSA_OAEP(hash_alg) \ - ((psa_algorithm_t)(0x07000300 | ((hash_alg) & 0x000000ff))) + ((psa_algorithm_t) (0x07000300 | ((hash_alg) & 0x000000ff))) #define PSA_ALG_RSA_PKCS1V15_SIGN(hash_alg) \ - ((psa_algorithm_t)(0x06000200 | ((hash_alg) & 0x000000ff))) + ((psa_algorithm_t) (0x06000200 | ((hash_alg) & 0x000000ff))) #define PSA_ALG_RSA_PSS(hash_alg) \ - ((psa_algorithm_t)(0x06000300 | ((hash_alg) & 0x000000ff))) + ((psa_algorithm_t) (0x06000300 | ((hash_alg) & 0x000000ff))) #define PSA_ALG_RSA_PSS_ANY_SALT(hash_alg) \ - ((psa_algorithm_t)(0x06001300 | ((hash_alg) & 0x000000ff))) + ((psa_algorithm_t) (0x06001300 | ((hash_alg) & 0x000000ff))) #define PSA_ALG_SP800_108_COUNTER_HMAC(hash_alg) \ ((psa_algorithm_t) (0x08000700 | ((hash_alg) & 0x000000ff))) From cb2157a64a75888724e1045311b2f71810693ae7 Mon Sep 17 00:00:00 2001 From: Andrew Thoelke Date: Wed, 8 Oct 2025 13:30:15 +0100 Subject: [PATCH 17/17] Use a single flexible wildcard for WPA3-SAE passwords and password tokens. --- doc/crypto/api.db/psa/crypto.h | 2 +- doc/crypto/api/keys/policy.rst | 2 +- doc/crypto/api/keys/types.rst | 8 +++++++ doc/crypto/api/ops/key-derivation.rst | 4 +++- doc/crypto/api/ops/pake.rst | 33 +++++++++++++++------------ doc/crypto/appendix/encodings.rst | 5 ++++ doc/crypto/appendix/history.rst | 1 + 7 files changed, 37 insertions(+), 18 deletions(-) diff --git a/doc/crypto/api.db/psa/crypto.h b/doc/crypto/api.db/psa/crypto.h index 12a565e0..c26a1e31 100644 --- a/doc/crypto/api.db/psa/crypto.h +++ b/doc/crypto/api.db/psa/crypto.h @@ -190,7 +190,7 @@ typedef struct psa_custom_key_parameters_t { #define PSA_ALG_TLS12_PSK_TO_MS(hash_alg) /* specification-defined value */ #define PSA_ALG_TRUNCATED_MAC(mac_alg, mac_length) \ /* specification-defined value */ -#define PSA_ALG_WPA3_SAE_ANY ((psa_algorithm_t)0x0a000800) +#define PSA_ALG_WPA3_SAE_ANY ((psa_algorithm_t)0x0a0088ff) #define PSA_ALG_WPA3_SAE_FIXED(hash_alg) /* specification-defined value */ #define PSA_ALG_WPA3_SAE_GDH(hash_alg) /* specification-defined value */ #define PSA_ALG_WPA3_SAE_H2E(hash_alg) /* specification-defined value */ diff --git a/doc/crypto/api/keys/policy.rst b/doc/crypto/api/keys/policy.rst index 1ae38fe2..40dc7704 100644 --- a/doc/crypto/api/keys/policy.rst +++ b/doc/crypto/api/keys/policy.rst @@ -37,7 +37,7 @@ The following algorithm policies are supported: * An algorithm built from `PSA_ALG_AT_LEAST_THIS_LENGTH_MAC()` permits any MAC algorithm from the same base class (for example, CMAC) which computes or verifies a MAC length greater than or equal to the length encoded in the wildcard algorithm. * An algorithm built from `PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG()` permits any AEAD algorithm from the same base class (for example, CCM) which computes or verifies a tag length greater than or equal to the length encoded in the wildcard algorithm. * The `PSA_ALG_CCM_STAR_ANY_TAG` wildcard algorithm permits the `PSA_ALG_CCM_STAR_NO_TAG` cipher algorithm, the `PSA_ALG_CCM` AEAD algorithm, and the :code:`PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_length)` truncated-tag AEAD algorithm for ``tag_length`` equal to 4, 8 or 16. -* The wildcard key policy :code:`PSA_ALG_WPA3_SAE_H2E(PSA_ALG_ANY_HASH)` permits a password key to be used with any WPA3-SAE cipher suite. +* The wildcard key policy `PSA_ALG_WPA3_SAE_ANY` permits a password key or WPA3-SAE password token key to be used with any WPA3-SAE cipher suite. When a key is used in a cryptographic operation, the application must supply the algorithm to use for the operation. This algorithm is checked against the key's permitted-algorithm policy. diff --git a/doc/crypto/api/keys/types.rst b/doc/crypto/api/keys/types.rst index 292b7247..e545a701 100644 --- a/doc/crypto/api/keys/types.rst +++ b/doc/crypto/api/keys/types.rst @@ -996,6 +996,10 @@ See :secref:`wpa3-sae-passwords`. To construct a WPA3-SAE password token, it must be output from key derivation operation using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + .. note:: + + To use a password token key with both `PSA_ALG_WPA3_SAE_FIXED` and `PSA_ALG_WPA3_SAE_GDH` algorithms, create the key with the wildcard `PSA_ALG_WPA3_SAE_ANY` permitted algorithm. + .. subsection:: Compatible algorithms .. hlist:: @@ -1044,6 +1048,10 @@ See :secref:`wpa3-sae-passwords`. To construct a WPA3-SAE password token, it must be output from key derivation operation using the `PSA_ALG_WPA3_SAE_H2E` algorithm. + .. note:: + + To use a password token key with both `PSA_ALG_WPA3_SAE_FIXED` and `PSA_ALG_WPA3_SAE_GDH` algorithms, create the key with the wildcard `PSA_ALG_WPA3_SAE_ANY` permitted algorithm. + .. subsection:: Compatible algorithms .. hlist:: diff --git a/doc/crypto/api/ops/key-derivation.rst b/doc/crypto/api/ops/key-derivation.rst index 1f4bef13..c3cffcbb 100644 --- a/doc/crypto/api/ops/key-derivation.rst +++ b/doc/crypto/api/ops/key-derivation.rst @@ -394,7 +394,9 @@ Key-derivation algorithms :secref:`wpa3-sae-keys` provides details of the derivation procedures. - The wildcard key policy :code:`PSA_ALG_WPA3_SAE_H2E(PSA_ALG_ANY_HASH)` permits a password key to be used with any WPA3-SAE cipher suite. + .. note:: + + To use a single password key with `PSA_ALG_WPA3_SAE_H2E` for any WPA3-SAE cipher suite, create the key with the wildcard `PSA_ALG_WPA3_SAE_ANY` permitted algorithm. .. macro:: PSA_ALG_PBKDF2_HMAC :definition: /* specification-defined value */ diff --git a/doc/crypto/api/ops/pake.rst b/doc/crypto/api/ops/pake.rst index d3e0d6bf..0b07737c 100644 --- a/doc/crypto/api/ops/pake.rst +++ b/doc/crypto/api/ops/pake.rst @@ -2168,7 +2168,7 @@ For example, the following code creates a PAKE cipher suite for WPA3-SAE using h WPA3-SAE password processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -WPA3-SAE defines the following two methods for deriving the password element (PWE) from the password: +WPA3-SAE defines the following two methods for deriving the password element PWE from the password: .. list-table:: :widths: 1 4 @@ -2193,20 +2193,23 @@ The hash-to-element method is recommended, as it is less vulnerable to timing-ba WPA3-SAE password processing +For both methods, the password must be imported as a key of type `PSA_KEY_TYPE_PASSWORD`. +The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. + +.. note:: + + `[IEEE-802.11]` specifies that the same password is used for any configured WPA3-SAE cipher suites, and with any configured PWE-derivation methods. + The wildcard key policy `PSA_ALG_WPA3_SAE_ANY` permits a password key to be used for any valid derivation method, and with any valid WPA3-SAE cipher suite. + .. rubric:: Looping method -To use the looping method, import the password into a key of type `PSA_KEY_TYPE_PASSWORD`. -The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. -Provide this key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. +Provide the password key directly to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. .. rubric:: Hash-to-element method To use the hash-to-element method: -1. Import the password into a key of type `PSA_KEY_TYPE_PASSWORD`. - The password must be encoded as defined in `[IEEE-802.11]` §12.4.3. - -#. A WPA3-SAE password token is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. +1. A WPA3-SAE password token is derived from the WPA3-SAE password, using a key-derivation operation with the `PSA_ALG_WPA3_SAE_H2E()` algorithm. The `PSA_ALG_WPA3_SAE_H2E()` algorithm is parameterized by the hash used in the required WPA3-SAE cipher suite. The password token is output from the key-derivation operation as a key of type `PSA_KEY_TYPE_WPA3_SAE_ECC()` or `PSA_KEY_TYPE_WPA3_SAE_DH()`. @@ -2216,7 +2219,9 @@ To use the hash-to-element method: #. Pass the password token key to the WPA3-SAE PAKE operation in the call to `psa_pake_setup()`. -The wildcard key policy `PSA_ALG_WPA3_SAE_ANY` permits a password token key to be used with both the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. +.. note:: + + The wildcard key policy `PSA_ALG_WPA3_SAE_ANY` permits a password token key to be used with both the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. The following steps demonstrate the derivation of a password token for use with the group-dependent-hash variant of WPA3-SAE. The selected cipher suite in the example is IANA Group 20: ECC using secp384r1, hash function SHA-384. @@ -2518,15 +2523,13 @@ WPA3-SAE algorithms WPA3-SAE algorithms with a group-dependent size for the output key, are constructed using :code:`PSA_ALG_WPA3_SAE_GDH(hash_alg)`. .. macro:: PSA_ALG_WPA3_SAE_ANY - :definition: ((psa_algorithm_t)0x0a000800) + :definition: ((psa_algorithm_t)0x0a0088ff) .. summary:: - A wildcard algorithm that permits a WPA3-SAE password token key to be used in hash-to-element and group-dependent-hash variants of the WPA3-SAE PAKE algorithm. + A wildcard algorithm for WPA3-SAE password keys and password token keys. .. versionadded:: 1.4 - If a WPA3-SAE password token key specifies `PSA_ALG_WPA3_SAE_ANY` as its permitted algorithm, then the key can be used with the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. - - .. todo:: + If a password key (key type `PSA_KEY_TYPE_PASSWORD`) specifies `PSA_ALG_WPA3_SAE_ANY` as its permitted algorithm, then the key can be used for any WPA3-SAE cipher suite with the `PSA_ALG_WPA3_SAE_H2E` key-derivation algorithm, and with the `PSA_ALG_WPA3_SAE_FIXED` PAKE algorithm. - We could extend this wildcard key policy to also cover the cases for password keys being used in the PAKE and KDF algorithms for WPA3-SAE? + If a WPA3-SAE password token key specifies `PSA_ALG_WPA3_SAE_ANY` as its permitted algorithm, then the key can be used with both the `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()` PAKE algorithms. diff --git a/doc/crypto/appendix/encodings.rst b/doc/crypto/appendix/encodings.rst index 65976e87..17c26d6d 100644 --- a/doc/crypto/appendix/encodings.rst +++ b/doc/crypto/appendix/encodings.rst @@ -492,9 +492,14 @@ The permitted values of HASH-TYPE (see :numref:`table-hash-type`) depend on the SPAKE2+ for Matter, ``0x06``, :code:`PSA_ALG_SPAKE2P_MATTER`, ``0x0A000609`` WPA3-SAE, ``0x08``, :code:`PSA_ALG_WPA3_SAE_FIXED(hash)`, ``0x0A0008hh`` :sup:`a` WPA3-SAE (GDH), ``0x09``, :code:`PSA_ALG_WPA3_SAE_GDH(hash)`, ``0x0A0009hh`` :sup:`a` + *WPA3-SAE wildcard* :sup:`b c`, ``0x88``, `PSA_ALG_WPA3_SAE_ANY`, ``0x0A0088FF`` a. ``hh`` is the HASH-TYPE for the hash algorithm, ``hash``, used to construct the key-derivation algorithm. +b. The wildcard algorithm `PSA_ALG_WPA3_SAE_ANY` permits a password key to be used for any WPA3-SAE cipher suite with the `PSA_ALG_WPA3_SAE_H2E` key-derivation algorithm, and with the `PSA_ALG_WPA3_SAE_FIXED` PAKE algorithm. + +c. The wildcard algorithm `PSA_ALG_WPA3_SAE_ANY` permits a WPA3-SAE password token key to be used for both the `PSA_ALG_WPA3_SAE_FIXED` and `PSA_ALG_WPA3_SAE_GDH` PAKE algorithms. + .. _key-type-encoding: Key type encoding diff --git a/doc/crypto/appendix/history.rst b/doc/crypto/appendix/history.rst index 878d3882..855929d8 100644 --- a/doc/crypto/appendix/history.rst +++ b/doc/crypto/appendix/history.rst @@ -32,6 +32,7 @@ Changes to the API - Added the `PSA_ALG_WPA3_SAE_H2E()` KDF for generating a WPA3-SAE password token from a password. - Added WPA3-SAE PAKE algorithms, `PSA_ALG_WPA3_SAE_FIXED()` and `PSA_ALG_WPA3_SAE_GDH()`. - Added finite field Diffie-Hellman family `PSA_DH_FAMILY_RFC3526`, which provides cyclic groups used for WPA3-SAE. + - Added wildcard key policy `PSA_ALG_WPA3_SAE_ANY` to permit password and password token keys to be used in any WPA3-SAE cipher suite. See :secref:`pake-wpa3-sae`.