Skip to content

Commit 5c9bd49

Browse files
committed
crypto/rsa,crypto/ecdsa,crypto/ed25519: implement PublicKey.Equal
This makes all modern public keys in the standard library implement a common interface (below) that can be used by applications for better type safety and allows for checking that public (and private keys via Public()) are equivalent. interface { Equal(crypto.PublicKey) bool } Equality for ECDSA keys is complicated, we take a strict interpretation that works for all secure applications (the ones not using the unfortunate non-constant time CurveParams implementation) and fails closed otherwise. Tests in separate files to make them x_tests and avoid an import loop with crypto/x509. Fixes #21704 Change-Id: Id5379c96384a11c5afde0614955360e7470bb1c4 Reviewed-on: https://go-review.googlesource.com/c/go/+/223754 Reviewed-by: Katie Hockman <[email protected]>
1 parent 24925c7 commit 5c9bd49

File tree

6 files changed

+171
-0
lines changed

6 files changed

+171
-0
lines changed

src/crypto/ecdsa/ecdsa.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,24 @@ type PublicKey struct {
6262
X, Y *big.Int
6363
}
6464

65+
// Equal reports whether pub and x have the same value.
66+
//
67+
// Two keys are only considered to have the same value if they have the same Curve value.
68+
// Note that for example elliptic.P256() and elliptic.P256().Params() are different
69+
// values, as the latter is a generic not constant time implementation.
70+
func (pub *PublicKey) Equal(x crypto.PublicKey) bool {
71+
xx, ok := x.(*PublicKey)
72+
if !ok {
73+
return false
74+
}
75+
return pub.X.Cmp(xx.X) == 0 && pub.Y.Cmp(xx.Y) == 0 &&
76+
// Standard library Curve implementations are singletons, so this check
77+
// will work for those. Other Curves might be equivalent even if not
78+
// singletons, but there is no definitive way to check for that, and
79+
// better to err on the side of safety.
80+
pub.Curve == xx.Curve
81+
}
82+
6583
// PrivateKey represents an ECDSA private key.
6684
type PrivateKey struct {
6785
PublicKey

src/crypto/ecdsa/equal_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2020 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 ecdsa_test
6+
7+
import (
8+
"crypto"
9+
"crypto/ecdsa"
10+
"crypto/elliptic"
11+
"crypto/rand"
12+
"crypto/x509"
13+
"testing"
14+
)
15+
16+
func testEqual(t *testing.T, c elliptic.Curve) {
17+
private, _ := ecdsa.GenerateKey(c, rand.Reader)
18+
public := &private.PublicKey
19+
20+
if !public.Equal(public) {
21+
t.Errorf("public key is not equal to itself: %v", public)
22+
}
23+
if !public.Equal(crypto.Signer(private).Public().(*ecdsa.PublicKey)) {
24+
t.Errorf("private.Public() is not Equal to public: %q", public)
25+
}
26+
27+
enc, err := x509.MarshalPKIXPublicKey(public)
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
decoded, err := x509.ParsePKIXPublicKey(enc)
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
if !public.Equal(decoded) {
36+
t.Errorf("public key is not equal to itself after decoding: %v", public)
37+
}
38+
39+
other, _ := ecdsa.GenerateKey(c, rand.Reader)
40+
if public.Equal(other) {
41+
t.Errorf("different public keys are Equal")
42+
}
43+
44+
// Ensure that keys with the same coordinates but on different curves
45+
// aren't considered Equal.
46+
differentCurve := &ecdsa.PublicKey{}
47+
*differentCurve = *public // make a copy of the public key
48+
if differentCurve.Curve == elliptic.P256() {
49+
differentCurve.Curve = elliptic.P224()
50+
} else {
51+
differentCurve.Curve = elliptic.P256()
52+
}
53+
if public.Equal(differentCurve) {
54+
t.Errorf("public keys with different curves are Equal")
55+
}
56+
57+
// This is not necessarily desirable, but if the Curve implementations are
58+
// different, the PublicKeys are not considered Equal.
59+
differentImpl := &ecdsa.PublicKey{}
60+
*differentImpl = *public
61+
// CurveParams also implements the Curve interface, although with a generic
62+
// non-constant time implementation. See golang.org/issue/34648.
63+
differentImpl.Curve = differentImpl.Curve.Params()
64+
if public.Equal(differentImpl) {
65+
t.Errorf("public keys with different curve implementations are Equal")
66+
}
67+
}
68+
69+
func TestEqual(t *testing.T) {
70+
t.Run("P224", func(t *testing.T) { testEqual(t, elliptic.P224()) })
71+
if testing.Short() {
72+
return
73+
}
74+
t.Run("P256", func(t *testing.T) { testEqual(t, elliptic.P256()) })
75+
t.Run("P384", func(t *testing.T) { testEqual(t, elliptic.P384()) })
76+
t.Run("P521", func(t *testing.T) { testEqual(t, elliptic.P521()) })
77+
}

src/crypto/ed25519/ed25519.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ const (
4040
// PublicKey is the type of Ed25519 public keys.
4141
type PublicKey []byte
4242

43+
// Equal reports whether pub and x have the same value.
44+
func (pub PublicKey) Equal(x crypto.PublicKey) bool {
45+
xx, ok := x.(PublicKey)
46+
if !ok {
47+
return false
48+
}
49+
return bytes.Equal(pub, xx)
50+
}
51+
4352
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
4453
type PrivateKey []byte
4554

src/crypto/ed25519/ed25519_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ func TestCryptoSigner(t *testing.T) {
8888
}
8989
}
9090

91+
func TestEqual(t *testing.T) {
92+
public, private, _ := GenerateKey(rand.Reader)
93+
94+
if !public.Equal(public) {
95+
t.Errorf("public key is not equal to itself: %q", public)
96+
}
97+
if !public.Equal(crypto.Signer(private).Public().(PublicKey)) {
98+
t.Errorf("private.Public() is not Equal to public: %q", public)
99+
}
100+
101+
other, _, _ := GenerateKey(rand.Reader)
102+
if public.Equal(other) {
103+
t.Errorf("different public keys are Equal")
104+
}
105+
}
106+
91107
func TestGolden(t *testing.T) {
92108
// sign.input.gz is a selection of test cases from
93109
// https://ed25519.cr.yp.to/python/sign.input

src/crypto/rsa/equal_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2020 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 rsa_test
6+
7+
import (
8+
"crypto"
9+
"crypto/rand"
10+
"crypto/rsa"
11+
"crypto/x509"
12+
"testing"
13+
)
14+
15+
func TestEqual(t *testing.T) {
16+
private, _ := rsa.GenerateKey(rand.Reader, 512)
17+
public := &private.PublicKey
18+
19+
if !public.Equal(public) {
20+
t.Errorf("public key is not equal to itself: %v", public)
21+
}
22+
if !public.Equal(crypto.Signer(private).Public().(*rsa.PublicKey)) {
23+
t.Errorf("private.Public() is not Equal to public: %q", public)
24+
}
25+
26+
enc, err := x509.MarshalPKIXPublicKey(public)
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
decoded, err := x509.ParsePKIXPublicKey(enc)
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
if !public.Equal(decoded) {
35+
t.Errorf("public key is not equal to itself after decoding: %v", public)
36+
}
37+
38+
other, _ := rsa.GenerateKey(rand.Reader, 512)
39+
if public.Equal(other) {
40+
t.Errorf("different public keys are Equal")
41+
}
42+
}

src/crypto/rsa/rsa.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ func (pub *PublicKey) Size() int {
5050
return (pub.N.BitLen() + 7) / 8
5151
}
5252

53+
// Equal reports whether pub and x have the same value.
54+
func (pub *PublicKey) Equal(x crypto.PublicKey) bool {
55+
xx, ok := x.(*PublicKey)
56+
if !ok {
57+
return false
58+
}
59+
return pub.N.Cmp(xx.N) == 0 && pub.E == xx.E
60+
}
61+
5362
// OAEPOptions is an interface for passing options to OAEP decryption using the
5463
// crypto.Decrypter interface.
5564
type OAEPOptions struct {

0 commit comments

Comments
 (0)