mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-05 10:40:42 +00:00
Refactor library-dependent crypto calls into separate file and add unit tests.
This commit is contained in:
parent
1cf4b2b6eb
commit
4b4a0edb0e
91
oscar/SleepLib/crypto.cpp
Normal file
91
oscar/SleepLib/crypto.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/* SleepLib cryptography abstraction
|
||||
*
|
||||
* Copyright (c) 2021-2022 The OSCAR Team
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file COPYING in the main directory of the source code
|
||||
* for more details. */
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "SleepLib/crypto.h"
|
||||
#include "SleepLib/thirdparty/botan_all.h"
|
||||
|
||||
CryptoResult decrypt_aes256(const QByteArray & key, const QByteArray & ciphertext, QByteArray & plaintext)
|
||||
{
|
||||
CryptoResult result = OK;
|
||||
plaintext.clear();
|
||||
try {
|
||||
const std::vector<uint8_t> botan_key(key.begin(), key.end());
|
||||
Botan::secure_vector<uint8_t> botan_message(ciphertext.begin(), ciphertext.end());
|
||||
|
||||
std::unique_ptr<Botan::BlockCipher> dec = Botan::BlockCipher::create("AES-256");
|
||||
dec->set_key(botan_key);
|
||||
dec->decrypt(botan_message);
|
||||
QByteArray message((char*) botan_message.data(), botan_message.size());
|
||||
plaintext = message;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
// Make sure no Botan exceptions leak out and terminate the application.
|
||||
qWarning() << "Unexpected exception in decrypt_aes256:" << e.what();
|
||||
result = UnknownError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CryptoResult decrypt_aes256_gcm(const QByteArray & key,
|
||||
const QByteArray & iv, const QByteArray & ciphertext, const QByteArray & tag,
|
||||
QByteArray & plaintext)
|
||||
{
|
||||
CryptoResult result = OK;
|
||||
plaintext.clear();
|
||||
try {
|
||||
const std::vector<uint8_t> botan_key(key.begin(), key.end());
|
||||
const std::vector<uint8_t> botan_iv(iv.begin(), iv.end());
|
||||
const std::vector<uint8_t> botan_tag(tag.begin(), tag.end());
|
||||
|
||||
Botan::secure_vector<uint8_t> botan_message(ciphertext.begin(), ciphertext.end());
|
||||
botan_message += botan_tag;
|
||||
|
||||
std::unique_ptr<Botan::Cipher_Mode> dec = Botan::Cipher_Mode::create("AES-256/GCM", Botan::DECRYPTION);
|
||||
dec->set_key(botan_key);
|
||||
dec->start(botan_iv);
|
||||
try {
|
||||
dec->finish(botan_message);
|
||||
//qDebug() << QString::fromStdString(Botan::hex_encode(message.data(), message.size()));
|
||||
QByteArray message((char*) botan_message.data(), botan_message.size());
|
||||
plaintext = message;
|
||||
}
|
||||
catch (const Botan::Invalid_Authentication_Tag& e) {
|
||||
result = InvalidTag;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
// Make sure no Botan exceptions leak out and terminate the application.
|
||||
qWarning() << "Unexpected exception in decrypt_aes256_gcm:" << e.what();
|
||||
result = UnknownError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CryptoResult pbkdf2_sha256(const QByteArray & passphrase, const QByteArray & salt, int iterations, QByteArray & key)
|
||||
{
|
||||
CryptoResult result = OK;
|
||||
try {
|
||||
std::unique_ptr<Botan::PasswordHashFamily> family = Botan::PasswordHashFamily::create("PBKDF2(SHA-256)");
|
||||
std::unique_ptr<Botan::PasswordHash> kdf = family->from_params(iterations);
|
||||
Botan::secure_vector<uint8_t> botan_key(key.size());
|
||||
kdf->derive_key(botan_key.data(), botan_key.size(),
|
||||
(const char*) passphrase.data(), passphrase.size(),
|
||||
(const uint8_t*) salt.data(), salt.size());
|
||||
QByteArray output((char*) botan_key.data(), botan_key.size());
|
||||
key = output;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
// Make sure no Botan exceptions leak out and terminate the application.
|
||||
qWarning() << "Unexpected exception in pbkdf2_sha256:" << e.what();
|
||||
result = UnknownError;
|
||||
key.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
27
oscar/SleepLib/crypto.h
Normal file
27
oscar/SleepLib/crypto.h
Normal file
@ -0,0 +1,27 @@
|
||||
/* SleepLib cryptography abstraction
|
||||
*
|
||||
* Copyright (c) 2021-2022 The OSCAR Team
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file COPYING in the main directory of the source code
|
||||
* for more details. */
|
||||
|
||||
#ifndef CRYPTO_H
|
||||
#define CRYPTO_H
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
enum CryptoResult
|
||||
{
|
||||
OK = 0,
|
||||
UnknownError = -1,
|
||||
InvalidTag = 1,
|
||||
};
|
||||
|
||||
CryptoResult decrypt_aes256(const QByteArray & key, const QByteArray & ciphertext, QByteArray & plaintext);
|
||||
CryptoResult decrypt_aes256_gcm(const QByteArray & key,
|
||||
const QByteArray & iv, const QByteArray & ciphertext, const QByteArray & tag,
|
||||
QByteArray & plaintext);
|
||||
CryptoResult pbkdf2_sha256(const QByteArray & passphrase, const QByteArray & salt, int iterations, QByteArray & key);
|
||||
|
||||
#endif // CRYPTO_H
|
@ -24,6 +24,7 @@
|
||||
#include "prs1_parser.h"
|
||||
#include "SleepLib/session.h"
|
||||
#include "SleepLib/calcs.h"
|
||||
#include "SleepLib/crypto.h"
|
||||
#include "rawdata.h"
|
||||
|
||||
|
||||
@ -248,8 +249,6 @@ const char* PRS1ModelInfo::Name(const QString & model) const
|
||||
|
||||
//********************************************************************************************
|
||||
|
||||
#include "SleepLib/thirdparty/botan_all.h"
|
||||
|
||||
// Decoder for DreamStation 2 files, which encrypt the actual data after a header with the key.
|
||||
// The public read/seek/pos/etc. functions are all in terms of the decoded stream.
|
||||
class PRDS2File : public RawDataFile
|
||||
@ -344,34 +343,22 @@ qint64 PRDS2File::readData(char *data, qint64 maxSize)
|
||||
bool PRDS2File::decryptData()
|
||||
{
|
||||
bool valid = false;
|
||||
try {
|
||||
QByteArray ciphertext = m_device.read(m_device.size() - m_device.pos());
|
||||
QByteArray plaintext;
|
||||
|
||||
const std::vector<uint8_t> key(m_payload_key.begin(), m_payload_key.end());
|
||||
const std::vector<uint8_t> iv(m_iv.begin(), m_iv.end());
|
||||
const std::vector<uint8_t> tag(m_payload_tag.begin(), m_payload_tag.end());
|
||||
CryptoResult error = decrypt_aes256_gcm(m_payload_key, m_iv, ciphertext, m_payload_tag, plaintext);
|
||||
|
||||
Botan::secure_vector<uint8_t> message(ciphertext.begin(), ciphertext.end());
|
||||
message += tag;
|
||||
|
||||
std::unique_ptr<Botan::Cipher_Mode> dec = Botan::Cipher_Mode::create("AES-256/GCM", Botan::DECRYPTION);
|
||||
dec->set_key(key);
|
||||
dec->start(iv);
|
||||
try {
|
||||
dec->finish(message);
|
||||
//qDebug() << QString::fromStdString(Botan::hex_encode(message.data(), message.size()));
|
||||
m_payload.setData((char*) message.data(), message.size());
|
||||
m_payload.open(QIODevice::ReadOnly);
|
||||
valid = true;
|
||||
}
|
||||
catch (const Botan::Invalid_Authentication_Tag& e) {
|
||||
if (error) {
|
||||
if (error == InvalidTag) {
|
||||
// This has been observed where the tag is zero and the data appears truncated.
|
||||
qWarning() << name() << "DS2 payload doesn't match tag, skipping";
|
||||
} else {
|
||||
qWarning() << "*** DS2 unexpected exception decrypting" << name();
|
||||
}
|
||||
}
|
||||
catch (exception& e) {
|
||||
// Make sure no Botan exceptions leak out and terminate the application.
|
||||
qWarning() << "*** DS2 unexpected exception decrypting" << name() << ":" << e.what();
|
||||
} else {
|
||||
m_payload.setData(plaintext);
|
||||
m_payload.open(QIODevice::ReadOnly);
|
||||
valid = true;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
@ -381,45 +368,36 @@ static const int KEY_SIZE = 256 / 8; // AES-256
|
||||
static const uint8_t OSCAR_KEY[KEY_SIZE+1] = "Patient access to their own data";
|
||||
static const uint8_t COMMON_KEY[KEY_SIZE] = { 0x75, 0xB3, 0xA2, 0x12, 0x4A, 0x65, 0xAF, 0x97, 0x54, 0xD8, 0xC1, 0xF3, 0xE5, 0x2E, 0xB6, 0xF0, 0x23, 0x20, 0x57, 0x69, 0x7E, 0x38, 0x0E, 0xC9, 0x4A, 0xDC, 0x46, 0x45, 0xB6, 0x92, 0x5A, 0x98 };
|
||||
|
||||
static const QByteArray s_oscar_key((const char*) OSCAR_KEY, KEY_SIZE);
|
||||
static const QByteArray s_common_key((const char*) COMMON_KEY, KEY_SIZE);
|
||||
|
||||
bool PRDS2File::initializeKey()
|
||||
{
|
||||
bool valid = false;
|
||||
try {
|
||||
Botan::secure_vector<uint8_t> common_key(COMMON_KEY, COMMON_KEY + KEY_SIZE);
|
||||
Botan::secure_vector<uint8_t> oscar_key(OSCAR_KEY, OSCAR_KEY + KEY_SIZE);
|
||||
std::unique_ptr<Botan::BlockCipher> oscar = Botan::BlockCipher::create("AES-256");
|
||||
oscar->set_key(oscar_key);
|
||||
oscar->decrypt(common_key);
|
||||
QByteArray common_key;
|
||||
|
||||
std::unique_ptr<Botan::PasswordHashFamily> family = Botan::PasswordHashFamily::create("PBKDF2(SHA-256)");
|
||||
std::unique_ptr<Botan::PasswordHash> kdf = family->from_params(10000);
|
||||
Botan::secure_vector<uint8_t> salted_key(KEY_SIZE);
|
||||
kdf->derive_key(salted_key.data(), salted_key.size(),
|
||||
(const char*) common_key.data(), common_key.size(),
|
||||
(const uint8_t*) m_salt.data(), m_salt.size());
|
||||
|
||||
const std::vector<uint8_t> iv(m_iv.begin(), m_iv.end());
|
||||
const std::vector<uint8_t> tag(m_export_key_tag.begin(), m_export_key_tag.end());
|
||||
Botan::secure_vector<uint8_t> message(m_export_key.begin(), m_export_key.end());
|
||||
message += tag;
|
||||
|
||||
std::unique_ptr<Botan::Cipher_Mode> dec = Botan::Cipher_Mode::create("AES-256/GCM", Botan::DECRYPTION);
|
||||
dec->set_key(salted_key);
|
||||
dec->start(iv);
|
||||
try {
|
||||
dec->finish(message);
|
||||
//qDebug() << QString::fromStdString(Botan::hex_encode(message.data(), message.size()));
|
||||
QByteArray payload_key((char*) message.data(), message.size());
|
||||
m_payload_key = payload_key;
|
||||
valid = true;
|
||||
CryptoResult error = decrypt_aes256(s_oscar_key, s_common_key, common_key);
|
||||
if (error) {
|
||||
qWarning() << "*** DS2 unexpected exception deriving common key";
|
||||
return false;
|
||||
}
|
||||
catch (const Botan::Invalid_Authentication_Tag& e) {
|
||||
|
||||
QByteArray salted_key(KEY_SIZE, 0);
|
||||
error = pbkdf2_sha256(common_key, m_salt, 10000, salted_key);
|
||||
if (error) {
|
||||
qWarning() << "*** DS2 unexpected exception deriving salted key for" << name();
|
||||
return false;
|
||||
}
|
||||
|
||||
error = decrypt_aes256_gcm(salted_key, m_iv, m_export_key, m_export_key_tag, m_payload_key);
|
||||
if (error) {
|
||||
if (error == InvalidTag) {
|
||||
qWarning() << "DS2 validation of payload key failed for" << name();
|
||||
} else {
|
||||
qWarning() << "*** DS2 unexpected exception deriving key for" << name();
|
||||
}
|
||||
}
|
||||
catch (exception& e) {
|
||||
// Make sure no Botan exceptions leak out and terminate the application.
|
||||
qWarning() << "*** DS2 unexpected exception deriving key for" << name() << ":" << e.what();
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
@ -312,6 +312,7 @@ SOURCES += \
|
||||
SleepLib/loader_plugins/viatom_loader.cpp \
|
||||
SleepLib/loader_plugins/zeo_loader.cpp \
|
||||
SleepLib/thirdparty/botan_all.cpp \
|
||||
SleepLib/crypto.cpp \
|
||||
zip.cpp \
|
||||
SleepLib/thirdparty/miniz.c \
|
||||
csv.cpp \
|
||||
@ -402,6 +403,7 @@ HEADERS += \
|
||||
SleepLib/thirdparty/botan_windows.h \
|
||||
SleepLib/thirdparty/botan_linux.h \
|
||||
SleepLib/thirdparty/botan_macos.h \
|
||||
SleepLib/crypto.h \
|
||||
zip.h \
|
||||
SleepLib/thirdparty/miniz.h \
|
||||
csv.h \
|
||||
@ -583,6 +585,7 @@ test {
|
||||
tests/versiontests.cpp \
|
||||
tests/viatomtests.cpp \
|
||||
tests/deviceconnectiontests.cpp \
|
||||
tests/cryptotests.cpp \
|
||||
tests/dreemtests.cpp \
|
||||
tests/zeotests.cpp
|
||||
|
||||
@ -595,6 +598,7 @@ test {
|
||||
tests/versiontests.h \
|
||||
tests/viatomtests.h \
|
||||
tests/deviceconnectiontests.h \
|
||||
tests/cryptotests.h \
|
||||
tests/dreemtests.h \
|
||||
tests/zeotests.h
|
||||
}
|
||||
|
103
oscar/tests/cryptotests.cpp
Normal file
103
oscar/tests/cryptotests.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/* Cryptographic Abstraction Unit Tests
|
||||
*
|
||||
* Copyright (c) 2021-2022 The OSCAR Team
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file COPYING in the main directory of the source code
|
||||
* for more details. */
|
||||
|
||||
#include "cryptotests.h"
|
||||
#include "SleepLib/crypto.h"
|
||||
|
||||
void CryptoTests::testAES256()
|
||||
{
|
||||
// From FIPS-197 C.3
|
||||
QByteArray expected_plaintext = QByteArray::fromHex("00112233445566778899aabbccddeeff");
|
||||
QByteArray key = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
QByteArray ciphertext = QByteArray::fromHex("8ea2b7ca516745bfeafc49904b496089");
|
||||
|
||||
QByteArray plaintext;
|
||||
CryptoResult result = decrypt_aes256(key, ciphertext, plaintext);
|
||||
Q_ASSERT(result == OK);
|
||||
Q_ASSERT(plaintext == expected_plaintext);
|
||||
}
|
||||
|
||||
|
||||
// From https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/gcm-spec.pdf
|
||||
typedef struct AES256GCMVector_t
|
||||
{
|
||||
const char* key;
|
||||
const char* p;
|
||||
const char* iv;
|
||||
const char* c;
|
||||
const char* tag;
|
||||
} AES256GCMVector_t;
|
||||
static const int s_AES256GCMVectorCount = 3;
|
||||
static const AES256GCMVector_t s_AES256GCMVectors[s_AES256GCMVectorCount] = {
|
||||
// Test Case 13
|
||||
{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"",
|
||||
"000000000000000000000000",
|
||||
"",
|
||||
"530f8afbc74536b9a963b4f1c4cb738b"
|
||||
},
|
||||
// Test Case 14
|
||||
{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"00000000000000000000000000000000",
|
||||
"000000000000000000000000",
|
||||
"cea7403d4d606b6e074ec5d3baf39d18",
|
||||
"d0d1c8a799996bf0265b98b5d48ab919"
|
||||
},
|
||||
// Test Case 15
|
||||
{
|
||||
"feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
|
||||
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
|
||||
"cafebabefacedbaddecaf888",
|
||||
"522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
|
||||
"b094dac5d93471bdec1a502270e3cc6c"
|
||||
}
|
||||
};
|
||||
|
||||
void CryptoTests::testAES256GCM()
|
||||
{
|
||||
QByteArray empty;
|
||||
for (int i = 0; i < s_AES256GCMVectorCount; i++) {
|
||||
const AES256GCMVector_t* v = &s_AES256GCMVectors[i];
|
||||
QByteArray key = QByteArray::fromHex(v->key);
|
||||
QByteArray expected_plaintext = QByteArray::fromHex(v->p);
|
||||
QByteArray iv = QByteArray::fromHex(v->iv);
|
||||
QByteArray ciphertext = QByteArray::fromHex(v->c);
|
||||
QByteArray tag = QByteArray::fromHex(v->tag);
|
||||
|
||||
QByteArray plaintext;
|
||||
CryptoResult result = decrypt_aes256_gcm(key, iv, ciphertext, tag, plaintext);
|
||||
Q_ASSERT(result == OK);
|
||||
Q_ASSERT(plaintext == expected_plaintext);
|
||||
|
||||
tag = QByteArray::fromHex(s_AES256GCMVectors[(i+1) % s_AES256GCMVectorCount].tag);
|
||||
result = decrypt_aes256_gcm(key, iv, ciphertext, tag, plaintext);
|
||||
Q_ASSERT(result == InvalidTag);
|
||||
Q_ASSERT(plaintext == empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CryptoTests::testPBKDF2_SHA256()
|
||||
{
|
||||
// From RFC 7914 section 11
|
||||
QByteArray passphrase("passwd");
|
||||
QByteArray salt("salt");
|
||||
int iterations = 1;
|
||||
QByteArray expected_key = QByteArray::fromHex(
|
||||
"55 ac 04 6e 56 e3 08 9f ec 16 91 c2 25 44 b6 05"
|
||||
"f9 41 85 21 6d de 04 65 e6 8b 9d 57 c2 0d ac bc"
|
||||
"49 ca 9c cc f1 79 b6 45 99 16 64 b3 9d 77 ef 31"
|
||||
"7c 71 b8 45 b1 e3 0b d5 09 11 20 41 d3 a1 97 83");
|
||||
|
||||
QByteArray derived_key(expected_key.size(), 0);
|
||||
CryptoResult result = pbkdf2_sha256(passphrase, salt, iterations, derived_key);
|
||||
Q_ASSERT(result == OK);
|
||||
Q_ASSERT(derived_key == expected_key);
|
||||
}
|
20
oscar/tests/cryptotests.h
Normal file
20
oscar/tests/cryptotests.h
Normal file
@ -0,0 +1,20 @@
|
||||
/* Cryptographic Abstraction Unit Tests
|
||||
*
|
||||
* Copyright (c) 2021-2022 The OSCAR Team
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file COPYING in the main directory of the source code
|
||||
* for more details. */
|
||||
|
||||
#include "tests/AutoTest.h"
|
||||
|
||||
class CryptoTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testAES256();
|
||||
void testAES256GCM();
|
||||
void testPBKDF2_SHA256();
|
||||
};
|
||||
DECLARE_TEST(CryptoTests)
|
||||
|
Loading…
Reference in New Issue
Block a user