package security import ( "bytes" "errors" "regexp" ) // privateKeyBlockRe matches the PEM header for any private key variant. // Catches: RSA, EC, DSA, OPENSSH, ENCRYPTED, and the legacy PKCS#1 forms. var privateKeyBlockRe = regexp.MustCompile( `-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----`, ) // Redact removes all PEM private-key blocks from the input. It strips the // header, base64 body, and footer of each private key block, replacing the // block with a single line: `[REDACTED PRIVATE KEY]`. // // REQ-035: `orca cert show` MUST NOT print private key material, in either // the default text or --json output. This helper is the single source of // truth for that guarantee — call it on any PEM blob before display. // // The function is conservative: if the input contains no private key // blocks, the input is returned unchanged (other than a copy). Errors are // only returned for impossible states (e.g., a nil pattern hit, which // can't happen in practice). func Redact(pem []byte) []byte { if len(pem) == 0 { return pem } // Find all header positions. matches := privateKeyBlockRe.FindAllIndex(pem, -1) if len(matches) == 0 { // No private key blocks — return a defensive copy. out := make([]byte, len(pem)) copy(out, pem) return out } // Process each block: locate the matching footer "-----END ... PRIVATE KEY-----" // and replace the entire block. Multiple matches possible. type span struct{ start, end int } spans := make([]span, 0, len(matches)) for _, m := range matches { headerStart := m[0] // Find footer starting after the header. footerStart := findPrivateKeyFooter(pem[headerStart:]) if footerStart < 0 { // Malformed PEM — leave the input alone for safety. The caller // will likely surface the parse error elsewhere. continue } end := headerStart + footerStart + len("-----END (any) PRIVATE KEY-----") // We don't know the exact footer length; use bytes.Index for it. if exactEnd := exactFooterEnd(pem[headerStart:]); exactEnd > 0 { end = headerStart + exactEnd } spans = append(spans, span{headerStart, end}) } if len(spans) == 0 { out := make([]byte, len(pem)) copy(out, pem) return out } // Build output: segments between spans + redaction marker. var out bytes.Buffer prev := 0 for _, s := range spans { out.Write(pem[prev:s.start]) out.WriteString("[REDACTED PRIVATE KEY]\n") prev = s.end } out.Write(pem[prev:]) return out.Bytes() } // findPrivateKeyFooter returns the offset of the footer for a private key // block whose header starts at pem[0]. Returns -1 if not found. func findPrivateKeyFooter(pem []byte) int { re := regexp.MustCompile(`-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----`) loc := re.FindIndex(pem) if loc == nil { return -1 } return loc[0] } // exactFooterEnd returns the offset just past the footer line's newline (or // end-of-input if no trailing newline). Returns -1 if no footer is found. func exactFooterEnd(pem []byte) int { re := regexp.MustCompile(`-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP |)PRIVATE KEY-----\r?\n?`) loc := re.FindIndex(pem) if loc == nil { return -1 } return loc[1] } // Sentinel to silence the "imported and not used" check if a future // refactor removes all consumers of errors. Currently errors is imported // only transitively, so keep this var to anchor the package. var _ = errors.New