Skip to content

Commit

Permalink
GD-73 encryption implementation (#452)
Browse files Browse the repository at this point in the history
* Implemented encoding/decoding of encryption keys for GD73.
* Added unit tests.
* Added limits to check encryption keys. Addresses #451.
* Fixed docs.
  • Loading branch information
hmatuschek authored Jul 16, 2024
1 parent ae2f025 commit f865560
Show file tree
Hide file tree
Showing 19 changed files with 689 additions and 101 deletions.
51 changes: 40 additions & 11 deletions doc/manual/codeplug/commercial/encryption.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<para>
This extension allows to configure the commercial encryption features of DMR. Almost all DMR
radios implement means for encrypting the traffic. This feature however, is usually illegal
when used within a amateur radio context.
when used within amateur radio.
</para>

<example>
Expand All @@ -24,20 +24,23 @@
id: key1
name: Example key 1
key: ABCD
- aes:
- rc4:
id: key2
name: Example key 2
key: 01234567890123456789012345678901
key: 0123456789
- aes:
id: key3
name: Example key 3
key: 0123456789ABCDEF0123456789ABCDEF
]]></programlisting>
</example>

<para>
This extension is a simple list of keys held in the global <token>commercial</token> extension.
Each key must either be a DMR (basic) or AES (enhanced) key.
The former defines a simple 16bit key while the latter defines a 126bit AES key. To
differentiate these key types, each list entry must be a mapping with a single entry. The name
specifies the type (i.e., either <token>dmr</token> or <token>aes</token>). The value then
specifies the properties of the key.
Each key must either be a DMR (basic), RC4 (enhanced) or AES (advanced) key.
To differentiate these key types, each list entry must be a mapping with a single entry. The name
specifies the type (i.e., either <token>dmr</token>, <token>rc4</token> or <token>aes</token>).
The value then specifies the properties of the key.
</para>


Expand Down Expand Up @@ -86,7 +89,9 @@
<term><token>key</token></term>
<listitem>
<para>
Specifies the 16bit key as a HEX string. To this end, a 4-char hex string is specified.
Specifies the key as a HEX string. It must be at least 8bit but can be of variable size.
The actual size depends on the device. Usually, a fixed size of 16 or 32bit is supported.
Some devices, however, support variable sized keys.
</para>
</listitem>
</varlistentry>
Expand All @@ -95,8 +100,31 @@


<section>
<info>
<title>RC4 (enhanced) key attributes</title>
</info>

<para>
</para>

<variablelist>
<title>RC4 key fields</title>
<varlistentry>
<term><token>key</token></term>
<listitem>
<para>
Specifies the key as a HEX string. This key is fixed to a size of 40bit. That is, the hex
string must be of length 10.
</para>
</listitem>
</varlistentry>
</variablelist>
</section>


<section>
<info>
<title>AES (enhanced) key attributes</title>
<title>AES (advanced) key attributes</title>
</info>

<para>
Expand All @@ -108,7 +136,8 @@
<term><token>key</token></term>
<listitem>
<para>
Specifies the 128bit key as a HEX string. To this end, a 32-char hex string is specified.
Specifies the key as a HEX string. Also this key can be of variable size. Usually, these
keys are 128 or 256bit. The actual size depends on the device.
</para>
</listitem>
</varlistentry>
Expand Down
19 changes: 19 additions & 0 deletions lib/dm1701_limits.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "dm1701_limits.hh"
#include "dm1701_codeplug.hh"
#include "channel.hh"
#include "radioid.hh"
#include "contact.hh"
Expand Down Expand Up @@ -154,6 +155,24 @@ DM1701Limits::DM1701Limits(QObject *parent)
{ "revert", new RadioLimitObjRef(DMRChannel::staticMetaObject, true) }
} ) );

/* Check encryption keys. */
add("commercial", new RadioLimitItem {
{"encryptionKeys", new RadioLimitList {
{BasicEncryptionKey::staticMetaObject,
0, TyTCodeplug::EncryptionElement::Limit::basicKeys(),
new RadioLimitObject {
{"name", new RadioLimitIgnored()},
{"key", new RadioLimitStringRegEx("[0-9a-fA-F]{4}")}
}},
{AESEncryptionKey::staticMetaObject,
0, TyTCodeplug::EncryptionElement::Limit::advancedKeys(),
new RadioLimitObject {
{"name", new RadioLimitIgnored()},
{"key", new RadioLimitStringRegEx("[0-9a-fA-F]{32}")}
}} }
}
});

