diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt
index e8851d4..7a6088c 100644
--- a/android/src/main/java/com/tailscale/ipn/App.kt
+++ b/android/src/main/java/com/tailscale/ipn/App.kt
@@ -150,10 +150,12 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
private fun initializeApp() {
// Check if a directory URI has already been stored.
val storedUri = getStoredDirectoryUri()
+ val rm = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
+ val hardwareAttestation = rm.applicationRestrictions.getBoolean(MDMSettings.KEY_HARDWARE_ATTESTATION, false)
if (storedUri != null && storedUri.toString().startsWith("content://")) {
- startLibtailscale(storedUri.toString())
+ startLibtailscale(storedUri.toString(), hardwareAttestation)
} else {
- startLibtailscale(this.filesDir.absolutePath)
+ startLibtailscale(this.filesDir.absolutePath, hardwareAttestation)
}
healthNotifier = HealthNotifier(Notifier.health, Notifier.state, applicationScope)
connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -202,8 +204,8 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
* Called when a SAF directory URI is available (either already stored or chosen). We must restart
* Tailscale because directFileRoot must be set before LocalBackend starts being used.
*/
- fun startLibtailscale(directFileRoot: String) {
- app = Libtailscale.start(this.filesDir.absolutePath, directFileRoot, this)
+ fun startLibtailscale(directFileRoot: String, hardwareAttestation: Boolean) {
+ app = Libtailscale.start(this.filesDir.absolutePath, directFileRoot, hardwareAttestation, this)
ShareFileHelper.init(this, app, directFileRoot, applicationScope)
Request.setApp(app)
Notifier.setApp(app)
diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt
index 34b341f..096e99b 100644
--- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt
+++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt
@@ -18,6 +18,9 @@ object MDMSettings {
// to the backend.
class NoSuchKeyException : Exception("no such key")
+ // MDM restriction keys
+ const val KEY_HARDWARE_ATTESTATION = "HardwareAttestation"
+
val forceEnabled = BooleanMDMSetting("ForceEnabled", "Force Enabled Connection Toggle")
// Handled on the backed
@@ -115,6 +118,11 @@ object MDMSettings {
.map { it.call(MDMSettings) as MDMSetting<*> }
}
+ val hardwareAttestation = BooleanMDMSetting(
+ KEY_HARDWARE_ATTESTATION,
+ "Use hardware-backed keys to bind node identity to the device",
+ )
+
val allSettingsByKey by lazy { allSettings.associateBy { it.key } }
fun update(app: App, restrictionsManager: RestrictionsManager?) {
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
index 97d7edc..001774a 100644
--- a/android/src/main/res/values/strings.xml
+++ b/android/src/main/res/values/strings.xml
@@ -360,4 +360,8 @@
What is taildrop?
Open Directory Picker
+
+ Enable hardware attestation
+ Use hardware-backed keys to bind node identity to the device
+
diff --git a/android/src/main/res/xml/app_restrictions.xml b/android/src/main/res/xml/app_restrictions.xml
index b47cc58..b313f78 100644
--- a/android/src/main/res/xml/app_restrictions.xml
+++ b/android/src/main/res/xml/app_restrictions.xml
@@ -148,4 +148,11 @@
android:key="OnboardingFlow"
android:restrictionType="choice"
android:title="@string/onboarding_flow" />
-
\ No newline at end of file
+
+
+
diff --git a/libtailscale/backend.go b/libtailscale/backend.go
index e7cbb78..864136c 100644
--- a/libtailscale/backend.go
+++ b/libtailscale/backend.go
@@ -60,7 +60,7 @@ type App struct {
backendMu sync.Mutex
}
-func start(dataDir, directFileRoot string, appCtx AppContext) Application {
+func start(dataDir, directFileRoot string, hwAttestationPref bool, appCtx AppContext) Application {
defer func() {
if p := recover(); p != nil {
log.Printf("panic in Start %s: %s", p, debug.Stack())
@@ -84,7 +84,7 @@ func start(dataDir, directFileRoot string, appCtx AppContext) Application {
os.Setenv("HOME", dataDir)
}
- return newApp(dataDir, directFileRoot, appCtx)
+ return newApp(dataDir, directFileRoot, hwAttestationPref, appCtx)
}
type backend struct {
@@ -111,7 +111,7 @@ type backend struct {
type settingsFunc func(*router.Config, *dns.OSConfig) error
-func (a *App) runBackend(ctx context.Context) error {
+func (a *App) runBackend(ctx context.Context, hardwareAttestation bool) error {
paths.AppSharedDir.Store(a.dataDir)
hostinfo.SetOSVersion(a.osVersion())
hostinfo.SetPackage(a.appCtx.GetInstallSource())
@@ -139,6 +139,9 @@ func (a *App) runBackend(ctx context.Context) error {
}
a.logIDPublicAtomic.Store(&b.logIDPublic)
a.backend = b.backend
+ if hardwareAttestation {
+ a.backend.SetHardwareAttested()
+ }
defer b.CloseTUNs()
hc := localapi.HandlerConfig{
diff --git a/libtailscale/interfaces.go b/libtailscale/interfaces.go
index 14c5694..7155f06 100644
--- a/libtailscale/interfaces.go
+++ b/libtailscale/interfaces.go
@@ -11,8 +11,8 @@ import (
// Start starts the application, storing state in the given dataDir and using
// the given appCtx.
-func Start(dataDir, directFileRoot string, appCtx AppContext) Application {
- return start(dataDir, directFileRoot, appCtx)
+func Start(dataDir, directFileRoot string, hwAttestationPref bool, appCtx AppContext) Application {
+ return start(dataDir, directFileRoot, hwAttestationPref, appCtx)
}
// AppContext provides a context within which the Application is running. This
diff --git a/libtailscale/keystore.go b/libtailscale/keystore.go
index b803de9..54498cf 100644
--- a/libtailscale/keystore.go
+++ b/libtailscale/keystore.go
@@ -93,3 +93,7 @@ func (k *hardwareAttestationKey) Close() error {
func (k *hardwareAttestationKey) Clone() key.HardwareAttestationKey {
return &hardwareAttestationKey{appCtx: k.appCtx, id: k.id, public: k.public}
}
+
+func (k* hardwareAttestationKey) IsZero() bool {
+ return k.id == ""
+}
diff --git a/libtailscale/tailscale.go b/libtailscale/tailscale.go
index 76dc979..63b4032 100644
--- a/libtailscale/tailscale.go
+++ b/libtailscale/tailscale.go
@@ -32,7 +32,7 @@ const (
customLoginServerPrefKey = "customloginserver"
)
-func newApp(dataDir, directFileRoot string, appCtx AppContext) Application {
+func newApp(dataDir, directFileRoot string, hardwareAttestationPref bool, appCtx AppContext) Application {
a := &App{
directFileRoot: directFileRoot,
dataDir: dataDir,
@@ -44,7 +44,9 @@ func newApp(dataDir, directFileRoot string, appCtx AppContext) Application {
a.policyStore = &syspolicyStore{a: a}
netmon.RegisterInterfaceGetter(a.getInterfaces)
rsop.RegisterStore("DeviceHandler", setting.DeviceScope, a.policyStore)
- if appCtx.HardwareAttestationKeySupported() {
+
+ hwAttestEnabled := appCtx.HardwareAttestationKeySupported() && hardwareAttestationPref
+ if hwAttestEnabled {
key.RegisterHardwareAttestationKeyFns(
func() key.HardwareAttestationKey { return emptyHardwareAttestationKey(appCtx) },
func() (key.HardwareAttestationKey, error) { return createHardwareAttestationKey(appCtx) },
@@ -63,7 +65,7 @@ func newApp(dataDir, directFileRoot string, appCtx AppContext) Application {
}()
ctx := context.Background()
- if err := a.runBackend(ctx); err != nil {
+ if err := a.runBackend(ctx, hwAttestEnabled); err != nil {
fatalErr(err)
}
}()