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)) 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. // provided public key.
func KeyDigest(key crypto.PublicKey) (string, error) { func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) {
switch t := key.(type) { switch t := key.(type) {
case *jose.JSONWebKey: case *jose.JSONWebKey:
if t == nil { 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) return KeyDigest(t.Key)
case jose.JSONWebKey: case jose.JSONWebKey:
@ -103,17 +105,26 @@ func KeyDigest(key crypto.PublicKey) (string, error) {
if err != nil { if err != nil {
logger := blog.Get() logger := blog.Get()
logger.Debugf("Problem marshaling public key: %s", err) logger.Debugf("Problem marshaling public key: %s", err)
return "", err return Sha256Digest{}, err
} }
spkiDigest := sha256.Sum256(keyDER) return sha256.Sum256(keyDER), nil
return base64.StdEncoding.EncodeToString(spkiDigest[0:32]), 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. // KeyDigestEquals determines whether two public keys have the same digest.
func KeyDigestEquals(j, k crypto.PublicKey) bool { func KeyDigestEquals(j, k crypto.PublicKey) bool {
digestJ, errJ := KeyDigest(j) digestJ, errJ := KeyDigestB64(j)
digestK, errK := KeyDigest(k) digestK, errK := KeyDigestB64(k)
// Keys that don't have a valid digest (due to marshalling problems) // Keys that don't have a valid digest (due to marshalling problems)
// are never equal. So, e.g. nil keys are not equal. // are never equal. So, e.g. nil keys are not equal.
if errJ != nil || errK != nil { if errJ != nil || errK != nil {

View File

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

View File

@ -2,6 +2,9 @@ package goodkey
import ( import (
"crypto" "crypto"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors" "errors"
"io/ioutil" "io/ioutil"
@ -10,13 +13,15 @@ import (
yaml "gopkg.in/yaml.v2" 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. // of SubjectPublicKeyInfo's that should be considered blocked.
// blockedKeys are created by using loadBlockedKeysList. // 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 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 // Important: blocked should not be called except on a blockedKeys instance
// returned from loadBlockedKeysList. // returned from loadBlockedKeysList.
// function should not be used until after `loadBlockedKeysList` has returned. // 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 // 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 // SHA256 hashes of SubjectPublicKeyInfo's in the input YAML file
// with the expected format: // with the expected format:
// //
@ -52,19 +57,41 @@ func loadBlockedKeysList(filename string) (*blockedKeys, error) {
} }
var list struct { var list struct {
BlockedHashes []string `yaml:"blocked"` BlockedHashes []string `yaml:"blocked"`
BlockedHashesHex []string `yaml:"blockedHashesHex"`
} }
if err := yaml.Unmarshal(yamlBytes, &list); err != nil { if err := yaml.Unmarshal(yamlBytes, &list); err != nil {
return nil, err 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") return nil, errors.New("no blocked hashes in YAML")
} }
blockedKeys := make(blockedKeys, len(list.BlockedHashes)) blockedKeys := make(blockedKeys, len(list.BlockedHashes)+len(list.BlockedHashesHex))
for _, hash := range list.BlockedHashes { for _, b64Hash := range list.BlockedHashes {
blockedKeys[hash] = true 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 return &blockedKeys, nil
} }

View File

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

View File

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

View File

@ -115,7 +115,7 @@ func (ssa *SQLStorageAuthority) GetRegistrationByKey(ctx context.Context, key *j
if key == nil { if key == nil {
return core.Registration{}, fmt.Errorf("key argument to GetRegistrationByKey must not be 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 { if err != nil {
return core.Registration{}, err return core.Registration{}, err
} }

View File

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

View File

@ -51,7 +51,7 @@ func TestKeyBlocking(t *testing.T) {
key, err = keyFromCert(tc.certPath) key, err = keyFromCert(tc.certPath)
} }
test.AssertNotError(t, err, "error getting key from input file") 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.AssertNotError(t, err, "error computing spki hash")
test.AssertEquals(t, spkiHash, tc.expected) test.AssertEquals(t, spkiHash, tc.expected)
}) })

View File

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