ESP32 acting as BLE server, cannot pair cyclocomputer - c++

I'm trying to create a cycling power service on the ESP32 using PlatformIO and the NimBLE-Arduino library.
Connecting, bonding, subscribing to the notifications and receiving the power data all work when the client is the nRF Connect Android app, but my Lezyne GPS Mini cyclocomputer is not able to complete the pairing process. I'm looking for a way to decypher the logs and understand the problem in the communication.
My code, reduced to to the minimum:
#include <Arduino.h>
#include <NimBLEDevice.h>
#define CYCLING_POWER_SERVICE_UUID ((uint16_t) 0x1818)
#define CYCLING_POWER_FEATURE_CHAR_UUID ((uint16_t) 0x2A65)
#define SENSOR_LOCATION_CHAR_UUID ((uint16_t) 0x2A5D)
#define SENSOR_LOCATION_RIGHT_CRANK ((uint8_t) 6)
#define CYCLING_POWER_MEASUREMENT_CHAR_UUID ((uint16_t) 0x2A63)
class BLE : public BLEServerCallbacks {
public:
BLEServer *server;
BLECharacteristic *measurementCharacteristic;
bool connected = false;
bool oldConnected = false;
short power = 0;
unsigned short revolutions = 0;
unsigned short timestamp = 0;
const unsigned short flags = 0x20; // TODO
unsigned char bufMeasurent[8];
unsigned char bufSensorLocation[1];
unsigned char bufControlPoint[1];
unsigned char bufFeature[4];
void setup() {
BLEDevice::init("PM");
server = BLEDevice::createServer();
server->setCallbacks(this);
BLEUUID serviceUUID = BLEUUID(CYCLING_POWER_SERVICE_UUID);
BLEService *service = server->createService(serviceUUID);
BLECharacteristic *featureCharasteristic = service->createCharacteristic(
BLEUUID(CYCLING_POWER_FEATURE_CHAR_UUID),
NIMBLE_PROPERTY::READ
);
bufFeature[0] = 0xff;
bufFeature[1] = 0xff;
bufFeature[2] = 0xff;
bufFeature[3] = 0xff; // TODO
featureCharasteristic->setValue((uint8_t *)&bufFeature, 4);
BLECharacteristic *sensorLocationCharasteristic = service->createCharacteristic(
BLEUUID(SENSOR_LOCATION_CHAR_UUID),
NIMBLE_PROPERTY::READ
);
bufSensorLocation[0] = SENSOR_LOCATION_RIGHT_CRANK & 0xff;
sensorLocationCharasteristic->setValue((uint8_t *)bufSensorLocation, 1);
measurementCharacteristic = service->createCharacteristic(
BLEUUID(CYCLING_POWER_MEASUREMENT_CHAR_UUID),
NIMBLE_PROPERTY::READ
| NIMBLE_PROPERTY::NOTIFY
| NIMBLE_PROPERTY::INDICATE
);
service->start();
BLEAdvertising *advertising = BLEDevice::getAdvertising();
advertising->addServiceUUID(serviceUUID);
BLEDevice::startAdvertising();
}
void loop() {
// notify changed value
if (connected) {
bufMeasurent[0] = flags & 0xff;
bufMeasurent[1] = (flags >> 8) & 0xff;
bufMeasurent[2] = power & 0xff;
bufMeasurent[3] = (power >> 8) & 0xff;
bufMeasurent[4] = revolutions & 0xff;
bufMeasurent[5] = (revolutions >> 8) & 0xff;
bufMeasurent[6] = timestamp & 0xff;
bufMeasurent[7] = (timestamp >> 8) & 0xff;
measurementCharacteristic->setValue((uint8_t *)&bufMeasurent, 8);
measurementCharacteristic->notify();
delay(1000);
}
// disconnecting
if (!connected && oldConnected) {
delay(500);
server->startAdvertising();
Serial.println("start advertising");
oldConnected = connected;
}
// connecting
if (connected && !oldConnected) {
oldConnected = connected;
}
}
void onConnect(BLEServer *pServer, ble_gap_conn_desc* desc) {
connected = true;
Serial.println("Server onConnect");
}
void onDisconnect(BLEServer *pServer) {
connected = false;
Serial.println("Server onDisconnect");
}
};
BLE ble;
void setup() {
Serial.begin(115200);
ble.setup();
}
void loop() {
if (ble.connected) {
ble.power = random(300);
ble.revolutions = random(2);
ble.timestamp = (ushort)millis();
delay(100);
}
ble.loop();
}
Logs with NimBLE debug log enabled:
entry 0x400806a8
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0003 len=0
0x03 0x0c 0x00
ble_hs_hci_cmd_send: ogf=0x04 ocf=0x0001 len=0
0x01 0x10 0x00
ble_hs_hci_cmd_send: ogf=0x04 ocf=0x0003 len=0
0x03 0x10 0x00
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0001 len=8
0x01 0x0c 0x08 0x90 0x80 0x00 0x02 0x00 0x80 0x00 0x20
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0063 len=8
0x63 0x0c 0x08 0x00 0x00 0x80 0x00 0x00 0x00 0x00 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0001 len=8
0x01 0x20 0x08 0x7f 0x06 0x00 0x00 0x00 0x00 0x00 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0002 len=0
0x02 0x20 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0003 len=0
0x03 0x20 0x00
ble_hs_hci_cmd_send: ogf=0x04 ocf=0x0009 len=0
0x09 0x10 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0018 len=0
0x18 0x20 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0018 len=0
0x18 0x20 0x00
Device added to RL, Resolving list count = 1
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0031 len=1
0x31 0x0c 0x01 0x01
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0033 len=7
0x33 0x0c 0x07 0xff 0x00 0x00 0x0c 0x00 0x00 0x00
ble_hs_hci_cmd_send: ogf=0x03 ocf=0x0031 len=1
0x31 0x0c 0x01 0x00
looking up peer sec;
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0018 len=0
0x18 0x20 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0018 len=0
0x18 0x20 0x00
Device added to RL, Resolving list count = 2
looking up peer sec;
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0009 len=32
0x09 0x20 0x20 0x00 0xa9 0xfb 0x3f 0x02 0x00 0x00 0x00 0x3c 0x1f 0xfc 0x3f 0xc0 0x46 0xfc 0x3f 0x01 0x24 0xf4 0x02 0xfd 0x1f 0x0d 0x80 0xe0 0x64 0xfc 0x3f 0xbc 0xbd 0xfb 0x3f
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0008 len=32
0x08 0x20 0x20 0x0b 0x02 0x01 0x06 0x03 0x03 0x18 0x18 0x03 0x09 0x50 0x4d 0xc0 0x46 0xfc 0x3f 0x01 0x24 0xf4 0x02 0xfd 0x1f 0x0d 0x80 0xe0 0x64 0xfc 0x3f 0xbc 0xbd 0xfb 0x3f
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0006 len=15
0x06 0x20 0x0f 0x30 0x00 0x60 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x000a len=1
0x0a 0x20 0x01 0x01
Server onConnect
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0016 len=2
0x16 0x20 0x02 0x00 0x00
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x01 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=24
ble_hs_hci_acl_tx(): 0x00 0x00 0x18 0x00 0x14 0x00 0x04 0x00 0x11 0x06 0x01 0x00 0x05 0x00 0x00 0x18 0x06 0x00 0x09 0x00 0x01 0x18 0x0a 0x00 0xff 0xff 0x18 0x18
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x06 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=18
ble_hs_hci_acl_tx(): 0x00 0x00 0x12 0x00 0x0e 0x00 0x04 0x00 0x11 0x06 0x06 0x00 0x09 0x00 0x01 0x18 0x0a 0x00 0xff 0xff 0x18 0x18
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x0a 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=12
ble_hs_hci_acl_tx(): 0x00 0x00 0x0c 0x00 0x08 0x00 0x04 0x00 0x11 0x06 0x0a 0x00 0xff 0xff 0x18 0x18
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x00 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x00 0x00 0x01
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x00 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x00 0x00 0x01
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x00 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x00 0x00 0x01
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x00 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x00 0x00 0x01
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x00 0x00 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x00 0x00 0x01
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
ble_hs_hci_evt_acl_process(): conn_handle=0 pb=2 len=11 data=0x07 0x00 0x04 0x00 0x10 0x42 0x4e 0xff 0xff 0x00 0x28
host tx hci data; handle=0 length=9
ble_hs_hci_acl_tx(): 0x00 0x00 0x09 0x00 0x05 0x00 0x04 0x00 0x01 0x10 0x42 0x4e 0x0a
Server onDisconnect
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x0006 len=15
0x06 0x20 0x0f 0x30 0x00 0x60 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x07 0x00
ble_hs_hci_cmd_send: ogf=0x08 ocf=0x000a len=1
0x0a 0x20 0x01 0x01
start advertising
Update
Following Youssif Saeed's insightful suggestions I think I made some progress. Observing the logs from the nRF Connect mobile app while connecting to the ESP32 was not really helpful as there was no error in the pairing or bonding process. However, after cloning the service and setting up the GATT server in nRF Connect I was able to capture the HCI communication. This required using an old rooted Android phone. Now I'm looking at the btsnoop_hci.log file in Wireshark, trying to understand what's going wrong, any pointers are appreciated.

