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