Skip to content

Commit eb4f295

Browse files
FiloSottilegopherbot
authored andcommitted
internal/wycheproof: add ECDH tests, including point decompression
Fixes golang/go#38936 Change-Id: I231d30fcc683abd9efb36b6fd9cc05f599078ade Reviewed-on: https://go-review.googlesource.com/c/crypto/+/396174 Run-TryBot: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Filippo Valsorda <valsorda@google.com> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org>
1 parent 7b82a4e commit eb4f295

File tree

4 files changed

+190
-156
lines changed

4 files changed

+190
-156
lines changed

internal/wycheproof/ecdh_test.go

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package wycheproof
6+
7+
import (
8+
"bytes"
9+
"crypto/ecdsa"
10+
"crypto/elliptic"
11+
"crypto/x509"
12+
"encoding/asn1"
13+
"errors"
14+
"fmt"
15+
"testing"
16+
17+
"golang.org/x/crypto/cryptobyte"
18+
casn1 "golang.org/x/crypto/cryptobyte/asn1"
19+
)
20+
21+
func TestECDH(t *testing.T) {
22+
type ECDHTestVector struct {
23+
// A brief description of the test case
24+
Comment string `json:"comment,omitempty"`
25+
// A list of flags
26+
Flags []string `json:"flags,omitempty"`
27+
// the private key
28+
Private string `json:"private,omitempty"`
29+
// Encoded public key
30+
Public string `json:"public,omitempty"`
31+
// Test result
32+
Result string `json:"result,omitempty"`
33+
// The shared secret key
34+
Shared string `json:"shared,omitempty"`
35+
// Identifier of the test case
36+
TcID int `json:"tcId,omitempty"`
37+
}
38+
39+
type ECDHTestGroup struct {
40+
Curve string `json:"curve,omitempty"`
41+
Tests []*ECDHTestVector `json:"tests,omitempty"`
42+
}
43+
44+
type Root struct {
45+
TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
46+
}
47+
48+
flagsShouldPass := map[string]bool{
49+
// ParsePKIXPublicKey doesn't support compressed points, but we test
50+
// them against UnmarshalCompressed anyway.
51+
"CompressedPoint": true,
52+
// We don't support decoding custom curves.
53+
"UnnamedCurve": false,
54+
// WrongOrder and UnusedParam are only found with UnnamedCurve.
55+
"WrongOrder": false,
56+
"UnusedParam": false,
57+
}
58+
59+
// supportedCurves is a map of all elliptic curves supported
60+
// by crypto/elliptic, which can subsequently be parsed and tested.
61+
supportedCurves := map[string]bool{
62+
"secp224r1": true,
63+
"secp256r1": true,
64+
"secp384r1": true,
65+
"secp521r1": true,
66+
}
67+
68+
var root Root
69+
readTestVector(t, "ecdh_test.json", &root)
70+
for _, tg := range root.TestGroups {
71+
if !supportedCurves[tg.Curve] {
72+
continue
73+
}
74+
for _, tt := range tg.Tests {
75+
tg, tt := tg, tt
76+
t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
77+
t.Logf("Type: %v", tt.Result)
78+
t.Logf("Flags: %q", tt.Flags)
79+
t.Log(tt.Comment)
80+
81+
shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
82+
83+
p := decodeHex(tt.Public)
84+
pp, err := x509.ParsePKIXPublicKey(p)
85+
if err != nil {
86+
pp, err = decodeCompressedPKIX(p)
87+
}
88+
if err != nil {
89+
if shouldPass {
90+
t.Errorf("unexpected parsing error: %s", err)
91+
}
92+
return
93+
}
94+
pub := pp.(*ecdsa.PublicKey)
95+
96+
priv := decodeHex(tt.Private)
97+
shared := decodeHex(tt.Shared)
98+
99+
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, priv)
100+
xBytes := make([]byte, (pub.Curve.Params().BitSize+7)/8)
101+
got := bytes.Equal(shared, x.FillBytes(xBytes))
102+
103+
if want := shouldPass; got != want {
104+
t.Errorf("wanted success %v, got %v", want, got)
105+
}
106+
})
107+
}
108+
}
109+
}
110+
111+
func decodeCompressedPKIX(der []byte) (interface{}, error) {
112+
s := cryptobyte.String(der)
113+
var s1, s2 cryptobyte.String
114+
var algoOID, namedCurveOID asn1.ObjectIdentifier
115+
var pointDER []byte
116+
if !s.ReadASN1(&s1, casn1.SEQUENCE) || !s.Empty() ||
117+
!s1.ReadASN1(&s2, casn1.SEQUENCE) ||
118+
!s2.ReadASN1ObjectIdentifier(&algoOID) ||
119+
!s2.ReadASN1ObjectIdentifier(&namedCurveOID) || !s2.Empty() ||
120+
!s1.ReadASN1BitStringAsBytes(&pointDER) || !s1.Empty() {
121+
return nil, errors.New("failed to parse PKIX structure")
122+
}
123+
124+
if !algoOID.Equal(oidPublicKeyECDSA) {
125+
return nil, errors.New("wrong algorithm OID")
126+
}
127+
namedCurve := namedCurveFromOID(namedCurveOID)
128+
if namedCurve == nil {
129+
return nil, errors.New("unsupported elliptic curve")
130+
}
131+
x, y := elliptic.UnmarshalCompressed(namedCurve, pointDER)
132+
if x == nil {
133+
return nil, errors.New("failed to unmarshal elliptic curve point")
134+
}
135+
pub := &ecdsa.PublicKey{
136+
Curve: namedCurve,
137+
X: x,
138+
Y: y,
139+
}
140+
return pub, nil
141+
}
142+
143+
var (
144+
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
145+
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
146+
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
147+
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
148+
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
149+
)
150+
151+
func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
152+
switch {
153+
case oid.Equal(oidNamedCurveP224):
154+
return elliptic.P224()
155+
case oid.Equal(oidNamedCurveP256):
156+
return elliptic.P256()
157+
case oid.Equal(oidNamedCurveP384):
158+
return elliptic.P384()
159+
case oid.Equal(oidNamedCurveP521):
160+
return elliptic.P521()
161+
}
162+
return nil
163+
}

