Not able to receive the Device Authentication Token via Device API. signature verification failed

I am using hosted Mender with a virtual qemu device.
I am calling the device authentication API endpoint and try to receive the Device Token as a response.

curl \
    -H "Content-Type: application/json" \
    -H "X-MEN-Signature: ${X_MEN_SIGNATURE}" \
    --data "${REQUEST_BODY}" \
    https://eu.hosted.mender.io/api/devices/v1/authentication/auth_requests -v
$REQUEST_BODY=
{
    "id_data": "{ \"mac\": \"XXX\"}",
    "pubkey": "-----BEGIN PUBLIC KEY-----
XXX
-----END PUBLIC KEY-----
",
    "tenant_token": "XXX"
}

For that, I am following the commands of this tutorial exactly, in particular to avoid formatting issues.
https://hub.mender.io/t/how-to-write-a-custom-client-interfacing-a-mender-server/1353

I get the authentication request listed on the hosted mender website for the specific device which I accept. According to the tutorial, I just need to send the same request again. But I am getting “error”:“signature verification failed”. I compared the public key on the hosted mender site of the authorization set that I accepted and it matches the one I generated locally. Also, tenant_token and MAC address must be correct since I get the request listed for the target device on hosted mender.

This is the first request which gives "error":"dev auth: unauthorized" as expected.

*   Trying 9.163.208.52:443...
* Connected to eu.hosted.mender.io (9.163.208.52) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=eu.hosted.mender.io
*  start date: Mar 11 09:46:22 2025 GMT
*  expire date: Jun  9 09:46:21 2025 GMT
*  subjectAltName: host "eu.hosted.mender.io" matched cert's "eu.hosted.mender.io"
*  issuer: C=US; O=Let's Encrypt; CN=R11
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x5d3810d2b9f0)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> POST /api/devices/v1/authentication/auth_requests HTTP/2
> Host: eu.hosted.mender.io
> user-agent: curl/7.81.0
> accept: */*
> content-type: application/json
> x-men-signature: fFA/gr99cVoklgW6ANlPKX+azofnQP/fZKU2zvJjklXReV4BJLrgc1cmZeT8MEt8QFZGF0wQagh3GMsTAKHbvW/QkV4O0LMsloDnBwXzHKq+5yAqooxt//eFZ47h33CwiJIQKFMEvT4S+RXcKBoOeq2AZCrAoITINhW85gEEEKoZjPuFmAxHbjk15IjyvA8piCy0siRQI9GKIbo3fgU2EDiUZO9iQid1KmhVFZAjvaWnWtus/lWSsgb1wOdZr3UQPtu1IbpcM5wI9behW5p46XWFRjmQv+BP8NcV7NJq1Ozm9EjFM/ewTBPY87veIx8oKJKJPOfAz5uZr1Vd6Hw2MHdBqgx9csIyaMIvUaYex8ho9bkapCJQlR8wnNq1Q77mfzuc571NL9SVIDlKBrTUiML/y+WWSXbbRJbUb9epZ5uZI36uEqPpKNKeEyTvjsArWLLZGnw8281k7dgNfs6uplQU0oM3r0rljoVSe/zhIr7uy1gdbbG+9a9YlFRXysaZ
> content-length: 775
> 
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* We are completely uploaded and fine
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 401 
< date: Mon, 07 Apr 2025 12:37:28 GMT
< content-type: application/json; charset=utf-8
< content-length: 86
< referrer-policy: no-referrer
< strict-transport-security: max-age=31536000; includeSubDomains
< x-content-type-options: nosniff
< x-men-requestid: 55f20b65-1819-4587-816e-a05722dd0aee
< x-xss-protection: 1; mode=block
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection #0 to host eu.hosted.mender.io left intact
{"error":"dev auth: unauthorized","request_id":"55f20b65-1819-4587-816e-a05722dd0aee"}%

The second request gives "error":"signature verification failed".

curl \
    -H "Content-Type: application/json" \
    -H "X-MEN-Signature: ${X_MEN_SIGNATURE}" \
    --data "${REQUEST_BODY}" \
    https://eu.hosted.mender.io/api/devices/v1/authentication/auth_requests -v

*   Trying 9.163.208.52:443...
* Connected to eu.hosted.mender.io (9.163.208.52) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=eu.hosted.mender.io
*  start date: Mar 11 09:46:22 2025 GMT
*  expire date: Jun  9 09:46:21 2025 GMT
*  subjectAltName: host "eu.hosted.mender.io" matched cert's "eu.hosted.mender.io"
*  issuer: C=US; O=Let's Encrypt; CN=R11
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x560fa26b09f0)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> POST /api/devices/v1/authentication/auth_requests HTTP/2
> Host: eu.hosted.mender.io
> user-agent: curl/7.81.0
> accept: */*
> content-type: application/json
> x-men-signature: fFA/gr99cVoklgW6ANlPKX+azofnQP/fZKU2zvJjklXReV4BJLrgc1cmZeT8MEt8QFZGF0wQagh3GMsTAKHbvW/QkV4O0LMsloDnBwXzHKq+5yAqooxt//eFZ47h33CwiJIQKFMEvT4S+RXcKBoOeq2AZCrAoITINhW85gEEEKoZjPuFmAxHbjk15IjyvA8piCy0siRQI9GKIbo3fgU2EDiUZO9iQid1KmhVFZAjvaWnWtus/lWSsgb1wOdZr3UQPtu1IbpcM5wI9behW5p46XWFRjmQv+BP8NcV7NJq1Ozm9EjFM/ewTBPY87veIx8oKJKJPOfAz5uZr1Vd6Hw2MHdBqgx9csIyaMIvUaYex8ho9bkapCJQlR8wnNq1Q77mfzuc571NL9SVIDlKBrTUiML/y+WWSXbbRJbUb9epZ5uZI36uEqPpKNKeEyTvjsArWLLZGnw8281k7dgNfs6uplQU0oM3r0rljoVSe/zhIr7uy1gdbbG+9a9YlFRXysaZ
> content-length: 775
> 
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* We are completely uploaded and fine
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 401 
< date: Mon, 07 Apr 2025 12:49:29 GMT
< content-type: application/json; charset=utf-8
< content-length: 93
< referrer-policy: no-referrer
< strict-transport-security: max-age=31536000; includeSubDomains
< x-content-type-options: nosniff
< x-men-requestid: 90407db8-9ec7-454b-9556-47613f99d04a
< x-xss-protection: 1; mode=block
< 
* Connection #0 to host eu.hosted.mender.io left intact
{"error":"signature verification failed","request_id":"90407db8-9ec7-454b-9556-47613f99d04a"}%

