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  }