@@ -117,6 +117,13 @@ const (
117117 wsMaxPermaWgKeys = 5
118118
119119 disablePermaCreds = true
120+
121+ // managedPermaCreds selects a server-managed keypair strategy for permanent creds.
122+ // When true, the keypair is generated entirely by the server (no local key-gen and no
123+ // /WgConfigs/init call). The server-generated keypair is always returned by
124+ // /WgConfigs/list_keys, so liveness can be checked without any /init round-trip.
125+ // When false (default), the existing locally-generated key + /init + /permanent flow is used.
126+ managedPermaCreds = false
120127)
121128
122129// github.com/Windscribe/Android-App/blob/746d505dc69/base/src/main/java/com/windscribe/vpn/constants/NetworkErrorCodes.kt
@@ -1509,10 +1516,13 @@ func getServerList(h *http.Client, sess *WsSession, ent *WsEntitlement) (*WsServ
15091516
15101517// initAndConnectCreds registers creds via /WgConfigs/init and then either:
15111518// - dynamic creds (perma=false): reserves a WireGuard interface via /WgConfigs/connect.
1512- // - perma creds (perma=true): registers the init'd pubkey via /WgConfigs/permanent.
1519+ // - perma creds (perma=true, managedPermaCreds=false): registers the init'd pubkey via
1520+ // /WgConfigs/permanent. The keypair is generated locally; /WgConfigs/init is required.
1521+ // - managed perma creds (perma=true, managedPermaCreds=true): delegates entirely to
1522+ // managedPermaCredsFn; no local key-gen and no /WgConfigs/init call is performed.
15131523//
1514- // For perma=true, if existingCreds is non-nil and its pubkey is still present in
1515- // /WgConfigs/list_keys, existingCreds is returned as-is (no /init or /permanent needed).
1524+ // For perma=true (non-managed) , if existingCreds is non-nil and its pubkey is still present
1525+ // in /WgConfigs/list_keys, existingCreds is returned as-is (no /init or /permanent needed).
15161526// If the key is no longer listed, a fresh /init + /permanent cycle is performed.
15171527// The /init error handling is identical for both dynamic and perma paths.
15181528func initAndConnectCreds (h * http.Client , existingCreds * WsWgCreds , perma bool , sess * WsSession , ent * WsEntitlement , forceInit bool ) (* WsWgCreds , error ) {
@@ -1535,6 +1545,12 @@ func initAndConnectCreds(h *http.Client, existingCreds *WsWgCreds, perma bool, s
15351545 return nil , nil // perma creds disabled; no API calls, no error
15361546 }
15371547
1548+ // Managed perma creds: server generates both priv+pub; no /init call required.
1549+ // list_keys always includes remotely-generated keys, so liveness is cheap to verify.
1550+ if perma && managedPermaCreds {
1551+ return managedPermaCredsFn (h , existingCreds , ent , bearer )
1552+ }
1553+
15381554 force := "0" // 0 when forced registration (which deletes older keys) is not needed
15391555
15401556 // For perma creds, check whether the existing pubkey is still registered on the server.
@@ -2123,6 +2139,43 @@ func listKeys(h *http.Client, ent *WsEntitlement, bearer string) (*WsWgListKeysR
21232139 return & out , nil
21242140}
21252141
2142+ // managedPermaCredsFn implements the managed-perma-creds flow (managedPermaCreds=true).
2143+ // The server generates both the private and public key; no /WgConfigs/init is needed.
2144+ // If existingCreds are still present in /WgConfigs/list_keys they are reused directly.
2145+ // Otherwise /WgConfigs/permanent is called without a pubkey so the server creates a fresh
2146+ // keypair. Remotely-generated keypairs are always included in subsequent list_keys responses.
2147+ func managedPermaCredsFn (h * http.Client , existingCreds * WsWgPermanentConfig , ent * WsEntitlement , bearer string ) (* WsWgPermanentConfig , error ) {
2148+ if len (bearer ) <= 0 {
2149+ return nil , errWsNoToken
2150+ }
2151+ tokst := tokenState (bearer )
2152+
2153+ // If we already have remotely-generated creds, verify they are still active.
2154+ if existingCreds != nil && len (existingCreds .PublicKey ) > 0 {
2155+ for range 2 {
2156+ keys , kerr := listKeys (h , ent , bearer )
2157+ if kerr != nil || keys == nil {
2158+ log .E ("ws: wgconfs: perma(managed): list keys err (nil? %t / tok? %s): %v" , keys == nil , tokst , kerr )
2159+ wsBriefPauseBeforeRetry ()
2160+ continue
2161+ }
2162+ for _ , k := range keys .Data .PubKeys {
2163+ if k == existingCreds .PublicKey {
2164+ log .I ("ws: wgconfs: perma(managed): existing key %s active (%s); reusing" , trunc8 (existingCreds .PublicKey ), tokst )
2165+ return existingCreds , nil
2166+ }
2167+ }
2168+ // key not found in list; fall through to generate a new remote keypair
2169+ log .I ("ws: wgconfs: perma(managed): existing key %s missing from list_keys (%s); regenerating" , trunc8 (existingCreds .PublicKey ), tokst )
2170+ break
2171+ }
2172+ }
2173+
2174+ // Let the server generate both the private and public keys.
2175+ // No pubkey is supplied; the returned creds include PrivateKey from the server.
2176+ return createPermaCreds (h , ent , bearer , "" /*no pubkey: server generates keypair*/ )
2177+ }
2178+
21262179// createPermaCreds calls POST WgConfigs/permanent to create a permanent WG config.
21272180// If pubkey is empty the server generates both the private and public keys.
21282181func createPermaCreds (h * http.Client , ent * WsEntitlement , bearer , pubkey string ) (* WsWgPermanentConfig , error ) {
@@ -2131,7 +2184,7 @@ func createPermaCreds(h *http.Client, ent *WsEntitlement, bearer, pubkey string)
21312184 }
21322185 port := wsRandomPort () // some port; doesn't matter which one
21332186 tokst := tokenState (bearer )
2134-
2187+ managed := len ( pubkey ) <= 0
21352188 // curl --location --request POST '.../WgConfigs/permanent' \
21362189 // --header 'Authorization: Bearer <token>' \
21372190 // --data-urlencode 'port=443' \
@@ -2145,19 +2198,19 @@ func createPermaCreds(h *http.Client, ent *WsEntitlement, bearer, pubkey string)
21452198 u := baseurl (ent .TestDomain , ent .Cid , ent .Did ).JoinPath (wswgpermanentpath )
21462199 req , err := http .NewRequest ("POST" , u .String (), strings .NewReader (data .Encode ()))
21472200 if err != nil {
2148- return nil , log .EE ("ws: conf: perma: req err: %v" , err )
2201+ return nil , log .EE ("ws: conf: perma: (m? %t) req err: %v" , managed , err )
21492202 }
21502203 req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
21512204 authHeader (req , bearer )
21522205 didHeader (req , ent .DidToken )
21532206
21542207 if settings .Debug {
2155- log .V ("ws: conf: perma: req: %s tok %s; port %s" , u .String (), tokst , port )
2208+ log .V ("ws: conf: perma: (m? %t) req: %s tok %s; port %s" , managed , u .String (), tokst , port )
21562209 }
21572210
21582211 res , err := h .Do (req )
21592212 if err != nil || res == nil {
2160- return nil , log .EE ("ws: conf: perma: do err (nil? %t / tok? %s): %v" , res == nil , tokst , err )
2213+ return nil , log .EE ("ws: conf: perma: (m? %t) do err (nil? %t / tok? %s): %v" , managed , res == nil , tokst , err )
21612214 }
21622215 defer core .Close (res .Body )
21632216 updateDidTokenIfNeeded (ent , res )
@@ -2171,11 +2224,11 @@ func createPermaCreds(h *http.Client, ent *WsEntitlement, bearer, pubkey string)
21712224 return nil , err
21722225 }
21732226 if out .Data .Success != 1 {
2174- return nil , log .EE ("ws: conf: perma: success != 1; tok? %s; debug: %v" , tokst , out .Data .Debug )
2227+ return nil , log .EE ("ws: conf: perma: (m? %t) success != 1; tok? %s; debug: %v" , managed , tokst , out .Data .Debug )
21752228 }
21762229
21772230 cfg := out .Data .Config
2178- log .I ("ws: conf: perma: ok (tok? %s); pubkey: %s" , tokst , trunc8 (cfg .PublicKey ))
2231+ log .I ("ws: conf: perma: ok (m? %t / tok? %s); pubkey: %s" , managed , tokst , trunc8 (cfg .PublicKey ))
21792232 return & cfg , nil
21802233}
21812234
0 commit comments