|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.fxa.login; |
|
6 |
|
7 import java.io.IOException; |
|
8 import java.io.UnsupportedEncodingException; |
|
9 import java.security.GeneralSecurityException; |
|
10 import java.security.InvalidKeyException; |
|
11 import java.security.NoSuchAlgorithmException; |
|
12 import java.util.ArrayList; |
|
13 import java.util.Collections; |
|
14 import java.util.HashMap; |
|
15 |
|
16 import org.json.simple.parser.ParseException; |
|
17 import org.mozilla.gecko.background.fxa.FxAccountUtils; |
|
18 import org.mozilla.gecko.browserid.BrowserIDKeyPair; |
|
19 import org.mozilla.gecko.browserid.JSONWebTokenUtils; |
|
20 import org.mozilla.gecko.fxa.FxAccountConstants; |
|
21 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate; |
|
22 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage; |
|
23 import org.mozilla.gecko.sync.ExtendedJSONObject; |
|
24 import org.mozilla.gecko.sync.NonObjectJSONException; |
|
25 import org.mozilla.gecko.sync.Utils; |
|
26 import org.mozilla.gecko.sync.crypto.KeyBundle; |
|
27 |
|
28 public class Married extends TokensAndKeysState { |
|
29 private static final String LOG_TAG = Married.class.getSimpleName(); |
|
30 |
|
31 protected final String certificate; |
|
32 protected final String clientState; |
|
33 |
|
34 public Married(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair, String certificate) { |
|
35 super(StateLabel.Married, email, uid, sessionToken, kA, kB, keyPair); |
|
36 Utils.throwIfNull(certificate); |
|
37 this.certificate = certificate; |
|
38 try { |
|
39 this.clientState = FxAccountUtils.computeClientState(kB); |
|
40 } catch (NoSuchAlgorithmException e) { |
|
41 // This should never occur. |
|
42 throw new IllegalStateException("Unable to compute client state from kB."); |
|
43 } |
|
44 } |
|
45 |
|
46 @Override |
|
47 public ExtendedJSONObject toJSONObject() { |
|
48 ExtendedJSONObject o = super.toJSONObject(); |
|
49 // Fields are non-null by constructor. |
|
50 o.put("certificate", certificate); |
|
51 return o; |
|
52 } |
|
53 |
|
54 @Override |
|
55 public void execute(final ExecuteDelegate delegate) { |
|
56 delegate.handleTransition(new LogMessage("staying married"), this); |
|
57 } |
|
58 |
|
59 public String generateAssertion(String audience, String issuer) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException { |
|
60 // We generate assertions with no iat and an exp after 2050 to avoid |
|
61 // invalid-timestamp errors from the token server. |
|
62 final long expiresAt = JSONWebTokenUtils.DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS; |
|
63 String assertion = JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience, issuer, null, expiresAt); |
|
64 if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) { |
|
65 return assertion; |
|
66 } |
|
67 |
|
68 try { |
|
69 FxAccountConstants.pii(LOG_TAG, "Generated assertion: " + assertion); |
|
70 ExtendedJSONObject a = JSONWebTokenUtils.parseAssertion(assertion); |
|
71 if (a != null) { |
|
72 FxAccountConstants.pii(LOG_TAG, "aHeader : " + a.getObject("header")); |
|
73 FxAccountConstants.pii(LOG_TAG, "aPayload : " + a.getObject("payload")); |
|
74 FxAccountConstants.pii(LOG_TAG, "aSignature: " + a.getString("signature")); |
|
75 String certificate = a.getString("certificate"); |
|
76 if (certificate != null) { |
|
77 ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate); |
|
78 FxAccountConstants.pii(LOG_TAG, "cHeader : " + c.getObject("header")); |
|
79 FxAccountConstants.pii(LOG_TAG, "cPayload : " + c.getObject("payload")); |
|
80 FxAccountConstants.pii(LOG_TAG, "cSignature: " + c.getString("signature")); |
|
81 // Print the relevant timestamps in sorted order with labels. |
|
82 HashMap<Long, String> map = new HashMap<Long, String>(); |
|
83 map.put(a.getObject("payload").getLong("iat"), "aiat"); |
|
84 map.put(a.getObject("payload").getLong("exp"), "aexp"); |
|
85 map.put(c.getObject("payload").getLong("iat"), "ciat"); |
|
86 map.put(c.getObject("payload").getLong("exp"), "cexp"); |
|
87 ArrayList<Long> values = new ArrayList<Long>(map.keySet()); |
|
88 Collections.sort(values); |
|
89 for (Long value : values) { |
|
90 FxAccountConstants.pii(LOG_TAG, map.get(value) + ": " + value); |
|
91 } |
|
92 } else { |
|
93 FxAccountConstants.pii(LOG_TAG, "Could not parse certificate!"); |
|
94 } |
|
95 } else { |
|
96 FxAccountConstants.pii(LOG_TAG, "Could not parse assertion!"); |
|
97 } |
|
98 } catch (Exception e) { |
|
99 FxAccountConstants.pii(LOG_TAG, "Got exception dumping assertion debug info."); |
|
100 } |
|
101 return assertion; |
|
102 } |
|
103 |
|
104 public KeyBundle getSyncKeyBundle() throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException { |
|
105 // TODO Document this choice for deriving from kB. |
|
106 return FxAccountUtils.generateSyncKeyBundle(kB); |
|
107 } |
|
108 |
|
109 public String getClientState() { |
|
110 if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { |
|
111 FxAccountConstants.pii(LOG_TAG, "Client state: " + this.clientState); |
|
112 } |
|
113 return this.clientState; |
|
114 } |
|
115 |
|
116 public State makeCohabitingState() { |
|
117 return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair); |
|
118 } |
|
119 } |