Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e375963243 | |||
| 31baa10b3b | |||
| 0556c7c56d | |||
| 83c95d9db8 | |||
| beccb551e2 | |||
| c761d98b82 |
40
cmd/ca-signed/README.txt
Normal file
40
cmd/ca-signed/README.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
ca-signed: verify certificates against a CA
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
Description
|
||||||
|
ca-signed verifies whether one or more certificates are signed by a given
|
||||||
|
Certificate Authority (CA). It prints a concise status per input certificate
|
||||||
|
along with the certificate’s expiration date when validation succeeds.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
ca-signed CA.pem cert1.pem [cert2.pem ...]
|
||||||
|
|
||||||
|
- CA.pem: A file containing one or more CA certificates in PEM, DER, or PKCS#7/PKCS#12 formats.
|
||||||
|
- certN.pem: A file containing the end-entity (leaf) certificate to verify. If the file contains a chain,
|
||||||
|
the first certificate is treated as the leaf and the remaining ones are used as intermediates.
|
||||||
|
|
||||||
|
Output format
|
||||||
|
For each input certificate file, one line is printed:
|
||||||
|
<filename>: OK (expires YYYY-MM-DD)
|
||||||
|
<filename>: INVALID
|
||||||
|
|
||||||
|
Special self-test mode
|
||||||
|
ca-signed selftest
|
||||||
|
|
||||||
|
Runs a built-in test suite using embedded certificates. This mode requires no
|
||||||
|
external files or network access. The program exits with code 0 if all tests
|
||||||
|
pass, or a non-zero exit code if any test fails. Example output lines include
|
||||||
|
whether validation succeeds and the leaf’s expiration when applicable.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
# Verify a server certificate against a root CA
|
||||||
|
ca-signed isrg-root-x1.pem le-e7.pem
|
||||||
|
|
||||||
|
# Run the embedded self-test suite
|
||||||
|
ca-signed selftest
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- The tool attempts to parse certificates in PEM first, then falls back to
|
||||||
|
DER/PKCS#7/PKCS#12 (with an empty password) where applicable.
|
||||||
|
- Expiration is shown for the leaf certificate only.
|
||||||
|
- In selftest mode, test certificates are compiled into the binary using go:embed.
|
||||||
287
cmd/ca-signed/main.go
Normal file
287
cmd/ca-signed/main.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/certlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadCertsFromFile attempts to parse certificates from a file that may be in
|
||||||
|
// PEM or DER/PKCS#7 format. Returns the parsed certificates or an error.
|
||||||
|
func loadCertsFromFile(path string) ([]*x509.Certificate, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try PEM first
|
||||||
|
if certs, err := certlib.ParseCertificatesPEM(data); err == nil {
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try DER/PKCS7/PKCS12 (with no password)
|
||||||
|
if certs, _, err := certlib.ParseCertificatesDER(data, ""); err == nil {
|
||||||
|
return certs, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePoolFromFile(path string) (*x509.CertPool, error) {
|
||||||
|
// Try PEM via helper (it builds a pool)
|
||||||
|
if pool, err := certlib.LoadPEMCertPool(path); err == nil && pool != nil {
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: read as DER(s), add to a new pool
|
||||||
|
certs, err := loadCertsFromFile(path)
|
||||||
|
if err != nil || len(certs) == 0 {
|
||||||
|
return nil, fmt.Errorf("failed to load CA certificates from %s", path)
|
||||||
|
}
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
for _, c := range certs {
|
||||||
|
pool.AddCert(c)
|
||||||
|
}
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed testdata/*.pem
|
||||||
|
var embeddedTestdata embed.FS
|
||||||
|
|
||||||
|
// loadCertsFromBytes attempts to parse certificates from bytes that may be in
|
||||||
|
// PEM or DER/PKCS#7 format.
|
||||||
|
func loadCertsFromBytes(data []byte) ([]*x509.Certificate, error) {
|
||||||
|
// Try PEM first
|
||||||
|
if certs, err := certlib.ParseCertificatesPEM(data); err == nil {
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
|
// Try DER/PKCS7/PKCS12 (with no password)
|
||||||
|
if certs, _, err := certlib.ParseCertificatesDER(data, ""); err == nil {
|
||||||
|
return certs, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePoolFromBytes(data []byte) (*x509.CertPool, error) {
|
||||||
|
certs, err := loadCertsFromBytes(data)
|
||||||
|
if err != nil || len(certs) == 0 {
|
||||||
|
return nil, fmt.Errorf("failed to load CA certificates from embedded bytes")
|
||||||
|
}
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
for _, c := range certs {
|
||||||
|
pool.AddCert(c)
|
||||||
|
}
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSelfSigned returns true if the given certificate is self-signed.
|
||||||
|
// It checks that the subject and issuer match and that the certificate's
|
||||||
|
// signature verifies against its own public key.
|
||||||
|
func isSelfSigned(cert *x509.Certificate) bool {
|
||||||
|
if cert == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Quick check: subject and issuer match
|
||||||
|
if cert.Subject.String() != cert.Issuer.String() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Cryptographic check: the certificate is signed by itself
|
||||||
|
if err := cert.CheckSignatureFrom(cert); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAgainstCA(caPool *x509.CertPool, path string) (ok bool, expiry string) {
|
||||||
|
certs, err := loadCertsFromFile(path)
|
||||||
|
if err != nil || len(certs) == 0 {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf := certs[0]
|
||||||
|
ints := x509.NewCertPool()
|
||||||
|
if len(certs) > 1 {
|
||||||
|
for _, ic := range certs[1:] {
|
||||||
|
ints.AddCert(ic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: caPool,
|
||||||
|
Intermediates: ints,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
|
}
|
||||||
|
if _, err := leaf.Verify(opts); err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, leaf.NotAfter.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAgainstCABytes(caPool *x509.CertPool, certData []byte) (ok bool, expiry string) {
|
||||||
|
certs, err := loadCertsFromBytes(certData)
|
||||||
|
if err != nil || len(certs) == 0 {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf := certs[0]
|
||||||
|
ints := x509.NewCertPool()
|
||||||
|
if len(certs) > 1 {
|
||||||
|
for _, ic := range certs[1:] {
|
||||||
|
ints.AddCert(ic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: caPool,
|
||||||
|
Intermediates: ints,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
|
}
|
||||||
|
if _, err := leaf.Verify(opts); err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, leaf.NotAfter.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
// selftest runs built-in validation using embedded certificates.
|
||||||
|
func selftest() int {
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
caFile string
|
||||||
|
certFile string
|
||||||
|
expectOK bool
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []testCase{
|
||||||
|
{name: "ISRG Root X1 validates LE E7", caFile: "testdata/isrg-root-x1.pem", certFile: "testdata/le-e7.pem", expectOK: true},
|
||||||
|
{name: "ISRG Root X1 does NOT validate Google WR2", caFile: "testdata/isrg-root-x1.pem", certFile: "testdata/goog-wr2.pem", expectOK: false},
|
||||||
|
{name: "GTS R1 validates Google WR2", caFile: "testdata/gts-r1.pem", certFile: "testdata/goog-wr2.pem", expectOK: true},
|
||||||
|
{name: "GTS R1 does NOT validate LE E7", caFile: "testdata/gts-r1.pem", certFile: "testdata/le-e7.pem", expectOK: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
failures := 0
|
||||||
|
for _, tc := range cases {
|
||||||
|
caBytes, err := embeddedTestdata.ReadFile(tc.caFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", tc.caFile, err)
|
||||||
|
failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
certBytes, err := embeddedTestdata.ReadFile(tc.certFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", tc.certFile, err)
|
||||||
|
failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pool, err := makePoolFromBytes(caBytes)
|
||||||
|
if err != nil || pool == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: failed to build CA pool for %s: %v\n", tc.caFile, err)
|
||||||
|
failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok, exp := verifyAgainstCABytes(pool, certBytes)
|
||||||
|
if ok != tc.expectOK {
|
||||||
|
fmt.Printf("%s: unexpected result: got %v, want %v\n", tc.name, ok, tc.expectOK)
|
||||||
|
failures++
|
||||||
|
} else {
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("%s: OK (expires %s)\n", tc.name, exp)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: INVALID (as expected)\n", tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that both embedded root CAs are detected as self-signed
|
||||||
|
roots := []string{"testdata/gts-r1.pem", "testdata/isrg-root-x1.pem"}
|
||||||
|
for _, root := range roots {
|
||||||
|
b, err := embeddedTestdata.ReadFile(root)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", root, err)
|
||||||
|
failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
certs, err := loadCertsFromBytes(b)
|
||||||
|
if err != nil || len(certs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: failed to parse cert(s) from %s: %v\n", root, err)
|
||||||
|
failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
leaf := certs[0]
|
||||||
|
if isSelfSigned(leaf) {
|
||||||
|
fmt.Printf("%s: SELF-SIGNED (as expected)\n", root)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: expected SELF-SIGNED, but was not detected as such\n", root)
|
||||||
|
failures++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failures == 0 {
|
||||||
|
fmt.Println("selftest: PASS")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "selftest: FAIL (%d failure(s))\n", failures)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Special selftest mode: single argument "selftest"
|
||||||
|
if len(os.Args) == 2 && os.Args[1] == "selftest" {
|
||||||
|
os.Exit(selftest())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
prog := filepath.Base(os.Args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage:\n %s ca.pem cert1.pem cert2.pem ...\n %s selftest\n", prog, prog)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
caPath := os.Args[1]
|
||||||
|
caPool, err := makePoolFromFile(caPath)
|
||||||
|
if err != nil || caPool == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to load CA certificate(s): %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, certPath := range os.Args[2:] {
|
||||||
|
ok, exp := verifyAgainstCA(caPool, certPath)
|
||||||
|
name := filepath.Base(certPath)
|
||||||
|
// Load the leaf once for self-signed detection and potential expiry fallback
|
||||||
|
var leaf *x509.Certificate
|
||||||
|
if certs, err := loadCertsFromFile(certPath); err == nil && len(certs) > 0 {
|
||||||
|
leaf = certs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the certificate is self-signed, prefer the SELF-SIGNED label
|
||||||
|
if isSelfSigned(leaf) {
|
||||||
|
fmt.Printf("%s: SELF-SIGNED\n", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
// Display with the requested format
|
||||||
|
// Example: file: OK (expires 2031-01-01)
|
||||||
|
// Ensure deterministic date formatting
|
||||||
|
// Note: no timezone displayed; date only as per example
|
||||||
|
// If exp ended up empty for some reason, recompute safely
|
||||||
|
if exp == "" {
|
||||||
|
if leaf != nil {
|
||||||
|
exp = leaf.NotAfter.Format("2006-01-02")
|
||||||
|
} else {
|
||||||
|
// fallback to the current date to avoid empty; though shouldn't happen
|
||||||
|
exp = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: OK (expires %s)\n", name, exp)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s: INVALID\n", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
cmd/ca-signed/testdata/goog-wr2.pem
vendored
Normal file
29
cmd/ca-signed/testdata/goog-wr2.pem
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
31
cmd/ca-signed/testdata/gts-r1.pem
vendored
Normal file
31
cmd/ca-signed/testdata/gts-r1.pem
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
|
||||||
|
-----END CERTIFICATE-----
|
||||||
31
cmd/ca-signed/testdata/isrg-root-x1.pem
vendored
Normal file
31
cmd/ca-signed/testdata/isrg-root-x1.pem
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
26
cmd/ca-signed/testdata/le-e7.pem
vendored
Normal file
26
cmd/ca-signed/testdata/le-e7.pem
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEVzCCAj+gAwIBAgIRAKp18eYrjwoiCWbTi7/UuqEwDQYJKoZIhvcNAQELBQAw
|
||||||
|
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||||
|
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
|
||||||
|
WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
|
||||||
|
RW5jcnlwdDELMAkGA1UEAxMCRTcwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARB6AST
|
||||||
|
CFh/vjcwDMCgQer+VtqEkz7JANurZxLP+U9TCeioL6sp5Z8VRvRbYk4P1INBmbef
|
||||||
|
QHJFHCxcSjKmwtvGBWpl/9ra8HW0QDsUaJW2qOJqceJ0ZVFT3hbUHifBM/2jgfgw
|
||||||
|
gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD
|
||||||
|
ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSuSJ7chx1EoG/aouVgdAR4
|
||||||
|
wpwAgDAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB
|
||||||
|
AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g
|
||||||
|
BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu
|
||||||
|
Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAjx66fDdLk5ywFn3CzA1w1qfylHUD
|
||||||
|
aEf0QZpXcJseddJGSfbUUOvbNR9N/QQ16K1lXl4VFyhmGXDT5Kdfcr0RvIIVrNxF
|
||||||
|
h4lqHtRRCP6RBRstqbZ2zURgqakn/Xip0iaQL0IdfHBZr396FgknniRYFckKORPG
|
||||||
|
yM3QKnd66gtMst8I5nkRQlAg/Jb+Gc3egIvuGKWboE1G89NTsN9LTDD3PLj0dUMr
|
||||||
|
OIuqVjLB8pEC6yk9enrlrqjXQgkLEYhXzq7dLafv5Vkig6Gl0nuuqjqfp0Q1bi1o
|
||||||
|
yVNAlXe6aUXw92CcghC9bNsKEO1+M52YY5+ofIXlS/SEQbvVYYBLZ5yeiglV6t3S
|
||||||
|
M6H+vTG0aP9YHzLn/KVOHzGQfXDP7qM5tkf+7diZe7o2fw6O7IvN6fsQXEQQj8TJ
|
||||||
|
UXJxv2/uJhcuy/tSDgXwHM8Uk34WNbRT7zGTGkQRX0gsbjAea/jYAoWv0ZvQRwpq
|
||||||
|
Pe79D/i7Cep8qWnA+7AE/3B3S/3dEEYmc0lpe1366A/6GEgk3ktr9PEoQrLChs6I
|
||||||
|
tu3wnNLB2euC8IKGLQFpGtOO/2/hiAKjyajaBP25w1jF0Wl8Bbqne3uZ2q1GyPFJ
|
||||||
|
YRmT7/OXpmOH/FVLtwS+8ng1cAmpCujPwteJZNcDG0sF2n/sc0+SQf49fdyUK0ty
|
||||||
|
+VUwFj9tmWxyR/M=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -110,6 +110,14 @@ func showBasicConstraints(cert *x509.Certificate) {
|
|||||||
|
|
||||||
if cert.IsCA {
|
if cert.IsCA {
|
||||||
fmt.Printf(", is a CA certificate")
|
fmt.Printf(", is a CA certificate")
|
||||||
|
if !cert.BasicConstraintsValid {
|
||||||
|
fmt.Printf(" (basic constraint failure)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("is not a CA certificate")
|
||||||
|
if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 {
|
||||||
|
fmt.Printf(" (key encipherment usage enabled!)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) {
|
if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) {
|
||||||
|
|||||||
3
cmd/minmax/README
Normal file
3
cmd/minmax/README
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
minmax
|
||||||
|
|
||||||
|
A quick tool to calculate minmax codes if needed for uLisp.
|
||||||
53
cmd/minmax/minmax.go
Normal file
53
cmd/minmax/minmax.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kinds = map[string]int{
|
||||||
|
"sym": 0,
|
||||||
|
"tf": 1,
|
||||||
|
"fn": 2,
|
||||||
|
"sp": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
func dieIf(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[!] %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: minmax type min max\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " type is one of fn, sp, sym, tf\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flag.NArg() != 3 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
kind, ok := kinds[flag.Arg(0)]
|
||||||
|
if !ok {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
min, err := strconv.Atoi(flag.Arg(1))
|
||||||
|
dieIf(err)
|
||||||
|
|
||||||
|
max, err := strconv.Atoi(flag.Arg(2))
|
||||||
|
dieIf(err)
|
||||||
|
|
||||||
|
code := kind << 6
|
||||||
|
code += (min << 3)
|
||||||
|
code += max
|
||||||
|
fmt.Printf("%0o\n", code)
|
||||||
|
}
|
||||||
34
cmd/tlsinfo/README.txt
Normal file
34
cmd/tlsinfo/README.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
tlsinfo: show TLS version, cipher, and peer certificates
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
Description
|
||||||
|
tlsinfo connects to a TLS server and prints the negotiated TLS version and
|
||||||
|
cipher suite, followed by details for each certificate in the server’s
|
||||||
|
presented chain (as provided by the server).
|
||||||
|
|
||||||
|
Usage
|
||||||
|
tlsinfo <hostname:port>
|
||||||
|
|
||||||
|
Output
|
||||||
|
The program prints the negotiated protocol and cipher, then one section per
|
||||||
|
certificate in the order received from the server. Example fields:
|
||||||
|
TLS Version: TLS 1.3
|
||||||
|
Cipher Suite: TLS_AES_128_GCM_SHA256
|
||||||
|
Certificate 1
|
||||||
|
Subject: CN=example.com, O=Example Corp, C=US
|
||||||
|
Issuer: CN=Example Root CA, O=Example Corp, C=US
|
||||||
|
DNS Names: [example.com www.example.com]
|
||||||
|
Not Before: 2025-01-01 00:00:00 +0000 UTC
|
||||||
|
Not After: 2026-01-01 23:59:59 +0000 UTC
|
||||||
|
|
||||||
|
Examples
|
||||||
|
# Inspect a public HTTPS endpoint
|
||||||
|
tlsinfo example.com:443
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Verification is intentionally disabled (InsecureSkipVerify=true). The tool
|
||||||
|
does not validate the server certificate or hostname; it is for inspection
|
||||||
|
only.
|
||||||
|
- The SNI/ServerName is inferred from <hostname> when applicable.
|
||||||
|
- You must specify a port (e.g., 443 for HTTPS).
|
||||||
|
- The entire certificate chain is printed exactly as presented by the server.
|
||||||
64
cmd/tlsinfo/main.go
Normal file
64
cmd/tlsinfo/main.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Printf("Usage: %s ‹hostname:port>\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort := os.Args[1]
|
||||||
|
conn, err := tls.Dial("tcp", hostPort, &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to connect to the TLS server: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
state := conn.ConnectionState()
|
||||||
|
printConnectionDetails(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printConnectionDetails(state tls.ConnectionState) {
|
||||||
|
version := tlsVersion(state.Version)
|
||||||
|
cipherSuite := tls.CipherSuiteName(state.CipherSuite)
|
||||||
|
fmt.Printf("TLS Version: %s\n", version)
|
||||||
|
fmt.Printf("Cipher Suite: %s\n", cipherSuite)
|
||||||
|
printPeerCertificates(state.PeerCertificates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tlsVersion(version uint16) string {
|
||||||
|
switch version {
|
||||||
|
|
||||||
|
case tls.VersionTLS13:
|
||||||
|
return "TLS 1.3"
|
||||||
|
case tls.VersionTLS12:
|
||||||
|
|
||||||
|
return "TLS 1.2"
|
||||||
|
case tls.VersionTLS11:
|
||||||
|
return "TLS 1.1"
|
||||||
|
case tls.VersionTLS10:
|
||||||
|
return "TLS 1.0"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPeerCertificates(certificates []*x509.Certificate) {
|
||||||
|
for i, cert := range certificates {
|
||||||
|
fmt.Printf("Certificate %d\n", i+1)
|
||||||
|
fmt.Printf("\tSubject: %s\n", cert.Subject)
|
||||||
|
fmt.Printf("\tIssuer: %s\n", cert.Issuer)
|
||||||
|
fmt.Printf("\tDNS Names: %v\n", cert.DNSNames)
|
||||||
|
fmt.Printf("\tNot Before: %s\n:", cert.NotBefore)
|
||||||
|
fmt.Printf("\tNot After: %s\n", cert.NotAfter)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user