JWT authentication using python terminal commands together with make.com

Hey guys! So I’m trying to sign and encrypt the payload using a flask app.py running terminal commands. I’m able to sign it using the user’s private key and encrypt it with the server’s public key but when sending the post webhook to /auth/jwt/login.json I just get the “The credentials are invalid” error.

Here’s my code:

import subprocess
import uuid
import json
from datetime import datetime, timedelta
from flask import Flask, Response, request, jsonify

app = Flask(__name__)

domain = 'https://sample.domain.com'
userUUID = '040b4777-48a8-4d3f-b724-c8r548457024'

@app.route("/")
def sign_message():
    try:
        # Import the public key before signing
        #import_pubkey()

        # Prepare the JSON payload to sign
        input_message = {
            "version": "1.0.5",
            "domain": domain,
            "verify_token": str(uuid.uuid4()),
            "verify_token_expiry": (datetime.now() + timedelta(seconds=60 * 60)).strftime('%s')
        }
        input_message_str = json.dumps(input_message, indent=4)  # Serialize to JSON string
        print(input_message_str)
        # Command to sign the message
        command = [
            "gpg", "--pinentry-mode", "loopback", "--batch", "--yes",
            "--passphrase", "[privateKeyPassphrase]", "--sign", "--armor"
        ]

        subcommand = [
            "gpg", "--encrypt", "--armor", "--recipient", "1232312[publickeyfingerprint]"
        ]

        # Run the GPG command and capture the signed output
        signed = subprocess.run(command, text=True, input=input_message_str, capture_output=True, check=True)

        # Get the signed message
        signed_output = signed.stdout

        #run the gpg command for encryption
        encrypted = subprocess.run(subcommand, text=True, input=signed_output, capture_output=True, check=True)

        #get the encrypted output
        encrypted_output = encrypted.stdout

        #format both data
        response_data = {"encrypted_output": encrypted_output}

        # Return the encrypted output as plain text (non-escaped)
        return jsonify(response_data)

    except subprocess.CalledProcessError as e:
        # Return error details in JSON format
        return Response(f'{{"error": "Command failed.", "details": "{e.stderr}"}}', mimetype="application/json", status=500)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

I then copy the “encrypted_output” showing on my browser to make.com’s HTTP make a request module to send a post command to https://sample.domain.com/auth/jwt/login.json. It then gives me the credentials invalid error.

I’m able to decrypt and verify the encrypted payload within the terminal so I’m scratching my head on what I’m missing here.

here’s what I’m sending out via make.com

{"user_id":"040b4777-48a8-4d3f-b724-c8r548457024","challenge":"-----BEGIN PGP MESSAGE-----\n\nhQGMAyeBZQ98+. . . . .."}

Here’s passbolt healthcheck

     ____                  __          ____  
    / __ \____  _____ ____/ /_  ____  / / /_ 
   / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ 
  / ____/ /_/ (__  |__  ) /_/ / /_/ / / /    
 /_/    \__,_/____/____/_.___/\____/_/\__/   

 Open source password manager for teams
-------------------------------------------------------------------------------
 Healthcheck shell                                                               
-------------------------------------------------------------------------------

 Environment

 [INFO] Linux 8f88c8290c3e 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024 x86_64 GNU/Linux
 [PASS] PHP version 8.2.24.
 [PASS] PHP version is 8.1 or above.
 [PASS] 64-bit architecture system detected.
 [INFO] gpg (GnuPG) 2.2.40 / libgcrypt 1.10.1
 [PASS] PCRE compiled with unicode support.
 [PASS] Mbstring extension is installed.
 [PASS] Intl extension is installed.
 [PASS] GD or Imagick extension is installed.
 [PASS] The temporary directory and its content are writable and not executable.
 [PASS] The logs directory and its content are writable.
 [WARN] System clock and NTP service information cannot be found.
 [HELP] See `timedatectl | grep -i -A 1 clock`. More information: https://www.passbolt.com/docs/hosting/configure/ntp/

 Config files

 [PASS] The application config file is present
 [PASS] The passbolt config file is present

 Core config

 [PASS] Cache is working.
 [PASS] Debug mode is off.
 [PASS] Unique value set for security.salt
 [PASS] Full base url is set to https://vps.useworkhero.com
 [PASS] App.fullBaseUrl validation OK.
 [PASS] /healthcheck/status is reachable.

 SSL Certificate

 [PASS] SSL peer certificate validates.
 [PASS] Hostname is matching in SSL certificate.
 [PASS] Not using a self-signed certificate.

 SMTP settings

 [PASS] The SMTP Settings plugin is enabled.
 [PASS] SMTP Settings coherent. You may send a test email to validate them.
 [PASS] The SMTP Settings source is: database.
 [WARN] The SMTP Settings plugin endpoints are enabled.
 [HELP] It is recommended to disable the plugin endpoints.
 [HELP] Set the PASSBOLT_SECURITY_SMTP_SETTINGS_ENDPOINTS_DISABLED environment variable to true.
 [HELP] Or set passbolt.security.smtpSettings.endpointsDisabled to true in /etc/passbolt/passbolt.php.
 [PASS] No custom SSL configuration for SMTP server.

 JWT Authentication

 [PASS] The JWT Authentication plugin is enabled.
 [PASS] The /etc/passbolt/jwt/ directory is not writable.
 [PASS] A valid JWT key pair was found.

 GPG Configuration

 [PASS] PHP GPG Module is installed and loaded.
 [PASS] The environment variable GNUPGHOME is set to /var/lib/passbolt/.gnupg.
 [PASS] The directory /var/lib/passbolt/.gnupg containing the keyring is writable by the webserver user.
 [PASS] The server OpenPGP key is not the default one.
 [PASS] The public key file is defined in /etc/passbolt/passbolt.php and readable.
 [PASS] The private key file is defined in /etc/passbolt/passbolt.php and readable.
 [PASS] The server key fingerprint matches the one defined in /etc/passbolt/passbolt.php.
 [PASS] The server public key defined in the /etc/passbolt/passbolt.php (or environment variables) is in the keyring.
 [PASS] There is a valid email id defined for the server key.
 [PASS] The public key can be used to encrypt a message.
 [PASS] The private key can be used to sign a message.
 [PASS] The public and private keys can be used to encrypt and sign a message.
 [PASS] The private key can be used to decrypt a message.
 [PASS] The private key can be used to decrypt and verify a message.
 [PASS] The public key can be used to verify a signature.
 [PASS] The server public key format is Gopengpg compatible.
 [PASS] The server private key format is Gopengpg compatible.

 Application configuration

 [PASS] Using latest passbolt version (4.10.1).
 [FAIL] Passbolt is not configured to force SSL use.
 [HELP] Set passbolt.ssl.force to true in /etc/passbolt/passbolt.php.
 [PASS] App.fullBaseUrl is set to HTTPS.
 [PASS] Selenium API endpoints are disabled.
 [PASS] Search engine robots are told not to index content.
 [INFO] The Self Registration plugin is enabled.
 [INFO] Registration is closed, only administrators can add users.
 [PASS] The deprecated self registration public setting was not found in /etc/passbolt/passbolt.php.
 [PASS] Host availability will be checked.
 [PASS] Serving the compiled version of the javascript app.
 [WARN] Some email notifications are disabled by the administrator.
 [PASS] The database schema is up to date.

 Database

 [PASS] The application is able to connect to the database
 [PASS] 34 tables found.
 [PASS] Some default content is present.

 [FAIL] 1 error(s) found. Hang in there!

