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