pubkeypin.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package pubkeypin provides primitives for x509 public key pinning in the
  14. // style of RFC7469.
  15. package pubkeypin
  16. import (
  17. "crypto/sha256"
  18. "crypto/x509"
  19. "encoding/hex"
  20. "strings"
  21. "github.com/pkg/errors"
  22. )
  23. const (
  24. // formatSHA256 is the prefix for pins that are full-length SHA-256 hashes encoded in base 16 (hex)
  25. formatSHA256 = "sha256"
  26. )
  27. // Set is a set of pinned x509 public keys.
  28. type Set struct {
  29. sha256Hashes map[string]bool
  30. }
  31. // NewSet returns a new, empty PubKeyPinSet
  32. func NewSet() *Set {
  33. return &Set{make(map[string]bool)}
  34. }
  35. // Allow adds an allowed public key hash to the Set
  36. func (s *Set) Allow(pubKeyHashes ...string) error {
  37. for _, pubKeyHash := range pubKeyHashes {
  38. parts := strings.Split(pubKeyHash, ":")
  39. if len(parts) != 2 {
  40. return errors.New("invalid public key hash, expected \"format:value\"")
  41. }
  42. format, value := parts[0], parts[1]
  43. switch strings.ToLower(format) {
  44. case "sha256":
  45. return s.allowSHA256(value)
  46. default:
  47. return errors.Errorf("unknown hash format %q", format)
  48. }
  49. }
  50. return nil
  51. }
  52. // CheckAny checks if at least one certificate matches one of the public keys in the set
  53. func (s *Set) CheckAny(certificates []*x509.Certificate) error {
  54. var hashes []string
  55. for _, certificate := range certificates {
  56. if s.checkSHA256(certificate) {
  57. return nil
  58. }
  59. hashes = append(hashes, Hash(certificate))
  60. }
  61. return errors.Errorf("none of the public keys %q are pinned", strings.Join(hashes, ":"))
  62. }
  63. // Empty returns true if the Set contains no pinned public keys.
  64. func (s *Set) Empty() bool {
  65. return len(s.sha256Hashes) == 0
  66. }
  67. // Hash calculates the SHA-256 hash of the Subject Public Key Information (SPKI)
  68. // object in an x509 certificate (in DER encoding). It returns the full hash as a
  69. // hex encoded string (suitable for passing to Set.Allow).
  70. func Hash(certificate *x509.Certificate) string {
  71. spkiHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
  72. return formatSHA256 + ":" + strings.ToLower(hex.EncodeToString(spkiHash[:]))
  73. }
  74. // allowSHA256 validates a "sha256" format hash and adds a canonical version of it into the Set
  75. func (s *Set) allowSHA256(hash string) error {
  76. // validate that the hash is the right length to be a full SHA-256 hash
  77. hashLength := hex.DecodedLen(len(hash))
  78. if hashLength != sha256.Size {
  79. return errors.Errorf("expected a %d byte SHA-256 hash, found %d bytes", sha256.Size, hashLength)
  80. }
  81. // validate that the hash is valid hex
  82. _, err := hex.DecodeString(hash)
  83. if err != nil {
  84. return err
  85. }
  86. // in the end, just store the original hex string in memory (in lowercase)
  87. s.sha256Hashes[strings.ToLower(hash)] = true
  88. return nil
  89. }
  90. // checkSHA256 returns true if the certificate's "sha256" hash is pinned in the Set
  91. func (s *Set) checkSHA256(certificate *x509.Certificate) bool {
  92. actualHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
  93. actualHashHex := strings.ToLower(hex.EncodeToString(actualHash[:]))
  94. return s.sha256Hashes[actualHashHex]
  95. }