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/. */
7
* Module for handling PGP/MIME signed messages
8
* implemented as JS module
13
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
14
Components.utils.import("resource://enigmail/enigmailCommon.jsm");
16
var EXPORTED_SYMBOLS = [ "EnigmailVerify" ];
18
const Cc = Components.classes;
19
const Ci = Components.interfaces;
20
const Ec = EnigmailCommon;
22
const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
24
const maxBufferLen = 102400;
26
var gDebugLog = false;
28
function MimeVerify(verifyEmbedded, msgUrl)
30
this.verifyEmbedded = verifyEmbedded;
35
// verify the signature of PGP/MIME signed messages
36
MimeVerify.prototype = {
42
statusDisplayed: false,
47
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
49
startStreaming: function(window, msgWindow, msgUriSpec) {
50
DEBUG_LOG("mimeVerify.jsm: startStreaming\n");
52
this.msgWindow = msgWindow;
53
this.msgUriSpec = msgUriSpec;
55
var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
56
var msgSvc = messenger.messageServiceFromURI(this.msgUriSpec);
58
this.inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream),
60
msgSvc.streamMessage(this.msgUriSpec,
69
verifyData: function(window, msgWindow, msgUriSpec, data) {
70
DEBUG_LOG("mimeVerify.jsm: streamFromChannel\n");
72
this.msgWindow = msgWindow;
73
this.msgUriSpec = msgUriSpec;
75
this.onStartRequest();
76
this.onTextData(data);
80
onStartRequest: function() {
81
DEBUG_LOG("mimeVerify.jsm: onStartRequest\n");
83
this.foundMsg = false;
84
this.startMsgStr = "";
88
this.closePipe = false;
94
this.returnStatus = null;
95
this.statusDisplayed = false;
97
if (!this.verifyEmbedded) this.startVerification();
100
onDataAvailable: function(req, sup, stream, offset, count) {
101
DEBUG_LOG("mimeVerify.jsm: onDataAvailable: "+count+"\n");
102
this.inStream.init(stream);
103
var data = this.inStream.read(count);
104
this.onTextData(data);
107
onTextData: function(data) {
108
DEBUG_LOG("mimeVerify.jsm: onTextData\n");
109
if (!this.foundMsg) {
110
// check if mime part could be pgp/mime signed message
111
if (this.dataCount > 10240) return;
112
this.startMsgStr += data;
113
let i = this.startMsgStr.search(/^content-type:/im);
115
let s = data.substr(i).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
116
if (s.search(/multipart\/signed/i) > 0 &&
117
s.search(/micalg\s*=\s*[\"\']?pgp-[\"\']?/i) > 0 &&
118
s.search(/protocol\s*=\s*[\'\"]application\/pgp-signature[\"\']/i) > 0) {
120
DEBUG_LOG("mimeVerify.jsm: onTextData: found PGP/MIME signed message\n");
121
this.foundMsg = true;
122
let hdr = getHeaderData(s);
123
this.boundary = hdr["boundary"].replace(/[\'\"]/g, "");
124
this.hash = hdr["micalg"].replace(/[\'\"]/g, "").toUpperCase().replace(/^PGP-/, "");
128
this.dataCount += data.length;
130
if (this.verifyEmbedded && this.foundMsg) {
131
// process data as signed message
133
this.startVerification();
137
this.keepData += data;
138
if (this.writeMode == 0) {
140
let i = this.findNextMimePart();
142
i += 2 + this.boundary.length;
143
if (this.keepData[i] == "\n") {
146
else if (this.keepData[i] == "\r") {
148
if (this.keepData[i] == "\n") ++i;
151
this.keepData = this.keepData.substr(i);
152
data = this.keepData;
156
this.keepData = data.substr(-this.boundary.length - 3);
159
if (! this.hash) this.hash = "SHA1";
161
this.writeToPipe("-----BEGIN PGP SIGNED MESSAGE-----\n");
162
this.writeToPipe("Hash: " + this.hash + "\n\n");
165
if (this.writeMode == 1) {
167
let i = this.findNextMimePart();
169
data = this.keepData.substr(0, i);
170
this.keepData = this.keepData.substr(i);
174
data = this.keepData.substr(0, this.keepData.length - this.boundary.length - 3);
175
this.keepData = this.keepData.substr(-this.boundary.length - 3);
177
this.writeToPipe(data.replace(/^-/gm, "- -"));
180
if (this.writeMode == 2) {
181
// signature data "header"
182
let i = this.keepData.search(/^-----BEGIN PGP /m);
184
this.keepData = this.keepData.substr(i);
189
if (this.writeMode == 3) {
191
let i = this.keepData.search(/^-----END PGP /m);
192
if (i >= 0) this.writeMode = 4;
193
this.writeToPipe(this.keepData.substr(0, i + 30));
199
findNextMimePart: function() {
203
let i = this.keepData.indexOf("--"+this.boundary);
204
if (i == 0) startOk = true;
206
if (this.keepData[i-1] == '\r' || this.keepData[i-1] == '\n') startOk = true;
209
if (i + this.boundary.length + 2 < this.keepData) {
210
if (this.keepData[i + this.boundary.length + 2] == '\r' ||
211
this.keepData[i + this.boundary.length + 2] == '\n') endOk = true;
216
if (i >= 0 && startOk && endOk) {
222
startVerification: function() {
223
DEBUG_LOG("mimeVerify.jsm: startVerification\n");
224
var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
225
var win = windowManager.getMostRecentWindow(null);
226
var statusFlagsObj = {};
227
var errorMsgObj = {};
228
this.proc = Ec.decryptMessageStart(win, true, this,
229
statusFlagsObj, errorMsgObj);
232
onStopRequest: function() {
233
DEBUG_LOG("mimeVerify.jsm: onStopRequest\n");
239
this.closePipe = true;
242
writeToPipe: function(str) {
243
//DEBUG_LOG("mimeVerify.jsm: writeToPipe: "+str+"\n");
246
this.outQueue += str;
247
if (this.outQueue.length > maxBufferLen)
251
this.outQueue += str;
254
flushInput: function() {
255
DEBUG_LOG("mimeVerify.jsm: flushInput\n");
256
if (! this.pipe) return;
257
this.pipe.write(this.outQueue);
261
// API for decryptMessage Listener
262
stdin: function(pipe) {
263
DEBUG_LOG("mimeVerify.jsm: stdin\n");
264
if (this.outQueue.length > 0) {
265
pipe.write(this.outQueue);
267
if (this.closePipe) pipe.close();
272
stdout: function(s) {
273
DEBUG_LOG("mimeVerify.jsm: stdout:"+s.length+"\n");
274
this.dataLength += s.length;
277
stderr: function(s) {
278
DEBUG_LOG("mimeVerify.jsm: stderr\n");
282
done: function(exitCode) {
283
DEBUG_LOG("mimeVerify.jsm: done: "+exitCode+"\n");
284
this.exitCode = exitCode;
285
//DEBUG_LOG("mimeVerify.jsm: "+this.statusStr+"\n");
287
this.returnStatus = {};
288
Ec.decryptMessageEnd(this.statusStr,
293
Ci.nsIEnigmail.UI_PGP_MIME,
295
this.displayStatus();
299
setMsgWindow: function(msgWindow, msgUriSpec) {
300
DEBUG_LOG("mimeVerify.jsm: setMsgWindow: "+msgUriSpec+"\n");
302
if (! this.msgWindow) {
303
this.msgWindow = msgWindow;
304
this.msgUriSpec = msgUriSpec;
308
displayStatus: function() {
309
DEBUG_LOG("mimeVerify.jsm: displayStatus\n");
310
if (this.exitCode == null || this.msgWindow == null || this.statusDisplayed)
314
DEBUG_LOG("mimeVerify.jsm: displayStatus displaying result\n");
315
let headerSink = this.msgWindow.msgHeaderSink.securityInfo.QueryInterface(Ci.nsIEnigMimeHeaderSink);
318
headerSink.updateSecurityStatus(this.msgUriSpec,
320
this.returnStatus.statusFlags,
321
this.returnStatus.keyId,
322
this.returnStatus.userId,
323
this.returnStatus.sigDetails,
324
this.returnStatus.errorMsg,
325
this.returnStatus.blockSeparation,
328
this.statusDisplayed = true;
331
Ec.writeException("mimeVerify.jsm", ex);
336
var EnigmailVerify = {
340
setMsgWindow: function(msgWindow, msgUriSpec) {
341
DEBUG_LOG("mimeVerify.jsm: setMsgWindow: "+msgUriSpec+"\n");
343
this.lastMsgWindow = msgWindow;
344
this.lastMsgUri = msgUriSpec;
347
newVerfier: function (embedded, msgUrl) {
348
let v = new MimeVerify(embedded, msgUrl);
354
////////////////////////////////////////////////////////////////////
355
// General-purpose functions, not exported
357
// extract the data fields following a header.
358
// e.g. ContentType: xyz; Aa=b; cc=d
359
// returns aa=b and cc=d in an array of arrays
360
function getHeaderData(data) {
361
DEBUG_LOG("mimeVerify.jsm: getHeaderData: "+data.substr(0, 100)+"\n");
362
var a = data.split(/\n/);
364
for (let i = 0; i < a.length; i++) {
365
if (a[i].length == 0) break;
366
let b = a[i].split(/;/);
368
// extract "abc = xyz" tuples
369
for (let j=0; j < b.length; j++) {
370
let m = b[j].match(/^(\s*)([^=\s;]+)(\s*)(=)(\s*)(.*)(\s*)$/);
372
// m[2]: identifier / m[6]: data
373
res[m[2].toLowerCase()] = m[6].replace(/\s*$/, "");
374
DEBUG_LOG("mimeVerify.jsm: getHeaderData: "+m[2].toLowerCase()+" = "+res[m[2].toLowerCase()] +"\n");
377
if (i == 0 && a[i].indexOf(";") < 0) break;
378
if (i > 0 && a[i].search(/^\s/) < 0) break;
384
function DEBUG_LOG(str) {
385
if (gDebugLog) Ec.DEBUG_LOG(str);
388
function initModule() {
390
var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
391
var nspr_log_modules = env.get("NSPR_LOG_MODULES");
392
var matches = nspr_log_modules.match(/mimeVerify:(\d+)/);
394
if (matches && (matches.length > 1)) {
395
if (matches[1] > 2) gDebugLog = true;
396
dump("mimeVerify.jsm: enabled debug logging\n");
400
dump("caught error "+ex);
405
dump("mimeVerify.jsm: module initialized\n");