Cannot register a client for FHIR bulk export

Hello Team, I’m using latest OpenEMR 6.1.0 Docker version. I’m trying to register a client for bulk export. It breaks when I’m requesting access (bearer) token using client assertion I generated with command-runner. The response json is nothing special:
“error”: “invalid_client”,
“error_description”: “Client authentication failed”,
“message”: “Client authentication failed”

But the logs (full logs attached) are more interesting, especially this part:

[php:notice] [pid 148] [client 3.232.164.182:53598] [2022-04-18T13:11:04.539748+00:00] OpenEMR.ERROR: CustomClientCredentialsGrant->validateClient() jwt failed required constraints {“client”:“A3wnpy-IgyZN6NOxzDVxjajW3f5_NgdKHUbjngYwCn0”,“exceptionMessage”:“The token violates some mandatory constraints, details:\n- Token signature mismatch”,“claims”:{“iss”:“A3wnpy-IgyZN6NOxzDVxjajW3f5_NgdKHUbjngYwCn0”,“aud”:[“http://3.232.176.248/oauth2/default/token"],“jti”:“f8815641-cb4f-41dc-ba01-3c123cc5de58”,“iat”:"{“date”:“2022-04-18 13:10:33.920247”,“timezone_type”:1,“timezone”:”+00:00"}",“nbf”:"{“date”:“2022-04-18 13:10:33.920247”,“timezone_type”:1,“timezone”:"+00:00"}",“exp”:"{“date”:“2022-04-18 13:11:33.920247”,“timezone_type”:1,“timezone”:"+00:00"}",“sub”:“A3wnpy-IgyZN6NOxzDVxjajW3f5_NgdKHUbjngYwCn0”},“expectedAudience”:“http://3.232.176.248/oauth2/default/token”} []

logs.txt (6.2 KB)

So is it safe to propose that the root cause is somehow date/time connected? Or there’s something else involved?

Definitely looks like a timestamp issue. I’d check to make sure your server / client time are set up correctly. I believe we give a ±300 second jitter allowance for date differences. Also when you generated the client did you use the correct issuer? That would be the other thing I would check.

@adunsulag Thanks for your answer.
As for client creation - I created it using public key generated here
https://bulk-data.smarthealthit.org/ (purely for testing, following Lecture 2 - FHIR Bulk Data in OpenEMR). May it also be the case?

"jwks":{"keys":[

        {

            "kty": "RSA",

            "alg": "RS384",

            "n": "4WlUBml-3OuoFjXRjocOIO7Eny82Er2kOz5dUWYP3E4-TNkapWLcMbbAXjY3FqQZPdA2Snjt_D6QY6pJMSufXxLL_A5BC-8nsjhTXNHcyBvY22cTDmGnacENnQYpMmg3W8gDyggx3LEPQwjVfdh8uxYjnlDjQ81uAnSN_wgTF4agKIFgsZWQTc0w8kQ_BJWG0u-dDQ0UlxqZqZWDsyH9flQOQ60tFkTA7VCzhWXGyBj6z4XnGT-U9Em51--1D9VihM32UOiDfBYWSZJMkznKTzILghkGo6veHxQFSW6SFiZTsnRwQR8j4MMBGZYlgSp2zmzNmjDCvOU6YGx7aeV2JQ",

            "e": "AQAB",

            "key_ops": [

                "verify"

            ],

            "ext": true,

            "kid": "b6dc76a7e5ea555b883bf0d51a436fcf"

        }

        ]}

I’ve also checked time difference - of course it’s not 300s but still around 2-4s

Hi @adunsulag , so I haven’t moved forward on this question. It is 3-4s between client and server so I guess it’s ok?
Maybe me using key generator here https://bulk-data.smarthealthit.org/ can be the case?

This is the code that is used for the client credentials JWT validation

$configuration->setValidationConstraints(
                // we only allow 1 minute drift (note the 'T' specifier here, super important as we want to do time
                // we had a bug here where P1M was a 1 month drift which is BAD.
                new ValidAt(new SystemClock(new \DateTimeZone(\date_default_timezone_get())), new \DateInterval('PT1M')),
                new SignedWith(new RsaSha384Signer(), $jsonWebKeySet),
                new IssuedBy($client->getIdentifier()),
                new PermittedFor($this->authTokenUrl), // allowed audience
                new UniqueID($jwtRepository)
            );

You are failing the SignedWith constraint which code is here:

 public function assert(Token $token): void
    {
        if (! $token instanceof UnencryptedToken) {
            throw new ConstraintViolation('You should pass a plain token');
        }

        if ($token->headers()->get('alg') !== $this->signer->algorithmId()) {
            throw new ConstraintViolation('Token signer mismatch');
        }

        if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) {
            throw new ConstraintViolation('Token signature mismatch');
        }
    }

When you use the command line runner are you replacing the openemr-rsa-384-private.key and the openemr-rsa384-public.pem key? You can see that those are the keys that are used to generate the assertion in the command line runner. If you are not replacing those key/pem files then the assertion you are generating as a client will not match the JWKS stored when you generate your assertion through the command line runner.

@adunsulag Thanks again for the answer.
As I’m following OpenEMR FHIR Lecture Series: Lecture 2 - FHIR Bulk Data in OpenEMR - YouTube
Here is the command runner specifics I’m using for generating assertion:
./command-runner -c CreateClientCredentialsAssertion -i A3wnpy-IgyZN6NOxzDVxjajW3f5_NgdKHUbjngYwCn0 -a http://3.232.176.248/oauth2/default/token

There are no instructions whatsoever on changing default certs.
So looks like I have two options here:

  1. Use default public openemr-rsa384-public.pem cert in JWKS to register a client and therefore when I’ll be generating assertion by command-runner they will match?
  2. Change openemr-rsa384-public.pem on whatever they use here https://bulk-data.smarthealthit.org/ ?

Excuse me in advance as certificates and tokens are really my kryptonite and there might be flaws in my logic.

Hi @VBregman in the video demonstration I did uses the default certs that are inside OpenEMR. If you don’t want to mess with converting your JWKS to pems and are ok with just going with the test pem files in the system you can register a bulk client using the JWKS that is in OpenEMR. To print out the test JWKS just pass the -k parameter to the command runner like so:

./command-runner -c CreateClientCredentialsAssertion -k

You’ll see the following JWKS:

{"keys":[{"kty":"RSA","e":"AQAB","kid":"5c17409c-87f0-4713-814f-c864bfe876bc","n":"4dKFtTbLuj_ohXaxa5yOkQK6uarDBww-7QtQaA8zDt2IjfpcEW5hRbKMswU5cXmMSLc33c_jemJQoXxWHriW4xO0FREqvA0u4PpInJtte7uwqDzml0sDUS6LLqWdOANapEnvovH7aAUb-v_GTU6eK3pcJsquQTnTLOeXLSkk9ukGJDQ5rcbkguOQXZAngKhalWGzHx_rYoQv2kH1F9rshgfjpiPFmjs9EyRtuo1yc8RQvioAKugc72MbPPlGN6saesDh3tnvyL5sbMs1cLjSldehZ4y0KHVSubuvipcM4RctbUIiZQQSwVIV3hCLKhVSKX_owz_46vpvk-7VyKwDjH5D6kdM86u_g6SkP7cF272LAsNUJak98qLWaogWGm-UWzaHvZpuS2w5sMMOE-8tEBZc-ZIjOgDWWy5AYYl8KCpVeOwuQlZ59X3rd67Pinc98LmDd4jJTGYWNsygdti76MEWlvA9tP8E8dOcr_SSn9TN832NopbjbG9H6dXid3e7XNobLAGRXM9n0JD0MPOH3ltMBQDi6JDzkFoYmONtNI2-e6_R_uogoCDZWUqZF72eGknoawPGwGLEqRW1sIoI4ziVT9hsZxaoiMVjIAYNOxBmooTBgp4NgbbQUXtqZxcEnzgZO3WVBg0P_ldkeu-kS4xV0mg8x25TQhws1EytVa0"}]}

Your Option #2 is what you need to do when you are ready to use this in a production environment. If you use the default JWKS then your data can be stolen as the private key is publicly accessible in the OpenEMR codebase.

I probably will need to do a follow up video on how to convert JWKs to pem format. There are plenty of tools to do that for you. I would recommend only using a command line tool or something that runs on your public computer. You don’t want to paste your key information into another online tool as you can’t be sure for data security reasons that someone didn’t keep a copy of that key in their http logs or database.

@adunsulag Thanks, for now I’ll just try the first option.
But if you can add any info to that topic (videos, manuals, github pages) that would be awesome as I’m practically learning FHIR integrations with that stuff.
Again, thanks for your support!

@adunsulag Now it’s getting more interesting :slight_smile: I believe it’s a first step for possible sql injection.
Here is the response I’m getting when trying to perform a root or patient bulk export
I’ll attach full log as well.
Actually the response has a little bit more info then logs

logs.txt (22.5 KB)

<h2>
	<font color='red'>Query Error</font>
</h2>
<p>
	<font color='red'>ERROR:</font> query failed: SELECT id,
	uuid,
	users.title as title,
	fname,
	lname,
	mname,
	federaltaxid,
	federaldrugid,
	upin,
	facility_id,
	facility,
	npi,
	email,
	active,
	specialty,
	billname,
	url,
	assistant,
	organization,
	valedictory,
	street,
	streetb,
	city,
	state,
	zip,
	phone,
	fax,
	phonew1,
	phonecell,
	users.notes,
	state_license_number,
	abook.title as abook_title,
	FROM users
	LEFT JOIN list_options as abook ON abook.option_id = users.abook_type
</p>
<p>Error: <font color='red'>You have an error in your SQL syntax; check the manual that corresponds to your MariaDB
		server version for the right syntax to use near 'FROM users
		LEFT JOIN list_options as abook ON abook.option_i...' at line 33</font>
</p>
<br />/var/www/localhost/htdocs/openemr/src/Services/UserService.php at 196:sqlStatement<br />/var/www/localhost/htdocs/openemr/src/Services/FHIR/FhirPersonService.php at 263:getAll(Array,)<br />/var/www/localhost/htdocs/openemr/src/Services/FHIR/FhirServiceBase.php at 208:searchForOpenEMRRecords(Array)<br />/var/www/localhost/htdocs/openemr/src/Services/FHIR/Traits/FhirBulkExportDomainResourceTrait.php at 69:getAll(Array)<br />/var/www/localhost/htdocs/openemr/src/RestControllers/FHIR/FhirExportRestController.php at 359:export

This was fixed two weeks ago by @brady.miller here: completed g7 for mu3 (#5044) · openemr/openemr@9e2e7a9 · GitHub

It has yet to be merged into the 6.1 release. It will go in automatically into our upcoming 7.0 release or you can cherry-pick the commit if you want. Another option is to run your tests against master until we release the next 6.1 patch.

As far as SQL injection goes that line here: openemr/UserService.php at c7ce44f491fa30349e3a3eb7fa1f9afb48cb3ae7 · openemr/openemr · GitHub uses bound parameters. Its failing because you can’t send in an array into a bound parameter ‘?’ with the php mysqli extension. So while you can trigger an error, I’m pretty certain there’s no risk for SQL injection here.

@adunsulag
Thanks a lot for your support, this is really helpful!