diff --git a/src/ripple/sslutil/impl/ECDSACanonical.cpp b/src/ripple/sslutil/impl/ECDSACanonical.cpp index 9baab4fd3..68b344242 100644 --- a/src/ripple/sslutil/impl/ECDSACanonical.cpp +++ b/src/ripple/sslutil/impl/ECDSACanonical.cpp @@ -18,6 +18,8 @@ //============================================================================== #include "../../../beast/beast/unit_test/suite.h" +#include +#include 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> + 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> [ <02> ] [ <02> ] - const bool strict = strict_param == ECDSA::strict; - unsigned char const* sig = reinterpret_cast (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) (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 (); } };