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
128 stars 69 forks source link

Sequence Repeat Packets #145

Closed vk2tds closed 7 months ago

vk2tds commented 7 months ago

I have finally come back to working on an Arduino implementation of libosdp for a PD, and getting it to work with the InnerRange Inception. My issues with it turned out to be that the Inception didn't actually support OSDP despite being advertised as such. They have now resolved, although there are certain unusual 'features' like ignoring the capabilities data structure and encrypting regardless.

I mention this as the Inception may not be implementing OSDP in full accordance with the published specification, or may be implementing some strange behaviour.

Occasionally, at least once a day, I am getting an error saying that the PD has received a sequence repeat packet, and immediately after a message noting an invalid MAC and discarding the SC. As you can see from the comms logs below, the CP sends a packet, which the PD replies to and then the CP sends the exact same packet again.

When the identical packet is identified, the library identifies this, but then discards by noting an invalid MAC.

I have downloaded some logs from the Inception and they appear right at the bottom. I don't have much information on what the Inception logs are saying because they are designed for manufacturer use and are not normally available.

My thinking is:

I am guessing that repeat packets doesn't get tested much. My questions would be:

Any thoughts on this would be appreciated. Thanks.

Comms Logs
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  0e 03 0e 00 0f 02 15 60  5c b3 c6 b3 7f 48        |S......`\....H  |
P_TRACE_SEND: PD[3]->CP: [zu] =>
    0000  0e 83 0e 00 0f 02 16 40  30 0e 1c d5 43 5b        |S......@0...C[  |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  0e 03 0e 00 0d 02 15 60  37 62 f6 4e 97 e9        |S......`7b.N..  |
P_TRACE_SEND: PD[3]->CP: [zu] =>
    0000  0e 83 0e 00 0d 02 16 40  73 94 51 9b c7 4c        |S......@s.Q..L  |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  0e 03 0e 00 0e 02 15 60  48 c2 4b a5 f1 2e        |S......`H.K...  |
P_TRACE_SEND: PD[3]->CP: [zu] =>
    0000  0e 83 0e 00 0e 02 16 40  96 26 98 65 9f 8e        |S......@.&.e..  |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  0e 03 0e 00 0e 02 15 60  48 c2 4b a5 f1 2e        |S......`H.K...  |
OSDP: PD-3: [] [INFO ] yl/Documents/Arduino/libraries/libosdp_arduino/osdp_phy.c:449: received a sequence repeat packet!
OSDP: PD-3: [] [ERROR] yl/Documents/Arduino/libraries/libosdp_arduino/osdp_phy.c:625: Invalid MAC; discarding SC
P_TRACE_SEND: PD[3]->CP: [zu] =>
    0000  09 83 09 00 06 41 06 40  74                       |S....A.@t       |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 0d 08 00 00 62 00 36                           |S....b.6        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  0d 7f 0d 00 04 80 00 11  b9 00 00 86 8e           |S............   |
OSDP: CMD: MFG(80) [zu] =>
    0000  05 00 11 b9 00                                    |.....           |
OSDP: REPLY: NAK(41) [zu] =>
    0000  01                                                |.               |
P_TRACE_SEND: PD[3]->CP: [zu] =>
    0000  09 83 09 00 04 41 09 cf  eb                       |S....A...       |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 0e 08 00 00 62 00 35                           |S....b.5        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 0f 08 00 00 62 00 34                           |S....b.4        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 00 08 00 00 62 00 43                           |S....b.C        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 04 08 00 00 62 00 3f                           |S....b.?        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 01 08 00 00 62 00 42                           |S....b.B        |
P_TRACE_RECV: CP->PD[3]: [zu] =>
    0000  08 02 08 00 00 62 00 41                           |S....b.A        |
Inception CP Logs
03 2023-12-02 18:18:30.766 Webtegriti 0 OsdpManager: Retrying message. Packet type OSDP_POLL, sequence 2
04 2023-12-02 18:18:30.818 Webtegriti 0 OSDP: Reader Offline (SIFER Type None, Address 3, Serial 8188020)
02 2023-12-02 18:18:30.818 Webtegriti 0 OsdpManager: ProcessMessage - Reader 3 sent a NAK - Crypt Required message
02 2023-12-02 18:18:30.913 Webtegriti 0 OsdpManager: Error processing received message
02 2023-12-02 18:18:38.997 Webtegriti 0 OsdpManager: ProcessMessage - Reader 3 sent incorrect sequence num (0 instead of 1)
04 2023-12-02 18:18:41.800 Webtegriti 0 OSDP: Reader Online (SIFER Type None, Address 3, Serial 8188020)
03 2023-12-02 18:18:41.804 Webtegriti 0 Database - ReadOnly object was saved. Item was IR.Webtegriti.DBObjects.Peripherals.OSDPReader_T, saved by System
sidcha commented 7 months ago

