Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/signing #1358

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
[submodule "vendor/sqlite3-unicodesn"]
path = vendor/sqlite3-unicodesn
url = https://github.com/couchbasedeps/sqlite3-unicodesn
[submodule "vendor/Monocypher"]
path = vendor/Monocypher
url = https://github.com/LoupVaillant/Monocypher.git
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ target_include_directories(
vendor/sqlite3-unicodesn
vendor/mbedtls/include
vendor/mbedtls/crypto/include
vendor/Monocypher/src
vendor/Monocypher/src/optional
vendor/sockpp/include
)

Expand Down
96 changes: 72 additions & 24 deletions Crypto/SecureDigest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,69 +17,117 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation-deprecated-sync"
#include "mbedtls/sha1.h"
#include "mbedtls/sha256.h"
#pragma clang diagnostic pop

#ifdef __APPLE__
#define USE_COMMON_CRYPTO
# define USE_COMMON_CRYPTO
#endif

#ifdef USE_COMMON_CRYPTO
#include <CommonCrypto/CommonDigest.h>
#define _CONTEXT ((CC_SHA1_CTX*)_context)
#else
#define _CONTEXT ((mbedtls_sha1_context*)_context)
#endif

namespace litecore {

void SHA1::computeFrom(fleece::slice s) {
(SHA1Builder() << s).finish(&bytes, sizeof(bytes));
}


bool SHA1::setDigest(fleece::slice s) {
if (s.size != sizeof(bytes))
template <DigestType TYPE, size_t SIZE>
bool Digest<TYPE,SIZE>::setDigest(fleece::slice s) {
if (s.size != _bytes.size())
return false;
memcpy(bytes, s.buf, sizeof(bytes));
s.copyTo(_bytes.data());
return true;
}


std::string SHA1::asBase64() const {
template <DigestType TYPE, size_t SIZE>
std::string Digest<TYPE,SIZE>::asBase64() const {
return fleece::base64::encode(asSlice());
}


SHA1Builder::SHA1Builder() {
#pragma mark - SHA1:


template <>
Digest<SHA,1>::Builder::Builder() {
static_assert(sizeof(_context) >= sizeof(mbedtls_sha1_context));
#ifdef USE_COMMON_CRYPTO
static_assert(sizeof(_context) >= sizeof(CC_SHA1_CTX));
CC_SHA1_Init(_CONTEXT);
CC_SHA1_Init((CC_SHA1_CTX*)_context);
#else
mbedtls_sha1_init(_CONTEXT);
mbedtls_sha1_starts(_CONTEXT);
mbedtls_sha1_init((mbedtls_sha1_context*)_context);
mbedtls_sha1_starts((mbedtls_sha1_context*)_context);
#endif
}


SHA1Builder& SHA1Builder::operator<< (fleece::slice s) {
template <>
Digest<SHA,1>::Builder& Digest<SHA,1>::Builder::operator<< (fleece::slice s) {
#ifdef USE_COMMON_CRYPTO
CC_SHA1_Update(_CONTEXT, s.buf, (CC_LONG)s.size);
CC_SHA1_Update((CC_SHA1_CTX*)_context, s.buf, (CC_LONG)s.size);
#else
mbedtls_sha1_update(_CONTEXT, (unsigned char*)s.buf, s.size);
mbedtls_sha1_update((mbedtls_sha1_context*)_context, (unsigned char*)s.buf, s.size);
#endif
return *this;
}


void SHA1Builder::finish(void *result, size_t resultSize) {
DebugAssert(resultSize == sizeof(SHA1::bytes));
template <>
void Digest<SHA,1>::Builder::finish(void *result, size_t resultSize) {
Assert(resultSize == kSizeInBytes);
#ifdef USE_COMMON_CRYPTO
CC_SHA1_Final((uint8_t*)result, _CONTEXT);
CC_SHA1_Final((uint8_t*)result, (CC_SHA1_CTX*)_context);
#else
mbedtls_sha1_finish(_CONTEXT, (uint8_t*)result);
mbedtls_sha1_free(_CONTEXT);
mbedtls_sha1_finish((mbedtls_sha1_context*)_context, (uint8_t*)result);
mbedtls_sha1_free((mbedtls_sha1_context*)_context);
#endif
}

// Force the non-specialized methods to be instantiated:
template class Digest<SHA,1>;


#pragma mark - SHA256:


template <>
Digest<SHA,256>::Builder::Builder() {
static_assert(sizeof(_context) >= sizeof(mbedtls_sha256_context));
#ifdef USE_COMMON_CRYPTO
static_assert(sizeof(_context) >= sizeof(CC_SHA256_CTX));
CC_SHA256_Init((CC_SHA256_CTX*)_context);
#else
mbedtls_sha256_init((mbedtls_sha256_context*)_context);
mbedtls_sha256_starts((mbedtls_sha256_context*)_context, 0);
#endif
}


template <>
Digest<SHA,256>::Builder& Digest<SHA,256>::Builder::operator<< (fleece::slice s) {
#ifdef USE_COMMON_CRYPTO
CC_SHA256_Update((CC_SHA256_CTX*)_context, s.buf, (CC_LONG)s.size);
#else
mbedtls_sha256_update((mbedtls_sha256_context*)_context, (unsigned char*)s.buf, s.size);
#endif
return *this;
}


template <>
void Digest<SHA,256>::Builder::finish(void *result, size_t resultSize) {
Assert(resultSize == kSizeInBytes);
#ifdef USE_COMMON_CRYPTO
CC_SHA256_Final((uint8_t*)result, (CC_SHA256_CTX*)_context);
#else
mbedtls_sha256_finish((mbedtls_sha256_context*)_context, (uint8_t*)result);
mbedtls_sha256_free((mbedtls_sha256_context*)_context);
#endif
}


// Force the non-specialized methods to be instantiated:
template class Digest<SHA,256>;

}
90 changes: 60 additions & 30 deletions Crypto/SecureDigest.hh
Original file line number Diff line number Diff line change
Expand Up @@ -12,66 +12,96 @@

#pragma once
#include "fleece/slice.hh"
#include <array>
#include <string>

namespace litecore {

/// A SHA-1 digest.
class SHA1 {
enum DigestType {
SHA,
};


/// A cryptographic digest. Available instantiations are <SHA,1> and <SHA,256>.
/// (SHA384 and SHA512 could be added by doing some copy-and-pasting in the .cc file.)
template <DigestType TYPE, size_t SIZE>
class Digest {
public:
SHA1() { memset(bytes, 0, sizeof(bytes)); }
class Builder;

Digest() {_bytes.fill(std::byte{0});}

/// Constructs instance with a digest of the data in `s`.
explicit Digest(fleece::slice s) {computeFrom(s);}

/// Constructs instance with a SHA-1 digest of the data in `s`
explicit SHA1(fleece::slice s) {computeFrom(s);}
inline Digest(Builder&&);

/// Computes a SHA-1 digest of the data
void computeFrom(fleece::slice);
/// Computes a digest of the data.
void computeFrom(fleece::slice data);

/// Stores a digest; returns false if slice is the wrong size
bool setDigest(fleece::slice);
/// Stores a digest; returns false if slice is the wrong size.
bool setDigest(fleece::slice digestData);

/// The digest as a slice
fleece::slice asSlice() const {return {bytes, sizeof(bytes)};}
/// The digest as a slice.
fleece::slice asSlice() const {return {_bytes.data(), _bytes.size()};}
operator fleece::slice() const {return asSlice();}

/// The digest encoded in Base64.
std::string asBase64() const;

bool operator==(const SHA1 &x) const {return memcmp(&bytes, &x.bytes, sizeof(bytes)) == 0;}
bool operator!= (const SHA1 &x) const {return !(*this == x);}
bool operator==(const Digest &x) const {return _bytes == x._bytes;}
bool operator!=(const Digest &x) const {return _bytes != x._bytes;}

private:
char bytes[20];
static constexpr size_t kSizeInBytes = ((TYPE == SHA && SIZE == 1) ? 160 : SIZE) / 8;

friend class SHA1Builder;
std::array<std::byte,kSizeInBytes> _bytes;
};


/// Builder for creating SHA-1 digests from piece-by-piece data.
class SHA1Builder {
/// Builder for creating digests incrementally from piece-by-piece data.
template <DigestType TYPE, size_t SIZE>
class Digest<TYPE, SIZE>::Builder {
public:
SHA1Builder();
Builder();

/// Add a single byte
SHA1Builder& operator<< (fleece::slice s);
/// Adds data.
Builder& operator<< (fleece::slice s);

/// Add data
SHA1Builder& operator<< (uint8_t b) {return *this << fleece::slice(&b, 1);}
/// Adds a single byte.
Builder& operator<< (uint8_t b) {return *this << fleece::slice(&b, 1);}

/// Finish and write the digest to `result`. (Don't reuse the builder.)
/// Finishes and writes the digest.
/// @warning Don't reuse this builder.
/// @param result The address to write the digest to.
/// @param resultSize Must be equal to the size of a digest.
void finish(void *result, size_t resultSize);

/// Finish and return the digest as a SHA1 object. (Don't reuse the builder.)
SHA1 finish() {
SHA1 result;
finish(&result.bytes, sizeof(result.bytes));
return result;
}
/// Finishes and returns the digest as a new object.
/// @warning Don't reuse this builder.
Digest finish() {return Digest(std::move(*this));}

private:
uint8_t _context[100]; // big enough to hold any platform's context struct
std::byte _context[110]; // big enough to hold any platform's context struct
};


template<DigestType TYPE, size_t SIZE>
Digest<TYPE, SIZE>::Digest(Builder &&builder) {
builder.finish(_bytes.data(), _bytes.size());
}

template <DigestType TYPE, size_t SIZE>
void Digest<TYPE,SIZE>::computeFrom(fleece::slice s) {
(Builder() << s).finish(_bytes.data(), _bytes.size());
}


// Shorthand names:
using SHA1 = Digest<SHA,1>;
using SHA1Builder = Digest<SHA,1>::Builder;

using SHA256 = Digest<SHA,256>;
}


114 changes: 114 additions & 0 deletions Crypto/SignatureTest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// SignatureTest.cc
//
// Copyright 2022-Present Couchbase, Inc.
//
// Use of this software is governed by the Business Source License included
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
// in that file, in accordance with the Business Source License, use of this
// software will be governed by the Apache License, Version 2.0, included in
// the file licenses/APL2.txt.
//

#include "PublicKey.hh"
#include "SignedDict.hh"
#include "Base64.hh"
#include "Error.hh"
#include "LiteCoreTest.hh"
#include "fleece/Mutable.hh"
#include <iostream>


using namespace litecore;
using namespace litecore::crypto;
using namespace std;
using namespace fleece;


TEST_CASE("Signatures", "[Signatures]") {
static constexpr slice kDataToSign = "The only thing we learn from history"
" is that people do not learn from history. --Hegel";

const char *alg = GENERATE(kRSAAlgorithmName, kEd25519AlgorithmName);
cerr << "\t---- " << alg << endl;

auto signingKey = SigningKey::generate(alg);
alloc_slice signature = signingKey->sign(kDataToSign);
cout << "Signature is " << signature.size << " bytes: " << base64::encode(signature) << endl;

// Verify:
auto verifyingKey = signingKey->verifyingKey();
CHECK(verifyingKey->verifySignature(kDataToSign, signature));

// Verification fails with wrong public key:
auto key2 = SigningKey::generate(kRSAAlgorithmName);
CHECK(!key2->verifyingKey()->verifySignature(kDataToSign, signature));

// Verification fails with incorrect digest:
auto badDigest = SHA256(kDataToSign);
((uint8_t*)&badDigest)[10]++;
CHECK(!verifyingKey->verifySignature(badDigest, signature));

// Verification fails with altered signature:
((uint8_t&)signature[30])++;
CHECK(!verifyingKey->verifySignature(kDataToSign, signature));
}


TEST_CASE("Signed Document", "[Signatures]") {
const char *algorithm = GENERATE(kRSAAlgorithmName, kEd25519AlgorithmName);
bool embedKey = GENERATE(false, true);
cerr << "\t---- " << algorithm << "; embed key in signature = " << embedKey << endl;

// Create a signed doc and convert to JSON:
alloc_slice publicKeyData;
string json;
{
auto priv = SigningKey::generate(algorithm);
auto pub = priv->verifyingKey();
publicKeyData = pub->data();

MutableDict doc = MutableDict::newDict();
doc["name"] = "Oliver Bolliver Butz";
doc["age"] = 6;
cout << "Document: " << doc.toJSONString() << endl;

MutableDict sig = makeSignature(doc, *priv, 5 /*minutes*/, embedKey);
REQUIRE(sig);
string sigJson = sig.toJSONString();
cout << "Signature, " << sigJson.size() << " bytes: " << sigJson << endl;

CHECK(verifySignature(doc, sig, pub.get()) == VerifyResult::Valid);

doc["(sig)"] = sig; // <-- add signature to doc, in "(sig)" property
json = doc.toJSONString();
}
cout << "Signed Document: " << json << endl;

// Now parse the JSON and verify the signature:
{
Doc parsedDoc = Doc::fromJSON(json);
Dict doc = parsedDoc.asDict();
Dict sig = doc["(sig)"].asDict();
REQUIRE(sig);

auto parsedKey = getSignaturePublicKey(sig, algorithm);
if (embedKey) {
REQUIRE(parsedKey);
CHECK(parsedKey->data() == publicKeyData);
} else {
CHECK(!parsedKey);
parsedKey = VerifyingKey::instantiate(publicKeyData, algorithm);
}

MutableDict unsignedDoc = doc.mutableCopy();
unsignedDoc.remove("(sig)"); // <-- detach signature to restore doc to signed form

if (embedKey)
CHECK(verifySignature(unsignedDoc, sig) == VerifyResult::Valid);
else
CHECK(verifySignature(unsignedDoc, sig) == VerifyResult::MissingKey);

CHECK(verifySignature(unsignedDoc, sig, parsedKey.get()) == VerifyResult::Valid);
}
}
Loading