github.com/vmware/govmomi@v0.51.0/object/host_certificate_info.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package object 6 7 import ( 8 "crypto/tls" 9 "crypto/x509" 10 "crypto/x509/pkix" 11 "encoding/asn1" 12 "encoding/pem" 13 "errors" 14 "fmt" 15 "io" 16 "net/url" 17 "strings" 18 "text/tabwriter" 19 20 "github.com/vmware/govmomi/vim25/soap" 21 "github.com/vmware/govmomi/vim25/types" 22 ) 23 24 // HostCertificateInfo provides helpers for types.HostCertificateManagerCertificateInfo 25 type HostCertificateInfo struct { 26 types.HostCertificateManagerCertificateInfo 27 28 ThumbprintSHA1 string `json:"thumbprintSHA1"` 29 ThumbprintSHA256 string `json:"thumbprintSHA256"` 30 31 Err error `json:"err"` 32 Certificate *x509.Certificate `json:"-"` 33 34 subjectName *pkix.Name 35 issuerName *pkix.Name 36 } 37 38 // FromCertificate converts x509.Certificate to HostCertificateInfo 39 func (info *HostCertificateInfo) FromCertificate(cert *x509.Certificate) *HostCertificateInfo { 40 info.Certificate = cert 41 info.subjectName = &cert.Subject 42 info.issuerName = &cert.Issuer 43 44 info.Issuer = info.fromName(info.issuerName) 45 info.NotBefore = &cert.NotBefore 46 info.NotAfter = &cert.NotAfter 47 info.Subject = info.fromName(info.subjectName) 48 49 info.ThumbprintSHA1 = soap.ThumbprintSHA1(cert) 50 info.ThumbprintSHA256 = soap.ThumbprintSHA256(cert) 51 52 if info.Status == "" { 53 info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusUnknown) 54 } 55 56 return info 57 } 58 59 func (info *HostCertificateInfo) FromPEM(cert []byte) (*HostCertificateInfo, error) { 60 block, _ := pem.Decode(cert) 61 if block == nil { 62 return nil, errors.New("failed to pem.Decode cert") 63 } 64 x, err := x509.ParseCertificate(block.Bytes) 65 if err != nil { 66 return nil, err 67 } 68 return info.FromCertificate(x), nil 69 } 70 71 // FromURL connects to the given URL.Host via tls.Dial with the given tls.Config and populates the HostCertificateInfo 72 // via tls.ConnectionState. If the certificate was verified with the given tls.Config, the Err field will be nil. 73 // Otherwise, Err will be set to the x509.UnknownAuthorityError or x509.HostnameError. 74 // If tls.Dial returns an error of any other type, that error is returned. 75 func (info *HostCertificateInfo) FromURL(u *url.URL, config *tls.Config) error { 76 addr := u.Host 77 if !(strings.LastIndex(addr, ":") > strings.LastIndex(addr, "]")) { 78 addr += ":443" 79 } 80 81 conn, err := tls.Dial("tcp", addr, config) 82 if err != nil { 83 if !soap.IsCertificateUntrusted(err) { 84 return err 85 } 86 87 info.Err = err 88 89 conn, err = tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) 90 if err != nil { 91 return err 92 } 93 } else { 94 info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusGood) 95 } 96 97 state := conn.ConnectionState() 98 _ = conn.Close() 99 info.FromCertificate(state.PeerCertificates[0]) 100 101 return nil 102 } 103 104 var emailAddressOID = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1} 105 106 func (info *HostCertificateInfo) fromName(name *pkix.Name) string { 107 var attrs []string 108 109 oids := map[string]string{ 110 emailAddressOID.String(): "emailAddress", 111 } 112 113 for _, attr := range name.Names { 114 if key, ok := oids[attr.Type.String()]; ok { 115 attrs = append(attrs, fmt.Sprintf("%s=%s", key, attr.Value)) 116 } 117 } 118 119 attrs = append(attrs, fmt.Sprintf("CN=%s", name.CommonName)) 120 121 add := func(key string, vals []string) { 122 for _, val := range vals { 123 attrs = append(attrs, fmt.Sprintf("%s=%s", key, val)) 124 } 125 } 126 127 elts := []struct { 128 key string 129 val []string 130 }{ 131 {"OU", name.OrganizationalUnit}, 132 {"O", name.Organization}, 133 {"L", name.Locality}, 134 {"ST", name.Province}, 135 {"C", name.Country}, 136 } 137 138 for _, elt := range elts { 139 add(elt.key, elt.val) 140 } 141 142 return strings.Join(attrs, ",") 143 } 144 145 func (info *HostCertificateInfo) toName(s string) *pkix.Name { 146 var name pkix.Name 147 148 for _, pair := range strings.Split(s, ",") { 149 attr := strings.SplitN(pair, "=", 2) 150 if len(attr) != 2 { 151 continue 152 } 153 154 v := attr[1] 155 156 switch strings.ToLower(attr[0]) { 157 case "cn": 158 name.CommonName = v 159 case "ou": 160 name.OrganizationalUnit = append(name.OrganizationalUnit, v) 161 case "o": 162 name.Organization = append(name.Organization, v) 163 case "l": 164 name.Locality = append(name.Locality, v) 165 case "st": 166 name.Province = append(name.Province, v) 167 case "c": 168 name.Country = append(name.Country, v) 169 case "emailaddress": 170 name.Names = append(name.Names, pkix.AttributeTypeAndValue{Type: emailAddressOID, Value: v}) 171 } 172 } 173 174 return &name 175 } 176 177 // SubjectName parses Subject into a pkix.Name 178 func (info *HostCertificateInfo) SubjectName() *pkix.Name { 179 if info.subjectName != nil { 180 return info.subjectName 181 } 182 183 return info.toName(info.Subject) 184 } 185 186 // IssuerName parses Issuer into a pkix.Name 187 func (info *HostCertificateInfo) IssuerName() *pkix.Name { 188 if info.issuerName != nil { 189 return info.issuerName 190 } 191 192 return info.toName(info.Issuer) 193 } 194 195 // Write outputs info similar to the Chrome Certificate Viewer. 196 func (info *HostCertificateInfo) Write(w io.Writer) error { 197 tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) 198 199 s := func(val string) string { 200 if val != "" { 201 return val 202 } 203 return "<Not Part Of Certificate>" 204 } 205 206 ss := func(val []string) string { 207 return s(strings.Join(val, ",")) 208 } 209 210 name := func(n *pkix.Name) { 211 fmt.Fprintf(tw, " Common Name (CN):\t%s\n", s(n.CommonName)) 212 fmt.Fprintf(tw, " Organization (O):\t%s\n", ss(n.Organization)) 213 fmt.Fprintf(tw, " Organizational Unit (OU):\t%s\n", ss(n.OrganizationalUnit)) 214 } 215 216 status := info.Status 217 if info.Err != nil { 218 status = fmt.Sprintf("ERROR %s", info.Err) 219 } 220 fmt.Fprintf(tw, "Certificate Status:\t%s\n", status) 221 222 fmt.Fprintln(tw, "Issued To:\t") 223 name(info.SubjectName()) 224 225 fmt.Fprintln(tw, "Issued By:\t") 226 name(info.IssuerName()) 227 228 fmt.Fprintln(tw, "Validity Period:\t") 229 fmt.Fprintf(tw, " Issued On:\t%s\n", info.NotBefore) 230 fmt.Fprintf(tw, " Expires On:\t%s\n", info.NotAfter) 231 232 if info.ThumbprintSHA1 != "" { 233 fmt.Fprintln(tw, "Thumbprints:\t") 234 if info.ThumbprintSHA256 != "" { 235 fmt.Fprintf(tw, " SHA-256 Thumbprint:\t%s\n", info.ThumbprintSHA256) 236 } 237 fmt.Fprintf(tw, " SHA-1 Thumbprint:\t%s\n", info.ThumbprintSHA1) 238 } 239 240 return tw.Flush() 241 }