Code Monkey home page Code Monkey logo

django-eidas-specific-node's Introduction

eIDAS Specific Node

Django implementation of member specific Connector and Proxy Service for CEF eIDAS Node version 2.4 and later.

Requirements

Changes

See CHANGELOG.md for changes.

Specific Proxy Service

Sample settings are provided in samples/proxy_service_settings.py.

Usage

To use eIDAS Proxy Service, adjust Django settings:

  • Set up Django template engine and static files.
  • Add 'eidas_node.proxy_service.apps.ProxyServiceConfig' to INSTALLED_APPS.
  • Set ROOT_URLCONF to 'eidas_node.proxy_service.urls' or include it in your URL configuration.
  • Provide mandatory configuration options PROXY_SERVICE_REQUEST_TOKEN, PROXY_SERVICE_RESPONSE_TOKEN, PROXY_SERVICE_LIGHT_STORAGE, and PROXY_SERVICE_IDENTITY_PROVIDER (see below).

Settings

PROXY_SERVICE_REQUEST_TOKEN

Settings of a light token corresponding to an incoming light request. A dictionary with following items:

  • HASH_ALGORITHM (optional, default 'sha256'): A hash algorithm used for token digest.
  • SECRET (required): A token secret shared with eIDAS node.
  • ISSUER (required): An issuer of the light token.
  • LIFETIME (optional, default 10): A lifetime of the light token in minutes until is its considered expired. Set to 0 for unlimited lifetime.
  • PARAMETER_NAME (optional, default 'token'): The name of the HTTP POST parameter to provide encoded light token.

PROXY_SERVICE_RESPONSE_TOKEN

Settings of a light token corresponding to an outgoing light response. A dictionary with following items:

  • HASH_ALGORITHM (optional, default 'sha256'): A hash algorithm used for token digest.
  • SECRET (required): A token secret shared with eIDAS node.
  • ISSUER (required): An issuer of the light token.
  • PARAMETER_NAME (optional, default 'token'): The name of the HTTP POST parameter to provide encoded light token.

PROXY_SERVICE_LIGHT_STORAGE

Settings for a storage of light requests and responses. A dictionary with following items:

  • BACKEND (optional, default 'eidas_node.storage.ignite.IgniteStorage'): The backend class for communication with the light storage.
  • OPTIONS (required): A dictionary with configuration of the selected backend. The IgniteStorage backend expects following options:
    • host: Apache Ignite service host.
    • port: Apache Ignite service port.
    • request_cache_name: The cache to retrieve light requests (e.g., nodeSpecificProxyserviceRequestCache).
    • response_cache_name: The cache to store light responses (e.g., specificNodeProxyserviceResponseCache).
    • timeout: A timeout for socket operations.

PROXY_SERVICE_IDENTITY_PROVIDER

Settings for the interaction with Identity Provider. A dictionary with following items:

  • ENDPOINT (required): The URL where the Identity Provider expects authentication requests.
  • REQUEST_ISSUER (required): The issuer of the authentication request registered at Identity Provider.
  • KEY_SOURCE (optional, default None): The source ('file' or 'engine') of a key to decrypt Identity Provider's authentication response.
  • KEY_LOCATION (optional, default None): The path of a key to decrypt Identity Provider's authentication response.
  • CERT_FILE (optional, default None): The path of a certificate to verify the signature of Identity Provider's authentication response.
  • REQUEST_SIGNATURE (dictionary, required, use {} to disable signing): Options for signing SAML requests sent to Service Provider:
    • KEY_SOURCE (required, string): The source ('file' or 'engine') of a signing key.
    • KEY_LOCATION (required, string): The path to a signing key.
    • CERT_FILE: (required, string): The path to the corresponding certificate.
    • SIGNATURE_METHOD (optional, string, default RSA_SHA512): XML signature method.
    • DIGEST_METHOD (optional, string, default SHA512): XML digest method.

PROXY_SERVICE_EIDAS_NODE

