feat: extend certificate pinning to all TLS protocols and add SSH
host-key pinning
This commit is contained in:
@@ -6,7 +6,10 @@ package gemini
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -63,6 +66,27 @@ func (p *Protocol) downloadWithRedirects(ctx context.Context, req *core.Download
|
||||
InsecureSkipVerify: p.TLSInsecure,
|
||||
}
|
||||
|
||||
// Certificate pinning: require the leaf certificate to match the
|
||||
// caller-provided SHA-256 hash. The check runs after the standard
|
||||
// verification chain so a pinned self-signed cert still needs
|
||||
// InsecureSkipVerify (or a matching CA) to clear the normal path.
|
||||
if req != nil && req.PinnedCertHash != "" {
|
||||
expected := strings.ToLower(strings.TrimSpace(req.PinnedCertHash))
|
||||
tlsCfg.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
for _, rawCert := range rawCerts {
|
||||
cert, err := x509.ParseCertificate(rawCert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hash := sha256.Sum256(cert.Raw)
|
||||
if hex.EncodeToString(hash[:]) == expected {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("certificate pinning failed: no certificate matches sha-256 %s", expected)
|
||||
}
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{Timeout: 30 * time.Second}
|
||||
conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsCfg)
|
||||
if err != nil {
|
||||
@@ -123,11 +147,12 @@ func (p *Protocol) downloadWithRedirects(ctx context.Context, req *core.Download
|
||||
}
|
||||
// Follow redirect with incremented depth
|
||||
redirectReq := &core.DownloadRequest{
|
||||
URL: resolved,
|
||||
Output: req.Output,
|
||||
Verbose: req.Verbose,
|
||||
Writer: req.Writer,
|
||||
Ctx: ctx,
|
||||
URL: resolved,
|
||||
Output: req.Output,
|
||||
Verbose: req.Verbose,
|
||||
Writer: req.Writer,
|
||||
PinnedCertHash: req.PinnedCertHash,
|
||||
Ctx: ctx,
|
||||
}
|
||||
return p.downloadWithRedirects(ctx, redirectReq, redirectCount+1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user