|
1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 |
|
10 const Cu = Components.utils; |
|
11 const Ci = Components.interfaces; |
|
12 const Cc = Components.classes; |
|
13 const Cr = Components.results; |
|
14 |
|
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
16 Cu.import("resource://gre/modules/Services.jsm"); |
|
17 Cu.import("resource://gre/modules/identity/LogUtils.jsm"); |
|
18 |
|
19 XPCOMUtils.defineLazyServiceGetter(this, |
|
20 "IdentityCryptoService", |
|
21 "@mozilla.org/identity/crypto-service;1", |
|
22 "nsIIdentityCryptoService"); |
|
23 |
|
24 this.EXPORTED_SYMBOLS = ["jwcrypto"]; |
|
25 |
|
26 const ALGORITHMS = { RS256: "RS256", DS160: "DS160" }; |
|
27 const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime |
|
28 |
|
29 function log(...aMessageArgs) { |
|
30 Logger.log.apply(Logger, ["jwcrypto"].concat(aMessageArgs)); |
|
31 } |
|
32 |
|
33 function generateKeyPair(aAlgorithmName, aCallback) { |
|
34 log("Generate key pair; alg =", aAlgorithmName); |
|
35 |
|
36 IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) { |
|
37 if (!Components.isSuccessCode(rv)) { |
|
38 return aCallback("key generation failed"); |
|
39 } |
|
40 |
|
41 var publicKey; |
|
42 |
|
43 switch (aKeyPair.keyType) { |
|
44 case ALGORITHMS.RS256: |
|
45 publicKey = { |
|
46 algorithm: "RS", |
|
47 exponent: aKeyPair.hexRSAPublicKeyExponent, |
|
48 modulus: aKeyPair.hexRSAPublicKeyModulus |
|
49 }; |
|
50 break; |
|
51 |
|
52 case ALGORITHMS.DS160: |
|
53 publicKey = { |
|
54 algorithm: "DS", |
|
55 y: aKeyPair.hexDSAPublicValue, |
|
56 p: aKeyPair.hexDSAPrime, |
|
57 q: aKeyPair.hexDSASubPrime, |
|
58 g: aKeyPair.hexDSAGenerator |
|
59 }; |
|
60 break; |
|
61 |
|
62 default: |
|
63 return aCallback("unknown key type"); |
|
64 } |
|
65 |
|
66 let keyWrapper = { |
|
67 serializedPublicKey: JSON.stringify(publicKey), |
|
68 _kp: aKeyPair |
|
69 }; |
|
70 |
|
71 return aCallback(null, keyWrapper); |
|
72 }); |
|
73 } |
|
74 |
|
75 function sign(aPayload, aKeypair, aCallback) { |
|
76 aKeypair._kp.sign(aPayload, function(rv, signature) { |
|
77 if (!Components.isSuccessCode(rv)) { |
|
78 log("ERROR: signer.sign failed"); |
|
79 return aCallback("Sign failed"); |
|
80 } |
|
81 log("signer.sign: success"); |
|
82 return aCallback(null, signature); |
|
83 }); |
|
84 } |
|
85 |
|
86 function jwcryptoClass() |
|
87 { |
|
88 } |
|
89 |
|
90 jwcryptoClass.prototype = { |
|
91 /* |
|
92 * Determine the expiration of the assertion. Returns expiry date |
|
93 * in milliseconds as integer. |
|
94 * |
|
95 * @param localtimeOffsetMsec (optional) |
|
96 * The number of milliseconds that must be added to the local clock |
|
97 * for it to agree with the server. For example, if the local clock |
|
98 * if two minutes fast, localtimeOffsetMsec would be -120000 |
|
99 * |
|
100 * @param now (options) |
|
101 * Current date in milliseconds. Useful for mocking clock |
|
102 * skew in testing. |
|
103 */ |
|
104 getExpiration: function(duration=DURATION_MS, localtimeOffsetMsec=0, now=Date.now()) { |
|
105 return now + localtimeOffsetMsec + duration; |
|
106 }, |
|
107 |
|
108 isCertValid: function(aCert, aCallback) { |
|
109 // XXX check expiration, bug 769850 |
|
110 aCallback(true); |
|
111 }, |
|
112 |
|
113 generateKeyPair: function(aAlgorithmName, aCallback) { |
|
114 log("generating"); |
|
115 generateKeyPair(aAlgorithmName, aCallback); |
|
116 }, |
|
117 |
|
118 /* |
|
119 * Generate an assertion and return it through the provided callback. |
|
120 * |
|
121 * @param aCert |
|
122 * Identity certificate |
|
123 * |
|
124 * @param aKeyPair |
|
125 * KeyPair object |
|
126 * |
|
127 * @param aAudience |
|
128 * Audience of the assertion |
|
129 * |
|
130 * @param aOptions (optional) |
|
131 * Can include: |
|
132 * { |
|
133 * localtimeOffsetMsec: <clock offset in milliseconds>, |
|
134 * now: <current date in milliseconds> |
|
135 * duration: <validity duration for this assertion in milliseconds> |
|
136 * } |
|
137 * |
|
138 * localtimeOffsetMsec is the number of milliseconds that need to be |
|
139 * added to the local clock time to make it concur with the server. |
|
140 * For example, if the local clock is two minutes fast, the offset in |
|
141 * milliseconds would be -120000. |
|
142 * |
|
143 * @param aCallback |
|
144 * Function to invoke with resulting assertion. Assertion |
|
145 * will be string or null on failure. |
|
146 */ |
|
147 generateAssertion: function(aCert, aKeyPair, aAudience, aOptions, aCallback) { |
|
148 if (typeof aOptions == "function") { |
|
149 aCallback = aOptions; |
|
150 aOptions = { }; |
|
151 } |
|
152 |
|
153 // for now, we hack the algorithm name |
|
154 // XXX bug 769851 |
|
155 var header = {"alg": "DS128"}; |
|
156 var headerBytes = IdentityCryptoService.base64UrlEncode( |
|
157 JSON.stringify(header)); |
|
158 |
|
159 var payload = { |
|
160 exp: this.getExpiration( |
|
161 aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now), |
|
162 aud: aAudience |
|
163 }; |
|
164 var payloadBytes = IdentityCryptoService.base64UrlEncode( |
|
165 JSON.stringify(payload)); |
|
166 |
|
167 log("payload bytes", payload, payloadBytes); |
|
168 sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) { |
|
169 if (err) |
|
170 return aCallback(err); |
|
171 |
|
172 var signedAssertion = headerBytes + "." + payloadBytes + "." + signature; |
|
173 return aCallback(null, aCert + "~" + signedAssertion); |
|
174 }); |
|
175 } |
|
176 |
|
177 }; |
|
178 |
|
179 this.jwcrypto = new jwcryptoClass(); |
|
180 this.jwcrypto.ALGORITHMS = ALGORITHMS; |