github.com/vmware/govmomi@v0.37.2/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  }