internal/wycheproof/ecdsa_compat_test.go

-34
This file was deleted.

internal/wycheproof/ecdsa_go115_test.go

-16
This file was deleted.

internal/wycheproof/ecdsa_test.go

+27-106
Original file line numberDiff line numberDiff line change
@@ -9,126 +9,47 @@ import (
99
"testing"
1010
)
1111

12-
func TestEcdsa(t *testing.T) {
13-
// AsnSignatureTestVector
14-
type AsnSignatureTestVector struct {
15-
12+
func TestECDSA(t *testing.T) {
13+
type ASNSignatureTestVector struct {
1614
// A brief description of the test case
17-
Comment string `json:"comment,omitempty"`
18-
15+
Comment string `json:"comment"`
1916
// A list of flags
20-
Flags []string `json:"flags,omitempty"`
21-
17+
Flags []string `json:"flags"`
2218
// The message to sign
23-
Msg string `json:"msg,omitempty"`
24-
19+
Msg string `json:"msg"`
2520
// Test result
26-
Result string `json:"result,omitempty"`
27-
28-
// An ASN encoded signature for msg
29-
Sig string `json:"sig,omitempty"`
30-
21+
Result string `json:"result"`
22+
// An ASN.1 encoded signature for msg
23+
Sig string `json:"sig"`
3124
// Identifier of the test case
32-
TcId int `json:"tcId,omitempty"`
25+
TcID int `json:"tcId"`
3326
}
3427

35-
// EcPublicKey
36-
type EcPublicKey struct {
37-
38-
// the EC group used by this public key
39-
Curve interface{} `json:"curve,omitempty"`
40-
41-
// the key size in bits
42-
KeySize int `json:"keySize,omitempty"`
43-
44-
// the key type
45-
Type string `json:"type,omitempty"`
46-
47-
// encoded public key point
48-
Uncompressed string `json:"uncompressed,omitempty"`
49-
50-
// the x-coordinate of the public key point
51-
Wx string `json:"wx,omitempty"`
52-
53-
// the y-coordinate of the public key point
54-
Wy string `json:"wy,omitempty"`
28+
type ECPublicKey struct {
29+
// The EC group used by this public key
30+
Curve interface{} `json:"curve"`
5531
}
5632

57-
// EcUnnamedGroup
58-
type EcUnnamedGroup struct {
59-
60-
// coefficient a of the elliptic curve equation
61-
A string `json:"a,omitempty"`
62-
63-
// coefficient b of the elliptic curve equation
64-
B string `json:"b,omitempty"`
65-
66-
// the x-coordinate of the generator
67-
Gx string `json:"gx,omitempty"`
68-
69-
// the y-coordinate of the generator
70-
Gy string `json:"gy,omitempty"`
71-
72-
// the cofactor
73-
H int `json:"h,omitempty"`
74-
75-
// the order of the generator
76-
N string `json:"n,omitempty"`
77-
78-
// the order of the underlying field
79-
P string `json:"p,omitempty"`
80-
81-
// an unnamed EC group over a prime field in Weierstrass form
82-
Type string `json:"type,omitempty"`
83-
}
84-
85-
// EcdsaTestGroup
86-
type EcdsaTestGroup struct {
87-
88-
// unenocded EC public key
89-
Key *EcPublicKey `json:"key,omitempty"`
90-
33+
type ECDSATestGroup struct {
34+
// Unencoded EC public key
35+
Key *ECPublicKey `json:"key"`
9136
// DER encoded public key
92-
KeyDer string `json:"keyDer,omitempty"`
93-
94-
// Pem encoded public key
95-
KeyPem string `json:"keyPem,omitempty"`
96-
37+
KeyDER string `json:"keyDer"`
9738
// the hash function used for ECDSA
98-
Sha string `json:"sha,omitempty"`
99-
Tests []*AsnSignatureTestVector `json:"tests,omitempty"`
100-
Type interface{} `json:"type,omitempty"`
101-
}
102-
103-
// Notes a description of the labels used in the test vectors
104-
type Notes struct {
39+
SHA string `json:"sha"`
40+
Tests []*ASNSignatureTestVector `json:"tests"`
10541
}
10642

107-
// Root
10843
type Root struct {
109-
110-
// the primitive tested in the test file
111-
Algorithm string `json:"algorithm,omitempty"`
112-
113-
// the version of the test vectors.
114-
GeneratorVersion string `json:"generatorVersion,omitempty"`
115-
116-
// additional documentation
117-
Header []string `json:"header,omitempty"`
118-
119-
// a description of the labels used in the test vectors
120-
Notes *Notes `json:"notes,omitempty"`
121-
122-
// the number of test vectors in this test
123-
NumberOfTests int `json:"numberOfTests,omitempty"`
124-
Schema interface{} `json:"schema,omitempty"`
125-
TestGroups []*EcdsaTestGroup `json:"testGroups,omitempty"`
44+
TestGroups []*ECDSATestGroup `json:"testGroups"`
12645
}
12746

12847
flagsShouldPass := map[string]bool{
129-
// An encoded ASN.1 integer missing a leading zero is invalid, but accepted by some implementations.
48+
// An encoded ASN.1 integer missing a leading zero is invalid, but
49+
// accepted by some implementations.
13050
"MissingZero": false,
131-
// A signature using a weaker hash than the EC params is not a security risk, as long as the hash is secure.
51+
// A signature using a weaker hash than the EC params is not a security
52+
// risk, as long as the hash is secure.
13253
// https://www.imperialviolet.org/2014/05/25/strengthmatching.html
13354
"WeakHash": true,
13455
}
@@ -149,15 +70,15 @@ func TestEcdsa(t *testing.T) {
14970
if !supportedCurves[curve] {
15071
continue
15172
}
152-
pub := decodePublicKey(tg.KeyDer).(*ecdsa.PublicKey)
153-
h := parseHash(tg.Sha).New()
73+
pub := decodePublicKey(tg.KeyDER).(*ecdsa.PublicKey)
74+
h := parseHash(tg.SHA).New()
15475
for _, sig := range tg.Tests {
15576
h.Reset()
15677
h.Write(decodeHex(sig.Msg))
15778
hashed := h.Sum(nil)
158-
got := verifyASN1(pub, hashed, decodeHex(sig.Sig))
79+
got := ecdsa.VerifyASN1(pub, hashed, decodeHex(sig.Sig))
15980
if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
160-
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
81+
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcID, sig.Result, sig.Comment, want)
16182
}
16283
}
16384
}

0 commit comments

Comments
 (0)