Skip to content

Commit 287db5c

Browse files
adecarojimthematrix
authored andcommitted
ECDSA Signature malleability resistance
This change-set introduces ECDSA Signature malleability resistance. ECDSA signatures do not have unique representation and this can facilitate replay attacks and more. In order to have a unique representation, this change-set forses BCCSP to generate and accept only signatures with low-S. Bitcoin has also addressed this issue with the following BIP: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki Before merging this change-set, we need to ensure that client-sdks generates signatures properly in order to avoid massive rejection of transactions. This change-set comes in the context of: https://jira.hyperledger.org/browse/FAB-1276 Change-Id: I83a7a9406ef3551447e1f6540330d3199c0f517e Signed-off-by: Angelo De Caro <[email protected]>
1 parent 0f90df8 commit 287db5c

File tree

3 files changed

+199
-18
lines changed

3 files changed

+199
-18
lines changed

bccsp/sw/ecdsa.go

+96-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,103 @@ limitations under the License.
1515
*/
1616
package sw
1717

18-
import "math/big"
18+
import (
19+
"crypto/ecdsa"
20+
"crypto/elliptic"
21+
"crypto/rand"
22+
"encoding/asn1"
23+
"errors"
24+
"fmt"
25+
"math/big"
26+
27+
"github.com/hyperledger/fabric/bccsp"
28+
)
1929

20-
// ECDSASignature represents an ECDSA signature
2130
type ecdsaSignature struct {
2231
R, S *big.Int
2332
}
33+
34+
var (
35+
// curveHalfOrders contains the precomputed curve group orders halved.
36+
// It is used to ensure that signature' S value is lower or equal to the
37+
// curve group order halved. We accept only low-S signatures.
38+
// They are precomputed for efficiency reasons.
39+
curveHalfOrders map[elliptic.Curve]*big.Int = map[elliptic.Curve]*big.Int{
40+
elliptic.P224(): new(big.Int).Rsh(elliptic.P224().Params().N, 1),
41+
elliptic.P256(): new(big.Int).Rsh(elliptic.P256().Params().N, 1),
42+
elliptic.P384(): new(big.Int).Rsh(elliptic.P384().Params().N, 1),
43+
elliptic.P521(): new(big.Int).Rsh(elliptic.P521().Params().N, 1),
44+
}
45+
)
46+
47+
func marshalECDSASignature(r, s *big.Int) ([]byte, error) {
48+
return asn1.Marshal(ecdsaSignature{r, s})
49+
}
50+
51+
func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
52+
// Unmarshal
53+
sig := new(ecdsaSignature)
54+
_, err := asn1.Unmarshal(raw, sig)
55+
if err != nil {
56+
return nil, nil, fmt.Errorf("Failed unmashalling signature [%s]", err)
57+
}
58+
59+
// Validate sig
60+
if sig.R == nil {
61+
return nil, nil, errors.New("Invalid signature. R must be different from nil.")
62+
}
63+
if sig.S == nil {
64+
return nil, nil, errors.New("Invalid signature. S must be different from nil.")
65+
}
66+
67+
if sig.R.Sign() != 1 {
68+
return nil, nil, errors.New("Invalid signature. R must be larger than zero")
69+
}
70+
if sig.S.Sign() != 1 {
71+
return nil, nil, errors.New("Invalid signature. S must be larger than zero")
72+
}
73+
74+
return sig.R, sig.S, nil
75+
}
76+
77+
func (csp *impl) signECDSA(k *ecdsa.PrivateKey, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
78+
r, s, err := ecdsa.Sign(rand.Reader, k, digest)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// check for low-S
84+
halfOrder, ok := curveHalfOrders[k.Curve]
85+
if !ok {
86+
return nil, fmt.Errorf("Curve not recognized [%s]", k.Curve)
87+
}
88+
89+
// is s > halfOrder Then
90+
if s.Cmp(halfOrder) == 1 {
91+
// Set s to N - s that will be then in the lower part of signature space
92+
// less or equal to half order
93+
s.Sub(k.Params().N, s)
94+
}
95+
96+
return marshalECDSASignature(r, s)
97+
}
98+
99+
func (csp *impl) verifyECDSA(k *ecdsa.PublicKey, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) {
100+
r, s, err := unmarshalECDSASignature(signature)
101+
if err != nil {
102+
return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
103+
}
104+
105+
// check for low-S
106+
halfOrder, ok := curveHalfOrders[k.Curve]
107+
if !ok {
108+
return false, fmt.Errorf("Curve not recognized [%s]", k.Curve)
109+
}
110+
111+
// If s > halfOrder Then
112+
if s.Cmp(halfOrder) == 1 {
113+
return false, fmt.Errorf("Invalid S. Must be smaller than half the order [%s][%s].", s, halfOrder)
114+
}
115+
116+
return ecdsa.Verify(k, digest, r, s), nil
117+
}

