summaryrefslogtreecommitdiffstats
path: root/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
blob: a0a52f6eaf2f2dcd52cb1ca158a8eb1b25c2ec79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
 * Copyright (C) 2016 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.hotspot2;

import android.net.wifi.hotspot2.PasspointConfiguration;
import android.security.Credentials;
import android.util.Log;

import com.android.server.wifi.WifiKeyStore;
import com.android.server.wifi.hotspot2.anqp.ANQPElement;
import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;

/**
 * Abstraction for Passpoint service provider.  This class contains the both static
 * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
 */
public class PasspointProvider {
    private static final String TAG = "PasspointProvider";

    // Prefix for certificates and keys aliases.
    private static final String ALIAS_PREFIX = "HS2_";

    private final PasspointConfiguration mConfig;
    private final WifiKeyStore mKeyStore;

    // Unique identifier for this provider. Used as part of the alias names for identifying
    // certificates and keys installed on the keystore.
    private final long mProviderId;

    // Aliases for the private keys and certificates installed in the keystore.
    private String mCaCertificateAlias;
    private String mClientPrivateKeyAlias;
    private String mClientCertificateAlias;

    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
            long providerId) {
        // Maintain a copy of the configuration to avoid it being updated by others.
        mConfig = new PasspointConfiguration(config);
        mKeyStore = keyStore;
        mProviderId = providerId;
    }

    public PasspointConfiguration getConfig() {
        // Return a copy of the configuration to avoid it being updated by others.
        return new PasspointConfiguration(mConfig);
    }

    public String getCaCertificateAlias() {
        return mCaCertificateAlias;
    }

    public String getClientPrivateKeyAlias() {
        return mClientPrivateKeyAlias;
    }

    public String getClientCertificateAlias() {
        return mClientCertificateAlias;
    }

    /**
     * Install certificates and key based on current configuration.
     * Note: the certificates and keys in the configuration will get cleared once
     * they're installed in the keystore.
     *
     * @return true on success
     */
    public boolean installCertsAndKeys() {
        // Install CA certificate.
        if (mConfig.credential.caCertificate != null) {
            String alias = formatAliasName(Credentials.CA_CERTIFICATE, mProviderId);
            if (!mKeyStore.putCertInKeyStore(alias, mConfig.credential.caCertificate)) {
                Log.e(TAG, "Failed to install CA Certificate");
                uninstallCertsAndKeys();
                return false;
            }
            mCaCertificateAlias = alias;
        }

        // Install the client private key.
        if (mConfig.credential.clientPrivateKey != null) {
            String alias = formatAliasName(Credentials.USER_PRIVATE_KEY, mProviderId);
            if (!mKeyStore.putKeyInKeyStore(alias, mConfig.credential.clientPrivateKey)) {
                Log.e(TAG, "Failed to install client private key");
                uninstallCertsAndKeys();
                return false;
            }
            mClientPrivateKeyAlias = alias;
        }

        // Install the client certificate.
        if (mConfig.credential.clientCertificateChain != null) {
            X509Certificate clientCert =
                    getClientCertificate(mConfig.credential.clientCertificateChain,
                                         mConfig.credential.certCredential.certSha256FingerPrint);
            if (clientCert == null) {
                Log.e(TAG, "Failed to locate client certificate");
                uninstallCertsAndKeys();
                return false;
            }
            String alias = formatAliasName(Credentials.USER_CERTIFICATE, mProviderId);
            if (!mKeyStore.putCertInKeyStore(alias, clientCert)) {
                Log.e(TAG, "Failed to install client certificate");
                uninstallCertsAndKeys();
                return false;
            }
            mClientCertificateAlias = alias;
        }

        // Clear the keys and certificates in the configuration.
        mConfig.credential.caCertificate = null;
        mConfig.credential.clientPrivateKey = null;
        mConfig.credential.clientCertificateChain = null;
        return true;
    }

    /**
     * Remove any installed certificates and key.
     */
    public void uninstallCertsAndKeys() {
        if (mCaCertificateAlias != null) {
            if (!mKeyStore.removeEntryFromKeyStore(mCaCertificateAlias)) {
                Log.e(TAG, "Failed to remove entry: " + mCaCertificateAlias);
            }
            mCaCertificateAlias = null;
        }
        if (mClientPrivateKeyAlias != null) {
            if (!mKeyStore.removeEntryFromKeyStore(mClientPrivateKeyAlias)) {
                Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
            }
            mClientPrivateKeyAlias = null;
        }
        if (mClientCertificateAlias != null) {
            if (!mKeyStore.removeEntryFromKeyStore(mClientCertificateAlias)) {
                Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
            }
            mClientCertificateAlias = null;
        }
    }

    /**
     * Return the matching status with the given AP, based on the ANQP elements from the AP.
     * @param anqpElements ANQP elements from the AP
     * @return {@link com.android.server.wifi.hotspot2.PasspointMatch}
     */
    public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements) {
        // TODO(b/33246489): To be implemented.
        return PasspointMatch.None;
    }

    /**
     * Create and return a certificate or key alias name based on the given prefix and uid.
     *
     * @param type The key or certificate type string
     * @param uid The UID of the alias
     * @return String
     */
    private static String formatAliasName(String type, long uid) {
        return type + ALIAS_PREFIX + uid;
    }

    /**
     * Retrieve the client certificate from the certificates chain.  The certificate
     * with the matching SHA256 digest is the client certificate.
     *
     * @param certChain The client certificates chain
     * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
     * @return {@link java.security.cert.X509Certificate}
     */
    private static X509Certificate getClientCertificate(X509Certificate[] certChain,
            byte[] expectedSha256Fingerprint) {
        if (certChain == null) {
            return null;
        }
        try {
            MessageDigest digester = MessageDigest.getInstance("SHA-256");
            for (X509Certificate certificate : certChain) {
                digester.reset();
                byte[] fingerprint = digester.digest(certificate.getEncoded());
                if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
                    return certificate;
                }
            }
        } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
            return null;
        }

        return null;
    }
}