Skip to content

Commit

Permalink
Merge PR #2868: CryptographicHash: new class for computing cryptograp…
Browse files Browse the repository at this point in the history
…hic hashes.
  • Loading branch information
mkrautz committed Feb 25, 2017
2 parents e6ac067 + 7c22b84 commit accc8fd
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 2 deletions.
164 changes: 164 additions & 0 deletions src/CryptographicHash.cpp
@@ -0,0 +1,164 @@
// Copyright 2005-2017 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "murmur_pch.h"

#include "CryptographicHash.h"

class CryptographicHashPrivate {
public:
CryptographicHashPrivate(const EVP_MD *type);
~CryptographicHashPrivate();
void addData(const QByteArray &buf);
QByteArray result();

private:
EVP_MD_CTX *m_mdctx;

/// The result of the cryptographic hash.
/// This field is filled out by the result()
/// method.
/// Once this field is set, the m_mdctx is finalized
/// and can't be used anymore, so result() and addData()
/// must guard against that possibility.
QByteArray m_result;

CryptographicHashPrivate();

/// If m_mdctx is set, cleanupMdctx will destroy
/// the EVP_MD_CTX object and set m_mdctx to NULL.
void cleanupMdctx();
};

CryptographicHashPrivate::CryptographicHashPrivate()
: m_mdctx(NULL) {
}

void CryptographicHashPrivate::cleanupMdctx() {
if (m_mdctx) {
EVP_MD_CTX_destroy(m_mdctx);
m_mdctx = NULL;
}
}

CryptographicHashPrivate::CryptographicHashPrivate(const EVP_MD *type)
: m_mdctx(NULL) {

m_mdctx = EVP_MD_CTX_create();
if (m_mdctx == NULL) {
return;
}

int err = EVP_DigestInit_ex(m_mdctx, type, NULL);
if (err != 1) {
cleanupMdctx();
return;
}
}

CryptographicHashPrivate::~CryptographicHashPrivate() {
cleanupMdctx();
}

void CryptographicHashPrivate::addData(const QByteArray &buf) {
if (m_mdctx == NULL) {
return;
}

// If we have a result already, that means our m_mdctx
// object has been finalized, and can't be used anymore.
// In that case, transition into an error state by cleaning
// up the mdctx -- that way, subsequent calls to result()
// will return an empty QByteArray.
if (!m_result.isNull()) {
cleanupMdctx();
return;
}

int err = EVP_DigestUpdate(m_mdctx, buf.constData(), buf.size());
if (err != 1) {
cleanupMdctx();
}
}

QByteArray CryptographicHashPrivate::result() {
if (m_mdctx == NULL) {
return QByteArray();
}

// If we have a result already, that means our m_mdctx
// object has been finalized, and can't be used anymore.
// In that case, just return the already computed result.
if (!m_result.isNull()) {
return m_result;
}

QByteArray digest(EVP_MD_CTX_size(m_mdctx), '\0');
int err = EVP_DigestFinal_ex(m_mdctx, reinterpret_cast<unsigned char *>(digest.data()), NULL);
if (err != 1) {
cleanupMdctx();
return QByteArray();
}

m_result = digest;

return m_result;
}

QByteArray CryptographicHash::hash(const QByteArray &buf, Algorithm algo) {
CryptographicHash h(algo);
h.addData(buf);
return h.result();
}

QString CryptographicHash::humanReadableAlgorithmName(CryptographicHash::Algorithm algo) {
switch (algo) {
case CryptographicHash::Sha1:
return QLatin1String("SHA-1");
case CryptographicHash::Sha256:
return QLatin1String("SHA-256");
}
return QString();
}

QString CryptographicHash::shortAlgorithmName(CryptographicHash::Algorithm algo) {
switch (algo) {
case CryptographicHash::Sha1:
return QLatin1String("sha1");
case CryptographicHash::Sha256:
return QLatin1String("sha256");
}
return QString();
}

CryptographicHash::CryptographicHash()
: d(NULL) {
}

CryptographicHash::CryptographicHash(CryptographicHash::Algorithm algo)
: d(NULL) {

switch (algo) {
case CryptographicHash::Sha1:
d = new CryptographicHashPrivate(EVP_sha1());
break;
case CryptographicHash::Sha256:
d = new CryptographicHashPrivate(EVP_sha256());
break;
}
}

void CryptographicHash::addData(const QByteArray &buf) {
if (d) {
d->addData(buf);
}
}

