| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package x509check |
| 4 | |
| 5 | import ( |
| 6 | "crypto/tls" |
| 7 | "crypto/x509" |
| 8 | "encoding/pem" |
| 9 | "fmt" |
| 10 | "net" |
| 11 | "net/smtp" |
| 12 | "net/url" |
| 13 | "os" |
| 14 | "time" |
| 15 | |
| 16 | "github.com/netdata/netdata/go/plugins/pkg/tlscfg" |
| 17 | ) |
| 18 | |
| 19 | type provider interface { |
| 20 | certificates() ([]*x509.Certificate, error) |
| 21 | } |
| 22 | |
| 23 | type fromFile struct { |
| 24 | path string |
| 25 | } |
| 26 | |
| 27 | type fromNet struct { |
| 28 | url *url.URL |
| 29 | tlsConfig *tls.Config |
| 30 | timeout time.Duration |
| 31 | } |
| 32 | |
| 33 | type fromSMTP struct { |
| 34 | url *url.URL |
| 35 | tlsConfig *tls.Config |
| 36 | timeout time.Duration |
| 37 | } |
| 38 | |
| 39 | func newProvider(config Config) (provider, error) { |
| 40 | sourceURL, err := url.Parse(config.Source) |
| 41 | if err != nil { |
| 42 | return nil, fmt.Errorf("source parse: %v", err) |
| 43 | } |
| 44 | |
| 45 | tlsCfg, err := tlscfg.NewTLSConfig(config.TLSConfig) |
| 46 | if err != nil { |
| 47 | return nil, fmt.Errorf("create tls config: %v", err) |
| 48 | } |
| 49 | |
| 50 | if tlsCfg == nil { |
| 51 | tlsCfg = &tls.Config{} |
| 52 | } |
| 53 | tlsCfg.ServerName = sourceURL.Hostname() |
| 54 | |
| 55 | switch sourceURL.Scheme { |
| 56 | case "file": |
| 57 | return &fromFile{path: sourceURL.Path}, nil |
| 58 | case "https", "udp", "udp4", "udp6", "tcp", "tcp4", "tcp6": |
| 59 | if sourceURL.Scheme == "https" { |
| 60 | sourceURL.Scheme = "tcp" |
| 61 | } |
| 62 | return &fromNet{url: sourceURL, tlsConfig: tlsCfg, timeout: config.Timeout.Duration()}, nil |
| 63 | case "smtp": |
| 64 | sourceURL.Scheme = "tcp" |
| 65 | return &fromSMTP{url: sourceURL, tlsConfig: tlsCfg, timeout: config.Timeout.Duration()}, nil |
| 66 | default: |
| 67 | return nil, fmt.Errorf("unsupported scheme '%s'", sourceURL) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | func (f fromFile) certificates() ([]*x509.Certificate, error) { |
| 72 | content, err := os.ReadFile(f.path) |
| 73 | if err != nil { |
| 74 | return nil, fmt.Errorf("error on reading '%s': %v", f.path, err) |
| 75 | } |
| 76 | |
| 77 | block, _ := pem.Decode(content) |
| 78 | if block == nil { |
| 79 | return nil, fmt.Errorf("error on decoding '%s': %v", f.path, err) |
| 80 | } |
| 81 | |
| 82 | cert, err := x509.ParseCertificate(block.Bytes) |
| 83 | if err != nil { |
| 84 | return nil, fmt.Errorf("error on parsing certificate '%s': %v", f.path, err) |
| 85 | } |
| 86 | |
| 87 | return []*x509.Certificate{cert}, nil |
| 88 | } |
| 89 | |
| 90 | func (f fromNet) certificates() ([]*x509.Certificate, error) { |
| 91 | ipConn, err := net.DialTimeout(f.url.Scheme, f.url.Host, f.timeout) |
| 92 | if err != nil { |
| 93 | return nil, fmt.Errorf("error on dial to '%s': %v", f.url, err) |
| 94 | } |
| 95 | defer func() { _ = ipConn.Close() }() |
| 96 | |
| 97 | conn := tls.Client(ipConn, f.tlsConfig.Clone()) |
| 98 | defer func() { _ = conn.Close() }() |
| 99 | if err := conn.Handshake(); err != nil { |
| 100 | return nil, fmt.Errorf("error on SSL handshake with '%s': %v", f.url, err) |
| 101 | } |
| 102 | |
| 103 | certs := conn.ConnectionState().PeerCertificates |
| 104 | return certs, nil |
| 105 | } |
| 106 | |
| 107 | func (f fromSMTP) certificates() ([]*x509.Certificate, error) { |
| 108 | ipConn, err := net.DialTimeout(f.url.Scheme, f.url.Host, f.timeout) |
| 109 | if err != nil { |
| 110 | return nil, fmt.Errorf("error on dial to '%s': %v", f.url, err) |
| 111 | } |
| 112 | defer func() { _ = ipConn.Close() }() |
| 113 | |
| 114 | host, _, _ := net.SplitHostPort(f.url.Host) |
| 115 | smtpClient, err := smtp.NewClient(ipConn, host) |
| 116 | if err != nil { |
| 117 | return nil, fmt.Errorf("error on creating SMTP client: %v", err) |
| 118 | } |
| 119 | defer func() { _ = smtpClient.Quit() }() |
| 120 | |
| 121 | err = smtpClient.StartTLS(f.tlsConfig.Clone()) |
| 122 | if err != nil { |
| 123 | return nil, fmt.Errorf("error on startTLS with '%s': %v", f.url, err) |
| 124 | } |
| 125 | |
| 126 | conn, ok := smtpClient.TLSConnectionState() |
| 127 | if !ok { |
| 128 | return nil, fmt.Errorf("startTLS didn't succeed") |
| 129 | } |
| 130 | return conn.PeerCertificates, nil |
| 131 | } |