Settings for the interaction with eIDAS Node. A dictionary with following items:

  • PROXY_SERVICE_RESPONSE_URL (required): The URL where eIDAS Node expects authentication responses (e.g., https://test.example.net/EidasNode/SpecificProxyServiceResponse).
  • RESPONSE_ISSUER (required): The issuer for light responses specified in eIDAS Node configuration.

PROXY_SERVICE_TRANSIENT_NAME_ID_FALLBACK

Optional boolean, disabled by default. If enabled, PROXY_SERVICE_AUXILIARY_STORAGE must be set too. If the transient name ID format is requested in the request but a different format is provided in the response, a new random transient ID is generated instead of the provided ID.

PROXY_SERVICE_TRACK_COUNTRY_CODE

Optional boolean, disabled by default. If enabled, PROXY_SERVICE_AUXILIARY_STORAGE must be set too. Once enabled, the country code of the request is logged along with the status of the corresponding request.

PROXY_SERVICE_AUXILIARY_STORAGE

An auxiliary storage to hold some response metadata needed during request processing. It is required if PROXY_SERVICE_TRACK_COUNTRY_CODE or PROXY_SERVICE_TRANSIENT_NAME_ID_FALLBACK is enabled.

A dictionary with following items:

  • BACKEND (optional, default 'eidas_node.storage.ignite.AuxiliaryIgniteStorage'): The backend class for communication with the light storage.
  • OPTIONS (required): A dictionary with configuration of the selected backend. The AuxiliaryIgniteStorage backend expects following options:
    • host: Apache Ignite service host.
    • port: Apache Ignite service port.
    • cache_name: The cache to store the data.
    • prefix: The prefix for cache keys (optional).
    • timeout: A timeout for socket operations.

PROXY_SERVICE_LEVELS_OF_ASSURANCE

An optional mapping of Authentication Context Classes (str) to Levels of Assurance (eidas_node.constants.LevelOfAssurance). The default mapping is empty so other classes than Levels of Assurance are unrecognized and propagated as an error. Example:

from eidas_node.constants import LevelOfAssurance
PROXY_SERVICE_LEVELS_OF_ASSURANCE = {
    'urn:oasis:names:tc:SAML:2.0:ac:classes:Password': LevelOfAssurance.LOW,
}

Customization

You can customize the authorization flow by subclassing view classes in eidas_node.proxy_service.views, overriding necessary methods and adjusting URL configuration.

CZ NIA

eidas_node.proxy_service.cznia (ROOT_URLCONF = 'eidas_node.proxy_service.cznia.urls') contains modifications required for CZ NIA (the official identity provider of the Czech Republic) with following settings:

  • PROXY_SERVICE_STRIP_PREFIX (boolean, optional, default False): If the Subject ID starts with a 'CZ/CZ/' prefix, it is stripped.

Specific Connector

Sample settings are provided in samples/connector_settings.py.

Usage

To use eIDAS Connector, adjust Django settings:

  • Set up Django template engine and static files.
  • Add 'eidas_node.connector.apps.ConnectorConfig' to INSTALLED_APPS.
  • Set ROOT_URLCONF to 'eidas_node.connector.urls' or include it in your URL configuration.
  • Provide mandatory configuration options CONNECTOR_REQUEST_TOKEN, CONNECTOR_RESPONSE_TOKEN, CONNECTOR_LIGHT_STORAGE, and CONNECTOR_SERVICE_PROVIDER (see below).

Views

Setting ROOT_URLCONF to eidas_node.connector.urls will provide you with three main views:

  • /CountrySelector: Accepts a SAML Request and Relay State from Service Provider and lets user select his/her country unless it has already been provided. The SAML Request is then forwarded to /ServiceProviderRequest endpoint.

    • Method: HTTP POST.
    • POST Parameters:
      • SAMLRequest (required): A SAML request to forward to eIDAS Network.
      • RelayState (required): A relay state.
      • country or the value set in CONNECTOR_SERVICE_PROVIDER['COUNTRY_PARAMETER'] (optional): Citizen country code.
  • /ServiceProviderRequest: Accepts a SAML Request, Relay State and citizen country code from Service Provider and forwards the request to eIDAS Network.

    • Method: HTTP POST.
    • POST Parameters:
      • SAMLRequest (required): A SAML request to forward to eIDAS Network.
      • RelayState (required): A relay state.
      • country or the value set in CONNECTOR_SERVICE_PROVIDER['COUNTRY_PARAMETER'] (required): Citizen country code.
  • /ConnectorResponse: Accepts a light token from eIDAS Network and forwards corresponding light response to Service Provider.

    • Method: HTTP POST.
    • POST Parameters:
      • token or the value set in CONNECTOR_RESPONSE_TOKEN['PARAMETER_NAME'](required): A light token corresponding to a light response.

Setting ROOT_URLCONF to eidas_node.connector.demo.urls will provide you with two additional views:

  • /DemoServiceProviderRequest: A demo service provider page for sending preset SAML requests to Specific Connector.
  • /DemoServiceProviderResponse: A demo service provider page for displaying SAML responses from Specific Connector.

Settings

CONNECTOR_REQUEST_TOKEN

Settings of a light token corresponding to an outgoing light request. A dictionary with following items:

  • HASH_ALGORITHM (optional, default 'sha256'): A hash algorithm used for token digest.
  • SECRET (required): A token secret shared with eIDAS node.
  • ISSUER (required): An issuer of the light token.
  • PARAMETER_NAME (optional, default 'token'): The name of the HTTP POST parameter to provide encoded light token.

CONNECTOR_RESPONSE_TOKEN

Settings of a light token corresponding to an incoming light response. A dictionary with following items:

  • HASH_ALGORITHM (optional, default 'sha256'): A hash algorithm used for token digest.
  • SECRET (required): A token secret shared with eIDAS node.
  • ISSUER (required): An issuer of the light token.
  • PARAMETER_NAME (optional, default 'token'): The name of the HTTP POST parameter to provide encoded light token.
  • LIFETIME (optional, default 10): A lifetime of the light token in minutes until is its considered expired. Set to 0 for unlimited lifetime.

CONNECTOR_LIGHT_STORAGE

Settings for a storage of light requests and responses. A dictionary with following items:

  • BACKEND (optional, default 'eidas_node.storage.ignite.IgniteStorage'): The backend class for communication with the light storage.
  • OPTIONS (required): A dictionary with configuration of the selected backend. The IgniteStorage backend expects following options:
    • host: Apache Ignite service host.
    • port: Apache Ignite service port.
    • request_cache_name: The cache to retrieve light requests (e.g., specificNodeConnectorRequestCache).
    • response_cache_name: The cache to store light responses (e.g., nodeSpecificConnectorResponseCache).
    • timeout: A timeout for socket operations in seconds.

CONNECTOR_SERVICE_PROVIDER

Settings for the interaction with Service Provider. A dictionary with following items:

  • ENDPOINT (required): The URL where the Service Provider expects authentication responses.
  • CERT_FILE (optional, default None): The path of a certificate to verify the signature of Service Provider's authentication requests.
  • REQUEST_ISSUER (required): The expected issuer of the Service Provider's authentication request.
  • RESPONSE_ISSUER (required): The issuer of the authentication response registered at Service Provider.
  • COUNTRY_PARAMETER (optional, default country): The name of a POST parameter containing citizen country code for /CitizenCountrySelector and /ServiceProviderRequest views.
  • RESPONSE_SIGNATURE (dictionary, required, use {} to disable signing): Options for signing SAML responses returned to Service Provider:
    • KEY_SOURCE (required, string): The source ('file' or 'engine') to a signing key.
    • KEY_LOCATION (required, string): The path to a signing key.
    • CERT_FILE: (required, string): The path to the corresponding certificate.
    • SIGNATURE_METHOD (optional, string, default RSA_SHA512): XML signature method.
    • DIGEST_METHOD (optional, string, default SHA512): XML digest method.
  • RESPONSE_ENCRYPTION (dictionary, required, use {} to disable encryption): Options for encrypting SAML responses returned to Service Provider:
    • CERT_FILE: (required, string): The path to the certificate to encrypt a generated encryption key.
    • ENCRYPTION_METHOD (optional, string, default AES256_GCM): XML encryption method. AES256_GCM, AES192_GCM, and AES168_GCM are available with libxmlsec1 >= 1.2.27. AES256_CBC, AES192_CBC, AES168_CBC, and TRIPLEDES_CBC are supported by older libxmlsec1 but should not be used without consideration of possibly severe security risks.
    • KEY_TRANSPORT (optional, string, default RSA_OAEP_MGF1P): XML key transport method. RSA (RSA Version 1.5) and RSA_OAEP_MGF1P (RSA-OAEP with MGF1-SHA1 as a mask generation function) are supported.
  • RESPONSE_VALIDITY (int, optional, default 10): The validity of the SAML response in minutes.

CONNECTOR_EIDAS_NODE

Settings for the interaction with eIDAS Node. A dictionary with following items:

  • CONNECTOR_REQUEST_URL (required): The URL where eIDAS Node expects authentication requests (e.g., https://test.example.net/EidasNode/SpecificConnectorRequest).
  • REQUEST_ISSUER (required): The issuer for light requests specified in eIDAS Node configuration.

CONNECTOR_ALLOWED_ATTRIBUTES

A set containing URI names (strings, e.g. 'http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier') of attributes that a service provider can request. Other attributes are dropped from the authentication request. All eIDAS natural and legal person attributes are enabled by default. An empty set disables the filter.

CONNECTOR_SELECTOR_COUNTRIES

A list of pairs with country code and name to be displayed in citizen country selector (/CitizenCountrySelector). Default is all 28 countries of EU.

CONNECTOR_TRACK_COUNTRY_CODE

Optional boolean, disabled by default. If enabled, CONNECTOR_AUXILIARY_STORAGE must be set too. Once enabled, the country code of the request is logged along with the status of the corresponding request.

CONNECTOR_AUXILIARY_STORAGE

An auxiliary storage to hold some response metadata needed during request processing. It is required if CONNECTOR_TRACK_COUNTRY_CODE is enabled.

A dictionary with following items:

  • BACKEND (optional, default 'eidas_node.storage.ignite.AuxiliaryIgniteStorage'): The backend class for communication with the light storage.
  • OPTIONS (required): A dictionary with configuration of the selected backend. The AuxiliaryIgniteStorage backend expects following options:
    • host: Apache Ignite service host.
    • port: Apache Ignite service port.
    • cache_name: The cache to store the data.
    • prefix: The prefix for cache keys (optional).
    • timeout: A timeout for socket operations.

Customization

You can customize the authorization flow by subclassing view classes in eidas_node.connector.views, overriding necessary methods and adjusting URL configuration.

Copyright

  • The django-eidas-specific-node project:
    • Copyright 2019 CZ.NIC, z. s. p. o.
    • License: GPL-3+
  • Country flags:
    • Copyright 2013 Panayiotis Lipiridis
    • License: MIT

django-eidas-specific-node's People

Contributors

jiri-janousek avatar jtalir avatar lvymetalik avatar tdivis avatar tpazderka avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

jtalir vrk-kpa

django-eidas-specific-node's Issues

Reorganize XML-related functions

There are enough XML-related functions to deserve own submodule eidas_node.xml:

  • eidas_node.utils contains 5 XML-related functions.
  • eidas_node.saml contains decrypt_xml
  • A new function remove_extra_xml_whitespace should be extracted from the code in decrypt_xml
  • More functions are expected: sign_xml (#10, #27), verify_xml_signature (#9), encrypt_xml (#45)

In addition, IdentityProviderResponseView.get_saml_response should not call decrypt_xml directly but via SAMLResponse.decrypt.

Remove TOKEN_PREFIX

It was introduced to fix #14 but it is no longer needed because token id is not used as SAML id since #26.

Extended logging for statistical processing

In the INFO logging level there is a need to log the CountryCode in the request and repeat the same CountryCode in the response together with StatusCode and SubStatusCode. This is the same for both roles of Connector and ProxyService.

Filter attributes requested by Service Provider

NIA requests attributes that Generic eIDAS Node cannot handle:

ERROR : SpecificCommunicationException {} eu.eidas.specificcommunication.exception.SpecificCommunicationException: Attribute http://schemas.microsoft.com/cgg/2010/identity/claims/profileid not present in the registry at eu.eidas.specificcommunication.protocol.impl.LightJAXBCodec.getByName(LightJAXBCodec.java:169) ~[eidas-specific-communication-definition-2.3.1.jar:na]

Design:

  • New settings: CONNECTOR_ALLOWED_ATTRIBUTES (Set[str], optional, default set(eidas_node.attributes.ATTRIBUTE_MAP)): Attributes that a service provider can request. Other attributes are dropped from the authentication request. Empty set disables the filter.
  • eidas_node.connector.views.ServiceProviderRequestView.adjust_requested_attributes: If CONNECTOR_ALLOWED_ATTRIBUTES is not empty, all requested attributes not present in the set are dropped and logged with WARNING level.

Undeclared prefix saml2 when decrypting encrypted assertion

@jtalir got a response from NAKIT about why our encrypted SAML responses are not accepted: 'saml2' is an undeclared prefix. Line 1, position 2.

We should check whether xmlsec adds XML namespaces declarations inherited from parent elements. If it doesn't, the decrypted <samp2:Assertion> cannot act as an independent XML document. (It doesn't matter in our decryption method where the decrypted <samp2:Assertion> is a part of the XML document with all namespaces declared.)

Make master branch work

Master is currently broken:

  • Mypy.
  • New NIA certificate.
  • Incompatibility with django appsettings 0.7.1

Some light request fields are actually optional

These fields shouldn't be optional according to XML schema (eIDAS-Node National IdP and SP Integration Guide, Version 2.3, page 47):

  • <xs:element name="subject" type="xs:string" minOccurs="1" maxOccurs="1" />
  • <xs:element name="subjectNameIdFormat" minOccurs="1" maxOccurs="1" />
  • <xs:element name="levelOfAssurance" minOccurs="1" maxOccurs="1" />

However, they are optional for failed responses: eIDAS node accepts and provides light responses without these fields.

Changes to implement:

  • LightResponse.validate() - treat subject, subject_name_id_format, and level_of_assurance as optional if status.failure is True.
  • SAMLResponse.create_light_response() - stop providing dummy data for subject, subject_name_id_format, and level_of_assurance in case of failure responses.

Wrong order of <Issuer> and <Signature> in SAML request/response

Specification

  • Specification of RequestAbstractType (the base type for requests):
<complexType name="RequestAbstractType" abstract="true">
  <sequence>
    <element ref="saml:Issuer" minOccurs="0"/>
    <element ref="ds:Signature" minOccurs="0"/>
    <element ref="samlp:Extensions" minOccurs="0"/>
  </sequence>
...
</complexType>
  • Specification of StatusResponseType (the base type for responses)
<complexType name="StatusResponseType">
  <sequence>
    <element ref="saml:Issuer" minOccurs="0"/>
    <element ref="ds:Signature" minOccurs="0"/>
    <element ref="samlp:Extensions" minOccurs="0"/>
    <element ref="samlp:Status"/>
  </sequence>
...
</complexType>
  • Specification of AssertionType:
<element name="Assertion" type="saml:AssertionType"/>
<complexType name="AssertionType">
  <sequence>
   <element ref="saml:Issuer"/>
    <element ref="ds:Signature" minOccurs="0"/>
    <element ref="saml:Subject" minOccurs="0"/>
    <element ref="saml:Conditions" minOccurs="0"/>
    <element ref="saml:Advice" minOccurs="0"/>
    ...
  </sequence>
...
</complexType>

Implementation

  • eidas_node.xml.sign_xml_node(): New parameter position: int = 0 to specify the position of the signature.
  • SAMLRequest.sign_request(), SAMLResponse.sign_response(), SAMLResponse.sign_assertion(): If <Issuer> element is present, insert signature after it.

Invalid signature while assertions are both signed and encrypted

This might be related to issue #87.

I updated our specific connector to version 0.6.0 and got it working with assertion encryption disabled. However, when I enable the encryption I get an error from our sp which says the signature of the assertion is not valid. I fixed this issue in our development version by temporarily disabling assertion signing and leaving the encryption on. So I can either have the assertions signed or encrypted but when I enable both the signature of the assertions seems to be invalid.

Add <saml2:Conditions>

Required by NIA.

Sample:

<saml2:Conditions NotBefore="2019-10-22T14:25:05.928Z"
                  NotOnOrAfter="2019-10-22T14:30:05.928Z">
  <saml2:AudienceRestriction>
    <saml2:Audience>urn:microsoft:cgg2010:fpsts</saml2:Audience>
  </saml2:AudienceRestriction>
</saml2:Conditions>

Assertion signing should be configurable option

When configuration of Service Provider is based on parsing metadata, Service Provider may indicate requirement for signed assertion in WantAssertionsSigned attribute.
In our case we could add configuration option that would allow having unsigned assertion. Can work also as workaround for #89

Incompatibility of light response status codes and SAML status codes

Incompatibilities:

  • urn:oasis:names:tc:SAML:2.0:status:VersionMismatch is a status code in SAML but a sub status code in light response.
  • Only a few sub status codes are recognized by eIDAS.

Implementation:

  • SAMLResponse.create_light_response and SAMLResponse.from_light_response: Translate urn:oasis:names:tc:SAML:2.0:status:VersionMismatch.
  • SAMLResponse.create_light_response: Ignore sub status codes not recognized by eIDAS.

Add Specific Connector

diagram

Views:

  • ServiceProviderRequestView (/ServiceProviderRequest)

    • Receives an authorization request (SAML) from a service provider
    • Verifies the request.
    • Shows a country selector unless a country is provided.
    • Converts SAML request to a light request.
    • Stores the light request in a light cache.
    • Redirects to eIDAS Node with a light token.
  • ConnectorResponseView (/ConnectorResponse)

    • Receives and verifies a light token.
    • Retrieves a light response from a light cache.
    • Converts the light response to a SAML response.
    • Signs and encrypts the SAML response (optional).
    • Redirects to the service provider.

Revise texts and styles

  • Remove headings.
  • Connector: "Identity Provider" → "eIDAS node"
  • Proxy Service: "Service Provider" → "eIDAS node"
  • Title → "eIDAS node"
  • Styles as close as possible to the original selector.

Add complexity check

It is a good idea to add check for function complexity.

Add max_complexity = 15 to flake section in setup.cfg.

Add styles and CZ.NIC branding

  • Directory eidas_node/cznic/
  • Deployment settings:
    • Add from eidas_node import cznic.
    • Add STATICFILES_DIRS = [cznic.STATIC_DIR].
    • Add TEMPLATES['DIRS'] = [cznic.TEMPLATE_DIR].
...
from eidas_node import cznic
...

STATIC_ROOT = '/var/www/eidas/static'
STATIC_URL = '/static/'
STATICFILES_DIRS = [cznic.STATIC_DIR]

...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'APP_DIRS': True,
        'DIRS': [cznic.TEMPLATE_DIR],
        'OPTIONS': {
            'context_processors': [
                'django.contrib.messages.context_processors.messages',
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.template.context_processors.i18n',
            ]
        }
    }
]

