diff --git a/app/.DS_Store b/app/.DS_Store index a2fac30..19c2116 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/app/src/main/java/com/felkertech/ussenterprise/fragments/EnterpriseFragment.java b/app/src/main/java/com/felkertech/ussenterprise/fragments/EnterpriseFragment.java index 449908f..ba8687f 100644 --- a/app/src/main/java/com/felkertech/ussenterprise/fragments/EnterpriseFragment.java +++ b/app/src/main/java/com/felkertech/ussenterprise/fragments/EnterpriseFragment.java @@ -29,7 +29,10 @@ import android.widget.TextView; import android.widget.Toast; +import java.io.BufferedInputStream; +import java.io.InputStream; import java.security.KeyStore; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import com.felkertech.ussenterprise.R; @@ -308,6 +311,67 @@ private WifiEnterpriseConfig buildEnterpriseConfig(EnterpriseWifiConnection conn enterpriseConfig.setEapMethod(connection.getEap()); enterpriseConfig.setPhase2Method(connection.getPhase2()); } + + // Android 12+ (API 31) requires certificate configuration for EAP methods + // that use server certificates (PEAP, TLS, TTLS, UNAUTH_TLS). + // We load CA certificates and set domain suffix match and TLS version. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + X509Certificate[] certs; + + // For UofT, try to load bundled certificates first + if ("UofT".equalsIgnoreCase(connection.getSsid())) { + certs = loadBundledCaCertificates(); + if (certs != null && certs.length > 0) { + Logd("Loaded " + certs.length + " bundled CA certificate(s) for UofT"); + } else { + // Fallback to system certificates if bundled certs not found + Logd("Bundled certificates not found, using system certificates"); + certs = loadSystemCaCertificates(); + } + } else { + // For non-UofT networks, use system certificates + certs = loadSystemCaCertificates(); + } + + if (certs != null && certs.length > 0) { + enterpriseConfig.setCaCertificates(certs); + // Set domain suffix match based on the network + // For UofT, use the official domain; for others, use common TLDs + String domainSuffix; + if ("UofT".equalsIgnoreCase(connection.getSsid())) { + domainSuffix = "utoronto.ca"; + } else { + // For non-UofT networks, use common enterprise TLDs + domainSuffix = "com;org;edu;net;gov;ca;uk;de;fr;au;jp;cn;in;br"; + } + enterpriseConfig.setDomainSuffixMatch(domainSuffix); + + // Set minimum TLS version to 1.2 as per Android 11+ requirements + // This is available from API 23+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + // Use reflection to call setMinimumTlsVersion if available + // WifiEnterpriseConfig.setMinimumTlsVersion(int version) + // where version is 2 for TLS 1.2 + java.lang.reflect.Method method = WifiEnterpriseConfig.class.getMethod("setMinimumTlsVersion", int.class); + method.invoke(enterpriseConfig, 2); // 2 = TLS 1.2 + Logd("Set minimum TLS version to 1.2"); + } catch (Exception tlsEx) { + // Method might not be available on all devices + Logd("Could not set TLS version: " + tlsEx.getMessage()); + } + } + + Logd("Loaded " + certs.length + " CA certificate(s) for validation"); + } else { + Logd("Warning: No CA certificates found"); + } + } catch (Exception e) { + Logd("Failed to configure certificates: " + e.getMessage()); + } + } + return enterpriseConfig; } @@ -461,6 +525,84 @@ private X509Certificate findCaCertificate(String name) { return null; } + private X509Certificate[] loadSystemCaCertificates() { + try { + KeyStore ks = KeyStore.getInstance("AndroidCAStore"); + ks.load(null); + java.util.Enumeration aliases = ks.aliases(); + List certificates = new ArrayList<>(); + + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + X509Certificate cert = (X509Certificate) ks.getCertificate(alias); + if (cert != null) { + certificates.add(cert); + } + } + + return certificates.toArray(new X509Certificate[0]); + } catch (Exception e) { + Log.e(TAG, "Failed to load system CA certificates", e); + Logd("Failed to load system CA certificates: " + e.getMessage()); + } + return null; + } + + /** + * Load CA certificates bundled with the app from res/raw directory. + * Supports loading both root and intermediate certificates. + * Certificate files should be in PEM format (.cer extension). + * + * Expected files in res/raw: + * - uoft_root_ca.cer (Required: Root CA certificate) + * - uoft_intermediate_ca.cer (Optional: Intermediate CA certificate) + * + * @return Array of X509 certificates loaded from bundled resources, or null if none found + */ + private X509Certificate[] loadBundledCaCertificates() { + List certificates = new ArrayList<>(); + CertificateFactory cf; + + try { + cf = CertificateFactory.getInstance("X.509"); + } catch (Exception e) { + Log.e(TAG, "Failed to get CertificateFactory", e); + return null; + } + + // Try to load root CA certificate + try { + InputStream rootCaStream = getResources().openRawResource(R.raw.uoft_root_ca); + BufferedInputStream bis = new BufferedInputStream(rootCaStream); + X509Certificate rootCert = (X509Certificate) cf.generateCertificate(bis); + certificates.add(rootCert); + bis.close(); + rootCaStream.close(); + Log.d(TAG, "Loaded root CA certificate from bundle"); + } catch (Exception e) { + Log.w(TAG, "Root CA certificate not found in bundle: " + e.getMessage()); + } + + // Try to load intermediate CA certificate (optional) + try { + InputStream intermediateCaStream = getResources().openRawResource(R.raw.uoft_intermediate_ca); + BufferedInputStream bis = new BufferedInputStream(intermediateCaStream); + X509Certificate intermediateCert = (X509Certificate) cf.generateCertificate(bis); + certificates.add(intermediateCert); + bis.close(); + intermediateCaStream.close(); + Log.d(TAG, "Loaded intermediate CA certificate from bundle"); + } catch (Exception e) { + Log.d(TAG, "Intermediate CA certificate not found in bundle (optional): " + e.getMessage()); + } + + if (certificates.isEmpty()) { + return null; + } + + return certificates.toArray(new X509Certificate[0]); + } + public void printSavedWifiNetworks() { final WifiManager wifiManager = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE); if (wifiManager == null) { diff --git a/app/src/main/res/raw/.gitkeep b/app/src/main/res/raw/.gitkeep new file mode 100644 index 0000000..bfe9dbd --- /dev/null +++ b/app/src/main/res/raw/.gitkeep @@ -0,0 +1,4 @@ +# Placeholder file to ensure the raw directory is tracked by git +# Place certificate files here: +# - uoft_root_ca.cer (Root CA certificate in PEM format) +# - uoft_intermediate_ca.cer (Optional: Intermediate CA certificate in PEM format) diff --git a/app/src/main/res/raw/uoft_intermediate_ca.cer b/app/src/main/res/raw/uoft_intermediate_ca.cer new file mode 100644 index 0000000..4440a73 --- /dev/null +++ b/app/src/main/res/raw/uoft_intermediate_ca.cer @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBDSgAwIBAgIQLBo8dulD3d3/GRsxiQrtcTANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBgMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgT1YgUjM2MIIBojANBgkqhkiG9w0B +AQEFAAOCAY8AMIIBigKCAYEApkMtJ3R06jo0fceI0M52B7K+TyMeGcv2BQ5AVc3j +lYt76TvHIu/nNe22W/RJXX9rWUD/2GE6GF5x0V4bsY7K3IeJ8E7+KzG/TGboySfD +u+F52jqQBbY62ofhYjMeiAbLI02+FqwHeM8uIrUtcX8b2RCxF358TB0NHVccAXZc +FYgZndZCeXxjuca7pJJ20LLUnXtgXcjAE1vY4WvbReW0W6mkeZyNGdmpTcFs5Y+s +yy6LtE5Zocji9J9NlNnReox2RWVyEXpA1ChZ4gqN+ZpVSIQ0HBorVFbBKyhdZyEX +gZgNSNtBRwxqwIzJePJhYd4ZUhO1vk+/uP3nwDk0p95q/j7naXNCSvESnrHPypaB +WRK066nKfPRPi9m9kIOhMdYfS8giFRTcdgL24Ycilj7ecAK9Trh0VbjwouJ4WH+x +bt47u68ZFCD/ac55I0DNHkCpaPruj6e9Rmr7K46wZDAYXuEAqB7tGG/jd6JAA+H2 +O44CV98NRsU213f1kScIZntNAgMBAAGjggGBMIIBfTAfBgNVHSMEGDAWgBRWc1hk +lfmSGrASKgRieaFAFYghSTAdBgNVHQ4EFgQU42Z0u3BojSxdTg6mSo+bNyKcgpIw +DgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgIw +VAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv +UHVibGljU2VydmVyQXV0aGVudGljYXRpb25Sb290UjQ2LmNybDCBhAYIKwYBBQUH +AQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp +Z29QdWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvblJvb3RSNDYucDdjMCMGCCsGAQUF +BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEA +BZXWDHWC3cubb/e1I1kzi8lPFiK/ZUoH09ufmVOrc5ObYH/XKkWUexSPqRkwKFKr +7r8OuG+p7VNB8rifX6uopqKAgsvZtZsq7iAFw04To6vNcxeBt1Eush3cQ4b8nbQR +MQLChgEAqwhuXp9P48T4QEBSksYav7+aFjNySsLYlPzNqVM3RNwvBdvp6vgDtGwc +xlKQZVuuNVIaoYyls8swhxDeSHKpRdxRauTLZ+pl+wGvy0pnrLEJGSz9mOEmfbod +e/XopR2NGqaHJ6bIjyxPu6UtyQGI26En7UAEozACrHz06Nx2jTAY9E6NeB6XuobE +wLK025ZRmvglcURG1BrV24tGHHTgxCe8M3oGlpUSMTKQ2dkgljZVYt+gKdFtWELZ +MuRdi+X3XsrR8LFz+aLUiDRfQqhmw3RxjIyVKvvu9UPYY1nsvxYmFnUSeM+2q1z/ +iPUry+xDY9MC6+IhleKT094VKdFVp7LXH42+wvU+17lRolQ2mK2N/nBLVBwaIhib +QXw4VYKwB86Bc6eS6iqsc94KEgD/U4VsjmgfhK+Xp4NM+VYzTTa3QeV3p8xOM0cw +q1p8oZFA+OBcz3FYWpDIe5j0NWKlw9hXsTyPY/HeZUV59akskSOSRSmDfe8wJDPX +58uB9/7lud0G3x0pxQAcffP0ayKavNwDTw4UfJ34cEw= +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/uoft_root_ca.cer b/app/src/main/res/raw/uoft_root_ca.cer new file mode 100644 index 0000000..71afc16 --- /dev/null +++ b/app/src/main/res/raw/uoft_root_ca.cer @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE-----