Related

Resolve length limit with xb command in gdb

I need to copy the hex bytes in xb command, but gdb print them in multiple lines, and I had to remove the memory address from the output
(gdb) x/20xb 0x00007ffff7e84000
0x7ffff7e84000 <opendir>: 0xf3 0x0f 0x1e 0xfa 0x41 0x55 0x41 0x54
0x7ffff7e84008 <opendir+8>: 0x55 0x53 0x48 0x81 0xec 0xa8 0x00 0x00
0x7ffff7e84010 <opendir+16>: 0x00 0x64 0x48 0x8b
Tried set width unlimited but it didn't work.
What should I do?
EDIT
Full session while testing Employed Russian's solution:
# gdb /bin/ls
Reading symbols from /bin/ls...
(No debugging symbols found in /bin/ls)
(gdb) b opendir
Breakpoint 1 at 0x4870
(gdb) r
Starting program: /usr/bin/ls
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x00007ffff7e84000 in opendir () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) define xb
Type commands for definition of "xb".
End with a line saying just "end".
> set var $j = 0
> while $j < $arg1
> printf "0x%02x ", (char *) $arg0[$j++]
> end
> printf "\n"
>end
(gdb) xb 0x00007ffff7e84000 20
cannot subscript something of type `long'
(gdb)
Test program:
int main()
{
char buf[] = "abcdefghijklmopqrstuvwxyz";
return 0;
}
gcc -g t.c
cat > xb.txt <<'EOF'
define xb
set var $j = 0
while $j < $arg1
printf "0x%02x ", ((char *) $arg0)[$j++] & 0xFF
end
printf "\n"
end
EOF
gdb -q -x xb.txt ./a.out
Reading symbols from ./a.out...
(gdb) start
Temporary breakpoint 1 at 0x1129: file t.c, line 3.
Starting program: /tmp/a.out
Temporary breakpoint 1, main () at t.c:3
3 char buf[] = "abcdefghijklmnopqrstuvwxyz";
(gdb) n
4 return 0;
(gdb) x/16bx buf
0x7fffffffdad0: 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68
0x7fffffffdad8: 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70
(gdb) xb buf 16
0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70
(gdb) xb buf 27
0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x00

gdb print hex array in single byte mode

I'm using x/20x to print binary data in gdb
(gdb) x/20x 0x555555558df0
0x555555558df0: 0xfa1e0ff3 0x56415741 0x54415541 0x55fc8941
I wanted to print it in single byte like this:
0xf3 0x0f 0x1e 0xfa 0x41 0x57 0x41 0x56 ...
Is that possible?
EDIT
I've tried xb command as j6 suggested, but how can I print all of them in a single line?
(gdb) x/20xb 0x00007ffff7e84000
0x7ffff7e84000 <opendir>: 0xf3 0x0f 0x1e 0xfa 0x41 0x55 0x41 0x54
0x7ffff7e84008 <opendir+8>: 0x55 0x53 0x48 0x81 0xec 0xa8 0x00 0x00
0x7ffff7e84010 <opendir+16>: 0x00 0x64 0x48 0x8b
Try format xb, which is format x (hex), size b (bytes):
(gdb) x /8xb argv
0x7fffffffdc88: 0x20 0xe1 0xff 0xff 0xff 0x7f 0x00 0x00
help x is your friend.

C++ How to validate Google JWT (RS256) using OpenSSL

I am trying to validate a JWT token using OpenSSL and c++.
As an exercise for experimentation and learning, please do not suggest to use 3rd party libraries to do the job.
The token has the usual form Header.Payload.Signature that I can Base64URL decode but I am not able to validate the signature.
Following the RFC does not mention how to procceed with RS256:
Validate the JWS Signature against the JWS Signing Input
ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS
Payload)) in the manner defined for the algorithm being used, which
MUST be accurately represented by the value of the "alg" (algorithm)
Header Parameter, which MUST be present.
I am following JWT: The Complete Guide to JSON Web Tokens:
How does the receiver check RS256 Signatures? The receiver of the JWT
will then:
take the header and the payload, and hash everything with SHA-256
decrypt the signature using the public key, and obtain the signature hash
the receiver compares the signature hash with the hash that he calculated himself based on the Header and the Payload
Do the two hashes match? Then this proves that the JWT was indeed created by the Authentication server!
When decoding the header using Base64Url I got a valid JSON. Payload is also valid JSON:
{"alg":"RS256","kid":"03b2d22c2fecf873ed19e5b8cf704afb7e2ed4be","typ":"JWT"}
Then I recovered the proper certificate from Google for the given kid.
My test code is:
// Split fields for convenience
static std::string GTOKEN_B64URL_HEADER ("eyJhb...shortened...V1QifQ");
static std::string GTOKEN_B64URL_PAYLOAD("eyJpc...shortened...MzExfQ");
static std::string GTOKEN_B64URL_SIGN ("k7Ppq...shortened...TJCTdQ");
// From https://www.googleapis.com/oauth2/v1/certs using the specified "kid"
static const char* CERT =
"-----BEGIN CERTIFICATE-----\n"
"MIIDJjCCAg6gAwIBAgIIHdBXKdu8rS4wDQYJKoZIhvcNAQEFBQAwNjE0MDIGA1UE\n"
...
"MB7mbimIU22061HCjFbdlEscy26X/BXtxPpQjEwbkzJ5wy2bVu2AIIdo\n"
"-----END CERTIFICATE-----\n";
// Preparation: Get the public key from the PEM cert
//
BIO *memCert = BIO_new_mem_buf(CERT, -1);
X509* cert= PEM_read_bio_X509(memCert, nullptr, nullptr, nullptr);
if (nullptr == cert) {
showOpenSSLErrors("Unable to load CERT: ");
return;
}
EVP_PKEY* key = X509_get_pubkey(cert);
if (nullptr == key) {
showOpenSSLErrors("Unable to get pubkey from cert: ");
return;
}
int idKey = EVP_PKEY_id(key);
int type = EVP_PKEY_type(idKey);
if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA2) {
std::cout << "Key type is not RSA" << std::endl;
return;
}
RSA* rsa = EVP_PKEY_get1_RSA(key);
if (nullptr == rsa) {
showOpenSSLErrors("Invalid RSA: ");
return;
}
// 1) take the header and the payload, and hash everything with SHA-256
//
std::string whatToValidate;
computeHashSHA256(GTOKEN_B64URL_HEADER+"."+GTOKEN_B64URL_PAYLOAD, whatToValidate);
// 2) decrypt the signature using the public key ...
//
std::string signatureB64 = decodeBase64URL(GTOKEN_B64URL_SIGN);
std::string signature;
signature.resize( RSA_size(rsa) );
int len = RSA_public_decrypt(
signatureB64.size(),
(unsigned char*)signatureB64.data(),
(unsigned char*)signature.data(),
rsa, RSA_NO_PADDING);
if (len == -1) {
std::cout << "Decrypt failed" << std::endl;
return;
}
signature.resize(len);
// 2) ... and obtain the signature hash
std::string signatureHash;
computeHashSHA256(signature, signatureHash);
if (whatToValidate.size() != signatureHash.size()) {
printf("Len does not match! (%d vs %d) \n", whatToValidate.size(), signatureHash.size());
return;
}
std::cout << "whatToValidate: " << whatToValidate << std::endl;
std::cout << "signatureHash: " << signatureHash << std::endl;
// 3) the receiver compares the signature hash with the hash that he
// calculated himself based on the Header and the Payload
if (signatureHash != whatToValidate) {
printf(" comparison FAILED!!!\n");
}
// Extra check: Ensure SHA256 algorithm is working
//
const std::string decodedHeader(decodeBase64URL(GTOKEN_B64URL_HEADER));
std::string headerSHA256;
computeHashSHA256(decodedHeader, headerSHA256);
std::cout << "Header: " << decodedHeader << std::endl;
std::cout << "Header SHA256: " << headerSHA256 << std::endl;
std::cout << "Signature size: " << signature.size() << "(" << GTOKEN_B64URL_SIGN.size() << " base64Url)" << std::endl;
std::cout << "Validate: " << whatToValidate.size() << std::endl;
std::cout << std::endl;
Output of this code is:
whatToValidate: d4981a11b8d9a686e7f9919cf7d6477c5e7c0e35fcd61133ad2fdb8cb845b49a
signatureHash: e79eee72dcc4412601689f03c0c83e6958b87447172f5109bffebbc7f009c38d
comparison FAILED!!!
Header: {"alg":"RS256","kid":"03b2d22c2fecf873ed19e5b8cf704afb7e2ed4be","typ":"JWT"}
Header SHA256: 5b53315f0b0424c866ff364e9f7bd2f882c61e4460aa1f503c2abd1ad753426e
Signature size: 256(342 base64Url)
Validate: 64
Header SHA256 proves that computeHashSHA256() works as expected.
What am I doing wrong?
Is there any alternative approach I can use? (Also tried RSA_verify() with no luck since I do not really know how)
Edit
SHA256 for JWS Signing Input will be 32 bytes. whatToValidate (ASCII representation of SHA256) will be 64 bytes. signature is 256 bytes long.
Signature does not look like a SHA256 either raw or ASCII.
Hence the question: shall whatToValidate be the SHA256 on the JWS Signing Input?
Edit - Base64URL decoded Signature (Binary):
0x93 0xB3 0xE9 0xA8 0x40 0xBA 0x03 0xB8 0x26 0x5C 0x84 0x97 0xD0 0x66 0xA5 0xF2
0x21 0x90 0x34 0x77 0x03 0x79 0x61 0xEE 0x06 0xC4 0xCD 0x81 0x06 0x22 0x7B 0x59
0xF7 0x2B 0x13 0x5B 0xEC 0x21 0x29 0xD6 0x81 0xB5 0xE1 0x18 0x64 0xE7 0xB2 0x0E
0xE1 0xF6 0x8F 0xB5 0x39 0x98 0xF5 0x28 0x65 0xBC 0xB5 0x5D 0x02 0x0E 0x80 0x8B
0x07 0x7A 0xF0 0x14 0x57 0x6E 0xF6 0x2C 0x9D 0xEE 0x7A 0x2E 0x2D 0xA0 0x1C 0xFD
0xC6 0x45 0xBC 0xE3 0x60 0xA9 0x67 0x05 0x84 0x05 0xBA 0xDC 0x34 0xBC 0x97 0xF1
0x51 0x3E 0x30 0x73 0xEA 0x4D 0x4F 0xF1 0x33 0xE2 0x1C 0x44 0x8E 0x6F 0x3F 0x0B
0xE6 0x62 0xA8 0x9E 0xFE 0x27 0xB3 0xF3 0x41 0xFB 0x5C 0xA0 0xC1 0x06 0x6B 0x91
0x4A 0xA5 0x7C 0xB8 0x85 0xEF 0xB3 0xAE 0x28 0x1C 0xC1 0x74 0x91 0xBB 0xB8 0xF9
0xAD 0xB0 0x13 0x34 0x96 0x4C 0xBF 0x6C 0xD2 0x5A 0x55 0x0D 0x4C 0x2D 0x01 0xC7
0x8D 0xBF 0x4B 0x8E 0x9B 0x31 0xAB 0x2B 0x1B 0x9A 0x8F 0x7A 0x32 0xB5 0x91 0x52
0x7E 0xE7 0xA8 0x7F 0x49 0x3F 0xCF 0x2C 0xAA 0x9B 0xE3 0x11 0x08 0x20 0x4E 0x5D
0x68 0x2B 0x75 0xEB 0xB4 0xE7 0xDA 0x23 0xDA 0xE0 0xCD 0xF7 0xD9 0x0D 0x42 0x15
0x27 0x94 0x86 0xA3 0xCE 0xF5 0xAF 0xD0 0x38 0x32 0xD7 0x05 0xD2 0xB2 0xED 0x7E
0xEC 0xB1 0x3D 0x3C 0xFA 0xE8 0xA4 0x14 0xE1 0x67 0x0E 0x16 0xF5 0x57 0x3B 0xAA
0x84 0x31 0x02 0x3F 0x29 0x34 0x1D 0x68 0xCF 0x82 0x23 0x32 0x4C 0x90 0x93 0x75
Edit - Decrypted signature:
0x00 0x01 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 0x30 0x31 0x30
0x0d 0x06 0x09 0x60 0x86 0x48 0x01 0x65 0x03 0x04 0x02 0x01 0x05 0x00 0x04 0x20
0xd4 0x98 0x1a 0x11 0xb8 0xd9 0xa6 0x86 0xe7 0xf9 0x91 0x9c 0xf7 0xd6 0x47 0x7c
0x5e 0x7c 0x0e 0x35 0xfc 0xd6 0x11 0x33 0xad 0x2f 0xdb 0x8c 0xb8 0x45 0xb4 0x9a
Your primary issue is that you're computing a hash of the hash recovered when you call RSA_public_decrypt.
An RSA signature is just a hash of some data plus some extra info that gets run through the RSA algorithm. When you call RSA_public_decrypt it's that original hash+metadata that you'll recover; not the message that was originally signed. You're treating it like it's the original message that was signed and then computing its hash to compare against the JWT.
The second issue you'll run into is the fact that RSA signatures are padded out to the size of the key modulus before being fed into the RSA algorithm. That's why the output of RSA_public_decrypt is 256 bytes long even though the original hash was only 32 bytes long. The actual recovered hash is the last 32 bytes of the output.
Putting that all together, you need to do something like this:
// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);
// 2) decrypt the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);
std::string decrypted_signature(RSA_size(rsa), '\0');
int len = RSA_public_decrypt(
raw_signature.size(),
reinterpret_cast<unsigned char*>(raw_signature.data()),
reinterpret_cast<unsigned char*>(decrypted_signature.data()),
rsa,
RSA_PKCS1_PADDING // Will verify that the padding is at least structurally correct
);
decrypted_signature.resize(len);
// 3) Extract the last 32 bytes to get the original message hash from the decrypted signature
std::string recovered_hash = decrypted_signature.substr(decrypted_signature.size() - to_validate.size())
// 4) Compare the recovered hash to the hash of the token
if (to_validate == recovered_hash) {
std::cout << "Signature verified\n";
} else {
std::cout << "Signature validation failed\n"
}
Note that this still isn't quite right. Part of the signature padding contains information about the type of hash that was applied to generate the signature (see RFC 8017 section 9.2). The correct way to do the final comparison is to generate the appropriate padding and append to_validate to the end of it rather than strip the padding off of decrypted_signature. OpenSSL doesn't provide a public function do do that however.
That's why you should use RSA_verify instead of fiddling with RSA_public_decrypt at all. It just calls RSA_public_decrypt internally, but it also does the comparison the correct way using a private function to generate the PKCS#1 header (RSA_public_decrypt with RSA_PKCS1_PADDING verifies the rest of the padding is correct). To do that, you would do something like this:
// 1) Calculate the SHA256 hash of the base64urled header and payload
std::string to_validate = sha256raw(TOKEN_B64URL_HEADER + "." + TOKEN_B64URL_PAYLOAD);
// 2) verify the signature
std::string raw_signature = decodeBase64URL(TOKEN_B64URL_SIGNATURE);
int verified = RSA_verify(
NID_sha256,
reinterpret_cast<unsigned char*>(to_validate.data()),
to_validate.size(),
reinterpret_cast<unsigned char*>(raw_signature.data()),
raw_signature.size(),
rsa
);
if (verified) {
std::cout << "Signature verified\n";
} else {
std::cout << "Signature validation failed\n"
}
See here for a live demo of both versions

What does ZTV,ZTS,ZTI mean in the result of gdb x/nfu "vtable_address"?

1. the code
class Parent {
public:
virtual void Foo() {}
virtual void FooNotOverridden() {}
};
class Derived : public Parent {
public:
void Foo() override {}
};
int main() {
Parent p1, p2;
Derived d1, d2;
}
2. gdb command
(gdb) x/300xb 0x400b30
0x400b30 is the first address of d's vtable.
3. gdb result
0x400b30 <_ZTV7Derived>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400b38 <_ZTV7Derived+8>: 0x80 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400b40 <_ZTV7Derived+16>: 0x60 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
0x400b48 <_ZTV7Derived+24>: 0x70 0x0a 0x40 0x00 0x00 0x00 0x00 0x00
0x400b50 <_ZTS7Derived>: 0x37 0x44 0x65 0x72 0x69 0x76 0x65 0x64
0x400b58 <_ZTS7Derived+8>: 0x00 0x36 0x50 0x61 0x72 0x65 0x6e 0x74
0x400b60 <_ZTS6Parent+7>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400b68 <_ZTI6Parent>: 0x70 0x20 0x60 0x00 0x00 0x00 0x00 0x00
4. question
What does _ZTV, _ZTS, _ZTI mean in <_ZTV7Derived>, <_ZTS7Derived>, <_ZTI6Parent>?
It is the way the C++ symbol names are mangled by your development platform. You can use the c++filt command line tool from GNU Binutils to find out:
$ c++filt _ZTV7Derived
vtable for Derived
$ c++filt _ZTS7Derived
typeinfo name for Derived
$ c++filt _ZTI6Parent
typeinfo for Parent
More specifically, it is the mangling defined by the Itanium or IA-64 C++ ABI which is also used on x86_64 (because the System V Application Binary Interface - AMD64 Architecture Processor Supplement says so in section 9.1 titled "C++"). See section on "Virtual Tables and RTTI" in the Itanium C++ ABI for the exact mangling details.

c++ boost filtering_streambuf read only some data

I try to create filter which would first read body size from stream and then consume only body_size bytes from stream. Although output stream is correctly created, yet for some reason all data from input stream is read. Is there a way to correct this behaviour?
struct body_size_filter {
size_t body_size;
size_t bytes_read;
typedef char char_type;
typedef boost::iostreams::multichar_input_filter_tag category;
template<typename Source>
std::streamsize read(Source& src, char* s, std::streamsize n)
{
if (bytes_read >= body_size + 4)
{
return -1;
}
int c;
// read body size
while (bytes_read < 4 && (c = boost::iostreams::get(src)) != EOF && c != boost::iostreams::WOULD_BLOCK)
{
body_size = body_size | (static_cast<size_t>(c) << (24 - 8 * bytes_read));
bytes_read++;
}
if (bytes_read < 4)
{
return c;
}
char* first = s;
char* last = s + n;
while (bytes_read < body_size + 4 && first != last && (c = boost::iostreams::get(src)) != EOF && c != boost::iostreams::WOULD_BLOCK )
{
*first++ = c;
bytes_read++;
}
std::streamsize result = static_cast<std::streamsize>(first - s);
return result == 0 && c != boost::iostreams::WOULD_BLOCK ? -1 : result;
}
body_size_filter()
{
body_size = 0;
bytes_read = 0;
}
};
and usage
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
in.push(body_size_filter());
in.push(buf);
boost::asio::streambuf body_buf;
boost::iostreams::copy(in, body_buf); // after this call buf.size() == 0 - i was expecting some data to be left in buffer
I suppose it's because of the way copy is implemented. It looks like it just exhausts the source reading blocks of some size (e.g. 128 bytes by default, but the blocksize can be overridden using the third argument to copy).
So, by the time your filter knows it's time to stop reading, the source has already been polled for too much.
I'd fix it by making a proper function to do the copy instead (after all, you do not want to copy streams. You want to read a BSON-like document off the source):
template <typename Source, typename Dest>
std::streamsize bson_copy(Source& src, Dest& dst) {
unsigned char bytes[4];
auto n = src.sgetn(reinterpret_cast<char*>(+bytes), 4);
assert(n == 4);
n = 0;
n = (n << 8) + bytes[0];
n = (n << 8) + bytes[1];
n = (n << 8) + bytes[2];
n = (n << 8) + bytes[3];
std::cout << "\n\nLength: " << n << "\n";
std::istreambuf_iterator<char> it(&src);
std::copy_n(it, n, std::ostreambuf_iterator<char>(&dst));
++it; // somehow copy_n leaves the input iterator unincremented, so a `sbumpc` would be in order (?!)
return n;
}
DEMO and test
Here's a complete and working example.
Live On Coliru
int main() {
boost::asio::streambuf buf, body_buf;
write_bson(buf);
append_deadbeef(buf);
append_deadbeef(buf);
append_deadbeef(buf);
auto n = bson_copy(buf, body_buf);
std::cout << "bson_copy: " << n << "\n";
dumpoutput(body_buf);
std::cout << "remain: " << std::dec << buf.in_avail() << "\n";
verify_deadbeef(buf);
verify_deadbeef(buf);
verify_deadbeef(buf);
std::cout << "remain: " << std::dec << buf.in_avail() << "\n";
}
Prints the (correct) output:
Length: 257
bson_copy: 257
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f
0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f
0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f
0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f
0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f
0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f
0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f
0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f
0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f
0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf
0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf
0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf
0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf
0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef
0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff
0x00 remain: 12
verify: 0xdeadbeef
verify: 0xdeadbeef
verify: 0xdeadbeef
remain: 0