gematik / api-vzd

API specification for gematik Directory, a service which provides Information about organizations and practitioners in the german healthcare.
Other
25 stars 6 forks source link

Documentation regarding the owner-authenticate process using ECC softcerts #133

Open dotWee opened 2 months ago

dotWee commented 2 months ago

While implementing the handling of ECC certificates (*_SMCB_HCI_ENC_E256), I came accross the ECC sample in ./samples/directory-samples-python/directory_samples/owner_authenticate_konnektor.py.

In this sample, the signing of the challenge is performed by a request to a Gematik Konnektor using external_authenticate():

    challenge_response_signature = external_authenticate(card_handle, challenge_response_hash, "ECC")
    debug_print("ExternalAuthenticate Request")
    debug_print(last_soap_request())
    debug_print("ExternalAuthenticate Response")
    debug_print(last_soap_response())

    debug_print("Received signature from konnektor")
    debug_print(challenge_response_signature)

    # ECC signatures must be post-processed
    r, s = ec_utils.decode_dss_signature(challenge_response_signature)
    challenge_response_signature = _encode_int(r, 256) + _encode_int(s, 256)

    signed_token = header_and_payload + "." + base64url_encode(challenge_response_signature)

    debug_print("Complete signed challenge response")
    debug_print(signed_token, soft_wrap=True)

I'm trying to implement this signing manually without requesting a Konntektor, just like in the provided RSA sample ./samples/directory-samples-python/directory_samples/owner_authenticate_softcert.py:

    smcb_cert_bytes = open(owner_cert_filename_der, "rb").read()
    cert_base64 = b64encode(smcb_cert_bytes).decode("ascii")

    payload = {
      "njwt": challenge
    }
    jwstoken = jws.JWS(dumps(payload).encode('utf-8'))
    jwstoken.add_signature(
        smcb_key,
        None,
        json_encode({
          "typ": "JWT",
          "cty": "NJWT",
          # BASE64(DER)
          "x5c": [cert_base64],
          # BP256R1 or PS256
          "alg": "PS256"
        }),
    )
    signed_token = jwstoken.serialize(True)
    debug_print(signed_token, soft_wrap=True)

I tried looking for the documentation about how the Konntektor is creating this signature, but looking through the Konnektor spec (gemSpec_Kon_V*.pdf) wasn't helpful.

def external_authenticate(card_handle: str, hash: bytes, crypt: str):
    service = create_service_client(
      "AuthSignatureService_v7_4_1",
      "http://ws.gematik.de/conn/AuthSignatureService/WSDL/v7.4"
    )

    if crypt == "RSA":
        optional_outputs = {
            'SignatureType': 'urn:ietf:rfc:3447',
            'SignatureSchemes': 'RSASSA-PSS'
        }
    else:
        optional_outputs = {
            'SignatureType': 'urn:bsi:tr:03111:ecdsa',
        }

    response = service.ExternalAuthenticate(
        CardHandle=card_handle,
        Context={
            "MandantId": connector_config.mandant_id,
            "ClientSystemId": connector_config.client_system_id,
            "WorkplaceId": connector_config.workplace_id,
        },
        OptionalInputs=optional_outputs,
        BinaryString={
          "Base64Data": {
              "MimeType": "application/octet-stream",
              "_value_1": hash
          }
        }
    )

    return response.SignatureObject.Base64Signature._value_1

Could you point me into the right direction, or did I overlook something the Konntektor specification?

gem-uhe commented 2 months ago

"api-vzd" is the directory project. The Konnektor is a different team. But I asked this team and the answer is, that this is not described in the Konnektor Specification and there is no reference implementation. Sorry.