Wed, 31 Dec 2014 06:09:35 +0100
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 }