Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
533f61d
feat(ipc): add SSE status event streaming and replace string status w…
garmr-ulfr Feb 26, 2026
d255847
fix tests
garmr-ulfr Feb 26, 2026
5867baf
address pr comments
garmr-ulfr Feb 26, 2026
ebff68f
Merge branch 'main' into stream-status-events
garmr-ulfr Feb 27, 2026
d6187c1
fix test
garmr-ulfr Feb 27, 2026
b409b8c
refactor!: restructure codebase around LocalBackend and VPNClient pat…
garmr-ulfr Mar 22, 2026
c4eb9df
Merge branch 'main' into refactor
garmr-ulfr Mar 23, 2026
a3b19e1
Merge branch 'main' into refactor
garmr-ulfr Mar 23, 2026
ff00176
don't update default logger
garmr-ulfr Mar 23, 2026
8b2f946
remove concept of server groups from tunnel, just use auto/manual
garmr-ulfr Mar 24, 2026
18ef661
macos & win lantern/lanternd support
garmr-ulfr Mar 24, 2026
a379494
update README
garmr-ulfr Mar 24, 2026
0de6782
add just cmds for building
garmr-ulfr Mar 24, 2026
7c0df38
pass values from settings to StripeBillingPortalURL
garmr-ulfr Mar 25, 2026
7505c54
missed file
garmr-ulfr Mar 25, 2026
62b48c5
poll datacap info
garmr-ulfr Mar 25, 2026
1b24db1
use .exe extension on windows
garmr-ulfr Mar 25, 2026
ef78610
fix windows service start issue
garmr-ulfr Mar 25, 2026
6445eaa
add run cmd to service file
garmr-ulfr Mar 25, 2026
b1038e2
use auto for empty string in connect
garmr-ulfr Mar 26, 2026
f4a8261
fix daemon service name linux
garmr-ulfr Mar 26, 2026
70e0898
cleanup daemon binary on uninstall
garmr-ulfr Mar 26, 2026
0468a7a
Merge branch 'main' into refactor
garmr-ulfr Mar 26, 2026
1cf7bc0
fix tests
garmr-ulfr Mar 26, 2026
84631cc
add standalone build tag to cli on macos
garmr-ulfr Mar 26, 2026
8d96cff
fix tracer name, start telemetry if enabled
garmr-ulfr Mar 30, 2026
a139350
add version cmd, check version before installing
garmr-ulfr Mar 30, 2026
0ff448b
Apply suggestions from code review
garmr-ulfr Mar 30, 2026
8459e01
remove systemd unit file, resolve dst symlink
garmr-ulfr Mar 30, 2026
ef79a49
small cleanup
garmr-ulfr Mar 31, 2026
3705380
add default data/log directories for desktop platforms
garmr-ulfr Mar 31, 2026
77b555e
fix linux daemon path
garmr-ulfr Mar 31, 2026
7933ad4
rename PreTest to OfflineTest
garmr-ulfr Apr 1, 2026
6de1ba2
fix status for auto selected server
garmr-ulfr Apr 1, 2026
afb5803
wrap inner log writer so published logs are fmted
garmr-ulfr Apr 1, 2026
75d805f
pass header in data-cap and update to user data saving
jigar-f Apr 2, 2026
900f9e9
Merge branch 'main' into 'refactor'
garmr-ulfr Apr 2, 2026
1ef7403
use publicip in cli, fetch public ip on start
garmr-ulfr Apr 2, 2026
add14c0
clean up
garmr-ulfr Apr 2, 2026
8bf6ee5
merge in datacap streaming
garmr-ulfr Apr 3, 2026
7c042ee
servers types marshal/unmarshal funcs, move gostack to lantern
garmr-ulfr Apr 3, 2026
fb77d6a
persist oauth provider
garmr-ulfr Apr 4, 2026
b21573b
fix cli vpn connect handler to select server if connected
garmr-ulfr Apr 4, 2026
c9076da
clear oauth provider on logout, check error when setting it
garmr-ulfr Apr 4, 2026
f64ad18
remove redundant peer verification on macos
garmr-ulfr Apr 6, 2026
becbffd
expose urltest results
garmr-ulfr Apr 6, 2026
f42186d
refactor server manager: remove server group concept
garmr-ulfr Apr 7, 2026
7c895bd
remove modes from servers
garmr-ulfr Apr 7, 2026
f4bf568
return tags for added servers
garmr-ulfr Apr 7, 2026
e169f57
run initial offline url test on start
garmr-ulfr Apr 7, 2026
6bab4fb
remove isLantern parameters, reject adding server with existing tag
garmr-ulfr Apr 8, 2026
874a8d7
auto-restart daemon if it's not running
garmr-ulfr Apr 8, 2026
4435887
add getters for servers as json
garmr-ulfr Apr 8, 2026
428008a
set selected server in settings to nil when removed
garmr-ulfr Apr 8, 2026
8a3f4fc
add loopback ipc client for mobile
garmr-ulfr Apr 8, 2026
93b0cc3
uninstall existing daemon befor installing
garmr-ulfr Apr 8, 2026
95d89d0
add selected server json function
garmr-ulfr Apr 9, 2026
4936bd8
update lantern-box, change windows service name
garmr-ulfr Apr 9, 2026
175744f
fix several bugs, small cleanup
garmr-ulfr Apr 9, 2026
4f274d6
Merge branch 'main' into refactor
garmr-ulfr Apr 9, 2026
c93b17a
code review, accept version for makefile/justfile
garmr-ulfr Apr 10, 2026
4ca8565
fix split tunnel dangling-pointer issues
garmr-ulfr Apr 10, 2026
9c8c343
add code review from PR fix to main
garmr-ulfr Apr 10, 2026
91d30a6
prevent scientific notation for numeric settings values
garmr-ulfr Apr 10, 2026
0bc5e06
bundle all .log files for issue report
garmr-ulfr Apr 13, 2026
4186141
lazily init kindling and requesting initial IP/config
garmr-ulfr Apr 13, 2026
015cb4a
Merge branch 'main' into refactor
garmr-ulfr Apr 13, 2026
650b8ab
Auth error code issues and token issue
jigar-f Apr 15, 2026
b1c6191
fix(servers): release write lock during saveServers to prevent reader…
myleshorton Apr 15, 2026
3a6b52f
eagerly start kindling and public ip detection
garmr-ulfr Apr 15, 2026
dcb32cb
start autoselected listener
garmr-ulfr Apr 15, 2026
2efb960
Merge remote-tracking branch 'origin/main' into refactor
garmr-ulfr Apr 16, 2026
0cb7994
Pass empty when value is nil
jigar-f Apr 16, 2026
09bde9d
config: match main's UserID format in ConfigRequest (#420)
myleshorton Apr 16, 2026
25cb671
ipc: add /env endpoint to patch env vars at runtime
garmr-ulfr Apr 16, 2026
1998c37
ipc: add /config/events SSE endpoint for config-change notifications …
myleshorton Apr 20, 2026
18dc743
add deviceid migration to copy file to new location
garmr-ulfr Apr 20, 2026
6d05abc
ci: run on every PR regardless of target branch (#431)
myleshorton Apr 21, 2026
c3f1237
deps: bump lantern-box to v0.0.70 + broflake to main (caea079) (#430)
myleshorton Apr 21, 2026
97ff9ac
vpn: direct-transport + streaming wrapper for Unbounded signaling (pl…
myleshorton Apr 21, 2026
73091d2
feat(settings): runtime-reloadable toggles + set/get CLI
garmr-ulfr Apr 21, 2026
ea9bc97
feat(config): add IPC + CLI command to force a config update
garmr-ulfr Apr 21, 2026
0b47686
don't send config request if one is in flight
garmr-ulfr Apr 21, 2026
2af9260
save country and feature overrides env var to settings in ipc patch
garmr-ulfr Apr 22, 2026
2f4231e
deps: bump sing-box-minimal to v1.12.22-lantern (#435)
myleshorton Apr 22, 2026
81bd540
vpn: close MutableGroupManager on tunnel close (#432) (#437)
myleshorton Apr 22, 2026
230c9c5
fix(3263): simplify ruleset guards and strengthen test coverage
garmr-ulfr Apr 22, 2026
6e4010e
fix(3265): gate auto-selected polling on VPN connect
garmr-ulfr Apr 22, 2026
23054be
emit auto selected server event after url tests
garmr-ulfr Apr 22, 2026
30ef525
bump go to v1.26.2
garmr-ulfr Apr 22, 2026
34cea62
country env override, urltest history ownership, dep bumps
garmr-ulfr Apr 23, 2026
cb3a197
refactor(ipc): split event streams by platform with retry and local-o…
garmr-ulfr Apr 23, 2026
2c7b22f
Fix sign up issue
jigar-f Apr 23, 2026
42d491c
backend: treat ConnectVPN while connected as a server swap (#439)
myleshorton Apr 23, 2026
e20a238
Revert "backend: treat ConnectVPN while connected as a server swap (#…
garmr-ulfr Apr 23, 2026
59c8084
add documentation for PlatformInterface
garmr-ulfr Apr 23, 2026
1ee1163
refactor(vpn): own VPN status on the client so restarts span tunnels
garmr-ulfr Apr 23, 2026
9703bcf
vpn: instrument tunnel.start phases + VPNClient.Restart (#443)
myleshorton Apr 23, 2026
fac9089
fix(vpn): treat the empty string as AutoSelect in SelectServer
garmr-ulfr Apr 24, 2026
d5a1872
fix(backend): treat empty tag as AutoSelect in LocalBackend.SelectSer…
myleshorton Apr 24, 2026
510838b
centralize file permissions in common/fileperm
garmr-ulfr Apr 24, 2026
2f83277
docs: prune redundant doc comments and document AGENTS.md style
garmr-ulfr Apr 27, 2026
93e3d6c
feat(vpn): persist URL test results across tunnel close/open
garmr-ulfr Apr 27, 2026
5643163
feat(vpn): wire InitialServer for startup server selection
garmr-ulfr Apr 28, 2026
7a95d63
backend: stop logging ErrTunnelAlreadyConnected as an error after con…
myleshorton Apr 28, 2026
e78b4fd
qa: add RADIANCE_OUTBOUND_SOCKS_ADDRESS for whole-process residential…
Apr 27, 2026
653b0ab
qa-bandit: Android-shaped bandit probe + complete the egress override
Apr 27, 2026
b884143
qa-bandit: drop UDP-only protocols + retry probe while URLTest converges
Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ name: Go

on:
push:
branches: [ "main" ]
branches: [ "main", "refactor" ]
# No branch filter here — run CI on every pull request regardless of target
# branch. This catches PRs aimed at refactor, feature branches, or anything
# else, not just main. The previous filter ("branches: [ main ]") silently
# skipped CI for PRs into refactor, which is our active dev branch.
pull_request:
branches: [ "main" ]

jobs:

Expand All @@ -21,6 +24,13 @@ jobs:
with:
go-version-file: "go.mod"

- name: Check for slog overwrite calls in tests
run: |
if grep -rn 'slog\.SetDefault\|slog\.SetLogLoggerLevel' --include='*_test.go' .; then
echo "::error::Test files should not upate the slog.Default logger or level. This pollutes the output."
exit 1
fi

- name: Build
run: go build -v -tags with_clash_api ./...
- name: Test
Expand Down
60 changes: 60 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1 +1,61 @@
- Telemetry attributes: follow rules in https://github.com/getlantern/semconv/blob/main/AGENTS.md

## Code Comments

**Default: no comment.** Only add one if a specific *why* is load-bearing — invariant, concurrency guarantee, error condition, zero-value behavior, non-obvious caller contract, or a constraint that would surprise the reader. Aesthetic "this section is well-documented" comments are noise.

Before writing any comment, run this checklist on the proposed text. If any answer is yes, delete or rewrite:

1. Does it restate the identifier name or signature? (`// Foo does foo`, `// updateX manages X across Y`)
2. Does it narrate what the visible next line does? (`// Cancel any existing listener` immediately above `cancel()`)
3. Does it open with a generic lifecycle/management preamble before getting to the point? (`// manages the lifecycle of...`, `// handles the X for Y`)
4. Does it reference tickets, coworkers, sibling files, commit SHAs, or other code locations? Those belong in the commit message / PR description — they rot in source.
5. Does it describe the mechanism instead of the contract? (`authenticates via peer credentials over a Unix socket` vs. `authenticates each connection`)

Lead with the *why*, not a summary of the function. If the only thing you can write is a summary, the comment isn't needed.

Examples:

```go
// BAD — restates name, generic preamble, narrates the code
// updateURLTestListener manages the lifecycle of the URL test result listener
// across VPN status changes. Connected always re-attaches (canceling any
// existing listener) so a stale event still leaves the listener bound to
// the live storage.

// GOOD — leads with the trap, no narration
// Status events are dispatched in unordered goroutines, so reacting to
// intermediate statuses risks a stale handler tearing down a listener
// a concurrent Connected handler just attached. Only Connected (which
// re-attaches unconditionally) and terminal-down statuses are acted on.
```

```go
// BAD — narrates the next line
// Cancel any in-flight offline tests and wait for them to finish.
c.offlineTestCancel()
<-done

// GOOD — no comment; the names already say it
c.offlineTestCancel()
<-done
```

```go
// BAD — references ticket and coworker
// Per Freshdesk #172640 (reported by Alice), saveServers held the lock
// for 1+ minute. We now release access before disk I/O.

// GOOD — states the invariant; the ticket lives in git history
// access is released before disk I/O so a slow write can't starve readers.
```

Before writing an inline comment, consider whether a doc comment on the enclosing function or type would make it unnecessary. Prefer documenting contracts at the declaration over explaining implementation details inline.

TODO comments must state *what* needs to happen and *why* it isn't done now. `TODO: ???` is not actionable — either resolve it or remove it.

## Go Doc Comments

- When a doc comment is warranted on an exported identifier, start it with the identifier's name and use complete sentences: `// Foo does X.` The first sentence is the summary shown by `go doc` and pkg.go.dev.
- Package comments: one per package, above the `package` clause (conventionally in `doc.go` for larger packages), starting with `// Package foo ...`.
- Formatting (gofmt-aware since Go 1.19): blank lines separate paragraphs; indented lines render as code blocks; lines starting with `-`, `*`, or `1.` render as lists; `[Name]` links to other symbols; `# Heading` renders as a heading. Avoid HTML and manual wrapping.
4 changes: 0 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,5 @@ proto:
protoc --go_out=. --plugin=build/protoc-gen-go --go_opt=paths=source_relative api/protos/subscription.proto
protoc --go_out=. --plugin=build/protoc-gen-go --go_opt=paths=source_relative issue/issue.proto

mock:
go install go.uber.org/mock/mockgen@latest
go generate ./...

test:
go test -v ./...
97 changes: 75 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,50 +34,103 @@ Available variables:
* `RADIANCE_FEATURE_OVERRIDE`: Comma-separated list of feature flags to force-enable on the server side. If set, the value is sent as the `X-Lantern-Feature-Override` header on config requests in any environment, and it is recommended for testing/non-production use. For example, `RADIANCE_FEATURE_OVERRIDE=bandit_assignment` enables bandit-based proxy assignment during testing.


## Packages
## Architecture

Use `common.Init` to setup directories and configure loggers.
> [!note]
> This isn't necessary if `NewRadiance` was called as it will call `Init` for you.
Radiance is structured around a `LocalBackend` pattern that ties together all core functionality: configuration, servers, VPN connection, account management, issue reporting, and telemetry. The `LocalBackend` is the central coordinator and should be the primary interface for interacting with Radiance programmatically.

### `vpn`
In addition to being the core of the [Lantern client](https://github.com/getlantern/lantern), radiance also provides a standalone daemon and CLI:

The `vpn` package provides high-level functions for controlling the VPN tunnel.
- **`lanternd`** — the VPN daemon that runs the `LocalBackend` and exposes an IPC server. It can run in the foreground or be installed as a system service.
- **`lantern`** — a CLI client that communicates with the daemon over IPC.

To connect to the best available server, you can use the `QuickConnect` function. This function takes a server group (`servers.SGLantern`, `servers.SGUser`, or `"all"`) and a `PlatformInterface` as input. For example:
### Building CLI & Daemon

```go
err := vpn.QuickConnect(servers.SGLantern, platIfce)
From the `cmd/` directory:

```sh
make build-daemon
make build-cli
```
Or using [just](https://github.com/casey/just)
```sh
just build-daemon
just build-cli
```

will connect to the best Lantern server, while:
Both binaries are output to `bin/`. You can also run the daemon directly with `make run-daemon`.

```go
err := vpn.QuickConnect("all", platIfce)
### Running

```sh
# Start the daemon
lanternd run --data-path ~/data --log-path ~/logs

# Install/uninstall as a system service
lanternd install --data-path ~/data --log-path ~/logs
lanternd uninstall

# CLI commands (requires a running daemon)
lantern connect [--tag <server-tag>]
lantern disconnect
lantern status
lantern servers
lantern account login
lantern subscription
lantern split-tunnel
lantern logs
lantern ip
```

will connect to the best overall.
## Packages

Use `common.Init` to setup directories and configure loggers.
> [!note]
> This isn't necessary if `NewLocalBackend` was called as it will call `Init` for you.

### `backend`

The `backend` package provides `LocalBackend`, the main entry point for all Radiance functionality. Create one with `NewLocalBackend(ctx, opts)` and call `Start()` to begin fetching configuration and serving requests. `LocalBackend` owns and coordinates the `VPNClient`, `ServerManager`, `ConfigHandler`, `AccountClient`, `IssueReporter`, and telemetry.

### `vpn`

You can also connect to a specific server using `ConnectToServer`. This function requires a server group, a server tag, and a `PlatformInterface`. For example:
The `vpn` package provides `VPNClient`, which manages the lifecycle of the VPN tunnel.

```go
err := vpn.ConnectToServer(servers.SGUser, "my-server", platIfce)
client := vpn.NewVPNClient(dataPath, logger, platformIfce)
err := client.Connect(boxOptions)
```

Both `QuickConnect` and `ConnectToServer` can be called without disconnecting first, allowing you to seamlessly switch between servers or connection modes.
`Connect` can be called without disconnecting first, allowing you to seamlessly switch between servers. Once connected, you can query status or view `Connections`. To stop the VPN, call `Disconnect`.

Once connected, you can check the `GetStatus` or view `ActiveConnections`. To stop the VPN, simply call `Disconnect`. The package also supports reconnecting to the last used server with `Reconnect`.
> [!note]
> In most cases, you should use the `LocalBackend` methods (`ConnectVPN`, `DisconnectVPN`, `RestartVPN`, `VPNStatus`) rather than using `VPNClient` directly.

This package also includes split tunneling capabilities, allowing you to include or exclude specific applications, domains, or IP addresses from the VPN tunnel. You can manage split tunneling by creating a `SplitTunnel` handler with `NewSplitTunnelHandler`. This handler allows you to `Enable` or `Disable` split tunneling, `AddItem` or `RemoveItem` from the filter, and view the current `Filters`.
This package also includes split tunneling capabilities via the `SplitTunnel` type, allowing you to include or exclude specific applications, domains, or IP addresses from the VPN tunnel.

### `servers`

The `servers` package is responsible for managing all VPN server configurations, separating them into two groups: `lantern` (official Lantern servers) and `user` (user-provided servers).
The `servers` package manages all VPN server configurations, separating them into two groups: `lantern` (official Lantern servers fetched from the config) and `user` (user-provided servers).

The `Manager` allows you to `AddServers` and `RemoveServer` configurations. You can retrieve the config for a specific server with `GetServerByTag` or use `Servers` to retrieve all configs.
The `Manager` allows you to `AddServers` and `RemoveServers` configurations. You can retrieve the config for a specific server with `GetServerByTag` or use `Servers` to retrieve all configs.

> [!caution]
> While you can get a new `Manager` instance with `NewManager`, it is recommended to use `Radiance.ServerManager`. This will return the shared manager instance. `NewManager` can be useful for retrieving server information if you don't have access to the shared instance, but the new instance should not be kept as it won't stay in sync and adding server configs to it will overwrite existing configs if both manager instances are pointed to the same server file.
> While you can get a new `Manager` instance with `NewManager`, it is recommended to use the `LocalBackend`'s server methods (`Servers`, `AddServers`, `RemoveServers`, `GetServerByTag`). These use the shared manager instance. `NewManager` can be useful for retrieving server information if you don't have access to the shared instance, but the new instance should not be kept as it won't stay in sync.

A key feature of this package is the ability to add private servers from a server manager via an access token using `AddPrivateServer`. This process uses Trust-on-first-use (TOFU) to securely add the server. Once a private server is added, you can invite other users with `InviteToPrivateServer` and revoke access with `RevokePrivateServerInvite`.

### `ipc`

The `ipc` package provides the communication layer between the `lantern` CLI and the `lanternd` daemon. The `ipc.Server` exposes an HTTP API backed by the `LocalBackend`, and the `ipc.Client` provides a typed Go client for calling it. All communication happens over a local socket.

### `account`

The `account` package handles user authentication (email/password and OAuth), signup, email verification, account recovery, device management, and subscription operations. It communicates with the Lantern account server and caches authentication state locally.

### `config`

The `config` package fetches proxy configuration from the Lantern API on a polling interval and emits `NewConfigEvent` events when the configuration changes. The `LocalBackend` subscribes to these events to update server configurations automatically.

### `events`

A key feature of this package is the ability to add private servers from a server manager via an access token using `AddPrivateServer`. This process uses Trust-on-first-use (TOFU) to securely add the server. Once a private server is added, you can use the manager to invite other users to it with `InviteToPrivateServer` and revoke access with `RevokePrivateServerInvite`.
A generic pub-sub event system used throughout Radiance for decoupled communication between components (config changes, VPN status updates, log entries, etc.).

117 changes: 117 additions & 0 deletions account/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package account

import (
"context"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"math/big"

"github.com/1Password/srp"
"golang.org/x/crypto/pbkdf2"
"google.golang.org/protobuf/proto"

"github.com/getlantern/radiance/account/protos"
)

func (a *Client) fetchSalt(ctx context.Context, email string) (*protos.GetSaltResponse, error) {
query := map[string]string{"email": email}
resp, err := a.sendRequest(ctx, "GET", "/users/salt", query, nil, nil)
if err != nil {
return nil, err
}
var salt protos.GetSaltResponse
if err := proto.Unmarshal(resp, &salt); err != nil {
return nil, fmt.Errorf("unmarshaling salt response: %w", err)
}
return &salt, nil
}

// clientProof performs the SRP authentication flow to generate the client proof for the given email and password.
func (a *Client) clientProof(ctx context.Context, email, password string, salt []byte) ([]byte, error) {
srpClient, err := newSRPClient(email, password, salt)
if err != nil {
return nil, err
}

A := srpClient.EphemeralPublic()
data := &protos.PrepareRequest{
Email: email,
A: A.Bytes(),
}
resp, err := a.sendRequest(ctx, "POST", "/users/prepare", nil, nil, data)
if err != nil {
return nil, err
}

var srpB protos.PrepareResponse
if err := proto.Unmarshal(resp, &srpB); err != nil {
return nil, fmt.Errorf("unmarshaling prepare response: %w", err)
}
B := big.NewInt(0).SetBytes(srpB.B)
if err = srpClient.SetOthersPublic(B); err != nil {
return nil, err
}

key, err := srpClient.Key()
if err != nil || key == nil {
return nil, fmt.Errorf("user_not_found error while generating Client key %w", err)
}
if !srpClient.GoodServerProof(salt, email, srpB.Proof) {
return nil, fmt.Errorf("user_not_found checking server proof %w", err)
}

proof, err := srpClient.ClientProof()
if err != nil {
return nil, fmt.Errorf("user_not_found generating client proof %w", err)
}
return proof, nil
}

// getSalt retrieves the salt for the given email address or it's cached value.
func (a *Client) getSalt(ctx context.Context, email string) ([]byte, error) {
if cached := a.getSaltCached(); cached != nil {
return cached, nil
}
resp, err := a.fetchSalt(ctx, email)
if err != nil {
return nil, err
}
return resp.Salt, nil
}

const group = srp.RFC5054Group3072

func newSRPClient(email, password string, salt []byte) (*srp.SRP, error) {
if len(salt) == 0 || len(password) == 0 || len(email) == 0 {
return nil, errors.New("salt, password and email should not be empty")
}

encryptedKey, err := generateEncryptedKey(password, email, salt)
if err != nil {
return nil, fmt.Errorf("failed to generate encrypted key: %w", err)
}

return srp.NewSRPClient(srp.KnownGroups[group], encryptedKey, nil), nil
}

func generateEncryptedKey(password, email string, salt []byte) (*big.Int, error) {
if len(salt) == 0 || len(password) == 0 || len(email) == 0 {
return nil, errors.New("salt or password or email is empty")
}
combinedInput := password + email
encryptedKey := pbkdf2.Key([]byte(combinedInput), salt, 4096, 32, sha256.New)
encryptedKeyBigInt := big.NewInt(0).SetBytes(encryptedKey)
return encryptedKeyBigInt, nil
}

func generateSalt() ([]byte, error) {
salt := make([]byte, 16)
if n, err := rand.Read(salt); err != nil {
return nil, err
} else if n != 16 {
return nil, errors.New("failed to generate 16 byte salt")
}
return salt, nil
}
Loading
Loading