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