goToMain / libosdp

Implementation of IEC 60839-11-5 OSDP (Open Supervised Device Protocol); provides a C library with support for C++, Rust and Python3
https://libosdp.sidcha.dev
Apache License 2.0
134 stars 71 forks source link

Initiating Secure Channel in CP Mode #60

Closed matt-treiber-compass-es closed 3 years ago

matt-treiber-compass-es commented 3 years ago

First off, thanks for the help to @patrick-compass with porting to FreeRTOS on a Cortex M4 on Issue 40. We are able to talk to multiple readers on a channel and move readers to a new PD address and 115,200 as well as detect cards and key presses.

Now we are trying to get a secure channel established -- first with the default SCBK after which we can then issue a osdp_KEYSET command with a new SCBK. I am working off of the current master -- cdf4627.

Unfortunately, the library returns an error: "CP: Failed to verify PD cryptogram".

I have a Veridt reader in its default state which a Veridt engineer assures me I should be able to use the SCBK-D to initiate a secure channel. It is my understanding from working with Veridt, that the reader needs be informed to use the SCBK-D via the osdp_CHLNG command.

I am hoping that this is a usage error on my part.

Describe the bug I initiate a CP session and I set the OSDP_FLAG_ENFORCE_SECURE, OSDP_FLAG_INSTALL_MODE, and PD_FLAG_SC_USE_SCBKD flags in the PD info structure. I also set the pd_info->scbk to the SCBK-D, otherwise the session will fail and NULL is returned by osdp_cp_setup(). My call to osdp_cp_setup() gives NULL for the master_key as that is being deprecated by the spec.

Communication with the reader starts successfully with CMD_ID and CMD_CAP commands, but verification of the PD cryptogram fails.

Expected behavior Secure Channel is established

Observed behavior OSDP: ERROR: PD[0]: CP : Failed to verify PD cryptogram

Additional context

There is an issue with printf in the code handling the color coding, I apologize, but here is a debug output log:

.[32mOSDP: INFO : CP : CP setup complete
cmd> OSDP: PD[0]: Sent [zu] =>
    0000  0a 53 01 09 00 04 61 00  60 23                    |.S....a.`#      |
OSDP: PD[0]: Received [zu] =>
    0000  15 53 81 14 00 04 45 00  11 12 4d 42 30 31 31 30  |.S....E...MB0110|
    000zu  02 02 25 e5 21                                    |..%.!           |
.[0mOSDP: DEBUG: PD[0]: CP : CMD(61) REPLY(45)
.[0mOSDP: PD[0]: Sent [zu] =>
    0000  0a 53 01 09 00 05 62 00  03 41                    |.S....b..A      |
OSDP: PD[0]: Received [zu] =>
    0000  1c 53 81 38 00 05 46 01  01 02 03 03 00 04 04 01  |.S.8..F.........|
    000zu  04 02 06 05 01 01 06 01  00 07 02 00              |............    |
OSDP: PD[0]: Received [zu] =>
    0000  39 53 81 38 00 05 46 01  01 02 03 03 00 04 04 01  |.S.8..F.........|
    000zu  04 02 06 05 01 01 06 01  00 07 02 00 08 01 00 09  |................|
    000zu  11 00 0a 00 10 0b 00 10  0c 01 00 0d 01 01 0e 00  |................|
    000zu  00 0f 01 01 10 01 01 a7  f7                       |.........       |
.[0mOSDP: DEBUG: PD[0]: CP : CMD(62) REPLY(46)
.[0mOSDP: PD[0]: Sent [zu] =>
    0000  14 53 01 13 00 0e 03 11  01 76 00 b0 81 97 8e 60  |.S.......v.....`|
    000zu  41 35 ad ed                                       |A5..            |
OSDP: PD[0]: Received [zu] =>
    0000  23 53 81 2b 00 0e 03 12  01 76 00 11 12 4d 42 31  |.S.+.....v...MB1|
    000zu  31 30 95 9f 9a 98 99 99  99 99 26 0e 49 39 9a 02  |10........&.I9..|
    000zu  ca f4 14                                          |...             |
OSDP: PD[0]: Received [zu] =>
    0000  2c 53 81 2b 00 0e 03 12  01 76 00 11 12 4d 42 31  |.S.+.....v...MB1|
    000zu  31 30 95 9f 9a 98 99 99  99 99 26 0e 49 39 9a 02  |10........&.I9..|
    000zu  ca f4 14 7e cf 37 6b a1  a6 96 a6 c3              |...~.7k.....    |
.[31mOSDP: ERROR: PD[0]: CP : Failed to verify PD cryptogram
.[0m.[32mOSDP: INFO : PD[0]: CP : SC Failed. Set PD offline due to ENFORCE_SECURE

In debugging this, I've found that in the OSDP_CP_STATE_CAPDET, PD_FLAG_SC_USE_SCBKD is cleared and I'm not sure why. Because when the response arrives, osdp_compute_session_keys() doesn't use the SCBK-D, but generates the SCBK with osdp_compute_scbk(pd, ctx->sc_master_key, pd->sc.scbk);. However, If I force the used of SCBK-D in osdp_compute_session_keys(), I get the same result with failing to verify PD cryptogram.

However, if I comment out lines 928 and 929 of osdp_cp.c which clears the PD_FLAG_SC_USE_SCBKD, the reader responds with a NAK, even if I also force a 0 in writing to the SMB[2] byte in cp_build_command():

.[0mOSDP: PD[0]: Sent [zu] =>
    0000  14 53 01 13 00 0e 03 11  00 76 87 fe 43 1e 56 12  |.S.......v..C.V.|
    000zu  b9 f8 e8 e1                                       |....            |
OSDP: PD[0]: Received [zu] =>
    0000  0d 53 81 0c 00 0e 03 12  00 41 06 19 50           |.S.......A..P   |
.[33mOSDP: WARN : PD[0]: CP : PD replied with NAK(6) for CMD(76)
.[0m.[0mOSDP: DEBUG: PD[0]: CP : CMD(76) REPLY(41)
.[0m.[31mOSDP: ERROR: PD[0]: CP : CHLNG failed. Set PD offline due to ENFORCE_SECURE

From issue 40:

Implement SC methods osdp_encrypt(), osdp_decrypt() and osdp_get_rand() using some crypto library available in FreeRTOS.

Obviously, OpenSSL is not defined or used, so libOSDP is using hand written implementation of the above functions and I'm able to step through them.

Any help would be appreciated!

sidcha commented 3 years ago

@matt-treiber-compass-es, when OSDP_FLAG_ENFORCE_SECURE is requested, use of SCBK-D is not permitted (see include/osdp.h for other such restrictions). In fact, the log line that you pasted says exactly that: CHLNG failed. Set PD offline due to ENFORCE_SECURE.

When you see a "due to ENFORCE_SECURE" failure, the CP/PD is doing something that we deem to be a protected action that needs to be under supervision. So, in this case, you should start the communication channel without OSDP_FLAG_ENFORCE_SECURE to set the SCBK and once this is done, start passing OSDP_FLAG_ENFORCE_SECURE in future (non-supervised) runs.

Another point to note: PD_FLAG_SC_USE_SCBKD is an internal flag and using it may be as harmless as not doing anything or causing security issues (OSDP isn't super secure anyways; you wouldn't want to cut down any more limbs :D). So the general rule of thumb is: if it ain't on include/osdp.h don't use it.

Let me know if that helps and feel free to reopen this issue if this did not fix your problem.

bve-ape commented 3 years ago

Hello, I am also trying to establish a secure communication with a HID reader. The reader is set up with a default key. What I want is to establish a secure channel using this default key. I tried to set up a secure channel more or less the same way @matt-treiber-compass-es tried. However after your explaining it is still not clear how to do it. For example we are doing:


osdp_pd_info_t pd_info[] = {
  {
    .baud_rate = 115200,
    .address = 0,
    .flags = OSDP_FLAG_ENFORCE_SECURE
...

cp.setup(1, pd_info, "Default Key I want to use in hex");

And I got the same errors described in this thread.

_So, in this case, you should start the communication channel without OSDP_FLAG_ENFORCE_SECURE to set the SCBK and once this is done, start passing OSDP_FLAG_ENFORCESECURE in future (non-supervised) runs.

Can you give some hints on how to set the keys and establish a SC following your explanation? Thanks

sidcha commented 3 years ago

@bve-ape, you should NOT pass OSDP_FLAG_ENFORCE_SECURE when you want to use SCB-D to setup the secure channel. So you should do this:

uint8_t my_pd_scbk[16] = { /* Put you key as 16 bytes */ };
osdp_pd_info_t pd_info[] = {
  {
    .baud_rate = 115200,
    .address = 1,
    .flags = 0,
    .scbk = my_pd_scbk,
} 
cp.setup(1, pd_info, NULL);

In the logs, you should see messages indicating that a secure channel was setup using SCBK-D and then a subsequent KEYSET was performed. After this, the PD is setup with my_pd_scbk as SCBK. In the next runs, instead of .flags = 0, you can pass .flags = OSDP_FLAG_ENFORCE_SECURE.

bve-ape commented 3 years ago

Thanks for your insight. What is not clear is what you mean with In the next runs. Do I need to do another cp.setup ? Right now the code is doing one setup then setting callbacks, etc then doing some polling:

    cp.set_event_callback(cp_event_callback);
    cp.set_osdp_command_complete_callback(osdp_command_complete_callback);
..
    cp.refresh();
    usleep(1000);   // sleep 1 milli second

Thank you very much

sidcha commented 3 years ago

The PD has to be forced to enter the install-mode (to use SCBK-D) with some method such as by presenting config-card. During this time, CP must also be stared in a special mode where it does not pass the OSDP_FLAG_ENFORCE_SECURE flag (this operation is insecure because it would start a secure channel with default keys).

After the SC was setup using SCBK-D and the SCBK was set to the PD (at this time the PD would be moved out of install-mode) it's generally a good practice to restart everything from scratch (ie., call cp.setup() again or just reboot the device) only this time you can start passing OSDP_FLAG_ENFORCE_SECURE.

matt-treiber-compass-es commented 3 years ago

Another point to note: OSDP_FLAG_INSTALL_MODE is an internal flag and using it may be as harmless as not doing anything or causing security issues (OSDP isn't super secure anyways; you wouldn't want to cut down any more limbs :D). So the general rule of thumb is: if it ain't on include/osdp.h don't use it.

Do you mean the flag PD_FLAG_SC_USE_SCBKD?

matt-treiber-compass-es commented 3 years ago

In the code example above:

uint8_t my_pd_scbk[16] = { /* Put you key as 16 bytes */ };
osdp_pd_info_t pd_info[] = {
  {
    .baud_rate = 115200,
    .address = 1,
    .flags = 0,
    .scbk = my_pd_scbk,
} 
cp.setup(1, pd_info, NULL);

If a SCBK is passed to the CP setup, is that key used to decrypt the first PD cryptogram? I am passing the SCBK_D to use, but the library is still unable to decrypt:

.[0mOSDP: CP->PD[0]: Sent [zu] =>
    0000  14 53 01 13 00 0e 03 11  01 76 03 4a 71 dd b9 af  |.S.......v.Jq...|
    000zu  44 ef b4 dd                                       |D...            |
OSDP: CP->PD[0]: Received [zu] =>
    0000  16 53 81 2b 00 0e 03 12  01 76 00 11 12 4d 42 31  |.S.+.....v...MB1|
    000zu  31 30 ec 1a 61 5c                                 |10..a\          |
OSDP: CP->PD[0]: Received [zu] =>
    0000  2c 53 81 2b 00 0e 03 12  01 76 00 11 12 4d 42 31  |.S.+.....v...MB1|
    000zu  31 30 ec 1a 61 5c 42 4d  4a 49 a8 df b0 6b 34 b1  |10..a\BMJI...k4.|
    000zu  2f 12 7a 00 47 a7 06 40  c6 8f 7b 50              |/.z.G..@..{P    |
.[31mOSDP: ERROR: PD[0]: CP : Failed to verify PD cryptogram
.[0m.[33mOSDP: WARN : PD[0]: CP : SC Failed. Retry with SCBK-D
.[0mOSDP: CP->PD[0]: Sent [zu] =>
    0000  14 53 01 13 00 0f 03 11  00 76 95 3d 41 2b 37 e1  |.S.......v.=A+7.|
    000zu  b1 41 c8 36                                       |.A.6            |
OSDP: CP->PD[0]: Received [zu] =>
    0000  0d 53 81 0c 00 0f 03 12  00 41 06 b9 15           |.S.......A...   |
.[33mOSDP: WARN : PD[0]: CP : PD replied with NAK(6) for CMD(76)
.[0m.[0mOSDP: DEBUG: PD[0]: CP : CMD(76) REPLY(41)
.[0m.[31mOSDP: ERROR: PD[0]: CP : CHLNG failed. Online without SC

Unfortunately, with this Veridt reader, it is NAKing all subsequent osdp_CHLNG commands.

sidcha commented 3 years ago

If a SCBK is passed to the CP setup, is that key used to decrypt the first PD cryptogram?

Yes.

I am passing the SCBK_D to use, but the library is still unable to decrypt

The library will do this for you. You do not need to pass the SCBK-D. It will first try to setup a connection with the provided SCBK; if that fails, it attempts to setup a secure channel session with SCBK-D (make sure that the reader is in install-mode) and if that succeeds, the SCBK that you passed initially will be set to the device.

If this is not what you are seeing, please post the full logs to https://pastebin.com/ and link it here.

Note: You can disable colours in the log lines at configure time if you are not reading them on a terminal with:

cmake -DCONFIG_DISABLE_PRETTY_LOGGING=off ..
# (OR)
./configure.sh --no-colours
bve-ape commented 3 years ago

So, I have the following piece of code as you suggested:

uint8_t masterkey[] = "my key here";
osdp_pd_info_t  pd_info[] = {
  {
    .baud_rate = 9600,
    .address = 0,
    .flags = 0,
    .id = {},
    ...
    .scbk = masterkey
  }
};
....
cp.setup(1, pd_info, nullptr);
...

while loop {
cp.refresh()
}

For which I get the following output from the library:

OSDP: INFO : CP : CP setup complete
OSDP: DEBUG: PD[0]: CP : CMD(61) REPLY(45)
CMD: osdp_ID(0x61) completed
OSDP: DEBUG: PD[0]: CP : CMD(62) REPLY(46)
CMD: osdp_CAP(0x62) completed capabilities read
OSDP: ERROR: PD[0]: CP : Failed to verify PD cryptogram
OSDP: WARN : PD[0]: CP : SC Failed. Retry with SCBK-D
OSDP: WARN : PD[0]: CP : PD replied with NAK(6) for CMD(76)
OSDP: DEBUG: PD[0]: CP : CMD(76) REPLY(41)
CMD: osdp_CHLNG(0x76) completed
OSDP: ERROR: PD[0]: CP : CHLNG failed. Online without SC

Can you give us a pseudo code sample on how you would initialize a secure communication from a cp.setup to a cp.refresh ?

Thanks a lot

sidcha commented 3 years ago

What you have there is a valid init sequence, with one minor comment: you shouldn't pass the master_key in place of scbk (see [1]). If you need more samples you can look at samples directory or have a look at the osdpctl directory (which implements LibOSDP fully). If these are not clear for you to setup the device, please open a separate ticket with what you feel is the gap, I'll address them.

Bases on the logs, it appears your PD is not in "install-mode". That is the only reason why it would respond with NACK(6) (which translates to "Communication security criteria not met") as it didn't accept our attempt to start the secure channel with SCBK-D.

[1]: The distinction between a Master Key and SCBK is that the Master Key is common for all PDs connected to this CP and the SCBK is derived from this key. The SCBK (when passed via the osdp_pd_info_t) has to be unique to each PD.

sidcha commented 3 years ago

Another point to note: OSDP_FLAG_INSTALL_MODE is an internal flag and using it may be as harmless as not doing anything or causing security issues (OSDP isn't super secure anyways; you wouldn't want to cut down any more limbs :D). So the general rule of thumb is: if it ain't on include/osdp.h don't use it.

Do you mean the flag PD_FLAG_SC_USE_SCBKD?

Yes. I've updated the original comment with this correction.

sidcha commented 3 years ago

@matt-treiber-compass-es, there was indeed an issue with the SC implementation. Update to the most recent master to get the fixes.

@bve-ape, thanks for your efforts in finding/fixing this bug.