Any help would be appreciated. Thanks.

Hey @grasshopper

thank you for your interest in Mender.
writing from my own experience with the self-generated signatures:

  1. if your request body looks verbatim like that:
{
    "id_data": "{ \"mac\": \"XXX\"}",
    "pubkey": "-----BEGIN PUBLIC KEY-----
XXX
-----END PUBLIC KEY-----
",
    "tenant_token": "XXX"
}

you have to generate the signature exactly from this data. each and every byte of what you send and what you use to generate the signature must match
2. you are going nowhere fast with the public key formatting: the sooner you convert the newlines to \n (two characters, \ and n) the better off you will be.
3. the signatures generation can be tricky. for a hands-on example, checkout the proof-of-concept bash Mender Client written by Mirza: A fake Mender client implemented in bash · GitHub . especially note the normalize_data and generate_signature functions – if I have not, then they will put you on the right track.

let me know how it goes.

best regards,
peter

Hi @peter, thanks for your quick reply.

The REQUEST_BODY I posted may have given a wrong impression. I mainly posted it regarding content. As I said, to avoid formatting issues I followed the tutorial I linked, and used the same shell commands.

How to write a custom client interfacing a Mender server

There, shell variables are (re)-used for the process. I am in particular wondering what may be the issue if I am following the tutorial exactly. The process is still up to date, right?

Thanks for the fake mender client link, I will check it out. And thanks for your help.

Got it working. I can confirm it works with the tutorial - I received the token.
It was an issue with my shell. I packed the commands in a bash script and there was no issue.
The PUBLIC_KEY indeed should have the \n included, and not do line breaks which my shell did automatically, since the command was the same. I mistakenly thought, it was just a way to display \n.

Thanks for your help @peter!