mobile/android/base/browserid/JSONWebTokenUtils.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     5 package org.mozilla.gecko.browserid;
     7 import java.io.IOException;
     8 import java.io.UnsupportedEncodingException;
     9 import java.security.GeneralSecurityException;
    10 import java.util.ArrayList;
    11 import java.util.Map;
    12 import java.util.TreeMap;
    14 import org.json.simple.JSONObject;
    15 import org.json.simple.parser.ParseException;
    16 import org.mozilla.apache.commons.codec.binary.Base64;
    17 import org.mozilla.apache.commons.codec.binary.StringUtils;
    18 import org.mozilla.gecko.sync.ExtendedJSONObject;
    19 import org.mozilla.gecko.sync.NonObjectJSONException;
    20 import org.mozilla.gecko.sync.Utils;
    22 /**
    23  * Encode and decode JSON Web Tokens.
    24  * <p>
    25  * Reverse-engineered from the Node.js jwcrypto library at
    26  * <a href="https://github.com/mozilla/jwcrypto">https://github.com/mozilla/jwcrypto</a>
    27  * and informed by the informal draft standard "JSON Web Token (JWT)" at
    28  * <a href="http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html">http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html</a>.
    29  */
    30 public class JSONWebTokenUtils {
    31   public static final long DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
    32   public static final long DEFAULT_ASSERTION_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
    33   public static final long DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS = 9999999999999L;
    34   public static final String DEFAULT_CERTIFICATE_ISSUER = "127.0.0.1";
    35   public static final String DEFAULT_ASSERTION_ISSUER = "127.0.0.1";
    37   public static String encode(String payload, SigningPrivateKey privateKey) throws UnsupportedEncodingException, GeneralSecurityException  {
    38     return encode(payload, privateKey, null);
    39   }
    41   protected static String encode(String payload, SigningPrivateKey privateKey, Map<String, Object> headerFields) throws UnsupportedEncodingException, GeneralSecurityException  {
    42     ExtendedJSONObject header = new ExtendedJSONObject();
    43     if (headerFields != null) {
    44       header.putAll(headerFields);
    45     }
    46     header.put("alg", privateKey.getAlgorithm());
    47     String encodedHeader  = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes("UTF-8"));
    48     String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
    49     ArrayList<String> segments = new ArrayList<String>();
    50     segments.add(encodedHeader);
    51     segments.add(encodedPayload);
    52     byte[] message = Utils.toDelimitedString(".", segments).getBytes("UTF-8");
    53     byte[] signature = privateKey.signMessage(message);
    54     segments.add(Base64.encodeBase64URLSafeString(signature));
    55     return Utils.toDelimitedString(".", segments);
    56   }
    58   public static String decode(String token, VerifyingPublicKey publicKey) throws GeneralSecurityException, UnsupportedEncodingException  {
    59     if (token == null) {
    60       throw new IllegalArgumentException("token must not be null");
    61     }
    62     String[] segments = token.split("\\.");
    63     if (segments == null || segments.length != 3) {
    64       throw new GeneralSecurityException("malformed token");
    65     }
    66     byte[] message = (segments[0] + "." + segments[1]).getBytes("UTF-8");
    67     byte[] signature = Base64.decodeBase64(segments[2]);
    68     boolean verifies = publicKey.verifyMessage(message, signature);
    69     if (!verifies) {
    70       throw new GeneralSecurityException("bad signature");
    71     }
    72     String payload = StringUtils.newStringUtf8(Base64.decodeBase64(segments[1]));
    73     return payload;
    74   }
    76   /**
    77    * Public for testing.
    78    */
    79   @SuppressWarnings("unchecked")
    80   public static String getPayloadString(String payloadString, String audience, String issuer,
    81       Long issuedAt, long expiresAt) throws NonObjectJSONException,
    82       IOException, ParseException {
    83     ExtendedJSONObject payload;
    84     if (payloadString != null) {
    85       payload = new ExtendedJSONObject(payloadString);
    86     } else {
    87       payload = new ExtendedJSONObject();
    88     }
    89     if (audience != null) {
    90       payload.put("aud", audience);
    91     }
    92     payload.put("iss", issuer);
    93     if (issuedAt != null) {
    94       payload.put("iat", issuedAt);
    95     }
    96     payload.put("exp", expiresAt);
    97     // TreeMap so that keys are sorted. A small attempt to keep output stable over time.
    98     return JSONObject.toJSONString(new TreeMap<Object, Object>(payload.object));
    99   }
   101   protected static String getCertificatePayloadString(VerifyingPublicKey publicKeyToSign, String email) throws NonObjectJSONException, IOException, ParseException  {
   102     ExtendedJSONObject payload = new ExtendedJSONObject();
   103     ExtendedJSONObject principal = new ExtendedJSONObject();
   104     principal.put("email", email);
   105     payload.put("principal", principal);
   106     payload.put("public-key", publicKeyToSign.toJSONObject());
   107     return payload.toJSONString();
   108   }
   110   public static String createCertificate(VerifyingPublicKey publicKeyToSign, String email,
   111       String issuer, long issuedAt, long expiresAt, SigningPrivateKey privateKey) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException  {
   112     String certificatePayloadString = getCertificatePayloadString(publicKeyToSign, email);
   113     String payloadString = getPayloadString(certificatePayloadString, null, issuer, issuedAt, expiresAt);
   114     return JSONWebTokenUtils.encode(payloadString, privateKey);
   115   }
   117   /**
   118    * Create a Browser ID assertion.
   119    *
   120    * @param privateKeyToSignWith
   121    *          private key to sign assertion with.
   122    * @param certificate
   123    *          to include in assertion; no attempt is made to ensure the
   124    *          certificate is valid, or corresponds to the private key, or any
   125    *          other condition.
   126    * @param audience
   127    *          to produce assertion for.
   128    * @param issuer
   129    *          to produce assertion for.
   130    * @param issuedAt
   131    *          timestamp for assertion, in milliseconds since the epoch; if null,
   132    *          no timestamp is included.
   133    * @param expiresAt
   134    *          expiration timestamp for assertion, in milliseconds since the epoch.
   135    * @return assertion.
   136    * @throws NonObjectJSONException
   137    * @throws IOException
   138    * @throws ParseException
   139    * @throws GeneralSecurityException
   140    */
   141   public static String createAssertion(SigningPrivateKey privateKeyToSignWith, String certificate, String audience,
   142       String issuer, Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException  {
   143     String emptyAssertionPayloadString = "{}";
   144     String payloadString = getPayloadString(emptyAssertionPayloadString, audience, issuer, issuedAt, expiresAt);
   145     String signature = JSONWebTokenUtils.encode(payloadString, privateKeyToSignWith);
   146     return certificate + "~" + signature;
   147   }
   149   /**
   150    * For debugging only!
   151    *
   152    * @param input
   153    *          certificate to dump.
   154    * @return non-null object with keys header, payload, signature if the
   155    *         certificate is well-formed.
   156    */
   157   public static ExtendedJSONObject parseCertificate(String input) {
   158     try {
   159       String[] parts = input.split("\\.");
   160       if (parts.length != 3) {
   161         return null;
   162       }
   163       String cHeader = new String(Base64.decodeBase64(parts[0]));
   164       String cPayload = new String(Base64.decodeBase64(parts[1]));
   165       String cSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
   166       ExtendedJSONObject o = new ExtendedJSONObject();
   167       o.put("header", new ExtendedJSONObject(cHeader));
   168       o.put("payload", new ExtendedJSONObject(cPayload));
   169       o.put("signature", cSignature);
   170       return o;
   171     } catch (Exception e) {
   172       return null;
   173     }
   174   }
   176   /**
   177    * For debugging only!
   178    *
   179    * @param input certificate to dump.
   180    * @return true if the certificate is well-formed.
   181    */
   182   public static boolean dumpCertificate(String input) {
   183     ExtendedJSONObject c = parseCertificate(input);
   184     try {
   185       if (c == null) {
   186         System.out.println("Malformed certificate -- got exception trying to dump contents.");
   187         return false;
   188       }
   189       System.out.println("certificate header:    " + c.getString("header"));
   190       System.out.println("certificate payload:   " + c.getString("payload"));
   191       System.out.println("certificate signature: " + c.getString("signature"));
   192       return true;
   193     } catch (Exception e) {
   194       System.out.println("Malformed certificate -- got exception trying to dump contents.");
   195       return false;
   196     }
   197   }
   199   /**
   200    * For debugging only!
   201    *
   202    * @param input assertion to dump.
   203    * @return true if the assertion is well-formed.
   204    */
   205   public static ExtendedJSONObject parseAssertion(String input) {
   206     try {
   207       String[] parts = input.split("~");
   208       if (parts.length != 2) {
   209         return null;
   210       }
   211       String certificate = parts[0];
   212       String assertion = parts[1];
   213       parts = assertion.split("\\.");
   214       if (parts.length != 3) {
   215         return null;
   216       }
   217       String aHeader = new String(Base64.decodeBase64(parts[0]));
   218       String aPayload = new String(Base64.decodeBase64(parts[1]));
   219       String aSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
   220       // We do all the assertion parsing *before* dumping the certificate in
   221       // case there's a malformed assertion.
   222       ExtendedJSONObject o = new ExtendedJSONObject();
   223       o.put("header", new ExtendedJSONObject(aHeader));
   224       o.put("payload", new ExtendedJSONObject(aPayload));
   225       o.put("signature", aSignature);
   226       o.put("certificate", certificate);
   227       return o;
   228     } catch (Exception e) {
   229       return null;
   230     }
   231   }
   233   /**
   234    * For debugging only!
   235    *
   236    * @param input assertion to dump.
   237    * @return true if the assertion is well-formed.
   238    */
   239   public static boolean dumpAssertion(String input) {
   240     ExtendedJSONObject a = parseAssertion(input);
   241     try {
   242       if (a == null) {
   243         System.out.println("Malformed assertion -- got exception trying to dump contents.");
   244         return false;
   245       }
   246       dumpCertificate(a.getString("certificate"));
   247       System.out.println("assertion   header:    " + a.getString("header"));
   248       System.out.println("assertion   payload:   " + a.getString("payload"));
   249       System.out.println("assertion   signature: " + a.getString("signature"));
   250       return true;
   251     } catch (Exception e) {
   252       System.out.println("Malformed assertion -- got exception trying to dump contents.");
   253       return false;
   254     }
   255   }
   256 }

mercurial