evReady/cmd/web/certificates.go (89 lines of code) (raw):
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"github.com/rs/xid"
"net"
"os"
"strings"
"time"
)
type CertChain struct {
Hostname string
IP string
Certs []string
}
// getCertFromHost reaches out to the submitted hostname and retrieves its certificates
func getCertFromHost(hostname, port string, skipVerify bool) ([]*x509.Certificate, string, error) {
config := tls.Config{InsecureSkipVerify: skipVerify}
canonicalName := hostname + ":" + port
ip := ""
dialer := &net.Dialer{
Timeout: 10 * time.Second,
}
conn, err := tls.DialWithDialer(dialer, "tcp", canonicalName, &config)
if err != nil {
return nil, ip, err
}
defer conn.Close()
ip = strings.TrimSuffix(conn.RemoteAddr().String(), ":443")
certs := conn.ConnectionState().PeerCertificates
if certs == nil {
return nil, ip, errors.New("could not get server's certificate from the TLS connection")
}
return certs, ip, nil
}
// certConvert is used to convert raw bytes into a string for cert creation
func certConvert(rawCert []byte) string {
certString := base64.StdEncoding.EncodeToString(rawCert)
lineLength := 64
runes := []rune(certString)
prefix := "-----BEGIN CERTIFICATE-----"
suffix := "-----END CERTIFICATE-----\n"
pem := make([]string, 0)
pem = append(pem, prefix)
for i := 0; i < len(runes); i += lineLength {
if i+lineLength < len(runes) {
pem = append(pem, string(runes[i:(i+lineLength)]))
} else {
pem = append(pem, string(runes[i:]))
}
}
pem = append(pem, suffix)
return strings.Join(pem, "\n")
}
// pemCreator creates a valid PEM file which is used for ev-checker
func (app *application) pemCreator(hostname, rootCert string) (string, error) {
certs, ip, err := getCertFromHost(hostname, "443", true)
if err != nil || certs == nil {
app.logger.Error("Unable to retrieve cert from host.", "hostname", hostname, "error", err.Error())
}
certChain := CertChain{
Hostname: hostname,
IP: ip,
}
f, err := os.Create("/tmp/" + xid.New().String() + ".pem")
if err != nil {
app.logger.Error("Unable to create certs file.", "error", err.Error())
}
defer f.Close()
for _, cert := range certs {
_, err := f.WriteString(certConvert(cert.Raw))
if err != nil {
app.logger.Error("Unable to write certs to file.", "error", err.Error())
}
certChain.Certs = append(certChain.Certs, base64.StdEncoding.EncodeToString(cert.Raw))
}
_, err = f.WriteString(rootCert)
if err != nil {
app.logger.Error("Unable to write cert to PEM chain file.", "error", err.Error())
}
return f.Name(), f.Sync()
}
// certCleanup removes the cert submitted after ev-checker runs to keep things tidy
func (app *application) certCleanup(pemFile string) {
err := os.RemoveAll(pemFile)
if err != nil {
app.logger.Error("Unable to delete PEM files or directories", "Error", err.Error())
} else {
app.logger.Info("Removed unused PEM file", "File", pemFile)
}
}