Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
409 lines
12 KiB
Go
409 lines
12 KiB
Go
// Copyright 2025 The BoringSSL Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package runner
|
|
|
|
import "crypto/x509"
|
|
|
|
func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool {
|
|
certPool := x509.NewCertPool()
|
|
for _, cred := range creds {
|
|
cert, err := x509.ParseCertificate(cred.RootCertificate)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
certPool.AddCert(cert)
|
|
}
|
|
return certPool
|
|
}
|
|
|
|
func addClientAuthTests() {
|
|
// Add a dummy cert pool to stress certificate authority parsing.
|
|
certPool := makeCertPoolFromRoots(&rsaCertificate, &rsa1024Certificate)
|
|
caNames := certPool.Subjects()
|
|
|
|
for _, ver := range tlsVersions {
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: ver.name + "-Client-ClientAuth-RSA",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
ClientAuth: RequireAnyClientCert,
|
|
ClientCAs: certPool,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: ver.name + "-Server-ClientAuth-RSA",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
},
|
|
flags: []string{"-require-any-client-certificate"},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: ver.name + "-Server-ClientAuth-ECDSA",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &ecdsaP256Certificate,
|
|
},
|
|
flags: []string{"-require-any-client-certificate"},
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: ver.name + "-Client-ClientAuth-ECDSA",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
ClientAuth: RequireAnyClientCert,
|
|
ClientCAs: certPool,
|
|
},
|
|
shimCertificate: &ecdsaP256Certificate,
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
name: "NoClientCertificate-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
ClientAuth: RequireAnyClientCert,
|
|
},
|
|
shouldFail: true,
|
|
expectedLocalError: "client didn't provide a certificate",
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
// Even if not configured to expect a certificate, OpenSSL will
|
|
// return X509_V_OK as the verify_result.
|
|
testType: serverTest,
|
|
name: "NoClientCertificateRequested-Server-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
},
|
|
flags: []string{
|
|
"-expect-verify-result",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
// If a client certificate is not provided, OpenSSL will still
|
|
// return X509_V_OK as the verify_result.
|
|
testType: serverTest,
|
|
name: "NoClientCertificate-Server-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
},
|
|
flags: []string{
|
|
"-expect-verify-result",
|
|
"-verify-peer",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
|
|
certificateRequired := "remote error: certificate required"
|
|
if ver.version < VersionTLS13 {
|
|
// Prior to TLS 1.3, the generic handshake_failure alert
|
|
// was used.
|
|
certificateRequired = "remote error: handshake failure"
|
|
}
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "RequireAnyClientCertificate-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
},
|
|
flags: []string{"-require-any-client-certificate"},
|
|
shouldFail: true,
|
|
expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
|
|
expectedLocalError: certificateRequired,
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "SkipClientCertificate-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Bugs: ProtocolBugs{
|
|
SkipClientCertificate: true,
|
|
},
|
|
},
|
|
// Setting SSL_VERIFY_PEER allows anonymous clients.
|
|
flags: []string{"-verify-peer"},
|
|
shouldFail: true,
|
|
expectedError: ":UNEXPECTED_MESSAGE:",
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: ver.name + "-Server-CertReq-CA-List",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
Bugs: ProtocolBugs{
|
|
ExpectCertificateReqNames: caNames,
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-require-any-client-certificate",
|
|
"-use-client-ca-list", encodeDERValues(caNames),
|
|
},
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: ver.name + "-Client-CertReq-CA-List",
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
ClientAuth: RequireAnyClientCert,
|
|
ClientCAs: certPool,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: []string{
|
|
"-expect-client-ca-list", encodeDERValues(caNames),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Client auth is only legal in certificate-based ciphers.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: "ClientAuth-PSK",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
|
|
PreSharedKey: []byte("secret"),
|
|
ClientAuth: RequireAnyClientCert,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: []string{
|
|
"-psk", "secret",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":UNEXPECTED_MESSAGE:",
|
|
})
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: "ClientAuth-ECDHE_PSK",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
|
|
PreSharedKey: []byte("secret"),
|
|
ClientAuth: RequireAnyClientCert,
|
|
},
|
|
shimCertificate: &rsaCertificate,
|
|
flags: []string{
|
|
"-psk", "secret",
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":UNEXPECTED_MESSAGE:",
|
|
})
|
|
|
|
// Regression test for a bug where the client CA list, if explicitly
|
|
// set to NULL, was mis-encoded.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "Null-Client-CA-List",
|
|
config: Config{
|
|
MaxVersion: VersionTLS12,
|
|
Credential: &rsaCertificate,
|
|
Bugs: ProtocolBugs{
|
|
ExpectCertificateReqNames: [][]byte{},
|
|
},
|
|
},
|
|
flags: []string{
|
|
"-require-any-client-certificate",
|
|
"-use-client-ca-list", "<NULL>",
|
|
},
|
|
})
|
|
|
|
// Test that an empty client CA list doesn't send a CA extension.
|
|
// (This is implicitly tested by the parser. An empty CA extension is
|
|
// a syntax error.)
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "TLS13-Empty-Client-CA-List",
|
|
config: Config{
|
|
MaxVersion: VersionTLS13,
|
|
Credential: &rsaCertificate,
|
|
},
|
|
flags: []string{
|
|
"-require-any-client-certificate",
|
|
"-use-client-ca-list", "<EMPTY>",
|
|
},
|
|
})
|
|
}
|
|
|
|
func addCertificateTests() {
|
|
for _, ver := range tlsVersions {
|
|
// Test that a certificate chain with intermediate may be sent
|
|
// and received as both client and server.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: "SendReceiveIntermediate-Client-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaChainCertificate,
|
|
ClientAuth: RequireAnyClientCert,
|
|
},
|
|
expectations: connectionExpectations{
|
|
peerCertificate: &rsaChainCertificate,
|
|
},
|
|
shimCertificate: &rsaChainCertificate,
|
|
flags: []string{
|
|
"-expect-peer-cert-file", rsaChainCertificate.ChainPath,
|
|
},
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "SendReceiveIntermediate-Server-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaChainCertificate,
|
|
},
|
|
expectations: connectionExpectations{
|
|
peerCertificate: &rsaChainCertificate,
|
|
},
|
|
shimCertificate: &rsaChainCertificate,
|
|
flags: []string{
|
|
"-require-any-client-certificate",
|
|
"-expect-peer-cert-file", rsaChainCertificate.ChainPath,
|
|
},
|
|
})
|
|
|
|
// Test that garbage leaf certificates are properly rejected.
|
|
testCases = append(testCases, testCase{
|
|
testType: clientTest,
|
|
name: "GarbageCertificate-Client-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &garbageCertificate,
|
|
},
|
|
shouldFail: true,
|
|
expectedError: ":CANNOT_PARSE_LEAF_CERT:",
|
|
expectedLocalError: "remote error: error decoding message",
|
|
})
|
|
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "GarbageCertificate-Server-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &garbageCertificate,
|
|
},
|
|
flags: []string{"-require-any-client-certificate"},
|
|
shouldFail: true,
|
|
expectedError: ":CANNOT_PARSE_LEAF_CERT:",
|
|
expectedLocalError: "remote error: error decoding message",
|
|
})
|
|
}
|
|
}
|
|
|
|
func addRetainOnlySHA256ClientCertTests() {
|
|
for _, ver := range tlsVersions {
|
|
// Test that enabling
|
|
// SSL_CTX_set_retain_only_sha256_of_client_certs without
|
|
// actually requesting a client certificate is a no-op.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "RetainOnlySHA256-NoCert-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
},
|
|
flags: []string{
|
|
"-on-initial-retain-only-sha256-client-cert",
|
|
"-on-resume-retain-only-sha256-client-cert",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
|
|
// Test that when retaining only a SHA-256 certificate is
|
|
// enabled, the hash appears as expected.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "RetainOnlySHA256-Cert-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-on-initial-retain-only-sha256-client-cert",
|
|
"-on-resume-retain-only-sha256-client-cert",
|
|
"-on-initial-expect-sha256-client-cert",
|
|
"-on-resume-expect-sha256-client-cert",
|
|
},
|
|
resumeSession: true,
|
|
})
|
|
|
|
// Test that when the config changes from on to off, a
|
|
// resumption is rejected because the server now wants the full
|
|
// certificate chain.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "RetainOnlySHA256-OnOff-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-on-initial-retain-only-sha256-client-cert",
|
|
"-on-initial-expect-sha256-client-cert",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
})
|
|
|
|
// Test that when the config changes from off to on, a
|
|
// resumption is rejected because the server now wants just the
|
|
// hash.
|
|
testCases = append(testCases, testCase{
|
|
testType: serverTest,
|
|
name: "RetainOnlySHA256-OffOn-" + ver.name,
|
|
config: Config{
|
|
MinVersion: ver.version,
|
|
MaxVersion: ver.version,
|
|
Credential: &rsaCertificate,
|
|
},
|
|
flags: []string{
|
|
"-verify-peer",
|
|
"-on-resume-retain-only-sha256-client-cert",
|
|
"-on-resume-expect-sha256-client-cert",
|
|
},
|
|
resumeSession: true,
|
|
expectResumeRejected: true,
|
|
})
|
|
}
|
|
}
|