Is the Inception behaviour of re-sending packets with the same encryption normal?

Regardless of whether there is a secure channel active, we definitely don't want the same messages that are being resent from either side to be registered as a valid packet. This becomes a security issue (reply-attack) when we have secure channel active (think: last card-read event resent by an attacker).

That's why these kind of protocols have sequence numbers in the first place; but SIA seems to have made some questionable decision regarding sequence numbers and how they can be repeated. Even so repeated sequence numbers have to be sent with the proper MAC chaining otherwise they would be rejected.

If the a message was totally lost (hence the MAC needed keep the SC going), the right thing for the CP/PD to do at this point is to discard the current SC session and start another one. Based on both the logs, this is what happens due to the NAK response from libosdp PD. Please reach out to the CP manufacturer to fix this issue: they cannot just resend a secure channel message when they think the PD did not respond to it.

Is the library when acting as a PD assuming that packet retries have new encryption because it is a new incoming packet?

Yes.

vk2tds commented 6 months ago

Thanks for this. I did reach out to the manufacturer and essentially their response was that they were implementing the specification.

I have looked into this more, and I think I have found a solution that I don't think is too much of a target for a reply-attack. It is not perfect, but works with what I have found in the field. It appears that at least one manufacturer has decided that a packet retry should use the same MAC. By my understanding, the MAC for a received packet is based on the last transmitted packet. A retry cannot be certain which transmission was lost so should use the last known good MAC. Well, that is my understanding. The old MAC is invalidated as soon as a new valid packet comes through, meaning that in normal circumstances, the MAC is invalidated in under a second.

So, what I have done to get this working firstly is to expand the osdp_secure_chanel structure with storage space for r_mac_backup[16].

struct osdp_secure_channel {
    uint8_t scbk[16];
    uint8_t s_enc[16];
    uint8_t s_mac1[16];
    uint8_t s_mac2[16];
    uint8_t r_mac[16];
    uint8_t r_mac_backup[16]; //vk2tds
    uint8_t c_mac[16];
    uint8_t cp_random[8];
    uint8_t pd_random[8];
    uint8_t pd_client_uid[8];
    uint8_t cp_cryptogram[16];
    uint8_t pd_cryptogram[16];
};

Next was to populate this. The place to do that was the aforementioned osdp_computer_mac() function. Just before pd->sc.r_mac is overwritten with a new value, we save a copy in case we need it. This might not be the most elegant, but it works.

        /* N-1 blocks -- encrypted with SMAC-1 */
        osdp_encrypt(pd->sc.s_mac1, iv, buf, pad_len - 16);
        /* N-1 th block is the IV for N th block */
        memcpy(iv, buf + pad_len - 32, 16);
    }

    /* N-th Block encrypted with SMAC-2 == MAC */
    osdp_encrypt(pd->sc.s_mac2, iv, buf + pad_len - 16, 16);
    if (!is_cmd){ //vk2tds
        // r_mac is about to be updated. Save a copy
        memcpy (pd->sc.r_mac_backup, pd->sc.r_mac, 16);
    }
    memcpy(is_cmd ? pd->sc.c_mac : pd->sc.r_mac, buf + pad_len - 16, 16);

Finally, we need to make a change to phy_check_packet() in the file osdp_phy.c. The change here is really simple. Whenever we have a sequence repeat packet, we overwrite the pd->sc.r_mac with pd->sc.r_mac_backup. We should probably be comparing the entire packet and making sure that it was identical to the previously sent one, but this works, at least for proof of concept.

            LOG_INF("received a sequence repeat packet!");
            memcpy (pd->sc.r_mac, pd->sc.r_mac_backup, 16); //vk2tds

And it works! Now, before overwriting the R_MAC, I should probably compare the total packet and not just the sequence number. That can be done later.

sidcha commented 6 months ago

Thank you for the writeup. OSDP specification talks about sequence repeat and secure channel in two different places without any relation to one and other. Also, do you know why you are hitting this issue of CP/PD not receiving your messages? Is the channel so unstable?

vk2tds commented 6 months ago

The path between the Off The Shelf CP and the STM32 running LibOSDP as a PD is only about 2M. I am normally only seeing a lost packet about once or twice a day with two PD's on the RS-485 bus. Not exactly unstable, but it is annoying when the CP sends a Beep when the SC channel comes back online.

The second OSPD device is a commercial Wiegand to OSDP device, although running 'custom' protocol modifications...

Given that I am definitely sending the response to the first message, and this is getting lost, the options are:

The frequency of retransmissions does not seem to change if I add key-up time before and after the serial transmission to make sure that the RS485 transceiver switches from receive to transmit properly and vice versa. Sending is one of the easiest things to do. So I suspect that is not it. I have just unplugged the other OSDP device and will see if that affects things. This test will likely take days. Even so, I don't expect packet collisions to be the issue. That only leaves the firmware on the CP. I have my suspicions.

However, I suspect the CP