Support cryptographic functions from AWS KMS

There should be a possibility to use https://aws.amazon.com/kms/ for cryptographic functions. One possible integration is described in the fork master...vrk-kpa:kms_support

Design

API

  • abstract class eidas_node.xml.security.XmlSigner - defines the interface
    • abstract sign_node(node: Element, position: int = 0) - sign the XML node
  • class XmlSecSigner(XmlSigner) - signs with xmlsec
    • __init__(key_file: str, cert_file: str, signature_method: str, digest_method: str) - initialized with values from settings.
  • class AwsKmsSigner(XmlSigner) - signs with AWS KMS.
    • The first implementation may use XmlSecSigner internally and then overwrite the signature with that from AWS KMS.
    • __init__(key_alias: str, cert_file: str, signature_method: str, digest_method: str) - initialized with values from settings.
  • Port SAMLRequest.sign_request(key_file: str, cert_file: str, signature_method: str, digest_method: str) to SAMLRequest.sign_request(signer: XmlSigner).
  • SAMLResponse.sign_assertion, SAMLResponse.sign_response: As above.

Settings

  • PROXY_SERVICE_IDENTITY_PROVIDER['REQUEST_SIGNATURE'] becomes DictSetting. Key class specifies the backend to use (defaults to 'eidas_node.xml.security.XmlSecSigner'). Other parameters are used for initialization.
  • CONNECTOR_SERVICE_PROVIDER['RESPONSE_SIGNATURE']: As above.

