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