mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Cryptoconditions: RSA-SHA-256 (RIPD-1213)
This commit is contained in:
@@ -150,6 +150,9 @@ validate (Condition const& c)
|
||||
return (cf2 & definedFeatures) == cf2;
|
||||
}
|
||||
|
||||
if (c.type == condition_rsa_sha256)
|
||||
return (c.featureBitmask == (feature_rsa_pss | feature_sha256));
|
||||
|
||||
if (c.type == condition_ed25519)
|
||||
return (c.featureBitmask == feature_ed25519);
|
||||
|
||||
|
||||
83
src/ripple/conditions/RsaSha256.h
Normal file
83
src/ripple/conditions/RsaSha256.h
Normal file
@@ -0,0 +1,83 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_CONDITIONS_RSA_SHA256_H
|
||||
#define RIPPLE_CONDITIONS_RSA_SHA256_H
|
||||
|
||||
#include <ripple/conditions/Condition.h>
|
||||
#include <ripple/conditions/Fulfillment.h>
|
||||
#include <ripple/basics/Buffer.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
namespace cryptoconditions {
|
||||
|
||||
class RsaSha256 final
|
||||
: public Fulfillment
|
||||
{
|
||||
Buffer modulus_;
|
||||
Buffer signature_;
|
||||
|
||||
public:
|
||||
RsaSha256 () = default;
|
||||
|
||||
Condition
|
||||
condition() const override;
|
||||
|
||||
std::uint16_t
|
||||
type () const override
|
||||
{
|
||||
return condition_rsa_sha256;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
features () const override
|
||||
{
|
||||
return feature_rsa_pss | feature_sha256;
|
||||
}
|
||||
|
||||
bool
|
||||
ok () const override
|
||||
{
|
||||
return !modulus_.empty() && !signature_.empty();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
payloadSize () const override;
|
||||
|
||||
Buffer
|
||||
payload() const override;
|
||||
|
||||
bool
|
||||
validate (Slice data) const override;
|
||||
|
||||
/** Sign the given message with an RSA key */
|
||||
bool sign (
|
||||
std::string const& key,
|
||||
Slice message);
|
||||
|
||||
bool
|
||||
parsePayload (Slice s) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <ripple/conditions/Fulfillment.h>
|
||||
#include <ripple/conditions/PreimageSha256.h>
|
||||
#include <ripple/conditions/PrefixSha256.h>
|
||||
#include <ripple/conditions/RsaSha256.h>
|
||||
#include <ripple/conditions/Ed25519.h>
|
||||
#include <ripple/conditions/impl/utils.h>
|
||||
#include <boost/regex.hpp>
|
||||
@@ -85,6 +86,10 @@ loadFulfillment (std::uint16_t type, Slice payload)
|
||||
p = std::make_unique<PrefixSha256>();
|
||||
break;
|
||||
|
||||
case condition_rsa_sha256:
|
||||
p = std::make_unique<RsaSha256>();
|
||||
break;
|
||||
|
||||
case condition_ed25519:
|
||||
p = std::make_unique<Ed25519>();
|
||||
break;
|
||||
|
||||
313
src/ripple/conditions/impl/RsaSha256.cpp
Normal file
313
src/ripple/conditions/impl/RsaSha256.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/conditions/Condition.h>
|
||||
#include <ripple/conditions/Fulfillment.h>
|
||||
#include <ripple/conditions/RsaSha256.h>
|
||||
#include <ripple/conditions/impl/base64.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <boost/algorithm/clamp.hpp>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
namespace ripple {
|
||||
namespace cryptoconditions {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct rsa_deleter
|
||||
{
|
||||
void operator() (RSA* rsa) const
|
||||
{
|
||||
RSA_free (rsa);
|
||||
}
|
||||
};
|
||||
|
||||
using RsaKey = std::unique_ptr<RSA, rsa_deleter>;
|
||||
|
||||
struct bn_deleter
|
||||
{
|
||||
void operator() (BIGNUM* bn) const
|
||||
{
|
||||
BN_free (bn);
|
||||
}
|
||||
};
|
||||
|
||||
using BigNum = std::unique_ptr<BIGNUM, bn_deleter>;
|
||||
|
||||
// Check whether the public modulus meets the length
|
||||
// requirements imposed by section 4.4.1 of the RFC.
|
||||
bool
|
||||
checkModulusLength (int len)
|
||||
{
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
return len == boost::algorithm::clamp(len, 128, 512);
|
||||
}
|
||||
|
||||
bool
|
||||
signHelper (
|
||||
RSA* key,
|
||||
Slice message,
|
||||
Buffer& modulus,
|
||||
Buffer& signature)
|
||||
{
|
||||
int const keySize = RSA_size(key);
|
||||
if (!checkModulusLength (keySize))
|
||||
return false;
|
||||
|
||||
sha256_hasher h;
|
||||
h (message.data(), message.size());
|
||||
auto digest = static_cast<sha256_hasher::result_type>(h);
|
||||
|
||||
Buffer buf;
|
||||
|
||||
// Pad the result (-1 -> use hash length as salt length)
|
||||
if (!RSA_padding_add_PKCS1_PSS(key,
|
||||
buf.alloc(keySize), digest.data(),
|
||||
EVP_sha256(), -1))
|
||||
return false;
|
||||
|
||||
// Sign - we've manually padded the input already.
|
||||
auto ret = RSA_private_encrypt(keySize, buf.data(),
|
||||
signature.alloc (buf.size()), key, RSA_NO_PADDING);
|
||||
|
||||
if (ret == -1)
|
||||
return false;
|
||||
|
||||
BN_bn2bin (key->n, modulus.alloc(BN_num_bytes (key->n)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
validateHelper (
|
||||
RSA* key,
|
||||
Slice message,
|
||||
Slice signature)
|
||||
{
|
||||
int const keySize = RSA_size(key);
|
||||
if (!checkModulusLength (keySize))
|
||||
return false;
|
||||
|
||||
Buffer buf;
|
||||
|
||||
auto ret = RSA_public_decrypt(
|
||||
keySize,
|
||||
signature.data(),
|
||||
buf.alloc (keySize),
|
||||
key,
|
||||
RSA_NO_PADDING);
|
||||
|
||||
if (ret == -1)
|
||||
return false;
|
||||
|
||||
sha256_hasher h;
|
||||
h (message.data(), message.size());
|
||||
auto digest = static_cast<sha256_hasher::result_type>(h);
|
||||
|
||||
return RSA_verify_PKCS1_PSS(key, digest.data(), EVP_sha256(), buf.data(), -1) == 1;
|
||||
}
|
||||
|
||||
bool
|
||||
parsePayloadHelper(
|
||||
Slice s,
|
||||
Buffer& modulus,
|
||||
Buffer& signature)
|
||||
{
|
||||
auto start = s.data ();
|
||||
auto finish = s.data () + s.size();
|
||||
|
||||
std::size_t len;
|
||||
|
||||
std::tie (start, len) = oer::decode_length (
|
||||
start, finish);
|
||||
|
||||
if (std::distance (start, finish) < len)
|
||||
return false;
|
||||
|
||||
std::memcpy (modulus.alloc (len), start, len);
|
||||
std::advance (start, len);
|
||||
|
||||
std::tie (start, len) = oer::decode_length (
|
||||
start, finish);
|
||||
|
||||
if (std::distance (start, finish) < len)
|
||||
return false;
|
||||
|
||||
std::memcpy (signature.alloc (len), start, len);
|
||||
std::advance (start, len);
|
||||
|
||||
// Enforce constraints from the RFC:
|
||||
BigNum sig (BN_bin2bn (
|
||||
signature.data(), signature.size(), nullptr));
|
||||
|
||||
BigNum mod (BN_bin2bn (
|
||||
modulus.data(), modulus.size(), nullptr));
|
||||
|
||||
if (!sig || !mod)
|
||||
return false;
|
||||
|
||||
// Per 4.4.1 of the RFC we are required to reject
|
||||
// moduli smaller than 128 bytes or greater than
|
||||
// 512 bytes.
|
||||
int modBytes = BN_num_bytes (mod.get());
|
||||
|
||||
if (!checkModulusLength (modBytes))
|
||||
return false;
|
||||
|
||||
// Per 4.4.2 of the RFC we must check whether the
|
||||
// signature and modulus consist of the same number
|
||||
// of octets and that the signature is numerically
|
||||
// less than the modulus:
|
||||
if (BN_num_bytes (sig.get()) != modBytes)
|
||||
return false;
|
||||
|
||||
return BN_cmp (sig.get(), mod.get()) < 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Condition
|
||||
RsaSha256::condition() const
|
||||
{
|
||||
std::vector<std::uint8_t> m;
|
||||
m.reserve (1024);
|
||||
|
||||
oer::encode_octetstring (
|
||||
modulus_.size(),
|
||||
modulus_.data(),
|
||||
modulus_.data() + modulus_.size(),
|
||||
std::back_inserter(m));
|
||||
|
||||
sha256_hasher h;
|
||||
h (m.data(), m.size());
|
||||
|
||||
Condition cc;
|
||||
cc.type = type();
|
||||
cc.featureBitmask = features();
|
||||
cc.maxFulfillmentLength = payloadSize();
|
||||
|
||||
cc.fingerprint = static_cast<sha256_hasher::result_type>(h);
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
|
||||
std::size_t
|
||||
RsaSha256::payloadSize () const
|
||||
{
|
||||
return
|
||||
oer::predict_octetstring_size (modulus_.size()) +
|
||||
oer::predict_octetstring_size (signature_.size());
|
||||
}
|
||||
|
||||
Buffer
|
||||
RsaSha256::payload() const
|
||||
{
|
||||
Buffer b (payloadSize());
|
||||
|
||||
auto out = oer::encode_octetstring (
|
||||
modulus_.size(),
|
||||
modulus_.data(),
|
||||
modulus_.data() + modulus_.size(),
|
||||
b.data());
|
||||
|
||||
oer::encode_octetstring (
|
||||
signature_.size(),
|
||||
signature_.data(),
|
||||
signature_.data() + modulus_.size(),
|
||||
out);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
bool
|
||||
RsaSha256::validate (Slice data) const
|
||||
{
|
||||
if (!ok())
|
||||
return false;
|
||||
|
||||
detail::RsaKey rsa (RSA_new());
|
||||
|
||||
rsa->n = BN_new();
|
||||
BN_bin2bn(modulus_.data(), modulus_.size(), rsa->n);
|
||||
|
||||
rsa->e = BN_new();
|
||||
BN_set_word (rsa->e, 65537);
|
||||
|
||||
return detail::validateHelper (rsa.get(), data, signature_);
|
||||
}
|
||||
|
||||
/** Sign the given message with an RSA key */
|
||||
bool
|
||||
RsaSha256::sign (
|
||||
std::string const& key,
|
||||
Slice message)
|
||||
{
|
||||
// This ugly const_cast/reinterpret_cast is needed
|
||||
// on some machines. Although the documentation
|
||||
// suggests that the function accepts a void const*
|
||||
// argument, apparently some platforms have OpenSSL
|
||||
// libraries that are up-to-date but accept void*.
|
||||
auto bio = BIO_new_mem_buf(
|
||||
const_cast<void*>(static_cast<void const*>(key.data())),
|
||||
key.size());
|
||||
|
||||
if (!bio)
|
||||
return false;
|
||||
|
||||
detail::RsaKey rsa (PEM_read_bio_RSAPrivateKey(
|
||||
bio, NULL, NULL, NULL));
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
if (!rsa)
|
||||
return false;
|
||||
|
||||
if (detail::signHelper (rsa.get(), message, modulus_, signature_))
|
||||
return true;
|
||||
|
||||
modulus_.clear();
|
||||
signature_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
RsaSha256::parsePayload (Slice s)
|
||||
{
|
||||
// The payload may not be empty
|
||||
if (!s.empty() && detail::parsePayloadHelper (s, modulus_, signature_))
|
||||
return true;
|
||||
|
||||
// Clear the state
|
||||
modulus_.clear();
|
||||
signature_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,3 +22,4 @@
|
||||
#include <ripple/conditions/impl/Condition.cpp>
|
||||
#include <ripple/conditions/impl/Fulfillment.cpp>
|
||||
#include <ripple/conditions/impl/Ed25519.cpp>
|
||||
#include <ripple/conditions/impl/RsaSha256.cpp>
|
||||
|
||||
Reference in New Issue
Block a user