|
| 1 | +/* |
| 2 | +Copyright © 2025 xiexianbin.cn |
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +you may not use this file except in compliance with the License. |
| 5 | +You may obtain a copy of the License at |
| 6 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +Unless required by applicable law or agreed to in writing, software |
| 8 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 9 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 10 | +See the License for the specific language governing permissions and |
| 11 | +limitations under the License. |
| 12 | +*/ |
| 13 | + |
| 14 | +package main |
| 15 | + |
| 16 | +import ( |
| 17 | + "crypto/ecdsa" |
| 18 | + "crypto/rsa" |
| 19 | + "crypto/x509" |
| 20 | + "crypto/x509/pkix" |
| 21 | + "encoding/asn1" |
| 22 | + "encoding/pem" |
| 23 | + "fmt" |
| 24 | + "math/big" |
| 25 | + "os" |
| 26 | + "strings" |
| 27 | + |
| 28 | + "github.com/spf13/cobra" |
| 29 | +) |
| 30 | + |
| 31 | +var ( |
| 32 | + certfilePath string |
| 33 | +) |
| 34 | + |
| 35 | +// OIDs for various X.509 extensions. |
| 36 | +var ( |
| 37 | + oidExtensionSubjectKeyId = asn1.ObjectIdentifier{2, 5, 29, 14} |
| 38 | + oidExtensionKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15} |
| 39 | + oidExtensionExtendedKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37} |
| 40 | + oidExtensionAuthorityKeyId = asn1.ObjectIdentifier{2, 5, 29, 35} |
| 41 | + oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17} |
| 42 | + oidExtensionBasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19} |
| 43 | + oidExtensionCRLDistributionPoints = asn1.ObjectIdentifier{2, 5, 29, 31} |
| 44 | + oidExtensionCertificatePolicies = asn1.ObjectIdentifier{2, 5, 29, 32} |
| 45 | + oidAuthorityInfoAccess = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1} |
| 46 | +) |
| 47 | + |
| 48 | +// Maps OIDs to their string representations for better readability. |
| 49 | +var oidToStringMap = map[string]string{ |
| 50 | + "2.5.29.14": "X509v3 Subject Key Identifier", |
| 51 | + "2.5.29.15": "X509v3 Key Usage", |
| 52 | + "2.5.29.19": "X509v3 Basic Constraints", |
| 53 | + "2.5.29.31": "X509v3 CRL Distribution Points", |
| 54 | + "2.5.29.32": "X509v3 Certificate Policies", |
| 55 | + "2.5.29.35": "X509v3 Authority Key Identifier", |
| 56 | + "2.5.29.37": "X509v3 Extended Key Usage", |
| 57 | + "2.5.29.17": "X509v3 Subject Alternative Name", |
| 58 | + "1.3.6.1.5.5.7.1.1": "Authority Information Access", |
| 59 | +} |
| 60 | + |
| 61 | +func printCertificateInfo(cert *x509.Certificate) { |
| 62 | + fmt.Println("Certificate:") |
| 63 | + fmt.Println(" Data:") |
| 64 | + // Version |
| 65 | + fmt.Printf(" Version: %d (0x%x)\n", cert.Version, cert.Version-1) |
| 66 | + |
| 67 | + // Serial Number |
| 68 | + fmt.Printf(" Serial Number:\n %s\n", formatSerialNumber(cert.SerialNumber)) |
| 69 | + |
| 70 | + // Signature Algorithm |
| 71 | + fmt.Printf(" Signature Algorithm: %s\n", cert.SignatureAlgorithm.String()) |
| 72 | + |
| 73 | + // Issuer |
| 74 | + fmt.Printf(" Issuer: %s\n", formatName(cert.Issuer)) |
| 75 | + |
| 76 | + // Validity |
| 77 | + fmt.Println(" Validity") |
| 78 | + fmt.Printf(" Not Before: %s\n", cert.NotBefore.UTC().Format("Jan 2 15:04:05 2006 GMT")) |
| 79 | + fmt.Printf(" Not After : %s\n", cert.NotAfter.UTC().Format("Jan 2 15:04:05 2006 GMT")) |
| 80 | + |
| 81 | + // Subject |
| 82 | + fmt.Printf(" Subject: %s\n", formatName(cert.Subject)) |
| 83 | + |
| 84 | + // Public Key |
| 85 | + fmt.Println(" Subject Public Key Info:") |
| 86 | + printPublicKeyInfo(cert.PublicKey) |
| 87 | + |
| 88 | + // Extensions |
| 89 | + if len(cert.Extensions) > 0 { |
| 90 | + fmt.Println(" X509v3 extensions:") |
| 91 | + printExtensions(cert) |
| 92 | + } |
| 93 | + |
| 94 | + // Signature |
| 95 | + fmt.Printf(" Signature Algorithm: %s\n", cert.SignatureAlgorithm.String()) |
| 96 | + printHexBlock(" ", cert.Signature, 18) |
| 97 | +} |
| 98 | + |
| 99 | +func formatName(name pkix.Name) string { |
| 100 | + var parts []string |
| 101 | + if len(name.Country) > 0 { |
| 102 | + parts = append(parts, "C="+strings.Join(name.Country, ",")) |
| 103 | + } |
| 104 | + if len(name.Province) > 0 { |
| 105 | + parts = append(parts, "ST="+strings.Join(name.Province, ",")) |
| 106 | + } |
| 107 | + if len(name.Locality) > 0 { |
| 108 | + parts = append(parts, "L="+strings.Join(name.Locality, ",")) |
| 109 | + } |
| 110 | + if len(name.Organization) > 0 { |
| 111 | + parts = append(parts, "O="+strings.Join(name.Organization, ",")) |
| 112 | + } |
| 113 | + if len(name.OrganizationalUnit) > 0 { |
| 114 | + parts = append(parts, "OU="+strings.Join(name.OrganizationalUnit, ",")) |
| 115 | + } |
| 116 | + if name.CommonName != "" { |
| 117 | + parts = append(parts, "CN="+name.CommonName) |
| 118 | + } |
| 119 | + return strings.Join(parts, ", ") |
| 120 | +} |
| 121 | + |
| 122 | +func formatSerialNumber(serial *big.Int) string { |
| 123 | + hex := fmt.Sprintf("%x", serial) |
| 124 | + if len(hex)%2 != 0 { |
| 125 | + hex = "0" + hex |
| 126 | + } |
| 127 | + var parts []string |
| 128 | + for i := 0; i < len(hex); i += 2 { |
| 129 | + parts = append(parts, hex[i:i+2]) |
| 130 | + } |
| 131 | + return strings.Join(parts, ":") |
| 132 | +} |
| 133 | + |
| 134 | +func printPublicKeyInfo(pub interface{}) { |
| 135 | + switch pub := pub.(type) { |
| 136 | + case *rsa.PublicKey: |
| 137 | + fmt.Printf(" Public Key Algorithm: %s\n", x509.RSA.String()) |
| 138 | + fmt.Printf(" RSA Public-Key: (%d bit)\n", pub.N.BitLen()) |
| 139 | + fmt.Println(" Modulus:") |
| 140 | + printHexBlock(" ", pub.N.Bytes(), 15) |
| 141 | + fmt.Printf(" Exponent: %d (0x%x)\n", pub.E, pub.E) |
| 142 | + |
| 143 | + case *ecdsa.PublicKey: |
| 144 | + fmt.Printf(" Public Key Algorithm: %s\n", x509.ECDSA.String()) |
| 145 | + fmt.Printf(" Public-Key: (%d bit)\n", pub.Curve.Params().BitSize) |
| 146 | + printHexBlock(" ", pub.X.Bytes(), 15) // Just showing the X coordinate for brevity, similar to some tools |
| 147 | + fmt.Printf(" Curve: %s\n", pub.Curve.Params().Name) |
| 148 | + default: |
| 149 | + fmt.Println(" Public Key Algorithm: Unknown") |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +func printExtensions(cert *x509.Certificate) { |
| 154 | + for _, ext := range cert.Extensions { |
| 155 | + oidStr := ext.Id.String() |
| 156 | + extName, ok := oidToStringMap[oidStr] |
| 157 | + if !ok { |
| 158 | + extName = oidStr // Fallback to OID if not in map |
| 159 | + } |
| 160 | + |
| 161 | + criticalStr := "" |
| 162 | + if ext.Critical { |
| 163 | + criticalStr = "critical" |
| 164 | + } |
| 165 | + fmt.Printf(" %s: %s\n", extName, criticalStr) |
| 166 | + |
| 167 | + // Parse and print specific extension details |
| 168 | + printExtensionValue(ext, cert) |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +func printExtensionValue(ext pkix.Extension, cert *x509.Certificate) { |
| 173 | + indent := " " |
| 174 | + switch { |
| 175 | + case ext.Id.Equal(oidExtensionKeyUsage): |
| 176 | + printKeyUsage(cert.KeyUsage, indent) |
| 177 | + case ext.Id.Equal(oidExtensionExtendedKeyUsage): |
| 178 | + printExtendedKeyUsage(cert.ExtKeyUsage, indent) |
| 179 | + case ext.Id.Equal(oidExtensionBasicConstraints): |
| 180 | + fmt.Printf("%sCA:%t\n", indent, cert.IsCA) |
| 181 | + case ext.Id.Equal(oidExtensionSubjectKeyId): |
| 182 | + fmt.Printf("%s%s\n", indent, formatHex(cert.SubjectKeyId)) |
| 183 | + case ext.Id.Equal(oidExtensionAuthorityKeyId): |
| 184 | + fmt.Printf("%skeyid:%s\n", indent, formatHex(cert.AuthorityKeyId)) |
| 185 | + case ext.Id.Equal(oidExtensionSubjectAltName): |
| 186 | + printSAN(cert, indent) |
| 187 | + case ext.Id.Equal(oidAuthorityInfoAccess): |
| 188 | + printAIA(cert, indent) |
| 189 | + case ext.Id.Equal(oidExtensionCRLDistributionPoints): |
| 190 | + for _, point := range cert.CRLDistributionPoints { |
| 191 | + fmt.Printf("%sFull Name:\n%s URI:%s\n", indent, indent, point) |
| 192 | + } |
| 193 | + case ext.Id.Equal(oidExtensionCertificatePolicies): |
| 194 | + for _, policy := range cert.PolicyIdentifiers { |
| 195 | + fmt.Printf("%sPolicy: %s\n", indent, policy.String()) |
| 196 | + } |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +func printKeyUsage(ku x509.KeyUsage, indent string) { |
| 201 | + var usages []string |
| 202 | + if ku&x509.KeyUsageDigitalSignature != 0 { |
| 203 | + usages = append(usages, "Digital Signature") |
| 204 | + } |
| 205 | + if ku&x509.KeyUsageContentCommitment != 0 { |
| 206 | + usages = append(usages, "Content Commitment") |
| 207 | + } |
| 208 | + if ku&x509.KeyUsageKeyEncipherment != 0 { |
| 209 | + usages = append(usages, "Key Encipherment") |
| 210 | + } |
| 211 | + if ku&x509.KeyUsageDataEncipherment != 0 { |
| 212 | + usages = append(usages, "Data Encipherment") |
| 213 | + } |
| 214 | + if ku&x509.KeyUsageKeyAgreement != 0 { |
| 215 | + usages = append(usages, "Key Agreement") |
| 216 | + } |
| 217 | + if ku&x509.KeyUsageCertSign != 0 { |
| 218 | + usages = append(usages, "Certificate Sign") |
| 219 | + } |
| 220 | + if ku&x509.KeyUsageCRLSign != 0 { |
| 221 | + usages = append(usages, "CRL Sign") |
| 222 | + } |
| 223 | + if ku&x509.KeyUsageEncipherOnly != 0 { |
| 224 | + usages = append(usages, "Encipher Only") |
| 225 | + } |
| 226 | + if ku&x509.KeyUsageDecipherOnly != 0 { |
| 227 | + usages = append(usages, "Decipher Only") |
| 228 | + } |
| 229 | + fmt.Printf("%s%s\n", indent, strings.Join(usages, ", ")) |
| 230 | +} |
| 231 | + |
| 232 | +func printExtendedKeyUsage(ekus []x509.ExtKeyUsage, indent string) { |
| 233 | + var usages []string |
| 234 | + for _, eku := range ekus { |
| 235 | + switch eku { |
| 236 | + case x509.ExtKeyUsageAny: |
| 237 | + usages = append(usages, "Any") |
| 238 | + case x509.ExtKeyUsageServerAuth: |
| 239 | + usages = append(usages, "TLS Web Server Authentication") |
| 240 | + case x509.ExtKeyUsageClientAuth: |
| 241 | + usages = append(usages, "TLS Web Client Authentication") |
| 242 | + case x509.ExtKeyUsageCodeSigning: |
| 243 | + usages = append(usages, "Code Signing") |
| 244 | + case x509.ExtKeyUsageEmailProtection: |
| 245 | + usages = append(usages, "E-mail Protection") |
| 246 | + case x509.ExtKeyUsageIPSECEndSystem: |
| 247 | + usages = append(usages, "IPSEC End System") |
| 248 | + case x509.ExtKeyUsageIPSECTunnel: |
| 249 | + usages = append(usages, "IPSEC Tunnel") |
| 250 | + case x509.ExtKeyUsageIPSECUser: |
| 251 | + usages = append(usages, "IPSEC User") |
| 252 | + case x509.ExtKeyUsageTimeStamping: |
| 253 | + usages = append(usages, "Time Stamping") |
| 254 | + case x509.ExtKeyUsageOCSPSigning: |
| 255 | + usages = append(usages, "OCSP Signing") |
| 256 | + case x509.ExtKeyUsageMicrosoftServerGatedCrypto: |
| 257 | + usages = append(usages, "Microsoft Server Gated Crypto") |
| 258 | + case x509.ExtKeyUsageNetscapeServerGatedCrypto: |
| 259 | + usages = append(usages, "Netscape Server Gated Crypto") |
| 260 | + default: |
| 261 | + usages = append(usages, "Unknown") |
| 262 | + } |
| 263 | + } |
| 264 | + fmt.Printf("%s%s\n", indent, strings.Join(usages, ", ")) |
| 265 | +} |
| 266 | + |
| 267 | +func printSAN(cert *x509.Certificate, indent string) { |
| 268 | + var san []string |
| 269 | + for _, name := range cert.DNSNames { |
| 270 | + san = append(san, "DNS:"+name) |
| 271 | + } |
| 272 | + for _, email := range cert.EmailAddresses { |
| 273 | + san = append(san, "email:"+email) |
| 274 | + } |
| 275 | + for _, ip := range cert.IPAddresses { |
| 276 | + san = append(san, "IP Address:"+ip.String()) |
| 277 | + } |
| 278 | + for _, uri := range cert.URIs { |
| 279 | + san = append(san, "URI:"+uri.String()) |
| 280 | + } |
| 281 | + fmt.Printf("%s%s\n", indent, strings.Join(san, ", ")) |
| 282 | +} |
| 283 | + |
| 284 | +func printAIA(cert *x509.Certificate, indent string) { |
| 285 | + if len(cert.OCSPServer) > 0 { |
| 286 | + fmt.Printf("%sOCSP - URI:%s\n", indent, strings.Join(cert.OCSPServer, ", ")) |
| 287 | + } |
| 288 | + if len(cert.IssuingCertificateURL) > 0 { |
| 289 | + fmt.Printf("%sCA Issuers - URI:%s\n", indent, strings.Join(cert.IssuingCertificateURL, ", ")) |
| 290 | + } |
| 291 | +} |
| 292 | + |
| 293 | +func formatHex(data []byte) string { |
| 294 | + var parts []string |
| 295 | + for i, b := range data { |
| 296 | + if i > 0 { |
| 297 | + parts = append(parts, ":") |
| 298 | + } |
| 299 | + parts = append(parts, fmt.Sprintf("%02X", b)) |
| 300 | + } |
| 301 | + return strings.Join(parts, "") |
| 302 | +} |
| 303 | + |
| 304 | +func printHexBlock(prefix string, data []byte, wrap int) { |
| 305 | + var parts []string |
| 306 | + for i, b := range data { |
| 307 | + if i > 0 && i%wrap == 0 { |
| 308 | + parts = append(parts, "\n"+prefix) |
| 309 | + } |
| 310 | + parts = append(parts, fmt.Sprintf("%02x:", b)) |
| 311 | + } |
| 312 | + // remove last ":" |
| 313 | + if len(parts) > 0 { |
| 314 | + str := strings.Join(parts, "") |
| 315 | + fmt.Printf("%s%s\n", prefix, str[:len(str)-1]) |
| 316 | + } |
| 317 | +} |
| 318 | + |
| 319 | +// infoCmd represents the info command |
| 320 | +var infoCmd = &cobra.Command{ |
| 321 | + Use: "info", |
| 322 | + Short: "Display information about the XCA tool", |
| 323 | + Long: `Display information about Certificate, like 'openssl x509 -noout -text -in xxx.crt' including version, and basic information. |
| 324 | +
|
| 325 | +Examples: |
| 326 | + xca info <path-of>/root-ca.crt`, |
| 327 | + Args: cobra.ExactArgs(1), |
| 328 | + Run: func(cmd *cobra.Command, args []string) { |
| 329 | + certfilePath = args[0] |
| 330 | + |
| 331 | + // Read the certificate file |
| 332 | + certPEM, err := os.ReadFile(certfilePath) |
| 333 | + if err != nil { |
| 334 | + fmt.Fprintf(os.Stderr, "Error reading certificate file: %v\n", err) |
| 335 | + os.Exit(1) |
| 336 | + } |
| 337 | + |
| 338 | + // Decode the PEM-encoded certificate |
| 339 | + block, _ := pem.Decode(certPEM) |
| 340 | + if block == nil || block.Type != "CERTIFICATE" { |
| 341 | + fmt.Fprintf(os.Stderr, "Failed to decode PEM block containing certificate\n") |
| 342 | + os.Exit(1) |
| 343 | + } |
| 344 | + |
| 345 | + // Parse the certificate |
| 346 | + cert, err := x509.ParseCertificate(block.Bytes) |
| 347 | + if err != nil { |
| 348 | + fmt.Fprintf(os.Stderr, "Error parsing certificate: %v\n", err) |
| 349 | + os.Exit(1) |
| 350 | + } |
| 351 | + |
| 352 | + // Print certificate details |
| 353 | + printCertificateInfo(cert) |
| 354 | + }, |
| 355 | +} |
0 commit comments