QByteArray CryptographicHash::result() const {
if (d) {
return d->result();
}
return QByteArray();
}
71 changes: 71 additions & 0 deletions src/CryptographicHash.h
@@ -0,0 +1,71 @@
// Copyright 2005-2017 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#ifndef MUMBLE_CRYPTOGRAPHICHASH_H_
#define MUMBLE_CRYPTOGRAPHICHASH_H_

#include <QByteArray>

class CryptographicHashPrivate;

/// CryptographicHash is used for computing cryptographic hashes.
class CryptographicHash {
public:
/// Algorithm specifies an algorithm for use with CryptographicHash.
enum Algorithm {
/// The SHA-1 hashing algorithm.
Sha1,
/// The SHA-256 hashing algorithm.
Sha256
};

/// Compute the cryptographic hash of |buf| using algorithm |algo|
/// and return the result.
/// On failure, the returned QByteArray will be empty.
static QByteArray hash(const QByteArray &buf, Algorithm algo);

/// Return the human readable name of |algo|.
///
/// Examples:
/// CryptographicHash::Sha1 -> "SHA-1"
/// CryptographicHash::Sha256 -> "SHA-256"
static QString humanReadableAlgorithmName(CryptographicHash::Algorithm algo);

/// Return a machine-readable short name of |algo|.
/// This variant is a short, lower-case name for the algorithm, suitable
/// for prefixing when serializing a cryptographic hash.
///
/// Examples:
/// CryptographicHash::Sha1 -> "sha1"
/// CryptographicHash::Sha256 -> "sha256"
static QString shortAlgorithmName(CryptographicHash::Algorithm algo);

/// Consruct a CryptographicHash object that can be used
/// to incrementally compute a cryptographic hash using the
/// hash function specified in |algo|.
CryptographicHash(Algorithm algo);

/// addData adds the content of |buf| to the hash computation.
///
/// Attempting to add data to the CryptographicHash after result()
/// has been called is an error, and will cause the CryptographicHash
/// object to transition to an error state. Subsequent calls to result()
/// will return an empty QByteArray.
void addData(const QByteArray &buf);

/// result() finalizes the CryptographicHash object,
/// and returns the computed hash.
/// After calling this method, it is no longer possible
/// to add more data to the CryptographicHash object.
/// If an error occurred during the computation, the
/// returned QByteArray will be empty.
QByteArray result() const;

private:
CryptographicHash();
CryptographicHashPrivate *d;
};

#endif
4 changes: 2 additions & 2 deletions src/mumble.pri
Expand Up @@ -14,8 +14,8 @@ CONFIG += qt thread debug_and_release warn_on
DEFINES *= MUMBLE_VERSION_STRING=$$VERSION
INCLUDEPATH += $$PWD . ../mumble_proto
VPATH += $$PWD
HEADERS *= ACL.h Channel.h CryptState.h Connection.h Group.h HTMLFilter.h User.h Net.h OSInfo.h Timer.h SSL.h Version.h SSLCipherInfo.h SSLCipherInfoTable.h licenses.h License.h LogEmitter.h
SOURCES *= ACL.cpp Group.cpp Channel.cpp Connection.cpp HTMLFilter.cpp User.cpp Timer.cpp CryptState.cpp OSInfo.cpp Net.cpp SSL.cpp Version.cpp SSLCipherInfo.cpp License.cpp LogEmitter.cpp
HEADERS *= ACL.h Channel.h CryptState.h Connection.h Group.h HTMLFilter.h User.h Net.h OSInfo.h Timer.h SSL.h Version.h SSLCipherInfo.h SSLCipherInfoTable.h licenses.h License.h LogEmitter.h CryptographicHash.h
SOURCES *= ACL.cpp Group.cpp Channel.cpp Connection.cpp HTMLFilter.cpp User.cpp Timer.cpp CryptState.cpp OSInfo.cpp Net.cpp SSL.cpp Version.cpp SSLCipherInfo.cpp License.cpp LogEmitter.cpp CryptographicHash.cpp
LIBS *= -lmumble_proto
# Note: Protobuf generates into its own directory so we can mark it as a
# system include folder for unix. Otherwise the generated code creates
Expand Down
139 changes: 139 additions & 0 deletions src/tests/TestCryptographicHash.cpp
@@ -0,0 +1,139 @@
// Copyright 2005-2017 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include <QtCore>
#include <QtTest>
#include <QLatin1String>

#include "CryptographicHash.h"

class TestCryptographicHash : public QObject {
Q_OBJECT
private slots:
void sha1_data();
void sha1();

void sha256_data();
void sha256();

void staticHashVsObject();
void multipleResultCalls();
void addDataAfterResult();
};

