package security import ( "crypto/sha256" "crypto/x509" "encoding/hex" "encoding/pem" "errors" "fmt" "os" ) // Fingerprint returns the SHA-256 hex digest of the certificate's DER bytes, // computed from the on-disk PEM at certPath. The output is lowercase hex // (64 chars) and matches the value operators see with `openssl x509 -fingerprint // -sha256 -noout`. Used for the `orca node join --ca-fingerprint ` pin. func Fingerprint(certPath string) (string, error) { if certPath == "" { return "", errors.New("Fingerprint: certPath is required") } pemBytes, err := os.ReadFile(certPath) if err != nil { return "", fmt.Errorf("Fingerprint: read cert: %w", err) } der, err := firstCertDER(pemBytes) if err != nil { return "", fmt.Errorf("Fingerprint: %w", err) } return FingerprintOf(der), nil } // FingerprintOf returns the SHA-256 hex digest of a DER-encoded certificate. // Lowercase hex; matches `openssl ... -fingerprint -sha256` output. func FingerprintOf(der []byte) string { sum := sha256.Sum256(der) return hex.EncodeToString(sum[:]) } // firstCertDER decodes PEM bytes and returns the DER of the first // CERTIFICATE block. Errors if the input is empty or no CERTIFICATE block // is present. func firstCertDER(pemBytes []byte) ([]byte, error) { if len(pemBytes) == 0 { return nil, errors.New("empty input") } block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("no PEM data found") } if block.Type != "CERTIFICATE" { return nil, fmt.Errorf("unexpected PEM type %q, want CERTIFICATE", block.Type) } // Re-parse through x509 to validate the cert is well-formed. if _, err := x509.ParseCertificate(block.Bytes); err != nil { return nil, fmt.Errorf("parse certificate: %w", err) } return block.Bytes, nil }