bpicode/fritzctl

View on GitHub
cmd/certificate_export.go

Summary

Maintainability
A
0 mins
Test Coverage
package cmd

import (
    "crypto/sha256"
    "crypto/tls"
    "crypto/x509"
    "encoding/hex"
    "encoding/pem"
    "errors"
    "fmt"
    "io"
    "os"

    "github.com/bpicode/fritzctl/config"
    "github.com/bpicode/fritzctl/internal/console"
    "github.com/bpicode/fritzctl/internal/stringutils"
    "github.com/spf13/cobra"
)

var certExportCmd = &cobra.Command{
    Use:   "export",
    Short: "Export the TLS certificate offered by the FRITZ!Box",
    Long: `Export TLS certificate offered by the FRITZ!Box to stdout, encoded in PEM format.
The output can be redirected to a file or copied from the terminal output.
Use the data beginning with "-----BEGIN CERTIFICATE-----" and ending with "-----END CERTIFICATE-----".`,
    Example: "fritzctl certificate export > /path/to/certificate.pem",
    RunE:    certExport,
}

func init() {
    certCmd.AddCommand(certExportCmd)
}

func certExport(_ *cobra.Command, _ []string) error {
    cfg := mustReadConfig()
    conn := mustConnect(cfg)
    defer conn.Close()
    crt := mustHaveCert(conn)
    logCrt(&printableCert{crt}, os.Stderr)
    writeCrt(crt, os.Stdout)
    return nil
}

func mustReadConfig() *config.Config {
    c, err := cfg(defaultConfigPlaces...)
    assertNoErr(err, "cannot parse configuration")
    return c
}

func mustConnect(cfg *config.Config) *tls.Conn {
    conn, err := tls.Dial("tcp", connectionString(cfg), &tls.Config{InsecureSkipVerify: true})
    assertNoErr(err, "certificate export failed, cannot connect to remote")
    return conn
}

func mustHaveCert(conn *tls.Conn) *x509.Certificate {
    state := conn.ConnectionState()
    assertTrue(len(state.PeerCertificates) > 0, errors.New("certificate export failed, list of peer certificates is empty"))
    crt := state.PeerCertificates[len(state.PeerCertificates)-1]
    return crt
}

func writeCrt(crt *x509.Certificate, w io.Writer) {
    p := pem.Block{Type: "CERTIFICATE", Bytes: crt.Raw}
    pem.Encode(w, &p)
}

func logCrt(crt *printableCert, w io.Writer) {
    fmt.Fprintf(w, "%s %s\n", console.Cyan("Serial Number:      "), crt.serial())
    fmt.Fprintf(w, "%s %s\n", console.Cyan("Issued to:          "), crt.issuedTo())
    fmt.Fprintf(w, "%s %s\n", console.Cyan("Validity:           "), crt.validity())
    fmt.Fprintf(w, "%s %s\n", console.Cyan("SHA-256 fingerprint:"), crt.fingerprintSHA256())
}

func connectionString(cfg *config.Config) string {
    return fmt.Sprintf("%s:%s", cfg.Host, stringutils.DefaultIfEmpty(cfg.Port, "443"))
}

type printableCert struct {
    *x509.Certificate
}

func (c *printableCert) serial() string {
    sn := c.SerialNumber.Bytes
    return hex.EncodeToString(sn())
}

func (c *printableCert) fingerprintSHA256() string {
    sum256 := sha256.Sum256(c.Raw)
    return hex.EncodeToString(sum256[:])
}

func (c *printableCert) issuedTo() string {
    return fmt.Sprintf("CN='%s' O='%s'", c.Subject.CommonName, c.Subject.Organization)
}

func (c *printableCert) validity() string {
    return fmt.Sprintf("from %s to %s", c.NotBefore, c.NotAfter)
}