repo_description: Complete container management platform
repo_topics: cattle containers docker kubernetes orchestration rancher
code: package cert
  
  import (
  	"bytes"
  	"crypto"
  	"crypto/ecdsa"
  	"crypto/rsa"
  	"crypto/sha1"
  	"crypto/x509"
  	"encoding/hex"
  	"encoding/pem"
  	"fmt"
  	"strings"
  	"time"
  
  	"github.com/pkg/errors"
  )
  
  type CertificateInfo struct {
  	Algorithm               string    `json:"algorithm"`
  	CN                      string    `json:"cn"`
  	Fingerprint             string    `json:"certFingerprint"`
  	ExpiresAt               time.Time `json:"expiresAt"`
  	IssuedAt                time.Time `json:"issuedAt"`
  	Issuer                  string    `json:"issuer"`
  	KeySize                 int       `json:"keySize"`
  	SerialNumber            string    `json:"serialNumber"`
  	SubjectAlternativeNames []string  `json:"subjectAlternativeNames"`
  	Version                 int       `json:"version"`
  }
  
  func matchAndKeySize(publicKey crypto.PublicKey, privateKey crypto.PrivateKey) (string, int, bool) {
  	if algo, size, ok := rsaMatchAndKeySize(publicKey, privateKey); ok {
  		return algo, size, ok
  	}
  
  	pubKey, ok := publicKey.(*ecdsa.PublicKey)
  	if !ok {
  		return "", 0, false
  	}
  
  	privKey, ok := privateKey.(*ecdsa.PrivateKey)
  	if !ok {
  		return "", 0, false
  	}
  
  	return "ECC", 256, privKey.X.Cmp(pubKey.X) == 0 && privKey.Y.Cmp(privKey.Y) == 0
  }
  
  func rsaMatchAndKeySize(publicKey crypto.PublicKey, privateKey crypto.PrivateKey) (string, int, bool) {
  	pubKey, ok := publicKey.(*rsa.PublicKey)
  	if !ok {
  		return "", 0, false
  	}
  
  	privKey, ok := privateKey.(*rsa.PrivateKey)
  	if !ok {
  		return "", 0, false
  	}
  
  	return "RSA", len(privKey.N.Bytes()), pubKey.N.Cmp(privKey.N) == 0
  }
  
  func Info(pemCerts, pemKey string) (*CertificateInfo, error) {
  	block, _ := pem.Decode([]byte(pemKey))
  	if block == nil {
  		return nil, errors.New("failed to decode key: not valid pem format")
  	}
  
  	var key crypto.PrivateKey
  	var err error
  	if key, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
  		if key, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
  			if key, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
  				return nil, errors.Wrap(err, "failed to parse key: key must be PEM encoded EC, PKCS1, or PKCS8")
  			}
  		}
  	}
  
  	rest := []byte(pemCerts)
  	for {
  		block, rest = pem.Decode(rest)
  		var certInfo CertificateInfo
  
  		if block == nil {
  			break
  		}
  
  		cert, err := x509.ParseCertificate(block.Bytes)
  		if err != nil {
  			return nil, errors.Wrap(err, "failed to parse certificate")
  		}
  
  		algo, size, ok := matchAndKeySize(cert.PublicKey, key)
  		if !ok {
  			continue
  		}
  
  		certInfo.Algorithm = algo
  		certInfo.Fingerprint = fingerprint(block.Bytes)
  		certInfo.CN = cert.Subject.CommonName
  		certInfo.ExpiresAt = cert.NotAfter
  		certInfo.IssuedAt = cert.NotBefore
  		certInfo.Issuer = cert.Issuer.CommonName
  		certInfo.KeySize = size
  		certInfo.SerialNumber = cert.SerialNumber.String()
  		certInfo.Version = cert.Version
  
  		certInfo.SubjectAlternativeNames = append(certInfo.SubjectAlternativeNames, cert.DNSNames...)
  		for _, ip := range cert.IPAddresses {
  			certInfo.SubjectAlternativeNames = append(certInfo.SubjectAlternativeNames, ip.String())
  		}
  
  		return &certInfo, nil
  	}
  
  	return nil, fmt.Errorf("failed to find cert that matched private key")
  }
  
  func fingerprint(data []byte) string {
  	digest := sha1.Sum(data)
  	buf := &bytes.Buffer{}
  	for i := 0; i < len(digest); i++ {
  		if buf.Len() > 0 {
  			buf.WriteString(":")
  		}
  		buf.WriteString(strings.ToUpper(hex.EncodeToString(digest[i : i+1])))
  	}
  	return buf.String()
  }
  