bccsp/sw/impl.go

+3-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package sw
1818
import (
1919
"crypto/ecdsa"
2020
"crypto/rand"
21-
"encoding/asn1"
2221
"errors"
2322
"fmt"
2423
"math/big"
@@ -603,7 +602,7 @@ func (csp *impl) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signat
603602
// Check key type
604603
switch k.(type) {
605604
case *ecdsaPrivateKey:
606-
return k.(*ecdsaPrivateKey).privKey.Sign(rand.Reader, digest, nil)
605+
return csp.signECDSA(k.(*ecdsaPrivateKey).privKey, digest, opts)
607606
case *rsaPrivateKey:
608607
if opts == nil {
609608
return nil, errors.New("Invalid options. Nil.")
@@ -631,21 +630,9 @@ func (csp *impl) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.Signer
631630
// Check key type
632631
switch k.(type) {
633632
case *ecdsaPrivateKey:
634-
ecdsaSignature := new(ecdsaSignature)
635-
_, err := asn1.Unmarshal(signature, ecdsaSignature)
636-
if err != nil {
637-
return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
638-
}
639-
640-
return ecdsa.Verify(&(k.(*ecdsaPrivateKey).privKey.PublicKey), digest, ecdsaSignature.R, ecdsaSignature.S), nil
633+
return csp.verifyECDSA(&(k.(*ecdsaPrivateKey).privKey.PublicKey), signature, digest, opts)
641634
case *ecdsaPublicKey:
642-
ecdsaSignature := new(ecdsaSignature)
643-
_, err := asn1.Unmarshal(signature, ecdsaSignature)
644-
if err != nil {
645-
return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
646-
}
647-
648-
return ecdsa.Verify(k.(*ecdsaPublicKey).pubKey, digest, ecdsaSignature.R, ecdsaSignature.S), nil
635+
return csp.verifyECDSA(k.(*ecdsaPublicKey).pubKey, signature, digest, opts)
649636
case *rsaPrivateKey:
650637
if opts == nil {
651638
return false, errors.New("Invalid options. It must not be nil.")

bccsp/sw/impl_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,106 @@ func TestKeyImportFromX509ECDSAPublicKey(t *testing.T) {
10141014
}
10151015
}
10161016

1017+
func TestECDSASignatureEncoding(t *testing.T) {
1018+
v := []byte{0x30, 0x07, 0x02, 0x01, 0x8F, 0x02, 0x02, 0xff, 0xf1}
1019+
_, err := asn1.Unmarshal(v, &ecdsaSignature{})
1020+
if err == nil {
1021+
t.Fatalf("Unmarshalling should fail for [% x]", v)
1022+
}
1023+
t.Logf("Unmarshalling correctly failed for [% x] [%s]", v, err)
1024+
1025+
v = []byte{0x30, 0x07, 0x02, 0x01, 0x8F, 0x02, 0x02, 0x00, 0x01}
1026+
_, err = asn1.Unmarshal(v, &ecdsaSignature{})
1027+
if err == nil {
1028+
t.Fatalf("Unmarshalling should fail for [% x]", v)
1029+
}
1030+
t.Logf("Unmarshalling correctly failed for [% x] [%s]", v, err)
1031+
1032+
v = []byte{0x30, 0x07, 0x02, 0x01, 0x8F, 0x02, 0x81, 0x01, 0x01}
1033+
_, err = asn1.Unmarshal(v, &ecdsaSignature{})
1034+
if err == nil {
1035+
t.Fatalf("Unmarshalling should fail for [% x]", v)
1036+
}
1037+
t.Logf("Unmarshalling correctly failed for [% x] [%s]", v, err)
1038+
1039+
v = []byte{0x30, 0x07, 0x02, 0x01, 0x8F, 0x02, 0x81, 0x01, 0x8F}
1040+
_, err = asn1.Unmarshal(v, &ecdsaSignature{})
1041+
if err == nil {
1042+
t.Fatalf("Unmarshalling should fail for [% x]", v)
1043+
}
1044+
t.Logf("Unmarshalling correctly failed for [% x] [%s]", v, err)
1045+
1046+
v = []byte{0x30, 0x0A, 0x02, 0x01, 0x8F, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x8F}
1047+
_, err = asn1.Unmarshal(v, &ecdsaSignature{})
1048+
if err == nil {
1049+
t.Fatalf("Unmarshalling should fail for [% x]", v)
1050+
}
1051+
t.Logf("Unmarshalling correctly failed for [% x] [%s]", v, err)
1052+
1053+
}
1054+
1055+
func TestECDSALowS(t *testing.T) {
1056+
// Ensure that signature with low-S are generated
1057+
k, err := currentBCCSP.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
1058+
if err != nil {
1059+
t.Fatalf("Failed generating ECDSA key [%s]", err)
1060+
}
1061+
1062+
msg := []byte("Hello World")
1063+
1064+
digest, err := currentBCCSP.Hash(msg, &bccsp.SHAOpts{})
1065+
if err != nil {
1066+
t.Fatalf("Failed computing HASH [%s]", err)
1067+
}
1068+
1069+
signature, err := currentBCCSP.Sign(k, digest, nil)
1070+
if err != nil {
1071+
t.Fatalf("Failed generating ECDSA signature [%s]", err)
1072+
}
1073+
1074+
R, S, err := unmarshalECDSASignature(signature)
1075+
if err != nil {
1076+
t.Fatalf("Failed unmarshalling signature [%s]", err)
1077+
}
1078+
1079+
if S.Cmp(curveHalfOrders[k.(*ecdsaPrivateKey).privKey.Curve]) >= 0 {
1080+
t.Fatal("Invalid signature. It must have low-S")
1081+
}
1082+
1083+
valid, err := currentBCCSP.Verify(k, signature, digest, nil)
1084+
if err != nil {
1085+
t.Fatalf("Failed verifying ECDSA signature [%s]", err)
1086+
}
1087+
if !valid {
1088+
t.Fatal("Failed verifying ECDSA signature. Signature not valid.")
1089+
}
1090+
1091+
// Ensure that signature with high-S are rejected.
1092+
for {
1093+
R, S, err = ecdsa.Sign(rand.Reader, k.(*ecdsaPrivateKey).privKey, digest)
1094+
if err != nil {
1095+
t.Fatalf("Failed generating signature [%s]", err)
1096+
}
1097+
1098+
if S.Cmp(curveHalfOrders[k.(*ecdsaPrivateKey).privKey.Curve]) > 0 {
1099+
break
1100+
}
1101+
}
1102+
1103+
sig, err := marshalECDSASignature(R, S)
1104+
if err != nil {
1105+
t.Fatalf("Failing unmarshalling signature [%s]", err)
1106+
}
1107+
1108+
valid, err = currentBCCSP.Verify(k, sig, digest, nil)
1109+
if err == nil {
1110+
t.Fatal("Failed verifying ECDSA signature. It must fail for a signature with high-S")
1111+
}
1112+
if valid {
1113+
t.Fatal("Failed verifying ECDSA signature. It must fail for a signature with high-S")
1114+
}
1115+
}
1116+
10171117
func TestAESKeyGen(t *testing.T) {
10181118

10191119
k, err := currentBCCSP.KeyGen(&bccsp.AESKeyGenOpts{Temporary: false})

0 commit comments

Comments
 (0)