Requirements

setup.py: extras_require['aws_kms'] = ['boto3']

Case sensitivity in NameIDFormat comparison

One country reported issue on our proxy service and corresponding error message is

File "/usr/lib/python3/dist-packages/eidas_node/models.py", line 144, in deserialize_name_id_format
   return NameIdFormat(elm.text) if elm.text else None
File "/usr/lib/python3.5/enum.py", line 241, in __call__
   return cls.__new__(cls, value)
File "/usr/lib/python3.5/enum.py", line 476, in __new__
   raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 'urn:oasis:names:tc:saml:1.1:nameid-format:unspecified' is not a valid NameIdFormat

Allowed formats are:

  • urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
  • urn:oasis:names:tc:SAML:2.0:nameid-format:transient
  • urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified

The difference is 'SAML' vs 'saml'. The question is if case sensitivity of this string comparison is bug or feature.

Verify signature of SAML responses

New settings:

  • PROXY_SERVICE_IDENTITY_PROVIDER['CERTIFICATE_FILE'] (string, optional) - a path to the file with Identity Provider's certificate used to sign its responses.

Add <saml2:SubjectConfirmation>

It is required by NIA: ID4131: A Saml2SecurityToken cannot be created from the Saml2Assertion because it has no SubjectConfirmation.

