Skip to content

Commit

Permalink
fixing possible InvalidCurve attack on NIST curves within ECDH key ma…
Browse files Browse the repository at this point in the history
…nagement
  • Loading branch information
DV committed Feb 16, 2017
1 parent 2a04a55 commit 0c50fb3
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 77 deletions.
160 changes: 83 additions & 77 deletions ecdh.go
Original file line number Diff line number Diff line change
@@ -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
}

Expand All @@ -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)
}
51 changes: 51 additions & 0 deletions sec_test/security_vulnerabilities_test.go
Original file line number Diff line number Diff line change
@@ -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})
}

0 comments on commit 0c50fb3

Please sign in to comment.