diff -r 000000000000 -r 6474c204b198 mobile/android/base/browserid/JSONWebTokenUtils.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mobile/android/base/browserid/JSONWebTokenUtils.java Wed Dec 31 06:09:35 2014 +0100
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.browserid;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.json.simple.JSONObject;
+import org.json.simple.parser.ParseException;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.apache.commons.codec.binary.StringUtils;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.NonObjectJSONException;
+import org.mozilla.gecko.sync.Utils;
+
+/**
+ * Encode and decode JSON Web Tokens.
+ *
+ * Reverse-engineered from the Node.js jwcrypto library at
+ * https://github.com/mozilla/jwcrypto
+ * and informed by the informal draft standard "JSON Web Token (JWT)" at
+ * http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html.
+ */
+public class JSONWebTokenUtils {
+ public static final long DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
+ public static final long DEFAULT_ASSERTION_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
+ public static final long DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS = 9999999999999L;
+ public static final String DEFAULT_CERTIFICATE_ISSUER = "127.0.0.1";
+ public static final String DEFAULT_ASSERTION_ISSUER = "127.0.0.1";
+
+ public static String encode(String payload, SigningPrivateKey privateKey) throws UnsupportedEncodingException, GeneralSecurityException {
+ return encode(payload, privateKey, null);
+ }
+
+ protected static String encode(String payload, SigningPrivateKey privateKey, Map headerFields) throws UnsupportedEncodingException, GeneralSecurityException {
+ ExtendedJSONObject header = new ExtendedJSONObject();
+ if (headerFields != null) {
+ header.putAll(headerFields);
+ }
+ header.put("alg", privateKey.getAlgorithm());
+ String encodedHeader = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes("UTF-8"));
+ String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
+ ArrayList segments = new ArrayList();
+ segments.add(encodedHeader);
+ segments.add(encodedPayload);
+ byte[] message = Utils.toDelimitedString(".", segments).getBytes("UTF-8");
+ byte[] signature = privateKey.signMessage(message);
+ segments.add(Base64.encodeBase64URLSafeString(signature));
+ return Utils.toDelimitedString(".", segments);
+ }
+
+ public static String decode(String token, VerifyingPublicKey publicKey) throws GeneralSecurityException, UnsupportedEncodingException {
+ if (token == null) {
+ throw new IllegalArgumentException("token must not be null");
+ }
+ String[] segments = token.split("\\.");
+ if (segments == null || segments.length != 3) {
+ throw new GeneralSecurityException("malformed token");
+ }
+ byte[] message = (segments[0] + "." + segments[1]).getBytes("UTF-8");
+ byte[] signature = Base64.decodeBase64(segments[2]);
+ boolean verifies = publicKey.verifyMessage(message, signature);
+ if (!verifies) {
+ throw new GeneralSecurityException("bad signature");
+ }
+ String payload = StringUtils.newStringUtf8(Base64.decodeBase64(segments[1]));
+ return payload;
+ }
+
+ /**
+ * Public for testing.
+ */
+ @SuppressWarnings("unchecked")
+ public static String getPayloadString(String payloadString, String audience, String issuer,
+ Long issuedAt, long expiresAt) throws NonObjectJSONException,
+ IOException, ParseException {
+ ExtendedJSONObject payload;
+ if (payloadString != null) {
+ payload = new ExtendedJSONObject(payloadString);
+ } else {
+ payload = new ExtendedJSONObject();
+ }
+ if (audience != null) {
+ payload.put("aud", audience);
+ }
+ payload.put("iss", issuer);
+ if (issuedAt != null) {
+ payload.put("iat", issuedAt);
+ }
+ payload.put("exp", expiresAt);
+ // TreeMap so that keys are sorted. A small attempt to keep output stable over time.
+ return JSONObject.toJSONString(new TreeMap