Skip to content

Commit 0db7d9f

Browse files
authored
Block keys using hex(sha256(spki)). (letsencrypt#4745)
In addition to base64(sha256(spki)). As part of that, change KeyDigest to return [32]byte, and add KeyDigestB64 which provides the base64-encoded output that KeyDigest used to provide. Also update all call sites.
1 parent 324d92d commit 0db7d9f

File tree

9 files changed

+72
-27
lines changed

9 files changed

+72
-27
lines changed

core/util.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ func Fingerprint256(data []byte) string {
8787
return base64.RawURLEncoding.EncodeToString(d.Sum(nil))
8888
}
8989

90-
// KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
90+
type Sha256Digest [sha256.Size]byte
91+
92+
// KeyDigest produces a Base64-encoded SHA256 digest of a
9193
// provided public key.
92-
func KeyDigest(key crypto.PublicKey) (string, error) {
94+
func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) {
9395
switch t := key.(type) {
9496
case *jose.JSONWebKey:
9597
if t == nil {
96-
return "", fmt.Errorf("Cannot compute digest of nil key")
98+
return Sha256Digest{}, fmt.Errorf("Cannot compute digest of nil key")
9799
}
98100
return KeyDigest(t.Key)
99101
case jose.JSONWebKey:
@@ -103,17 +105,26 @@ func KeyDigest(key crypto.PublicKey) (string, error) {
103105
if err != nil {
104106
logger := blog.Get()
105107
logger.Debugf("Problem marshaling public key: %s", err)
106-
return "", err
108+
return Sha256Digest{}, err
107109
}
108-
spkiDigest := sha256.Sum256(keyDER)
109-
return base64.StdEncoding.EncodeToString(spkiDigest[0:32]), nil
110+
return sha256.Sum256(keyDER), nil
111+
}
112+
}
113+
114+
// KeyDigestB64 produces a padded, standard Base64-encoded SHA256 digest of a
115+
// provided public key.
116+
func KeyDigestB64(key crypto.PublicKey) (string, error) {
117+
digest, err := KeyDigest(key)
118+
if err != nil {
119+
return "", err
110120
}
121+
return base64.StdEncoding.EncodeToString(digest[:]), nil
111122
}
112123

113124
// KeyDigestEquals determines whether two public keys have the same digest.
114125
func KeyDigestEquals(j, k crypto.PublicKey) bool {
115-
digestJ, errJ := KeyDigest(j)
116-
digestK, errK := KeyDigest(k)
126+
digestJ, errJ := KeyDigestB64(j)
127+
digestK, errK := KeyDigestB64(k)
117128
// Keys that don't have a valid digest (due to marshalling problems)
118129
// are never equal. So, e.g. nil keys are not equal.
119130
if errJ != nil || errK != nil {

core/util_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ func TestKeyDigest(t *testing.T) {
7878
if err != nil {
7979
t.Fatal(err)
8080
}
81-
digest, err := KeyDigest(jwk)
81+
digest, err := KeyDigestB64(jwk)
8282
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by value")
83-
digest, err = KeyDigest(&jwk)
83+
digest, err = KeyDigestB64(&jwk)
8484
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by reference")
85-
digest, err = KeyDigest(jwk.Key)
85+
digest, err = KeyDigestB64(jwk.Key)
8686
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest bare key")
8787

8888
// Test with unknown key type
89-
_, err = KeyDigest(struct{}{})
89+
_, err = KeyDigestB64(struct{}{})
9090
test.Assert(t, err != nil, "Should have rejected unknown key type")
9191
}
9292

goodkey/blocked.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package goodkey
22

33
import (
44
"crypto"
5+
"crypto/sha256"
6+
"encoding/base64"
7+
"encoding/hex"
58
"errors"
69
"io/ioutil"
710

@@ -10,13 +13,15 @@ import (
1013
yaml "gopkg.in/yaml.v2"
1114
)
1215

13-
// blockedKeys is a type for maintaining a map of Base64 encoded SHA256 hashes
16+
// blockedKeys is a type for maintaining a map of SHA256 hashes
1417
// of SubjectPublicKeyInfo's that should be considered blocked.
1518
// blockedKeys are created by using loadBlockedKeysList.
16-
type blockedKeys map[string]bool
19+
type blockedKeys map[core.Sha256Digest]bool
20+
21+
var ErrWrongDecodedSize = errors.New("not enough bytes decoded for sha256 hash")
1722

1823
// blocked checks if the given public key is considered administratively
19-
// blocked based on a Base64 encoded SHA256 hash of the SubjectPublicKeyInfo.
24+
// blocked based on a SHA256 hash of the SubjectPublicKeyInfo.
2025
// Important: blocked should not be called except on a blockedKeys instance
2126
// returned from loadBlockedKeysList.
2227
// function should not be used until after `loadBlockedKeysList` has returned.
@@ -33,7 +38,7 @@ func (b blockedKeys) blocked(key crypto.PublicKey) (bool, error) {
3338
}
3439

3540
// loadBlockedKeysList creates a blockedKeys object that can be used to check if
36-
// a key is blocked. It creates a lookup map from a list of Base64 encoded
41+
// a key is blocked. It creates a lookup map from a list of
3742
// SHA256 hashes of SubjectPublicKeyInfo's in the input YAML file
3843
// with the expected format:
3944
//
@@ -52,19 +57,41 @@ func loadBlockedKeysList(filename string) (*blockedKeys, error) {
5257
}
5358

5459
var list struct {
55-
BlockedHashes []string `yaml:"blocked"`
60+
BlockedHashes []string `yaml:"blocked"`
61+
BlockedHashesHex []string `yaml:"blockedHashesHex"`
5662
}
5763
if err := yaml.Unmarshal(yamlBytes, &list); err != nil {
5864
return nil, err
5965
}
6066

61-
if len(list.BlockedHashes) == 0 {
67+
if len(list.BlockedHashes) == 0 && len(list.BlockedHashesHex) == 0 {
6268
return nil, errors.New("no blocked hashes in YAML")
6369
}
6470

65-
blockedKeys := make(blockedKeys, len(list.BlockedHashes))
66-
for _, hash := range list.BlockedHashes {
67-
blockedKeys[hash] = true
71+
blockedKeys := make(blockedKeys, len(list.BlockedHashes)+len(list.BlockedHashesHex))
72+
for _, b64Hash := range list.BlockedHashes {
73+
decoded, err := base64.StdEncoding.DecodeString(b64Hash)
74+
if err != nil {
75+
return nil, err
76+
}
77+
if len(decoded) != sha256.Size {
78+
return nil, ErrWrongDecodedSize
79+
}
80+
var sha256Digest core.Sha256Digest
81+
copy(sha256Digest[:], decoded[0:sha256.Size])
82+
blockedKeys[sha256Digest] = true
83+
}
84+
for _, hexHash := range list.BlockedHashesHex {
85+
decoded, err := hex.DecodeString(hexHash)
86+
if err != nil {
87+
return nil, err
88+
}
89+
if len(decoded) != sha256.Size {
90+
return nil, ErrWrongDecodedSize
91+
}
92+
var sha256Digest core.Sha256Digest
93+
copy(sha256Digest[:], decoded[0:sha256.Size])
94+
blockedKeys[sha256Digest] = true
6895
}
6996
return &blockedKeys, nil
7097
}

goodkey/blocked_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import (
1616
func TestBlockedKeys(t *testing.T) {
1717
// Start with an empty list
1818
var inList struct {
19-
BlockedHashes []string `yaml:"blocked"`
19+
BlockedHashes []string `yaml:"blocked"`
20+
BlockedHashesHex []string `yaml:"blockedHashesHex"`
2021
}
2122

2223
yamlList, err := yaml.Marshal(&inList)
@@ -56,7 +57,9 @@ func TestBlockedKeys(t *testing.T) {
5657
// public keys in the test certs/JWKs
5758
inList.BlockedHashes = []string{
5859
"cuwGhNNI6nfob5aqY90e7BleU6l7rfxku4X3UTJ3Z7M=",
59-
"Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE=",
60+
}
61+
inList.BlockedHashesHex = []string{
62+
"41e6dcd55dd2917de2ce461118d262966f4172ebdfd28a31e14d919fe6f824e1",
6063
}
6164

6265
yamlList, err = yaml.Marshal(&inList)

sa/model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func registrationToModel(r *core.Registration) (*regModel, error) {
190190
return nil, err
191191
}
192192

193-
sha, err := core.KeyDigest(r.Key)
193+
sha, err := core.KeyDigestB64(r.Key)
194194
if err != nil {
195195
return nil, err
196196
}

sa/sa.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (ssa *SQLStorageAuthority) GetRegistrationByKey(ctx context.Context, key *j
115115
if key == nil {
116116
return core.Registration{}, fmt.Errorf("key argument to GetRegistrationByKey must not be nil")
117117
}
118-
sha, err := core.KeyDigest(key.Key)
118+
sha, err := core.KeyDigestB64(key.Key)
119119
if err != nil {
120120
return core.Registration{}, err
121121
}

test/block-a-key/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func main() {
100100
log.Fatalf("error loading public key: %v", err)
101101
}
102102

103-
spkiHash, err := core.KeyDigest(key)
103+
spkiHash, err := core.KeyDigestB64(key)
104104
if err != nil {
105105
log.Fatalf("error computing spki hash: %v", err)
106106
}

test/block-a-key/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestKeyBlocking(t *testing.T) {
5151
key, err = keyFromCert(tc.certPath)
5252
}
5353
test.AssertNotError(t, err, "error getting key from input file")
54-
spkiHash, err := core.KeyDigest(key)
54+
spkiHash, err := core.KeyDigestB64(key)
5555
test.AssertNotError(t, err, "error computing spki hash")
5656
test.AssertEquals(t, spkiHash, tc.expected)
5757
})

test/example-blocked-keys.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ blocked:
2626
- cuwGhNNI6nfob5aqY90e7BleU6l7rfxku4X3UTJ3Z7M=
2727
# test/block-a-key/test/test.rsa.jwk.json
2828
- Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE=
29+
blockedHashesHex:
30+
- 41e6dcd55dd2917de2ce461118d262966f4172ebdfd28a31e14d919fe6f824e1
31+
32+

0 commit comments

Comments
 (0)