aboutsummaryrefslogblamecommitdiff
path: root/cmd/ssh-cert-info/main.go
blob: 957dd9387a4561693eb4448ea7af3f203e56bd9c (plain) (tree)

































































































































































                                                                                           
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
)

func main() {
	sshDir, err := getSSHDirectory()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error getting SSH directory: %v\n", err)
		os.Exit(1)
	}

	certFiles, err := findCertificateFiles(sshDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error reading SSH directory %s: %v\n", sshDir, err)
		os.Exit(1)
	}

	if len(certFiles) == 0 {
		fmt.Printf("No SSH certificates found in %s\n", sshDir)
		return
	}

	fmt.Println("SSH Certificate Report")
	fmt.Println("======================")
	fmt.Println()

	for _, certPath := range certFiles {
		checkCertificate(certPath)
		fmt.Println()
	}
}

func getSSHDirectory() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("failed to get home directory: %w", err)
	}
	return filepath.Join(home, ".ssh"), nil
}

func findCertificateFiles(sshDir string) ([]string, error) {
	entries, err := os.ReadDir(sshDir)
	if err != nil {
		return nil, err
	}

	var certFiles []string
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		name := entry.Name()
		if strings.HasSuffix(name, ".pub") && strings.Contains(name, "-cert.pub") {
			certFiles = append(certFiles, filepath.Join(sshDir, name))
		}
	}

	return certFiles, nil
}

func checkCertificate(certPath string) {
	fmt.Printf("Certificate: %s\n", certPath)

	certData, err := os.ReadFile(certPath)
	if err != nil {
		fmt.Printf("  Error: Failed to read certificate file: %v\n", err)
		return
	}

	pubKey, _, _, _, err := ssh.ParseAuthorizedKey(certData)
	if err != nil {
		fmt.Printf("  Error: Failed to parse certificate: %v\n", err)
		return
	}

	cert, ok := pubKey.(*ssh.Certificate)
	if !ok {
		fmt.Printf("  Error: Not an SSH certificate\n")
		return
	}

	displayCertificateInfo(cert)
}

func displayCertificateInfo(cert *ssh.Certificate) {
	certType := "Unknown"
	switch cert.CertType {
	case ssh.UserCert:
		certType = "ssh-ed25519-cert-v01@openssh.com user certificate"
	case ssh.HostCert:
		certType = "ssh-ed25519-cert-v01@openssh.com host certificate"
	}
	fmt.Printf("  Type: %s\n", certType)

	fmt.Printf("  Public key: %s %s\n", cert.Key.Type(), cert.KeyId)

	fmt.Printf("  Signing CA: %s\n", cert.SignatureKey.Type())

	fmt.Printf("  Key ID: %s\n", cert.KeyId)

	fmt.Printf("  Serial: %d\n", cert.Serial)

	validFrom := time.Unix(int64(cert.ValidAfter), 0)
	validTo := time.Unix(int64(cert.ValidBefore), 0)

	fmt.Printf("  Valid from: %s\n", validFrom.Format("2006-01-02T15:04:05"))

	if cert.ValidBefore == ssh.CertTimeInfinity {
		fmt.Printf("  Valid to: forever\n")
		fmt.Println("  ✓ Certificate is currently valid")
	} else {
		fmt.Printf("  Valid to: %s\n", validTo.Format("2006-01-02T15:04:05"))
		checkCertificateExpiration(validTo)
	}

	if len(cert.ValidPrincipals) > 0 {
		fmt.Println("  Principals:")
		for _, principal := range cert.ValidPrincipals {
			fmt.Printf("    %s\n", principal)
		}
	}

	if len(cert.CriticalOptions) > 0 {
		fmt.Println("  Critical Options:")
		for key, value := range cert.CriticalOptions {
			if value == "" {
				fmt.Printf("    %s\n", key)
			} else {
				fmt.Printf("    %s %s\n", key, value)
			}
		}
	}

	if len(cert.Extensions) > 0 {
		fmt.Println("  Extensions:")
		for key, value := range cert.Extensions {
			if value == "" {
				fmt.Printf("    %s\n", key)
			} else {
				fmt.Printf("    %s %s\n", key, value)
			}
		}
	}
}

func checkCertificateExpiration(validTo time.Time) {
	now := time.Now()
	if validTo.Before(now) {
		fmt.Println("  ⚠️  WARNING: Certificate has EXPIRED!")
	} else {
		fmt.Println("  ✓ Certificate is currently valid")
	}
}