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  }