diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fef04ae..a7e6484 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(RSA decrypt.cpp encrypt.cpp keyPair.cpp + signature.cpp ) # Add src directory to path for header includes diff --git a/src/signature.cpp b/src/signature.cpp new file mode 100644 index 0000000..ba9eabc --- /dev/null +++ b/src/signature.cpp @@ -0,0 +1,54 @@ +#include "signature.h" + +#include "helper.h" +#include "math_utils.h" + +namespace core::signature { +namespace { + +[[nodiscard]] operations::BigInt digestToInteger(const std::vector& digest) { + return operations::BigInt(bytesToByteArray(digest)); +} + +[[nodiscard]] std::size_t modulusSize(const operations::BigInt& modulus) { + return modulus.getBytes().size() * sizeof(std::uint64_t); +} + +} // namespace + +std::vector signDigest(const PrivateKey& privateKey, + const std::vector& digest) { + const operations::BigInt one(1); + if (digest.empty() || privateKey.n <= one || privateKey.d <= one) { + return {}; + } + + const auto digestInteger = digestToInteger(digest); + if (digestInteger >= privateKey.n) { + return {}; + } + + const auto signature = operations::math::modPow(digestInteger, privateKey.d, privateKey.n); + return byteArrayToBytes(signature.getBytes(), modulusSize(privateKey.n)); +} + +bool verifyDigest(const PublicKey& publicKey, const std::vector& digest, + const std::vector& signature) { + const operations::BigInt one(1); + if (digest.empty() || publicKey.n <= one || publicKey.e <= one || + signature.size() != modulusSize(publicKey.n)) { + return false; + } + + const auto digestInteger = digestToInteger(digest); + const operations::BigInt signatureInteger(bytesToByteArray(signature)); + if (digestInteger >= publicKey.n || signatureInteger >= publicKey.n) { + return false; + } + + const auto verifiedDigest = + operations::math::modPow(signatureInteger, publicKey.e, publicKey.n); + return verifiedDigest == digestInteger; +} + +} // namespace core::signature diff --git a/src/signature.h b/src/signature.h new file mode 100644 index 0000000..d974f73 --- /dev/null +++ b/src/signature.h @@ -0,0 +1,19 @@ +#ifndef RSA_SIGNATURE_H +#define RSA_SIGNATURE_H + +#include +#include + +#include "keyPair.h" + +namespace core::signature { + +[[nodiscard]] std::vector signDigest(const PrivateKey& privateKey, + const std::vector& digest); + +[[nodiscard]] bool verifyDigest(const PublicKey& publicKey, const std::vector& digest, + const std::vector& signature); + +} // namespace core::signature + +#endif // RSA_SIGNATURE_H diff --git a/tests/test_rsa.cpp b/tests/test_rsa.cpp index 32dd851..374d28d 100644 --- a/tests/test_rsa.cpp +++ b/tests/test_rsa.cpp @@ -5,6 +5,7 @@ #include "decrypt.h" #include "encrypt.h" #include "keyPair.h" +#include "signature.h" using core::decryptor::decrypt; using core::encryptor::encrypt; @@ -125,4 +126,40 @@ TEST_CASE("RSA Core: Key Creation and Struct-based Initialization") { // Expect std::runtime_error as specified in keyPair::create REQUIRE_THROWS_AS(keyPair::create(invalidPub, invalidPriv), std::runtime_error); } -} \ No newline at end of file +} + +TEST_CASE("RSA Core: Digest signatures prove private-key possession") { + keyPair pairA(PublicKey{operations::BigInt(3233), operations::BigInt(17)}, + PrivateKey{operations::BigInt(3233), operations::BigInt(2753)}); + keyPair pairB(PublicKey{operations::BigInt(2773), operations::BigInt(17)}, + PrivateKey{operations::BigInt(2773), operations::BigInt(157)}); + const std::vector digest = {0x2A}; + + const auto signature = core::signature::signDigest(pairA.getPrivateKey(), digest); + REQUIRE_FALSE(signature.empty()); + REQUIRE(core::signature::verifyDigest(pairA.getPublicKey(), digest, signature)); + + SECTION("A different public key does not verify the signature") { + REQUIRE_FALSE(core::signature::verifyDigest(pairB.getPublicKey(), digest, signature)); + } + + SECTION("A modified digest does not verify the signature") { + auto modifiedDigest = digest; + modifiedDigest.front() ^= 0x01; + REQUIRE_FALSE( + core::signature::verifyDigest(pairA.getPublicKey(), modifiedDigest, signature)); + } + + SECTION("A modified signature is rejected") { + auto modifiedSignature = signature; + modifiedSignature.back() ^= 0x01; + REQUIRE_FALSE( + core::signature::verifyDigest(pairA.getPublicKey(), digest, modifiedSignature)); + } + + SECTION("Malformed inputs are rejected") { + REQUIRE(core::signature::signDigest(pairA.getPrivateKey(), {}).empty()); + REQUIRE_FALSE(core::signature::verifyDigest(pairA.getPublicKey(), {}, signature)); + REQUIRE_FALSE(core::signature::verifyDigest(pairA.getPublicKey(), digest, {})); + } +}