/// normalizeHash removes all whitespace from the hex-encoded hash string.
static QString normalizeHash(QString str) {
str.replace(QLatin1String(" "), QLatin1String(""));
str.replace(QLatin1String("\t"), QLatin1String(""));
str.replace(QLatin1String("\r"), QLatin1String(""));
str.replace(QLatin1String("\n"), QLatin1String(""));
return str;
}

/// normalizeHash removes all whitespace from the hex-encoded hash byte array.
static QByteArray normalizeHash(QByteArray buf) {
buf.replace(" ", "");
buf.replace("\t", "");
buf.replace("\r", "");
buf.replace("\n", "");
return buf;
}

void TestCryptographicHash::sha1_data() {
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expectedOutput");

// First 4 SHA1 test vectors from http://www.di-mgt.com.au/sha_testvectors.html
// ...The rest are a bit too excessive to put in a table-based test.
QTest::newRow("1") << QString(QLatin1String("abc")) << normalizeHash(QLatin1String("a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d"));
QTest::newRow("2") << QString(QLatin1String("")) << normalizeHash(QLatin1String("da39a3ee 5e6b4b0d 3255bfef 95601890 afd80709"));
QTest::newRow("3") << QString(QLatin1String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")) << normalizeHash(QLatin1String("84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1"));
QTest::newRow("4") << QString(QLatin1String("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")) << normalizeHash(QLatin1String("a49b2446 a02c645b f419f995 b6709125 3a04a259"));
}

void TestCryptographicHash::sha1() {
QFETCH(QString, input);
QFETCH(QString, expectedOutput);

QByteArray outBuf = CryptographicHash::hash(input.toLatin1(), CryptographicHash::Sha1);
QByteArray hexBuf = outBuf.toHex();
QString output = QString::fromLatin1(hexBuf);

QCOMPARE(output, expectedOutput);
}

void TestCryptographicHash::sha256_data() {
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expectedOutput");

// First 4 SHA256 test vectors from http://www.di-mgt.com.au/sha_testvectors.html
// ...The rest are a bit too excessive to put in a table-based test.
QTest::newRow("1") << QString(QLatin1String("abc")) << normalizeHash(QLatin1String("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad"));
QTest::newRow("2") << QString(QLatin1String("")) << normalizeHash(QLatin1String("e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855"));
QTest::newRow("3") << QString(QLatin1String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")) << normalizeHash(QLatin1String("248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1"));
QTest::newRow("4") << QString(QLatin1String("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")) << normalizeHash(QLatin1String("cf5b16a7 78af8380 036ce59e 7b049237 0b249b11 e8f07a51 afac4503 7afee9d1"));
}

void TestCryptographicHash::sha256() {
QFETCH(QString, input);
QFETCH(QString, expectedOutput);

QByteArray outBuf = CryptographicHash::hash(input.toLatin1(), CryptographicHash::Sha256);

QVERIFY(!outBuf.isEmpty());

QByteArray hexBuf = outBuf.toHex();
QString output = QString::fromLatin1(hexBuf);

QCOMPARE(output, expectedOutput);
}

void TestCryptographicHash::staticHashVsObject() {
CryptographicHash h(CryptographicHash::Sha256);
h.addData(QByteArray("abc"));
QByteArray objectResult = h.result();

QByteArray staticResult = CryptographicHash::hash(QByteArray("abc"), CryptographicHash::Sha256);

QVERIFY(!objectResult.isEmpty());
QVERIFY(!staticResult.isEmpty());

QCOMPARE(objectResult, staticResult);
}

void TestCryptographicHash::multipleResultCalls() {
CryptographicHash h(CryptographicHash::Sha256);
h.addData(QByteArray("abc"));

QByteArray result1 = h.result();
QByteArray result2 = h.result();
QByteArray result3 = h.result();

QVERIFY(!result1.isEmpty());
QVERIFY(!result2.isEmpty());
QVERIFY(!result3.isEmpty());

QCOMPARE(result1.toHex(), normalizeHash(QByteArray("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad")));
QCOMPARE(result1, result2);
QCOMPARE(result1, result3);
}

void TestCryptographicHash::addDataAfterResult() {
CryptographicHash h(CryptographicHash::Sha256);
h.addData(QByteArray("abc"));

QByteArray result1 = h.result();

QCOMPARE(result1.toHex(), normalizeHash(QByteArray("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad")));

// Adding data after getting the result of a
// CryptographicHash is an error.
h.addData(QByteArray("abc"));
QByteArray result2 = h.result();
QVERIFY(result2.isEmpty());
}

QTEST_MAIN(TestCryptographicHash)
#include "TestCryptographicHash.moc"

0 comments on commit accc8fd

Please sign in to comment.