michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.fxa.login; michael@0: michael@0: import java.io.IOException; michael@0: import java.io.UnsupportedEncodingException; michael@0: import java.security.GeneralSecurityException; michael@0: import java.security.InvalidKeyException; michael@0: import java.security.NoSuchAlgorithmException; michael@0: import java.util.ArrayList; michael@0: import java.util.Collections; michael@0: import java.util.HashMap; michael@0: michael@0: import org.json.simple.parser.ParseException; michael@0: import org.mozilla.gecko.background.fxa.FxAccountUtils; michael@0: import org.mozilla.gecko.browserid.BrowserIDKeyPair; michael@0: import org.mozilla.gecko.browserid.JSONWebTokenUtils; michael@0: import org.mozilla.gecko.fxa.FxAccountConstants; michael@0: import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate; michael@0: import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage; michael@0: import org.mozilla.gecko.sync.ExtendedJSONObject; michael@0: import org.mozilla.gecko.sync.NonObjectJSONException; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: import org.mozilla.gecko.sync.crypto.KeyBundle; michael@0: michael@0: public class Married extends TokensAndKeysState { michael@0: private static final String LOG_TAG = Married.class.getSimpleName(); michael@0: michael@0: protected final String certificate; michael@0: protected final String clientState; michael@0: michael@0: public Married(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair, String certificate) { michael@0: super(StateLabel.Married, email, uid, sessionToken, kA, kB, keyPair); michael@0: Utils.throwIfNull(certificate); michael@0: this.certificate = certificate; michael@0: try { michael@0: this.clientState = FxAccountUtils.computeClientState(kB); michael@0: } catch (NoSuchAlgorithmException e) { michael@0: // This should never occur. michael@0: throw new IllegalStateException("Unable to compute client state from kB."); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public ExtendedJSONObject toJSONObject() { michael@0: ExtendedJSONObject o = super.toJSONObject(); michael@0: // Fields are non-null by constructor. michael@0: o.put("certificate", certificate); michael@0: return o; michael@0: } michael@0: michael@0: @Override michael@0: public void execute(final ExecuteDelegate delegate) { michael@0: delegate.handleTransition(new LogMessage("staying married"), this); michael@0: } michael@0: michael@0: public String generateAssertion(String audience, String issuer) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException { michael@0: // We generate assertions with no iat and an exp after 2050 to avoid michael@0: // invalid-timestamp errors from the token server. michael@0: final long expiresAt = JSONWebTokenUtils.DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS; michael@0: String assertion = JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience, issuer, null, expiresAt); michael@0: if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) { michael@0: return assertion; michael@0: } michael@0: michael@0: try { michael@0: FxAccountConstants.pii(LOG_TAG, "Generated assertion: " + assertion); michael@0: ExtendedJSONObject a = JSONWebTokenUtils.parseAssertion(assertion); michael@0: if (a != null) { michael@0: FxAccountConstants.pii(LOG_TAG, "aHeader : " + a.getObject("header")); michael@0: FxAccountConstants.pii(LOG_TAG, "aPayload : " + a.getObject("payload")); michael@0: FxAccountConstants.pii(LOG_TAG, "aSignature: " + a.getString("signature")); michael@0: String certificate = a.getString("certificate"); michael@0: if (certificate != null) { michael@0: ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate); michael@0: FxAccountConstants.pii(LOG_TAG, "cHeader : " + c.getObject("header")); michael@0: FxAccountConstants.pii(LOG_TAG, "cPayload : " + c.getObject("payload")); michael@0: FxAccountConstants.pii(LOG_TAG, "cSignature: " + c.getString("signature")); michael@0: // Print the relevant timestamps in sorted order with labels. michael@0: HashMap map = new HashMap(); michael@0: map.put(a.getObject("payload").getLong("iat"), "aiat"); michael@0: map.put(a.getObject("payload").getLong("exp"), "aexp"); michael@0: map.put(c.getObject("payload").getLong("iat"), "ciat"); michael@0: map.put(c.getObject("payload").getLong("exp"), "cexp"); michael@0: ArrayList values = new ArrayList(map.keySet()); michael@0: Collections.sort(values); michael@0: for (Long value : values) { michael@0: FxAccountConstants.pii(LOG_TAG, map.get(value) + ": " + value); michael@0: } michael@0: } else { michael@0: FxAccountConstants.pii(LOG_TAG, "Could not parse certificate!"); michael@0: } michael@0: } else { michael@0: FxAccountConstants.pii(LOG_TAG, "Could not parse assertion!"); michael@0: } michael@0: } catch (Exception e) { michael@0: FxAccountConstants.pii(LOG_TAG, "Got exception dumping assertion debug info."); michael@0: } michael@0: return assertion; michael@0: } michael@0: michael@0: public KeyBundle getSyncKeyBundle() throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException { michael@0: // TODO Document this choice for deriving from kB. michael@0: return FxAccountUtils.generateSyncKeyBundle(kB); michael@0: } michael@0: michael@0: public String getClientState() { michael@0: if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { michael@0: FxAccountConstants.pii(LOG_TAG, "Client state: " + this.clientState); michael@0: } michael@0: return this.clientState; michael@0: } michael@0: michael@0: public State makeCohabitingState() { michael@0: return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair); michael@0: } michael@0: }