Block keys using hex(sha256(spki)). (#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.
This commit is contained in:
Jacob Hoffman-Andrews 2020-04-09 09:41:33 -07:00 committed by GitHub
parent 324d92d7c5
commit 0db7d9ff89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 27 deletions

View File

@ -87,13 +87,15 @@ func Fingerprint256(data []byte) string {
return base64.RawURLEncoding.EncodeToString(d.Sum(nil))
}
// KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
type Sha256Digest [sha256.Size]byte
// KeyDigest produces a Base64-encoded SHA256 digest of a
// provided public key.
func KeyDigest(key crypto.PublicKey) (string, error) {
func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) {
switch t := key.(type) {
case *jose.JSONWebKey:
if t == nil {
return "", fmt.Errorf("Cannot compute digest of nil key")
return Sha256Digest{}, fmt.Errorf("Cannot compute digest of nil key")
}
return KeyDigest(t.Key)
case jose.JSONWebKey:
@ -103,17 +105,26 @@ func KeyDigest(key crypto.PublicKey) (string, error) {
if err != nil {
logger := blog.Get()
logger.Debugf("Problem marshaling public key: %s", err)
return "", err
return Sha256Digest{}, err
}
spkiDigest := sha256.Sum256(keyDER)
return base64.StdEncoding.EncodeToString(spkiDigest[0:32]), nil
return sha256.Sum256(keyDER), nil
}
}
// KeyDigestB64 produces a padded, standard Base64-encoded SHA256 digest of a
// provided public key.
func KeyDigestB64(key crypto.PublicKey) (string, error) {
digest, err := KeyDigest(key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(digest[:]), nil
}
// KeyDigestEquals determines whether two public keys have the same digest.
func KeyDigestEquals(j, k crypto.PublicKey) bool {
digestJ, errJ := KeyDigest(j)
digestK, errK := KeyDigest(k)
digestJ, errJ := KeyDigestB64(j)
digestK, errK := KeyDigestB64(k)
// Keys that don't have a valid digest (due to marshalling problems)
// are never equal. So, e.g. nil keys are not equal.
if errJ != nil || errK != nil {

View File

@ -78,15 +78,15 @@ func TestKeyDigest(t *testing.T) {
if err != nil {
t.Fatal(err)
}
digest, err := KeyDigest(jwk)
digest, err := KeyDigestB64(jwk)
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by value")
digest, err = KeyDigest(&jwk)
digest, err = KeyDigestB64(&jwk)
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by reference")
digest, err = KeyDigest(jwk.Key)
digest, err = KeyDigestB64(jwk.Key)
test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest bare key")
// Test with unknown key type
_, err = KeyDigest(struct{}{})
_, err = KeyDigestB64(struct{}{})
test.Assert(t, err != nil, "Should have rejected unknown key type")
}

View File

@ -2,6 +2,9 @@ package goodkey
import (
"crypto"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"io/ioutil"
@ -10,13 +13,15 @@ import (
yaml "gopkg.in/yaml.v2"
)
// blockedKeys is a type for maintaining a map of Base64 encoded SHA256 hashes
// blockedKeys is a type for maintaining a map of SHA256 hashes
// of SubjectPublicKeyInfo's that should be considered blocked.
// blockedKeys are created by using loadBlockedKeysList.
type blockedKeys map[string]bool
type blockedKeys map[core.Sha256Digest]bool
var ErrWrongDecodedSize = errors.New("not enough bytes decoded for sha256 hash")
// blocked checks if the given public key is considered administratively
// blocked based on a Base64 encoded SHA256 hash of the SubjectPublicKeyInfo.
// blocked based on a SHA256 hash of the SubjectPublicKeyInfo.
// Important: blocked should not be called except on a blockedKeys instance
// returned from loadBlockedKeysList.
// function should not be used until after `loadBlockedKeysList` has returned.
@ -33,7 +38,7 @@ func (b blockedKeys) blocked(key crypto.PublicKey) (bool, error) {
}
// loadBlockedKeysList creates a blockedKeys object that can be used to check if
// a key is blocked. It creates a lookup map from a list of Base64 encoded
// a key is blocked. It creates a lookup map from a list of
// SHA256 hashes of SubjectPublicKeyInfo's in the input YAML file
// with the expected format:
//
@ -52,19 +57,41 @@ func loadBlockedKeysList(filename string) (*blockedKeys, error) {
}
var list struct {
BlockedHashes []string `yaml:"blocked"`
BlockedHashes []string `yaml:"blocked"`
BlockedHashesHex []string `yaml:"blockedHashesHex"`
}
if err := yaml.Unmarshal(yamlBytes, &list); err != nil {
return nil, err
}
if len(list.BlockedHashes) == 0 {
if len(list.BlockedHashes) == 0 && len(list.BlockedHashesHex) == 0 {
return nil, errors.New("no blocked hashes in YAML")
}
blockedKeys := make(blockedKeys, len(list.BlockedHashes))
for _, hash := range list.BlockedHashes {
blockedKeys[hash] = true
blockedKeys := make(blockedKeys, len(list.BlockedHashes)+len(list.BlockedHashesHex))
for _, b64Hash := range list.BlockedHashes {
decoded, err := base64.StdEncoding.DecodeString(b64Hash)
if err != nil {
return nil, err
}
if len(decoded) != sha256.Size {
return nil, ErrWrongDecodedSize
}
var sha256Digest core.Sha256Digest
copy(sha256Digest[:], decoded[0:sha256.Size])
blockedKeys[sha256Digest] = true
}
for _, hexHash := range list.BlockedHashesHex {
decoded, err := hex.DecodeString(hexHash)
if err != nil {
return nil, err
}
if len(decoded) != sha256.Size {
return nil, ErrWrongDecodedSize
}
var sha256Digest core.Sha256Digest
copy(sha256Digest[:], decoded[0:sha256.Size])
blockedKeys[sha256Digest] = true
}
return &blockedKeys, nil
}

View File

@ -16,7 +16,8 @@ import (
func TestBlockedKeys(t *testing.T) {
// Start with an empty list
var inList struct {
BlockedHashes []string `yaml:"blocked"`
BlockedHashes []string `yaml:"blocked"`
BlockedHashesHex []string `yaml:"blockedHashesHex"`
}
yamlList, err := yaml.Marshal(&inList)
@ -56,7 +57,9 @@ func TestBlockedKeys(t *testing.T) {
// public keys in the test certs/JWKs
inList.BlockedHashes = []string{
"cuwGhNNI6nfob5aqY90e7BleU6l7rfxku4X3UTJ3Z7M=",
"Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE=",
}
inList.BlockedHashesHex = []string{
"41e6dcd55dd2917de2ce461118d262966f4172ebdfd28a31e14d919fe6f824e1",
}
yamlList, err = yaml.Marshal(&inList)

View File

@ -190,7 +190,7 @@ func registrationToModel(r *core.Registration) (*regModel, error) {
return nil, err
}
sha, err := core.KeyDigest(r.Key)
sha, err := core.KeyDigestB64(r.Key)
if err != nil {
return nil, err
}

View File

@ -115,7 +115,7 @@ func (ssa *SQLStorageAuthority) GetRegistrationByKey(ctx context.Context, key *j
if key == nil {
return core.Registration{}, fmt.Errorf("key argument to GetRegistrationByKey must not be nil")
}
sha, err := core.KeyDigest(key.Key)
sha, err := core.KeyDigestB64(key.Key)
if err != nil {
return core.Registration{}, err
}

View File

@ -100,7 +100,7 @@ func main() {
log.Fatalf("error loading public key: %v", err)
}
spkiHash, err := core.KeyDigest(key)
spkiHash, err := core.KeyDigestB64(key)
if err != nil {
log.Fatalf("error computing spki hash: %v", err)
}

View File

@ -51,7 +51,7 @@ func TestKeyBlocking(t *testing.T) {
key, err = keyFromCert(tc.certPath)
}
test.AssertNotError(t, err, "error getting key from input file")
spkiHash, err := core.KeyDigest(key)
spkiHash, err := core.KeyDigestB64(key)
test.AssertNotError(t, err, "error computing spki hash")
test.AssertEquals(t, spkiHash, tc.expected)
})

View File

@ -26,3 +26,7 @@ blocked:
- cuwGhNNI6nfob5aqY90e7BleU6l7rfxku4X3UTJ3Z7M=
# test/block-a-key/test/test.rsa.jwk.json
- Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE=
blockedHashesHex:
- 41e6dcd55dd2917de2ce461118d262966f4172ebdfd28a31e14d919fe6f824e1