Sample

<saml2:Subject>
  <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                NameQualifier="http://C-PEPS.gov.xx">SE/CZ/199008199391</saml2:NameID>
  <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
    <saml2:SubjectConfirmationData Address="127.0.0.1"
                                   InResponseTo="idf4e3164e4e1b4d8f8836647f779d63d5"
                                   NotOnOrAfter="2019-10-22T14:30:05.928Z"
                                   Recipient="https://tnia.eidentita.cz/fpsts/processRequest.aspx" /></saml2:SubjectConfirmation>
</saml2:Subject>

Remove processed light requests and responses

  • Use pyignite.cache.Cache.get_and_remove instead of get.
  • Rename LightStorage.get_light_request/get_light_response to pop_light_request/pop_light_response.
  • Don't replace original response id in ProxyServiceRequestView.create_saml_request and the response/request pairing in IdentityProviderResponseView.create_light_response becomes unnecessary.

SAML id is sometimes invalid

xsd:ID must start with a letter or underscore, and can only contain letters, digits, underscores, hyphens, and periods. However, we use str(uuid4()) which sometimes produces strings starting with a digit.

Logs:

ID4128: The value is not a valid SAML ID.
Parameter name: value
Name cannot begin with the '7' character, hexadecimal value 0x37.

Implementation:

  • utils.create_xml_uuid() -> str: create a valid unique XML id (str(uuid4()) prefixed by an underscore).
  • utils.is_xml_id_valid(xml_id: str) -> bool: validate a XML id.

