Compare commits

...

80 Commits

Author SHA1 Message Date
87bba395d0 chore: Laviolette Bridge 3.22.0 changelog. 2026-01-22 16:47:19 +01:00
4ab5bcdf65 fix(BRIDGE-449): IMAP OK heartbeat; gluon bump 2026-01-21 15:54:03 +00:00
6194a1a125 chore: bump badssl pin 2026-01-21 12:43:51 +01:00
c6ec3cb53f fix(BRIDGE-447): bump GPA 2026-01-09 12:27:25 +01:00
16e1177ec2 chore(BRIDGE-455): bump go toolchain to 1.24.11; logrus package and cloudflare/circl bump; bump GPA and Gluon; remove ignored vulns 2026-01-08 13:00:18 +01:00
194942e895 chore: Happy New Year (2026) 2026-01-08 13:00:18 +01:00
fee4570a3a ci: DEVEX-54 migrating win runners. 2025-12-15 12:48:48 +01:00
63ea76d757 chore(BRIDGE-440): supress govulnechk findings x2 2025-12-09 12:29:09 +01:00
3db547d187 feat(BRIDGE-409): update gluon and go-proton-api to the latest version. 2025-11-27 12:04:21 +01:00
4378adab1a chore: bump badssl pin 2025-11-26 15:50:57 +01:00
726eb72026 chore: Bump VCPKG on Windows; resolve build issues 2025-11-10 12:35:12 +01:00
5a28fbf2df feat(BRIDGE-409): update gluon to fix max message size issue. 2025-11-05 11:09:19 +01:00
131a663578 chore(BRIDGE-440): supress govulnechk findings 2025-10-30 14:48:40 +01:00
6ab8558e17 chore: Attempt at stabilizing Bridge launch for e2e tests 2025-10-24 14:57:25 +02:00
ac3d7375f7 fix(BRIDGE-424): patch second password prompt 2025-10-01 11:42:48 +02:00
cd375c81fa chore(BRIDGE-429): migrate GitHub forks to ProtonMail organization 2025-09-30 12:06:44 +02:00
7fce966a65 fix(BRIDGE-424): resolved wrong layout ordering; u2f pin option is now treated as a 'password' 2025-09-29 11:58:09 +02:00
edf903fd21 feat(BRIDGE-424): FIDO2 GUI support. 2025-09-25 17:09:00 +02:00
e091e58be1 feat(BRIDGE-424): implement FIDO2 support 2025-09-24 14:42:15 +00:00
2fb5b751b6 chore(BRIDGE-428): suppress vulnerability finding - GO-2025-3956 2025-09-24 16:09:05 +02:00
e442b67ac0 chore: bump badssl pin 2025-09-24 15:57:35 +02:00
8baa7e3b6e chore: Got rid of something that is not needed, but was failing the pipeline. 2025-08-15 13:31:29 +00:00
5bb71575c3 chore: Changes made so that the e2e tests can be run on a scheduled nightly pipeline. 2025-08-15 10:24:03 +02:00
fd709b0d08 test(BRIDGE-136): Download Bridge 2025-08-15 07:25:10 +00:00
cf9b35163a chore: let's see 2025-08-13 12:44:21 +00:00
f3b3fd960b chore: BRIDGE-416 silence database/sql vuln 2025-08-12 16:30:50 +02:00
02684e27fb chore: Kanmon Bridge 3.21.2 changelog. 2025-07-21 16:46:06 +02:00
f6d4fc0c29 chore: Kanmon Bridge 3.21.1 changelog. 2025-07-21 16:46:00 +02:00
054a51ba15 chore: Kanmon Bridge 3.21.0 changelog. 2025-07-21 16:45:53 +02:00
7b2ea635ee chore(BRIDGE-409): Bump GPA 2025-07-18 15:39:36 +02:00
7faf32d0ff feat(BRIDGE-396): Observability metrics for vault issues; Extension to observability service to support caching 2025-07-17 14:06:23 +02:00
de3fd34998 feat(BRIDGE-356): Added retry logic for unavailable preferred keychain on Linux; Feature flag support before bridge initialization; Refactored some bits of the code; 2025-07-10 13:23:26 +00:00
20183bf984 fix(BRIDGE-374): Tweaked MBOX header sanitization logic, ensures that RFC822 headers are present. 2025-07-10 12:08:47 +02:00
47316a1843 ci: DEVOPS-3481 adding back cache (reduces build time by 1h) 2025-07-08 10:31:55 +02:00
366f7eb4e0 feat(BRIDGE-358): hover tooltip on copy to clipboard UI button 2025-07-07 08:49:02 +00:00
5ea45790fe fix(BRIDGE-406): fixed faulty certificate chain validation logic; made certificate pin checks exclusive to leaf certs; 2025-07-04 15:19:05 +02:00
7e8d16e883 feat(BRIDGE-151): Additional sentry report for auto-update error case 2025-07-02 09:35:21 +00:00
e9b1befae4 chore: refactor; unify vault mutex usage with wrappers; 2025-07-02 08:59:44 +00:00
26cc6169fa feat(BRIDGE-361): log the utilized keychain helper 2025-07-01 10:56:50 +02:00
be9e03d917 feat(BRIDGE-278): Rollout Feature Flag stickiness support; a new UUID 'sticky' value has been added to the vault" 2025-06-27 16:21:40 +02:00
2669bb4df9 fix(BRIDGE-395): skip saving the last used keychain helper as user preference on Windows & macOS 2025-06-27 13:10:41 +00:00
bffad05933 fix(BRIDGE-394): bump Gluon 2025-06-27 12:29:47 +02:00
7269e5ece0 fix(BRIDGE-355): bump GPA 2025-06-27 12:27:50 +02:00
6ee9e6e38b chore(BRIDGE-369): bump GPA & gopenpg to 2.9.0 2025-06-24 12:41:02 +02:00
2f2a8a200b chore(BRIDGE-392): bump go to 1.24.4 2025-06-24 10:29:20 +02:00
5832d48a5b chore: remove windows env vcpkg cache 2025-06-23 15:07:42 +00:00
6b1d203225 feat(BRIDGE-391): Simplified internal label conflict resolver 2025-06-17 13:09:39 +02:00
b699a53b12 chore(BRIDGE-388): disable particular vuln reporting 2025-06-12 12:17:09 +02:00
4ee149ecd7 fix(BRIDGE-387): use gluon ID for getting the message count instead of the address ID 2025-06-12 11:47:33 +02:00
7b533d5951 feat(BRIDGE-383): Internal mailbox conflict resolution extended; Minor alterations to mailbox conflict pre-checker 2025-06-11 14:09:53 +02:00
39f2362996 feat(BRIDGE-379): mailbox pre-checker on startup & conflict resolver for bridge internal mailboxes; TODO potentially add this for system mailboxes as well 2025-06-05 14:34:29 +02:00
d2742c81e5 feat(BRIDGE-376): catch gluon errors related to label uniqueness constrainst... 2025-06-05 14:34:29 +02:00
9cb914cf13 fix(BRIDGE-377): Correct label field usage on label update handler 2025-06-05 14:34:29 +02:00
4088cf18c3 feat(BRIDGE-373): extend label conflict resolver logging & report sync errors to sentry 2025-06-05 14:34:29 +02:00
c02bae5eb2 fix(BRIDGE-378): Fix incorrect field usage for system mailbox names 2025-06-03 17:36:58 +02:00
2aa8acfb5b chore: changes to reconcile release/jubilee with dev 2025-05-28 16:56:28 +02:00
8109b384c5 fix(BRIDGE-362): added label conflict reconciliation logic 2025-05-28 16:56:07 +02:00
6d79ad3e41 chore: Jubilee Bridge 3.20.1 changelog. 2025-05-28 16:53:23 +02:00
5d93ee0cfc chore: Jubilee Bridge 3.20.0 changelog. 2025-05-28 16:53:23 +02:00
c3e2201945 feat(BRIDGE-366): Kill switch support for IMAP IDLE 2025-05-28 09:53:45 +02:00
89da7335b6 feat(BRIDGE-363): Observability metrics for IMAP connections; minor unleash service refactor; 2025-05-16 15:28:53 +02:00
a305ee1113 chore: Infinity Bridge 3.19.0 changelog. 2025-04-24 11:13:17 +02:00
e38f7748d0 chore: bump GPA 2025-04-22 12:41:13 +00:00
92b2024e3e chore(BRIDGE-352): bump go to 1.24.2 2025-04-17 14:06:54 +00:00
37a8fc95d2 chore(BRIDGE-353): update x/net package to 0.38.0 2025-04-17 10:48:31 +02:00
0c63533aa7 fix(BRIDGE-351): allow draft creation and import to BYOE addresses in combined mode 2025-04-15 17:26:14 +02:00
af98bc2273 fix(BRIDGE-301): don't use external, non-BYOE addresses for imports 2025-04-10 09:51:31 +00:00
b37f2d138a feat(BRIDGE-348): display BYOE addresses in Bridge 2025-04-10 10:06:40 +02:00
7831a98e6c chore(BRIDGE-346): silence http/net vulnerability 2025-04-09 10:49:34 +02:00
4d415675e0 fix(BRIDGE-341): replace go-autostart with fork; added ability to create shortcuts with unicode chars 2025-04-02 11:56:58 +00:00
291f44d1b5 fix(BRIDGE-332): filter new line characters from username and password fields in GUI 2025-04-01 14:37:18 +02:00
a4b315d67a fix(BRIDGE-336): check and create all labels in Gluon on Bridge start 2025-03-25 15:24:59 +01:00
a15d4eb3ef ci: update CODEOWNERS 2025-03-24 15:38:03 +01:00
4e764fe93d feat(BRIDGE-340): additional logging for label operations & bad events 2025-03-24 14:30:19 +01:00
df409925ec fix(BRIDGE-335): store last sucessfully used keychain helper as user preference 2025-03-19 15:10:09 +01:00
e68f3441d7 fix(BRIDGE-196): bump badssl public key 2025-03-19 10:00:23 +00:00
899d3293bc feat(BRIDGE-324): added a log entry for the vault key hash 2025-03-18 11:21:12 +00:00
c66f0b800a fix(BRIDGE-333): ignore unkown label IDs during synchronization 2025-03-17 10:43:26 +01:00
b9c75d02b2 chore: stabilize windows tests 2025-03-14 11:56:42 +01:00
4b91e66505 chore(BRIDGE-315): remove silenced vulns 2025-03-06 14:49:03 +00:00
699 changed files with 9719 additions and 4477 deletions

