Add aescmac for more security modes.

pull/70/head
weetmuts 2020-01-28 16:15:11 +01:00
rodzic 35ab71bf0d
commit a6c73e10db
10 zmienionych plików z 304 dodań i 62 usunięć

Wyświetl plik

@ -89,6 +89,7 @@ $(BUILD)/%.o: src/%.cc $(wildcard src/%.h)
METER_OBJS:=\
$(BUILD)/aes.o \
$(BUILD)/aescmac.o \
$(BUILD)/cmdline.o \
$(BUILD)/config.o \
$(BUILD)/dvparser.o \

Wyświetl plik

@ -21,10 +21,10 @@ ECB-AES128
2b7e151628aed2a6abf7158809cf4f3c
resulting cipher
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
@ -65,7 +65,7 @@ NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
#define keyExpSize 176
#endif
// jcallan@github points out that declaring Multiply as a function
// jcallan@github points out that declaring Multiply as a function
// reduces code size considerably with the Keil ARM compiler.
// See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3
#ifndef MULTIPLY_AS_A_FUNCTION
@ -92,7 +92,7 @@ static const uint8_t* Key;
#endif
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
// The numbers below can be computed dynamically trading ROM for RAM -
// The numbers below can be computed dynamically trading ROM for RAM -
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
static const uint8_t sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
@ -131,7 +131,7 @@ static const uint8_t rsbox[256] = {
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
// The round constant word array, Rcon[i], contains the values given by
// The round constant word array, Rcon[i], contains the values given by
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
@ -141,8 +141,8 @@ static const uint8_t Rcon[11] = {
* that you can remove most of the elements in the Rcon array, because they are unused.
*
* From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon
*
* "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed),
*
* "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed),
* up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm."
*
* ... which is why the full array below has been 'disabled' below.
@ -180,12 +180,12 @@ static uint8_t getSBoxInvert(uint8_t num)
return rsbox[num];
}
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
static void KeyExpansion(void)
{
uint32_t i, k;
uint8_t tempa[4]; // Used for the column/row operations
// The first round key is the key itself.
for (i = 0; i < Nk; ++i)
{
@ -220,7 +220,7 @@ static void KeyExpansion(void)
tempa[3] = k;
}
// SubWord() is a function that takes a four-byte input word and
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword()
@ -287,14 +287,14 @@ static void ShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to left
// Rotate first row 1 columns to left
temp = (*state)[0][1];
(*state)[0][1] = (*state)[1][1];
(*state)[1][1] = (*state)[2][1];
(*state)[2][1] = (*state)[3][1];
(*state)[3][1] = temp;
// Rotate second row 2 columns to left
// Rotate second row 2 columns to left
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
@ -322,7 +322,7 @@ static void MixColumns(void)
uint8_t i;
uint8_t Tmp,Tm,t;
for (i = 0; i < 4; ++i)
{
{
t = (*state)[i][0];
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
@ -360,7 +360,7 @@ static void InvMixColumns(void)
int i;
uint8_t a, b, c, d;
for (i = 0; i < 4; ++i)
{
{
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
@ -392,14 +392,14 @@ static void InvShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to right
// Rotate first row 1 columns to right
temp = (*state)[3][1];
(*state)[3][1] = (*state)[2][1];
(*state)[2][1] = (*state)[1][1];
(*state)[1][1] = (*state)[0][1];
(*state)[0][1] = temp;
// Rotate second row 2 columns to right
// Rotate second row 2 columns to right
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
@ -423,8 +423,8 @@ static void Cipher(void)
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(0);
AddRoundKey(0);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below.
@ -435,7 +435,7 @@ static void Cipher(void)
MixColumns();
AddRoundKey(round);
}
// The last round is given below.
// The MixColumns function is not here in the last round.
SubBytes();
@ -448,7 +448,7 @@ static void InvCipher(void)
uint8_t round=0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr);
AddRoundKey(Nr);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
@ -460,7 +460,7 @@ static void InvCipher(void)
AddRoundKey(round);
InvMixColumns();
}
// The last round is given below.
// The MixColumns function is not here in the last round.
InvShiftRows();

122
src/aescmac.cc 100644
Wyświetl plik

@ -0,0 +1,122 @@
/*
Copyright (C) 2020 Fredrik Öhrström
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include<stdio.h>
#include<memory.h>
#include"aes.h"
#include"aescmac.h"
#include"util.h"
uchar vec87[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87
};
void generateSubkeys(uchar *key, uchar *K1, uchar *K2)
{
uchar L[16];
uchar Z[16];
uchar tmp[16];
memset(Z, 0, 16);
AES_ECB_encrypt(Z, key, L, 16);
if (!(L[0] & 0x80))
{
shiftLeft(L, K1, 16);
}
else
{
shiftLeft(L, tmp, 16);
xorit(tmp, vec87, K1, 16);
}
if (!(K1[0] & 0x80))
{
shiftLeft(K1, K2, 16);
}
else
{
shiftLeft(K1, tmp, 16);
xorit(tmp, vec87, K2, 16);
}
}
void pad(uchar *in, uchar *out, int len)
{
for (int i = 0; i < 16; i++)
{
if (i < len)
{
out[i] = in[i];
}
else if (i == len)
{
out[i] = 0x80;
}
else
{
out[i] = 0x00;
}
}
}
void AES_CMAC(uchar *key, uchar *input, int len, uchar *mac)
{
bool len_is_multiple_of_block;
uchar X[16], Y[16];
uchar K1[16], K2[16];
uchar M_last[16], padded[16];
generateSubkeys(key, K1, K2);
int num_blocks = (len+15)/16;
if (!num_blocks)
{
num_blocks = 1;
len_is_multiple_of_block = false;
}
else
{
len_is_multiple_of_block = !(len%16);
}
if (len_is_multiple_of_block)
{
xorit(input+(16*(num_blocks-1)), K1, M_last, 16);
}
else
{
pad(input+(16*(num_blocks-1)), padded, len%16);
xorit(padded, K2, M_last, 16);
}
memset(X, 0, 16);
for (int i=0; i<num_blocks-1; i++)
{
xorit(X, input+(16*i), Y, 16);
AES_ECB_encrypt(Y, key, X, 16);
}
xorit(X,M_last,Y, 16);
AES_ECB_encrypt(Y, key, X, 16);
memcpy(mac, X, 16);
}

25
src/aescmac.h 100644
Wyświetl plik

@ -0,0 +1,25 @@
/*
Copyright (C) 2020 Fredrik Öhrström
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AESCMAC_H_
#define _AESCMAC_H_
typedef unsigned char uchar;
void AES_CMAC (uchar *key, uchar *input, int length, uchar *mac);
#endif //_AESCMAC_H_

Wyświetl plik

@ -15,6 +15,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include"aescmac.h"
#include"cmdline.h"
#include"config.h"
#include"meters.h"
@ -32,6 +33,7 @@ int test_crc();
int test_dvparser();
int test_linkmodes();
void test_ids();
void test_kdf();
int main(int argc, char **argv)
{
@ -45,6 +47,7 @@ int main(int argc, char **argv)
test_dvparser();
test_linkmodes();
test_ids();
test_kdf();
return 0;
}
@ -411,3 +414,33 @@ void eqn(int a, int b, const char *tn)
printf("ERROR in test %s expected %d to be equal to %d\n", tn, a, b);
}
}
void test_kdf()
{
vector<uchar> key;
vector<uchar> input;
vector<uchar> mac;
hex2bin("2b7e151628aed2a6abf7158809cf4f3c", &key);
mac.resize(16);
AES_CMAC(&key[0], &input[0], 0, &mac[0]);
string s = bin2hex(mac);
string ex = "BB1D6929E95937287FA37D129B756746";
if (s != ex)
{
printf("ERROR in aes-cmac expected \"%s\" but got \"%s\"\n", ex.c_str(), s.c_str());
}
input.clear();
hex2bin("6bc1bee22e409f96e93d7e117393172a", &input);
AES_CMAC(&key[0], &input[0], 16, &mac[0]);
s = bin2hex(mac);
ex = "070A16B46B4D4144F79BDD9DD04A287C";
if (s != ex)
{
printf("ERROR in aes-cmac expected \"%s\" but got \"%s\"\n", ex.c_str(), s.c_str());
}
}

Wyświetl plik

@ -30,7 +30,7 @@
X(RelativeHumidity,RH) \
X(HCA,HCA) \
X(Text,TXT) \
X(Time,Seconds) \
#define LIST_OF_UNITS \
X(KWH,kwh,kWh,Energy,"kilo Watt hour") \
@ -44,6 +44,7 @@
X(RH,rh,RH,RelativeHumidity,"relative humidity") \
X(HCA,hca,hca,HCA,"heat cost allocation") \
X(TXT,txt,txt,Text,"text") \
X(Seconds,s,s,Time,"seconds") \
enum class Unit
{

Wyświetl plik

@ -224,6 +224,19 @@ void xorit(uchar *srca, uchar *srcb, uchar *dest, int len)
for (int i=0; i<len; ++i) { dest[i] = srca[i]^srcb[i]; }
}
void shiftLeft(uchar *srca, uchar *srcb, int len)
{
uchar overflow = 0;
for (int i = len-1; i >= 0; i--)
{
srcb[i] = srca[i] << 1;
srcb[i] |= overflow;
overflow = (srca[i] & 0x80) >> 7;
}
return;
}
string format3fdot3f(double v)
{
string r;

Wyświetl plik

@ -50,6 +50,7 @@ std::string strdate(struct tm *date);
std::string strdatetime(struct tm *date);
void xorit(uchar *srca, uchar *srcb, uchar *dest, int len);
void shiftLeft(uchar *srca, uchar *srcb, int len);
std::string format3fdot3f(double v);
bool enableLogfile(std::string logfile, bool daemon);
void disableLogfile();

Wyświetl plik

@ -15,6 +15,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include"aescmac.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include"dvparser.h"
@ -958,10 +959,10 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
// All ELL:s (including ELL I) start with cc,acc.
ell_cc = *pos;
addExplanationAndIncrementPos(pos, 1, "%02x cc-field (%s)", ell_cc, ccType(ell_cc).c_str());
addExplanationAndIncrementPos(pos, 1, "%02x ell-cc (%s)", ell_cc, ccType(ell_cc).c_str());
ell_acc = *pos;
addExplanationAndIncrementPos(pos, 1, "%02x ell-acc-field", ell_acc);
addExplanationAndIncrementPos(pos, 1, "%02x ell-acc", ell_acc);
bool has_target_mft_address = false;
bool has_session_number_pl_crc = false;
@ -991,7 +992,7 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
ell_mfct_b[0] = *(pos+0);
ell_mfct_b[1] = *(pos+1);
addExplanationAndIncrementPos(pos, 2, "%02x%02x mfct2", ell_mfct_b[0], ell_mfct_b[1]);
addExplanationAndIncrementPos(pos, 2, "%02x%02x ell-mfct2", ell_mfct_b[0], ell_mfct_b[1]);
ell_addr_b[0] = *(pos+0);
ell_addr_b[1] = *(pos+1);
@ -1000,7 +1001,7 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
ell_addr_b[4] = *(pos+4);
ell_addr_b[5] = *(pos+5);
addExplanationAndIncrementPos(pos, 6, "%02x%02x%02x%02x%02x%02x adr2",
addExplanationAndIncrementPos(pos, 6, "%02x%02x%02x%02x%02x%02x ell-adr2",
ell_addr_b[0], ell_addr_b[1], ell_addr_b[2],
ell_addr_b[3], ell_addr_b[4], ell_addr_b[5]);
}
@ -1115,12 +1116,12 @@ bool Telegram::parseAFL(vector<uchar>::iterator &pos)
afl_counter_b[1] = *(pos+1);
afl_counter_b[2] = *(pos+2);
afl_counter_b[3] = *(pos+3);
afl_counter = afl_counter_b[0] << 24 |
afl_counter_b[1] << 16 |
afl_counter_b[2] << 8 |
afl_counter_b[3];
afl_counter = afl_counter_b[3] << 24 |
afl_counter_b[2] << 16 |
afl_counter_b[1] << 8 |
afl_counter_b[0];
addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x afl-counter (%d)",
addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x afl-counter (%u)",
afl_counter_b[0],afl_counter_b[1],
afl_counter_b[2],afl_counter_b[3],
afl_counter);
@ -1265,9 +1266,42 @@ bool Telegram::parseTPLConfig(std::vector<uchar>::iterator &pos)
int m = (tpl_cfg >> 8) & 0x1f;
tpl_sec_mode = fromIntToTPLSecurityMode(m);
}
bool has_cfg_ext = false;
string info = toStringFromTPLConfig(tpl_cfg);
info += " ";
if (tpl_sec_mode == TPLSecurityMode::AES_CBC_NO_IV) // Security mode 7
{
tpl_num_encr_blocks = (tpl_cfg >> 4) & 0x0f;
info += "NEB=";
info += to_string(tpl_num_encr_blocks);
info += " ";
has_cfg_ext = true;
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x tpl-cfg (%s)", cfg1, cfg2, info.c_str());
if (has_cfg_ext)
{
tpl_cfg_ext = *(pos+0);
tpl_kdf_selection = (tpl_cfg_ext >> 4) & 3;
addExplanationAndIncrementPos(pos, 1, "%02x tpl-cfg-ext (KDFS=%d)", tpl_cfg_ext, tpl_kdf_selection);
if (tpl_kdf_selection == 1)
{
vector<uchar> key;
vector<uchar> input;
vector<uchar> mac;
mac.resize(16);
AES_CMAC(&key[0], &input[0], 16, &mac[0]);
string s = bin2hex(mac);
tpl_generated_key.clear();
tpl_generated_key.insert(tpl_generated_key.end(), mac.begin(), mac.end());
}
}
return true;
}
@ -1305,7 +1339,7 @@ bool Telegram::parseLongTPL(std::vector<uchar>::iterator &pos)
tpl_version = *(pos+0);
addExplanationAndIncrementPos(pos, 1, "%02x tpl-version", tpl_version);
tpl_type = *(pos+1);
tpl_type = *(pos+0);
string info = mediaType(tpl_type);
addExplanationAndIncrementPos(pos, 1, "%02x tpl-type (%s)", tpl_type, info.c_str());
@ -1316,11 +1350,42 @@ bool Telegram::parseLongTPL(std::vector<uchar>::iterator &pos)
bool loadFormatBytesFromSignature(uint16_t format_signature, vector<uchar> *format_bytes);
bool Telegram::parse_TPL_72(vector<uchar>::iterator &pos)
bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
{
if (tpl_sec_mode == TPLSecurityMode::AES_CBC_IV)
{
bool ok = decrypt_TPL_AES_CBC_IV(this, frame, pos, meter_keys->confidentiality_key);
if (!ok) return false;
// Now the frame from pos and onwards has been decrypted.
if (*(pos+0) != 0x2f || *(pos+1) != 0x2f)
{
if (parser_warns_) warning("(wmbus) decrypted content failed check, did you use the correct decryption key?\n");
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1));
}
else if (tpl_sec_mode == TPLSecurityMode::AES_CBC_NO_IV)
{
bool ok = decrypt_TPL_AES_CBC_NO_IV(this, frame, pos, tpl_generated_key);
if (!ok) return false;
// Now the frame from pos and onwards has been decrypted.
if (*(pos+0) != 0x2f || *(pos+1) != 0x2f)
{
if (parser_warns_) warning("(wmbus) decrypted content failed check, did you use the correct decryption key?\n");
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1));
}
return true;
}
bool Telegram::parse_TPL_72(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
{
bool ok = parseLongTPL(pos);
if (!ok) return false;
potentiallyDecrypt(pos, meter_keys);
header_size = distance(frame.begin(), pos);
int remaining = distance(pos, frame.end());
suffix_size = 0;
@ -1384,30 +1449,7 @@ bool Telegram::parse_TPL_7A(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
bool ok = parseShortTPL(pos);
if (!ok) return false;
if (tpl_sec_mode == TPLSecurityMode::AES_CBC_IV)
{
bool ok = decrypt_TPL_AES_CBC_IV(this, frame, pos, meter_keys->confidentiality_key);
if (!ok) return false;
// Now the frame from pos and onwards has been decrypted.
if (*(pos+0) != 0x2f || *(pos+1) != 0x2f)
{
if (parser_warns_) warning("(wmbus) decrypted content failed check, did you use the correct decryption key?\n");
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1));
}
else if (tpl_sec_mode == TPLSecurityMode::AES_CBC_NO_IV)
{
bool ok = decrypt_TPL_AES_CBC_NO_IV(this, frame, pos, meter_keys->confidentiality_key);
if (!ok) return false;
// Now the frame from pos and onwards has been decrypted.
if (*(pos+0) != 0x2f || *(pos+1) != 0x2f)
{
if (parser_warns_) warning("(wmbus) decrypted content failed check, did you use the correct decryption key?\n");
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1));
}
potentiallyDecrypt(pos, meter_keys);
header_size = distance(frame.begin(), pos);
int remaining = distance(pos, frame.end());
@ -1438,7 +1480,7 @@ bool Telegram::parseTPL(vector<uchar>::iterator &pos, MeterKeys *meter_keys)
switch (tpl_ci)
{
case CI_Field_Values::TPL_72: return parse_TPL_72(pos);
case CI_Field_Values::TPL_72: return parse_TPL_72(pos, meter_keys);
case CI_Field_Values::TPL_78: return parse_TPL_78(pos);
case CI_Field_Values::TPL_79: return parse_TPL_79(pos);
case CI_Field_Values::TPL_7A: return parse_TPL_7A(pos, meter_keys);

Wyświetl plik

@ -277,6 +277,10 @@ struct Telegram
int tpl_sts {}; // 1 byte
int tpl_cfg {}; // 2 bytes
TPLSecurityMode tpl_sec_mode {}; // Based on 5 bits extracted from cfg.
int tpl_num_encr_blocks {};
int tpl_cfg_ext {}; // 1 byte
int tpl_kdf_selection {}; // 1 byte
vector<uchar> tpl_generated_key; // 16 bytes
uchar tpl_id_b[4]; // 4 bytes
uchar tpl_mft_b[2]; // 2 bytes
@ -332,11 +336,11 @@ private:
void printAFL();
void printTPL();
bool parse_TPL_72(vector<uchar>::iterator &pos);
bool parse_TPL_72(vector<uchar>::iterator &pos, MeterKeys *meter_keys);
bool parse_TPL_78(vector<uchar>::iterator &pos);
bool parse_TPL_79(vector<uchar>::iterator &pos);
bool parse_TPL_7A(vector<uchar>::iterator &pos, MeterKeys *meter_keys);
bool potentiallyDecrypt(vector<uchar>::iterator &pos, MeterKeys *meter_keys);
bool parseTPLConfig(std::vector<uchar>::iterator &pos);
static string toStringFromELLSN(int sn);
static string toStringFromTPLConfig(int cfg);