134 lines
3.4 KiB
Go
134 lines
3.4 KiB
Go
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
|
|
|
|
for _, name := range cert.DNSNames {
|
|
certInfo.SubjectAlternativeNames = append(certInfo.SubjectAlternativeNames, name)
|
|
}
|
|
|
|
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()
|
|
}
|