View File

@ -1 +1 @@
* @go/bridge-ppl/devs
* inbox-desktop-approvers

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "submodules/vcpkg"]
path = extern/vcpkg
url = https://github.com/Microsoft/vcpkg.git
[submodule "extern/vcpkg-windows"]
path = extern/vcpkg-windows
url = https://github.com/microsoft/vcpkg.git

View File

@ -42,7 +42,10 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
* [cbor](https://github.com/fxamacker/cbor/v2) available under [license](https://github.com/fxamacker/cbor/v2/blob/master/LICENSE)
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
* [ctaphid](https://github.com/go-ctap/ctaphid) available under [license](https://github.com/go-ctap/ctaphid/blob/master/LICENSE)
* [winhello](https://github.com/go-ctap/winhello) available under [license](https://github.com/go-ctap/winhello/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
* [mock](https://github.com/golang/mock) available under [license](https://github.com/golang/mock/blob/master/LICENSE)
@ -52,6 +55,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
* [go-locale](https://github.com/jeandeaual/go-locale) available under [license](https://github.com/jeandeaual/go-locale/blob/master/LICENSE)
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [go-libfido2](https://github.com/keys-pub/go-libfido2) available under [license](https://github.com/keys-pub/go-libfido2/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
@ -70,7 +74,6 @@ Proton Mail Bridge includes the following 3rd party software:
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
@ -111,6 +114,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [multierror](https://github.com/joeshaw/multierror) available under [license](https://github.com/joeshaw/multierror/blob/master/LICENSE)
* [go](https://github.com/json-iterator/go) available under [license](https://github.com/json-iterator/go/blob/master/LICENSE)
* [cpuid](https://github.com/klauspost/cpuid/v2) available under [license](https://github.com/klauspost/cpuid/v2/blob/master/LICENSE)
* [cose](https://github.com/ldclabs/cose) available under [license](https://github.com/ldclabs/cose/blob/master/LICENSE)
* [go-urn](https://github.com/leodido/go-urn) available under [license](https://github.com/leodido/go-urn/blob/master/LICENSE)
* [go-colorable](https://github.com/mattn/go-colorable) available under [license](https://github.com/mattn/go-colorable/blob/master/LICENSE)
* [go-isatty](https://github.com/mattn/go-isatty) available under [license](https://github.com/mattn/go-isatty/blob/master/LICENSE)
@ -127,9 +131,11 @@ Proton Mail Bridge includes the following 3rd party software:
* [blackfriday](https://github.com/russross/blackfriday/v2) available under [license](https://github.com/russross/blackfriday/v2/blob/master/LICENSE)
* [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE)
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
* [objx](https://github.com/stretchr/objx) available under [license](https://github.com/stretchr/objx/blob/master/LICENSE)
* [golang-asm](https://github.com/twitchyliquid64/golang-asm) available under [license](https://github.com/twitchyliquid64/golang-asm/blob/master/LICENSE)
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
* [float16](https://github.com/x448/float16) available under [license](https://github.com/x448/float16/blob/master/LICENSE)
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
@ -141,8 +147,11 @@ Proton Mail Bridge includes the following 3rd party software:
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
* [winhello](https://github.com/ProtonMail/winhello) available under [license](https://github.com/ProtonMail/winhello/blob/master/LICENSE)
* [resty](https://github.com/ProtonMail/resty/v2) available under [license](https://github.com/ProtonMail/resty/v2/blob/master/LICENSE)
* [go-keychain](https://github.com/ProtonMail/go-keychain) available under [license](https://github.com/ProtonMail/go-keychain/blob/master/LICENSE)
* [go-libfido2](https://github.com/ProtonMail/go-libfido2) available under [license](https://github.com/ProtonMail/go-libfido2/blob/master/LICENSE)
<!-- END AUTOGEN -->

View File

@ -3,6 +3,94 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Laviolette Bridge 3.22.0
### Added
* BRIDGE-358: Hover tooltip for IMAP/SMTP settings clipboard UI actions.
* BRIDGE-356: Added support for unavailable keychain retries on Linux, such that we don't wipe the vault. Feature flag support before Bridge initialization.
* BRIDGE-278: Rollout feature flag support.
* BRIDGE-151: Additional sentry reporting related to auto-update failures.
* BRIDGE-361: Debug information on the utilized keychain helper.
* BRIDGE-396: Observability caching and metrics for vault related issues.
* BRIDGE-449: Generic IMAP OK heartbeat for long lasting commands.
### Changed
* BRIDGE-374: Modified MBOX header sanitization logic, it now ensures that RFC822 headers are present before stripping content.
* BRIDGE-391: Simplified internal label conflict resolver.
* BRIDGE-409: Increased the import size limit to 55MB.
* BRIDGE-369: Bumped gopenpgp to v2.9.0.
* BRIDGE-455: Bumped Go to 1.24.11.
* BRIDGE-424: FIDO2 support.
### Fixed
* BRIDGE-395: Don't store the last utilized keychain as the user preference on Windows & macOS.
* BRIDGE-387: Use the address gluon ID, instead of the address ID to fetch message counts.
* BRIDGE-394: Prevent the RFC822 parser from mutating the message literal.
* BRIDGE-355: Prevent Bridge from crashing when an unknown message charset is detected on Import.
* BRIDGE-447: Adjusted error message for message import size limitation.
## Kanmon Bridge 3.21.2
### Fixed
* BRIDGE-406: Fixed faulty certificate chain validation logic. Made certificate pin checks exclusive to leaf certificates.
## Kanmon Bridge 3.21.1
### Changed
* BRIDGE-383: Extended internal mailbox conflict resolution logic and minor changes to the mailbox conflict pre-checker.
## Kanmon Bridge 3.21.0
### Added
* BRIDGE-379: Mailbox pre-check on Bridge startup & conflict resolver for Bridge internal mailboxes.
### Changed
* BRIDGE-376: Explicitly catch Gluon DB mailbox name conflicts and report them to Sentry.
* BRIDGE-373: Extend user mailbox conflict resolver logging & report sync errors to Sentry.
* BRIDGE-366: Kill switch support for IMAP IDLE.
* BRIDGE-363: Observability metric support for IMAP connections.
### Fixed
* BRIDGE-377: Correct API label field usage on user label conflict resolver - update handler (event loop).
* BRIDGE-378: Fix incorrect field usage for system mailbox names.
## Jubilee Bridge 3.20.1
### Fixed
* BRIDGE-362: Implemented logic for reconciling label conflicts.
## Jubilee Bridge 3.20.0
### Added
* BRIDGE-348: Enable display of BYOE addresses in Bridge.
* BRIDGE-340: Added additional logging for label operations and related bad events.
* BRIDGE-324: Log a hash of the vault key on Bridge start.
### Changed
* BRIDGE-352: Chore: bump go to 1.24.2.
* BRIDGE-353: Chore: update x/net package to 0.38.0.
### Fixed
* BRIDGE-351: Allow draft creation and import to BYOE addresses in combined mode.
* BRIDGE-301: Prevent imports into non-BYOE external addresses.
* BRIDGE-341: Replaced go-autostart with a fork to support creating autostart shortcuts in directories with Unicode characters on Windows.
* BRIDGE-332: Strip newline characters from username and password fields in the Bridge GUI.
* BRIDGE-336: Ensure all remote labels are verified and created in Gluon at Bridge startup.
* BRIDGE-335: Persist the last successfully used keychain helper as a user preference on Linux.
* BRIDGE-333: Ignore unknown label IDs during Bridge synchronization.
## Infinity Bridge 3.19.0
### Changed
* BRIDGE-316: Update Qt to latest LTS version 6.8.2.
## Helix Bridge 3.18.0
### Changed

View File

@ -9,10 +9,10 @@ TARGET_OS?=${GOOS}
ROOT_DIR:=$(realpath .)
## Build
.PHONY: build build-gui build-nogui build-launcher versioner hasher
.PHONY: build build-gui build-nogui build-launcher versioner hasher install-libfido2
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.18.0+git
BRIDGE_APP_VERSION?=3.22.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG
@ -32,6 +32,24 @@ BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/v3/internal/constants., Version=${APP_VERSION} Revision=${REVISION} Tag=${TAG} BuildTime=${BUILD_TIME})
GO_LDFLAGS+=-X "github.com/ProtonMail/proton-bridge/v3/internal/constants.FullAppName=${APP_FULL_NAME}"
## Libfido2 set-up.
# We use vcpkg for libfido2 on *nix systems.
VCPKG_ROOT_NIX := $(ROOT_DIR)/extern/vcpkg
ifeq "${TARGET_OS}" "darwin"
VCPKG_INSTALLED_ARM := $(VCPKG_ROOT_NIX)/installed/arm64-osx
VCPKG_INSTALLED_X64 := $(VCPKG_ROOT_NIX)/installed/x64-osx
LIBFIDO2_CFLAGS_ARM64 := -I$(VCPKG_INSTALLED_ARM)/include
LIBFIDO2_LDFLAGS_ARM64 := -L$(VCPKG_INSTALLED_ARM)/lib -lfido2 -lcbor -lssl -lcrypto
LIBFIDO2_CFLAGS_X64 := -I$(VCPKG_INSTALLED_X64)/include
LIBFIDO2_LDFLAGS_X64 := -L$(VCPKG_INSTALLED_X64)/lib -lfido2 -lcbor -lssl -lcrypto
endif
ifeq "${TARGET_OS}" "linux"
LIBFIDO2_LDFLAGS := -lfido2 -lcbor -lssl -lcrypto
endif
ifneq "${DSN_SENTRY}" ""
GO_LDFLAGS+=-X github.com/ProtonMail/proton-bridge/v3/internal/constants.DSNSentry=${DSN_SENTRY}
endif
@ -95,8 +113,14 @@ go-build=go build $(1) -o $(2) $(3)
go-build-finalize=${go-build}
ifeq "${GOOS}-$(shell uname -m)" "darwin-arm64"
go-build-finalize= \
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_ARM64} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_ARM64}" GOARCH=arm64 $(call go-build,$(1),$(2)_arm,$(3)) && \
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_AMD64} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_AMD64}" GOARCH=amd64 $(call go-build,$(1),$(2)_amd,$(3)) && \
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_ARM64} CGO_ENABLED=1 \
CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_ARM64} ${LIBFIDO2_CFLAGS_ARM64}" \
CGO_LDFLAGS="${LIBFIDO2_LDFLAGS_ARM64}" \
GOARCH=arm64 $(call go-build,$(1),$(2)_arm,$(3)) && \
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_AMD64} CGO_ENABLED=1 \
CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_AMD64} ${LIBFIDO2_CFLAGS_X64}" \
CGO_LDFLAGS="${LIBFIDO2_LDFLAGS_X64}" \
GOARCH=amd64 $(call go-build,$(1),$(2)_amd,$(3)) && \
lipo -create -output $(2) $(2)_arm $(2)_amd && rm -f $(2)_arm $(2)_amd
endif
@ -107,6 +131,14 @@ ifeq "${GOOS}" "windows"
$(if $(4), && rm -f ${4},)
endif
ifneq "${GOOS}" "darwin"
ifneq "${GOOS}" "windows"
go-build-finalize= \
CGO_LDFLAGS="${LIBFIDO2_LDFLAGS}" \
$(call go-build,$(1),$(2),$(3))
endif
endif
${EXE_NAME}: gofiles ${RESOURCE_FILE}
$(call go-build-finalize,${BUILD_FLAGS},"${LAUNCHER_EXE}","./cmd/${TARGET_CMD}/","${ROOT_DIR}/cmd/${TARGET_CMD}/${RESOURCE_FILE}")
mv ${LAUNCHER_EXE} ${BRIDGE_EXE}
@ -137,7 +169,7 @@ ${DEPLOY_DIR}/linux: ${EXE_TARGET} build-launcher
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
mv ${LAUNCHER_EXE} ${DEPLOY_DIR}/linux/
${DEPLOY_DIR}/darwin: ${EXE_TARGET} build-launcher
${DEPLOY_DIR}/darwin: install-libfido2 ${EXE_TARGET} build-launcher
mv ${EXE_GUI_TARGET} ${EXE_TARGET_DARWIN}
mv ${EXE_TARGET} ${DARWINAPP_CONTENTS}/MacOS/${BRIDGE_EXE_NAME}
perl -i -pe"s/>${BRIDGE_GUI_EXE_NAME}/>${LAUNCHER_EXE}/g" ${DARWINAPP_CONTENTS}/Info.plist
@ -411,6 +443,10 @@ clean-vcpkg:
rm -rf ./.git/submodule/vcpkg
rm -rf ./extern/vcpkg
git checkout -- extern/vcpkg
git submodule deinit -f ./extern/vcpkg-windows
rm -rf ./.git/submodule/vcpkg-windows
rm -rf ./extern/vcpkg-windows
git checkout -- extern/vcpkg-windows
clean: clean-vendor clean-gui clean-vcpkg
rm -rf vendor-cache
@ -423,6 +459,15 @@ clean: clean-vendor clean-gui clean-vcpkg
rm -f ${LAUNCHER_EXE} ${BRIDGE_EXE} ${BRIDGE_EXE_NAME}
install-libfido2:
ifeq "${TARGET_OS}" "darwin"
git submodule update --init --recursive ${VCPKG_ROOT_NIX} || \
{ echo "Failed to init vcpkg submodule"; exit 1; }
${VCPKG_ROOT_NIX}/bootstrap-vcpkg.sh -disableMetrics
cd extern/vcpkg && ./vcpkg install libfido2:arm64-osx libfido2:x64-osx
endif
.PHONY: generate
generate:
go generate ./...

View File

@ -1,5 +1,5 @@
# Proton Mail Bridge
Copyright (c) 2025 Proton AG
Copyright (c) 2026 Proton AG
This repository holds the Proton Mail Bridge application.
For a detailed build information see [BUILDS](./BUILDS.md).

View File

@ -1,5 +1,4 @@
---
.script-build:
stage: build
needs: ["lint"]
@ -22,6 +21,7 @@
- bridge_*.tgz
- vault-editor
- bridge-rollout
build-linux:
extends:
- .script-build
@ -33,6 +33,7 @@ build-linux-qa:
- .rules-branch-manual-MR-and-devel-always
variables:
BUILD_TAGS: "build_qa"
VCPKG_MAX_CONCURRENCY: 1
build-darwin:
extends:

View File

@ -1,6 +1,4 @@
---
.env-windows:
extends:
- .image-windows-virt-build
@ -15,10 +13,10 @@
BRIDGE_SYNC_FORCE_MINIMUM_SPEC: 1
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
cache:
key: windows-vcpkg-go-0
key: windows-vcpkg-go-1
paths:
- .cache
when: 'always'
when: "always"
.env-darwin:
extends:
@ -36,7 +34,7 @@
key: darwin-go-and-vcpkg
paths:
- .cache
when: 'always'
when: "always"
.env-linux-build:
extends:
@ -47,7 +45,7 @@
key: linux-vcpkg
paths:
- .cache
when: 'always'
when: "always"
before_script:
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
- !reference [.before-script-git-config, before_script]
@ -56,4 +54,3 @@
- export GOPATH="$CI_PROJECT_DIR/.cache"
tags:
- shared-large

View File

@ -1,7 +1,5 @@
---
include:
- project: 'go/bridge-internal'
ref: 'master'
file: 'ci/runners-setup.yml'
- project: "go/bridge-internal"
ref: "master"
file: "ci/runners-setup.yml"

View File

@ -1,6 +1,4 @@
---
lint:
stage: test
extends:
@ -33,8 +31,6 @@ lint-bug-report-preview:
paths:
- coverage/**
test-linux:
extends:
- .image-linux-test
@ -93,7 +89,6 @@ test-integration-race:
paths:
- integration-race-job.log
test-integration-nightly:
extends:
- test-integration
@ -131,12 +126,43 @@ test-coverage:
paths:
- coverage*
- coverage/**
when: 'always'
when: "always"
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-e2e-ui:
stage: test
extends:
- .rules-branch-manual-scheduled-and-test-branch-always
tags:
- inbox-hyperv-windows-v1
image: windows-2022-inbox-gui-1.0.0
variables:
REQUIRES_GRAPHICAL_CONSOLE: true
before_script:
- echo "Downloading dotnet dependencies"
- cd ./tests/e2e/ui_tests/windows_os/
- dotnet restore ./ProtonMailBridge.UI.Tests.csproj
- dotnet list package
script:
- |
pwsh "$Env:CI_PROJECT_DIR/tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1"
- |
$Env:no_grpc_proxy="127.0.0.1"
$no_grpc_proxy="127.0.0.1"
dotnet test ./ProtonMailBridge.UI.Tests.csproj -- NUnit.Where="cat != TemporarilyExcluded"
after_script:
- |
cp "C:\Users\gitlab-runner\AppData\Roaming\protonmail\bridge-v3\logs\*" "$Env:CI_PROJECT_DIR\tests\e2e\ui_tests\windows_os\Results\artifacts\Logs\"
- |
pwsh "$Env:CI_PROJECT_DIR\tests\e2e\ui_tests\windows_os\InstallerScripts\Remove-Bridge.ps1"
artifacts:
paths:
- tests/e2e/ui_tests/windows_os/Results/artifacts/*
when: always
go-vuln-check:
extends:
- .image-linux-test
@ -150,4 +176,3 @@ go-vuln-check:
when: always
paths:
- vulns*

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -31,6 +31,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
@ -164,7 +165,7 @@ func main() { //nolint:funlen
// On windows, if you use Run(), a terminal stays open; we don't want that.
if //goland:noinspection GoBoolExpressions
runtime.GOOS == "windows" {
runtime.GOOS == platform.WINDOWS {
err = cmd.Start()
} else {
err = cmd.Run()

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

2
extern/vcpkg vendored

1
extern/vcpkg-windows vendored Submodule

Submodule extern/vcpkg-windows added at 120deac306

61
go.mod
View File

@ -1,16 +1,16 @@
module github.com/ProtonMail/proton-bridge/v3
go 1.24
go 1.24.4
toolchain go1.24.0
toolchain go1.24.11
require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2
github.com/ProtonMail/gluon v0.17.1-0.20260112123503-2046c95ca745
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton
github.com/ProtonMail/go-proton-api v0.4.1-0.20260109112619-daf7af47921d
github.com/ProtonMail/gopenpgp/v2 v2.9.0-proton
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
@ -26,41 +26,44 @@ require (
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3
github.com/fatih/color v1.13.0
github.com/fxamacker/cbor/v2 v2.9.0
github.com/getsentry/sentry-go v0.15.0
github.com/go-ctap/ctaphid v0.8.1
github.com/go-ctap/winhello v0.1.0
github.com/go-resty/resty/v2 v2.7.0
github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.3.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173
github.com/keybase/go-keychain v0.0.0
github.com/keys-pub/go-libfido2 v1.5.4-0.20250104233141-2534349bd685
github.com/miekg/dns v1.1.50
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0
github.com/sirupsen/logrus v1.9.2
github.com/stretchr/testify v1.8.4
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.24.4
github.com/vmihailenco/msgpack/v5 v5.3.5
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0
golang.org/x/net v0.42.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
google.golang.org/api v0.114.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.33.0
google.golang.org/grpc v1.75.1
google.golang.org/protobuf v1.36.6
howett.net/plist v1.0.0
)
require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
github.com/ProtonMail/go-crypto v1.1.4-proton // indirect
github.com/ProtonMail/go-crypto v1.3.0-proton // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/go-srp v0.0.7 // indirect
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
@ -68,7 +71,7 @@ require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/chzyer/test v1.0.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cronokirby/saferith v0.33.0 // indirect
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
@ -87,7 +90,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/uuid v4.3.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
@ -98,6 +101,7 @@ require (
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/ldclabs/cose v1.3.2 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
@ -114,25 +118,30 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/tools v0.35.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace (
github.com/ProtonMail/go-autostart => github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
github.com/go-ctap/winhello => github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56
github.com/go-resty/resty/v2 => github.com/ProtonMail/resty/v2 v2.0.0-20250929142426-e3dc6308c80b
github.com/keybase/go-keychain => github.com/ProtonMail/go-keychain v0.0.0-20250929142014-ea8548dff768
github.com/keys-pub/go-libfido2 => github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1
)

152
go.sum
View File

@ -7,10 +7,8 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
@ -24,37 +22,49 @@ github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a h1:eQO/GF/+H8/9udc9QAgieFr+jr1tjXlJo35RAhsUbWY=
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2 h1:lDgMidI/9j2eedavcy7YICv8+F73ooVTUoUGBE4dO0s=
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/gluon v0.17.1-0.20251127091939-17b9426ae8f7 h1:PaqJBeXv30G45LFglNMUxChxzGPg+V870BplSGrt0RM=
github.com/ProtonMail/gluon v0.17.1-0.20251127091939-17b9426ae8f7/go.mod h1:OMwmLjgk6yJHX/P5KPck9WOcBVWIJLvuGZjj/8Ts/cw=
github.com/ProtonMail/gluon v0.17.1-0.20260108112233-b3e52866fa57 h1:aH0EeiBq/5c1rNI/1xzAmJWKgf+nFcqrKCUTUUV4/Sc=
github.com/ProtonMail/gluon v0.17.1-0.20260108112233-b3e52866fa57/go.mod h1:YbW3CyxVxdbXiEGBwOxTW9nczPa8tA58HMkxosSf8bw=
github.com/ProtonMail/gluon v0.17.1-0.20260112123503-2046c95ca745 h1:SHUpYnPoW78xQYAZCCcONiFMJDopk1a7vn7P42kYKMM=
github.com/ProtonMail/gluon v0.17.1-0.20260112123503-2046c95ca745/go.mod h1:YbW3CyxVxdbXiEGBwOxTW9nczPa8tA58HMkxosSf8bw=
github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033 h1:4r/ALoiixOOyjc1WhpwlkrcSFtRnc1GHWhk7ERELwbs=
github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v1.1.4-proton h1:KIo9uNlk3vzlwI7o5VjhiEjI4Ld1TDixOMnoNZyfpFE=
github.com/ProtonMail/go-crypto v1.1.4-proton/go.mod h1:zNoyBJW3p/yVWiHNZgfTF9VsjwqYof5YY0M9kt2QaX0=
github.com/ProtonMail/go-crypto v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYSQzhXQsrR7yUM=
github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-keychain v0.0.0-20250929142014-ea8548dff768 h1:fTHlbASGfwJ4x4EeQ/JX7CX0YBfbFEcYm2nA6puPaWs=
github.com/ProtonMail/go-keychain v0.0.0-20250929142014-ea8548dff768/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1 h1:MpPKmpti7MswJG5il3A+P24+iGxMj8V7/3JSMSRM1+c=
github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250121114701-67bd01ad0bc3 h1:YYnLBVcg7WrEbYVmF1PBr4AEQlob9rCphsMHAmF4CAo=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250121114701-67bd01ad0bc3/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c h1:dxnbB+ov77BDj1LC35fKZ14hLoTpU6OTpZySwxarVx0=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
github.com/ProtonMail/go-proton-api v0.4.1-0.20251127095056-9039cd6bf32a h1:g5A/1Jg7JR8MXucKDUJv48LnXq1mOSlI2yXo6/X4R/s=
github.com/ProtonMail/go-proton-api v0.4.1-0.20251127095056-9039cd6bf32a/go.mod h1:Xv7eeoGjaOLMZcjJj++yWNV99q5enByr0WcuF/ltTRA=
github.com/ProtonMail/go-proton-api v0.4.1-0.20260108112223-c9e6b92ad1fc h1:azlBBcGC5Y6FuEFRCY16pXh8vy268C9JBS6oU/AA33k=
github.com/ProtonMail/go-proton-api v0.4.1-0.20260108112223-c9e6b92ad1fc/go.mod h1:aVHyE5kG38rm99RQYuP3wWn8QuJpM5Me6KHaIDD92Qs=
github.com/ProtonMail/go-proton-api v0.4.1-0.20260109112619-daf7af47921d h1:q8y/G0qLRTxZr1xXk/kKFd2xwiyq44Yn2vI0xJJ6bhA=
github.com/ProtonMail/go-proton-api v0.4.1-0.20260109112619-daf7af47921d/go.mod h1:aVHyE5kG38rm99RQYuP3wWn8QuJpM5Me6KHaIDD92Qs=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton h1:MMVgE6nk5Ulh9Ud5L1Xc5iaPKE85FbfKQV17ZMucrR0=
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton/go.mod h1:+PjybET6fgcLzldFy1hpy7s8VibZ0T1hLFbxnnMk0lo=
github.com/ProtonMail/gopenpgp/v2 v2.9.0-proton h1:K3YRIBJo3YVObikaV9y1KWYGxFWRML+pFaiyh8ON2xA=
github.com/ProtonMail/gopenpgp/v2 v2.9.0-proton/go.mod h1:NJ4RywdeD2sXCJyRRwb0ZYCx+QwGi14HUmlyNPegiwI=
github.com/ProtonMail/resty/v2 v2.0.0-20250929142426-e3dc6308c80b h1:0GYNP0odNPJFn1fbfwthcYPd3it0AVntvSaCjh2nlaE=
github.com/ProtonMail/resty/v2 v2.0.0-20250929142426-e3dc6308c80b/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56 h1:OFDKuwogje2Nor+2X81P0wWGcSZPXu2HKNUE71o3qZI=
github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56/go.mod h1:kJnpbFRhpEatnRc05/CTeq4cWR2LUE7P6+KsPP/zRnE=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
@ -96,8 +106,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -116,8 +126,6 @@ github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6T
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77 h1:sdB/yJMbubPQothFl6KYCOrMBRgy0pZbBXIWoJqSFLo=
github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -163,6 +171,8 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWT
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/fyne-io/mobile v0.1.2-0.20201127155338-06aeb98410cc/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
@ -174,6 +184,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ctap/ctaphid v0.8.1 h1:HIDoSfqInkUIRBVPv61fVB2CNZ5nYxoaIgqmt8vzcs4=
github.com/go-ctap/ctaphid v0.8.1/go.mod h1:jRVrVfCs30jdZkSH2PoBopv9ry+tK99mpYumE4GIbb8=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
@ -182,6 +194,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -196,6 +212,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/goforj/godump v1.6.0 h1:3Dn8gaw5Xxxefr1ezTGTWrTKSr3ihK+eJ2xzRUoFfHQ=
github.com/goforj/godump v1.6.0/go.mod h1:/Vy+p50JtOkwsFN5dA1HQ7LS5gtPk3f61DaP4UR2o4s=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -223,9 +241,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -234,9 +251,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -245,8 +261,8 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@ -327,6 +343,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ldclabs/cose v1.3.2 h1:9M5l1zTvOyZONRsNj2PWJjmLdRqkcrsp80tyuNkOHdE=
github.com/ldclabs/cose v1.3.2/go.mod h1:X1srvv76GKudjv85VCUgka049gaK5aozbBhMDaCEbpc=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
@ -420,8 +438,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@ -443,8 +461,9 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -455,8 +474,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -469,6 +488,8 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
@ -482,6 +503,18 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
@ -500,8 +533,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -527,8 +560,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -557,13 +590,13 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -573,8 +606,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -613,8 +646,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -634,8 +667,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@ -665,12 +698,14 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -703,8 +738,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -714,10 +749,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -734,6 +767,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -39,7 +39,10 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
@ -285,56 +288,61 @@ func run(c *cli.Context) error {
logrus.WithError(err).Error("Failed to get settings path")
}
featureFlags := unleash.GetStartupFeatureFlagsAndStore(constants.APIHost, version, locations.ProvideUnleashStartupCachePath)
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
// Look for available keychains
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
// Unlock the encrypted vault.
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
if !v.Migrated() {
// Migrate old settings into the vault.
if err := migrateOldSettings(v); err != nil {
logrus.WithError(err).Error("Failed to migrate old settings")
}
// Migrate old accounts into the vault.
if err := migrateOldAccounts(locations, keychains, v); err != nil {
logrus.WithError(err).Error("Failed to migrate old accounts")
}
// The vault has been migrated.
if err := v.SetMigrated(); err != nil {
logrus.WithError(err).Error("Failed to mark vault as migrated")
}
}
logrus.WithFields(logrus.Fields{
"lastVersion": v.GetLastVersion().String(),
"showAllMail": v.GetShowAllMail(),
"updateCh": v.GetUpdateChannel(),
"autoUpdate": v.GetAutoUpdate(),
"rollout": v.GetUpdateRollout(),
"DoH": v.GetProxyAllowed(),
}).Info("Vault loaded")
// Load the cookies from the vault.
return withCookieJar(v, func(cookieJar http.CookieJar) error {
// Create a new bridge instance.
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, keychains, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
if insecure {
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
b.PushError(bridge.ErrVaultInsecure)
// Pre-init the observability service, load the cached metrics.
return observability.WithObservability(locations, func(obsService *observability.Service) error {
// Unlock the encrypted vault.
return WithVault(reporter, locations, keychains, obsService, featureFlags, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
if !v.Migrated() {
// Migrate old settings into the vault.
if err := migrateOldSettings(v); err != nil {
logrus.WithError(err).Error("Failed to migrate old settings")
}
if corrupt {
logrus.Warn("The vault is corrupt and has been wiped")
b.PushError(bridge.ErrVaultCorrupt)
// Migrate old accounts into the vault.
if err := migrateOldAccounts(locations, keychains, v); err != nil {
logrus.WithError(err).Error("Failed to migrate old accounts")
}
// Remove old updates files
b.RemoveOldUpdates()
// The vault has been migrated.
if err := v.SetMigrated(); err != nil {
logrus.WithError(err).Error("Failed to mark vault as migrated")
}
}
// Run the frontend.
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
logrus.WithFields(logrus.Fields{
"lastVersion": v.GetLastVersion().String(),
"showAllMail": v.GetShowAllMail(),
"updateCh": v.GetUpdateChannel(),
"autoUpdate": v.GetAutoUpdate(),
"rollout": v.GetUpdateRollout(),
"DoH": v.GetProxyAllowed(),
}).Info("Vault loaded")
// Load the cookies from the vault.
return withCookieJar(v, func(cookieJar http.CookieJar) error {
// Create a new bridge instance.
return withBridge(c, exe, locations, version, identifier, obsService, crashHandler, reporter, v, cookieJar, keychains, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
if insecure {
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
b.PushError(bridge.ErrVaultInsecure)
}
if corrupt {
logrus.Warn("The vault is corrupt and has been wiped")
b.PushError(bridge.ErrVaultCorrupt)
}
// Remove old updates files
b.RemoveOldUpdates()
// Run the frontend.
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
})
})
})
})
@ -577,5 +585,5 @@ func setDeviceCookies(jar *cookies.Jar) error {
}
func onMacOS() bool {
return runtime.GOOS == "darwin"
return runtime.GOOS == platform.MACOS
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -33,6 +33,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
@ -52,6 +53,7 @@ func withBridge(
locations *locations.Locations,
version *semver.Version,
identifier *useragent.UserAgent,
obsService *observability.Service,
crashHandler *crash.Handler,
reporter *sentry.Reporter,
vault *vault.Vault,
@ -100,6 +102,7 @@ func withBridge(
updater,
version,
keychains,
obsService,
// The API stuff.
constants.APIHost,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -138,7 +138,14 @@ func migrateOldAccounts(locations *locations.Locations, keychains *keychain.List
if err != nil {
return fmt.Errorf("failed to get helper: %w", err)
}
keychain, err := keychain.NewKeychain(helper, "bridge", keychains.GetHelpers(), keychains.GetDefaultHelper())
keychain, _, err := keychain.NewKeychain(
helper, "bridge",
keychains.GetHelpers(),
keychains.GetDefaultHelper(),
0,
make(map[string]bool),
)
if err != nil {
return fmt.Errorf("failed to create keychain: %w", err)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -31,6 +31,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
"github.com/ProtonMail/proton-bridge/v3/internal/legacy/credentials"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
@ -85,7 +86,7 @@ func TestMigratePrefsToVaultWithoutKeys(t *testing.T) {
func TestKeychainMigration(t *testing.T) {
// Migration tested only for linux.
if runtime.GOOS != "linux" {
if runtime.GOOS != platform.LINUX {
return
}
@ -134,7 +135,13 @@ func TestKeychainMigration(t *testing.T) {
func TestUserMigration(t *testing.T) {
kcl := keychain.NewTestKeychainsList()
kc, err := keychain.NewKeychain("mock", "bridge", kcl.GetHelpers(), kcl.GetDefaultHelper())
kc, _, err := keychain.NewKeychain(
"mock", "bridge",
kcl.GetHelpers(),
kcl.GetDefaultHelper(),
0,
make(map[string]bool),
)
require.NoError(t, err)
require.NoError(t, kc.Put("brokenID", "broken"))

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -18,25 +18,33 @@
package app
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"path"
"runtime"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/internal/vault/observabilitymetrics"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
"github.com/sirupsen/logrus"
)
func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, obsSender observability.BasicSender, featureFlags unleash.FeatureFlagStartupStore, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
logrus.Debug("Creating vault")
defer logrus.Debug("Vault stopped")
// Create the encVault.
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, panicHandler)
encVault, insecure, corrupt, err := newVault(reporter, locations, keychains, obsSender, featureFlags, panicHandler)
if err != nil {
return fmt.Errorf("could not create vault: %w", err)
}
@ -58,7 +66,7 @@ func WithVault(reporter *sentry.Reporter, locations *locations.Locations, keycha
return fn(encVault, insecure, corrupt != nil)
}
func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychains *keychain.List, obsSender observability.BasicSender, featureFlags unleash.FeatureFlagStartupStore, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
vaultDir, err := locations.ProvideSettingsPath()
if err != nil {
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
@ -67,11 +75,19 @@ func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychai
logrus.WithField("vaultDir", vaultDir).Debug("Loading vault from directory")
var (
vaultKey []byte
insecure bool
vaultKey []byte
insecure bool
lastUsedHelper string
)
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
if key, helper, err := loadVaultKey(vaultDir, keychains, featureFlags); err != nil {
if errors.Is(err, keychain.ErrPreferredKeychainNotAvailable) {
if err := vault.IncrementKeychainFailedAttemptCount(vaultDir); err != nil {
logrus.WithError(err).Error("Failed to increment failed keychain attempt count")
}
return &vault.Vault{}, false, nil, err
}
if reporter != nil {
if rerr := reporter.ReportMessageWithContext("Could not load/create vault key", map[string]any{
"keychainDefaultHelper": keychains.GetDefaultHelper(),
@ -87,8 +103,13 @@ func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychai
// We store the insecure vault in a separate directory
vaultDir = path.Join(vaultDir, "insecure")
// Schedule the relevant observability metric for sending.
obsSender.AddMetrics(observabilitymetrics.GenerateVaultKeyFetchGenericErrorMetric())
} else {
vaultKey = key
lastUsedHelper = helper
logHashedVaultKey(vaultKey) // Log a hash of the vault key.
}
gluonCacheDir, err := locations.ProvideGluonCachePath()
@ -96,34 +117,77 @@ func newVault(reporter *sentry.Reporter, locations *locations.Locations, keychai
return nil, false, nil, fmt.Errorf("could not provide gluon path: %w", err)
}
vault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
userVault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
if err != nil {
obsSender.AddMetrics(observabilitymetrics.GenerateVaultCreationGenericErrorMetric())
return nil, false, corrupt, fmt.Errorf("could not create vault: %w", err)
}
return vault, insecure, corrupt, nil
if corrupt != nil {
obsSender.AddMetrics(observabilitymetrics.GenerateVaultCreationCorruptErrorMetric())
}
// Remember the last successfully used keychain on Linux and store that as the user preference.
if runtime.GOOS == platform.LINUX {
if err := vault.SetHelper(vaultDir, lastUsedHelper); err != nil {
logrus.WithError(err).Error("Could not store last used keychain helper")
}
if err := vault.ResetFailedKeychainAttemptCount(vaultDir); err != nil {
logrus.WithError(err).Error("Could not reset and save failed keychain attempt count")
}
}
return userVault, insecure, corrupt, nil
}
func loadVaultKey(vaultDir string, keychains *keychain.List) ([]byte, error) {
helper, err := vault.GetHelper(vaultDir)
// loadVaultKey - loads the key used to encrypt the vault alongside the keychain helper used to access it.
func loadVaultKey(vaultDir string, keychains *keychain.List, featureFlags unleash.FeatureFlagStartupStore) (key []byte, keychainHelper string, err error) {
keychainHelper, err = vault.GetHelper(vaultDir)
if err != nil {
return nil, fmt.Errorf("could not get keychain helper: %w", err)
return nil, keychainHelper, fmt.Errorf("could not get keychain helper: %w", err)
}
kc, err := keychain.NewKeychain(helper, constants.KeyChainName, keychains.GetHelpers(), keychains.GetDefaultHelper())
keychainFailedAttemptCount, err := vault.GetKeychainFailedAttemptCount(vaultDir)
if err != nil {
return nil, fmt.Errorf("could not create keychain: %w", err)
return nil, keychainHelper, fmt.Errorf("could not get keychain failed attempt count: %w", err)
}
key, err := vault.GetVaultKey(kc)
kc, keychainHelper, err := keychain.NewKeychain(
keychainHelper, constants.KeyChainName,
keychains.GetHelpers(),
keychains.GetDefaultHelper(),
keychainFailedAttemptCount,
featureFlags,
)
if err != nil {
return nil, keychainHelper, fmt.Errorf("could not create keychain: %w", err)
}
logrus.WithField("keychainHelper", keychainHelper).Info("Initialized keychain helper")
key, err = vault.GetVaultKey(kc)
if err != nil {
if keychain.IsErrKeychainNoItem(err) {
logrus.WithError(err).Warn("no vault key found, generating new")
return vault.NewVaultKey(kc)
key, err := vault.NewVaultKey(kc)
return key, keychainHelper, err
}
return nil, fmt.Errorf("could not check for vault key: %w", err)
if keychain.ShouldRetryPreferredKeychain(featureFlags, keychainHelper) {
if keychainFailedAttemptCount < keychain.MaxFailedKeychainAttemptsLinux {
return nil, keychainHelper, keychain.PreferredKeychainRetryError(keychainFailedAttemptCount)
}
}
return nil, keychainHelper, fmt.Errorf("could not check for vault key: %w", err)
}
return key, nil
return key, keychainHelper, nil
}
// logHashedVaultKey - computes a sha256 hash and encodes it to base 64. The resulting string is logged.
func logHashedVaultKey(vaultKey []byte) {
hashedKey := sha256.Sum256(vaultKey)
logrus.WithField("hashedKey", hex.EncodeToString(hashedKey[:])).Info("Found vault key")
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -42,6 +42,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
"github.com/ProtonMail/proton-bridge/v3/internal/identifier"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
@ -57,6 +58,7 @@ import (
"github.com/bradenaw/juniper/xslices"
"github.com/elastic/go-sysinfo/types"
"github.com/go-resty/resty/v2"
uuid "github.com/google/uuid"
"github.com/sirupsen/logrus"
)
@ -166,6 +168,7 @@ func New(
updater Updater, // the updater to fetch and install updates
curVersion *semver.Version, // the current version of the bridge
keychains *keychain.List, // usable keychains
obsService *observability.Service,
apiURL string, // the URL of the API to use
cookieJar http.CookieJar, // the cookie jar to use
@ -204,6 +207,7 @@ func New(
keychains,
panicHandler,
reporter,
obsService,
api,
identifier,
@ -240,6 +244,7 @@ func newBridge(
keychains *keychain.List,
panicHandler async.PanicHandler,
reporter reporter.Reporter,
obsService *observability.Service,
api *proton.Manager,
identifier identifier.Identifier,
@ -271,9 +276,9 @@ func newBridge(
return nil, fmt.Errorf("failed to create focus service: %w", err)
}
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler, vault.GetFeatureFlagStickyKey())
observabilityService := observability.NewService(ctx, panicHandler)
obsService.Initialize(ctx, panicHandler)
bridge := &Bridge{
vault: vault,
@ -315,11 +320,11 @@ func newBridge(
lastVersion: lastVersion,
tasks: tasks,
syncService: syncservice.NewService(panicHandler, observabilityService),
syncService: syncservice.NewService(panicHandler, obsService),
unleashService: unleashService,
observabilityService: observabilityService,
observabilityService: obsService,
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
@ -334,7 +339,8 @@ func newBridge(
reporter,
uidValidityGenerator,
&bridgeIMAPSMTPTelemetry{b: bridge},
observabilityService,
obsService,
unleashService,
)
// Check whether username has changed and correct (macOS only)
@ -685,7 +691,7 @@ func (bridge *Bridge) HasAPIConnection() bool {
// then we verify whether the gluon cache exists using the "new" username (provided by the DB path in this case)
// if so we modify the cache directory in the user vault.
func (bridge *Bridge) verifyUsernameChange() {
if runtime.GOOS != "darwin" {
if runtime.GOOS != platform.MACOS {
return
}
@ -746,7 +752,7 @@ func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric)
bridge.observabilityService.AddMetrics(metric)
}
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionMetricTypeEnum, metrics ...proton.ObservabilityMetric) {
bridge.observabilityService.AddDistinctMetrics(errType, metrics...)
}
@ -784,3 +790,7 @@ func (bridge *Bridge) SetHostVersionGetterTest(fn func(host types.Host) string)
func (bridge *Bridge) SetRolloutPercentageTest(rollout float64) error {
return bridge.vault.SetUpdateRollout(rollout)
}
func (bridge *Bridge) GetFeatureFlagStickyKey() uuid.UUID {
return bridge.vault.GetFeatureFlagStickyKey()
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -45,6 +45,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
@ -56,6 +57,7 @@ import (
imapid "github.com/emersion/go-imap-id"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
@ -618,7 +620,7 @@ func TestBridge_AddressWithoutKeys(t *testing.T) {
require.NoError(t, err)
// Create an additional address for the user; it will not have keys.
aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password"))
aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password"), true)
require.NoError(t, err)
// Create an API client so we can remove the address keys.
@ -778,6 +780,30 @@ func TestBridge_ChangeCacheDirectory(t *testing.T) {
})
}
func TestBridge_FeatureFlagStickyKey_Persistence(t *testing.T) {
var uuidOne uuid.UUID
var uuidTwo uuid.UUID
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
uuidOne = b.GetFeatureFlagStickyKey()
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
require.Equal(t, uuidOne, b.GetFeatureFlagStickyKey())
})
})
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
uuidTwo = b.GetFeatureFlagStickyKey()
require.NotEqual(t, uuidOne, uuidTwo)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
require.Equal(t, uuidTwo, b.GetFeatureFlagStickyKey())
})
})
}
func TestBridge_ChangeAddressOrder(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
// Create a user.
@ -785,7 +811,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) {
require.NoError(t, err)
// Create a second address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true)
require.NoError(t, err)
// Create 10 messages for the user.
@ -919,6 +945,7 @@ func withBridgeNoMocks(
mocks.Updater,
v2_3_0,
keychain.NewTestKeychainsList(),
observability.NewTestService(),
// The API stuff.
apiURL,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -54,6 +54,9 @@ func NewMocks(tb testing.TB, version, minAuto *semver.Version) *Mocks {
mocks.Heartbeat.EXPECT().IsTelemetryAvailable(gomock.Any()).AnyTimes()
mocks.Heartbeat.EXPECT().GetHeartbeatPeriodicInterval().AnyTimes().Return(500 * time.Millisecond)
// It's called whenever a context is cancelled during sync. We should ought to remove this and make it more granular in the future.
mocks.Reporter.EXPECT().ReportMessageWithContext("Failed to sync, will retry later", gomock.Any()).AnyTimes()
return mocks
}

View File

@ -88,3 +88,18 @@ func (mr *MockReporterMockRecorder) ReportMessageWithContext(arg0, arg1 interfac
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportMessageWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
}
// ReportWarningWithContext mocks base method.
func (m *MockReporter) ReportWarningWithContext(arg0 string, arg1 map[string]interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReportWarningWithContext", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ReportWarningWithContext indicates an expected call of ReportWarningWithContext.
func (mr *MockReporterMockRecorder) ReportWarningWithContext(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportWarningWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -25,7 +25,7 @@ func NewMockObservabilitySender(ctrl *gomock.Controller) *MockObservabilitySende
func (m *MockObservabilitySender) EXPECT() *MockObservabilitySenderRecorder { return m.recorder }
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) {
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionMetricTypeEnum, _ ...proton.ObservabilityMetric) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddDistinctMetrics", errType)
}
@ -35,7 +35,18 @@ func (m *MockObservabilitySender) AddMetrics(metrics ...proton.ObservabilityMetr
m.ctrl.Call(m, "AddMetrics", metrics)
}
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
func (m *MockObservabilitySender) AddTimeLimitedMetric(metricType observability.DistinctionMetricTypeEnum, metric proton.ObservabilityMetric) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddTimeLimitedMetric", metricType, metric)
}
func (m *MockObservabilitySender) GetEmailClient() string {
m.ctrl.T.Helper()
m.ctrl.Call(m, "GetEmailClient")
return ""
}
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionMetricTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock,
"AddDistinctMetrics",
@ -47,3 +58,13 @@ func (mr *MockObservabilitySenderRecorder) AddMetrics(metrics ...proton.Observab
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetrics", reflect.TypeOf((*MockObservabilitySender)(nil).AddMetrics), metrics)
}
func (mr *MockObservabilitySenderRecorder) AddTimeLimitedMetric(metricType observability.DistinctionMetricTypeEnum, metric proton.ObservabilityMetric) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTimeLimitedMetric", reflect.TypeOf((*MockObservabilitySender)(nil).AddTimeLimitedMetric), metricType, metric)
}
func (mr *MockObservabilitySenderRecorder) GetEmailClient() {
mr.mock.ctrl.T.Helper()
mr.mock.ctrl.Call(mr.mock, "GetEmailClient", reflect.TypeOf((*MockObservabilitySender)(nil).GetEmailClient))
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -127,9 +127,9 @@ func TestBridge_Observability_UserMetric(t *testing.T) {
}
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
userMetricPeriod := time.Millisecond * 200
userMetricPeriod := time.Millisecond * 600
heartbeatPeriod := time.Second * 10
throttlePeriod := time.Millisecond * 100
throttlePeriod := time.Millisecond * 300
observability.ModifyUserMetricInterval(userMetricPeriod)
observability.ModifyThrottlePeriod(throttlePeriod)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -355,7 +355,7 @@ func TestBridge_CanProcessEventsDuringSync(t *testing.T) {
// Create a new address
newAddress := "foo@proton.ch"
addrID, err := s.CreateAddress(userID, newAddress, password)
addrID, err := s.CreateAddress(userID, newAddress, password, true)
require.NoError(t, err)
event := <-addressCreatedCh
@ -430,7 +430,7 @@ func TestBridge_EventReplayAfterSyncHasFinished(t *testing.T) {
createNumMessages(ctx, t, c, addrID, labelID, numMsg)
})
addrID1, err := s.CreateAddress(userID, "foo@proton.ch", password)
addrID1, err := s.CreateAddress(userID, "foo@proton.ch", password, true)
require.NoError(t, err)
var allowSyncToProgress atomic.Bool
@ -469,7 +469,7 @@ func TestBridge_EventReplayAfterSyncHasFinished(t *testing.T) {
})
// User AddrID2 event as a check point to see when the new address was created.
addrID2, err := s.CreateAddress(userID, "bar@proton.ch", password)
addrID2, err := s.CreateAddress(userID, "bar@proton.ch", password, true)
require.NoError(t, err)
allowSyncToProgress.Store(true)
@ -552,7 +552,7 @@ func TestBridge_MessageCreateDuringSync(t *testing.T) {
})
// User AddrID2 event as a check point to see when the new address was created.
addrID, err := s.CreateAddress(userID, "bar@proton.ch", password)
addrID, err := s.CreateAddress(userID, "bar@proton.ch", password, true)
require.NoError(t, err)
// At most two events can be published, one for the first address, then for the second.
@ -663,7 +663,7 @@ func TestBridge_AddressOrderChangeDuringSyncInCombinedModeDoesNotTriggerBadEvent
require.Equal(t, 1, len(info.Addresses))
require.Equal(t, info.Addresses[0], "user@proton.local")
addrID2, err := s.CreateAddress(userID, "foo@"+s.GetDomain(), password)
addrID2, err := s.CreateAddress(userID, "foo@"+s.GetDomain(), password, true)
require.NoError(t, err)
require.NoError(t, s.SetAddressOrder(userID, []string{addrID2, addrID}))

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -344,7 +344,12 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
case err != nil:
log.WithError(err).Error("The update could not be installed")
if reporterErr := bridge.reporter.ReportMessageWithContext(
"Cannot install update",
reporter.Context{"error": err},
); reporterErr != nil {
log.WithError(reporterErr).Error("Failed to report update error")
}
bridge.publish(events.UpdateFailed{
Release: job.Release,
Silent: job.Silent,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -28,6 +28,7 @@ import (
"github.com/ProtonMail/go-proton-api/server"
bridgePkg "github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/updater/versioncompare"
"github.com/elastic/go-sysinfo/types"
@ -331,7 +332,7 @@ func Test_Update_CheckOSVersion_NoUpdate(t *testing.T) {
bridge.CheckForUpdates()
if runtime.GOOS == "darwin" {
if runtime.GOOS == platform.MACOS {
require.Equal(t, events.UpdateNotAvailable{}, <-updateNotAvailableCh)
} else {
require.Equal(t, events.UpdateInstalled{
@ -442,7 +443,7 @@ func Test_Update_CheckOSVersion_HasUpdate(t *testing.T) {
bridge.CheckForUpdates()
if runtime.GOOS == "darwin" {
if runtime.GOOS == platform.MACOS {
require.Equal(t, events.UpdateInstalled{
Release: expectedUpdateRelease,
Silent: true,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -551,7 +551,7 @@ func (bridge *Bridge) addUserWithVault(
syncSettingsPath,
isNew,
bridge.notificationStore,
bridge.unleashService.GetFlagValue,
bridge.unleashService,
)
if err != nil {
return fmt.Errorf("failed to create user: %w", err)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -23,6 +23,7 @@ import (
"net"
"net/http"
"net/mail"
"runtime"
"strings"
"sync/atomic"
"testing"
@ -36,6 +37,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/bradenaw/juniper/stream"
"github.com/bradenaw/juniper/xslices"
@ -76,6 +78,9 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
if runtime.GOOS != platform.WINDOWS {
require.Equal(t, userID, (<-syncCh).UserID)
}
require.Equal(t, userID, (<-syncCh).UserID)
closeCh()
@ -304,7 +309,7 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
addrID, err = s.CreateAddress(userID, "other@pm.me", password)
addrID, err = s.CreateAddress(userID, "other@pm.me", password, true)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
@ -312,7 +317,7 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) {
userContinueEventProcess(ctx, t, s, bridge)
})
otherID, err := s.CreateAddress(userID, "another@pm.me", password)
otherID, err := s.CreateAddress(userID, "another@pm.me", password, true)
require.NoError(t, err)
require.NoError(t, s.RemoveAddress(userID, otherID))
@ -328,6 +333,87 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) {
})
}
func TestBridge_User_AddressEvents_BYOEAddressAdded(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
// Create an additional proton address
addrID, err = s.CreateAddress(userID, "other@pm.me", password, true)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressCreatedEvent(userID, addrID))
userContinueEventProcess(ctx, t, s, bridge)
userInfo, err := bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 2, len(userInfo.Addresses))
// Create an external address with sending disabled.
externalID, err := s.CreateExternalAddress(userID, "another@yahoo.com", password, false)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressCreatedEvent(userID, externalID))
userContinueEventProcess(ctx, t, s, bridge)
// User addresses should still return 2, as we ignore the external address.
userInfo, err = bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 2, len(userInfo.Addresses))
// Create an external address w. sending enabled. This is considered a BYOE address.
BYOEAddrID, err := s.CreateExternalAddress(userID, "other@yahoo.com", password, true)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressCreatedEvent(userID, BYOEAddrID))
userContinueEventProcess(ctx, t, s, bridge)
userInfo, err = bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 3, len(userInfo.Addresses))
})
})
}
func TestBridge_User_AddressEvents_ExternalAddressSendChanged(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
// Create an additional external address.
externalID, err := s.CreateExternalAddress(userID, "other@yahoo.me", password, false)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressCreatedEvent(userID, externalID))
userContinueEventProcess(ctx, t, s, bridge)
// We expect only one address, the external one without sending should not be considered a valid address.
userInfo, err := bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 1, len(userInfo.Addresses))
// Change it to allow sending such that it becomes a BYOE address.
err = s.ChangeAddressAllowSend(userID, externalID, true)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressUpdatedEvent(userID, externalID))
userContinueEventProcess(ctx, t, s, bridge)
// We should now have 2 usable addresses listed.
userInfo, err = bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 2, len(userInfo.Addresses))
})
})
}
func TestBridge_User_AddressEventUpdatedForAddressThatDoesNotExist_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
@ -694,7 +780,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
require.NoError(t, err)
// Create an additional address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
@ -745,7 +831,7 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) {
require.NoError(t, err)
// Create an additional address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true)
require.NoError(t, err)
// Immediately disable the address.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -658,7 +658,7 @@ func TestBridge_UserInfo_Alias(t *testing.T) {
require.NoError(t, err)
// Give the new user an alias.
require.NoError(t, getErr(s.CreateAddress(userID, "alias@pm.me", []byte("password"))))
require.NoError(t, getErr(s.CreateAddress(userID, "alias@pm.me", []byte("password"), true)))
// Login the user.
require.NoError(t, getErr(bridge.LoginFull(ctx, "primary", []byte("password"), nil, nil)))
@ -706,7 +706,7 @@ func TestBridge_User_GetAddresses(t *testing.T) {
// Create a user.
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
addrID2, err := s.CreateAddress(userID, "user@external.com", []byte("password"))
addrID2, err := s.CreateAddress(userID, "user@external.com", password, false)
require.NoError(t, err)
require.NoError(t, s.ChangeAddressType(userID, addrID2, proton.AddressTypeExternal))
@ -720,6 +720,29 @@ func TestBridge_User_GetAddresses(t *testing.T) {
})
}
func TestBridge_User_GetAddresses_BYOE(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
// Add a non-sending external address.
_, err = s.CreateExternalAddress(userID, "user@external.com", password, false)
require.NoError(t, err)
// Add a BYOE address.
_, err = s.CreateExternalAddress(userID, "user2@external.com", password, true)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
info, err := bridge.GetUserInfo(userID)
require.NoError(t, err)
require.Equal(t, 2, len(info.Addresses))
require.Equal(t, info.Addresses[0], "user@proton.local")
require.Equal(t, info.Addresses[1], "user2@external.com")
})
})
}
// getErr returns the error that was passed to it.
func getErr[T any](_ T, err error) error {
return err

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -21,6 +21,8 @@ package constants
import (
"fmt"
"runtime"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
)
const VendorName = "protonmail"
@ -72,13 +74,13 @@ const (
// nolint:goconst
func getAPIOS() string {
switch runtime.GOOS {
case "darwin":
case platform.MACOS:
return "macos"
case "linux":
case platform.LINUX:
return "linux"
case "windows":
case platform.WINDOWS:
return "windows"
default:

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -22,6 +22,8 @@ import (
"crypto/tls"
"net"
"net/http"
"net/url"
"strings"
"time"
)
@ -29,6 +31,11 @@ type TLSDialer interface {
DialTLSContext(ctx context.Context, network, address string) (conn net.Conn, err error)
}
type SecureTLSDialer interface {
DialTLSContext(ctx context.Context, network, address string) (conn net.Conn, err error)
ShouldSkipCertificateChainVerification(address string) bool
}
func SetBasicTransportTimeouts(t *http.Transport) {
t.MaxIdleConns = 100
t.MaxIdleConnsPerHost = 100
@ -71,6 +78,35 @@ func NewBasicTLSDialer(hostURL string) *BasicTLSDialer {
}
}
func extractDomain(hostname string) string {
parts := strings.Split(hostname, ".")
if len(parts) >= 2 {
return strings.Join(parts[len(parts)-2:], ".")
}
return hostname
}
// ShouldSkipCertificateChainVerification determines whether certificate chain validation should be skipped.
// It compares the domain of the requested address with the configured host URL domain.
// Returns true if the domains don't match (skip verification), false if they do (perform verification).
//
// NOTE: This assumes single-part TLDs (.com, .me) and won't handle multi-part TLDs correctly.
func (d *BasicTLSDialer) ShouldSkipCertificateChainVerification(address string) bool {
parsedURL, err := url.Parse(d.hostURL)
if err != nil {
return true
}
addressHost, _, err := net.SplitHostPort(address)
if err != nil {
addressHost = address
}
hostDomain := extractDomain(parsedURL.Host)
addressDomain := extractDomain(addressHost)
return addressDomain != hostDomain
}
// DialTLSContext returns a connection to the given address using the given network.
func (d *BasicTLSDialer) DialTLSContext(ctx context.Context, network, address string) (conn net.Conn, err error) {
return (&tls.Dialer{
@ -78,7 +114,7 @@ func (d *BasicTLSDialer) DialTLSContext(ctx context.Context, network, address st
Timeout: 30 * time.Second,
},
Config: &tls.Config{
InsecureSkipVerify: address != d.hostURL, //nolint:gosec
InsecureSkipVerify: d.ShouldSkipCertificateChainVerification(address), //nolint:gosec
},
}).DialContext(ctx, network, address)
}

View File

@ -0,0 +1,134 @@
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package dialer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBasicTLSDialer_ShouldSkipCertificateChainVerification(t *testing.T) {
tests := []struct {
hostURL string
address string
expected bool
}{
{
hostURL: "https://mail-api.proton.me",
address: "mail-api.proton.me:443",
expected: false,
},
{
hostURL: "https://proton.me",
address: "proton.me",
expected: false,
},
{
hostURL: "https://api.proton.me",
address: "mail.proton.me:443",
expected: false,
},
{
hostURL: "https://proton.me",
address: "mail-api.proton.me:443",
expected: false,
},
{
hostURL: "https://mail-api.proton.me",
address: "proton.me:443",
expected: false,
},
{
hostURL: "https://mail.google.com",
address: "mail-api.proton.me:443",
expected: true,
},
{
hostURL: "https://mail-api.protonmail.com",
address: "mail-api.proton.me:443",
expected: true,
},
{
hostURL: "https://proton.me",
address: "google.com:443",
expected: true,
},
{
hostURL: "https://proton.me",
address: "proton.com:443",
expected: true,
},
{
hostURL: "https://proton.me",
address: "example.me:443",
expected: true,
},
{
hostURL: "https://proton.me",
address: "mail.example.com:443",
expected: true,
},
{
hostURL: "https://proton.me",
address: "proton.me",
expected: false,
},
{
hostURL: "https://proton.me:8080",
address: "proton.me:443",
expected: true,
},
{
hostURL: "https://proton.me/api/v1",
address: "proton.me:443",
expected: false,
},
{
hostURL: "https://proton.black",
address: "mail-api.pascal.proton.black",
expected: false,
},
{
hostURL: "https://mail-api.pascal.proton.black",
address: "mail-api.pascal.proton.black",
expected: false,
},
{
hostURL: "https://mail-api.pascal.proton.black",
address: "proton.black:332",
expected: false,
},
{
hostURL: "https://mail-api.pascal.proton.black",
address: "proton.me",
expected: true,
},
{
hostURL: "https://mail-api.pascal.proton.black",
address: "proton.me:332",
expected: true,
},
}
for _, tt := range tests {
dialer := NewBasicTLSDialer(tt.hostURL)
result := dialer.ShouldSkipCertificateChainVerification(tt.address)
require.Equal(t, tt.expected, result)
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -50,12 +50,12 @@ var TrustedAPIPins = []string{ //nolint:gochecknoglobals
}
// TLSReportURI is the address where TLS reports should be sent.
const TLSReportURI = "https://reports.protonmail.ch/reports/tls"
const TLSReportURI = "https://reports.proton.me/reports/tls"
// PinningTLSDialer wraps a TLSDialer to check fingerprints after connecting and
// to report errors if the fingerprint check fails.
type PinningTLSDialer struct {
dialer TLSDialer
dialer SecureTLSDialer
pinChecker PinChecker
reporter Reporter
tlsIssueCh chan struct{}
@ -68,13 +68,13 @@ type Reporter interface {
// PinChecker is used to check TLS keys of connections.
type PinChecker interface {
CheckCertificate(conn net.Conn) error
CheckCertificate(conn net.Conn, certificateChainVerificationSkipped bool) error
}
// NewPinningTLSDialer constructs a new dialer which only returns TCP connections to servers
// which present known certificates.
// It checks pins using the given pinChecker and reports issues using the given reporter.
func NewPinningTLSDialer(dialer TLSDialer, reporter Reporter, pinChecker PinChecker) *PinningTLSDialer {
func NewPinningTLSDialer(dialer SecureTLSDialer, reporter Reporter, pinChecker PinChecker) *PinningTLSDialer {
return &PinningTLSDialer{
dialer: dialer,
pinChecker: pinChecker,
@ -85,6 +85,7 @@ func NewPinningTLSDialer(dialer TLSDialer, reporter Reporter, pinChecker PinChec
// DialTLSContext dials the given network/address, returning an error if the certificates don't match the trusted pins.
func (p *PinningTLSDialer) DialTLSContext(ctx context.Context, network, address string) (net.Conn, error) {
shouldSkipCertificateChainVerification := p.dialer.ShouldSkipCertificateChainVerification(address)
conn, err := p.dialer.DialTLSContext(ctx, network, address)
if err != nil {
return nil, err
@ -95,7 +96,7 @@ func (p *PinningTLSDialer) DialTLSContext(ctx context.Context, network, address
return nil, err
}
if err := p.pinChecker.CheckCertificate(conn); err != nil {
if err := p.pinChecker.CheckCertificate(conn, shouldSkipCertificateChainVerification); err != nil {
if tlsConn, ok := conn.(*tls.Conn); ok && p.reporter != nil {
p.reporter.ReportCertIssue(TLSReportURI, host, port, tlsConn.ConnectionState())
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -41,3 +41,15 @@ func NewTLSPinChecker(trustedPins []string) *TLSPinChecker {
func certFingerprint(cert *x509.Certificate) string {
return fmt.Sprintf(`pin-sha256=%q`, algo.HashBase64SHA256(string(cert.RawSubjectPublicKeyInfo)))
}
func (p *TLSPinChecker) isCertFoundInKnownPins(cert *x509.Certificate) bool {
fingerprint := certFingerprint(cert)
for _, pin := range p.trustedPins {
if pin == fingerprint {
return true
}
}
return false
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -25,8 +25,8 @@ import (
"net"
)
// CheckCertificate returns whether the connection presents a known TLS certificate.
func (p *TLSPinChecker) CheckCertificate(conn net.Conn) error {
// CheckCertificate verifies that the connection presents a known pinned leaf TLS certificate.
func (p *TLSPinChecker) CheckCertificate(conn net.Conn, certificateChainVerificationSkipped bool) error {
tlsConn, ok := conn.(*tls.Conn)
if !ok {
return errors.New("connection is not a TLS connection")
@ -34,14 +34,31 @@ func (p *TLSPinChecker) CheckCertificate(conn net.Conn) error {
connState := tlsConn.ConnectionState()
for _, peerCert := range connState.PeerCertificates {
fingerprint := certFingerprint(peerCert)
// When certificate chain verification is enabled (e.g., for known API hosts), we expect the TLS handshake to produce verified chains.
// We then validate that the leaf certificate of at least one verified chain matches a known pinned public key.
if !certificateChainVerificationSkipped {
if len(connState.VerifiedChains) == 0 {
return errors.New("no verified certificate chains")
}
for _, pin := range p.trustedPins {
if pin == fingerprint {
for _, chain := range connState.VerifiedChains {
// Check if the leaf certificate is one of the trusted pins.
if p.isCertFoundInKnownPins(chain[0]) {
return nil
}
}
return ErrTLSMismatch
}
// When certificate chain verification is skipped (e.g., for DoH proxies using self-signed certs),
// we only validate the leaf certificate against known pinned public keys.
if len(connState.PeerCertificates) == 0 {
return errors.New("no peer certificates available")
}
if p.isCertFoundInKnownPins(connState.PeerCertificates[0]) {
return nil
}
return ErrTLSMismatch

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -23,6 +23,6 @@ import "net"
// CheckCertificate returns whether the connection presents a known TLS certificate.
// The QA implementation always returns nil.
func (p *TLSPinChecker) CheckCertificate(conn net.Conn) error {
func (p *TLSPinChecker) CheckCertificate(conn net.Conn, _ bool) error {
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -64,8 +64,7 @@ func TestTLSPinInvalid(t *testing.T) {
checkTLSIssueHandler(t, 1, called)
}
// Disabled for now we'll need to patch this up.
func _TestTLSPinNoMatch(t *testing.T) { //nolint:unused
func TestTLSPinNoMatch(t *testing.T) {
skipIfProxyIsSet(t)
called, _, reporter, checker, cm := createClientWithPinningDialer(getRootURL())
@ -91,13 +90,22 @@ func TestTLSSignedCertWrongPublicKey(t *testing.T) {
r.Error(t, err, "expected dial to fail because of wrong public key")
}
// GODT-2293 bump badssl cert and re enable this.
func _TestTLSSignedCertTrustedPublicKey(t *testing.T) { //nolint:unused,deadcode
/*
For the following test the SSL pin rotates from time to time. Thus, the pin needs to be updated accordingly.
A new pin can be extracted by running the following command:
echo | openssl s_client -connect rsa4096.badssl.com:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
base64
*/
func TestTLSSignedCertTrustedPublicKey(t *testing.T) {
skipIfProxyIsSet(t)
_, dialer, _, checker, _ := createClientWithPinningDialer("")
copyTrustedPins(checker)
checker.trustedPins = append(checker.trustedPins, `pin-sha256="LwnIKjNLV3z243ap8y0yXNPghsqE76J08Eq3COvUt2E="`)
checker.trustedPins = append(checker.trustedPins, `pin-sha256="GGNnH/+pMnSFwy6vEDvfi5TRVWMpC5IINu3BHRM1c4E="`)
_, err := dialer.DialTLSContext(context.Background(), "tcp", "rsa4096.badssl.com:443")
r.NoError(t, err, "expected dial to succeed because public key is known and cert is signed by CA")
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2025 Proton AG
// Copyright (c) 2026 Proton AG
//
// This file is part of Proton Mail Bridge.
//

Some files were not shown because too many files have changed in this diff Show More