TLS Cert Chain Resolver is a CLI tool designed to resolve and manage TLS certificate chains efficiently. This tool is inspired by zakjan/cert-chain-resolver, but offers a more maintainable codebase and is actively maintained.
- Resolve TLS certificate chains
- Output in PEM, DER, or JSON format. The JSON format includes PEM-encoded certificates with their chains.
- Optionally include system root CAs
- Efficient memory usage with buffer pooling
To install the tool, use the following command:
go install github.com/H0llyW00dzZ/tls-cert-chain-resolver@latesttls-cert-chain-resolver -f [INPUT_FILE] [FLAGS]-f, --file: Input certificate file (required)-o, --output: Output to a specified file (default: stdout)-i, --intermediate-only: Output intermediate certificates only-d, --der: Output in DER format-s, --include-system: Include root CA from the system in output-j, --json: Output in JSON format containing PEM for listed certificates with their chains
Note
If you encounter issues installing with go install github.com/H0llyW00dzZ/tls-cert-chain-resolver@latest, try using go install github.com/H0llyW00dzZ/tls-cert-chain-resolver/cmd@latest or build manually from source with make build-linux, make build-macos, or make build-windows.
- Go 1.25.3 or later
Clone the repository:
git clone https://github.com/H0llyW00dzZ/tls-cert-chain-resolver.git
cd tls-cert-chain-resolverBuild the project for Linux:
make build-linuxBuild the project for macOS:
make build-macosBuild the project for Windows:
make build-windowsThis tool is compatible with Go 1.25.3 or later and works effectively across various clients (e.g., HTTP clients in Go, mobile browsers, OpenSSL). It resolves chaining issues, providing enhanced flexibility and control over certificate chain resolution.
h0llyw00dzz@ubuntu-pro:~/Workspace/git/tls-cert-chain-resolver$ ./bin/linux/tls-cert-chain-resolver -f test-leaf.cer -o test-output-bundle.pem
Starting TLS certificate chain resolver (v0.2.5)...
Note: Press CTRL+C or send a termination signal (e.g., SIGINT or SIGTERM) via your operating system to exit if incomplete (e.g., hanging while fetching certificates).
1: *.b0zal.io
2: Sectigo ECC Domain Validation Secure Server CA
3: USERTrust ECC Certification Authority
Output successfully written to test-output-bundle.pem.
Certificate chain complete. Total 3 certificate(s) found.
Certificate chain resolution completed successfully.
TLS certificate chain resolver stopped.- Verification:
h0llyw00dzz@ubuntu-pro:~/Workspace/git/tls-cert-chain-resolver$ openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted test-output-bundle.pem test-output-bundle.pem
test-output-bundle.pem: OKNote
These examples demonstrate the tool's effectiveness in resolving and verifying certificate chains using OpenSSL.
This tool can be integrated with Model Context Protocol (MCP) servers for automated certificate chain resolution. The MCP integration allows AI assistants and other tools to resolve TLS certificate chains programmatically.
Note
The examples below use the github.com/mark3labs/mcp-go library, which is a community-maintained Go implementation of MCP. This is not the official MCP SDK from github.com/modelcontextprotocol/go-sdk. Both libraries implement the MCP specification, but they have different APIs and features. Choose the one that best fits your needs.
Here's a complete example of an MCP server that exposes certificate chain resolution as a tool:
package main
import (
"context"
"encoding/base64"
"fmt"
"os"
x509certs "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/certs"
x509chain "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/chain"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create MCP server
s := server.NewMCPServer(
"TLS Certificate Chain Resolver",
"1.0.0",
server.WithToolCapabilities(true),
)
// Define certificate chain resolution tool
resolveCertChainTool := mcp.NewTool("resolve_cert_chain",
mcp.WithDescription("Resolve TLS certificate chain from a certificate file or base64-encoded certificate data"),
mcp.WithString("certificate",
mcp.Required(),
mcp.Description("Certificate file path or base64-encoded certificate data"),
),
mcp.WithString("format",
mcp.Description("Output format: 'pem', 'der', or 'json' (default: pem)"),
mcp.DefaultString("pem"),
),
mcp.WithBoolean("include_system_root",
mcp.Description("Include system root CA in output (default: false)"),
),
mcp.WithBoolean("intermediate_only",
mcp.Description("Output only intermediate certificates (default: false)"),
),
)
// Register tool handler
s.AddTool(resolveCertChainTool, handleResolveCertChain)
// Start server
if err := server.ServeStdio(s); err != nil {
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
os.Exit(1)
}
}
func handleResolveCertChain(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Extract arguments
certInput, err := request.RequireString("certificate")
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("certificate parameter required: %v", err)), nil
}
format, _ := request.GetString("format", "pem")
includeSystemRoot, _ := request.GetBool("include_system_root", false)
intermediateOnly, _ := request.GetBool("intermediate_only", false)
// Read certificate data
var certData []byte
// Try to read as file first
if fileData, err := os.ReadFile(certInput); err == nil {
certData = fileData
} else {
// Try to decode as base64
if decoded, err := base64.StdEncoding.DecodeString(certInput); err == nil {
certData = decoded
} else {
return mcp.NewToolResultError(fmt.Sprintf("failed to read certificate: not a valid file path or base64 data")), nil
}
}
// Decode certificate
certManager := x509certs.New()
cert, err := certManager.Decode(certData)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to decode certificate: %v", err)), nil
}
// Fetch certificate chain
chain := x509chain.New(cert, "1.0.0")
if err := chain.FetchCertificate(ctx); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to fetch certificate chain: %v", err)), nil
}
// Optionally add system root CA
if includeSystemRoot {
if err := chain.AddRootCA(); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to add root CA: %v", err)), nil
}
}
// Filter certificates if needed
certs := chain.Certs
if intermediateOnly {
certs = chain.FilterIntermediates()
}
// Format output
var output string
switch format {
case "der":
derData := certManager.EncodeMultipleDER(certs)
output = base64.StdEncoding.EncodeToString(derData)
case "json":
output = formatJSON(certs, certManager)
default: // pem
pemData := certManager.EncodeMultiplePEM(certs)
output = string(pemData)
}
// Build result with chain information
chainInfo := fmt.Sprintf("Certificate chain resolved successfully:\n")
for i, c := range certs {
chainInfo += fmt.Sprintf("%d: %s\n", i+1, c.Subject.CommonName)
}
chainInfo += fmt.Sprintf("\nTotal: %d certificate(s)\n\n", len(certs))
chainInfo += output
return mcp.NewToolResultText(chainInfo), nil
}
func formatJSON(certs []*x509.Certificate, certManager *x509certs.Certificate) string {
type CertInfo struct {
Subject string `json:"subject"`
Issuer string `json:"issuer"`
Serial string `json:"serial"`
SignatureAlgorithm string `json:"signatureAlgorithm"`
PEM string `json:"pem"`
}
certInfos := make([]CertInfo, len(certs))
for i, cert := range certs {
pemData := certManager.EncodePEM(cert)
certInfos[i] = CertInfo{
Subject: cert.Subject.CommonName,
Issuer: cert.Issuer.CommonName,
Serial: cert.SerialNumber.String(),
SignatureAlgorithm: cert.SignatureAlgorithm.String(),
PEM: string(pemData),
}
}
output := map[string]interface{}{
"title": "TLS Certificate Chain",
"totalChained": len(certs),
"listCertificates": certInfos,
}
jsonData, _ := json.MarshalIndent(output, "", " ")
return string(jsonData)
}To use this MCP server with an MCP-compatible client (like Claude Desktop or other AI assistants), you need to configure it in your MCP settings:
{
"mcpServers": {
"tls-cert-resolver": {
"command": "go",
"args": ["run", "path/to/your/mcp-server.go"]
}
}
}The tool can then be called by the AI assistant with parameters like:
{
"name": "resolve_cert_chain",
"arguments": {
"certificate": "/path/to/certificate.pem",
"format": "json",
"include_system_root": true
}
}You can also integrate with MCP by executing the pre-built binary directly, without using the Go API. This approach is useful when you want to avoid Go dependencies or prefer using the standalone binary in an MCP tool server.
package main
import (
"context"
"fmt"
"os"
"os/exec"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewMCPServer(
"TLS Certificate Chain Resolver CLI",
"1.0.0",
server.WithToolCapabilities(true),
)
resolveCertChainTool := mcp.NewTool("resolve_cert_chain_cli",
mcp.WithDescription("Resolve TLS certificate chain using the tls-cert-chain-resolver binary"),
mcp.WithString("certificate_file",
mcp.Required(),
mcp.Description("Path to certificate file"),
),
mcp.WithString("output_file",
mcp.Description("Output file path (optional, defaults to stdout)"),
),
mcp.WithString("format",
mcp.Description("Output format: 'pem', 'der', or 'json' (default: pem)"),
mcp.DefaultString("pem"),
),
mcp.WithBoolean("include_system_root",
mcp.Description("Include system root CA in output"),
),
mcp.WithBoolean("intermediate_only",
mcp.Description("Output only intermediate certificates"),
),
)
s.AddTool(resolveCertChainTool, handleResolveCertChainCLI)
if err := server.ServeStdio(s); err != nil {
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
os.Exit(1)
}
}
func handleResolveCertChainCLI(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
certFile, err := request.RequireString("certificate_file")
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("certificate_file parameter required: %v", err)), nil
}
outputFile, _ := request.GetString("output_file", "")
format, _ := request.GetString("format", "pem")
includeSystemRoot, _ := request.GetBool("include_system_root", false)
intermediateOnly, _ := request.GetBool("intermediate_only", false)
args := []string{"-f", certFile}
if outputFile != "" {
args = append(args, "-o", outputFile)
}
if format == "der" {
args = append(args, "-d")
} else if format == "json" {
args = append(args, "-j")
}
if includeSystemRoot {
args = append(args, "-s")
}
if intermediateOnly {
args = append(args, "-i")
}
cmd := exec.CommandContext(ctx, "tls-cert-chain-resolver", args...)
output, err := cmd.CombinedOutput()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("command failed: %v\nOutput: %s", err, string(output))), nil
}
result := string(output)
if outputFile != "" {
fileContent, err := os.ReadFile(outputFile)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to read output file: %v", err)), nil
}
result = fmt.Sprintf("Command output:\n%s\n\nFile content:\n%s", result, string(fileContent))
}
return mcp.NewToolResultText(result), nil
}Add this to your Claude Desktop MCP settings:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Note
Claude Desktop is currently not available on Linux. For Linux users, you can use other MCP-compatible clients or run the MCP server directly with the stdio transport for testing and development purposes.
{
"mcpServers": {
"tls-cert-resolver-cli": {
"command": "/path/to/tls-cert-chain-resolver-mcp-server"
}
}
}Make sure the tls-cert-chain-resolver binary is in your system PATH, or update the exec.CommandContext call to use the full path to the binary.
Once configured, you can ask Claude to resolve certificate chains:
Resolve the certificate chain for example.com certificate at /path/to/cert.pem
Claude will call the tool with appropriate parameters:
{
"name": "resolve_cert_chain_cli",
"arguments": {
"certificate_file": "/path/to/cert.pem",
"format": "json",
"include_system_root": true
}
}The tool will execute the binary and return the resolved certificate chain information.
package main
import (
"context"
"crypto/x509"
"fmt"
"log"
"os"
x509certs "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/certs"
x509chain "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/chain"
)
func main() {
certData, err := os.ReadFile("test-leaf.cer")
if err != nil {
log.Fatal(err)
}
certManager := x509certs.New()
cert, err := certManager.Decode(certData)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
chain := x509chain.New(cert, "1.0.0")
if err := chain.FetchCertificate(ctx); err != nil {
log.Fatal(err)
}
for i, c := range chain.Certs {
fmt.Printf("%d: %s\n", i+1, c.Subject.CommonName)
}
pemOutput := certManager.EncodeMultiplePEM(chain.Certs)
if err := os.WriteFile("output-bundle.pem", pemOutput, 0644); err != nil {
log.Fatal(err)
}
}package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
x509certs "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/certs"
x509chain "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/chain"
)
type CertificateInfo struct {
Subject string `json:"subject"`
Issuer string `json:"issuer"`
Serial string `json:"serial"`
SignatureAlgorithm string `json:"signatureAlgorithm"`
PEM string `json:"pem"`
}
type JSONOutput struct {
Title string `json:"title"`
TotalChained int `json:"totalChained"`
Certificates []CertificateInfo `json:"listCertificates"`
}
func main() {
certData, err := os.ReadFile("test-leaf.cer")
if err != nil {
log.Fatal(err)
}
certManager := x509certs.New()
cert, err := certManager.Decode(certData)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
chain := x509chain.New(cert, "1.0.0")
if err := chain.FetchCertificate(ctx); err != nil {
log.Fatal(err)
}
certInfos := make([]CertificateInfo, len(chain.Certs))
for i, c := range chain.Certs {
pemData := certManager.EncodePEM(c)
certInfos[i] = CertificateInfo{
Subject: c.Subject.CommonName,
Issuer: c.Issuer.CommonName,
Serial: c.SerialNumber.String(),
SignatureAlgorithm: c.SignatureAlgorithm.String(),
PEM: string(pemData),
}
}
output := JSONOutput{
Title: "TLS Certificate Resolver",
TotalChained: len(chain.Certs),
Certificates: certInfos,
}
jsonData, err := json.MarshalIndent(output, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
}package main
import (
"context"
"crypto/x509"
"fmt"
"log"
"os"
x509certs "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/certs"
x509chain "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/chain"
)
func main() {
certData, err := os.ReadFile("test-leaf.cer")
if err != nil {
log.Fatal(err)
}
certManager := x509certs.New()
cert, err := certManager.Decode(certData)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
chain := x509chain.New(cert, "1.0.0")
if err := chain.FetchCertificate(ctx); err != nil {
log.Fatal(err)
}
if err := chain.AddRootCA(); err != nil {
log.Fatal(err)
}
for i, c := range chain.Certs {
fmt.Printf("%d: %s (Root: %v)\n", i+1, c.Subject.CommonName, chain.IsRootNode(c))
}
intermediates := chain.FilterIntermediates()
fmt.Printf("\nFound %d intermediate certificate(s)\n", len(intermediates))
pemOutput := certManager.EncodeMultiplePEM(chain.Certs)
if err := os.WriteFile("output-bundle-with-root.pem", pemOutput, 0644); err != nil {
log.Fatal(err)
}
}package main
import (
"fmt"
"log"
"os"
x509certs "github.com/H0llyW00dzZ/tls-cert-chain-resolver/src/internal/x509/certs"
)
func main() {
bundleData, err := os.ReadFile("certificate-bundle.pem")
if err != nil {
log.Fatal(err)
}
certManager := x509certs.New()
certs, err := certManager.DecodeMultiple(bundleData)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d certificate(s) in bundle:\n", len(certs))
for i, cert := range certs {
fmt.Printf("%d: %s\n", i+1, cert.Subject.CommonName)
fmt.Printf(" Issuer: %s\n", cert.Issuer.CommonName)
fmt.Printf(" Valid: %s to %s\n", cert.NotBefore, cert.NotAfter)
}
}New() *Certificate: Create new certificate managerDecode(data []byte) (*x509.Certificate, error): Decode single certificate from PEM/DER/PKCS7DecodeMultiple(data []byte) ([]*x509.Certificate, error): Decode multiple certificatesEncodePEM(cert *x509.Certificate) []byte: Encode certificate to PEMEncodeDER(cert *x509.Certificate) []byte: Encode certificate to DEREncodeMultiplePEM(certs []*x509.Certificate) []byte: Encode multiple certificates to PEMEncodeMultipleDER(certs []*x509.Certificate) []byte: Encode multiple certificates to DER
New(cert *x509.Certificate, version string) *Chain: Create new chain managerFetchCertificate(ctx context.Context) error: Fetch complete certificate chainAddRootCA() error: Add system root CA to chainFilterIntermediates() []*x509.Certificate: Get only intermediate certificatesIsRootNode(cert *x509.Certificate) bool: Check if certificate is rootIsSelfSigned(cert *x509.Certificate) bool: Check if certificate is self-signedVerifyChain() error: Verify the certificate chain validity
This project was created to provide a more maintainable and actively maintained version of the original zakjan/cert-chain-resolver, which is no longer maintained.
- Maintain compatibility with
github.com/mark3labs/mcp-go - Create abstraction layer for both MCP libraries
- Document differences and use cases for each library
- Create standalone MCP server binary in
cmd/mcp-server/ - Add configuration file support for MCP server settings
- Implement streaming support for large certificate chains
- Add MCP server tests with mock certificate data
- Add metrics and logging for MCP server operations
- Add support for certificate validation through MCP tool
- Implement certificate expiry checking via MCP
- Add batch certificate resolution support
- Support for remote certificate fetching via URL/hostname
- Document MCP server deployment options (Docker, systemd, etc.)
- Create example MCP client implementations for both libraries
- Create MCP server configuration examples for different platforms
- Add troubleshooting guide for MCP integration
This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.