Refactor ECDSA canonical checks:

* Add signature canonicalization unit tests
* Tidy up into new classes and functions
This commit is contained in:
Nik Bougalis
2014-03-12 13:37:58 -07:00
committed by Vinnie Falco
parent a4a7dd4314
commit d447a1db39

View File

@@ -18,6 +18,8 @@
//==============================================================================
#include "../../../beast/beast/unit_test/suite.h"
#include <algorithm>
#include <iterator>
namespace ripple {
@@ -28,24 +30,31 @@ namespace detail {
{
BIGNUM* num;
BigNum (BigNum const&) = delete;
BigNum& operator=(BigNum const&) = delete;
BigNum (const char *hex)
BigNum ()
: num (BN_new ())
{
}
BigNum (const char *hex)
: num (BN_new ())
{
num = BN_new ();
BN_hex2bn (&num, hex);
}
BigNum ()
BigNum (unsigned char const* ptr, size_t len)
: num (BN_new ())
{
num = BN_new ();
set (ptr, len);
}
BigNum (unsigned char const* ptr, size_t len)
BigNum (BigNum const& other)
: num (BN_new ())
{
num = BN_new ();
BN_bin2bn (ptr, len, num);
if (BN_copy (num, other.num) == nullptr)
BN_clear (num);
}
~BigNum ()
@@ -57,77 +66,171 @@ namespace detail {
{
return num;
}
operator BIGNUM const* () const
{
return num;
}
bool set (unsigned char const* ptr, size_t len)
{
if (BN_bin2bn (ptr, len, num) == nullptr)
return false;
return true;
}
};
class SignaturePart
{
private:
size_t m_skip;
BigNum m_bn;
public:
SignaturePart (unsigned char const* sig, size_t len)
: m_skip (0)
{
// The format is: <02> <len> <sig>
if ((sig[0] != 0x02) || (len < 3))
return;
size_t sigLen = sig[1];
// Can't be longer than the data we have and must
// be between 1 and 33 bytes.
if ((sigLen > len) || (sigLen < 2) || (sigLen > 33))
return;
// The signature can't be negative
if ((sig[2] & 0x80) != 0)
return;
// It can't be zero
if ((sig[2] == 0) && (len == 1))
return;
// And it can't be padded
if ((sig[2] == 0) && ((sig[3] & 0x80) == 0))
return;
// Load the signature but skip the marker prefix and length
if (m_bn.set (sig + 2, sigLen))
m_skip = sigLen + 2;
}
bool valid () const
{
return m_skip != 0;
}
// The signature as a BIGNUM
BigNum getBigNum () const
{
return m_bn;
}
// Returns the number of bytes to skip for this signature part
size_t skip () const
{
return m_skip;
}
};
// The SECp256k1 modulus
static BigNum modulus ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
static BigNum const modulus (
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
}
/** Determine whether a signature is canonical.
Canonical signatures are important to protect against signature morphing
attacks.
@param vSig the signature data
@param sigLen the length of the signature
@param strict_param whether to enforce strictly canonical semantics
@note For more details please see:
https://ripple.com/wiki/Transaction_Malleability
https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c
*/
bool isCanonicalECDSASig (void const* vSig, size_t sigLen, ECDSA strict_param)
{
// Make sure signature is canonical
// To protect against signature morphing attacks
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// and https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c
// Signature should be:
// The format of a signature should be:
// <30> <len> [ <02> <lenR> <R> ] [ <02> <lenS> <S> ]
const bool strict = strict_param == ECDSA::strict;
unsigned char const* sig = reinterpret_cast<unsigned char const*> (vSig);
if ((sigLen < 10) || (sigLen > 74))
if ((sigLen < 10) || (sigLen > 72))
return false;
if ((sig[0] != 0x30) || (sig[1] != (sigLen - 2)))
return false;
// The first two bytes are verified. Eat them.
sig += 2;
sigLen -= 2;
// Find R and check its length
int rPos = 4, rLen = sig[3];
if ((rLen < 2) || ((rLen + 6) > sigLen))
// Verify the R signature
detail::SignaturePart sigR (sig, sigLen);
if (!sigR.valid ())
return false;
// Find S and check its length
int sPos = rLen + 6, sLen = sig [rLen + 5];
if ((sLen < 2) || ((rLen + sLen + 6) != sigLen))
// Eat the number of bytes we consumed
sig += sigR.skip ();
sigLen -= sigR.skip ();
// Verify the S signature
detail::SignaturePart sigS (sig, sigLen);
if (!sigS.valid ())
return false;
// Eat the number of bytes we consumed
sig += sigS.skip ();
sigLen -= sigS.skip ();
// Nothing should remain at this point.
if (sigLen != 0)
return false;
if ((sig[rPos - 2] != 0x02) || (sig[sPos - 2] != 0x02))
return false; // R or S have wrong type
// Check whether R or S are greater than the modulus.
auto bnR (sigR.getBigNum ());
auto bnS (sigS.getBigNum ());
if ((sig[rPos] & 0x80) != 0)
return false; // R is negative
if (BN_cmp (bnR, detail::modulus) != -1)
return false;
if ((sig[rPos] == 0) && ((sig[rPos + 1] & 0x80) == 0))
return false; // R is padded
if (BN_cmp (bnS, detail::modulus) != -1)
return false;
if ((sig[sPos] & 0x80) != 0)
return false; // S is negative
// For a given signature, (R,S), the signature (R, N-S) is also valid. For
// a signature to be fully-canonical, the smaller of these two values must
// be specified. If operating in strict mode, check that as well.
if (strict_param == ECDSA::strict)
{
detail::BigNum mS;
if ((sig[sPos] == 0) && ((sig[sPos + 1] & 0x80) == 0))
return false; // S is padded
if (BN_sub (mS, detail::modulus, bnS) == 0)
return false;
detail::BigNum bnR (&sig[rPos], rLen);
detail::BigNum bnS (&sig[sPos], sLen);
if ((BN_cmp (bnR, detail::modulus) != -1) || (BN_cmp (bnS, detail::modulus) != -1))
return false; // R or S greater than modulus
if (BN_cmp (bnS, mS) == 1)
return false;
}
if (!strict)
return true;
detail::BigNum mS;
BN_sub (mS, detail::modulus, bnS);
return BN_cmp (bnS, mS) != 1;
return true;
}
// Returns true if original signature was alread canonical
/** Convert a signature into strictly canonical form.
Given the signature (R, S) then (R, G-S) is also valid. For a signature
to be canonical, the smaller of { S, G-S } must be specified.
@param vSig the signature we wish to convert
@param sigLen the length of the signature
@returns true if the signature was already canonical, false otherwise
*/
bool makeCanonicalECDSASig (void* vSig, size_t& sigLen)
{
// Signature is (r,s) where 0 < s < g
// If (g-s)<g, replace signature with (r,g-s)
unsigned char * sig = reinterpret_cast<unsigned char *> (vSig);
bool ret = false;
@@ -201,92 +304,280 @@ void hex_to_binary (FwdIter first, FwdIter last, Container& out)
class ECDSACanonical_test : public beast::unit_test::suite
{
public:
bool isCanonical (std::string const& hex)
Blob loadSignature (std::string const& hex)
{
Blob j;
hex_to_binary (hex.begin(), hex.end(), j);
Blob b;
hex_to_binary (hex.begin (), hex.end (), b);
return b;
}
/** Verifies that a signature is valid.
Valid signatures may not be canonical
*/
bool isValid (std::string const& hex)
{
Blob j (loadSignature(hex));
return isCanonicalECDSASig (&j[0], j.size(), ECDSA::not_strict);
}
void run ()
/** Verifies that a signature is canonical.
Canonical signatures are valid.
*/
bool isStrictlyCanonical (std::string const& hex)
{
expect (isCanonical("304402203932c892e2e550f3af8ee4ce9c215a87f9bb"
"831dcac87b2838e2c2eaa891df0c022030b61dd36543125d56b9f9f3a1f"
"53189e5af33cdda8d77a5209aec03978fa001"), "canonical signature");
Blob j (loadSignature(hex));
return isCanonicalECDSASig (&j[0], j.size (), ECDSA::strict);
}
expect (isCanonical("30450220076045be6f9eca28ff1ec606b833d0b87e70b"
"2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688"
"eca1d2ba7f6b180620eaa03488e6585db6ba01"), "canonical signature");
/** Verify that we correctly identify strictly canonical signatures */
void testStrictlyCanonicalSignatures ()
{
testcase ("Strictly canonical signature checks", abort_on_fail);
expect (isCanonical("3046022100876045be6f9eca28ff1ec606b833d0b87e7"
"0b2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688c"
"2eca1d2ba7f6b180620eaa03488e6585db6ba"), "canonical signature");
expect (isStrictlyCanonical("3045"
"022100FF478110D1D4294471EC76E0157540C2181F47DEBD25D7F9E7DDCCCD47EEE905"
"0220078F07CDAE6C240855D084AD91D1479609533C147C93B0AEF19BC9724D003F28"),
"Strictly canonical signature");
expect (isStrictlyCanonical("3045"
"0221009218248292F1762D8A51BE80F8A7F2CD288D810CE781D5955700DA1684DF1D2D"
"022041A1EE1746BFD72C9760CC93A7AAA8047D52C8833A03A20EAAE92EA19717B454"),
"Strictly canonical signature");
expect (isStrictlyCanonical("3044"
"02206A9E43775F73B6D1EC420E4DDD222A80D4C6DF5D1BEECC431A91B63C928B7581"
"022023E9CC2D61DDA6F73EAA6BCB12688BEB0F434769276B3127E4044ED895C9D96B"),
"Strictly canonical signature");
expect (!isCanonical("3005" "0201FF" "0200"), "tooshort");
expect (isStrictlyCanonical("3044"
"022056E720007221F3CD4EFBB6352741D8E5A0968D48D8D032C2FBC4F6304AD1D04E"
"02201F39EB392C20D7801C3E8D81D487E742FA84A1665E923225BD6323847C71879F"),
"Strictly canonical signature");
expect (!isCanonical("3047"
expect (isStrictlyCanonical("3045"
"022100FDFD5AD05518CEA0017A2DCB5C4DF61E7C73B6D3A38E7AE93210A1564E8C2F12"
"0220214FF061CCC123C81D0BB9D0EDEA04CD40D96BF1425D311DA62A7096BB18EA18"),
"Strictly canonical signature");
// These are canonical signatures, but *not* strictly canonical.
expect (!isStrictlyCanonical ("3046"
"022100F477B3FA6F31C7CB3A0D1AD94A231FDD24B8D78862EE334CEA7CD08F6CBC0A1B"
"022100928E6BCF1ED2684679730C5414AEC48FD62282B090041C41453C1D064AF597A1"),
"Not strictly canonical signature");
expect (!isStrictlyCanonical ("3045"
"022063E7C7CA93CB2400E413A342C027D00665F8BAB9C22EF0A7B8AE3AAF092230B6"
"0221008F2E8BB7D09521ABBC277717B14B93170AE6465C5A1B36561099319C4BEB254C"),
"Not strictly canonical signature");
expect (!isStrictlyCanonical ("3046"
"02210099DCA1188663DDEA506A06A7B20C2B7D8C26AFF41DECE69D6C5F7C967D32625F"
"022100897658A6B1F9EEE5D140D7A332DA0BD73BB98974EA53F6201B01C1B594F286EA"),
"Not strictly canonical signature");
expect (!isStrictlyCanonical ("3045"
"02200855DE366E4E323AA2CE2A25674401A7D11F72EC432770D07F7B57DF7387AEC0"
"022100DA4C6ADDEA14888858DE2AC5B91ED9050D6972BB388DEF582628CEE32869AE35"),
"Not strictly canonical signature");
}
/** Verify that we correctly identify valid signatures */
void testValidSignatures ()
{
testcase ("Canonical signature checks", abort_on_fail);
expect (isValid ("3044"
"02203932c892e2e550f3af8ee4ce9c215a87f9bb831dcac87b2838e2c2eaa891df0c"
"022030b61dd36543125d56b9f9f3a1f53189e5af33cdda8d77a5209aec03978fa001"),
"Canonical signature");
expect (isValid ("3045"
"0220076045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a"
"0221008fffd599910eefe00bc803c688eca1d2ba7f6b180620eaa03488e6585db6ba01"),
"Canonical signature");
expect (isValid("3046"
"022100876045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a"
"0221008fffd599910eefe00bc803c688c2eca1d2ba7f6b180620eaa03488e6585db6ba"),
"Canonical signature");
expect (isStrictlyCanonical("3045"
"022100FF478110D1D4294471EC76E0157540C2181F47DEBD25D7F9E7DDCCCD47EEE905"
"0220078F07CDAE6C240855D084AD91D1479609533C147C93B0AEF19BC9724D003F28"),
"Canonical signature");
expect (isStrictlyCanonical("3045"
"0221009218248292F1762D8A51BE80F8A7F2CD288D810CE781D5955700DA1684DF1D2D"
"022041A1EE1746BFD72C9760CC93A7AAA8047D52C8833A03A20EAAE92EA19717B454"),
"Canonical signature");
expect (isStrictlyCanonical("3044"
"02206A9E43775F73B6D1EC420E4DDD222A80D4C6DF5D1BEECC431A91B63C928B7581"
"022023E9CC2D61DDA6F73EAA6BCB12688BEB0F434769276B3127E4044ED895C9D96B"),
"Canonical signature");
expect (isStrictlyCanonical("3044"
"022056E720007221F3CD4EFBB6352741D8E5A0968D48D8D032C2FBC4F6304AD1D04E"
"02201F39EB392C20D7801C3E8D81D487E742FA84A1665E923225BD6323847C71879F"),
"Canonical signature");
expect (isStrictlyCanonical("3045"
"022100FDFD5AD05518CEA0017A2DCB5C4DF61E7C73B6D3A38E7AE93210A1564E8C2F12"
"0220214FF061CCC123C81D0BB9D0EDEA04CD40D96BF1425D311DA62A7096BB18EA18"),
"Canonical signature");
}
/** Verify that we correctly identify malformed or invalid signatures */
void testMalformedSignatures ()
{
testcase ("Non-canonical signature checks", abort_on_fail);
expect (!isValid("3005"
"0201FF"
"0200"),
"tooshort");
expect (!isValid("3047"
"0221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"022200002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"toolong");
expect (!isCanonical("3144"
expect (!isValid("3144"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"type");
expect (!isCanonical("3045"
expect (!isValid("3045"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"totallength");
expect (!isCanonical(
"301F" "01205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1"),
expect (!isValid("301F"
"01205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1"),
"Slenoob");
expect (!isCanonical("3045"
expect (!isValid("3045"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed00"),
"R+S");
expect (!isCanonical("3044"
expect (!isValid("3044"
"01205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"Rtype");
expect (!isCanonical("3024" "0200"
expect (!isValid("3024"
"0200"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"Rlen=0");
expect (!isCanonical("3044"
expect (!isValid("3044"
"02208990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"R<0");
expect (!isCanonical("3045"
expect (!isValid("3045"
"0221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"02202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"Rpadded");
expect (!isCanonical("3044"
expect (!isValid("3044"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105012"
"02d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"Stype");
expect (!isCanonical("3024"
expect (!isValid("3024"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"0200"),
"Slen=0");
expect (!isCanonical("3044"
expect (!isValid("3044"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"0220fd5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"S<0");
expect (!isCanonical("3045"
expect (!isValid("3045"
"02205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105"
"0221002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed"),
"Spadded");
}
void convertNonCanonical(std::string const& hex, std::string const& canonHex)
{
Blob b (loadSignature(hex));
// The signature ought to at least be valid before we begin.
expect (isValid (hex), "invalid signature");
size_t len = b.size ();
expect (!makeCanonicalECDSASig (&b[0], len),
"non-canonical signature was already canonical");
expect (b.size () >= len,
"canonicalized signature length longer than non-canonical");
b.resize (len);
expect (isCanonicalECDSASig (&b[0], len, ECDSA::strict),
"canonicalized signature is not strictly canonical");
Blob canonicalForm (loadSignature (canonHex));
expect (b.size () == canonicalForm.size (),
"canonicalized signature doesn't have the expected length");
expect (std::equal (b.begin (), b.end (), canonicalForm.begin ()),
"canonicalized signature isn't what we expected");
}
/** Verifies correctness of non-canonical to canonical conversion */
void testCanonicalConversions()
{
testcase ("Non-canonical signature canonicalization", abort_on_fail);
convertNonCanonical (
"3046"
"022100F477B3FA6F31C7CB3A0D1AD94A231FDD24B8D78862EE334CEA7CD08F6CBC0A1B"
"022100928E6BCF1ED2684679730C5414AEC48FD62282B090041C41453C1D064AF597A1",
"3045"
"022100F477B3FA6F31C7CB3A0D1AD94A231FDD24B8D78862EE334CEA7CD08F6CBC0A1B"
"02206D719430E12D97B9868CF3ABEB513B6EE48C5A361F4483FA7A9641868540A9A0");
convertNonCanonical (
"3045"
"022063E7C7CA93CB2400E413A342C027D00665F8BAB9C22EF0A7B8AE3AAF092230B6"
"0221008F2E8BB7D09521ABBC277717B14B93170AE6465C5A1B36561099319C4BEB254C",
"3044"
"022063E7C7CA93CB2400E413A342C027D00665F8BAB9C22EF0A7B8AE3AAF092230B6"
"022070D174482F6ADE5443D888E84EB46CE7AFC8968A552D69E5AF392CF0844B1BF5");
convertNonCanonical (
"3046"
"02210099DCA1188663DDEA506A06A7B20C2B7D8C26AFF41DECE69D6C5F7C967D32625F"
"022100897658A6B1F9EEE5D140D7A332DA0BD73BB98974EA53F6201B01C1B594F286EA",
"3045"
"02210099DCA1188663DDEA506A06A7B20C2B7D8C26AFF41DECE69D6C5F7C967D32625F"
"02207689A7594E06111A2EBF285CCD25F4277EF55371C4F4AA1BA4D09CD73B43BA57");
convertNonCanonical (
"3045"
"02200855DE366E4E323AA2CE2A25674401A7D11F72EC432770D07F7B57DF7387AEC0"
"022100DA4C6ADDEA14888858DE2AC5B91ED9050D6972BB388DEF582628CEE32869AE35",
"3044"
"02200855DE366E4E323AA2CE2A25674401A7D11F72EC432770D07F7B57DF7387AEC0"
"022025B3952215EB7777A721D53A46E126F9AD456A2B76BAB0E399A98FA9A7CC930C");
}
void run ()
{
testValidSignatures ();
testStrictlyCanonicalSignatures ();
testMalformedSignatures ();
testCanonicalConversions ();
}
};