blob: 62f689e516667723d62825f216fa8f667c89eb22 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* 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
*
* http://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 com.android.server.wifi;
import static com.android.server.wifi.InsecureEapNetworkHandler.TOFU_ANONYMOUS_IDENTITY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiContext;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Handler;
import android.os.test.TestLooper;
import android.text.TextUtils;
import android.text.format.DateFormat;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.util.CertificateSubjectInfo;
import com.android.wifi.resources.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.stubbing.Answer;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* Unit tests for {@link com.android.server.wifi.InsecureEapNetworkHandlerTest}.
*/
@SmallTest
public class InsecureEapNetworkHandlerTest extends WifiBaseTest {
private static final int ACTION_ACCEPT = 0;
private static final int ACTION_REJECT = 1;
private static final int ACTION_TAP = 2;
private static final String WIFI_IFACE_NAME = "wlan-test-9";
private static final int FRAMEWORK_NETWORK_ID = 2;
private static final String TEST_SSID = "\"test_ssid\"";
private static final String TEST_IDENTITY = "userid";
private static final String TEST_PASSWORD = "myPassWord!";
private static final String TEST_EXPECTED_SHA_256_SIGNATURE = "54:59:5D:FC:64:9C:17:72:C0:59:"
+ "9D:25:BD:1F:04:18:E6:00:AB:F4:0A:F0:78:D8:9A:FF:56:C0:7C:89:96:2F";
private static final int TEST_GEN_CA_CERT = 0;
private static final int TEST_GEN_CA2_CERT = 1;
private static final int TEST_GEN_SERVER_CERT = 2;
private static final int TEST_GEN_SELF_SIGNED_CERT = 3;
private static final int TEST_GEN_FAKE_CA_CERT = 4;
private static final String TEST_SERVER_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
+ "MIIGPjCCBCagAwIBAgIUN2Ss1JmvjveRe97iWoNh4V+Y5LYwDQYJKoZIhvcNAQEM\n"
+ "BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQK\n"
+ "DBJBbmRyb2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8\n"
+ "MDoGA1UEAwwzQW5kcm9pZCBQYXJ0bmVyIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5k\n"
+ "IGRldmVsb3BtZW50MB4XDTIzMDQxMzAyMTYwMVoXDTQzMDQwODAyMTYwMVowgYMx\n"
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMR0wGwYDVQQKDBRBbmRy\n"
+ "b2lkIFdpLUZpIFRlc3RlcjEYMBYGA1UECwwPYW5kcm9pZHdpZmkub2VtMSYwJAYD\n"
+ "VQQDDB1BbmRyb2lkIFdpLUZpIE9FTSBUZXN0IFNlcnZlcjCCAiIwDQYJKoZIhvcN\n"
+ "AQEBBQADggIPADCCAgoCggIBAKveC9QnsxvM2TMzkUINabtM2Bi5M5gzV4v1MN0h\n"
+ "n1XjXhfRXwwLMK9xtV05r91YQaOTPkHNgA6nhjmL7agcquGPlR7nuS04oxCaqfo4\n"
+ "unbroyyqDMaXd8U6B1VlvWSbWAAhBEEAPYDhFXF9V83XHEGcp61Hs4VetGmlC3tW\n"
+ "W1CLIk+o9JRYsZeK4Q1DurAY7YPU8U84QNxPG7OXg+ensGtspuLLNFEdnd9tSi45\n"
+ "u5KyPpnSwTdRGSCfMVocxj0EINpdrLnWZyf9NX8Uo7tg/D0TFVBo+MbKjgItIdMg\n"
+ "STLQwceOdOGHZTPiItzpFcP9EA5ug5gXobPjzDTJO2S3NhUt5NURfGr/wyepxR25\n"
+ "PDRhBgc/xwc7JrtDGaqmknguZuf7Zai/m4iquC0Wh38bWKms8R0ND/H923aFppxp\n"
+ "vzX/sWotsTYWiGMehh7v6iwIYADifsXBlJXTUhTZt6cnwttZYfp5oqymCsIhXKVU\n"
+ "IXOE/PLcU71G9U+jCa7PNs5X5LgqorNPABOpkVL+fDpvopNCdhOEVvwCAIl4tIxl\n"
+ "M0goFbBmY1wnFFYIUki91UfbeUimCUbBq/RSxuXn3liVB/X+dnyjJ3RnNxJ3Wy1m\n"
+ "mcHFIVV5VxN6tC7XTXYgZAv0EJGCcVn0RN3ldPWGRLTEIQu7cXRSfqs89N4S31Et\n"
+ "SjaxAgMBAAGjgZMwgZAwHQYDVR0OBBYEFHh9fcIU3LHamK7PdpasvHmzyRoLMB8G\n"
+ "A1UdIwQYMBaAFH7ro7AWsBlMNpyRXHGW1hG4c1ocMAkGA1UdEwQCMAAwCwYDVR0P\n"
+ "BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnNlcnZlci5h\n"
+ "bmRyb2lkd2lmaS5vZW0wDQYJKoZIhvcNAQEMBQADggIBAOIkOLyF8mmYvj8TeM2V\n"
+ "d4YMj4sWf7L5C2lq9OGBJwZad1xytymWWZ7PpNf1MopabfUzxPjw5EfMC94MJmpf\n"
+ "gqYOwFAye5fXQ8CLC39tb681u44tv/B5vqP74TKVhCR8O1YCsIssa8t8e5nIwcYr\n"
+ "fj3SBu7iOLtL7zjfEXFo3oSEwVYnvS3lhZL8NTrrHscy/ZLFE3nGRq2d3jPbyuoH\n"
+ "1FJwenxnD6a/AztERPkRNGk2oSFkWecNU9PC9w3bI5wF4I2AIaFgBOj20S7pVtq7\n"
+ "7nhKnQFrZYVeWbqbInQcRAcSopI6D6tB/F/T9R1WCWBxvpwdciv7BeNgOtGKAszA\n"
+ "z0sOxI6O4U77R+tFeb0vCwC0OhVL3W0zX3Fy2835D/hC2P1jmMBlxLVKYHY48RBC\n"
+ "sG1I1qAMD4eXle8rG9MkB9cE5KfncjCrzSQjT8gs7QBTafb6B3WDdwzfaCaQTOOF\n"
+ "Tsyrdq0TTJP71bt5qWTr6UZIBE5Tjel+DPpvQlPZPYygXPrI3WBcT12VLhti0II6\n"
+ "1jgkS8fPLR0VypHR02V5fqCRmy9ln0rSyHXFwL3JpeXYD92eLOKdS1MhIUN4bDxZ\n"
+ "fiXXVKpKU4gqqWAan2RjbBzQjsi6Eh3yuDm2SAqNZVacpOt7BIslqEZ+Og6KhTTk\n"
+ "DCzyEOB87ySrUWu3PN3r2sJN\n"
+ "-----END CERTIFICATE-----";
private static final String TEST_CA_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
+ "MIIGADCCA+igAwIBAgIUFkmrYCj/UYNrizDdMATu6dE3lBIwDQYJKoZIhvcNAQEM\n"
+ "BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQK\n"
+ "DBJBbmRyb2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8\n"
+ "MDoGA1UEAwwzQW5kcm9pZCBQYXJ0bmVyIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5k\n"
+ "IGRldmVsb3BtZW50MB4XDTIzMDQxMzAyMTYwMVoXDTQzMDQwODAyMTYwMVowgZcx\n"
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQKDBJBbmRy\n"
+ "b2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8MDoGA1UE\n"
+ "AwwzQW5kcm9pZCBQYXJ0bmVyIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5kIGRldmVs\n"
+ "b3BtZW50MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9JERd2HVp/PI\n"
+ "3WmaaHDKHDuWZoxUDlyVrTDd1Vu2zhH3A5KJ232QOMxJiLdZ/KxgcpGlAEllijae\n"
+ "xihxhkHEYr7ff2/p6ZhUWr+0vuk8f4TZsKDAE0SoZoDBHTIbrJf8hHM5/+R//sx1\n"
+ "/fTf8abOj20zyeWmXqvUNXoVKiRvjiZD69tcRHmfmTOMX0lAirOel8ZwwDFamH8d\n"
+ "wov0IIyd58m6CV91WnScgg7TOzw/IGpccft73RbDw7cHU5i3G3KhOqamwJbErgya\n"
+ "x97AsSVCqjBz7rEwm6pHjUagbgVAk9ULmI1McQzMINIrOWRF0Q8awWpvDNwPu86J\n"
+ "W/LfyzAruWtriimycpl7wv0b/f7JhKerG0+44JUI0sgTz/kobAsU8nfYSyVu8+cX\n"
+ "HwnDE2jBGB6co2Y00eVKxy6+gWTekpQTyHuPoCieNDukC/38Mj+U0KUZkgGv4CL7\n"
+ "zaVBGzjSjtnAp47aXciaDvDbpST23ICS7TN5cUnXQ1fWfNUMNkEbIPy2mrlRoCxg\n"
+ "OJ67UEvGIygE0IUvwDfFvF21+1yKk6D/kU9gMgd6DKtvWj1CIyKXWf+rQ01OHNhX\n"
+ "YcOTkF5aF2WU558DuS+utGBzXWFsLxqBRe9nDb9W/SlrT2jajfwLelMddvtZmVsY\n"
+ "NG8IeY8lDs5hcFBvm/BDr0SvBDhs9H0CAwEAAaNCMEAwHQYDVR0OBBYEFH7ro7AW\n"
+ "sBlMNpyRXHGW1hG4c1ocMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGG\n"
+ "MA0GCSqGSIb3DQEBDAUAA4ICAQBINF6auWUhCO0l/AqO5pLgOqZ7BjAzHGDNapW+\n"
+ "3nn2YicDD/X2eJASfsd3jN5JluBlbLqRBBWnIsNG/fyKxY8I4+IrR1x8ovwBjeJ3\n"
+ "McQeCW2zedluVp2SW3LaNQS+aptXHATJ6O8EOny2LDM+obEtFyLuDC89a1TXjEdj\n"
+ "XGIYmSJ8RwpKAi4u6ff4jhtNTSEa/eIUE5zUREV0916xtmu5y1vlmsEbpLEquOph\n"
+ "ZWxpUVTqGEyc0hHaivAWyBG1dtRgov5olzHchM2TsEq/VufiRAw5uzRQ/sAyVjj4\n"
+ "pcvWnLDLTYk/+uIG1zmbc0rNpAC7b3tplA4OqTtFb3yX0ppPFUg4OaxhMyu4WqS3\n"
+ "roNiXc8BmtfzMqyWAG21QUfosLa8heiiHgnvkiUa9V2oJ4kWAhOTmLdU70aocu4N\n"
+ "pcN5jcT5hSl/A91Lvfht0C9BLOrXU+RDCNAVIUnnWSrgduUPTydKVdUkLxau4G/+\n"
+ "G8fKAyeCouFNq7bp4DEMkgqAWpx96Qe6FLxAS59Ig3tI8MZSieBZezJyjP4GWtuq\n"
+ "QsnARbwD7z73FWQ+eqXOhkoqDoQc8E2lQGe8OGbacGuUwXo3PUgGaJobz+2Hqa9g\n"
+ "6AnBkH6AbvooUwSWSCyYIf2LA+GvZotI+PXWuQL7dqWtkaNf98qqfnlZXjp51e+h\n"
+ "B8nquw==\n"
+ "-----END CERTIFICATE-----";
private static final String TEST_CA2_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
+ "MIIGADCCA+igAwIBAgIUGm2nmrZw4ADU7h/TGKd67Uz5bJIwDQYJKoZIhvcNAQEM\n"
+ "BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQK\n"
+ "DBJBbmRyb2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8\n"
+ "MDoGA1UEAwwzQW5vdGhlciBBbmRyb2lkIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5k\n"
+ "IGRldmVsb3BtZW50MB4XDTIzMDQxMzAyMTkxOVoXDTQzMDQwODAyMTkxOVowgZcx\n"
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQKDBJBbmRy\n"
+ "b2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8MDoGA1UE\n"
+ "AwwzQW5vdGhlciBBbmRyb2lkIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5kIGRldmVs\n"
+ "b3BtZW50MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvv7PYhFHK+nC\n"
+ "KoDiQI7dhDFTNU4RxTTsxMRSt1n/FJGZX/r8nnr76gB+oofFVjKQusYhuquKGPGq\n"
+ "ZfrfmtsNhcVBMnNRjZkBWpNb3XO+7F+Qd/gT7yoiZ0L3Ef4QMCGqNrf10EWmXvVQ\n"
+ "tpaM7RrkmlW6Zu2VbfP/iQQ7EVFrFWmnZfkCxpkLT+LK+pxwNxtJz5l7VRYkXelw\n"
+ "9vFdq81C+obBpLWg62mNVNa25g6y46YrSOPyxhiemiRih+avIZ9Z6/7qRoVu7t8U\n"
+ "NpxzMdsDL5bJREadsjpQWZr7A+umm0nlod1DB204K18Y5Z4GuOEGifdHIUmb+3c4\n"
+ "Kz14FzBahyc3xsZL73AsGEVWLHIQQ/kjepomVl8HuSHdgw6SZR30JhWgU/bcVl01\n"
+ "8qc6qH7x3e64Ip9xHdng42oPJHEKYipRed3AXzlCQ7Lc9MeAeR+nB9JuSNc6HW0L\n"
+ "eh9Po0cDJa194UfNeqJ7SG2uNpeg/OUbM+M3iO3dmCRcV3GzirbT8eHZk3Cor3gb\n"
+ "h9AzmJnHyRaRc9Xtj7AE8swJRvAoWVlCzcBcvaLAW0hn2DWXbWXHDf63Q8n5F4J5\n"
+ "pf//2eXWaOXFLvkm9wYUj6kXOehcibB2O1F1YvqWE3XZ5GTDq/+E5wK55aifq+bz\n"
+ "l1Mb1ILIB3cEEL9w+0ClHCno+2XGMOkCAwEAAaNCMEAwHQYDVR0OBBYEFH0KeaUK\n"
+ "koS2PMYfpcanoTkRBTzmMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGG\n"
+ "MA0GCSqGSIb3DQEBDAUAA4ICAQCnnL83fEA3g8fiJHpeSNSVZ4R3VunQU2BDkwUj\n"
+ "NgFWPMsZNQcKoChUA5mb8wOM/fk9tdjMsQR5fRO30B94Eoo9NM39HztBcvvLV9i7\n"
+ "qNQCTjFE7zf4weX6K3tZICR8nZ1Ogccp3itEDkNpOylFLdQxkc29RrKcuculb3PM\n"
+ "C7IMREKROKFzrAwWkFAaxJGfByTRfjOqWJFgdRq/GHU2yCKkCLN4zRLjr5ZaAk2J\n"
+ "+8b+Y1/pIW4j2FAB7ebmq0ZbMbdc+EFdVf36WrsWf54L3DsZOuoaC+2wTsyWQ0b/\n"
+ "8tqJ/XS39I4uo8KpI5//aQpM1usxP0/pWUm9sTXE618Yf2Ynh64eDQHPIAmt+Xoh\n"
+ "BfIx+nXVkCl4DGGdwvOURUULdHN9wf6YPOXxaMEYxQRGMwmBAlmiDaH41xeaht/A\n"
+ "+iv3y918rJFDAXWKvGia8oDi1xIL+IDZ1AGVByNp+C/AE5BTV2m9UHZyXsXrMiQA\n"
+ "ezUrVpiWB6h4C4rUuaucQv1gO6gEPZGEDdvIG8TGJg8wvLL0oZiyaL3gQxlGs0CZ\n"
+ "tbDGqugtlh4RLeJ1N/TTFkLzf4CAgDTxfqhMKXkFvpMvO6ZHOT7xC0sdaD2FbZRj\n"
+ "h5ziC9nvWEdTA8RLr0i/r5nFb6GsxmEk6NYFmpnyo5pvlxf5xqOhsJZlcKnUJ8SQ\n"
+ "NIGLmw==\n"
+ "-----END CERTIFICATE-----";
private static final String TEST_SELF_SIGNED_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
+ "MIIFxzCCA6+gAwIBAgIUB8Kqwhhhs1liW23ve7pZsFlv0zAwDQYJKoZIhvcNAQEM\n"
+ "BQAwezELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExGzAZBgNVBAoM\n"
+ "EkFuZHJvaWQgV2ktRmkgVGVzdDEYMBYGA1UECwwPYW5kcm9pZHdpZmkuZGV2MSAw\n"
+ "HgYDVQQDDBdTZWxmLXNpZ25lZCBjZXJ0aWZpY2F0ZTAeFw0yMzA0MTMwMjE0MTda\n"
+ "Fw00MzA0MDgwMjE0MTdaMHsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y\n"
+ "bmlhMRswGQYDVQQKDBJBbmRyb2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJv\n"
+ "aWR3aWZpLmRldjEgMB4GA1UEAwwXU2VsZi1zaWduZWQgY2VydGlmaWNhdGUwggIi\n"
+ "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDD0oI2c+1D4D2wc4PnhkXbtA7g\n"
+ "64Mp/JSbnVbl7PseJKnFD/pdos5joFXFbySFqu60S905T1a3AWNwbucKc7C7IwQw\n"
+ "gtO7uMEPr35j7MhItyAbmj89dY729yXJ8gBnNnqc8PyYEIfZmnBvSry0Tsm60298\n"
+ "GGZ9yCQfOOb4TJFX/CIKjniI170eLCiGybOrBvG11Rx6BwwHnk1cjkDspejrkhb0\n"
+ "13RfkQ1S0cEnylrgnn/nRDAAnOscpHRerJ6Ud2vM64iIJy206ZyU/CrhcGeBWwi9\n"
+ "C1F4ojzvgoFW7bJahXiyEaC5R3G5WdvX5qOr/eu/yMaCAner0LHUibHc5XA02F/c\n"
+ "LO0LpN59tTT4dx9sLJVjZQGSUxyXnKHiR5TKkoAMWAZSO5hbE4drgivKLnYmYnhC\n"
+ "Z1rGM5R0D0gB2llAvecItmynDJNApY6L1F8wnNA9NfGUYFpeqJ8uEOn7RxAvyYmB\n"
+ "trmUFOqL7W84d1/XzORPGQ7n1wyPfBG3xyGIm2MMvanVsLs0/9NXAYAz2ZAHJPnS\n"
+ "DsiV+7OHtMCdgTI5BJFmiJpXKgVE+IaewQdSjXDU7bgMlll3lTVoVAiKJmxpOmZ6\n"
+ "FFz7mkd0pYhsO5jQpNGMfl+IaoIiTx4Zg9ZjwjTcPn9eGunBLJJ8SofkhM4boLrC\n"
+ "KSen8NYuHVDPwAOwpQIDAQABo0MwQTAdBgNVHQ4EFgQU2IB1Q35ysx0HpRttAqMU\n"
+ "FO9OhIAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3\n"
+ "DQEBDAUAA4ICAQBqf4zbooJ4hHeZs7Nd31G7i2WEr6/8OWTuEuDAmJkGW8WBeyUG\n"
+ "JMrxF3U+kGxVGops4mSxMwVpcsqQa+IwkrP807dSqkuntLVGFqVo7rcQ+l8OcNUv\n"
+ "oNQIFGPLlWPyMnjXvmWbfvgbgFoY9yUFoxFlHqsVf+1mEvTmW9ya4BGT2hlfvtb6\n"
+ "Jfvrrocg9zGSnBs9oyI+GzP4Xdqd0riXfk6OuFH3R05/cQj7SlPm8LU1J7ZML/4H\n"
+ "1AuMg+Ql8vxql4IzIk93CDR8Hq1jb3MhF/ae9UfttuNnHT4vu5X/6qLqWNKMs3zP\n"
+ "DQQaYkqxWTUWiNlWV7i7pXn8e2J8ZkRHVELvrpdXLKIfL6RxjzKWY+TKiHY+F48I\n"
+ "JwCAbL1FX+NzB2dS0RxXk/RTAxagenfmDcY1notHNsnDZB54cP9nv+N3wqkDoaKg\n"
+ "nqOZTlIRWJ4agygqGaxieUuZRgy/AE/dSGpetlXAScKUvhCcO22qXL2jSjBAg5+k\n"
+ "AynUuiZxdogXbvXrAwSWAVwlz8qEOK3NPFYnEKcjgNbTxiUHp3P/ULBgHQo55o9K\n"
+ "DdUEbIurd02xG6usEDWxR5ds/RPy6VZ5c6bFUiTEsfMMmQotPL/btuPVXsSdJUR4\n"
+ "xcxpcV7zx9IjFs/IylyQ1YEYDKWV+nH7iiOigO5WiZ5ck2Wa/Tk3uXg1Ew==\n"
+ "-----END CERTIFICATE-----\n";
private static final String TEST_FAKE_CA_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
+ "MIIGADCCA+igAwIBAgIUIxVGWM5Wrs86DpDA2+fo53UryqMwDQYJKoZIhvcNAQEM\n"
+ "BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQK\n"
+ "DBJBbmRyb2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8\n"
+ "MDoGA1UEAwwzQW5kcm9pZCBQYXJ0bmVyIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5k\n"
+ "IGRldmVsb3BtZW50MB4XDTIzMDQxMzE1MzkyM1oXDTQzMDQwODE1MzkyM1owgZcx\n"
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRswGQYDVQQKDBJBbmRy\n"
+ "b2lkIFdpLUZpIFRlc3QxGDAWBgNVBAsMD2FuZHJvaWR3aWZpLm9lbTE8MDoGA1UE\n"
+ "AwwzQW5kcm9pZCBQYXJ0bmVyIFJvb3QgQ0EgZm9yIHRlc3RpbmcgYW5kIGRldmVs\n"
+ "b3BtZW50MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAhTF8MJsucR5P\n"
+ "6oN/Nho92EYz9b3m7n52m9KgI/G6/9bK9PSDZ6Z6U3qNxpG7nFML+5qyk+qeBHP8\n"
+ "39lGNNoH1c2dQDXw3oLjOmd1UoN+zSZBznLwkDD8YQYafz1GWRcI34FYDgiPuSx7\n"
+ "o4+O4hxcimrelhoNRQcRsrZFoUyJZjtPy2Z5DTZTB7udg1QwZ+7+pHCme3DB2Im/\n"
+ "Eszsmm2TAG6yM3G/lxphLZMhUFy6kjeeIiuar56ED6dg7qEqdeIznt2gGKolXRWs\n"
+ "vPW4a5NX1RUjsQxOcKEQnrXZXJ9mATptY1hOxuP6kg8Jzh0tN/NzyyERGFvnvhGz\n"
+ "sN7CkTUhPOKUW3dVrKl9ZJ9PbYZ6xbpbOWOR/5znYQ/f3+bxxibbFI3WN/89VO50\n"
+ "WEzwfmiGiWC6Bz0iBoAmGjCxySbJg8iDCjrbRexkFsOJ84jlY0fDrfaqY1+WuyYu\n"
+ "vdk+w4lzk0wYRbp+oRuIXplMyZDsS15CPq+svoYeNCCOXlkRiMLuq/SpkdM8lRKp\n"
+ "Mrsc1AckI+BGVqh8S9lyJoP67uDmba1FUw7X3IMCkZQwvFduLkJLNYwO6QDV2M6R\n"
+ "nUCVCx+vxJdlIOLNQIAeKW9jzfASom4ehZY2HHErbUYGKzFQJJ/2+uQLLYn7PsaE\n"
+ "gYTYA1naakQegCgbD2UsbKqrEfOiHEECAwEAAaNCMEAwHQYDVR0OBBYEFBiYeS/E\n"
+ "IQ5+IoQ3bsXoibK3QuMzMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGG\n"
+ "MA0GCSqGSIb3DQEBDAUAA4ICAQACOOZdfcQ53BF43glA/0VuFeXQ+VS+keR9aFBX\n"
+ "caKrIbFXSns9EsnvKj1L/IoHem5k4LkMFoNgE1Skit2iktPYs6unV83p99PG6EQo\n"
+ "RG1CeZ50cHzZK6N56MbwZUGRy/p6Zr9ak9d6XE8GpvSwMW8XebrLPtgSBvpI+WIZ\n"
+ "epMVg7v8AIIRQuoR2VtZ7RZF/X1kwfU5t2aASVBnxTjlSy6KtBLuL+Vu4Aefa+Z0\n"
+ "d9Ma2jZV+hwWp0X6piSrVKkMZIR5tlvwJootNBlO0J1Jn4J0ecGNEGXmFwz4adnK\n"
+ "eYfpuNBJI4CKq7mv2Aszsvg0rQxfKlN8LV7gSNu3H6BjjkNUtHI6uwsajJfEmGKD\n"
+ "YRpAFgZq7FzRwoI8uWr0Bucz6+qxpISi48t0pmceSVpn6UV1UdSebLo8CX5P283F\n"
+ "yUqlw2hMpo22Gm3uW8GfPyHfMfsqfMU+7BCP38DDnhcGUO3CTINjREXUGtn6CuWS\n"
+ "ImhmATld6KJNtRCql3zQnaEO84IvKdFVOkm5q9qQjNWDr1oYsLhxoZJZjKK2rP5F\n"
+ "GRbMvqDhmzrV0yG+sIyW+aEjBl44bVjWQnFhGjtNr1BOOftSyjnseYiioLbiiaYG\n"
+ "9Mqu78VmTWJzfxyOP2QPK5K00jnVBZ+jQH0NyIE9yf2Cg/llfYRoHsz80cfY/DNt\n"
+ "jUR49A==\n"
+ "-----END CERTIFICATE-----";
@Mock WifiContext mContext;
@Mock WifiConfigManager mWifiConfigManager;
@Mock WifiNative mWifiNative;
@Mock FrameworkFacade mFrameworkFacade;
@Mock WifiNotificationManager mWifiNotificationManager;
@Mock WifiDialogManager mWifiDialogManager;
@Mock InsecureEapNetworkHandler.InsecureEapNetworkHandlerCallbacks mCallbacks;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
@Mock private WifiDialogManager.DialogHandle mTofuAlertDialog;
@Mock private java.text.DateFormat mDateFormat;
@Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
TestLooper mLooper;
Handler mHandler;
MockResources mResources;
InsecureEapNetworkHandler mInsecureEapNetworkHandler;
private MockitoSession mSession;
/**
* Sets up for unit test
*/
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mResources = new MockResources();
when(mContext.getString(anyInt())).thenReturn("TestString");
when(mContext.getString(anyInt(), any())).thenReturn("TestStringWithArgument");
when(mContext.getText(anyInt())).thenReturn("TestStr");
when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_issuer_name_text),
anyString()))
.thenAnswer((Answer<String>) invocation ->
"Issuer Name:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_server_name_text),
anyString()))
.thenAnswer((Answer<String>) invocation ->
"Server Name:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_organization_text),
anyString()))
.thenAnswer((Answer<String>) invocation ->
"Organization:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_contact_text),
anyString()))
.thenAnswer((Answer<String>) invocation ->
"Contact:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getString(eq(R.string.wifi_ca_cert_dialog_message_signature_name_text),
anyString()))
.thenAnswer((Answer<String>) invocation ->
"SHA-256 Fingerprint:\n" + invocation.getArguments()[1] + "\n\n");
when(mContext.getWifiOverlayApkPkgName()).thenReturn("test.com.android.wifi.resources");
when(mContext.getResources()).thenReturn(mResources);
when(mWifiDialogManager.createLegacySimpleDialogWithUrl(
any(), any(), any(), anyInt(), anyInt(), any(), any(), any(), any(), any()))
.thenReturn(mTofuAlertDialog);
when(mWifiDialogManager.createLegacySimpleDialog(
any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mTofuAlertDialog);
when(mFrameworkFacade.makeNotificationBuilder(any(), any()))
.thenReturn(mNotificationBuilder);
// static mocking
mSession = ExtendedMockito.mockitoSession()
.mockStatic(DateFormat.class, withSettings().lenient())
.startMocking();
when(DateFormat.getMediumDateFormat(any())).thenReturn(mDateFormat);
when(mDateFormat.format(any())).thenReturn("April 12, 2023");
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
}
@After
public void cleanUp() throws Exception {
validateMockitoUsage();
mSession.finishMocking();
}
/**
* Verify Trust On First Use flow.
* - This network is selected by a user.
* - Accept the connection.
*/
@Test
public void verifyTrustOnFirstUseAcceptWhenConnectByUser() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_ACCEPT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify Trust On First Use flow.
* - This network is selected by a user.
* - Reject the connection.
*/
@Test
public void verifyTrustOnFirstUseRejectWhenConnectByUser() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_REJECT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify Trust On First Use flow.
* - This network is auto-connected.
* - Accept the connection.
*/
@Test
public void verifyTrustOnFirstUseAcceptWhenConnectByAutoConnect() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = false;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_ACCEPT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify Trust On First Use flow.
* - This network is auto-connected.
* - Reject the connection.
*/
@Test
public void verifyTrustOnFirstUseRejectWhenConnectByAutoConnect() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = false;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_REJECT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify Trust On First Use flow.
* - This network is auto-connected.
* - Tap the notification to show the dialog.
*/
@Test
public void verifyTrustOnFirstUseTapWhenConnectByAutoConnect() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = false;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_TAP,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify that it reports errors if there is no pending Root CA certifiate
* with Trust On First Use support.
*/
@Test
public void verifyTrustOnFirstUseWhenTrustOnFirstUseNoPendingCert() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks).onError(eq(config.SSID));
verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus
.DISABLED_BY_WIFI_MANAGER));
}
/**
* Verify that Trust On First Use is not supported on T.
* It follows the same behavior on preT release.
*/
@Test
public void verifyTrustOnFirstUseWhenTrustOnFirstUseNotSupported() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = false, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
}
/**
* Verify legacy insecure EAP network flow.
* - This network is selected by a user.
* - Accept the connection.
*/
@Test
public void verifyLegacyEapNetworkAcceptWhenConnectByUser() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
boolean isAtLeastT = false, isTrustOnFirstUseSupported = false, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_ACCEPT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify legacy insecure EAP network flow.
* - Trust On First Use is not supported.
* - This network is selected by a user.
* - Reject the connection.
*/
@Test
public void verifyLegacyEapNetworkRejectWhenConnectByUser() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
boolean isAtLeastT = false, isTrustOnFirstUseSupported = false, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_REJECT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify legacy insecure EAP network flow.
* - This network is auto-connected.
* - Accept the connection.
*/
@Test
public void verifyLegacyEapNetworkAcceptWhenAutoConnect() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
boolean isAtLeastT = false, isTrustOnFirstUseSupported = false, isUserSelected = false;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_ACCEPT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify legacy insecure EAP network flow.
* - Trust On First Use is not supported.
* - This network is auto-connected.
* - Reject the connection.
*/
@Test
public void verifyLegacyEapNetworkRejectWhenAutoConnect() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
boolean isAtLeastT = false, isTrustOnFirstUseSupported = false, isUserSelected = false;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_REJECT,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify legacy insecure EAP network flow.
* - This network is selected by a user.
* - Tap the notification
*/
@Test
public void verifyLegacyEapNetworkOpenLinkWhenConnectByUser() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
boolean isAtLeastT = false, isTrustOnFirstUseSupported = false, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
verifyTrustOnFirstUseFlowWithDefaultCerts(config, ACTION_TAP,
isTrustOnFirstUseSupported, isUserSelected, needUserApproval);
}
/**
* Verify Trust On First Use flow with server certificate pinning
* - Single depth server certificate by signed by some unknown issuer, CA flag not set
* - This network is selected by a user.
* - Accept the connection.
*/
@Test
public void verifyTrustOnFirstUseFlowWithServerCertPinning1() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
runServerCertPinningTest(TEST_GEN_SERVER_CERT);
}
/**
* Verify Trust On First Use flow with server certificate pinning
* - Single depth server certificate by signed by some unknown issuer, CA flag set
* - This network is selected by a user.
* - Accept the connection.
*/
@Test
public void verifyTrustOnFirstUseFlowWithServerCertPinning2() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
runServerCertPinningTest(TEST_GEN_CA_CERT);
}
private void runServerCertPinningTest(int type)
throws Exception {
WifiConfiguration config = prepareWifiConfiguration(true);
setupTest(config, true, true);
CertificateEventInfo mockServerCert = generateMockCertEventInfo(type);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, true,
true, false, null, mockServerCert.getCert());
}
private CertificateEventInfo generateMockCertEventInfo(int type) throws Exception {
CertificateEventInfo certificateEventInfo = mock(CertificateEventInfo.class);
X509Certificate cert = getCertificate(type);
when(certificateEventInfo.getCert()).thenReturn(cert);
when(certificateEventInfo.getCertHash()).thenReturn("12345678");
return certificateEventInfo;
}
private X509Certificate getCertificate(int type) throws Exception {
String certString;
if (type == TEST_GEN_CA_CERT) {
certString = TEST_CA_CERTIFICATE;
} else if (type == TEST_GEN_CA2_CERT) {
certString = TEST_CA2_CERTIFICATE;
} else if (type == TEST_GEN_SERVER_CERT) {
certString = TEST_SERVER_CERTIFICATE;
} else if (type == TEST_GEN_SELF_SIGNED_CERT) {
certString = TEST_SELF_SIGNED_CERTIFICATE;
} else if (type == TEST_GEN_FAKE_CA_CERT) {
certString = TEST_FAKE_CA_CERTIFICATE;
} else {
throw (new Exception());
}
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certString.getBytes());
return (X509Certificate) certFactory.generateCertificate(in);
}
private WifiConfiguration prepareWifiConfiguration(boolean isAtLeastT) {
WifiConfiguration config = spy(WifiConfigurationTestUtil.createEapNetwork(
WifiEnterpriseConfig.Eap.TTLS, WifiEnterpriseConfig.Phase2.MSCHAPV2));
config.networkId = FRAMEWORK_NETWORK_ID;
config.SSID = TEST_SSID;
if (isAtLeastT) {
config.enterpriseConfig.enableTrustOnFirstUse(true);
}
config.enterpriseConfig.setCaPath("");
config.enterpriseConfig.setDomainSuffixMatch("");
config.enterpriseConfig.setIdentity(TEST_IDENTITY);
config.enterpriseConfig.setPassword(TEST_PASSWORD);
return config;
}
private void setupTest(WifiConfiguration config,
boolean isAtLeastT, boolean isTrustOnFirstUseSupported) {
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported, false);
}
private void setupTest(WifiConfiguration config,
boolean isAtLeastT, boolean isTrustOnFirstUseSupported,
boolean isInsecureEnterpriseConfigurationAllowed) {
mInsecureEapNetworkHandler = new InsecureEapNetworkHandler(
mContext,
mWifiConfigManager,
mWifiNative,
mFrameworkFacade,
mWifiNotificationManager,
mWifiDialogManager,
isTrustOnFirstUseSupported,
isInsecureEnterpriseConfigurationAllowed,
mCallbacks,
WIFI_IFACE_NAME,
mHandler);
if (isTrustOnFirstUseSupported
&& (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS
|| config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP)
&& config.enterpriseConfig.getPhase2Method() != WifiEnterpriseConfig.Phase2.NONE) {
// Verify that the configuration contains an identity
assertEquals(TEST_IDENTITY, config.enterpriseConfig.getIdentity());
assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getAnonymousIdentity()));
assertEquals(TEST_PASSWORD, config.enterpriseConfig.getPassword());
}
mInsecureEapNetworkHandler.prepareConnection(config);
if (isTrustOnFirstUseSupported && config.enterpriseConfig.isTrustOnFirstUseEnabled()
&& (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS
|| config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP)
&& config.enterpriseConfig.getPhase2Method() != WifiEnterpriseConfig.Phase2.NONE) {
// Verify identities are cleared
assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getIdentity()));
assertEquals(TOFU_ANONYMOUS_IDENTITY, config.enterpriseConfig.getAnonymousIdentity());
assertTrue(TextUtils.isEmpty(config.enterpriseConfig.getPassword()));
}
if (isTrustOnFirstUseSupported && config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
verify(mContext, atLeastOnce()).registerReceiver(
mBroadcastReceiverCaptor.capture(),
argThat(f -> f.hasAction(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_TAP)),
eq(null),
eq(mHandler));
} else if ((isTrustOnFirstUseSupported
&& !config.enterpriseConfig.isTrustOnFirstUseEnabled()
&& isInsecureEnterpriseConfigurationAllowed)
|| !isTrustOnFirstUseSupported) {
verify(mContext, atLeastOnce()).registerReceiver(
mBroadcastReceiverCaptor.capture(),
argThat(f -> f.hasAction(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_ACCEPT)
&& f.hasAction(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_REJECT)),
eq(null),
eq(mHandler));
}
}
/**
* Verify Trust On First Use flow with a minimal cert chain
* - This network is selected by a user.
* - Accept the connection.
*/
@Test
public void verifyTrustOnFirstUseAcceptWhenConnectByUserWithMinimalChain() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
boolean needUserApproval = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
CertificateEventInfo mockCaCert = generateMockCertEventInfo(TEST_GEN_CA_CERT);
CertificateEventInfo mockServerCert = generateMockCertEventInfo(TEST_GEN_SERVER_CERT);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
verifyTrustOnFirstUseFlow(config, ACTION_ACCEPT, isTrustOnFirstUseSupported,
isUserSelected, needUserApproval, mockCaCert.getCert(), mockServerCert.getCert());
}
/**
* Verify that the connection should be terminated.
* - TOFU is supported.
* - Insecure EAP network is not allowed.
* - No cert is received.
*/
@Test
public void verifyOnErrorWithoutCert() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks).onError(eq(config.SSID));
verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus
.DISABLED_BY_WIFI_MANAGER));
}
/**
* Verify that the connection should be upgraded to TOFU.
* - TOFU is supported.
* - Insecure EAP network is not allowed.
* - TOFU is not enabled
*/
@Test
public void verifyOnErrorWithTofuDisabledWhenInsecureEapNetworkIsNotAllowed()
throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
config.enterpriseConfig.enableTrustOnFirstUse(false);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1,
generateMockCertEventInfo(TEST_GEN_CA_CERT));
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
generateMockCertEventInfo(TEST_GEN_SERVER_CERT));
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
assertTrue(config.enterpriseConfig.isTrustOnFirstUseEnabled());
}
/**
* Verify that no error occurs in insecure network handling flow.
* - TOFU is supported.
* - Insecure EAP network is allowed.
* - TOFU is not enabled
* - No user approval is needed.
*/
@Test
public void verifyNoErrorWithTofuDisabledWhenInsecureEapNetworkIsAllowed()
throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
boolean isInsecureEnterpriseConfigurationAllowed = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
config.enterpriseConfig.enableTrustOnFirstUse(false);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported,
isInsecureEnterpriseConfigurationAllowed);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1,
generateMockCertEventInfo(TEST_GEN_CA_CERT));
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
generateMockCertEventInfo(TEST_GEN_SERVER_CERT));
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
}
/**
* Verify that is reports errors if the server cert issuer does not match the parent subject.
*/
@Test
public void verifyOnErrorWithIncompleteChain() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
CertificateEventInfo mockCaCert = generateMockCertEventInfo(TEST_GEN_CA2_CERT);
// Missing intermediate cert.
CertificateEventInfo mockServerCert = generateMockCertEventInfo(TEST_GEN_SERVER_CERT);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks).onError(eq(config.SSID));
verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus
.DISABLED_BY_WIFI_MANAGER));
}
/**
* Verify that it reports errors if the issuer is a fake Root CA with the same subject of the
* real Root CA. Simulates an attack where the leaf is copied from the real server but a fake
* Root CA that an attacker controls is attached.
*/
@Test
public void verifyOnErrorWithFakeRootCaCertInTheChain() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
// Fake Root CA that didn't sign the server cert
CertificateEventInfo mockCaCert = generateMockCertEventInfo(TEST_GEN_FAKE_CA_CERT);
CertificateEventInfo mockServerCert = generateMockCertEventInfo(TEST_GEN_SERVER_CERT);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks).onError(eq(config.SSID));
verify(mWifiConfigManager, atLeastOnce()).updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus
.DISABLED_BY_WIFI_MANAGER));
}
/**
* Verify that setting pending certificate won't crash with no current configuration.
*/
@Test
public void verifySetPendingCertificateNoCrashWithNoConfig()
throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
mInsecureEapNetworkHandler = new InsecureEapNetworkHandler(
mContext,
mWifiConfigManager,
mWifiNative,
mFrameworkFacade,
mWifiNotificationManager,
mWifiDialogManager,
true /* isTrustOnFirstUseSupported */,
false /* isInsecureEnterpriseConfigurationAllowed */,
mCallbacks,
WIFI_IFACE_NAME,
mHandler);
CertificateEventInfo mockSelfSignedCert =
generateMockCertEventInfo(TEST_GEN_SELF_SIGNED_CERT);
mInsecureEapNetworkHandler.addPendingCertificate("NotExist", 0, mockSelfSignedCert);
}
@Test
public void testExistingCertChainIsClearedOnPreparingNewConnection() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
// Missing root CA cert.
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0,
generateMockCertEventInfo(TEST_GEN_SERVER_CERT));
// The wrong cert chain should be cleared after this call.
mInsecureEapNetworkHandler.prepareConnection(config);
CertificateEventInfo mockSelfSignedCert =
generateMockCertEventInfo(TEST_GEN_SELF_SIGNED_CERT);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockSelfSignedCert);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
}
@Test
public void verifyUserApprovalIsNotNeededWithDifferentTargetConfig() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true, isUserSelected = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
CertificateEventInfo mockSelfSignedCert =
generateMockCertEventInfo(TEST_GEN_SELF_SIGNED_CERT);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockSelfSignedCert);
// Pass another PSK config which is not the same as the current one.
WifiConfiguration pskConfig = WifiConfigurationTestUtil.createPskNetwork();
pskConfig.networkId = FRAMEWORK_NETWORK_ID + 2;
mInsecureEapNetworkHandler.prepareConnection(pskConfig);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
// Pass another non-TOFU EAP config which is not the same as the current one.
WifiConfiguration anotherEapConfig = spy(WifiConfigurationTestUtil.createEapNetwork(
WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
anotherEapConfig.networkId = FRAMEWORK_NETWORK_ID + 1;
mInsecureEapNetworkHandler.prepareConnection(anotherEapConfig);
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
verify(mCallbacks, never()).onError(any());
}
private void verifyTrustOnFirstUseFlowWithDefaultCerts(WifiConfiguration config,
int action, boolean isTrustOnFirstUseSupported, boolean isUserSelected,
boolean needUserApproval) throws Exception {
CertificateEventInfo mockCaCert = generateMockCertEventInfo(TEST_GEN_CA_CERT);
CertificateEventInfo mockServerCert = generateMockCertEventInfo(TEST_GEN_SERVER_CERT);
if (isTrustOnFirstUseSupported) {
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 1, mockCaCert);
mInsecureEapNetworkHandler.addPendingCertificate(config.SSID, 0, mockServerCert);
}
verifyTrustOnFirstUseFlow(config, action, isTrustOnFirstUseSupported,
isUserSelected, needUserApproval, mockCaCert.getCert(), mockServerCert.getCert());
}
private void verifyTrustOnFirstUseFlow(WifiConfiguration config,
int action, boolean isTrustOnFirstUseSupported, boolean isUserSelected,
boolean needUserApproval, X509Certificate expectedCaCert,
X509Certificate expectedServerCert) throws Exception {
mInsecureEapNetworkHandler.startUserApprovalIfNecessary(isUserSelected);
ArgumentCaptor<String> dialogMessageCaptor = ArgumentCaptor.forClass(String.class);
if (isUserSelected) {
ArgumentCaptor<WifiDialogManager.SimpleDialogCallback> dialogCallbackCaptor =
ArgumentCaptor.forClass(WifiDialogManager.SimpleDialogCallback.class);
verify(mWifiDialogManager).createLegacySimpleDialogWithUrl(
any(), dialogMessageCaptor.capture(), any(), anyInt(), anyInt(), any(), any(),
any(), dialogCallbackCaptor.capture(), any());
if (isTrustOnFirstUseSupported) {
assertTofuDialogMessage(expectedServerCert,
dialogMessageCaptor.getValue());
}
if (action == ACTION_ACCEPT) {
dialogCallbackCaptor.getValue().onPositiveButtonClicked();
} else if (action == ACTION_REJECT) {
dialogCallbackCaptor.getValue().onNegativeButtonClicked();
}
} else {
verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
verify(mFrameworkFacade).makeNotificationBuilder(
eq(mContext), eq(WifiService.NOTIFICATION_NETWORK_ALERTS));
// Trust On First Use notification has no accept and reject action buttons.
// It only supports TAP and launch the dialog.
if (isTrustOnFirstUseSupported) {
Intent intent = new Intent(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_TAP);
intent.putExtra(InsecureEapNetworkHandler.EXTRA_PENDING_CERT_SSID, TEST_SSID);
BroadcastReceiver br = mBroadcastReceiverCaptor.getValue();
br.onReceive(mContext, intent);
ArgumentCaptor<WifiDialogManager.SimpleDialogCallback> dialogCallbackCaptor =
ArgumentCaptor.forClass(WifiDialogManager.SimpleDialogCallback.class);
verify(mWifiDialogManager).createLegacySimpleDialogWithUrl(
any(), dialogMessageCaptor.capture(), any(), anyInt(), anyInt(), any(),
any(), any(), dialogCallbackCaptor.capture(), any());
assertTofuDialogMessage(expectedServerCert,
dialogMessageCaptor.getValue());
if (action == ACTION_ACCEPT) {
dialogCallbackCaptor.getValue().onPositiveButtonClicked();
} else if (action == ACTION_REJECT) {
dialogCallbackCaptor.getValue().onNegativeButtonClicked();
}
} else {
Intent intent = new Intent();
if (action == ACTION_ACCEPT) {
intent = new Intent(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_ACCEPT);
} else if (action == ACTION_REJECT) {
intent = new Intent(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_REJECT);
} else if (action == ACTION_TAP) {
intent = new Intent(InsecureEapNetworkHandler.ACTION_CERT_NOTIF_TAP);
}
intent.putExtra(InsecureEapNetworkHandler.EXTRA_PENDING_CERT_SSID, TEST_SSID);
BroadcastReceiver br = mBroadcastReceiverCaptor.getValue();
br.onReceive(mContext, intent);
}
}
if (action == ACTION_ACCEPT) {
verify(mWifiConfigManager).updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE));
if (isTrustOnFirstUseSupported) {
if (expectedCaCert == null) {
// Simulate server cert pinning case where there is no Root CA
verify(mWifiConfigManager).updateCaCertificate(
eq(config.networkId), eq(expectedServerCert), eq(expectedServerCert),
eq("12345678")); // Server certificate hash
} else {
verify(mWifiConfigManager).updateCaCertificate(
eq(config.networkId), eq(expectedCaCert), eq(expectedServerCert),
eq(null)); // Cert pinning not used
}
} else {
verify(mWifiConfigManager, never()).updateCaCertificate(
anyInt(), any(), any(), any());
}
verify(mCallbacks).onAccept(eq(config.SSID), eq(config.networkId));
} else if (action == ACTION_REJECT) {
verify(mWifiConfigManager, atLeastOnce())
.updateNetworkSelectionStatus(eq(config.networkId),
eq(WifiConfiguration.NetworkSelectionStatus
.DISABLED_BY_WIFI_MANAGER));
verify(mCallbacks).onReject(eq(config.SSID), eq(!isTrustOnFirstUseSupported));
} else if (action == ACTION_TAP) {
verify(mWifiDialogManager).createLegacySimpleDialogWithUrl(
any(), any(), any(), anyInt(), anyInt(), any(), any(), any(), any(), any());
verify(mTofuAlertDialog).launchDialog();
}
verify(mCallbacks, never()).onError(any());
}
private void assertTofuDialogMessage(
X509Certificate serverCert,
String message) {
CertificateSubjectInfo serverCertSubjectInfo =
CertificateSubjectInfo.parse(serverCert.getSubjectX500Principal().getName());
CertificateSubjectInfo serverCertIssuerInfo =
CertificateSubjectInfo.parse(serverCert.getIssuerX500Principal().getName());
assertNotNull("Server cert subject info is null", serverCertSubjectInfo);
assertNotNull("Server cert issuer info is null", serverCertIssuerInfo);
assertTrue("TOFU dialog message does not contain server cert subject name ",
message.contains(serverCertSubjectInfo.commonName));
assertTrue("TOFU dialog message does not contain server cert issuer name",
message.contains(serverCertIssuerInfo.commonName));
if (!TextUtils.isEmpty(serverCertSubjectInfo.organization)) {
assertTrue("TOFU dialog message does not contain server cert organization",
message.contains(serverCertSubjectInfo.organization));
}
}
@Test
public void testCleanUp() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
boolean isAtLeastT = true, isTrustOnFirstUseSupported = true;
WifiConfiguration config = prepareWifiConfiguration(isAtLeastT);
setupTest(config, isAtLeastT, isTrustOnFirstUseSupported);
BroadcastReceiver br = mBroadcastReceiverCaptor.getValue();
mInsecureEapNetworkHandler.cleanup();
verify(mContext).unregisterReceiver(br);
}
/**
* Verify the getDigest and fingerprint methods
*/
@Test
public void verifyGetDigest() throws Exception {
CertificateEventInfo mockServerCert = generateMockCertEventInfo(TEST_GEN_SERVER_CERT);
assertEquals(TEST_EXPECTED_SHA_256_SIGNATURE,
mInsecureEapNetworkHandler.getDigest(mockServerCert.getCert(), "SHA256"));
}
}