/* Ignore roaming zones. */
add("roaming", new RadioLimitList(
ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint)
Expand Down
14 changes: 7 additions & 7 deletions lib/dr1801uv_codeplug.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2228,15 +2228,15 @@ DR1801UVCodeplug::EncryptionKeyBankElement::link(Context &ctx, const ErrorStack

bool
DR1801UVCodeplug::EncryptionKeyBankElement::encode(Context &ctx, const ErrorStack &err) {
unsigned int n = std::min(Limit::keyCount(), ctx.count<DMREncryptionKey>());
unsigned int n = std::min(Limit::keyCount(), ctx.count<BasicEncryptionKey>());
for (unsigned int i=0; i<Limit::keyCount(); i++) {
EncryptionKeyElement key = this->key(i);
if (i>=n) {
key.clear();
continue;
}
if (! key.encode(ctx.get<DMREncryptionKey>(i), ctx, err)) {
errMsg(err) << "Cannot encode DMR encryption key '" << ctx.get<DMREncryptionKey>(i)->name()
if (! key.encode(ctx.get<BasicEncryptionKey>(i), ctx, err)) {
errMsg(err) << "Cannot encode DMR encryption key '" << ctx.get<BasicEncryptionKey>(i)->name()
<< "' at index " << i << ".";
return false;
}
Expand Down Expand Up @@ -2304,7 +2304,7 @@ DR1801UVCodeplug::EncryptionKeyElement::toKeyObj(Context &ctx, const ErrorStack
return nullptr;
}

DMREncryptionKey *obj = new DMREncryptionKey();
BasicEncryptionKey *obj = new BasicEncryptionKey();
if (! obj->fromHex(key(), err)) {
errMsg(err) << "Cannot decode key '" << key() << "'.";
delete obj;
Expand All @@ -2325,11 +2325,11 @@ bool
DR1801UVCodeplug::EncryptionKeyElement::encode(EncryptionKey *obj, Context &ctx, const ErrorStack &err) {
Q_UNUSED(ctx);

if (!obj->is<DMREncryptionKey>()) {
if (!obj->is<BasicEncryptionKey>()) {
errMsg(err) << "Cannot encode AES encryption key. Not supported by the device.";
return false;
}
DMREncryptionKey *key = obj->as<DMREncryptionKey>();
BasicEncryptionKey *key = obj->as<BasicEncryptionKey>();
setKey(key->key().toHex());

return true;
Expand Down Expand Up @@ -3303,7 +3303,7 @@ DR1801UVCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &e
Q_UNUSED(flags);

Context ctx(config);
ctx.addTable(&DMREncryptionKey::staticMetaObject);
ctx.addTable(&BasicEncryptionKey::staticMetaObject);
if (! index(config, ctx, err)) {
errMsg(err) << "Cannot encode codeplug.";
return false;
Expand Down
123 changes: 99 additions & 24 deletions lib/encryptionextension.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ EncryptionKey::clear() {

bool
EncryptionKey::fromHex(const QString &hex, const ErrorStack &err) {
Q_UNUSED(err);
QByteArray newKey = QByteArray::fromHex(hex.toLocal8Bit());
if (_key == newKey)
return true;
_key = newKey;
emit modified(this);
return true;
return setKey(QByteArray::fromHex(hex.toLocal8Bit()), err);
}

QString
Expand All @@ -37,48 +31,120 @@ EncryptionKey::key() const {
return _key;
}

bool
EncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) {
if (key.isEmpty()) {
errMsg(err) << "Cannot set empty encryption key.";
return false;
}

if (_key == key)
return true;

_key = key;
emit modified(this);

return true;
}


/* ********************************************************************************************* *
* Implementation of DMREncryptionKey
* Implementation of BasicEncryptionKey
* ********************************************************************************************* */
DMREncryptionKey::DMREncryptionKey(QObject *parent)
BasicEncryptionKey::BasicEncryptionKey(QObject *parent)
: EncryptionKey(parent)
{
// pass...
}

ConfigItem *
DMREncryptionKey::clone() const {
DMREncryptionKey *key = new DMREncryptionKey();
BasicEncryptionKey::clone() const {
BasicEncryptionKey *key = new BasicEncryptionKey();
if (! key->copy(*this)) {
key->deleteLater();
return nullptr;
}
return key;
}

YAML::Node
BasicEncryptionKey::serialize(const Context &context, const ErrorStack &err) {
YAML::Node node = EncryptionKey::serialize(context, err);
if (node.IsNull())
return node;

YAML::Node type;
type["dmr"] = node;
return type;
}

bool
DMREncryptionKey::fromHex(const QString &hex, const ErrorStack &err) {
if (4 != hex.size()) {
errMsg(err) << "Cannot set DMR ecryption key to '" << hex << "': Not a 16bit key.";
BasicEncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) {
if (! node)
return false;

if ((! node.IsMap()) || (1 != node.size())) {
errMsg(err) << node.Mark().line << ":" << node.Mark().column
<< ": Cannot parse basic encryption key: Expected object with one child.";
return false;
}

YAML::Node key = node.begin()->second;
return EncryptionKey::parse(key, ctx, err);
}


/* ********************************************************************************************* *
* Implementation of EnhancedEncryptionKey
* ********************************************************************************************* */
EnhancedEncryptionKey::EnhancedEncryptionKey(QObject *parent)
: EncryptionKey(parent)
{
// pass...
}

ConfigItem *
EnhancedEncryptionKey::clone() const {
EnhancedEncryptionKey *key = new EnhancedEncryptionKey();
if (! key->copy(*this)) {
key->deleteLater();
return nullptr;
}
return key;
}

bool
EnhancedEncryptionKey::fromHex(const QString &hex, const ErrorStack &err) {
if (10 != hex.size()) {
errMsg(err) << "Cannot set RC4 (enhanced) ecryption key to '" << hex << "': Not a 40bit key.";
return false;
}
return EncryptionKey::fromHex(hex);
}

bool
EnhancedEncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) {
if (5 != key.size()) {
errMsg(err) << "Cannot set RC4 (enhanced) ecryption key: Not a 40bit key.";
return false;
}

return EncryptionKey::setKey(key, err);
}

YAML::Node
DMREncryptionKey::serialize(const Context &context, const ErrorStack &err) {
EnhancedEncryptionKey::serialize(const Context &context, const ErrorStack &err) {
YAML::Node node = EncryptionKey::serialize(context, err);
if (node.IsNull())
return node;

YAML::Node type;
type["basic"] = node;
type["rc4"] = node;
return type;
}

bool
DMREncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) {
EnhancedEncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) {
if (! node)
return false;

Expand Down Expand Up @@ -113,12 +179,12 @@ AESEncryptionKey::clone() const {
}

bool
AESEncryptionKey::fromHex(const QString &hex, const ErrorStack &err) {
if (32 != hex.size()) {
errMsg(err) << "Cannot set AES ecryption key to '" << hex << "': Not a 16bit key.";
AESEncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) {
if (16 > key.size()) {
errMsg(err) << "Cannot set AES ecryption key to '" << key.toHex() << "': Key smaller than 128bit.";
return false;
}
return EncryptionKey::fromHex(hex);
return EncryptionKey::setKey(key, err);
}

YAML::Node
Expand Down Expand Up @@ -152,7 +218,7 @@ AESEncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &
* Implementation of EncryptionKeys
* ********************************************************************************************* */
EncryptionKeys::EncryptionKeys(QObject *parent)
: ConfigObjectList({DMREncryptionKey::staticMetaObject, AESEncryptionKey::staticMetaObject}, parent)
: ConfigObjectList({BasicEncryptionKey::staticMetaObject, EnhancedEncryptionKey::staticMetaObject, AESEncryptionKey::staticMetaObject}, parent)
{
// pass...
}
Expand All @@ -166,6 +232,13 @@ EncryptionKeys::add(ConfigObject *obj, int row, bool unique) {
return ConfigObjectList::add(obj, row, unique);
}

EncryptionKey *
EncryptionKeys::key(int index) const {
if (index >= count())
return nullptr;
return get(index)->as<EncryptionKey>();
}

ConfigItem *
EncryptionKeys::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) {
Q_UNUSED(ctx)
Expand All @@ -179,8 +252,10 @@ EncryptionKeys::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx,
}

QString type = QString::fromStdString(node.begin()->first.as<std::string>());
if ("basic" == type) {
return new DMREncryptionKey();
if (("basic" == type) || ("dmr" == type)) {
return new BasicEncryptionKey();
} else if ("rc4" == type) {
return new EnhancedEncryptionKey();
} else if ("aes" == type) {
return new AESEncryptionKey();
}
Expand Down
Loading

0 comments on commit f865560

Please sign in to comment.