Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions core/src/main/cpp/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,73 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeSetAgeSecretKey(JNIEnv *env
setAgeSecretKey(_key);
}

JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeGenX25519KeyPair(JNIEnv *env, jobject thiz) {
TRACE_METHOD();

scoped_string response = genX25519KeyPair();

if (response == NULL)
return NULL;

return new_string(response);
}

JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeGenHybridKeyPair(JNIEnv *env, jobject thiz) {
TRACE_METHOD();

scoped_string response = genHybridKeyPair();

if (response == NULL)
return NULL;

return new_string(response);
}

JNIEXPORT jboolean JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeVeritySecretKeys(JNIEnv *env, jobject thiz,
jstring secret_keys) {
TRACE_METHOD();

if (secret_keys == NULL)
return 0;

scoped_string _secret_keys = get_string(secret_keys);

return (jboolean) veritySecretKeys(_secret_keys);
}

JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeToPublicKeys(JNIEnv *env, jobject thiz,
jstring secret_keys) {
TRACE_METHOD();

if (secret_keys == NULL)
return NULL;

scoped_string _secret_keys = get_string(secret_keys);
scoped_string response = toPublicKeys(_secret_keys);

if (response == NULL)
return NULL;

return new_string(response);
}

JNIEXPORT jboolean JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeVerityPublicKeys(JNIEnv *env, jobject thiz,
jstring public_keys) {
TRACE_METHOD();

if (public_keys == NULL)
return 0;

scoped_string _public_keys = get_string(public_keys);

return (jboolean) verityPublicKeys(_public_keys);
}

JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryProviders(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
Expand Down
53 changes: 53 additions & 0 deletions core/src/main/golang/native/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func (r *remoteValidCallback) reportStatus(json string) {
C.fetch_report(r.callback, marshalString(json))
}

type ageKeyPair struct {
SecretKey string `json:"secretKey"`
PublicKey string `json:"publicKey"`
}

//export fetchAndValid
func fetchAndValid(callback unsafe.Pointer, path, url C.c_string, force C.int) {
go func(path, url string, callback unsafe.Pointer) {
Expand Down Expand Up @@ -71,3 +76,51 @@ func setAgeSecretKey(key C.c_string) {
k := C.GoString(key)
config.SetGlobalSecretKeys(k)
}

//export genX25519KeyPair
func genX25519KeyPair() *C.char {
secretKey, publicKey, err := config.GenX25519KeyPair()
if err != nil {
return nil
}

return marshalJson(ageKeyPair{SecretKey: secretKey, PublicKey: publicKey})
}

//export genHybridKeyPair
func genHybridKeyPair() *C.char {
secretKey, publicKey, err := config.GenHybridKeyPair()
if err != nil {
return nil
}

return marshalJson(ageKeyPair{SecretKey: secretKey, PublicKey: publicKey})
}

//export veritySecretKeys
func veritySecretKeys(secretKeys C.c_string) C.int {
if config.VeritySecretKeys(C.GoString(secretKeys)) != nil {
return 0
}

return 1
}

//export toPublicKeys
func toPublicKeys(secretKeys C.c_string) *C.char {
publicKeys, err := config.ToPublicKeys(C.GoString(secretKeys))
if err != nil {
return nil
}

return marshalJson(publicKeys)
}

//export verityPublicKeys
func verityPublicKeys(publicKeys C.c_string) C.int {
if config.VerityPublicKeys(C.GoString(publicKeys)) != nil {
return 0
}

return 1
}
20 changes: 20 additions & 0 deletions core/src/main/golang/native/config/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,23 @@ import "github.com/metacubex/mihomo/component/age"
func SetGlobalSecretKeys(secretKeys ...string) {
age.SetGlobalSecretKeys(secretKeys...)
}

func GenX25519KeyPair() (secretKey string, publicKey string, err error) {
return age.GenX25519KeyPair()
}

func GenHybridKeyPair() (secretKey string, publicKey string, err error) {
return age.GenHybridKeyPair()
}

func ToPublicKeys(secretKeys ...string) (publicKeys []string, err error) {
return age.ToPublicKeys(secretKeys...)
}

func VeritySecretKeys(secretKeys ...string) error {
return age.VeritySecretKeys(secretKeys...)
}

func VerityPublicKeys(publicKeys ...string) error {
return age.VerityPublicKeys(publicKeys...)
}
28 changes: 28 additions & 0 deletions core/src/main/java/com/github/kr328/clash/core/Clash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.github.kr328.clash.core.util.parseInetSocketAddress
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonPrimitive
Expand Down Expand Up @@ -229,4 +231,30 @@ object Clash {
fun setAgeSecretKey(key: String?) {
Bridge.nativeSetAgeSecretKey(key)
}

fun genX25519KeyPair(): AgeKeyPair {
return parseAgeKeyPair(checkNotNull(Bridge.nativeGenX25519KeyPair()))
}

fun genHybridKeyPair(): AgeKeyPair {
return parseAgeKeyPair(checkNotNull(Bridge.nativeGenHybridKeyPair()))
}

fun veritySecretKeys(vararg secretKeys: String): Boolean {
return Bridge.nativeVeritySecretKeys(secretKeys.firstOrNull() ?: "")
}

fun toPublicKeys(vararg secretKeys: String): List<String> {
return Bridge.nativeToPublicKeys(secretKeys.firstOrNull() ?: "")
?.let { Json.Default.decodeFromString(ListSerializer(String.serializer()), it) }
?: emptyList()
}

fun verityPublicKeys(vararg publicKeys: String): Boolean {
return Bridge.nativeVerityPublicKeys(publicKeys.firstOrNull() ?: "")
}

private fun parseAgeKeyPair(value: String): AgeKeyPair {
return Json.Default.decodeFromString(AgeKeyPair.serializer(), value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ object Bridge {
external fun nativeCoreVersion(): String

external fun nativeSetAgeSecretKey(key: String?)
external fun nativeGenX25519KeyPair(): String?
external fun nativeGenHybridKeyPair(): String?
external fun nativeVeritySecretKeys(secretKeys: String): Boolean
external fun nativeToPublicKeys(secretKeys: String): String?
external fun nativeVerityPublicKeys(publicKeys: String): Boolean

private external fun nativeInit(home: String, versionName: String, sdkVersion: Int)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.kr328.clash.core.model

import kotlinx.serialization.Serializable

@Serializable
data class AgeKeyPair(
val secretKey: String,
val publicKey: String
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package com.github.kr328.clash.design

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.View
import androidx.core.content.getSystemService
import androidx.core.widget.doOnTextChanged
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.model.ConfigurationOverride
import com.github.kr328.clash.design.databinding.DesignSettingsMetaFeatureBinding
import com.github.kr328.clash.design.databinding.DialogAgeKeyHelperBinding
import com.github.kr328.clash.design.preference.*
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.design.util.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

Expand Down Expand Up @@ -63,6 +72,26 @@ class MetaFeatureSettingsDesign(
)

val screen = preferenceScreen(context) {
category(R.string.age_key_category)

clickable(
title = R.string.age_key_type_x25519,
summary = R.string.age_key_generate_summary,
) {
clicked {
requestAgeKeyHelper(hybrid = false)
}
}

clickable(
title = R.string.age_key_type_hybrid,
summary = R.string.age_key_generate_summary,
) {
clicked {
requestAgeKeyHelper(hybrid = true)
}
}

category(R.string.settings)

selectableList(
Expand Down Expand Up @@ -317,6 +346,75 @@ class MetaFeatureSettingsDesign(
binding.content.addView(screen.root)
}

private fun requestAgeKeyHelper(hybrid: Boolean) {
launch(Dispatchers.Main) {
val binding = DialogAgeKeyHelperBinding
.inflate(context.layoutInflater, context.root, false)
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(if (hybrid) R.string.age_key_type_hybrid else R.string.age_key_type_x25519)
.setView(binding.root)
.create()

fun copy(label: String, value: String) {
if (value.isBlank())
return

val data = ClipData.newPlainText(label, value)
context.getSystemService<ClipboardManager>()?.setPrimaryClip(data)

launch { showToast(R.string.copied, ToastDuration.Short) }
}

fun patchSecretKeyState() {
val secretKey = binding.secretKeyView.text?.toString() ?: ""
val valid = secretKey.isBlank() || Clash.veritySecretKeys(secretKey)

binding.secretKeyLayout.error = if (valid) null else context.getText(R.string.age_secret_key_error)
}

fun patchPublicKeyState() {
val publicKey = binding.publicKeyView.text?.toString() ?: ""
val valid = publicKey.isBlank() || Clash.verityPublicKeys(publicKey)

binding.publicKeyLayout.error = if (valid) null else context.getText(R.string.age_public_key_error)
}

dialog.setOnShowListener {
binding.secretKeyView.doOnTextChanged { _, _, _, _ -> patchSecretKeyState() }
binding.publicKeyView.doOnTextChanged { _, _, _, _ -> patchPublicKeyState() }

binding.generateView.setOnClickListener {
val keyPair = if (hybrid) {
Clash.genHybridKeyPair()
} else {
Clash.genX25519KeyPair()
}

binding.secretKeyView.setText(keyPair.secretKey)
binding.publicKeyView.setText(keyPair.publicKey)
}

binding.toPublicKeyView.setOnClickListener {
val publicKey = Clash.toPublicKeys(binding.secretKeyView.text?.toString() ?: "")
.firstOrNull()
?: ""

binding.publicKeyView.setText(publicKey)
}

binding.copySecretKeyView.setOnClickListener {
copy("age_secret_key", binding.secretKeyView.text?.toString() ?: "")
}

binding.copyPublicKeyView.setOnClickListener {
copy("age_public_key", binding.publicKeyView.text?.toString() ?: "")
}
}

dialog.show()
}
}

fun requestClear() {
requests.trySend(Request.ResetOverride)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fun PreferenceScreen.clickable(
get() = binding.iconView.background
set(value) {
binding.iconView.background = value
binding.iconView.visibility = if (value == null) View.GONE else View.VISIBLE
}
override var title: CharSequence
get() = binding.titleView.text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.kr328.clash.design.util

import com.github.kr328.clash.common.util.PatternFileName
import com.github.kr328.clash.core.Clash

typealias Validator = (String) -> Boolean

Expand All @@ -25,5 +26,5 @@ val ValidatorAutoUpdateInterval: Validator = {
}

val ValidatorAgeSecretKey: Validator = {
it.isEmpty() || it.startsWith("AGE-SECRET-KEY-", ignoreCase = true)
it.isEmpty() || Clash.veritySecretKeys(it)
}
Loading
Loading