diff --git a/ecdh.go b/ecdh.go index 61160e3..c3512fb 100644 --- a/ecdh.go +++ b/ecdh.go @@ -1,26 +1,26 @@ package jose import ( - "errors" - "crypto/ecdsa" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" + "errors" "fmt" - "math/big" - "github.com/dvsekhvalnov/jose2go/base64url" - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go/kdf" + "github.com/dvsekhvalnov/jose2go/arrays" + "github.com/dvsekhvalnov/jose2go/base64url" + "github.com/dvsekhvalnov/jose2go/kdf" + "github.com/dvsekhvalnov/jose2go/keys/ecc" "github.com/dvsekhvalnov/jose2go/padding" + "math/big" ) func init() { - RegisterJwa(&Ecdh{directAgreement:true}) + RegisterJwa(&Ecdh{directAgreement: true}) } // Elliptic curve Diffie–Hellman key management (key agreement) algorithm implementation -type Ecdh struct{ +type Ecdh struct { directAgreement bool } @@ -29,123 +29,129 @@ func (alg *Ecdh) Name() string { } func (alg *Ecdh) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - - if pubKey,ok := key.(*ecdsa.PublicKey);ok { - if _,ok := header[alg.idHeader()].(string);!ok { - return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.",alg.idHeader())) + if pubKey, ok := key.(*ecdsa.PublicKey); ok { + + if _, ok := header[alg.idHeader()].(string); !ok { + return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) } var d []byte var x, y *big.Int - if d, x, y, err=elliptic.GenerateKey(pubKey.Curve,rand.Reader); err!=nil { - return nil,nil,err + if d, x, y, err = elliptic.GenerateKey(pubKey.Curve, rand.Reader); err != nil { + return nil, nil, err } - ephemeral:=ecc.NewPrivate(x.Bytes(),y.Bytes(),d) + ephemeral := ecc.NewPrivate(x.Bytes(), y.Bytes(), d) - xBytes:=padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize) - yBytes:=padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize) + xBytes := padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize) + yBytes := padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize) - epk:= map[string]string { + epk := map[string]string{ "kty": "EC", - "x": base64url.Encode(xBytes), - "y": base64url.Encode(yBytes), + "x": base64url.Encode(xBytes), + "y": base64url.Encode(yBytes), "crv": name(pubKey.Curve), } - header["epk"]=epk + header["epk"] = epk - return alg.deriveKey(pubKey,ephemeral,cekSizeBits,header),nil,nil + return alg.deriveKey(pubKey, ephemeral, cekSizeBits, header), nil, nil } - return nil,nil,errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'") + return nil, nil, errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'") } func (alg *Ecdh) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - if privKey,ok := key.(*ecdsa.PrivateKey);ok { - + if privKey, ok := key.(*ecdsa.PrivateKey); ok { + var epk map[string]interface{} - - if epk,ok = header["epk"].(map[string]interface{});!ok { - return nil,errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.") + + if epk, ok = header["epk"].(map[string]interface{}); !ok { + return nil, errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.") } - - if _,ok := header[alg.idHeader()].(string);!ok { - return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.",alg.idHeader())) + + if _, ok := header[alg.idHeader()].(string); !ok { + return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) } - - var x,y,crv string + + var x, y, crv string var xBytes, yBytes []byte - - if x,ok=epk["x"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.") + + if x, ok = epk["x"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.") } - - if y,ok=epk["y"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.") + + if y, ok = epk["y"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.") } - - if crv,ok=epk["crv"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.") + + if crv, ok = epk["crv"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.") } - - if crv!="P-256" && crv!="P-384" && crv!="P-521" { - return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v",crv)) + + if crv != "P-256" && crv != "P-384" && crv != "P-521" { + return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v", crv)) } - - if xBytes,err=base64url.Decode(x);err!=nil { - return nil,err + + if xBytes, err = base64url.Decode(x); err != nil { + return nil, err } - if yBytes,err=base64url.Decode(y);err!=nil { - return nil,err - } - - pubKey := ecc.NewPublic(xBytes,yBytes) - - return alg.deriveKey(pubKey,privKey,cekSizeBits,header),nil + if yBytes, err = base64url.Decode(y); err != nil { + return nil, err + } + + pubKey := ecc.NewPublic(xBytes, yBytes) + + if !privKey.Curve.IsOnCurve(pubKey.X, pubKey.Y) { + return nil, errors.New(fmt.Sprintf("Ephemeral public key received in header is invalid for reciever's private key.")) + } + + return alg.deriveKey(pubKey, privKey, cekSizeBits, header), nil } - return nil,errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'") + return nil, errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'") } func (alg *Ecdh) deriveKey(pubKey *ecdsa.PublicKey, privKey *ecdsa.PrivateKey, keySizeBits int, header map[string]interface{}) []byte { - - var enc,apv,apu []byte + + var enc, apv, apu []byte var err error - - enc=[]byte(header[alg.idHeader()].(string)) - - if a,ok:=header["apv"].(string);!ok { - if apv,err=base64url.Decode(a);err!=nil { + + enc = []byte(header[alg.idHeader()].(string)) + + if a, ok := header["apv"].(string); !ok { + if apv, err = base64url.Decode(a); err != nil { apv = nil } } - if a,ok:=header["apu"].(string);!ok { - if apu,err=base64url.Decode(a);err!=nil { + if a, ok := header["apu"].(string); !ok { + if apu, err = base64url.Decode(a); err != nil { apu = nil } - } - - z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes()) - zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize) - - return kdf.DeriveConcatKDF(keySizeBits,zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv),arrays.UInt32ToBytes(uint32(keySizeBits)),nil,sha256.New()) + } + + z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes()) + zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize) + + return kdf.DeriveConcatKDF(keySizeBits, zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv), arrays.UInt32ToBytes(uint32(keySizeBits)), nil, sha256.New()) } -func(alg *Ecdh) idHeader() string { - if alg.directAgreement { return "enc" } - +func (alg *Ecdh) idHeader() string { + if alg.directAgreement { + return "enc" + } + return "alg" } func name(curve elliptic.Curve) string { - return fmt.Sprintf("P-%v",curve.Params().BitSize) + return fmt.Sprintf("P-%v", curve.Params().BitSize) } func prependDatalen(bytes []byte) []byte { - return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))),bytes) + return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))), bytes) } diff --git a/sec_test/security_vulnerabilities_test.go b/sec_test/security_vulnerabilities_test.go new file mode 100644 index 0000000..eaf1f6f --- /dev/null +++ b/sec_test/security_vulnerabilities_test.go @@ -0,0 +1,51 @@ +package sec_test + +import ( + "crypto/ecdsa" + "fmt" + "github.com/dvsekhvalnov/jose2go" + "github.com/dvsekhvalnov/jose2go/keys/ecc" + . "gopkg.in/check.v1" + "testing" +) + +func Test(t *testing.T) { TestingT(t) } + +type SecurityTestSuite struct{} + +var _ = Suite(&SecurityTestSuite{}) + +func (s *SecurityTestSuite) Test_InvalidCurve(c *C) { + // https://www.cs.bris.ac.uk/Research/CryptographySecurity/RWC/2017/nguyen.quan.pdf + // Attack exploits some ECDH implementations which do not check + // that ephemeral public key is on the private key's curve. + + //given + //JWT encrypted with attacker private key, which is equals to (reciever_pk mod 113) + attackMod113 := "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiZ1RsaTY1ZVRRN3otQmgxNDdmZjhLM203azJVaURpRzJMcFlrV0FhRkpDYyIsInkiOiJjTEFuakthNGJ6akQ3REpWUHdhOUVQclJ6TUc3ck9OZ3NpVUQta2YzMEZzIiwiY3J2IjoiUC0yNTYifX0.qGAdxtEnrV_3zbIxU2ZKrMWcejNltjA_dtefBFnRh9A2z9cNIqYRWg.pEA5kX304PMCOmFSKX_cEg.a9fwUrx2JXi1OnWEMOmZhXd94-bEGCH9xxRwqcGuG2AMo-AwHoljdsH5C_kcTqlXS5p51OB1tvgQcMwB5rpTxg.72CHiYFecyDvuUa43KKT6w" + + //JWT encrypted with attacker private key, which is equals to (reciever_pk mod 2447) + attackMod2447 := "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiWE9YR1E5XzZRQ3ZCZzN1OHZDSS1VZEJ2SUNBRWNOTkJyZnFkN3RHN29RNCIsInkiOiJoUW9XTm90bk56S2x3aUNuZUprTElxRG5UTnc3SXNkQkM1M1ZVcVZqVkpjIiwiY3J2IjoiUC0yNTYifX0.UGb3hX3ePAvtFB9TCdWsNkFTv9QWxSr3MpYNiSBdW630uRXRBT3sxw.6VpU84oMob16DxOR98YTRw.y1UslvtkoWdl9HpugfP0rSAkTw1xhm_LbK1iRXzGdpYqNwIG5VU33UBpKAtKFBoA1Kk_sYtfnHYAvn-aes4FTg.UZPN8h7FcvA5MIOq-Pkj8A" + + //when + test, _, err := jose.Decode(attackMod113, Ecc256()) + + //then + c.Assert(err, NotNil) + fmt.Printf("\nerr= %v\n", err) + c.Assert(test, Equals, "") + + //when + test, _, err = jose.Decode(attackMod2447, Ecc256()) + + //then + c.Assert(err, NotNil) + fmt.Printf("\nerr= %v\n", err) + c.Assert(test, Equals, "") +} + +func Ecc256() *ecdsa.PrivateKey { + return ecc.NewPrivate([]byte{193, 227, 73, 203, 97, 236, 112, 36, 140, 232, 1, 3, 76, 56, 52, 225, 184, 142, 190, 17, 97, 203, 37, 175, 56, 116, 31, 120, 95, 207, 196, 196}, + []byte{123, 201, 103, 8, 239, 128, 149, 43, 83, 248, 210, 85, 95, 231, 43, 132, 30, 208, 69, 136, 98, 139, 29, 55, 138, 89, 73, 57, 80, 14, 201, 201}, + []byte{84, 73, 131, 102, 144, 215, 92, 175, 41, 240, 221, 2, 157, 219, 49, 179, 221, 184, 171, 169, 210, 213, 21, 197, 1, 36, 101, 232, 23, 212, 169, 220}) +}