Skip to content

Commit 57ee468

Browse files
committed
fix ssl certificate renewal
1 parent cd28141 commit 57ee468

File tree

5 files changed

+182
-48
lines changed

5 files changed

+182
-48
lines changed

int/config/check.go

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"time"
89

910
"github.com/massalabs/station/int/configuration"
1011
"github.com/massalabs/station/pkg/certificate"
@@ -28,31 +29,19 @@ func Check() error {
2829

2930
certPath := filepath.Join(caRootPath, configuration.CertificateAuthorityFileName)
3031

31-
isBrandNewCA := false
32-
33-
_, err = os.Stat(certPath)
34-
if os.IsNotExist(err) {
35-
err = certificate.GenerateCA(
36-
configuration.OrganizationName,
37-
configuration.CertificateAuthorityKeyFileName,
38-
configuration.CertificateAuthorityFileName,
39-
caRootPath,
40-
)
41-
if err != nil {
42-
return caNonBlockingError("failed to generate new CA", err)
43-
}
44-
45-
logger.Infof("A new CA was generated at %s.", certPath)
46-
47-
// A new CA was generated, we need to clean the certificates.
48-
isBrandNewCA = true
32+
// Ensure CA certificate exists and is valid
33+
isBrandNewCA, err := ensureValidCA(certPath, caRootPath)
34+
if err != nil {
35+
return caNonBlockingError("failed to ensure valid CA", err)
4936
}
5037

38+
// Check certificate in system store
5139
err = checkCertificate(certPath)
5240
if err != nil {
5341
resultErr = caNonBlockingError("Error while checking certificate", err)
5442
}
5543

44+
// Check NSS configuration
5645
err = checkNSS(certPath, isBrandNewCA)
5746
if err != nil {
5847
resultErr = caNonBlockingError("Error while checking NSS", err)
@@ -61,6 +50,54 @@ func Check() error {
6150
return resultErr
6251
}
6352

53+
// ensureValidCA ensures that a valid CA certificate exists at the specified path.
54+
// It returns true if a new CA was generated, false if existing CA is valid.
55+
func ensureValidCA(certPath, caRootPath string) (bool, error) {
56+
// Check if certificate file exists
57+
_, err := os.Stat(certPath)
58+
if os.IsNotExist(err) {
59+
logger.Infof("Certificate file does not exist, generating new CA...")
60+
return true, generateCA(caRootPath, "certificate file does not exist")
61+
}
62+
63+
// Certificate file exists, validate it
64+
existingCert, loadErr := certificate.LoadCertificate(certPath)
65+
if loadErr != nil {
66+
logger.Warnf("Failed to load existing certificate: %v, generating new one", loadErr)
67+
return true, generateCA(caRootPath, "failed to load existing certificate")
68+
}
69+
70+
// Check if certificate is expired
71+
if existingCert.NotAfter.Before(time.Now()) {
72+
logger.Warnf("Certificate is expired (NotAfter=%s), generating new one", existingCert.NotAfter.String())
73+
return true, generateCA(caRootPath, "certificate is expired")
74+
}
75+
76+
// Certificate is valid
77+
logger.Debugf("Certificate is valid until %s", existingCert.NotAfter.String())
78+
return false, nil
79+
}
80+
81+
// generateCA generates a new CA certificate with consistent parameters and logging.
82+
func generateCA(caRootPath, reason string) error {
83+
logger.Infof("Generating new CA certificate (reason: %s)...", reason)
84+
85+
err := certificate.GenerateCA(
86+
configuration.OrganizationName,
87+
configuration.CertificateAuthorityKeyFileName,
88+
configuration.CertificateAuthorityFileName,
89+
caRootPath,
90+
)
91+
if err != nil {
92+
return fmt.Errorf("failed to generate CA certificate: %w", err)
93+
}
94+
95+
certPath := filepath.Join(caRootPath, configuration.CertificateAuthorityFileName)
96+
logger.Infof("New CA certificate generated successfully at %s", certPath)
97+
98+
return nil
99+
}
100+
64101
// nonBlockingError logs a non blocking error. It always returns a `nil` error to be used in `return`.
65102
func nonBlockingError(context string, consequence string, err error) error {
66103
if err != nil {
@@ -78,27 +115,49 @@ func caNonBlockingError(context string, err error) error {
78115

79116
// checkCertificate checks the certificate configuration.
80117
func checkCertificate(certPath string) error {
118+
// Load certificate
119+
certCA, err := loadCertificate(certPath)
120+
if err != nil {
121+
return err
122+
}
123+
124+
// Check if certificate is trusted by the operating system
125+
return ensureCertificateInSystemStore(certCA)
126+
}
127+
128+
// loadCertificate loads a certificate.
129+
func loadCertificate(certPath string) (*x509.Certificate, error) {
81130
certCA, err := certificate.LoadCertificate(certPath)
82131
if err != nil {
83-
return fmt.Errorf("failed to load the CA: %w", err)
132+
return nil, fmt.Errorf("failed to load the CA: %w", err)
84133
}
85134

86-
// disable linting as we don't care about checking specific attributes
87-
//nolint:exhaustruct
88-
_, err = certCA.Verify(x509.VerifyOptions{})
135+
return certCA, nil
136+
}
137+
138+
// ensureCertificateInSystemStore ensures the certificate is trusted by the operating system.
139+
func ensureCertificateInSystemStore(certCA *x509.Certificate) error {
140+
// Check if certificate is already trusted by the OS
141+
//nolint:exhaustruct // We don't care about checking specific attributes
142+
_, err := certCA.Verify(x509.VerifyOptions{})
89143
if err != nil {
90-
logger.Debug("the CA is not known by the operating system.")
144+
logger.Debug("Certificate is not trusted by the operating system, adding to store")
145+
return addCertificateToSystemStore(certCA)
146+
}
91147

92-
err := store.Add(certCA)
93-
if err != nil {
94-
return fmt.Errorf("failed to add the CA to the operating system: %w", err)
95-
}
148+
logger.Debug("Certificate is already trusted by the operating system")
149+
return nil
150+
}
96151

97-
logger.Debug("the CA was added to the operating system.")
98-
} else {
99-
logger.Debug("the CA is known by the operating system.")
152+
// addCertificateToSystemStore adds a certificate to the operating system store.
153+
func addCertificateToSystemStore(certCA *x509.Certificate) error {
154+
err := store.Add(certCA)
155+
if err != nil {
156+
logger.Errorf("Failed to add certificate to operating system: %v", err)
157+
return fmt.Errorf("failed to add the CA to the operating system: %w", err)
100158
}
101159

160+
logger.Debug("Certificate successfully added to the operating system store")
102161
return nil
103162
}
104163

int/config/manager.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ const (
2323
configDirName = "massa-station"
2424
)
2525

26-
var (
27-
// ErrNetworkAlreadyExists is returned when trying to add a network with a name that already exists
28-
ErrNetworkAlreadyExists = errors.New("network already exists")
29-
)
26+
// ErrNetworkAlreadyExists is returned when trying to add a network with a name that already exists
27+
var ErrNetworkAlreadyExists = errors.New("network already exists")
3028

3129
// MSConfigManager represents a manager for network configurations.
3230
type MSConfigManager struct {

pkg/certificate/store/store_windows.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@ import (
55
"fmt"
66
"syscall"
77
"unsafe"
8+
9+
"github.com/massalabs/station/pkg/logger"
810
)
911

1012
const (
1113
CRYPT_E_NOT_FOUND = 0x80092004
14+
15+
// Certificate store addition flags
16+
CERT_STORE_ADD_NEW = 1
17+
CERT_STORE_ADD_REPLACE_EXISTING = 3
18+
CERT_STORE_ADD_ALWAYS = 4
1219
)
1320

1421
// The functions below return an error even though they succeed.
@@ -24,6 +31,8 @@ var (
2431

2532
// Add adds the given certificate to the windows root store.
2633
func Add(cert *x509.Certificate) error {
34+
logger.Debugf("Adding certificate to Windows store: Subject=%s", cert.Subject.String())
35+
2736
rootStore, err := openStore()
2837
if err != nil {
2938
return fmt.Errorf("failed to open windows root store: %w", err)
@@ -39,6 +48,7 @@ func Add(cert *x509.Certificate) error {
3948
return fmt.Errorf("failed to close windows root store: %w", err)
4049
}
4150

51+
logger.Debugf("Successfully added certificate to Windows root store")
4252
return nil
4353
}
4454

@@ -89,26 +99,54 @@ func closeStore(store uintptr) error {
8999
return nil
90100
}
91101

92-
// addCertificateToStore adds the given certificate to the windows root store.
93-
func addCertificateToStore(store uintptr, cert *x509.Certificate) error {
94-
if store == 0 {
95-
return fmt.Errorf("pointer is nil")
96-
}
97-
102+
// addCertificateWithFlag attempts to add a certificate using the specified store flag.
103+
func addCertificateWithFlag(store uintptr, cert *x509.Certificate, flag uintptr) (bool, error) {
98104
ret, _, err := procCertAddEncodedCertificateToStore.Call(
99105
uintptr(store),
100106
uintptr(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING),
101107
uintptr(unsafe.Pointer(&cert.Raw[0])),
102108
uintptr(len(cert.Raw)),
103-
3,
109+
flag,
104110
0,
105111
)
106112

107-
if ret == 0 && err != nil {
113+
return ret != 0, err
114+
}
115+
116+
// addCertificateToStore adds the given certificate to the windows root store.
117+
func addCertificateToStore(store uintptr, cert *x509.Certificate) error {
118+
if store == 0 {
119+
return fmt.Errorf("pointer is nil")
120+
}
121+
122+
// Try CERT_STORE_ADD_NEW first
123+
success, err := addCertificateWithFlag(store, cert, CERT_STORE_ADD_NEW)
124+
if success {
125+
return nil // Success
126+
}
127+
128+
// If failed, try CERT_STORE_ADD_REPLACE_EXISTING
129+
logger.Debugf("Certificate addition with ADD_NEW failed, trying REPLACE_EXISTING")
130+
success, err = addCertificateWithFlag(store, cert, CERT_STORE_ADD_REPLACE_EXISTING)
131+
if success {
132+
logger.Debugf("Certificate successfully replaced existing")
133+
return nil
134+
}
135+
136+
// Final fallback: try CERT_STORE_ADD_ALWAYS
137+
success, err = addCertificateWithFlag(store, cert, CERT_STORE_ADD_ALWAYS)
138+
if success {
139+
logger.Debugf("Certificate successfully added with ALWAYS flag")
140+
return nil
141+
}
142+
143+
// All strategies failed
144+
logger.Errorf("All certificate addition strategies failed")
145+
if err != nil {
108146
return fmt.Errorf("failed adding cert: %w", err)
109147
}
110148

111-
return nil
149+
return fmt.Errorf("failed adding cert: all strategies returned 0")
112150
}
113151

114152
// deleteCertificateFromStore removes the given certificate from the windows root store.

pkg/certstore/store_windows.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,29 @@ func (s *CertStore) AddCertificate(cert *x509.Certificate) error {
156156
return err
157157
}
158158

159+
// First try to add as new certificate
159160
err = s.winAPI.CertAddCertificateContextToStore(
160161
s.handler,
161162
certContextPtr,
162163
windows.CERT_STORE_ADD_NEW,
163164
nil,
164165
)
165166
if err != nil {
166-
return interpretError(err)
167+
interpretedErr := interpretError(err)
168+
// If certificate already exists (possibly expired), replace it
169+
if errors.Is(interpretedErr, ErrCertAlreadyExists) {
170+
err = s.winAPI.CertAddCertificateContextToStore(
171+
s.handler,
172+
certContextPtr,
173+
windows.CERT_STORE_ADD_REPLACE_EXISTING,
174+
nil,
175+
)
176+
if err != nil {
177+
return interpretError(err)
178+
}
179+
} else {
180+
return interpretedErr
181+
}
167182
}
168183

169184
return nil

pkg/certstore/store_windows_test.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,44 @@ func TestCertStore_AddCertificate(t *testing.T) {
8181
wantErr: otherError,
8282
},
8383
{
84-
name: "error when CertAddCertificateContextToStore fails",
84+
name: "error when CertAddCertificateContextToStore fails with non-exists error",
8585
handler: windows.Handle(1),
8686
setupMock: func() {
8787
mockAPI.EXPECT().CertCreateCertificateContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCertContext, nil)
88-
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(ErrCertAlreadyExists)
88+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(otherError)
8989
},
90-
wantErr: ErrCertAlreadyExists,
90+
wantErr: otherError,
91+
},
92+
{
93+
name: "success when certificate already exists - replace existing",
94+
handler: windows.Handle(1),
95+
setupMock: func() {
96+
mockAPI.EXPECT().CertCreateCertificateContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCertContext, nil)
97+
// First call fails with ErrCertAlreadyExists
98+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Eq(uint32(1)), gomock.Any()).Return(ErrCertAlreadyExists)
99+
// Second call with replace flag succeeds
100+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Eq(uint32(3)), gomock.Any()).Return(nil)
101+
},
102+
wantErr: nil,
103+
},
104+
{
105+
name: "error when certificate already exists and replace also fails",
106+
handler: windows.Handle(1),
107+
setupMock: func() {
108+
mockAPI.EXPECT().CertCreateCertificateContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCertContext, nil)
109+
// First call fails with ErrCertAlreadyExists
110+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Eq(uint32(1)), gomock.Any()).Return(ErrCertAlreadyExists)
111+
// Second call with replace flag also fails
112+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Eq(uint32(3)), gomock.Any()).Return(otherError)
113+
},
114+
wantErr: otherError,
91115
},
92116
{
93117
name: "success case",
94118
handler: windows.Handle(1),
95119
setupMock: func() {
96120
mockAPI.EXPECT().CertCreateCertificateContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCertContext, nil)
97-
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
121+
mockAPI.EXPECT().CertAddCertificateContextToStore(gomock.Any(), gomock.Any(), gomock.Eq(uint32(1)), gomock.Any()).Return(nil)
98122
},
99123
wantErr: nil,
100124
},

0 commit comments

Comments
 (0)