mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[feat] add Android trusted device authentication
This commit is contained in:
1 parent
e6e72ed511
commit
94a5f57bea
5 files changed
+91
-6
No files matched your search
@@ -397,6 +397,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
toggle.setOnCheckedChangeListener((button,checked)->{inputs.setVisibility(checked?View.VISIBLE:View.GONE);if(!checked){host="api.crossdesk.cn";port=9099;preferences.edit().putString("host",host).putInt("port",port).apply();ensureSignaling();} });
|
||||
backAction=()->{if(toggle.isChecked()&&!saveServer(address,number))return;hideKeyboard();home("");};
|
||||
identitySummary=text("",12,false);identitySummary.setTextColor(SECONDARY);identitySummary.setGravity(Gravity.CENTER);identitySummary.setPadding(dp(16),dp(12),dp(16),dp(16));identitySummary.setTextIsSelectable(true);content.addView(identitySummary);renderSignaling();
|
||||
LinearLayout trust=section(content,L10n.tr("本机配对信息"),MobileUi.SETTINGS_CARD_RADIUS);
|
||||
link(trust,L10n.tr("查看指纹"),"",this::showPairingInfo);ui.gap(content,28);
|
||||
LinearLayout privacy=section(content,L10n.tr("隐私"),MobileUi.SETTINGS_CARD_RADIUS);link(privacy,L10n.tr("隐私与授权"),preferences.getBoolean("networkConsent",false)?L10n.tr("已授权"):L10n.tr("未授权"),this::privacyPage);ui.divider(privacy);
|
||||
LinearLayout preview=row();preview.setPadding(dp(16),dp(10),dp(16),dp(10));preview.addView(text(L10n.tr("保存远程画面预览"),16,false),new LinearLayout.LayoutParams(0,-2,1));previewSwitch=new Switch(this);previewSwitch.setContentDescription(L10n.tr("保存远程画面预览"));preview.addView(previewSwitch);privacy.addView(preview);
|
||||
previewSwitch.setOnCheckedChangeListener((button,checked)->{if(updatingPreviewSwitch)return;if(checked){renderPreviews();confirmPreviewSaving();}else previews.setEnabled(false);});
|
||||
@@ -406,6 +408,21 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
||||
aboutUpdateLabel=(TextView)aboutLink.getChildAt(1);aboutUpdateLabel.setPadding(0,0,dp(8),0);
|
||||
aboutUpdateDot=updateDot();LinearLayout.LayoutParams dotPosition=new LinearLayout.LayoutParams(dp(8),dp(8));dotPosition.setMarginEnd(dp(8));aboutLink.addView(aboutUpdateDot,2,dotPosition);renderAppUpdates();
|
||||
}
|
||||
private void showPairingInfo(){
|
||||
NativeSession.pairingInfo(this,localIdentity,info->{
|
||||
if(isFinishing()||isDestroyed()||!page.equals("settings"))return;
|
||||
if(info==null){toast(L10n.tr("安全存储不可用,请解锁设备后重试"));return;}
|
||||
TextView fingerprint=text(info.optString("fingerprint").replaceAll("(.{4})(?!$)","$1 "),14,false);
|
||||
fingerprint.setTypeface(android.graphics.Typeface.MONOSPACE);fingerprint.setTextIsSelectable(true);fingerprint.setPadding(dp(24),dp(16),dp(24),dp(16));
|
||||
new AlertDialog.Builder(this).setTitle(L10n.tr("本机配对信息"))
|
||||
.setMessage(L10n.tr("将配对信息添加到被控端的可信设备列表,并核对指纹。"))
|
||||
.setView(fingerprint).setNegativeButton(L10n.tr("取消"),null)
|
||||
.setPositiveButton(L10n.tr("复制配对信息"),(dialog,which)->{
|
||||
try{((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("CrossDesk",info.getString("pairing")));toast(L10n.tr("配对信息已复制。"));}
|
||||
catch(Exception error){toast(L10n.tr("无法复制配对信息,请重试。"));}
|
||||
}).show();
|
||||
});
|
||||
}
|
||||
private void confirmPreviewSaving(){
|
||||
previewDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("保存远程画面预览?"))
|
||||
.setMessage(L10n.tr("预览可能包含远程电脑上的个人或工作信息。开启后,每次连接会自动将一张画面保存在本机并显示在最近连接中。你可以随时关闭并清除。"))
|
||||
|
||||
@@ -94,7 +94,7 @@ final class NativeSession implements AutoCloseable {
|
||||
File certificates = exportTrust();
|
||||
File logs = new File(context.getNoBackupFilesDir(), "logs");
|
||||
if (!logs.isDirectory() && !logs.mkdirs()) throw new IllegalStateException("Log directory unavailable");
|
||||
handle = nCreate(this, host, port, identity, logs.getAbsolutePath(), certificates.getAbsolutePath());
|
||||
handle = nCreate(this, host, port, identity, logs.getAbsolutePath(), certificates.getAbsolutePath(), trustKey(context));
|
||||
if (handle == 0) { fail(L10n.tr("原生连接初始化失败")); return; }
|
||||
nFileDirectory(handle,new File(documents.directory,"received").getAbsolutePath());
|
||||
if (surface != null && surface.isValid()) nSurface(handle, surface);
|
||||
@@ -116,6 +116,33 @@ final class NativeSession implements AutoCloseable {
|
||||
});
|
||||
}
|
||||
boolean isClosed() { return closed; }
|
||||
// Called only on RTC, shared by every foreground/background session and UI export.
|
||||
// This key belongs to the installation, not to a signaling server or login ID.
|
||||
private static String trustKey(Context context) throws Exception {
|
||||
SecretStore store = new SecretStore(context);
|
||||
var preferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE);
|
||||
String seed = store.get("device-trust:v1");
|
||||
if (seed.isEmpty()) {
|
||||
if (preferences.getBoolean("deviceTrustInitialized", false))
|
||||
throw new IllegalStateException("Device signing identity missing");
|
||||
seed = nGenerateTrustKey();
|
||||
if (seed == null) throw new IllegalStateException("Device signing identity unavailable");
|
||||
store.put("device-trust:v1", seed);
|
||||
}
|
||||
if (!preferences.edit().putBoolean("deviceTrustInitialized", true).commit())
|
||||
throw new IllegalStateException("Device signing identity marker unavailable");
|
||||
return seed;
|
||||
}
|
||||
static void pairingInfo(Context context, String deviceId, java.util.function.Consumer<JSONObject> completed) {
|
||||
Context app = context.getApplicationContext();
|
||||
RTC.execute(() -> {
|
||||
JSONObject info = null;
|
||||
try { info = new JSONObject(nTrustInfo(trustKey(app), deviceId.split("@", 2)[0])); }
|
||||
catch (Exception ignored) { }
|
||||
JSONObject result = info;
|
||||
new Handler(Looper.getMainLooper()).post(() -> completed.accept(result));
|
||||
});
|
||||
}
|
||||
// UI callbacks run on the main thread. A service-owned state can replace
|
||||
// the Activity listener without recreating either native peer.
|
||||
void setListener(Listener value) { listener = java.util.Objects.requireNonNull(value); }
|
||||
@@ -347,7 +374,9 @@ final class NativeSession implements AutoCloseable {
|
||||
static native boolean nValidAppVersion(String version);
|
||||
static native boolean nHasAppUpdate(String version, byte[] releaseJson);
|
||||
static native String nCheckMobileUpdate(String version, byte[] releaseJson);
|
||||
private static native long nCreate(NativeSession owner, String host, int port, String identity, String logs, String roots);
|
||||
private static native long nCreate(NativeSession owner, String host, int port, String identity, String logs, String roots, String trustKey);
|
||||
private static native String nGenerateTrustKey();
|
||||
private static native String nTrustInfo(String seed, String deviceId);
|
||||
private static native boolean nConnect(long handle, String identity, String remote, String password);
|
||||
private static native void nReady(long handle,byte[] displayResolutions);
|
||||
private static native void nPointer(long handle, float x, float y, int flag, int wheel);
|
||||
|
||||
Reference in new issue
Block a user