Update SPCountry source in LightRequest for CEF eIDAS Node 2.4

Discussion at eIDAS Technical SubGroup about what should be correct place in LightRequest to Specific ProxyService resulted in temporary solution that in version 2.4 it will reuse element CitizenCountryCode. Final solution will be discussed later and one of the option is OriginCountryCode element that is used in current code.
Code should support both OriginCountryCode and CitizenCountryCode as the source for SPCountry in SamlRequest to IdP

Handling of non-LoA AuthnContextClassRef in SAMLResponse

Application fails when IdP responds with something other than LoA. In any case - it should not fail, but continue with LightResponse containing message about failure.
But I also suggest to to have an option to set relaxed mode where if response status is Success (it means that LoA is fullfilled) it would replace LoA from Response with LoA from Request (if it is known).

Cache NameID format from the request to fix missing transient support in IdP

Cache NameID format in the LightRequest to specific ProxyService and if transient is requested and IdP provides persistant, fill transient in the LightResponse regardless of the IdP response. NameID format is underspecified in eIDAS and has no meaning and no use. There is an initiative to keep the value unspecified only but until then transient must be supported as well.

Name ID Format Matrix

Request Response Result Note
unspecified unspecified ok -
unspecified persistent error -
unspecified transient error -
persistent unspecified error? We cannot ensure a valid persistent id.
persistent persistent ok -
persistent transient error We cannot ensure a valid persistent id.
transient unspecified error → ok We generate a random transient id instead of the persistent id.
transient persistent error → ok We generate a random transient id instead of the persistent id.
transient transient ok -

Settings

  • PROXY_SERVICE_TRANSIENT_NAME_ID_FALLBACK (bool, default false) - If set to true and transient name id is requested in the request but unspecified/persistent name id is provided in the response, new random transient name id is created.
  • PROXY_SERVICE_AUXILIARY_STORAGE (required if the fallback is enabled) - Light storage holding auxiliary data - now only Name ID format of the request. It may be a new Apache Ignite cache or we can reuse any of the two existing caches used by the proxy service.

Implementation

