1package com.android.server.wifi.hotspot2.pps;
2
3import android.net.wifi.WifiEnterpriseConfig;
4import android.security.Credentials;
5import android.security.KeyStore;
6import android.text.TextUtils;
7import android.util.Base64;
8import android.util.Log;
9
10import com.android.server.wifi.IMSIParameter;
11import com.android.server.wifi.anqp.eap.EAP;
12import com.android.server.wifi.anqp.eap.EAPMethod;
13import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
14import com.android.server.wifi.hotspot2.Utils;
15import com.android.server.wifi.hotspot2.omadm.OMAException;
16
17import java.io.IOException;
18import java.nio.charset.StandardCharsets;
19import java.security.GeneralSecurityException;
20import java.security.MessageDigest;
21import java.util.Arrays;
22
23public class Credential {
24    public enum CertType {IEEE, x509v3}
25
26    public static final String CertTypeX509 = "x509v3";
27    public static final String CertTypeIEEE = "802.1ar";
28
29    private final long mCtime;
30    private final long mExpTime;
31    private final String mRealm;
32    private final boolean mCheckAAACert;
33
34    private final String mUserName;
35    private final String mPassword;
36    private final boolean mDisregardPassword;
37    private final boolean mMachineManaged;
38    private final String mSTokenApp;
39    private final boolean mShare;
40    private final EAPMethod mEAPMethod;
41
42    private final CertType mCertType;
43    private final byte[] mFingerPrint;
44
45    private final IMSIParameter mImsi;
46
47    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
48                      EAPMethod eapMethod, String userName, String password,
49                      boolean machineManaged, String stApp, boolean share) {
50        mCtime = ctime;
51        mExpTime = expTime;
52        mRealm = realm;
53        mCheckAAACert = checkAAACert;
54        mEAPMethod = eapMethod;
55        mUserName = userName;
56
57        if (!TextUtils.isEmpty(password)) {
58            byte[] pwOctets = Base64.decode(password, Base64.DEFAULT);
59            mPassword = new String(pwOctets, StandardCharsets.UTF_8);
60        } else {
61            mPassword = null;
62        }
63        mDisregardPassword = false;
64
65        mMachineManaged = machineManaged;
66        mSTokenApp = stApp;
67        mShare = share;
68
69        mCertType = null;
70        mFingerPrint = null;
71
72        mImsi = null;
73    }
74
75    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
76                      EAPMethod eapMethod, Credential.CertType certType, byte[] fingerPrint) {
77        mCtime = ctime;
78        mExpTime = expTime;
79        mRealm = realm;
80        mCheckAAACert = checkAAACert;
81        mEAPMethod = eapMethod;
82        mCertType = certType;
83        mFingerPrint = fingerPrint;
84
85        mUserName = null;
86        mPassword = null;
87        mDisregardPassword = false;
88        mMachineManaged = false;
89        mSTokenApp = null;
90        mShare = false;
91
92        mImsi = null;
93    }
94
95    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
96                      EAPMethod eapMethod, IMSIParameter imsi) {
97        mCtime = ctime;
98        mExpTime = expTime;
99        mRealm = realm;
100        mCheckAAACert = checkAAACert;
101        mEAPMethod = eapMethod;
102        mImsi = imsi;
103
104        mCertType = null;
105        mFingerPrint = null;
106
107        mUserName = null;
108        mPassword = null;
109        mDisregardPassword = false;
110        mMachineManaged = false;
111        mSTokenApp = null;
112        mShare = false;
113    }
114
115    public Credential(Credential other, String password) {
116        mCtime = other.mCtime;
117        mExpTime = other.mExpTime;
118        mRealm = other.mRealm;
119        mCheckAAACert = other.mCheckAAACert;
120        mUserName = other.mUserName;
121        mPassword = password;
122        mDisregardPassword = other.mDisregardPassword;
123        mMachineManaged = other.mMachineManaged;
124        mSTokenApp = other.mSTokenApp;
125        mShare = other.mShare;
126        mEAPMethod = other.mEAPMethod;
127        mCertType = other.mCertType;
128        mFingerPrint = other.mFingerPrint;
129        mImsi = other.mImsi;
130    }
131
132    public Credential(WifiEnterpriseConfig enterpriseConfig, KeyStore keyStore, boolean update)
133            throws IOException {
134        mCtime = Utils.UNSET_TIME;
135        mExpTime = Utils.UNSET_TIME;
136        mRealm = enterpriseConfig.getRealm();
137        mCheckAAACert = false;
138        mEAPMethod = mapEapMethod(enterpriseConfig.getEapMethod(),
139                enterpriseConfig.getPhase2Method());
140        mCertType = mEAPMethod.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS ? CertType.x509v3 : null;
141        byte[] fingerPrint;
142
143        if (enterpriseConfig.getClientCertificate() != null) {
144            // !!! Not sure this will be true in any practical instances:
145            try {
146                MessageDigest digester = MessageDigest.getInstance("SHA-256");
147                fingerPrint = digester.digest(enterpriseConfig.getClientCertificate().getEncoded());
148            } catch (GeneralSecurityException gse) {
149                Log.e(Utils.hs2LogTag(getClass()),
150                        "Failed to generate certificate fingerprint: " + gse);
151                fingerPrint = null;
152            }
153        } else if (enterpriseConfig.getClientCertificateAlias() != null) {
154            String alias = enterpriseConfig.getClientCertificateAlias();
155            byte[] octets = keyStore.get(Credentials.USER_CERTIFICATE + alias);
156            if (octets != null) {
157                try {
158                    MessageDigest digester = MessageDigest.getInstance("SHA-256");
159                    fingerPrint = digester.digest(octets);
160                } catch (GeneralSecurityException gse) {
161                    Log.e(Utils.hs2LogTag(getClass()), "Failed to construct digest: " + gse);
162                    fingerPrint = null;
163                }
164            } else // !!! The current alias is *not* derived from the fingerprint...
165            {
166                try {
167                    fingerPrint = Base64.decode(enterpriseConfig.getClientCertificateAlias(),
168                            Base64.DEFAULT);
169                } catch (IllegalArgumentException ie) {
170                    Log.e(Utils.hs2LogTag(getClass()), "Bad base 64 alias");
171                    fingerPrint = null;
172                }
173            }
174        } else {
175            fingerPrint = null;
176        }
177        mFingerPrint = fingerPrint;
178        String imsi = enterpriseConfig.getPlmn();
179        mImsi = imsi == null || imsi.length() == 0 ? null : new IMSIParameter(imsi);
180        mUserName = enterpriseConfig.getIdentity();
181        mPassword = enterpriseConfig.getPassword();
182        mDisregardPassword = update && mPassword.length() < 2;
183        mMachineManaged = false;
184        mSTokenApp = null;
185        mShare = false;
186    }
187
188    public static CertType mapCertType(String certType) throws OMAException {
189        if (certType.equalsIgnoreCase(CertTypeX509)) {
190            return CertType.x509v3;
191        } else if (certType.equalsIgnoreCase(CertTypeIEEE)) {
192            return CertType.IEEE;
193        } else {
194            throw new OMAException("Invalid cert type: '" + certType + "'");
195        }
196    }
197
198    private static EAPMethod mapEapMethod(int eapMethod, int phase2Method) throws IOException {
199        switch (eapMethod) {
200            case WifiEnterpriseConfig.Eap.TLS:
201                return new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
202            case WifiEnterpriseConfig.Eap.TTLS:
203            /* keep this table in sync with WifiEnterpriseConfig.Phase2 enum */
204                NonEAPInnerAuth inner;
205                switch (phase2Method) {
206                    case WifiEnterpriseConfig.Phase2.PAP:
207                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.PAP);
208                        break;
209                    case WifiEnterpriseConfig.Phase2.MSCHAP:
210                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAP);
211                        break;
212                    case WifiEnterpriseConfig.Phase2.MSCHAPV2:
213                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAPv2);
214                        break;
215                    default:
216                        throw new IOException("TTLS phase2 method " +
217                                phase2Method + " not valid for Passpoint");
218                }
219                return new EAPMethod(EAP.EAPMethodID.EAP_TTLS, inner);
220            case WifiEnterpriseConfig.Eap.SIM:
221                return new EAPMethod(EAP.EAPMethodID.EAP_SIM, null);
222            case WifiEnterpriseConfig.Eap.AKA:
223                return new EAPMethod(EAP.EAPMethodID.EAP_AKA, null);
224            case WifiEnterpriseConfig.Eap.AKA_PRIME:
225                return new EAPMethod(EAP.EAPMethodID.EAP_AKAPrim, null);
226            default:
227                String methodName;
228                if (eapMethod >= 0 && eapMethod < WifiEnterpriseConfig.Eap.strings.length) {
229                    methodName = WifiEnterpriseConfig.Eap.strings[eapMethod];
230                } else {
231                    methodName = Integer.toString(eapMethod);
232                }
233                throw new IOException("EAP method id " + methodName + " is not valid for Passpoint");
234        }
235    }
236
237    public EAPMethod getEAPMethod() {
238        return mEAPMethod;
239    }
240
241    public String getRealm() {
242        return mRealm;
243    }
244
245    public IMSIParameter getImsi() {
246        return mImsi;
247    }
248
249    public String getUserName() {
250        return mUserName;
251    }
252
253    public String getPassword() {
254        return mPassword;
255    }
256
257    public boolean hasDisregardPassword() {
258        return mDisregardPassword;
259    }
260
261    public CertType getCertType() {
262        return mCertType;
263    }
264
265    public byte[] getFingerPrint() {
266        return mFingerPrint;
267    }
268
269    public long getCtime() {
270        return mCtime;
271    }
272
273    public long getExpTime() {
274        return mExpTime;
275    }
276
277    @Override
278    public boolean equals(Object o) {
279        if (this == o) return true;
280        if (o == null || getClass() != o.getClass()) return false;
281
282        Credential that = (Credential) o;
283
284        if (mCheckAAACert != that.mCheckAAACert) return false;
285        if (mCtime != that.mCtime) return false;
286        if (mExpTime != that.mExpTime) return false;
287        if (mMachineManaged != that.mMachineManaged) return false;
288        if (mShare != that.mShare) return false;
289        if (mCertType != that.mCertType) return false;
290        if (!mEAPMethod.equals(that.mEAPMethod)) return false;
291        if (!Arrays.equals(mFingerPrint, that.mFingerPrint)) return false;
292        if (!safeEquals(mImsi, that.mImsi)) {
293            return false;
294        }
295
296        if (!mDisregardPassword && !safeEquals(mPassword, that.mPassword)) {
297            return false;
298        }
299
300        if (!mRealm.equals(that.mRealm)) return false;
301        if (!safeEquals(mSTokenApp, that.mSTokenApp)) {
302            return false;
303        }
304        if (!safeEquals(mUserName, that.mUserName)) {
305            return false;
306        }
307
308        return true;
309    }
310
311    private static boolean safeEquals(Object s1, Object s2) {
312        if (s1 == null) {
313            return s2 == null;
314        }
315        else {
316            return s2 != null && s1.equals(s2);
317        }
318    }
319
320    @Override
321    public int hashCode() {
322        int result = (int) (mCtime ^ (mCtime >>> 32));
323        result = 31 * result + (int) (mExpTime ^ (mExpTime >>> 32));
324        result = 31 * result + mRealm.hashCode();
325        result = 31 * result + (mCheckAAACert ? 1 : 0);
326        result = 31 * result + (mUserName != null ? mUserName.hashCode() : 0);
327        result = 31 * result + (mPassword != null ? mPassword.hashCode() : 0);
328        result = 31 * result + (mMachineManaged ? 1 : 0);
329        result = 31 * result + (mSTokenApp != null ? mSTokenApp.hashCode() : 0);
330        result = 31 * result + (mShare ? 1 : 0);
331        result = 31 * result + mEAPMethod.hashCode();
332        result = 31 * result + (mCertType != null ? mCertType.hashCode() : 0);
333        result = 31 * result + (mFingerPrint != null ? Arrays.hashCode(mFingerPrint) : 0);
334        result = 31 * result + (mImsi != null ? mImsi.hashCode() : 0);
335        return result;
336    }
337
338    @Override
339    public String toString() {
340        return "Credential{" +
341                "mCtime=" + Utils.toUTCString(mCtime) +
342                ", mExpTime=" + Utils.toUTCString(mExpTime) +
343                ", mRealm='" + mRealm + '\'' +
344                ", mCheckAAACert=" + mCheckAAACert +
345                ", mUserName='" + mUserName + '\'' +
346                ", mPassword='" + mPassword + '\'' +
347                ", mDisregardPassword=" + mDisregardPassword +
348                ", mMachineManaged=" + mMachineManaged +
349                ", mSTokenApp='" + mSTokenApp + '\'' +
350                ", mShare=" + mShare +
351                ", mEAPMethod=" + mEAPMethod +
352                ", mCertType=" + mCertType +
353                ", mFingerPrint=" + Utils.toHexString(mFingerPrint) +
354                ", mImsi='" + mImsi + '\'' +
355                '}';
356    }
357}
358