All Advisories

gematik lib-vau / lib-vau-csharp

VAU Handshake Performs Only 2 of 6 Required Server-Key Checks

lib-vau and lib-vau-csharp are gematik's reference implementations of the VAU protocol — the security layer that protects traffic to Germany's electronic patient record (ePA) backend, in use across the ~73 million statutory-insured citizens enrolled since 2025 unless they have opted out. Both reference VAU clients deserialise the server's SignedPublicVauKeys structure during handshake Message 2 but never read the signatureEs256, certHash, or ocspResponse fields. Of the six verification steps gemSpec_Krypt A_24624-01 requires (chain validation, OCSP, role OID, ES256 signature, key format, expiry), only key-format and expiry are checked. The libraries expose no API for consumers to plug verification in. A network MITM positioned inside the TI / Klinik-Netzwerk segment in front of the VAU endpoint — the boundary the inner-VAU layer was designed to harden against — can substitute their own ECDH and Kyber public keys into Message 2 and decrypt or modify all subsequent VAU traffic. Tracked as MS-LIB-VAU-eaea8d (Java) and MS-LIB-VAU-CSHARP-9c69c4 (C#).

Authored byVolker Schönefeld, Simon Weber2026-05-28
SeverityHighCVSS 7.4CVSS 3.1 VectorAV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:NCWECWE-295 (Improper Certificate Validation)Productgematik lib-vau / lib-vau-csharpAffected Versionslib-vau 1.0.0 through 1.0.15 (Java); lib-vau-csharp 1.0.0 through 1.0.9 (C#)Fixed InNot patched upstream. The vendor has clarified the libraries' experimental status in the README and considers no advisory necessary. Downstream consumers that use the libraries in production deployments are responsible for adding the missing server-key verification — see Mitigation.

Description

gemSpec_Krypt A_24624-01 requires the VAU client to perform six verification steps on the server's SignedPublicVauKeys during the handshake: certificate-chain validation against the TI PKI root, OCSP revocation checking (max 24 h), role OID verification (oid_epa_vau), ES256 signature verification over the key material, ECC/Kyber key format validation, and expiry checking (exp > now). Both reference implementations perform only the last two.

In the Java reference implementation (lib-vau), the handshake gap is here:

lib-vau VauClientStateMachine.java:108-120

SignedPublicVauKeys signedPublicVauKeys;
try {
signedPublicVauKeys = decodeCborMessageToClass(
transferredSignedServerPublicKey, SignedPublicVauKeys.class);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not CBOR decode Signed Server Public Keys...", e);
}
VauPublicKeys transferredSignedServerPublicKeyList =
signedPublicVauKeys.extractVauKeys();
checkCertificateExpired(
transferredSignedServerPublicKeyList.getExp());
verifyClientMessageIsWellFormed(
transferredSignedServerPublicKeyList.getEcdhPublicKey(),
transferredSignedServerPublicKeyList);
// Proceeds directly to KEM encapsulation with unverified keys.

View source →

The SignedPublicVauKeys data class deserialises the relevant fields but never reads them:

lib-vau SignedPublicVauKeys.java:45-58

@JsonProperty("signature-ES256")
byte[] signatureEs256;
@JsonProperty("cert_hash")
byte[] certHash;
@JsonProperty("ocsp_response")
byte[] ocspResponse;

View source →

The class exposes a sign() method for the server side but no corresponding verify() method, and no interface or callback for consumers to inject verification externally. The README states "Es werden keine Zertifikate geprüft." — it documents the state of the library, but does not document that consumers must provide the verification themselves, or how.

The C# reference implementation (lib-vau-csharp) has the same shape:

lib-vau-csharp VauClientStateMachine.cs:109-113

SignedPublicVauKeys signedPublicVauKeysClient = SignedPublicVauKeys.fromCbor(transferredSignedServerPublicKey);
VauPublicKeys transferredSignedServerPublicKeyList = signedPublicVauKeysClient.ExtractVauKeys();
KeyUtils.CheckCertificateExpired(transferredSignedServerPublicKeyList.Exp);
KeyUtils.VerifyClientMessageIsWellFormed(transferredSignedServerPublicKeyList.EcdhPublicKey, transferredSignedServerPublicKeyList);
// Proceeds directly to KEM encapsulation with unverified keys.

View source →

lib-vau-csharp SignedPublicVauKeys.cs:33-36

readonly byte[] SignatureEs256;
readonly byte[] CertHash;
readonly byte[] OcspResponse;

View source →

A_24624-01 compliance summary across both libraries:

A_24624-01 implementation status

Requirement Java C#
--------------------------------------------------------
OCSP verification (max 24 h) Missing Missing
Certificate chain to TI PKI root Missing Missing
Role OID oid_epa_vau Missing Missing
ES256 signature verification Missing Missing
ECC/Kyber key format validation Present Present
Expiry check (exp > now) Present Present

Downstream adopters embed the reference implementation as-is. The missing verification propagated into shipping clients: med-united/epa4all embeds lib-vau as a submodule and inherits the missing verification unchanged, and oviva-ag/epa4all-client added a SignedPublicKeysTrustValidator interface but discarded the boolean return value of Signature.verify(), so a signature verification failure did not cause the validator to return false (CVE-2026-44900). gematik's official example code in epa4all-examples (v0.1.6, now archived) also demonstrates calling receiveMessage2() without any server-key verification step.

Impact

  • The inner-VAU layer was designed to defend patient data against compromise of TLS infrastructure that sits outside the TEE boundary. The attack requires a position inside the TI / Klinik-Netzwerk segment in front of the VAU endpoint — exactly the boundary the inner-VAU layer was specifically designed to harden against.
  • From that position, a network MITM can intercept handshake Message 1, perform KEM encapsulation with the client's keys, and respond with a Message 2 carrying attacker-controlled ECDH and Kyber public keys in the AEAD payload. The signatureEs256 field can be filled with arbitrary bytes.
  • Because the client never verifies the signature, certificate chain, OCSP response, or role OID, it derives session keys from the attacker's keys. The MITM then holds both halves of the session and can decrypt and re-encrypt every subsequent VAU message in clear: a patient's medication records, diagnostic reports, and prescription history read by the ePA client, documents written back to the ePA backend, and the authorization transactions that control who has access to the patient's record.

Mitigation

Downstream consumers that ship either library in a production deployment should add a SignedPublicKeysTrustValidator interface (the name Oviva's fork uses works well) which the VauClientStateMachine constructor *requires* the consumer to supply. Call it from receiveMessage2() before KEM encapsulation. Default to deny — if no validator is provided, fail the handshake rather than proceed without verification. The validator should perform the full A_24624-01 chain: fetch the certificate by cert_hash, validate the OCSP response, verify the chain against TI PKI roots, check for the oid_epa_vau role OID, and verify the ES256 signature over the signed key block. Use the boolean return value of Signature.verify() (Java) / VerifyData() (C#); a discarded return value is the same as no check at all.

Defender's Checklist

  • Identify whether you ship lib-vau / lib-vau-csharp.

    If your ePA client (or any product that speaks VAU) embeds lib-vau or lib-vau-csharp directly, via a fork, or via a git submodule, the gap applies. Two shipping ePA implementations inherited the missing verification unchanged: med-united/epa4all (lib-vau as a git submodule) and oviva-ag/epa4all-client (a validator was added but wired incorrectly — see CVE-2026-44900).

  • Add a trust validator with default-deny.

    Require the VauClientStateMachine constructor to take a SignedPublicKeysTrustValidator parameter and call it in receiveMessage2() before KEM encapsulation. If no validator is provided, fail the handshake; do not silently proceed. The constructor signature itself must change — Java: add a required parameter to the existing no-arg constructor; C#: equivalent change to VauClientStateMachine().

  • Implement the full A_24624-01 chain.

    Cert-hash lookup, OCSP within 24 h, chain to TI PKI root, oid_epa_vau role OID, ES256 signature verification over the signed key block. All six checks must succeed for the handshake to continue. TI PKI root certificates are not in the standard system trust store — source them from gematik's published TI PKI (gemSpec_PKI, Appendix — TI Root CA) and pin them explicitly in your validator.

  • Verify the boolean is read.

    Both Java's Signature.verify() and C#'s VerifyData() return a boolean for a bad signature rather than throwing. A discarded return value is a silent pass. The Oviva incident (CVE-2026-44900) is exactly this.

  • Audit your verifier with a forged Message 2.

    Stand up a local VAU peer that responds to Message 1 with a Message 2 containing well-formed but unsigned public keys and arbitrary signatureEs256 bytes. The client must refuse the handshake. If it doesn't, the validator is not wired in.

Severity Reasoning

AV:NThe threat model A_24624-01 is written against is a network attacker on the path between the ePA client and the VAU endpoint.AC:HRequires a MITM position on a TI-connected network segment (the local clinic network or any upstream peering), plus a working VAU peer to complete the handshake. Doable, but not single-shot.PR:NNo credentials needed beyond the network position itself.UI:NThe ePA client initiates the handshake on its own schedule.S:UImpact is bounded to the VAU channel and the ePA payloads it carries.C:HPlaintext access to ePA document reads and metadata.I:HPlaintext access to and modification of ePA document writes and authorization flows.A:NNo availability impact.

References

How We Can Help

Who We Are

The security researchers behind this advisory.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec Researcher

I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.

  • PhD on Hospital Cybersecurity
  • Critical vulnerabilities found in hospital systems
  • Alumni of THB MedSec Research Group
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.

  • 20+ years as CTO, 50M+ app downloads
  • Architected and secured large-scale IoT fleets
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Looking for a Penetration Test?

Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.