...
1
2
3
4
5
6
7
8
9
10
11
12
13 package cert
14
15 import (
16 "crypto/tls"
17 "crypto/x509"
18 "encoding/json"
19 "errors"
20 "fmt"
21 "os"
22 "os/exec"
23 "os/user"
24 "path/filepath"
25 "sync"
26 "time"
27 )
28
29 const (
30 metadataPath = ".secureConnect"
31 metadataFile = "context_aware_metadata.json"
32 )
33
34 type secureConnectSource struct {
35 metadata secureConnectMetadata
36
37
38 cachedCertMutex sync.Mutex
39 cachedCert *tls.Certificate
40 }
41
42 type secureConnectMetadata struct {
43 Cmd []string `json:"cert_provider_command"`
44 }
45
46
47
48
49
50
51 func NewSecureConnectSource(configFilePath string) (Source, error) {
52 if configFilePath == "" {
53 user, err := user.Current()
54 if err != nil {
55
56 return nil, errSourceUnavailable
57 }
58 configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
59 }
60
61 file, err := os.ReadFile(configFilePath)
62 if err != nil {
63 if errors.Is(err, os.ErrNotExist) {
64
65 return nil, errSourceUnavailable
66 }
67 return nil, err
68 }
69
70 var metadata secureConnectMetadata
71 if err := json.Unmarshal(file, &metadata); err != nil {
72 return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
73 }
74 if err := validateMetadata(metadata); err != nil {
75 return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
76 }
77 return (&secureConnectSource{
78 metadata: metadata,
79 }).getClientCertificate, nil
80 }
81
82 func validateMetadata(metadata secureConnectMetadata) error {
83 if len(metadata.Cmd) == 0 {
84 return errors.New("empty cert_provider_command")
85 }
86 return nil
87 }
88
89 func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
90 s.cachedCertMutex.Lock()
91 defer s.cachedCertMutex.Unlock()
92 if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
93 return s.cachedCert, nil
94 }
95
96 for i := 0; i < len(s.metadata.Cmd); i++ {
97 s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
98 }
99 command := s.metadata.Cmd
100 data, err := exec.Command(command[0], command[1:]...).Output()
101 if err != nil {
102 return nil, err
103 }
104 cert, err := tls.X509KeyPair(data, data)
105 if err != nil {
106 return nil, err
107 }
108 s.cachedCert = &cert
109 return &cert, nil
110 }
111
112
113 func isCertificateExpired(cert *tls.Certificate) bool {
114 if len(cert.Certificate) == 0 {
115 return true
116 }
117 parsed, err := x509.ParseCertificate(cert.Certificate[0])
118 if err != nil {
119 return true
120 }
121 return time.Now().After(parsed.NotAfter)
122 }
123
View as plain text