Skip to content

Commit 05a8049

Browse files
committed
home: cache certificate has ip addrs;
1 parent b2e2572 commit 05a8049

2 files changed

Lines changed: 119 additions & 10 deletions

File tree

internal/home/tls.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type tlsManager struct {
3535
// logger is used for logging the operation of the TLS Manager.
3636
logger *slog.Logger
3737

38-
// mu protects certLastMod, tlsCert, tlsConf, extTLSConf.
38+
// mu protects certHasIPAddrs, certLastMod, tlsCert, tlsConf, extTLSConf.
3939
mu *sync.Mutex
4040

4141
// certLastMod is the last modification time of the certificate file.
@@ -76,6 +76,10 @@ type tlsManager struct {
7676
// customCipherIDs are the IDs of the cipher suites that AdGuard Home must
7777
// use.
7878
customCipherIDs []uint16
79+
80+
// certHasIPAddrs is true if the current TLS certificate has at least one
81+
// IP address in its SAN extension.
82+
certHasIPAddrs bool
7983
}
8084

8185
// tlsManagerConfig contains the settings for initializing the TLS manager.
@@ -173,7 +177,7 @@ func newTLSManager(ctx context.Context, conf *tlsManagerConfig) (m *tlsManager,
173177
GetCertificate: m.onGetCertificate,
174178
}
175179

176-
m.tlsCert = &cert
180+
m.setTLSCert(&cert)
177181
m.setCertFileTime(ctx)
178182

179183
return m, nil
@@ -883,13 +887,7 @@ func (m *tlsManager) HasIPAddrs() (ok bool) {
883887
m.mu.Lock()
884888
defer m.mu.Unlock()
885889

886-
if m.tlsCert == nil || m.tlsCert.Leaf == nil {
887-
return false
888-
}
889-
890-
// TODO(m.kazantsev): Consider storing the value instead of parsing each
891-
// time.
892-
return aghtls.CertificateHasIP(m.tlsCert.Leaf)
890+
return m.certHasIPAddrs
893891
}
894892

895893
// onGetCertificate gets [*tls.Certificate] from [*tls.Config]. If
@@ -909,6 +907,13 @@ func (m *tlsManager) onGetCertificate(chi *tls.ClientHelloInfo) (cert *tls.Certi
909907
return &tlsCert, nil
910908
}
911909

910+
// setTLSCert stores the certificate and updates the cached properties of the
911+
// certificate. cert and cert.Leaf must not be nil. m.mu must be locked.
912+
func (m *tlsManager) setTLSCert(cert *tls.Certificate) {
913+
m.tlsCert = cert
914+
m.certHasIPAddrs = aghtls.CertificateHasIP(cert.Leaf)
915+
}
916+
912917
// updateTLSCert loads and updates a TLS certificate for m.tlsConf. If
913918
// m.tlsConf is nil, it will be initialized. extTLSConf must not be nil. m.mu
914919
// must be locked.
@@ -933,7 +938,7 @@ func (m *tlsManager) updateTLSCert(extTLSConf *tlsConfigSettings) (err error) {
933938
}
934939
}
935940

936-
m.tlsCert = &cert
941+
m.setTLSCert(&cert)
937942

938943
return nil
939944
}

internal/home/tls_internal_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/x509"
99
"encoding/pem"
1010
"math/big"
11+
"net"
1112
"os"
1213
"path/filepath"
1314
"testing"
@@ -164,6 +165,35 @@ func newCertWithoutIP(tb testing.TB) (
164165
return caCert, buf.Bytes(), leafKeyPEM
165166
}
166167

168+
// newCertWithIP generates a self-signed certificate with an IP address in its
169+
// SAN extension and returns the PEM-encoded certificate and private key.
170+
func newCertWithIP(tb testing.TB) (certPEM, keyPEM []byte) {
171+
tb.Helper()
172+
173+
key, err := rsa.GenerateKey(rand.Reader, 2048)
174+
require.NoError(tb, err)
175+
176+
now := time.Now()
177+
tmpl := &x509.Certificate{
178+
SerialNumber: big.NewInt(1),
179+
NotBefore: now.Add(-time.Hour),
180+
NotAfter: now.Add(time.Hour),
181+
KeyUsage: x509.KeyUsageDigitalSignature,
182+
IPAddresses: []net.IP{net.ParseIP("192.0.2.1")},
183+
}
184+
185+
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
186+
require.NoError(tb, err)
187+
188+
certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
189+
keyPEM = pem.EncodeToMemory(&pem.Block{
190+
Type: "RSA PRIVATE KEY",
191+
Bytes: x509.MarshalPKCS1PrivateKey(key),
192+
})
193+
194+
return certPEM, keyPEM
195+
}
196+
167197
// newCertAndKey is a helper function that generates certificate and key.
168198
func newCertAndKey(tb testing.TB, n int64) (certDER []byte, key *rsa.PrivateKey) {
169199
tb.Helper()
@@ -312,3 +342,77 @@ func TestTLSManager_Reload(t *testing.T) {
312342
extTLSConf = m.extendedTLSConfig()
313343
assertCertSerialNumber(t, extTLSConf, snAfter)
314344
}
345+
346+
func TestTLSManager_HasIPAddrs(t *testing.T) {
347+
ctx := testutil.ContextWithTimeout(t, testTimeout)
348+
349+
_, noIPChainPEM, noIPKeyPEM := newCertWithoutIP(t)
350+
ipChainPEM, ipKeyPEM := newCertWithIP(t)
351+
352+
noIPSettings := tlsConfigSettings{
353+
Enabled: true,
354+
CertificateChain: string(noIPChainPEM),
355+
PrivateKey: string(noIPKeyPEM),
356+
}
357+
ipSettings := tlsConfigSettings{
358+
Enabled: true,
359+
CertificateChain: string(ipChainPEM),
360+
PrivateKey: string(ipKeyPEM),
361+
}
362+
363+
testCases := []struct {
364+
name string
365+
settings tlsConfigSettings
366+
update *tlsConfigSettings
367+
want bool
368+
}{{
369+
name: "no_ip_in_cert",
370+
settings: noIPSettings,
371+
update: nil,
372+
want: false,
373+
}, {
374+
name: "has_ip_in_cert",
375+
settings: ipSettings,
376+
update: nil,
377+
want: true,
378+
}, {
379+
name: "updated_to_ip",
380+
settings: noIPSettings,
381+
update: &tlsConfigSettings{
382+
CertificateChainData: ipChainPEM,
383+
PrivateKeyData: ipKeyPEM,
384+
},
385+
want: true,
386+
}, {
387+
name: "updated_to_no_ip",
388+
settings: ipSettings,
389+
update: &tlsConfigSettings{
390+
CertificateChainData: noIPChainPEM,
391+
PrivateKeyData: noIPKeyPEM,
392+
},
393+
want: false,
394+
}}
395+
396+
for _, tc := range testCases {
397+
t.Run(tc.name, func(t *testing.T) {
398+
m, err := newTLSManager(ctx, &tlsManagerConfig{
399+
logger: testLogger,
400+
confModifier: agh.EmptyConfigModifier{},
401+
manager: aghtls.EmptyManager{},
402+
tlsSettings: tc.settings,
403+
servePlainDNS: false,
404+
})
405+
require.NoError(t, err)
406+
407+
if tc.update != nil {
408+
m.mu.Lock()
409+
err = m.updateTLSCert(tc.update)
410+
m.mu.Unlock()
411+
412+
require.NoError(t, err)
413+
}
414+
415+
assert.Equal(t, tc.want, m.HasIPAddrs())
416+
})
417+
}
418+
}

0 commit comments

Comments
 (0)