1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
package x509util
import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"io/ioutil"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/cli/errs"
)
// Fingerprint returns the SHA-256 fingerprint of the certificate.
func Fingerprint(cert *x509.Certificate) string {
return EncodedFingerprint(cert, HexFingerprint)
}
type FingerprintEncoding int
const (
HexFingerprint FingerprintEncoding = iota
Base64Fingerprint
Base64UrlFingerprint
)
// EncodedFingerprint returns an encoded the SHA-256 fingerprint of the certificate. Defaults to hex encoding
func EncodedFingerprint(cert *x509.Certificate, encoding FingerprintEncoding) string {
sum := sha256.Sum256(cert.Raw)
if encoding == HexFingerprint {
return strings.ToLower(hex.EncodeToString(sum[:]))
}
src := make([]byte, len(sum))
for i, b := range sum {
src[i] = b
}
switch encoding {
case Base64Fingerprint:
return base64.StdEncoding.EncodeToString(src)
case Base64UrlFingerprint:
return base64.URLEncoding.EncodeToString(src)
}
// should not get here
return ""
}
// SplitSANs splits a slice of Subject Alternative Names into slices of
// IP Addresses and DNS Names. If an element is not an IP address, then it
// is bucketed as a DNS Name.
func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string, uris []*url.URL) {
dnsNames = []string{}
ips = []net.IP{}
emails = []string{}
uris = []*url.URL{}
if sans == nil {
return
}
for _, san := range sans {
if ip := net.ParseIP(san); ip != nil {
ips = append(ips, ip)
} else if u, err := url.Parse(san); err == nil && u.Scheme != "" {
uris = append(uris, u)
} else if strings.Contains(san, "@") {
emails = append(emails, san)
} else {
dnsNames = append(dnsNames, san)
}
}
return
}
// ReadCertPool loads a certificate pool from disk.
// *path*: a file, a directory, or a comma-separated list of files.
func ReadCertPool(path string) (*x509.CertPool, error) {
info, err := os.Stat(path)
if err != nil && !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "os.Stat %s failed", path)
}
var (
files []string
pool = x509.NewCertPool()
)
if info != nil && info.IsDir() {
finfos, err := ioutil.ReadDir(path)
if err != nil {
return nil, errs.FileError(err, path)
}
for _, finfo := range finfos {
files = append(files, filepath.Join(path, finfo.Name()))
}
} else {
files = strings.Split(path, ",")
for i := range files {
files[i] = strings.TrimSpace(files[i])
}
}
var pems []byte
for _, f := range files {
bytes, err := ioutil.ReadFile(f)
if err != nil {
return nil, errs.FileError(err, f)
}
for len(bytes) > 0 {
var block *pem.Block
block, bytes = pem.Decode(bytes)
if block == nil {
// TODO: at a higher log level we should log the file we could not find.
break
}
// Ignore PEM blocks that are not CERTIFICATEs.
if block.Type != "CERTIFICATE" {
continue
}
pems = append(pems, pem.EncodeToMemory(block)...)
}
}
if ok := pool.AppendCertsFromPEM(pems); !ok {
return nil, errors.Errorf("error loading Root certificates")
}
return pool, nil
}
|