If the fallback is disabled, nothing happens. Otherwise:

  • ProxyServiceRequestView.post:
    • Creates self.auxiliary_storage.
    • Stores {"name_id_format": self.light_request.name_id_format} with id specific-aux-{self.light_request.id}
  • IdentityProviderResponseView.post:
    • Creates self.auxiliary_storage.
    • Retrieves specific-aux-{self.light_response.in_response_to_id} from cache (with deletion).
    • If cached name id format is transient but self.light_response.subject_name_id_format isn't, it is replaced with new random transient id.

Sign SAML requests

New/changed settings:

  • PROXY_SERVICE_IDENTITY_PROVIDER['KEY_FILE'] renamed to PROXY_SERVICE_IDENTITY_PROVIDER['ENCRYPTION_KEY_FILE'] (string, optional, default None) - The path of a key to decrypt Identity Provider's authentication response.
  • PROXY_SERVICE_IDENTITY_PROVIDER['SIGNING_KEY_FILE'] (string, optional, default None) - The path to a key to sign authentication requests.

Should we reuse PROXY_SERVICE_IDENTITY_PROVIDER['KEY_FILE'] or add new settings PROXY_SERVICE_IDENTITY_PROVIDER['SIGNING_KEY_FILE']?

eIDAS Node doesn't propagate relay state in light response

Upstream ticket: EID-977 in CEF issue tracker.

Our light response stored by Specific Proxy Service:

<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<lightResponse>
  <id>T94492c61-c8a8-4a6a-aee9-6ef6b02f213a</id>
  <inResponseToId>_cUZBLMtWjycfF4XeCy-nWYQXfJ98N0sDFuAlQzrFRDWTi1laUtkPIWlwuEtQccX</inResponseToId>
  <issuer>specific-proxy-service</issuer>
  <ipAddress>217.31.205.1</ipAddress>
  <relayState>xxxx</relayState>
  <subject>e9ef6043-99f6-4a6d-bf7b-956b560f6550</subject>
  <subjectNameIdFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</subjectNameIdFormat>
  <levelOfAssurance>http://eidas.europa.eu/LoA/substantial</levelOfAssurance>
  <status>
    <failure>false</failure>
    <statusCode>urn:oasis:names:tc:SAML:2.0:status:Success</statusCode>
  </status>
  <attributes>
    <attribute>
      <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName</definition>
      <value>DVOŘÁKOVÁ</value>
    </attribute>
    <attribute>
      <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName</definition>
      <value>PAVLA</value>
    </attribute>
    <attribute>
      <definition>http://eidas.europa.eu/attributes/naturalperson/DateOfBirth</definition>
      <value>1955-06-07</value>
    </attribute>
    <attribute>
      <definition>http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier</definition>
      <value>e9ef6043-99f6-4a6d-bf7b-956b560f6550</value>
    </attribute>
  </attributes>
</lightResponse>

Light response provided by Generic Connector:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<lightResponse>
    <id>_c.fR2g8Vq1miFuxjORQlvumxjzU4XQqbqNPn-Z-gRTZegbXmaM2qRbGZmYLfMwK</id>
    <issuer>http://pokuston:8888/EidasNode/ConnectorMetadata</issuer>
    <ipAddress>2001:1488:fffe:2:b86f:bcfd:5dc7:8228</ipAddress>
    <subject>e9ef6043-99f6-4a6d-bf7b-956b560f6550</subject>
    <subjectNameIdFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</subjectNameIdFormat>
    <status>
        <failure>false</failure>
        <statusCode>urn:oasis:names:tc:SAML:2.0:status:Success</statusCode>
        <statusMessage>urn:oasis:names:tc:SAML:2.0:status:Success</statusMessage>
        <subStatusCode>##</subStatusCode>
    </status>
    <inResponseToId>_16f32676-1c31-44d8-a2fe-3a100cf29c0e</inResponseToId>
    <levelOfAssurance>http://eidas.europa.eu/LoA/substantial</levelOfAssurance>
    <attributes>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName</definition>
            <value>DVOŘÁKOVÁ</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName</definition>
            <value>PAVLA</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/DateOfBirth</definition>
            <value>1955-06-07</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier</definition>
            <value>CA/CA/e9ef6043-99f6-4a6d-bf7b-956b560f6550</value>
        </attribute>
    </attributes>
</lightResponse>

<relayState>xxxx</relayState> is missing in the latter.

Verify signature on SAML requests

New settings:

  • CONNECTOR_SERVICE_PROVIDER['CERT_FILE] (optional, default None): The path of a certificate to verify the signature of Service Provider's authentication requests.

Implementation:

  • SAMLRequest.request_signature() -> Optional[Element]: returns <ds:Signature> element is there is any.
  • SAMLRequest.verify_request(cert_file: str) -> None: Verifies the signature. Raises SecurityError if there is no signature or the signature is invalid.
  • ServiceProviderRequestView.get_saml_requestcountry_parameter: str, cert_file: Optional[str]) -> SAMLRequest: Modified to verify the signature if cert_key is provided.
  • DemoServiceProviderRequestView.post: Modified to sign the SAML request reusing CONNECTOR_SERVICE_PROVIDER['RESPONSE_SIGNATURE'].
  • SAMLRequest.sign_request(key_file: str, cert_file: str, signature_method: str, digest_method: str) -> None: Sign SAML request.

