I'm trying to implement my own LoraWAN server for personal research. I'm utilizing Semtech's LNS protocol and Basic Station as Gateway server. Now when a device tries to perform an OTAA join I receive the message and try to send back a Join-Accept response. But I'm having trouble with the MIC calculation and key derivation for the AppSKey and NwkSKey.
From the docs I must calculate the MIC of a Join-Accept message like
cmac = aes128_cmac(NwkKey, MHDR | JoinNonce | NetID | DevAddr | DLSettings | RxDelay | CFList)
MIC = cmac[0..3]
The code below should exactly do what the documentation says, join the message parts and calculate the cmac hash over it. But when I try to calculate the MIC using some example data taken from this website
I'm using a combination of Java and Clojure (mostly Clojure but I'm using the bouncycastle library for the cryptography part).
This is the example code:
(let [;; Example data
NwkKey [0xB6 0xB5 0x3F 0x4A 0x16 0x8A 0x7A 0x88 0xBD 0xF7 0xEA 0x13 0x5C 0xE9 0xCF 0xCA]
MHDR (unchecked-byte 0x20)
JoinNonce [0xe5 0x06 0x3a]
NetID [0x00 0x00 0x13]
DevAddr [0x26 0x01 0x2e 0x43]
DLSettings (unchecked-byte 0x03)
RxDelay (unchecked-byte 0x01)
CFList [0x18 0x4f 0x84 0xe8 0x56 0x84 0xb8 0x5e 0x84 0x88 0x66 0x84 0x58 0x6e 0x84 0x00]]
;; prepare data for hashing
NwkKey' (to-byte-array NwkKey) ; 16 bytes
buffer (ByteBuffer/allocate 32)
CFList' (concat (reverse (subvec CFList 0 3)) ; 16 bytes, correct endianess to LE
(reverse (subvec CFList 3 6))
(reverse (subvec CFList 6 9))
(reverse (subvec CFList 9 12))
(reverse (subvec CFList 12 15))
[(get CFList 15)])]
;; fill buffer with data
(.put buffer MHDR) ; 1 bytes
(.put buffer (to-byte-array (reverse JoinNonce))) ; 3 bytes, little endian
(.put buffer (to-byte-array (reverse NetID))) ; 3 bytes, little endian
(.put buffer (to-byte-array (reverse DevAddr))) ; 4 bytes, little endian
(.put buffer DLSettings) ; 1 bytes
(.put buffer RxDelay) ; 1 bytes
(.put buffer (to-byte-array CFList')) ; 16 bytes, already LE (see abvove)
;; padding: 1 byte with value 1 then n bytes with valu 0 until a 128 bit block is full (RFC 4493)
(.put buffer (unchecked-byte 2r1000000))
(.put buffer (to-byte-array (map (constantly 0x00) (range 2))))
;; content of the byte buffer before calculating the cmac hash
(println (string/join " " (map byte->xstr (.array buffer))))
;; get the first four bytes of the hash, this should be the MIC
(println (string/join " " (map byte->xstr (take 4 (cmac NwkKey' (.array buffer))))))))
The example should yield a MIC with the value 55 12 1d e0
but instead it gives me 7d fd eb 7b
. Looking into the buffer it has the value 20 3a 06 e5 13 00 00 43 2e 01 26 03 01 84 4f 18 84 56 e8 84 5e b8 84 66 88 84 6e 58 00 40 00 00
which seems correct.
The byte->xstr
function is a helper that takes a single byte and converts it to a string with its value in hexadecimal.
I already tried to fiddle with the padding in case bouncycastle does some padding in the background which I'm unaware off but still I don't get the correct result.
Now I'm kinda lost here because I can't find my error. I hope you can spot my mistake and point me in the correct direction to fix it.
Edit:
For completeness here the function that prints the value of a byte as hexadecimal:
(defn byte->xstr
"Convert a byte to a hexadecimal string representation (2 digits fixed)."
[b]
(format "%02x" b))