github.com/microsoft/moc@v0.17.1/pkg/auth/pkv.go (about) 1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the Apache v2.0 license. 3 4 package auth 5 6 import ( 7 "crypto/sha256" 8 "crypto/x509" 9 "encoding/hex" 10 "strings" 11 12 "github.com/pkg/errors" 13 ) 14 15 const ( 16 // formatSHA256 is the prefix for pins that are full-length SHA-256 hashes encoded in base 16 (hex) 17 formatSHA256 = "sha256" 18 ) 19 20 type publicKeyVerifier struct { 21 pubkeypinSet *Set 22 } 23 24 func NewPublicKeyVerifier() *publicKeyVerifier { 25 pkv := &publicKeyVerifier{} 26 pkv.pubkeypinSet = NewSet() 27 return pkv 28 } 29 30 func (pkv *publicKeyVerifier) Allow(caCertHash string) error { 31 return pkv.pubkeypinSet.Allow(caCertHash) 32 } 33 34 // VerifyPeerCertificate is a callback to be used for client verification during the TLS handshake 35 func (c *publicKeyVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 36 37 x509certs := []*x509.Certificate{} 38 39 for _, crt := range rawCerts { 40 x509cert, err := x509.ParseCertificate(crt) 41 if err != nil { 42 return errors.Wrapf(err, "bad server certificate") 43 } 44 x509certs = append(x509certs, x509cert) 45 } 46 47 err := c.pubkeypinSet.CheckAny(x509certs) 48 if err != nil { 49 return err 50 } 51 return nil 52 } 53 54 // NB. This is taken from kubeadm https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/util/pubkeypin/pubkeypin.go 55 // Bringing the moc pkgs are light on dependencies and didn't want to muck that up for a self concerned pkg. 56 // Also this gives us freedom to tweak to our usecase 57 58 // Set is a set of pinned x509 public keys. 59 type Set struct { 60 sha256Hashes map[string]bool 61 } 62 63 // NewSet returns a new, empty PubKeyPinSet 64 func NewSet() *Set { 65 return &Set{make(map[string]bool)} 66 } 67 68 // Allow adds an allowed public key hash to the Set 69 func (s *Set) Allow(pubKeyHashes ...string) error { 70 for _, pubKeyHash := range pubKeyHashes { 71 parts := strings.Split(pubKeyHash, ":") 72 if len(parts) != 2 { 73 return errors.New("invalid public key hash, expected \"format:value\"") 74 } 75 format, value := parts[0], parts[1] 76 77 switch strings.ToLower(format) { 78 case "sha256": 79 return s.allowSHA256(value) 80 default: 81 return errors.Errorf("unknown hash format %q", format) 82 } 83 } 84 return nil 85 } 86 87 // CheckAny checks if at least one certificate matches one of the public keys in the set 88 func (s *Set) CheckAny(certificates []*x509.Certificate) error { 89 var hashes []string 90 91 for _, certificate := range certificates { 92 if s.checkSHA256(certificate) { 93 return nil 94 } 95 96 hashes = append(hashes, Hash(certificate)) 97 } 98 return errors.Errorf("none of the public keys %q are pinned", strings.Join(hashes, ":")) 99 } 100 101 // Hash calculates the SHA-256 hash of the Subject Public Key Information (SPKI) 102 // object in an x509 certificate (in DER encoding). It returns the full hash as a 103 // hex encoded string (suitable for passing to Set.Allow). 104 func Hash(certificate *x509.Certificate) string { 105 spkiHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo) 106 return formatSHA256 + ":" + strings.ToLower(hex.EncodeToString(spkiHash[:])) 107 } 108 109 // allowSHA256 validates a "sha256" format hash and adds a canonical version of it into the Set 110 func (s *Set) allowSHA256(hash string) error { 111 // validate that the hash is the right length to be a full SHA-256 hash 112 hashLength := hex.DecodedLen(len(hash)) 113 if hashLength != sha256.Size { 114 return errors.Errorf("expected a %d byte SHA-256 hash, found %d bytes", sha256.Size, hashLength) 115 } 116 117 // validate that the hash is valid hex 118 _, err := hex.DecodeString(hash) 119 if err != nil { 120 return err 121 } 122 123 // in the end, just store the original hex string in memory (in lowercase) 124 s.sha256Hashes[strings.ToLower(hash)] = true 125 return nil 126 } 127 128 // checkSHA256 returns true if the certificate's "sha256" hash is pinned in the Set 129 func (s *Set) checkSHA256(certificate *x509.Certificate) bool { 130 actualHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo) 131 actualHashHex := strings.ToLower(hex.EncodeToString(actualHash[:])) 132 return s.sha256Hashes[actualHashHex] 133 }