3 Commits
Author SHA1 Message Date
Bluemangoo 13e296f106 1.6 2026-11-01 20:18:13 +08:00
Bluemangoo 0f3a225516 请求host不匹配时重新生成证书 2026-11-01 12:39:44 +08:00
Bluemangoo 5b0cf965bd fix 没启用的mixin不再应用 2026-11-01 11:54:55 +08:00
8 changed files with 241 additions and 183 deletions
+2 -2
View File
@@ -18,8 +18,8 @@ android {
applicationId = "net.bluemangoo.indpjsk" applicationId = "net.bluemangoo.indpjsk"
minSdk = appMinSdk minSdk = appMinSdk
targetSdk = 37 targetSdk = 37
versionCode = 6 versionCode = 7
versionName = "1.5" versionName = "1.6"
} }
@@ -66,7 +66,8 @@ object MixinProfiles {
private fun collect(): LinkedHashMap<String, ProfileAcc> { private fun collect(): LinkedHashMap<String, ProfileAcc> {
val profiles = linkedMapOf<String, ProfileAcc>() val profiles = linkedMapOf<String, ProfileAcc>()
for ((_, profile) in UploadStore.load()) { for ((enabled, profile) in UploadStore.load()) {
if (!enabled) continue
val custom = profile as? UploadProfileCustom ?: continue val custom = profile as? UploadProfileCustom ?: continue
val mixin = custom.mixin ?: continue val mixin = custom.mixin ?: continue
for ((name, steps) in mixin.profiles) { for ((name, steps) in mixin.profiles) {
+1 -1
View File
@@ -13,7 +13,7 @@ tokio = { version = "1.47.5", features = ["full"] }
tokio-rustls = "0.26" tokio-rustls = "0.26"
rustls-pemfile = "2" rustls-pemfile = "2"
jni = "0.22.4" jni = "0.22.4"
rcgen = "0.14" rcgen = { version = "0.14", features = ["x509-parser"] }
anyhow = "1" anyhow = "1"
bytes = "1" bytes = "1"
tokio-util = "0.7.20" tokio-util = "0.7.20"
+4 -147
View File
@@ -1,156 +1,13 @@
use rcgen::{
CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair,
KeyUsagePurpose, SanType,
};
use std::{env, fs, path::PathBuf}; use std::{env, fs, path::PathBuf};
#[path = "src/cert.rs"]
const CA_KEY: &str = "-----BEGIN PRIVATE KEY----- mod cert;
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAAAAAAAAAAAAAAAAAA use cert::*;
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAShZANiAAQTglHNUqyS
mMHIqtl3Mh3rl+cJvQtMoKylXcitUdz8nRWJoVl+OlEg4e/WMcY+GDXKyuKYaaYu
FjHoooGBq1ZhbcRdkYq8CfOrDmPPeSqk3O1zh743u6VpVJ8cArJw7Wc=
-----END PRIVATE KEY-----";
const CA_CHAIN: &str = "-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAShZANiAAQTglHNUqyS
mMHIqtl3Mh3rl+cJvQtMoKylXcitUdz8nRWJoVl+OlEg4e/WMcY+GDXKyuKYaaYu
FjHoooGBq1ZhbcRdkYq8CfOrDmPPeSqk3O1zh743u6VpVJ8cArJw7Wc=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ
BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3
NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ
fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+
N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f
BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c
NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79
4rSb9vGqxZzelQth
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdDCCAvmgAwIBAgIQAJYaPvsTWVdu0EAFtGhKWzAKBggqhkjOPQQDAzBrMQsw
CQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhy
YSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290
IENBIC0gRzMwHhcNMjQwNzExMTU0MzM2WhcNMzkwNzA5MTgzMDAwWjBWMQswCQYD
VQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAARfD9w/q1cWxL6D9bMGH6CQ4u+gAV6GN9s+PwQx9Bl4hC+0iU8QOk8m
WO/RwXQy10DCSxT8vSIJClHKp+Ao4qJKdYeV0SwZ7LMQziyLfxIUOY6Grja/sQFw
LKYf8EdDXAyjggF1MIIBcTAfBgNVHSMEGDAWgBR8XQKEE9TMipuBzhccLikenEhj
QjAdBgNVHQ4EFgQU4TqrIXzx401UrtRyt/2LWUIgkFQwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIB
FhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMHgGCCsGAQUFBwEBBGwwajAi
BggrBgEFBQcwAYYWaHR0cDovL29jc3AuZW1zaWduLmNvbTBEBggrBgEFBQcwAoY4
aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbS9jZXJ0cy9lbVNpZ25FQ0NSb290
Q0FHMy5jcnQwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5lbXNpZ24uY29t
P1Jvb3RDQUczLmNybDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB
ATAKBggqhkjOPQQDAwNpADBmAjEAt9CIpRGp46QHQYDUBnLKw+vvK0/h9vmilEAg
TroUl/anH4x2rXHqiCtBTBfgKDegAjEA8WpG1LTZdAIL0FSYqxXeREaeTSxEofM0
1jJH3jGvCXC79LU9UPJ2o3TU+UIj2gMV
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIRAI1aSBGTnvRH13ERzdmEmukwDQYJKoZIhvcNAQELBQAw
ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y
ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu
YzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE
ACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZfjpRIOHv
1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+N7ulaVSf
HAKycO1no4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw
HQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMA4GA1UdDwEB/wQEAwIBBjAS
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAxBgNVHSAEKjAoMAgGBmeBDAECAjANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO
IQECbjBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B
QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB
BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC
AQEAHlbhZpjhkPEAQVhPHRtBDaHqiisVD20pQ1TUeSx3l62Qe7NQ0b7vWQy7DN0i
KOu7BGpP5EnIjvXbz9FhpfPJ4FhKEt3tQ51rqXz2oD8vRaryvExWPobDUz3j7HN6
OzBsVMIe3PD7AAkpky0x8zMSi/ep5R9OMzybmNptbtppyuB5ytgq+h7lQdRIP+CQ
TaJX3qTgfMqzntYre5GljmEB5hU2/rSn8zoSU2APkJidlZFpghtgbD4I2VRHZvCw
FFr/SrFM9pwrgbJNZ1MtyTSVgI5NlNb0aMfLR+/r6jJt3ROeY2yh2QQ2dx+Yqgdb
uL5ChFWOQU+ErSDnUq7yr+pjBQ==
-----END CERTIFICATE-----";
/// 证书里要覆盖的域名。抓包目标 host 必须在这里面,否则 App 会报证书不受信任。
const SAN: &[&str] = &[
// 国服
"*.dailygn.com",
// 日服
"production-game-api.sekai.colorfulpalette.org",
"*.sekai.colorfulpalette.org",
// 国际服
"n-production-game-api.sekai-en.com",
"*.sekai-en.com",
// 台服 / 韩服(cdn 和 https 两个域都会出现)
"*.bytedgame.com",
"mk-zian-obt-cdn.bytedgame.com",
"mk-zian-obt-https.bytedgame.com",
"mkkorea-obt-prod01-cdn.bytedgame.com",
"mkkorea-obt-prod01-https.bytedgame.com",
];
fn main() { fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let ca_key = KeyPair::from_pem(CA_KEY).expect("parse CA key failed"); let (fullchain_pem, leaf_key_pem) = sign(SAN.iter());
let first_ca_pem = first_pem_certificate(CA_CHAIN);
let issuer = rcgen::Issuer::from_ca_cert_pem(&first_ca_pem, ca_key)
.expect("build Issuer from CA cert + key failed");
let mut leaf_params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, SAN[0]);
leaf_params.distinguished_name = dn;
leaf_params.subject_alt_names = SAN
.iter()
.map(|d| SanType::DnsName((*d).try_into().unwrap()))
.collect();
leaf_params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::KeyEncipherment,
];
leaf_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
leaf_params.is_ca = IsCa::NoCa;
// AKI 必须和签发者证书里的 SKI 一致,rcgen 的 Issuer 会自动从 CA 证书里取
leaf_params.use_authority_key_identifier_extension = true;
// 有效期落在 CA 的有效期内(2025-04-30 ~ 2040-04-28)
leaf_params.not_before = rcgen::date_time_ymd(2025, 5, 1);
leaf_params.not_after = rcgen::date_time_ymd(2038, 1, 1);
let leaf_key = KeyPair::generate().expect("generate leaf key failed");
let leaf_cert = leaf_params
.signed_by(&leaf_key, &issuer)
.expect("sign leaf cert failed");
// leaf + 完整 CA 链(两个 G3C 交叉签名都带上,让验证器自己选路径)
let fullchain_pem = format!("{}{}", leaf_cert.pem(), CA_CHAIN);
fs::write(out_dir.join("fullchain.pem"), fullchain_pem).unwrap(); fs::write(out_dir.join("fullchain.pem"), fullchain_pem).unwrap();
let leaf_key_pem = leaf_key.serialize_pem();
fs::write(out_dir.join("leaf.key"), leaf_key_pem).unwrap(); fs::write(out_dir.join("leaf.key"), leaf_key_pem).unwrap();
} }
fn first_pem_certificate(pem: &str) -> String {
let begin = "-----BEGIN CERTIFICATE-----";
let end = "-----END CERTIFICATE-----";
if let (Some(s), Some(e)) = (pem.find(begin), pem.find(end)) {
pem[s..e + end.len()].to_string()
} else {
panic!("no certificate found in CA chain PEM");
}
}
+40
View File
@@ -0,0 +1,40 @@
use crate::cert::{check_cert_included, sign};
use crate::info;
use rustls_pemfile::{certs, private_key};
use std::io::BufReader;
use std::sync::Arc;
pub fn build_tls_acceptor(target_hosts: Vec<String>) -> Arc<rustls::ServerConfig> {
let is_included_built_in = check_cert_included(target_hosts.iter().map(|s| s.as_str()));
let (cert_chain, key) = if is_included_built_in {
let fullchain = include_bytes!(concat!(env!("OUT_DIR"), "/fullchain.pem"));
let leaf_key = include_bytes!(concat!(env!("OUT_DIR"), "/leaf.key"));
let cert_chain = certs(&mut BufReader::new(&fullchain[..]))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let key = private_key(&mut BufReader::new(&leaf_key[..]))
.unwrap()
.unwrap();
(cert_chain, key)
} else {
info!("Target hosts not match built-in cert, generating new cert");
let target = target_hosts.iter().map(|s| s.as_str()).collect::<Vec<_>>();
let (fullchain, leaf_key) = sign(target.iter());
let cert_chain = certs(&mut BufReader::new(fullchain.as_bytes()))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let key = private_key(&mut BufReader::new(leaf_key.as_bytes()))
.unwrap()
.unwrap();
(cert_chain, key)
};
let mut config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key)
.unwrap();
config.alpn_protocols = vec![b"http/1.1".to_vec()];
Arc::new(config)
}
+171 -19
View File
@@ -1,24 +1,176 @@
use rustls_pemfile::{certs, private_key}; use rcgen::{
use std::io::BufReader; CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair,
use std::sync::Arc; KeyUsagePurpose, SanType,
};
pub fn build_tls_acceptor() -> Arc<rustls::ServerConfig> { const CA_KEY: &str = "-----BEGIN PRIVATE KEY-----
let fullchain = include_bytes!(concat!(env!("OUT_DIR"), "/fullchain.pem")); MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAAAAAAAAAAAAAAAAAA
let leaf_key = include_bytes!(concat!(env!("OUT_DIR"), "/leaf.key")); AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAShZANiAAQTglHNUqyS
mMHIqtl3Mh3rl+cJvQtMoKylXcitUdz8nRWJoVl+OlEg4e/WMcY+GDXKyuKYaaYu
FjHoooGBq1ZhbcRdkYq8CfOrDmPPeSqk3O1zh743u6VpVJ8cArJw7Wc=
-----END PRIVATE KEY-----";
let cert_chain = certs(&mut BufReader::new(&fullchain[..])) const CA_CHAIN: &str = "-----BEGIN PRIVATE KEY-----
.collect::<Result<Vec<_>, _>>() MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAAAAAAAAAAAAAAAAAA
.unwrap(); AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAShZANiAAQTglHNUqyS
let key = private_key(&mut BufReader::new(&leaf_key[..])) mMHIqtl3Mh3rl+cJvQtMoKylXcitUdz8nRWJoVl+OlEg4e/WMcY+GDXKyuKYaaYu
.unwrap() FjHoooGBq1ZhbcRdkYq8CfOrDmPPeSqk3O1zh743u6VpVJ8cArJw7Wc=
.unwrap(); -----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ
BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3
NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ
fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+
N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f
BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c
NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79
4rSb9vGqxZzelQth
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdDCCAvmgAwIBAgIQAJYaPvsTWVdu0EAFtGhKWzAKBggqhkjOPQQDAzBrMQsw
CQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhy
YSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290
IENBIC0gRzMwHhcNMjQwNzExMTU0MzM2WhcNMzkwNzA5MTgzMDAwWjBWMQswCQYD
VQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAARfD9w/q1cWxL6D9bMGH6CQ4u+gAV6GN9s+PwQx9Bl4hC+0iU8QOk8m
WO/RwXQy10DCSxT8vSIJClHKp+Ao4qJKdYeV0SwZ7LMQziyLfxIUOY6Grja/sQFw
LKYf8EdDXAyjggF1MIIBcTAfBgNVHSMEGDAWgBR8XQKEE9TMipuBzhccLikenEhj
QjAdBgNVHQ4EFgQU4TqrIXzx401UrtRyt/2LWUIgkFQwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIB
FhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMHgGCCsGAQUFBwEBBGwwajAi
BggrBgEFBQcwAYYWaHR0cDovL29jc3AuZW1zaWduLmNvbTBEBggrBgEFBQcwAoY4
aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbS9jZXJ0cy9lbVNpZ25FQ0NSb290
Q0FHMy5jcnQwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5lbXNpZ24uY29t
P1Jvb3RDQUczLmNybDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB
ATAKBggqhkjOPQQDAwNpADBmAjEAt9CIpRGp46QHQYDUBnLKw+vvK0/h9vmilEAg
TroUl/anH4x2rXHqiCtBTBfgKDegAjEA8WpG1LTZdAIL0FSYqxXeREaeTSxEofM0
1jJH3jGvCXC79LU9UPJ2o3TU+UIj2gMV
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIRAI1aSBGTnvRH13ERzdmEmukwDQYJKoZIhvcNAQELBQAw
ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y
ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu
YzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE
ACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZfjpRIOHv
1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+N7ulaVSf
HAKycO1no4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw
HQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMA4GA1UdDwEB/wQEAwIBBjAS
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAxBgNVHSAEKjAoMAgGBmeBDAECAjANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO
IQECbjBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B
QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB
BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC
AQEAHlbhZpjhkPEAQVhPHRtBDaHqiisVD20pQ1TUeSx3l62Qe7NQ0b7vWQy7DN0i
KOu7BGpP5EnIjvXbz9FhpfPJ4FhKEt3tQ51rqXz2oD8vRaryvExWPobDUz3j7HN6
OzBsVMIe3PD7AAkpky0x8zMSi/ep5R9OMzybmNptbtppyuB5ytgq+h7lQdRIP+CQ
TaJX3qTgfMqzntYre5GljmEB5hU2/rSn8zoSU2APkJidlZFpghtgbD4I2VRHZvCw
FFr/SrFM9pwrgbJNZ1MtyTSVgI5NlNb0aMfLR+/r6jJt3ROeY2yh2QQ2dx+Yqgdb
uL5ChFWOQU+ErSDnUq7yr+pjBQ==
-----END CERTIFICATE-----";
let mut config = rustls::ServerConfig::builder() pub const SAN: &[&str] = &[
.with_no_client_auth() // 国服
.with_single_cert(cert_chain, key) "*.dailygn.com",
.unwrap(); // 日服
"production-game-api.sekai.colorfulpalette.org",
"*.sekai.colorfulpalette.org",
// 国际服
"n-production-game-api.sekai-en.com",
"*.sekai-en.com",
// 台服 / 韩服(cdn 和 https 两个域都会出现)
"*.bytedgame.com",
"mk-zian-obt-cdn.bytedgame.com",
"mk-zian-obt-https.bytedgame.com",
"mkkorea-obt-prod01-cdn.bytedgame.com",
"mkkorea-obt-prod01-https.bytedgame.com",
];
config.alpn_protocols = vec![b"http/1.1".to_vec()]; #[allow(unused)] // caused in re-build in build.rs
pub fn check_cert_included<'a>(target_hosts: impl Iterator<Item = &'a str>) -> bool {
Arc::new(config) for host in target_hosts {
if SAN.contains(&host) {
continue;
}
let mut found = false;
for included_host in SAN {
if !included_host.starts_with("*.") {
continue;
}
let without_star = &included_host[2..];
let host_without_first = host.split_once(".");
if let Some(parts) = host_without_first
&& parts.1 == without_star
{
found = true;
break;
}
}
if !found {
return false;
}
}
true
}
pub fn sign<'a>(target_hosts: impl Iterator<Item = &'a &'a str>) -> (String, String) {
let mut target_hosts = target_hosts.peekable();
let ca_key = KeyPair::from_pem(CA_KEY).expect("parse CA key failed");
let first_ca_pem = first_pem_certificate(CA_CHAIN);
let issuer = rcgen::Issuer::from_ca_cert_pem(&first_ca_pem, ca_key)
.expect("build Issuer from CA cert + key failed");
let mut leaf_params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, target_hosts.peek().unwrap().to_string());
leaf_params.distinguished_name = dn;
leaf_params.subject_alt_names = target_hosts
.map(|d| SanType::DnsName((*d).try_into().unwrap()))
.collect();
leaf_params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::KeyEncipherment,
];
leaf_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
leaf_params.is_ca = IsCa::NoCa;
// AKI 必须和签发者证书里的 SKI 一致,rcgen 的 Issuer 会自动从 CA 证书里取
leaf_params.use_authority_key_identifier_extension = true;
// 有效期落在 CA 的有效期内(2025-04-30 ~ 2040-04-28)
leaf_params.not_before = rcgen::date_time_ymd(2025, 5, 1);
leaf_params.not_after = rcgen::date_time_ymd(2038, 1, 1);
let leaf_key = KeyPair::generate().expect("generate leaf key failed");
let leaf_cert = leaf_params
.signed_by(&leaf_key, &issuer)
.expect("sign leaf cert failed");
let fullchain_pem = format!("{}{}", leaf_cert.pem(), CA_CHAIN);
let leaf_key_pem = leaf_key.serialize_pem();
(fullchain_pem, leaf_key_pem)
}
fn first_pem_certificate(pem: &str) -> String {
let begin = "-----BEGIN CERTIFICATE-----";
let end = "-----END CERTIFICATE-----";
if let (Some(s), Some(e)) = (pem.find(begin), pem.find(end)) {
pem[s..e + end.len()].to_string()
} else {
panic!("no certificate found in CA chain PEM");
}
} }
+1
View File
@@ -2,6 +2,7 @@ mod cert;
mod mitm; mod mitm;
mod mut_qstring; mod mut_qstring;
mod tun; mod tun;
mod acceptor;
use crate::mitm::{ProxyConfig, StepSpec, Waiter, Waiters, run_session}; use crate::mitm::{ProxyConfig, StepSpec, Waiter, Waiters, run_session};
use jni::objects::{Global, JClass, JObject, JString}; use jni::objects::{Global, JClass, JObject, JString};
+20 -13
View File
@@ -1,8 +1,8 @@
use crate::cert::build_tls_acceptor;
use crate::mut_qstring::MutQString; use crate::mut_qstring::MutQString;
use crate::tun::{BorrowedTunIo, RewindStream}; use crate::tun::{BorrowedTunIo, RewindStream};
use crate::{error, info, warn}; use crate::{error, info, warn};
use bytes::Bytes; use bytes::Bytes;
use http::Uri;
use http_body_util::{BodyExt, Full}; use http_body_util::{BodyExt, Full};
use hyper::body::Incoming; use hyper::body::Incoming;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
@@ -14,13 +14,13 @@ use std::net::SocketAddr;
use std::os::fd::RawFd; use std::os::fd::RawFd;
use std::sync::{Arc, Mutex as StdMutex, OnceLock}; use std::sync::{Arc, Mutex as StdMutex, OnceLock};
use std::time::Duration; use std::time::Duration;
use http::Uri;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use tokio_rustls::StartHandshake; use tokio_rustls::StartHandshake;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tun_rs::BorrowedAsyncDevice; use tun_rs::BorrowedAsyncDevice;
use crate::acceptor::build_tls_acceptor;
static ROOT_STORE: OnceLock<Arc<RootCertStore>> = OnceLock::new(); static ROOT_STORE: OnceLock<Arc<RootCertStore>> = OnceLock::new();
@@ -145,12 +145,11 @@ where
return passthrough_cancel_wrapped(io, dst, None, upstream, &cancel).await; return passthrough_cancel_wrapped(io, dst, None, upstream, &cancel).await;
}; };
// 崩溃上报之类的域名直接拒绝,不建立上游连接 if let Some(host) = sni.as_deref()
if let Some(host) = sni.as_deref() { && is_blocked_host(host)
if is_blocked_host(host) { {
warn!("blocked host {host} ({dst:?}), connection rejected"); warn!("blocked host {host} ({dst:?}), connection rejected");
return Ok(()); return Ok(());
}
} }
let host_matched = sni let host_matched = sni
@@ -382,7 +381,6 @@ fn force_full_mysekai(uri: &Uri) -> Option<Uri> {
Uri::try_from(format!("{}?{}", uri.path(), query).as_str()).ok() Uri::try_from(format!("{}?{}", uri.path(), query).as_str()).ok()
} }
#[derive(serde::Deserialize, Debug, Clone)] #[derive(serde::Deserialize, Debug, Clone)]
pub struct StepSpec { pub struct StepSpec {
pub id: String, pub id: String,
@@ -450,9 +448,16 @@ impl ProxyConfig {
None None
} else { } else {
let pass = url.password().unwrap_or(""); let pass = url.password().unwrap_or("");
Some(base64_encode(format!("{}:{}", url.username(), pass).as_bytes())) Some(base64_encode(
format!("{}:{}", url.username(), pass).as_bytes(),
))
}; };
Ok(Some(Self { tls, host, port, auth })) Ok(Some(Self {
tls,
host,
port,
auth,
}))
} }
} }
@@ -473,7 +478,9 @@ struct Upstream {
impl Upstream { impl Upstream {
fn new(proxy: Option<ProxyConfig>) -> Self { fn new(proxy: Option<ProxyConfig>) -> Self {
Self { proxy: proxy.map(Arc::new) } Self {
proxy: proxy.map(Arc::new),
}
} }
async fn connect(&self, dst: SocketAddr, host: Option<&str>) -> anyhow::Result<UpstreamIo> { async fn connect(&self, dst: SocketAddr, host: Option<&str>) -> anyhow::Result<UpstreamIo> {
@@ -571,7 +578,7 @@ pub async fn run_session(
let mut ip_stack = ipstack::IpStack::new(ipstack_config, tun_io); let mut ip_stack = ipstack::IpStack::new(ipstack_config, tun_io);
let mut bridge_tasks = JoinSet::new(); let mut bridge_tasks = JoinSet::new();
let tls_acceptor = build_tls_acceptor(); let tls_acceptor = build_tls_acceptor(target_hosts.clone());
let upstream = Upstream::new(proxy); let upstream = Upstream::new(proxy);
loop { loop {