Anything helps! Thanky you!

Hey guys! Just bumping this one up. Anything helps

Hello @devwh , sorry for the delay. Nice to see some API scripts :slight_smile: :construction:

I reviewed your script, to help with debugging, adding some more logging around the subprocess calls could make it easier to spot where things might be going wrong (like whether the input message is correctly passed or if the signing/encryption step is failing). Do you have any other errors in /var/log/passbolt/error.log ?

I made some month ago an example of a jwt authentication that you can check there. IMHO it looks like you might be running into some challenges with how the GPG commands are interacting with the system environment. The script relies on the system’s GPG configuration, so if the necessary keys aren’t properly imported or the environment isn’t set up to handle the --pinentry-mode loopback option, it could cause issues during the signing or encryption steps.

One thing to check is whether the keys are correctly available in the GPG keyring and that the commands are working as expected when run manually outside of the script.

Let me know :wink:

Best regards

Hey, @antony !

Thank you for responding. I’m still trying to get access the error.logs but for some reason I can’t read it. I’ll send it here ASAP!

I was able to view your JWT authentication poc and I think I’m doing the same as what you have under the encryptMessage() function. The rest I’m trying to do via make.com.

The keyring shows both the server’s public key and my user’s private key. I’ve also set their trust to ultimate. See:


I’ve also tried running the signing and encrypting the payload manually via the terminal and I was able to use the same scripts but still, it returns the “The credentials are invalid” error in make.com. I’ve also confirmed the public key is the same as with the one in /auth/verify.json. The user ID is also the one from https://domain.com/app/users/view/[uuid] when viewing the user in the Passbolt portal.

I can also verify the signed payload using my private key that the script is outputting via the terminal manually as I output both the encrypted and signed payload for testing.

Thanks again! :raised_hands:

Hello @devwh,

Just to confirm an assumption there, can you try setting up a verify token expiration to 10mns? e.g., verify_token_expiry = int((datetime.now() + timedelta(seconds=600)).timestamp())

Thanks

Hey @antony !

I’ve changed my code to match yours but it still responds with “The credentials are invalid.” error. I also verified the signed payload that the UNIX timestamp it was generating is indeed 10 minutes.
Here’s that part of the code:

"verify_token_expiry": (datetime.now() + timedelta(seconds=600)).strftime('%s')

Still working on the passblt error.log but here’s what I see in the server log when I try the /auth/jwt/login.json POST API call that returns the invalid credentials error in case it helps:

2025-01-30T13:49:06.149123717Z 10.0.1.6 - - [30/Jan/2025:13:49:06 +0000] "POST /auth/jwt/login.json HTTP/1.1" 400 250 "-" "Make/production"
2025-01-30T13:49:06.149534779Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963087 (exit status 0)
2025-01-30T13:49:06.149684270Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963089 (exit status 0)
2025-01-30T13:49:06.149770111Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963092 (exit status 0)
2025-01-30T13:49:06.149836332Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963094 (exit status 0)
2025-01-30T13:49:06.149912822Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963097 (exit status 0)
2025-01-30T13:49:06.149991163Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963099 (exit status 0)
2025-01-30T13:49:06.150073773Z 2025-01-30 13:49:06,149 INFO reaped unknown pid 963101 (exit status 0)
2025-01-30T13:49:06.150079563Z 2025-01-30 13:49:06,150 INFO reaped unknown pid 963103 (exit status 0)

Thank you!