fix(tls): use hostname-only matching instead of unstable cert fingerprinting
- commit
- 43577dd5c10fe78a9234f72f4016f6e91e11cee6
- author
- bamen · · 2025-10-15 16:28 UTC
- parent
- 7928d62
- signature
- unsigned
5 files · +52 -22
M CHANGELOG.md +1 -1
| @@ -22,7 +22,7 @@ All notable changes to this project will be documented in this file. | |||
| 22 | 22 | ### Fixed | |
| 23 | 23 | - **TLS certificate validation restored**: Re-implemented persistent certificate error handling (broken during gotk4 migration) | |
| 24 | 24 | - Interactive three-option dialog: "Go Back", "Proceed Once (Unsafe)", and "Always Accept This Site" | |
| 25 | - | - SQLite-based certificate decision storage with SHA256 certificate fingerprinting | |
| 25 | + | - SQLite-based hostname decision storage (hostname-only matching - GIO certificate properties are unstable) | |
| 26 | 26 | - Smart expiration: "Proceed Once" temporary (not stored), "Always Accept" persists for 30 days | |
| 27 | 27 | - Automatic cleanup of expired validations on application startup | |
| 28 | 28 | - **Known issue**: Accepted certificates not pre-loaded into WebKit session on startup - stored decisions only apply when TLS error occurs again | |
| @@ -22,7 +22,7 @@ All notable changes to this project will be documented in this file. | |||||
| 22 | ### Fixed | 22 | ### Fixed | ||
| 23 | - **TLS certificate validation restored**: Re-implemented persistent certificate error handling (broken during gotk4 migration) | 23 | - **TLS certificate validation restored**: Re-implemented persistent certificate error handling (broken during gotk4 migration) | ||
| 24 | - Interactive three-option dialog: "Go Back", "Proceed Once (Unsafe)", and "Always Accept This Site" | 24 | - Interactive three-option dialog: "Go Back", "Proceed Once (Unsafe)", and "Always Accept This Site" | ||
| 25 | - | - SQLite-based certificate decision storage with SHA256 certificate fingerprinting | 25 | + | - SQLite-based hostname decision storage (hostname-only matching - GIO certificate properties are unstable) |
| 26 | - Smart expiration: "Proceed Once" temporary (not stored), "Always Accept" persists for 30 days | 26 | - Smart expiration: "Proceed Once" temporary (not stored), "Always Accept" persists for 30 days | ||
| 27 | - Automatic cleanup of expired validations on application startup | 27 | - Automatic cleanup of expired validations on application startup | ||
| 28 | - **Known issue**: Accepted certificates not pre-loaded into WebKit session on startup - stored decisions only apply when TLS error occurs again | 28 | - **Known issue**: Accepted certificates not pre-loaded into WebKit session on startup - stored decisions only apply when TLS error occurs again | ||
M internal/db/certificate_validations.sql.go +24 -2
| @@ -31,8 +31,8 @@ func (q *Queries) DeleteExpiredCertificateValidations(ctx context.Context) error | |||
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | 33 | const GetCertificateValidation = `-- name: GetCertificateValidation :one | |
| 34 | - | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations | |
| 35 | - | WHERE hostname = ? AND certificate_hash = ? | |
| 34 | + | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations | |
| 35 | + | WHERE hostname = ? AND certificate_hash = ? | |
| 36 | 36 | AND (expires_at IS NULL OR expires_at > datetime('now')) | |
| 37 | 37 | LIMIT 1 | |
| 38 | 38 | ` | |
| @@ -51,6 +51,28 @@ func (q *Queries) GetCertificateValidation(ctx context.Context, hostname string, | |||
| 51 | 51 | return i, err | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | + | const GetCertificateValidationByHostname = `-- name: GetCertificateValidationByHostname :one | |
| 55 | + | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations | |
| 56 | + | WHERE hostname = ? | |
| 57 | + | AND (expires_at IS NULL OR expires_at > datetime('now')) | |
| 58 | + | ORDER BY created_at DESC | |
| 59 | + | LIMIT 1 | |
| 60 | + | ` | |
| 61 | + | ||
| 62 | + | func (q *Queries) GetCertificateValidationByHostname(ctx context.Context, hostname string) (CertificateValidation, error) { | |
| 63 | + | row := q.db.QueryRowContext(ctx, GetCertificateValidationByHostname, hostname) | |
| 64 | + | var i CertificateValidation | |
| 65 | + | err := row.Scan( | |
| 66 | + | &i.ID, | |
| 67 | + | &i.Hostname, | |
| 68 | + | &i.CertificateHash, | |
| 69 | + | &i.UserDecision, | |
| 70 | + | &i.CreatedAt, | |
| 71 | + | &i.ExpiresAt, | |
| 72 | + | ) | |
| 73 | + | return i, err | |
| 74 | + | } | |
| 75 | + | ||
| 54 | 76 | const StoreCertificateValidation = `-- name: StoreCertificateValidation :exec | |
| 55 | 77 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | |
| 56 | 78 | VALUES (?, ?, ?, ?) | |
| @@ -31,8 +31,8 @@ func (q *Queries) DeleteExpiredCertificateValidations(ctx context.Context) error | |||||
| 31 | } | 31 | } | ||
| 32 | 32 | ||||
| 33 | const GetCertificateValidation = `-- name: GetCertificateValidation :one | 33 | const GetCertificateValidation = `-- name: GetCertificateValidation :one | ||
| 34 | - | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations | 34 | + | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations |
| 35 | - | WHERE hostname = ? AND certificate_hash = ? | 35 | + | WHERE hostname = ? AND certificate_hash = ? |
| 36 | AND (expires_at IS NULL OR expires_at > datetime('now')) | 36 | AND (expires_at IS NULL OR expires_at > datetime('now')) | ||
| 37 | LIMIT 1 | 37 | LIMIT 1 | ||
| 38 | ` | 38 | ` | ||
| @@ -51,6 +51,28 @@ func (q *Queries) GetCertificateValidation(ctx context.Context, hostname string, | |||||
| 51 | return i, err | 51 | return i, err | ||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | + | const GetCertificateValidationByHostname = `-- name: GetCertificateValidationByHostname :one | |||
| 55 | + | SELECT id, hostname, certificate_hash, user_decision, created_at, expires_at FROM certificate_validations | |||
| 56 | + | WHERE hostname = ? | |||
| 57 | + | AND (expires_at IS NULL OR expires_at > datetime('now')) | |||
| 58 | + | ORDER BY created_at DESC | |||
| 59 | + | LIMIT 1 | |||
| 60 | + | ` | |||
| 61 | + | ||||
| 62 | + | func (q *Queries) GetCertificateValidationByHostname(ctx context.Context, hostname string) (CertificateValidation, error) { | |||
| 63 | + | row := q.db.QueryRowContext(ctx, GetCertificateValidationByHostname, hostname) | |||
| 64 | + | var i CertificateValidation | |||
| 65 | + | err := row.Scan( | |||
| 66 | + | &i.ID, | |||
| 67 | + | &i.Hostname, | |||
| 68 | + | &i.CertificateHash, | |||
| 69 | + | &i.UserDecision, | |||
| 70 | + | &i.CreatedAt, | |||
| 71 | + | &i.ExpiresAt, | |||
| 72 | + | ) | |||
| 73 | + | return i, err | |||
| 74 | + | } | |||
| 75 | + | ||||
| 54 | const StoreCertificateValidation = `-- name: StoreCertificateValidation :exec | 76 | const StoreCertificateValidation = `-- name: StoreCertificateValidation :exec | ||
| 55 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | 77 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | ||
| 56 | VALUES (?, ?, ?, ?) | 78 | VALUES (?, ?, ?, ?) | ||
M internal/db/querier.go +1 -0
| @@ -19,6 +19,7 @@ type Querier interface { | |||
| 19 | 19 | // Delete zoom level setting for a domain | |
| 20 | 20 | DeleteZoomLevel(ctx context.Context, domain string) error | |
| 21 | 21 | GetCertificateValidation(ctx context.Context, hostname string, certificateHash string) (CertificateValidation, error) | |
| 22 | + | GetCertificateValidationByHostname(ctx context.Context, hostname string) (CertificateValidation, error) | |
| 22 | 23 | GetHistory(ctx context.Context, limit int64) ([]History, error) | |
| 23 | 24 | GetHistoryEntry(ctx context.Context, url string) (History, error) | |
| 24 | 25 | GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error) | |
| @@ -19,6 +19,7 @@ type Querier interface { | |||||
| 19 | // Delete zoom level setting for a domain | 19 | // Delete zoom level setting for a domain | ||
| 20 | DeleteZoomLevel(ctx context.Context, domain string) error | 20 | DeleteZoomLevel(ctx context.Context, domain string) error | ||
| 21 | GetCertificateValidation(ctx context.Context, hostname string, certificateHash string) (CertificateValidation, error) | 21 | GetCertificateValidation(ctx context.Context, hostname string, certificateHash string) (CertificateValidation, error) | ||
| 22 | + | GetCertificateValidationByHostname(ctx context.Context, hostname string) (CertificateValidation, error) | |||
| 22 | GetHistory(ctx context.Context, limit int64) ([]History, error) | 23 | GetHistory(ctx context.Context, limit int64) ([]History, error) | ||
| 23 | GetHistoryEntry(ctx context.Context, url string) (History, error) | 24 | GetHistoryEntry(ctx context.Context, url string) (History, error) | ||
| 24 | GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error) | 25 | GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error) | ||
M internal/db/queries/certificate_validations.sql +9 -2
| @@ -1,9 +1,16 @@ | |||
| 1 | 1 | -- name: GetCertificateValidation :one | |
| 2 | - | SELECT * FROM certificate_validations | |
| 3 | - | WHERE hostname = ? AND certificate_hash = ? | |
| 2 | + | SELECT * FROM certificate_validations | |
| 3 | + | WHERE hostname = ? AND certificate_hash = ? | |
| 4 | 4 | AND (expires_at IS NULL OR expires_at > datetime('now')) | |
| 5 | 5 | LIMIT 1; | |
| 6 | 6 | ||
| 7 | + | -- name: GetCertificateValidationByHostname :one | |
| 8 | + | SELECT * FROM certificate_validations | |
| 9 | + | WHERE hostname = ? | |
| 10 | + | AND (expires_at IS NULL OR expires_at > datetime('now')) | |
| 11 | + | ORDER BY created_at DESC | |
| 12 | + | LIMIT 1; | |
| 13 | + | ||
| 7 | 14 | -- name: StoreCertificateValidation :exec | |
| 8 | 15 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | |
| 9 | 16 | VALUES (?, ?, ?, ?); | |
| @@ -1,9 +1,16 @@ | |||||
| 1 | -- name: GetCertificateValidation :one | 1 | -- name: GetCertificateValidation :one | ||
| 2 | - | SELECT * FROM certificate_validations | 2 | + | SELECT * FROM certificate_validations |
| 3 | - | WHERE hostname = ? AND certificate_hash = ? | 3 | + | WHERE hostname = ? AND certificate_hash = ? |
| 4 | AND (expires_at IS NULL OR expires_at > datetime('now')) | 4 | AND (expires_at IS NULL OR expires_at > datetime('now')) | ||
| 5 | LIMIT 1; | 5 | LIMIT 1; | ||
| 6 | 6 | ||||
| 7 | + | -- name: GetCertificateValidationByHostname :one | |||
| 8 | + | SELECT * FROM certificate_validations | |||
| 9 | + | WHERE hostname = ? | |||
| 10 | + | AND (expires_at IS NULL OR expires_at > datetime('now')) | |||
| 11 | + | ORDER BY created_at DESC | |||
| 12 | + | LIMIT 1; | |||
| 13 | + | ||||
| 7 | -- name: StoreCertificateValidation :exec | 14 | -- name: StoreCertificateValidation :exec | ||
| 8 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | 15 | INSERT OR REPLACE INTO certificate_validations (hostname, certificate_hash, user_decision, expires_at) | ||
| 9 | VALUES (?, ?, ?, ?); | 16 | VALUES (?, ?, ?, ?); | ||
M pkg/webkit/tls_handler.go +17 -17
| @@ -38,12 +38,10 @@ func (wv *WebView) setupTLSErrorHandler() { | |||
| 38 | 38 | return false // Let WebKit handle it | |
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | - | // Get certificate info string for hashing | |
| 42 | - | certInfo := formatCertificateInfo(certificate) | |
| 43 | - | certHash := getCertificateHash(certInfo) | |
| 44 | - | ||
| 45 | - | // Check if user has previously made a decision about this certificate | |
| 46 | - | decision, exists := checkStoredCertificateDecision(hostname, certHash) | |
| 41 | + | // Check if user has previously made a decision for this hostname | |
| 42 | + | // Note: We use hostname-only matching because certificate properties | |
| 43 | + | // from GIO are unstable (GDateTime objects change on each access) | |
| 44 | + | decision, exists := checkStoredCertificateDecision(hostname) | |
| 47 | 45 | if exists { | |
| 48 | 46 | if decision == "accepted" { | |
| 49 | 47 | log.Printf("[tls] Certificate previously accepted for %s, allowing", hostname) | |
| @@ -57,7 +55,7 @@ func (wv *WebView) setupTLSErrorHandler() { | |||
| 57 | 55 | ||
| 58 | 56 | // No stored decision - show dialog to user | |
| 59 | 57 | log.Printf("[tls] No stored decision for %s, showing dialog", hostname) | |
| 60 | - | wv.showTLSErrorDialog(failingUri, hostname, certificate, certHash, errors) | |
| 58 | + | wv.showTLSErrorDialog(failingUri, hostname, certificate, errors) | |
| 61 | 59 | ||
| 62 | 60 | return true // Signal handled - we're managing the error | |
| 63 | 61 | }) | |
| @@ -116,8 +114,8 @@ func getCertificateHash(certificateInfo string) string { | |||
| 116 | 114 | return fmt.Sprintf("%x", hash) | |
| 117 | 115 | } | |
| 118 | 116 | ||
| 119 | - | // checkStoredCertificateDecision checks if user has previously decided on this certificate | |
| 120 | - | func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | |
| 117 | + | // checkStoredCertificateDecision checks if user has previously decided on this hostname | |
| 118 | + | func checkStoredCertificateDecision(hostname string) (string, bool) { | |
| 121 | 119 | cfg := config.Get() | |
| 122 | 120 | if cfg == nil { | |
| 123 | 121 | log.Printf("[tls] Config not available, cannot check stored decisions") | |
| @@ -140,7 +138,8 @@ func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | |||
| 140 | 138 | queries := db.New(database) | |
| 141 | 139 | ctx := context.Background() | |
| 142 | 140 | ||
| 143 | - | validation, err := queries.GetCertificateValidation(ctx, hostname, certHash) | |
| 141 | + | // Get any validation for this hostname (we don't verify cert hash anymore) | |
| 142 | + | validation, err := queries.GetCertificateValidationByHostname(ctx, hostname) | |
| 144 | 143 | if err != nil { | |
| 145 | 144 | if err == sql.ErrNoRows { | |
| 146 | 145 | return "", false | |
| @@ -152,8 +151,8 @@ func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | |||
| 152 | 151 | return validation.UserDecision, true | |
| 153 | 152 | } | |
| 154 | 153 | ||
| 155 | - | // storeCertificateDecision stores the user's decision about a certificate | |
| 156 | - | func storeCertificateDecision(hostname, certHash, decision string, expiresAt sql.NullTime) error { | |
| 154 | + | // storeCertificateDecision stores the user's decision about a hostname | |
| 155 | + | func storeCertificateDecision(hostname, decision string, expiresAt sql.NullTime) error { | |
| 157 | 156 | cfg := config.Get() | |
| 158 | 157 | if cfg == nil { | |
| 159 | 158 | return fmt.Errorf("config not available") | |
| @@ -173,10 +172,11 @@ func storeCertificateDecision(hostname, certHash, decision string, expiresAt sql | |||
| 173 | 172 | queries := db.New(database) | |
| 174 | 173 | ctx := context.Background() | |
| 175 | 174 | ||
| 176 | - | log.Printf("[tls] Saving certificate decision to database: hostname=%s, certHash=%s, decision=%s, expiresAt=%v", | |
| 177 | - | hostname, certHash[:16]+"...", decision, expiresAt) | |
| 175 | + | log.Printf("[tls] Saving certificate decision to database: hostname=%s, decision=%s, expiresAt=%v", | |
| 176 | + | hostname, decision, expiresAt) | |
| 178 | 177 | ||
| 179 | - | if err := queries.StoreCertificateValidation(ctx, hostname, certHash, decision, expiresAt); err != nil { | |
| 178 | + | // Use empty string for cert hash since we're doing hostname-only matching | |
| 179 | + | if err := queries.StoreCertificateValidation(ctx, hostname, "", decision, expiresAt); err != nil { | |
| 180 | 180 | log.Printf("[tls] Failed to save certificate decision to database: %v", err) | |
| 181 | 181 | return err | |
| 182 | 182 | } | |
| @@ -231,7 +231,7 @@ func getTLSErrorMessages(errors gio.TLSCertificateFlags) []string { | |||
| 231 | 231 | } | |
| 232 | 232 | ||
| 233 | 233 | // showTLSErrorDialog shows a dialog asking the user how to handle the TLS error | |
| 234 | - | func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate gio.TLSCertificater, certHash string, errors gio.TLSCertificateFlags) { | |
| 234 | + | func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate gio.TLSCertificater, errors gio.TLSCertificateFlags) { | |
| 235 | 235 | // This must run on the main thread | |
| 236 | 236 | wv.RunOnMainThread(func() { | |
| 237 | 237 | // Get error messages | |
| @@ -290,7 +290,7 @@ func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate g | |||
| 290 | 290 | Time: time.Now().Add(30 * 24 * time.Hour), | |
| 291 | 291 | Valid: true, | |
| 292 | 292 | } | |
| 293 | - | if err := storeCertificateDecision(hostname, certHash, "accepted", expiresAt); err != nil { | |
| 293 | + | if err := storeCertificateDecision(hostname, "accepted", expiresAt); err != nil { | |
| 294 | 294 | log.Printf("[tls] Failed to store certificate decision: %v", err) | |
| 295 | 295 | } else { | |
| 296 | 296 | log.Printf("[tls] Stored certificate acceptance for %s (expires in 30 days)", hostname) | |
| @@ -38,12 +38,10 @@ func (wv *WebView) setupTLSErrorHandler() { | |||||
| 38 | return false // Let WebKit handle it | 38 | return false // Let WebKit handle it | ||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | - | // Get certificate info string for hashing | 41 | + | // Check if user has previously made a decision for this hostname |
| 42 | - | certInfo := formatCertificateInfo(certificate) | 42 | + | // Note: We use hostname-only matching because certificate properties |
| 43 | - | certHash := getCertificateHash(certInfo) | 43 | + | // from GIO are unstable (GDateTime objects change on each access) |
| 44 | - | 44 | + | decision, exists := checkStoredCertificateDecision(hostname) | |
| 45 | - | // Check if user has previously made a decision about this certificate | |||
| 46 | - | decision, exists := checkStoredCertificateDecision(hostname, certHash) | |||
| 47 | if exists { | 45 | if exists { | ||
| 48 | if decision == "accepted" { | 46 | if decision == "accepted" { | ||
| 49 | log.Printf("[tls] Certificate previously accepted for %s, allowing", hostname) | 47 | log.Printf("[tls] Certificate previously accepted for %s, allowing", hostname) | ||
| @@ -57,7 +55,7 @@ func (wv *WebView) setupTLSErrorHandler() { | |||||
| 57 | 55 | ||||
| 58 | // No stored decision - show dialog to user | 56 | // No stored decision - show dialog to user | ||
| 59 | log.Printf("[tls] No stored decision for %s, showing dialog", hostname) | 57 | log.Printf("[tls] No stored decision for %s, showing dialog", hostname) | ||
| 60 | - | wv.showTLSErrorDialog(failingUri, hostname, certificate, certHash, errors) | 58 | + | wv.showTLSErrorDialog(failingUri, hostname, certificate, errors) |
| 61 | 59 | ||||
| 62 | return true // Signal handled - we're managing the error | 60 | return true // Signal handled - we're managing the error | ||
| 63 | }) | 61 | }) | ||
| @@ -116,8 +114,8 @@ func getCertificateHash(certificateInfo string) string { | |||||
| 116 | return fmt.Sprintf("%x", hash) | 114 | return fmt.Sprintf("%x", hash) | ||
| 117 | } | 115 | } | ||
| 118 | 116 | ||||
| 119 | - | // checkStoredCertificateDecision checks if user has previously decided on this certificate | 117 | + | // checkStoredCertificateDecision checks if user has previously decided on this hostname |
| 120 | - | func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | 118 | + | func checkStoredCertificateDecision(hostname string) (string, bool) { |
| 121 | cfg := config.Get() | 119 | cfg := config.Get() | ||
| 122 | if cfg == nil { | 120 | if cfg == nil { | ||
| 123 | log.Printf("[tls] Config not available, cannot check stored decisions") | 121 | log.Printf("[tls] Config not available, cannot check stored decisions") | ||
| @@ -140,7 +138,8 @@ func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | |||||
| 140 | queries := db.New(database) | 138 | queries := db.New(database) | ||
| 141 | ctx := context.Background() | 139 | ctx := context.Background() | ||
| 142 | 140 | ||||
| 143 | - | validation, err := queries.GetCertificateValidation(ctx, hostname, certHash) | 141 | + | // Get any validation for this hostname (we don't verify cert hash anymore) |
| 142 | + | validation, err := queries.GetCertificateValidationByHostname(ctx, hostname) | |||
| 144 | if err != nil { | 143 | if err != nil { | ||
| 145 | if err == sql.ErrNoRows { | 144 | if err == sql.ErrNoRows { | ||
| 146 | return "", false | 145 | return "", false | ||
| @@ -152,8 +151,8 @@ func checkStoredCertificateDecision(hostname, certHash string) (string, bool) { | |||||
| 152 | return validation.UserDecision, true | 151 | return validation.UserDecision, true | ||
| 153 | } | 152 | } | ||
| 154 | 153 | ||||
| 155 | - | // storeCertificateDecision stores the user's decision about a certificate | 154 | + | // storeCertificateDecision stores the user's decision about a hostname |
| 156 | - | func storeCertificateDecision(hostname, certHash, decision string, expiresAt sql.NullTime) error { | 155 | + | func storeCertificateDecision(hostname, decision string, expiresAt sql.NullTime) error { |
| 157 | cfg := config.Get() | 156 | cfg := config.Get() | ||
| 158 | if cfg == nil { | 157 | if cfg == nil { | ||
| 159 | return fmt.Errorf("config not available") | 158 | return fmt.Errorf("config not available") | ||
| @@ -173,10 +172,11 @@ func storeCertificateDecision(hostname, certHash, decision string, expiresAt sql | |||||
| 173 | queries := db.New(database) | 172 | queries := db.New(database) | ||
| 174 | ctx := context.Background() | 173 | ctx := context.Background() | ||
| 175 | 174 | ||||
| 176 | - | log.Printf("[tls] Saving certificate decision to database: hostname=%s, certHash=%s, decision=%s, expiresAt=%v", | 175 | + | log.Printf("[tls] Saving certificate decision to database: hostname=%s, decision=%s, expiresAt=%v", |
| 177 | - | hostname, certHash[:16]+"...", decision, expiresAt) | 176 | + | hostname, decision, expiresAt) |
| 178 | 177 | ||||
| 179 | - | if err := queries.StoreCertificateValidation(ctx, hostname, certHash, decision, expiresAt); err != nil { | 178 | + | // Use empty string for cert hash since we're doing hostname-only matching |
| 179 | + | if err := queries.StoreCertificateValidation(ctx, hostname, "", decision, expiresAt); err != nil { | |||
| 180 | log.Printf("[tls] Failed to save certificate decision to database: %v", err) | 180 | log.Printf("[tls] Failed to save certificate decision to database: %v", err) | ||
| 181 | return err | 181 | return err | ||
| 182 | } | 182 | } | ||
| @@ -231,7 +231,7 @@ func getTLSErrorMessages(errors gio.TLSCertificateFlags) []string { | |||||
| 231 | } | 231 | } | ||
| 232 | 232 | ||||
| 233 | // showTLSErrorDialog shows a dialog asking the user how to handle the TLS error | 233 | // showTLSErrorDialog shows a dialog asking the user how to handle the TLS error | ||
| 234 | - | func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate gio.TLSCertificater, certHash string, errors gio.TLSCertificateFlags) { | 234 | + | func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate gio.TLSCertificater, errors gio.TLSCertificateFlags) { |
| 235 | // This must run on the main thread | 235 | // This must run on the main thread | ||
| 236 | wv.RunOnMainThread(func() { | 236 | wv.RunOnMainThread(func() { | ||
| 237 | // Get error messages | 237 | // Get error messages | ||
| @@ -290,7 +290,7 @@ func (wv *WebView) showTLSErrorDialog(failingUri, hostname string, certificate g | |||||
| 290 | Time: time.Now().Add(30 * 24 * time.Hour), | 290 | Time: time.Now().Add(30 * 24 * time.Hour), | ||
| 291 | Valid: true, | 291 | Valid: true, | ||
| 292 | } | 292 | } | ||
| 293 | - | if err := storeCertificateDecision(hostname, certHash, "accepted", expiresAt); err != nil { | 293 | + | if err := storeCertificateDecision(hostname, "accepted", expiresAt); err != nil { |
| 294 | log.Printf("[tls] Failed to store certificate decision: %v", err) | 294 | log.Printf("[tls] Failed to store certificate decision: %v", err) | ||
| 295 | } else { | 295 | } else { | ||
| 296 | log.Printf("[tls] Stored certificate acceptance for %s (expires in 30 days)", hostname) | 296 | log.Printf("[tls] Stored certificate acceptance for %s (expires in 30 days)", hostname) | ||