Incomplete copyright/license information

We should specify in README:

  • Copyright: Copyright 2019 CZ.NIC, z. s. p. o.
  • License: [Licensed under GPL-3+](COPYRIGHT)
  • Flags Copyright 2013 Panayiotis Lipiridis, MIT license

File COPYRIGHT:

Copyright (C) 2019 CZ.NIC, z. s. p. o.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

Source files should contain at least copyright information and a reference to license notice:

# Copyright (C) 2019 CZ.NIC, z. s. p. o.
# Licensed under GPL-3+, see file COPYRIGHT.

Invalid sub status code of a successfull light response from Generic Connector

Upstream ticket: EID-979 in CEF issue tracker

Our light response from Specific Proxy Service is:

LightResponse(
  id='_232731ca12d9461caf9eb32abf684aee',
  in_response_to_id='_pz-oy4.BYeLVIFvQv73NCtPrBCNa5giQcHCby4638MD3Ng3N-5PgWF-Itgc1crZ',
  issuer='specific-proxy-service',
  ip_address='217.31.205.1',
  relay_state='xxx',
  subject='b983d4ca-4812-4755-af51-f1d65fbd365e',
  subject_name_id_format='urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
  level_of_assurance='http://eidas.europa.eu/LoA/substantial',
  status=Status(
    failure=False,
    status_code='urn:oasis:names:tc:SAML:2.0:status:Success',
    sub_status_code=None,
    status_message=None),
  attributes=OrderedDict([
    ('http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName', ['NOSKOVÁ']),
    ('http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName', ['PETRA']),
    ('http://eidas.europa.eu/attributes/naturalperson/DateOfBirth', ['1981-09-26']),
    ('http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier',
     ['b983d4ca-4812-4755-af51-f1d65fbd365e'])
  ]))

Generic Connector provides us with:

<lightResponse>
    <id>_U_6MhGsSwNLGZGjQD6AiHO1-1gc-.HnQDmvVPawHo2eLRZ4TUxoxhFVWnGrmNAE</id>
    <issuer>http://pokuston:8888/EidasNode/ConnectorMetadata</issuer>
    <ipAddress>2001:1488:fffe:2:f1a6:e430:89d7:eadf</ipAddress>
    <subject>b983d4ca-4812-4755-af51-f1d65fbd365e</subject>
    <subjectNameIdFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</subjectNameIdFormat>
    <status>
        <failure>false</failure>
        <statusCode>urn:oasis:names:tc:SAML:2.0:status:Success</statusCode>
        <statusMessage>urn:oasis:names:tc:SAML:2.0:status:Success</statusMessage>
        <subStatusCode>##</subStatusCode>
    </status>
    <inResponseToId>_944b6f1f-c938-4412-b2aa-fde36d5e872c</inResponseToId>
    <levelOfAssurance>http://eidas.europa.eu/LoA/substantial</levelOfAssurance>
    <attributes>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName</definition>
            <value>NOSKOVÁ</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName</definition>
            <value>PETRA</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/DateOfBirth</definition>
            <value>1981-09-26</value>
        </attribute>
        <attribute>
            <definition>http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier</definition>
            <value>CA/CA/b983d4ca-4812-4755-af51-f1d65fbd365e</value>
        </attribute>
    </attributes>
</lightResponse>
  • Neither ## nor urn:oasis:names:tc:SAML:2.0:status:Success are valid sub status codes (the latter is a valid status code).
  • Failure responses are ok (urn:oasis:names:tc:SAML:2.0:status:AuthnFailed as a sub status code, ## as a status message).

Workaround:

  • Status.deserialize_status_code(): Ignore the sub status code if it contains ##.

Compare light token timestamp correctly

We compare light token timestamps in local time with timestamps in UTC, which is definitely not correct.

eIDAS-Node National IdP and SP Integration Guide doesn't specify whether the timestamp is in the local timezone or UTC, but the embedded Python example uses datetime.now(), which returns local time. We need to verify that eIDAS Generic Node uses local time too.

Internally, we should use timezone-aware timestamps everywhere and convert to bare timestamps converted to the correct timezone as needed.

Related CZ.NIC ticket

Extract signing key and endpoint from identity provider's metadata

New settings:

  • PROXY_SERVICE_IDENTITY_PROVIDER['METADATA_URL'] (string, optional) - URL or Identity Provider's metadata.

Implementation:

  • eidas_node.proxy_service.apps.ProxyServiceConfig.ready: If PROXY_SERVICE_IDENTITY_PROVIDER['METADATA_URL'] is set, PROXY_SERVICE_IDENTITY_PROVIDER['SIGNING_KEY'] and PROXY_SERVICE_IDENTITY_PROVIDER['ENDPOINT'] are extracted from that URL.

Generate Sphinx documentation

We already use Sphinx-style docstrings, but without any check for format correctness. The generation of documentation is the next step forward.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.