Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1a2039c7d | |||
| 46c9976e73 | |||
| 5a5dd5e6ea | |||
| 3317b8c33b | |||
| fb1b1ffcad | |||
| 7bb6973341 | |||
| 8e997bda34 | |||
| d76db4a947 | |||
| 7e36a828d4 | |||
| 8eaca580be | |||
| fd31e31afa | |||
| 7426988ae4 |
@@ -101,7 +101,7 @@ linters:
|
||||
- loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)
|
||||
- makezero # finds slice declarations with non-zero initial length
|
||||
- mirror # reports wrong mirror patterns of bytes/strings usage
|
||||
- mnd # detects magic numbers
|
||||
# - mnd # detects magic numbers
|
||||
- modernize # suggests simplifications to Go code, using modern language and library features
|
||||
- musttag # enforces field tags in (un)marshaled structs
|
||||
- nakedret # finds naked returns in functions greater than a specified function length
|
||||
|
||||
23
CHANGELOG
23
CHANGELOG
@@ -1,5 +1,28 @@
|
||||
CHANGELOG
|
||||
|
||||
v1.15.6 - 2025-11-19
|
||||
certlib: add FileKind function to determine file type.
|
||||
|
||||
v1.15.5 - 2025-11-19
|
||||
certlib/bundler: add support for crt files that are pem-encoded.
|
||||
|
||||
v1.15.4 - 2025-11-19
|
||||
Quality of life fixes for CSR generation.
|
||||
|
||||
v1.15.3 - 2025-11-19
|
||||
Minor bug fixes.
|
||||
|
||||
v1.15.2 - 2025-11-19
|
||||
Minor bug fixes.
|
||||
|
||||
v1.15.1 - 2025-11-19
|
||||
|
||||
Changed:
|
||||
- linter fixes.
|
||||
|
||||
Removed:
|
||||
- mnd removed from linter.
|
||||
|
||||
v1.15.0 - 2025-11-19
|
||||
|
||||
Changed:
|
||||
|
||||
@@ -422,6 +422,24 @@ func encodeCertsToFiles(
|
||||
name: baseName + ".pem",
|
||||
content: pemContent,
|
||||
})
|
||||
case "crt":
|
||||
pemContent := encodeCertsToPEM(certs)
|
||||
files = append(files, fileEntry{
|
||||
name: baseName + ".crt",
|
||||
content: pemContent,
|
||||
})
|
||||
case "pemcrt":
|
||||
pemContent := encodeCertsToPEM(certs)
|
||||
files = append(files, fileEntry{
|
||||
name: baseName + ".pem",
|
||||
content: pemContent,
|
||||
})
|
||||
|
||||
pemContent = encodeCertsToPEM(certs)
|
||||
files = append(files, fileEntry{
|
||||
name: baseName + ".crt",
|
||||
content: pemContent,
|
||||
})
|
||||
case "der":
|
||||
if isSingle {
|
||||
// For single file in DER, concatenate all cert DER bytes
|
||||
|
||||
@@ -63,12 +63,7 @@ type CertificateRequest struct {
|
||||
Profile Profile `yaml:"profile"`
|
||||
}
|
||||
|
||||
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
|
||||
pub, priv, err := cs.KeySpec.Generate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func (cs CertificateRequest) Request(priv crypto.PrivateKey) (*x509.CertificateRequest, error) {
|
||||
subject := pkix.Name{}
|
||||
subject.CommonName = cs.Subject.CommonName
|
||||
subject.Country = []string{cs.Subject.Country}
|
||||
@@ -81,13 +76,13 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
|
||||
for i, ip := range cs.Subject.IPAddresses {
|
||||
ipAddresses = append(ipAddresses, net.ParseIP(ip))
|
||||
if ipAddresses[i] == nil {
|
||||
return nil, nil, fmt.Errorf("invalid IP address: %s", ip)
|
||||
return nil, fmt.Errorf("invalid IP address: %s", ip)
|
||||
}
|
||||
}
|
||||
|
||||
req := &x509.CertificateRequest{
|
||||
PublicKeyAlgorithm: 0,
|
||||
PublicKey: pub,
|
||||
PublicKey: getPublic(priv),
|
||||
Subject: subject,
|
||||
DNSNames: cs.Subject.DNSNames,
|
||||
IPAddresses: ipAddresses,
|
||||
@@ -95,12 +90,26 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
|
||||
|
||||
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate request: %w", err)
|
||||
return nil, fmt.Errorf("failed to create certificate request: %w", err)
|
||||
}
|
||||
|
||||
req, err = x509.ParseCertificateRequest(reqBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse certificate request: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse certificate request: %w", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
|
||||
_, priv, err := cs.KeySpec.Generate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := cs.Request(priv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return priv, req, nil
|
||||
@@ -158,7 +167,11 @@ func (p Profile) templateFromRequest(req *x509.CertificateRequest) (*x509.Certif
|
||||
return certTemplate, nil
|
||||
}
|
||||
|
||||
func (p Profile) SignRequest(parent *x509.Certificate, req *x509.CertificateRequest, priv crypto.PrivateKey) (*x509.Certificate, error) {
|
||||
func (p Profile) SignRequest(
|
||||
parent *x509.Certificate,
|
||||
req *x509.CertificateRequest,
|
||||
priv crypto.PrivateKey,
|
||||
) (*x509.Certificate, error) {
|
||||
tpl, err := p.templateFromRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate template: %w", err)
|
||||
|
||||
@@ -8,14 +8,13 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110}
|
||||
)
|
||||
// var (
|
||||
// oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110}
|
||||
//)
|
||||
|
||||
func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicKey, crypto.PrivateKey, error) {
|
||||
var key crypto.PrivateKey
|
||||
@@ -28,7 +27,12 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
|
||||
case x509.Ed25519:
|
||||
key, err = rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err == nil {
|
||||
pub = key.(*rsa.PrivateKey).Public()
|
||||
rsaPriv, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
panic("failed to cast RSA private key to *rsa.PrivateKey")
|
||||
}
|
||||
|
||||
pub = rsaPriv.Public()
|
||||
}
|
||||
case x509.ECDSA:
|
||||
var curve elliptic.Curve
|
||||
@@ -46,8 +50,17 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
|
||||
|
||||
key, err = ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err == nil {
|
||||
pub = key.(*ecdsa.PrivateKey).Public()
|
||||
ecPriv, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
panic("failed to cast ECDSA private key to *ecdsa.PrivateKey")
|
||||
}
|
||||
|
||||
pub = ecPriv.Public()
|
||||
}
|
||||
case x509.DSA:
|
||||
fallthrough
|
||||
case x509.UnknownPublicKeyAlgorithm:
|
||||
fallthrough
|
||||
default:
|
||||
err = errors.New("unsupported algorithm")
|
||||
}
|
||||
@@ -58,3 +71,16 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
|
||||
|
||||
return pub, key, nil
|
||||
}
|
||||
|
||||
func getPublic(priv crypto.PrivateKey) crypto.PublicKey {
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &priv.PublicKey
|
||||
case *ecdsa.PrivateKey:
|
||||
return &priv.PublicKey
|
||||
case *ed25519.PrivateKey:
|
||||
return priv.Public()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package certlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.wntrmute.dev/kyle/goutils/certlib/certerr"
|
||||
)
|
||||
@@ -13,6 +16,7 @@ import (
|
||||
// ReadCertificate reads a DER or PEM-encoded certificate from the
|
||||
// byte slice.
|
||||
func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
||||
in = bytes.TrimSpace(in)
|
||||
if len(in) == 0 {
|
||||
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate)
|
||||
}
|
||||
@@ -24,10 +28,10 @@ func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
||||
}
|
||||
|
||||
rest := remaining
|
||||
if p.Type != "CERTIFICATE" {
|
||||
if p.Type != pemTypeCertificate {
|
||||
return nil, rest, certerr.ParsingError(
|
||||
certerr.ErrorSourceCertificate,
|
||||
certerr.ErrInvalidPEMType(p.Type, "CERTIFICATE"),
|
||||
certerr.ErrInvalidPEMType(p.Type, pemTypeCertificate),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,3 +113,98 @@ func PoolFromBytes(certBytes []byte) (*x509.CertPool, error) {
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func ExportPrivateKeyPEM(priv crypto.PrivateKey) ([]byte, error) {
|
||||
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: pemTypePrivateKey, Bytes: keyDER}), nil
|
||||
}
|
||||
|
||||
func LoadCSR(path string) (*x509.CertificateRequest, error) {
|
||||
in, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, certerr.LoadingError(certerr.ErrorSourceCSR, err)
|
||||
}
|
||||
|
||||
req, _, err := ParseCSR(in)
|
||||
return req, err
|
||||
}
|
||||
|
||||
func ExportCSRAsPEM(req *x509.CertificateRequest) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: pemTypeCertificateRequest, Bytes: req.Raw})
|
||||
}
|
||||
|
||||
type FileFormat uint8
|
||||
|
||||
const (
|
||||
FormatPEM FileFormat = iota + 1
|
||||
FormatDER
|
||||
)
|
||||
|
||||
func (f FileFormat) String() string {
|
||||
switch f {
|
||||
case FormatPEM:
|
||||
return "PEM"
|
||||
case FormatDER:
|
||||
return "DER"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type FileType struct {
|
||||
Format FileFormat
|
||||
Type string
|
||||
}
|
||||
|
||||
func (ft FileType) String() string {
|
||||
if ft.Type == "" {
|
||||
return ft.Format.String()
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", ft.Type, ft.Format)
|
||||
}
|
||||
|
||||
// FileKind returns the file type of the given file.
|
||||
func FileKind(path string) (*FileType, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data)
|
||||
if block != nil {
|
||||
return &FileType{
|
||||
Format: FormatPEM,
|
||||
Type: strings.ToLower(strings.TrimSpace(block.Type)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = x509.ParseCertificate(data)
|
||||
if err == nil {
|
||||
return &FileType{
|
||||
Format: FormatDER,
|
||||
Type: strings.ToLower(pemTypeCertificate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = x509.ParseCertificateRequest(data)
|
||||
if err == nil {
|
||||
return &FileType{
|
||||
Format: FormatDER,
|
||||
Type: strings.ToLower(pemTypeCertificateRequest),
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = x509.ParsePKCS8PrivateKey(data)
|
||||
if err == nil {
|
||||
return &FileType{
|
||||
Format: FormatDER,
|
||||
Type: strings.ToLower(pemTypePrivateKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("certlib; unknown file type")
|
||||
}
|
||||
|
||||
@@ -54,8 +54,6 @@ var extKeyUsages = map[x509.ExtKeyUsage]string{
|
||||
x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing",
|
||||
}
|
||||
|
||||
|
||||
|
||||
func sigAlgoPK(a x509.SignatureAlgorithm) string {
|
||||
switch a {
|
||||
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
|
||||
@@ -251,11 +249,6 @@ func showBasicConstraints(cert *x509.Certificate) {
|
||||
fmt.Fprintln(os.Stdout)
|
||||
}
|
||||
|
||||
var (
|
||||
dateFormat string
|
||||
showHash bool // if true, print a SHA256 hash of the certificate's Raw field
|
||||
)
|
||||
|
||||
func wrapPrint(text string, indent int) {
|
||||
tabs := ""
|
||||
var tabsSb140 strings.Builder
|
||||
@@ -267,11 +260,12 @@ func wrapPrint(text string, indent int) {
|
||||
fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
|
||||
}
|
||||
|
||||
func DisplayCert(w io.Writer, cert *x509.Certificate) {
|
||||
func DisplayCert(w io.Writer, cert *x509.Certificate, showHash bool) {
|
||||
fmt.Fprintln(w, "CERTIFICATE")
|
||||
if showHash {
|
||||
fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, wrap("Subject: "+DisplayName(cert.Subject), 0))
|
||||
fmt.Fprintln(w, wrap("Issuer: "+DisplayName(cert.Issuer), 0))
|
||||
fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
||||
@@ -287,8 +281,8 @@ func DisplayCert(w io.Writer, cert *x509.Certificate) {
|
||||
fmt.Fprintf(w, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
|
||||
}
|
||||
|
||||
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1)
|
||||
fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(dateFormat))
|
||||
wrapPrint("Valid from: "+cert.NotBefore.Format(lib.DateShortFormat), 1)
|
||||
fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(lib.DateShortFormat))
|
||||
fmt.Fprintf(w, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
|
||||
|
||||
if len(cert.ExtKeyUsage) > 0 {
|
||||
|
||||
@@ -75,6 +75,12 @@ var DelegationExtension = pkix.Extension{
|
||||
Value: []byte{0x05, 0x00}, // ASN.1 NULL
|
||||
}
|
||||
|
||||
const (
|
||||
pemTypeCertificate = "CERTIFICATE"
|
||||
pemTypeCertificateRequest = "CERTIFICATE REQUEST"
|
||||
pemTypePrivateKey = "PRIVATE KEY"
|
||||
)
|
||||
|
||||
// InclusiveDate returns the time.Time representation of a date - 1
|
||||
// nanosecond. This allows time.After to be used inclusively.
|
||||
func InclusiveDate(year int, month time.Month, day int) time.Time {
|
||||
@@ -246,7 +252,7 @@ func EncodeCertificatesPEM(certs []*x509.Certificate) []byte {
|
||||
var buffer bytes.Buffer
|
||||
for _, cert := range certs {
|
||||
if err := pem.Encode(&buffer, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Type: pemTypeCertificate,
|
||||
Bytes: cert.Raw,
|
||||
}); err != nil {
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package certlib
|
||||
package certlib_test
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.wntrmute.dev/kyle/goutils/certlib"
|
||||
)
|
||||
|
||||
var (
|
||||
testCert1 = "testdata/cert1.pem"
|
||||
@@ -16,25 +20,25 @@ type testCase struct {
|
||||
}
|
||||
|
||||
var testCases = []testCase{
|
||||
{"testdata/cert1.pem", "testdata/priv1.pem", true},
|
||||
{"testdata/cert2.pem", "testdata/priv2.pem", true},
|
||||
{"testdata/cert1.pem", "testdata/priv2.pem", false},
|
||||
{"testdata/cert2.pem", "testdata/priv1.pem", false},
|
||||
{testCert1, testPriv1, true},
|
||||
{testCert2, testPriv2, true},
|
||||
{testCert1, testPriv2, false},
|
||||
{testCert2, testPriv1, false},
|
||||
}
|
||||
|
||||
func TestMatchKeys(t *testing.T) {
|
||||
for i, tc := range testCases {
|
||||
cert, err := LoadCertificate(tc.cert)
|
||||
cert, err := certlib.LoadCertificate(tc.cert)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load cert %d: %v", i, err)
|
||||
}
|
||||
|
||||
priv, err := LoadPrivateKey(tc.key)
|
||||
priv, err := certlib.LoadPrivateKey(tc.key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load key %d: %v", i, err)
|
||||
}
|
||||
|
||||
ok, _ := MatchKeys(cert, priv)
|
||||
ok, _ := certlib.MatchKeys(cert, priv)
|
||||
switch {
|
||||
case ok && !tc.match:
|
||||
t.Fatalf("case %d: cert %s/key %s should not match", i, tc.cert, tc.key)
|
||||
|
||||
3
cmd/cert-bundler/testdata/bundle.yaml
vendored
3
cmd/cert-bundler/testdata/bundle.yaml
vendored
@@ -12,6 +12,7 @@ chains:
|
||||
include_single: true
|
||||
include_individual: true
|
||||
manifest: true
|
||||
encoding: pemcrt
|
||||
formats:
|
||||
- zip
|
||||
- tgz
|
||||
@@ -53,4 +54,4 @@ chains:
|
||||
manifest: false
|
||||
encoding: both
|
||||
formats:
|
||||
- zip
|
||||
- zip
|
||||
|
||||
@@ -39,7 +39,7 @@ func main() {
|
||||
|
||||
revoke.HardFail = hardfail
|
||||
// Build a proxy-aware HTTP client for OCSP/CRL fetches
|
||||
if httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: timeout}); err == nil {
|
||||
if httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: timeout}); err == nil {
|
||||
revoke.HTTPClient = httpClient
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func checkSite(hostport string) (string, error) {
|
||||
defer cancel()
|
||||
|
||||
// Use proxy-aware TLS dialer
|
||||
conn, err := dialer.DialTLS(ctx, target.String(), dialer.DialerOpts{Timeout: timeout, TLSConfig: &tls.Config{
|
||||
conn, err := dialer.DialTLS(ctx, target.String(), dialer.Opts{Timeout: timeout, TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true, // #nosec G402 -- CLI tool only verifies revocation
|
||||
ServerName: target.Host,
|
||||
}})
|
||||
|
||||
@@ -25,7 +25,11 @@ func main() {
|
||||
}
|
||||
|
||||
// Use proxy-aware TLS dialer
|
||||
conn, err := dialer.DialTLS(context.Background(), server, dialer.DialerOpts{TLSConfig: &tls.Config{}}) // #nosec G402
|
||||
conn, err := dialer.DialTLS(
|
||||
context.Background(),
|
||||
server,
|
||||
dialer.Opts{TLSConfig: &tls.Config{}},
|
||||
) // #nosec G402
|
||||
die.If(err)
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
@@ -35,12 +35,12 @@ func main() {
|
||||
}
|
||||
|
||||
if config.leafOnly {
|
||||
dump.DisplayCert(os.Stdout, certs[0])
|
||||
dump.DisplayCert(os.Stdout, certs[0], config.showHash)
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range certs {
|
||||
dump.DisplayCert(os.Stdout, certs[i])
|
||||
dump.DisplayCert(os.Stdout, certs[i], config.showHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func main() {
|
||||
continue
|
||||
}
|
||||
// Use proxy-aware HTTP client with a reasonable timeout for connects/handshakes
|
||||
httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: 30 * time.Second})
|
||||
httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: 30 * time.Second})
|
||||
if err != nil {
|
||||
_, _ = lib.Warn(err, "building HTTP client for %s", remote)
|
||||
continue
|
||||
|
||||
@@ -43,7 +43,7 @@ func main() {
|
||||
}
|
||||
|
||||
var conn *tls.Conn
|
||||
conn, err = dialer.DialTLS(context.Background(), site, dialer.DialerOpts{TLSConfig: tlsCfg})
|
||||
conn, err = dialer.DialTLS(context.Background(), site, dialer.Opts{TLSConfig: tlsCfg})
|
||||
die.If(err)
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
|
||||
@@ -25,7 +25,7 @@ func main() {
|
||||
conn, err := dialer.DialTLS(
|
||||
context.Background(),
|
||||
hostPort.String(),
|
||||
dialer.DialerOpts{TLSConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||
dialer.Opts{TLSConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||
) // #nosec G402
|
||||
die.If(err)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package lib contains reusable helpers. This file provides proxy-aware
|
||||
// dialers for plain TCP and TLS connections using environment variables.
|
||||
// Package dialer provides proxy-aware dialers for plain TCP and TLS
|
||||
// connections using environment variables.
|
||||
//
|
||||
// Supported proxy environment variables (checked case-insensitively):
|
||||
// - SOCKS5_PROXY (e.g., socks5://user:pass@host:1080)
|
||||
@@ -66,7 +66,7 @@ func BaselineTLSConfig(skipVerify bool, secure bool) (*tls.Config, error) {
|
||||
|
||||
var debug = dbg.NewFromEnv()
|
||||
|
||||
// DialerOpts controls creation of proxy-aware dialers.
|
||||
// Opts controls creation of proxy-aware dialers.
|
||||
//
|
||||
// Timeout controls the maximum amount of time spent establishing the
|
||||
// underlying TCP connection and any proxy handshake. If zero, a
|
||||
@@ -75,7 +75,7 @@ var debug = dbg.NewFromEnv()
|
||||
// TLSConfig is used by the TLS dialer to configure the TLS handshake to
|
||||
// the target endpoint. If TLSConfig.ServerName is empty, it will be set
|
||||
// from the host portion of the address passed to DialContext.
|
||||
type DialerOpts struct {
|
||||
type Opts struct {
|
||||
Timeout time.Duration
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
@@ -88,7 +88,7 @@ type ContextDialer interface {
|
||||
// DialTCP is a convenience helper that dials a TCP connection to address
|
||||
// using a proxy-aware dialer derived from opts. It honors SOCKS5_PROXY,
|
||||
// HTTPS_PROXY, and HTTP_PROXY environment variables.
|
||||
func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, error) {
|
||||
func DialTCP(ctx context.Context, address string, opts Opts) (net.Conn, error) {
|
||||
d, err := NewNetDialer(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,7 +100,7 @@ func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, er
|
||||
// address using a proxy-aware dialer derived from opts. It returns a *tls.Conn.
|
||||
// It honors SOCKS5_PROXY, HTTPS_PROXY, and HTTP_PROXY environment variables and
|
||||
// uses opts.TLSConfig for the handshake (filling ServerName from address if empty).
|
||||
func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, error) {
|
||||
func DialTLS(ctx context.Context, address string, opts Opts) (*tls.Conn, error) {
|
||||
d, err := NewTLSDialer(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -123,7 +123,7 @@ func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, e
|
||||
// proxies discovered from the environment (SOCKS5_PROXY, HTTPS_PROXY, HTTP_PROXY).
|
||||
// The returned dialer supports context cancellation for direct and HTTP(S)
|
||||
// proxies and applies the configured timeout to connection/proxy handshake.
|
||||
func NewNetDialer(opts DialerOpts) (ContextDialer, error) {
|
||||
func NewNetDialer(opts Opts) (ContextDialer, error) {
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = 30 * time.Second
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func NewNetDialer(opts DialerOpts) (ContextDialer, error) {
|
||||
//
|
||||
// The returned dialer performs proxy negotiation (if any), then completes a
|
||||
// TLS handshake to the target using opts.TLSConfig.
|
||||
func NewTLSDialer(opts DialerOpts) (ContextDialer, error) {
|
||||
func NewTLSDialer(opts Opts) (ContextDialer, error) {
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = 30 * time.Second
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func getProxyURLFromEnv(name string) *url.URL {
|
||||
// HTTPS_PROXY, and NO_PROXY/no_proxy.
|
||||
// - Connection and TLS handshake timeouts are derived from opts.Timeout.
|
||||
// - For HTTPS targets, opts.TLSConfig is applied to the transport.
|
||||
func NewHTTPClient(opts DialerOpts) (*http.Client, error) {
|
||||
func NewHTTPClient(opts Opts) (*http.Client, error) {
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = 30 * time.Second
|
||||
}
|
||||
@@ -422,7 +422,7 @@ func drainHeaders(br *bufio.Reader) error {
|
||||
}
|
||||
|
||||
// newSOCKS5Dialer builds a context-aware wrapper over the x/net/proxy dialer.
|
||||
func newSOCKS5Dialer(u *url.URL, opts DialerOpts) (ContextDialer, error) {
|
||||
func newSOCKS5Dialer(u *url.URL, opts Opts) (ContextDialer, error) {
|
||||
var auth *xproxy.Auth
|
||||
if u.User != nil {
|
||||
user := u.User.Username()
|
||||
@@ -468,8 +468,8 @@ func (s *socks5ContextDialer) DialContext(ctx context.Context, network, address
|
||||
|
||||
// tlsWrappingDialer performs a TLS handshake over an existing base dialer.
|
||||
type tlsWrappingDialer struct {
|
||||
base ContextDialer
|
||||
tcfg *tls.Config
|
||||
base ContextDialer
|
||||
tcfg *tls.Config
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func (sf *ServerFetcher) String() string {
|
||||
}
|
||||
|
||||
func (sf *ServerFetcher) GetChain() ([]*x509.Certificate, error) {
|
||||
opts := dialer.DialerOpts{
|
||||
opts := dialer.Opts{
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: sf.insecure, // #nosec G402 - no shit sherlock
|
||||
RootCAs: sf.roots,
|
||||
|
||||
24
lib/lib.go
24
lib/lib.go
@@ -3,6 +3,7 @@ package lib
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -118,6 +119,8 @@ func IsDigit(b byte) bool {
|
||||
return b >= '0' && b <= '9'
|
||||
}
|
||||
|
||||
const signedaMask64 = 1<<63 - 1
|
||||
|
||||
// ParseDuration parses a duration string into a time.Duration.
|
||||
// It supports standard units (ns, us/µs, ms, s, m, h) plus extended units:
|
||||
// d (days, 24h), w (weeks, 7d), y (years, 365d).
|
||||
@@ -127,7 +130,7 @@ func IsDigit(b byte) bool {
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
s = strings.ToLower(s) // Normalize to lowercase for case-insensitivity.
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("empty duration string")
|
||||
return 0, errors.New("empty duration string")
|
||||
}
|
||||
|
||||
var total time.Duration
|
||||
@@ -165,23 +168,24 @@ func ParseDuration(s string) (time.Duration, error) {
|
||||
var d time.Duration
|
||||
switch unit {
|
||||
case "ns":
|
||||
d = time.Nanosecond * time.Duration(num)
|
||||
d = time.Nanosecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "us", "µs":
|
||||
d = time.Microsecond * time.Duration(num)
|
||||
d = time.Microsecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "ms":
|
||||
d = time.Millisecond * time.Duration(num)
|
||||
d = time.Millisecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "s":
|
||||
d = time.Second * time.Duration(num)
|
||||
d = time.Second * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "m":
|
||||
d = time.Minute * time.Duration(num)
|
||||
d = time.Minute * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "h":
|
||||
d = time.Hour * time.Duration(num)
|
||||
d = time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "d":
|
||||
d = 24 * time.Hour * time.Duration(num)
|
||||
d = 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "w":
|
||||
d = 7 * 24 * time.Hour * time.Duration(num)
|
||||
d = 7 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "y":
|
||||
d = 365 * 24 * time.Hour * time.Duration(num) // Approximate, non-leap year.
|
||||
// Approximate, non-leap year.
|
||||
d = 365 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off;
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown unit %q at position %d", s[unitStart:i], unitStart)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user