Page MenuHome GnuPG

mimeVerify.jsm
No OneTemporary

mimeVerify.jsm

/*global Components: false */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["EnigmailVerify"];
/**
* Module for handling PGP/MIME signed messages
* implemented as JS module
*/
// TODO: Missing features
// - don't attempt to validate forwarded messages unless message is being viewed
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils: false */
Cu.import("resource://enigmail/funcs.jsm"); /*global EnigmailFuncs: false */
Cu.import("resource://enigmail/log.jsm"); /*global EnigmailLog: false */
Cu.import("resource://enigmail/files.jsm"); /*global EnigmailFiles: false */
Cu.import("resource://enigmail/mime.jsm"); /*global EnigmailMime: false */
Cu.import("resource://enigmail/data.jsm"); /*global EnigmailData: false */
Cu.import("resource://enigmail/prefs.jsm"); /*global EnigmailPrefs: false */
Cu.import("resource://enigmail/constants.jsm"); /*global EnigmailConstants: false */
Cu.import("resource://enigmail/decryption.jsm"); /*global EnigmailDecryption: false */
const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
const PGPMIME_PROTO = "application/pgp-signature";
const maxBufferLen = 102400;
var gDebugLog = false;
var gConv = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
// MimeVerify Constructor
function MimeVerify(protocol) {
if (!protocol) {
protocol = PGPMIME_PROTO;
}
this.protocol = protocol;
this.verifyEmbedded = false;
this.partiallySigned = false;
this.inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
}
const EnigmailVerify = {
lastMsgWindow: null,
lastMsgUri: null,
manualMsgUri: null,
currentCtHandler: EnigmailConstants.MIME_HANDLER_UNDEF,
setMsgWindow: function(msgWindow, msgUriSpec) {
LOCAL_DEBUG("mimeVerify.jsm: setMsgWindow: " + msgUriSpec + "\n");
this.lastMsgWindow = msgWindow;
this.lastMsgUri = msgUriSpec;
},
newVerifier: function(protocol) {
EnigmailLog.DEBUG("mimeVerify.jsm: newVerifier: " + (protocol || "null") + "\n");
let v = new MimeVerify(protocol);
return v;
},
setManualUri: function(msgUriSpec) {
LOCAL_DEBUG("mimeVerify.jsm: setManualUri: " + msgUriSpec + "\n");
this.manualMsgUri = msgUriSpec;
},
getManualUri: function() {
EnigmailLog.DEBUG("mimeVerify.jsm: getManualUri\n");
return this.manualMsgUri;
},
/***
* register a PGP/MIME verify object the same way PGP/MIME encrypted mail is handled
*/
registerContentTypeHandler: function() {
EnigmailLog.DEBUG("mimeVerify.jsm: registerContentTypeHandler\n");
let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let pgpMimeClass = Components.classes["@mozilla.org/mimecth;1?type=multipart/encrypted"];
reg.registerFactory(
pgpMimeClass,
"Enigmail PGP/MIME verification",
"@mozilla.org/mimecth;1?type=multipart/signed",
null);
this.currentCtHandler = EnigmailConstants.MIME_HANDLER_PGPMIME;
},
unregisterContentTypeHandler: function() {
EnigmailLog.DEBUG("mimeVerify.jsm: unregisterContentTypeHandler\n");
let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let sMimeClass = Components.classes["@mozilla.org/nsCMSDecoder;1"];
reg.registerFactory(sMimeClass, "S/MIME verification", "@mozilla.org/mimecth;1?type=multipart/signed", null);
this.currentCtHandler = EnigmailConstants.MIME_HANDLER_SMIME;
}
};
// MimeVerify implementation
// verify the signature of PGP/MIME signed messages
MimeVerify.prototype = {
dataCount: 0,
foundMsg: false,
startMsgStr: "",
msgWindow: null,
msgUriSpec: null,
statusDisplayed: false,
exitCode: null,
window: null,
inStream: null,
sigFile: null,
sigData: "",
mimePartNumber: "",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
startStreaming: function(window, msgWindow, msgUriSpec) {
LOCAL_DEBUG("mimeVerify.jsm: startStreaming\n");
this.msgWindow = msgWindow;
this.msgUriSpec = msgUriSpec;
this.window = window;
var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
var msgSvc = messenger.messageServiceFromURI(this.msgUriSpec);
msgSvc.streamMessage(this.msgUriSpec,
this,
this.msgWindow,
null,
false,
null,
false);
},
verifyData: function(window, msgWindow, msgUriSpec, data) {
LOCAL_DEBUG("mimeVerify.jsm: streamFromChannel\n");
this.msgWindow = msgWindow;
this.msgUriSpec = msgUriSpec;
this.window = window;
this.onStartRequest();
this.onTextData(data);
this.onStopRequest();
},
parseContentType: function() {
let contentTypeLine = this.mimeSvc.contentType;
// Eat up CRLF's.
contentTypeLine = contentTypeLine.replace(/[\r\n]/g, "");
EnigmailLog.DEBUG("mimeVerify.jsm: parseContentType: " + contentTypeLine + "\n");
let protoRx = RegExp("protocol\\s*=\\s*[\\'\\\"]" + this.protocol + "[\\\"\\']", "i");
if (contentTypeLine.search(/multipart\/signed/i) >= 0 &&
contentTypeLine.search(protoRx) > 0) {
EnigmailLog.DEBUG("mimeVerify.jsm: parseContentType: found MIME signed message\n");
this.foundMsg = true;
let hdr = EnigmailFuncs.getHeaderData(contentTypeLine);
hdr.boundary = hdr.boundary || "";
hdr.micalg = hdr.micalg || "";
this.boundary = hdr.boundary.replace(/['"]/g, "");
}
},
onStartRequest: function(request, uri) {
EnigmailLog.DEBUG("mimeVerify.jsm: onStartRequest\n"); // always log this one
this.uri = uri ? uri.QueryInterface(Ci.nsIURI).clone() : null;
this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
this.msgUriSpec = EnigmailVerify.lastMsgUri;
if ("mimePart" in this.mimeSvc) {
this.mimePartNumber = this.mimeSvc.mimePart;
}
else {
this.mimePartNumber = "";
}
this.dataCount = 0;
this.foundMsg = false;
this.backgroundJob = false;
this.startMsgStr = "";
this.boundary = "";
this.proc = null;
this.closePipe = false;
this.pipe = null;
this.readMode = 0;
this.keepData = "";
this.last80Chars = "";
this.signedData = "";
this.statusStr = "";
this.returnStatus = null;
this.statusDisplayed = false;
this.protectedHeaders = null;
this.parseContentType();
},
onDataAvailable: function(req, sup, stream, offset, count) {
LOCAL_DEBUG("mimeVerify.jsm: onDataAvailable: " + count + "\n");
if (count > 0) {
this.inStream.init(stream);
var data = this.inStream.read(count);
this.onTextData(data);
}
},
onTextData: function(data) {
LOCAL_DEBUG("mimeVerify.jsm: onTextData\n");
this.dataCount += data.length;
this.keepData += data;
if (this.readMode === 0) {
// header data
let i = this.findNextMimePart();
if (i >= 0) {
i += 2 + this.boundary.length;
if (this.keepData[i] == "\n") {
++i;
}
else if (this.keepData[i] == "\r") {
++i;
if (this.keepData[i] == "\n") ++i;
}
this.keepData = this.keepData.substr(i);
data = this.keepData;
this.readMode = 1;
}
else {
this.keepData = data.substr(-this.boundary.length - 3);
}
}
if (this.readMode === 1) {
// "real data"
if (data.indexOf("-") >= 0) { // only check current line for speed reasons
let i = this.findNextMimePart();
if (i >= 0) {
// end of "read data found"
if (this.keepData[i - 2] == '\r' && this.keepData[i - 1] == '\n') {
--i;
}
this.signedData = this.keepData.substr(0, i - 1);
this.keepData = this.keepData.substr(i);
this.readMode = 2;
}
}
else
return;
}
if (this.readMode === 2) {
let i = this.keepData.indexOf("--" + this.boundary + "--");
if (i >= 0) {
// ensure that we keep everything until we got the "end" boundary
if (this.keepData[i - 2] == '\r' && this.keepData[i - 1] == '\n') {
--i;
}
this.keepData = this.keepData.substr(0, i - 1);
this.readMode = 3;
}
}
if (this.readMode === 3) {
// signature data
if (this.protocol === PGPMIME_PROTO) {
let xferEnc = this.getContentTransferEncoding();
if (xferEnc.search(/base64/i) >= 0) {
let bound = this.getBodyPart();
this.keepData = EnigmailData.decodeBase64(this.keepData.substring(bound.start, bound.end)) + "\n";
}
else if (xferEnc.search(/quoted-printable/i) >= 0) {
let bound = this.getBodyPart();
let qp = this.keepData.substring(bound.start, bound.end);
this.keepData = EnigmailData.decodeQuotedPrintable(qp) + "\n";
}
// extract signature data
let s = Math.max(this.keepData.search(/^-----BEGIN PGP /m), 0);
let e = Math.max(this.keepData.search(/^-----END PGP /m), this.keepData.length - 30);
this.sigData = this.keepData.substring(s, e + 30);
}
else {
this.sigData = "";
}
this.keepData = "";
this.readMode = 4; // ignore any further data
}
},
getBodyPart: function() {
let start = this.keepData.search(/(\n\n|\r\n\r\n)/);
if (start < 0) {
start = 0;
}
let end = this.keepData.indexOf("--" + this.boundary + "--") - 1;
if (end < 0) {
end = this.keepData.length;
}
return {
start: start,
end: end
};
},
// determine content-transfer encoding of mime part, assuming that whole
// message is in this.keepData
getContentTransferEncoding: function() {
let enc = "7bit";
let m = this.keepData.match(/^(content-transfer-encoding:)(.*)$/mi);
if (m && m.length > 2) {
enc = m[2].trim().toLowerCase();
}
return enc;
},
findNextMimePart: function() {
let startOk = false;
let endOk = false;
let i = this.keepData.indexOf("--" + this.boundary);
if (i === 0) startOk = true;
if (i > 0) {
if (this.keepData[i - 1] == '\r' || this.keepData[i - 1] == '\n') startOk = true;
}
if (!startOk) return -1;
if (i + this.boundary.length + 2 < this.keepData.length) {
if (this.keepData[i + this.boundary.length + 2] == '\r' ||
this.keepData[i + this.boundary.length + 2] == '\n' ||
this.keepData.substr(i + this.boundary.length + 2, 2) == '--') endOk = true;
}
// else
// endOk = true;
if (i >= 0 && startOk && endOk) {
return i;
}
return -1;
},
onStopRequest: function() {
EnigmailLog.DEBUG("mimeVerify.jsm: onStopRequest\n");
this.msgWindow = EnigmailVerify.lastMsgWindow;
this.msgUriSpec = EnigmailVerify.lastMsgUri;
let url = {};
this.backgroundJob = false;
// don't try to verify if no message found
// if (this.verifyEmbedded && (!this.foundMsg)) return; // TODO - check
if (this.readMode < 4) {
// we got incomplete data; simply return what we got
this.returnData(this.signedData.length > 0 ? this.signedData : this.keepData);
return;
}
this.protectedHeaders = EnigmailMime.extractProtectedHeaders(this.signedData);
if (this.protectedHeaders && this.protectedHeaders.startPos >= 0 && this.protectedHeaders.endPos > this.protectedHeaders.startPos) {
let r = this.signedData.substr(0, this.protectedHeaders.startPos) + this.signedData.substr(this.protectedHeaders.endPos);
this.returnData(r);
}
else {
this.returnData(this.signedData);
}
if (this.uri) {
// return if not decrypting currently displayed message (except if
// printing, replying, etc)
this.backgroundJob = (this.uri.spec.search(/[&?]header=(print|quotebody|enigmailConvert)/) >= 0);
try {
var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
if (!EnigmailPrefs.getPref("autoDecrypt")) {
// "decrypt manually" mode
let manUrl = {};
if (EnigmailVerify.getManualUri()) {
let msgSvc = messenger.messageServiceFromURI(EnigmailVerify.getManualUri());
msgSvc.GetUrlForUri(EnigmailVerify.getManualUri(), manUrl, null);
}
else {
manUrl.value = {
spec: "enigmail://invalid/message"
};
}
// print a message if not message explicitly decrypted
let currUrlSpec = this.uri.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
let manUrlSpec = manUrl.value.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
if ((!this.backgroundJob) && currUrlSpec != manUrlSpec) {
return; // this.handleManualDecrypt();
}
}
if (this.msgUriSpec) {
let msgSvc = messenger.messageServiceFromURI(this.msgUriSpec);
msgSvc.GetUrlForUri(this.msgUriSpec, url, null);
}
if (this.uri.spec.search(/[&?]header=[a-zA-Z0-9]*$/) < 0 &&
this.uri.spec.search(/[&?]part=[.0-9]+/) < 0 &&
this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0) {
if (this.uri.spec.search(/[&?]header=filter&.*$/) > 0)
return;
if (this.uri && url && url.value) {
if (url.value.spec != this.uri.spec)
return;
}
}
}
catch (ex) {
EnigmailLog.writeException("mimeVerify.jsm", ex);
EnigmailLog.DEBUG("mimeVerify.jsm: error while processing " + this.msgUriSpec + "\n");
}
}
if (this.protocol === PGPMIME_PROTO) {
var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
var win = windowManager.getMostRecentWindow(null);
// create temp file holding signature data
this.sigFile = EnigmailFiles.getTempDirObj();
this.sigFile.append("data.sig");
this.sigFile.createUnique(this.sigFile.NORMAL_FILE_TYPE, 0x180);
EnigmailFiles.writeFileContents(this.sigFile, this.sigData, 0x180);
var statusFlagsObj = {};
var errorMsgObj = {};
this.proc = EnigmailDecryption.decryptMessageStart(win, true, true, this,
statusFlagsObj, errorMsgObj,
EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(this.sigFile)));
if (this.pipe) {
EnigmailLog.DEBUG("mimeVerify.jsm: onStopRequest: closing pipe\n"); // always log this one
this.pipe.close();
}
else
this.closePipe = true;
}
},
// return data to libMime
returnData: function(data) {
EnigmailLog.DEBUG("mimeVerify.jsm: returnData: " + data.length + " bytes\n");
let m = data.match(/^(content-type: +)([\w/]+)/im);
if (m && m.length >= 3) {
let contentType = m[2];
if (contentType.search(/^text/i) === 0) {
// add multipart/mixed boundary to work around TB bug (empty forwarded message)
let bound = EnigmailMime.createBoundary();
data = 'Content-Type: multipart/mixed; boundary="' + bound + '"\n' +
'Content-Disposition: inline\n\n--' +
bound + '\n' +
data +
'\n--' + bound + '--\n';
}
}
gConv.setData(data, data.length);
try {
this.mimeSvc.onStartRequest(null, null);
this.mimeSvc.onDataAvailable(null, null, gConv, 0, data.length);
this.mimeSvc.onStopRequest(null, null, 0);
}
catch (ex) {
EnigmailLog.ERROR("mimeVerify.jsm: returnData(): mimeSvc.onDataAvailable failed:\n" + ex.toString());
}
},
// API for decryptMessage Listener
stdin: function(pipe) {
LOCAL_DEBUG("mimeVerify.jsm: stdin\n");
if (this.signedData.length > 0) {
LOCAL_DEBUG("mimeVerify.jsm: writing " + this.signedData.length + " bytes\n");
// ensure all lines end with CRLF as specified in RFC 3156, section 5
this.signedData = this.signedData.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n");
pipe.write(this.signedData);
}
if (this.closePipe) {
pipe.close();
}
else {
this.pipe = pipe;
}
},
stdout: function(s) {
LOCAL_DEBUG("mimeVerify.jsm: stdout:" + s.length + "\n");
this.dataLength += s.length;
},
stderr: function(s) {
LOCAL_DEBUG("mimeVerify.jsm: stderr\n");
this.statusStr += s;
},
done: function(exitCode) {
LOCAL_DEBUG("mimeVerify.jsm: done: " + exitCode + "\n");
this.exitCode = exitCode;
//LOCAL_DEBUG("mimeVerify.jsm: "+this.statusStr+"\n");
this.returnStatus = {};
EnigmailDecryption.decryptMessageEnd(this.statusStr,
this.exitCode,
this.dataLength,
true, // verifyOnly
true,
Ci.nsIEnigmail.UI_PGP_MIME,
this.returnStatus);
if (this.partiallySigned)
this.returnStatus.statusFlags |= Ci.nsIEnigmail.PARTIALLY_PGP;
this.displayStatus();
if (this.sigFile) this.sigFile.remove(false);
},
setMsgWindow: function(msgWindow, msgUriSpec) {
EnigmailLog.DEBUG("mimeVerify.jsm: setMsgWindow: " + msgUriSpec + "\n");
if (!this.msgWindow) {
this.msgWindow = msgWindow;
this.msgUriSpec = msgUriSpec;
}
},
displayStatus: function() {
EnigmailLog.DEBUG("mimeVerify.jsm: displayStatus\n");
if (this.exitCode === null || this.msgWindow === null || this.statusDisplayed || this.backgroundJob)
return;
try {
LOCAL_DEBUG("mimeVerify.jsm: displayStatus displaying result\n");
let headerSink = this.msgWindow.msgHeaderSink.securityInfo.QueryInterface(Ci.nsIEnigMimeHeaderSink);
if (this.protectedHeaders) {
headerSink.processDecryptionResult(this.uri, "modifyMessageHeaders", JSON.stringify(this.protectedHeaders.newHeaders), this.mimePartNumber);
}
if (headerSink) {
headerSink.updateSecurityStatus(this.lastMsgUri,
this.exitCode,
this.returnStatus.statusFlags,
this.returnStatus.keyId,
this.returnStatus.userId,
this.returnStatus.sigDetails,
this.returnStatus.errorMsg,
this.returnStatus.blockSeparation,
this.uri,
JSON.stringify({
encryptedTo: this.returnStatus.encToDetails
}),
this.mimePartNumber);
}
this.statusDisplayed = true;
}
catch (ex) {
EnigmailLog.writeException("mimeVerify.jsm", ex);
}
}
};
////////////////////////////////////////////////////////////////////
// General-purpose functions, not exported
function LOCAL_DEBUG(str) {
if (gDebugLog) EnigmailLog.DEBUG(str);
}
function initModule() {
var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
var nspr_log_modules = env.get("NSPR_LOG_MODULES");
var matches = nspr_log_modules.match(/mimeVerify:(\d+)/);
if (matches && (matches.length > 1)) {
if (matches[1] > 2) gDebugLog = true;
}
}
initModule();

File Metadata

Mime Type
text/plain
Expires
Mon, May 12, 6:16 PM (13 h, 14 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
26/f8/222ff72362c382fe8bbc4814139b

Event Timeline