Skip to content

Commit 44804b0

Browse files
committed
android,libtailscale: allow toggling HW attestation via MDM
Previously hardware attestation was enabled on all supported devices. We now gate this functionality behind an MDM setting (whose default value is true) to allow disabling this in deployments where it might cause issues. Updates tailscale/corp#31269 OSS and Version updated to 1.89.254-t005e264b5-g0b32dd75c Signed-off-by: Jonathan Nobels <[email protected]> Signed-off-by: Patrick O'Doherty <[email protected]>
1 parent 0b32dd7 commit 44804b0

File tree

11 files changed

+48
-18
lines changed

11 files changed

+48
-18
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,12 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
150150
private fun initializeApp() {
151151
// Check if a directory URI has already been stored.
152152
val storedUri = getStoredDirectoryUri()
153+
val rm = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
154+
val hardwareAttestation = rm.applicationRestrictions.getBoolean(MDMSettings.KEY_HARDWARE_ATTESTATION, false)
153155
if (storedUri != null && storedUri.toString().startsWith("content://")) {
154-
startLibtailscale(storedUri.toString())
156+
startLibtailscale(storedUri.toString(), hardwareAttestation)
155157
} else {
156-
startLibtailscale(this.filesDir.absolutePath)
158+
startLibtailscale(this.filesDir.absolutePath, hardwareAttestation)
157159
}
158160
healthNotifier = HealthNotifier(Notifier.health, Notifier.state, applicationScope)
159161
connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -202,8 +204,8 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
202204
* Called when a SAF directory URI is available (either already stored or chosen). We must restart
203205
* Tailscale because directFileRoot must be set before LocalBackend starts being used.
204206
*/
205-
fun startLibtailscale(directFileRoot: String) {
206-
app = Libtailscale.start(this.filesDir.absolutePath, directFileRoot, this)
207+
fun startLibtailscale(directFileRoot: String, hardwareAttestation: Boolean) {
208+
app = Libtailscale.start(this.filesDir.absolutePath, directFileRoot, hardwareAttestation, this)
207209
ShareFileHelper.init(this, app, directFileRoot, applicationScope)
208210
Request.setApp(app)
209211
Notifier.setApp(app)

android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ object MDMSettings {
1818
// to the backend.
1919
class NoSuchKeyException : Exception("no such key")
2020

21+
// MDM restriction keys
22+
const val KEY_HARDWARE_ATTESTATION = "HardwareAttestation"
23+
2124
val forceEnabled = BooleanMDMSetting("ForceEnabled", "Force Enabled Connection Toggle")
2225

2326
// Handled on the backed
@@ -115,6 +118,11 @@ object MDMSettings {
115118
.map { it.call(MDMSettings) as MDMSetting<*> }
116119
}
117120

121+
val hardwareAttestation = BooleanMDMSetting(
122+
KEY_HARDWARE_ATTESTATION,
123+
"Use hardware-backed keys to bind node identity to the device",
124+
)
125+
118126
val allSettingsByKey by lazy { allSettings.associateBy { it.key } }
119127

120128
fun update(app: App, restrictionsManager: RestrictionsManager?) {

android/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,8 @@
360360
<string name="taildrop_directory_picker_info">What is taildrop?</string>
361361
<string name="taildrop_directory_picker_button">Open Directory Picker</string>
362362

363+
<!-- Strings for Hardware Attestation MDM setting -->
364+
<string name="enable_hardware_attestation">Enable hardware attestation</string>
365+
<string name="use_hardware_backed_keys_to_bind_node_identity_to_the_device">Use hardware-backed keys to bind node identity to the device</string>
366+
363367
</resources>

android/src/main/res/xml/app_restrictions.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,11 @@
148148
android:key="OnboardingFlow"
149149
android:restrictionType="choice"
150150
android:title="@string/onboarding_flow" />
151-
</restrictions>
151+
152+
<restriction
153+
android:defaultValue="true"
154+
android:description="@string/use_hardware_backed_keys_to_bind_node_identity_to_the_device"
155+
android:key="HardwareAttestation"
156+
android:restrictionType="bool"
157+
android:title="@string/enable_hardware_attestation" />
158+
</restrictions>

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.25.1
3+
go 1.25.2
44

55
require (
66
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da
77
golang.org/x/mobile v0.0.0-20240806205939-81131f6468ab
8-
tailscale.com v1.89.0-pre.0.20250929162250-7bcab4ab2841
8+
tailscale.com v1.89.0-pre.0.20251010193330-005e264b5456
99
)
1010

1111
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
235235
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
236236
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
237237
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
238-
tailscale.com v1.89.0-pre.0.20250929162250-7bcab4ab2841 h1:BfBXlsl/ffzlJoTCQL78hVmGGdRm//h/75lKIWOX79o=
239-
tailscale.com v1.89.0-pre.0.20250929162250-7bcab4ab2841/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw=
238+
tailscale.com v1.89.0-pre.0.20251010193330-005e264b5456 h1:ELfWhOfTpC6wEHvD74NUwhvwQtGaR+fSmU7ldTTgBzU=
239+
tailscale.com v1.89.0-pre.0.20251010193330-005e264b5456/go.mod h1:gsjhGL2raodX0jQJ6uTD5dWJmc1DFtf5nQ1MRpzCReU=

go.toolchain.rev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
aa85d1541af0921f830f053f29d91971fa5838f6
1+
a80a86e575c5b7b23b78540e947335d22f74d274

libtailscale/backend.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type App struct {
6060
backendMu sync.Mutex
6161
}
6262

63-
func start(dataDir, directFileRoot string, appCtx AppContext) Application {
63+
func start(dataDir, directFileRoot string, hwAttestationPref bool, appCtx AppContext) Application {
6464
defer func() {
6565
if p := recover(); p != nil {
6666
log.Printf("panic in Start %s: %s", p, debug.Stack())
@@ -84,7 +84,7 @@ func start(dataDir, directFileRoot string, appCtx AppContext) Application {
8484
os.Setenv("HOME", dataDir)
8585
}
8686

87-
return newApp(dataDir, directFileRoot, appCtx)
87+
return newApp(dataDir, directFileRoot, hwAttestationPref, appCtx)
8888
}
8989

9090
type backend struct {
@@ -111,7 +111,7 @@ type backend struct {
111111

112112
type settingsFunc func(*router.Config, *dns.OSConfig) error
113113

114-
func (a *App) runBackend(ctx context.Context) error {
114+
func (a *App) runBackend(ctx context.Context, hardwareAttestation bool) error {
115115
paths.AppSharedDir.Store(a.dataDir)
116116
hostinfo.SetOSVersion(a.osVersion())
117117
hostinfo.SetPackage(a.appCtx.GetInstallSource())
@@ -139,6 +139,9 @@ func (a *App) runBackend(ctx context.Context) error {
139139
}
140140
a.logIDPublicAtomic.Store(&b.logIDPublic)
141141
a.backend = b.backend
142+
if hardwareAttestation {
143+
a.backend.SetHardwareAttested()
144+
}
142145
defer b.CloseTUNs()
143146

144147
hc := localapi.HandlerConfig{

libtailscale/interfaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111

1212
// Start starts the application, storing state in the given dataDir and using
1313
// the given appCtx.
14-
func Start(dataDir, directFileRoot string, appCtx AppContext) Application {
15-
return start(dataDir, directFileRoot, appCtx)
14+
func Start(dataDir, directFileRoot string, hwAttestationPref bool, appCtx AppContext) Application {
15+
return start(dataDir, directFileRoot, hwAttestationPref, appCtx)
1616
}
1717

1818
// AppContext provides a context within which the Application is running. This

libtailscale/keystore.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,7 @@ func (k *hardwareAttestationKey) Close() error {
9393
func (k *hardwareAttestationKey) Clone() key.HardwareAttestationKey {
9494
return &hardwareAttestationKey{appCtx: k.appCtx, id: k.id, public: k.public}
9595
}
96+
97+
func (k* hardwareAttestationKey) IsZero() bool {
98+
return k.id == ""
99+
}

0 commit comments

Comments
 (0)