Skip to content

Commit e7fca1a

Browse files
LoganRupebradfitz
authored andcommitted
libtailscale,android: use ExtraRootCAs for user-installed CA certificates
Bridge user-installed Android CA certificates to Go's TLS stack using the in-memory ExtraRootCAs cert pool on tsd.System. Depends on tailscale/tailscale#19280. Fixes tailscale/tailscale#8085 Signed-off-by: Logan Rupe <logan@coldtap.io>
1 parent 2794bb4 commit e7fca1a

File tree

6 files changed

+44
-5
lines changed

6 files changed

+44
-5
lines changed

android/src/main/java/com/tailscale/ipn/App.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,22 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
476476
false
477477
}
478478
}
479+
480+
override fun getUserCACertsPEM(): ByteArray {
481+
val ks = java.security.KeyStore.getInstance("AndroidCAStore")
482+
ks.load(null)
483+
val sb = StringBuilder()
484+
val encoder = android.util.Base64.NO_WRAP
485+
for (alias in ks.aliases()) {
486+
if (!alias.startsWith("user:")) continue
487+
val cert = ks.getCertificate(alias) ?: continue
488+
val pem = android.util.Base64.encodeToString(cert.encoded, encoder)
489+
sb.append("-----BEGIN CERTIFICATE-----\n")
490+
pem.chunked(64).forEach { sb.append(it).append('\n') }
491+
sb.append("-----END CERTIFICATE-----\n")
492+
}
493+
return sb.toString().toByteArray(Charsets.UTF_8)
494+
}
479495
}
480496

481497
/**

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module github.com/tailscale/tailscale-android
22

3-
go 1.26.1
3+
go 1.26.2
44

55
require (
66
github.com/tailscale/wireguard-go v0.0.0-20260304043104-4184faf59e56
77
golang.org/x/mobile v0.0.0-20240806205939-81131f6468ab
8-
tailscale.com v1.97.0-pre.0.20260401194235-5b62f9889451
8+
tailscale.com v1.97.0-pre.0.20260408011054-a182b864ace4
99
)
1010

1111
require (

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4
159159
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
160160
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
161161
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
162+
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89 h1:glgVc1ZYMjwN1Q/ITWeuSQyl029uayagaR2sjsifehc=
163+
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89/go.mod h1:wn16Km1EZOX4UEAyaZa3dBwfFGOJ7neck40NcwosJUw=
162164
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
163165
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
164166
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
@@ -245,5 +247,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
245247
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
246248
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
247249
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
248-
tailscale.com v1.97.0-pre.0.20260401194235-5b62f9889451 h1:8vwLyeDR0eVjLvl9n54SSOwmJKOsHtwDjp3itkHTMYI=
249-
tailscale.com v1.97.0-pre.0.20260401194235-5b62f9889451/go.mod h1:uWl7oasAISX6cMzTGBNT7TmjKui8W3IU3qrDN/A2PFo=
250+
tailscale.com v1.97.0-pre.0.20260408011054-a182b864ace4 h1:EbBRCAjxEWU/61I/DDqMg4aMKw0uqYCtt8diNQ3DQjI=
251+
tailscale.com v1.97.0-pre.0.20260408011054-a182b864ace4/go.mod h1:J3yUifgjBmMBIylCvle8qVui/MDlsjP6aRPsZ9pjfUY=

go.toolchain.rev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
179b46cade24e44f53b53a3cbcc7b7eb78469c31
1+
dfe2a5fd8ee2e68b08ce5ff259269f50ecadf2f4

libtailscale/backend.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package libtailscale
55

66
import (
77
"context"
8+
"crypto/x509"
89
"errors"
910
"fmt"
1011
"log"
@@ -281,6 +282,22 @@ func (a *App) newBackend(dataDir string, appCtx AppContext, store *stateStore,
281282
sys := tsd.NewSystem()
282283
sys.Set(store)
283284

285+
if pemData, err := appCtx.GetUserCACertsPEM(); err != nil {
286+
log.Printf("GetUserCACertsPEM: %v", err)
287+
} else if len(pemData) > 0 {
288+
pool, err := x509.SystemCertPool()
289+
if err != nil {
290+
log.Printf("x509.SystemCertPool: %v; using empty pool", err)
291+
pool = x509.NewCertPool()
292+
}
293+
if pool.AppendCertsFromPEM(pemData) {
294+
sys.ExtraRootCAs = pool
295+
log.Printf("loaded user CA certificates into ExtraRootCAs")
296+
} else {
297+
log.Printf("failed to parse any user CA certificates from PEM data")
298+
}
299+
}
300+
284301
logf := logger.RusagePrefixLog(log.Printf)
285302
b := &backend{
286303
devices: newTUNDevices(),

libtailscale/interfaces.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ type AppContext interface {
7676
HardwareAttestationKeyLoad(id string) error
7777

7878
BindSocketToNetwork(fd int32) bool
79+
80+
// GetUserCACertsPEM returns PEM-encoded user-installed CA certificates
81+
// from the Android KeyStore, or empty bytes if none are installed.
82+
GetUserCACertsPEM() ([]byte, error)
7983
}
8084

8185
// IPNService corresponds to our IPNService in Java.

0 commit comments

Comments
 (0)