This repository has been archived by the owner on May 27, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
content-script.js
225 lines (198 loc) · 7.83 KB
/
content-script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
'use strict';
var gdr_gdr_name_pubkey = '[BUILD:GRD_KEY//TODO]'
// TODO: add pgp key here during build or read from file during execution if possible (inline is ugly)
// TODO: also put this into class and do not execute on every page load (which is done currently AFAIK)
keyring = new kbpgp.keyring.KeyRing();
kbpgp.KeyManager.import_from_armored_pgp({
armored: gdr_gdr_name_pubkey
}, function(err, gdr) {
if(!err) {
keyring.add_key_manager(gdr);
} else {
console.log(err);
}
});
// TODO: also puit into "class" and stuff, use function for PB detection to let it change
// TODO: this is obviously a crappy algorithm for detecting PrivateBin
if (document.title == "PrivateBin") {
console.log("PrivateBin validator reporting for duty!");
var storageRetrievedHandler = function(item) {
var itemAge = (new Date().getTime() / 1000) - item['privatebin']['last_modified'];
if (itemAge < 7*24*3600 && item && Object.getOwnPropertyNames(item).length > 0 && !Helper.getBrowser().runtime.lastError) {
checkScriptElements(item['privatebin']['hashes']);
} else {
HashUpdater.runUpdate();
}
};
Helper.getBrowser().storage.local.get("privatebin", storageRetrievedHandler);
}
/**
* Helper - Useful helper functions, whcih are needed very often.
*
* @return {object} Methods: checkScriptElements
*/
var Helper = (function () {
var me;
/**
* getBrowser - return browser object
*
* @return {object????}
*/
me.getBrowser = function getBrowser() {
if (typeof browser !== "undefined") {
return browser;
} else {
return chrome;
}
};
return me;
})();
/**
* Verifier - Verifies a PrivateBin web page.
*
* @return {object} Methods: checkScriptElements
*/
var Verifier = (function () {
var me;
/**
* compareHashes - compares the hashes of all script tags when given an
* object with expected hashes
*
* @todo do not return string, needs better solution
* @param {???} elements HTMLCollection elements containing <script> elements
* @param {object} elements versionHashes map of expected filename => integrity mapping
* @return {bool}
*/
function compareHashes(elements, versionHashes) {
var foundFiles = new Object();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var src = element.getAttribute("src");
var integrity = element.getAttribute("integrity");
if (src === null) {
return "Found a rouge script tag without src";
}
if (integrity === null) {
return "Found a script tag without integrity";
}
if (versionHashes[src] !== undefined) {
if (versionHashes[src] != integrity) {
return false;
} else {
foundFiles[src] = true;
}
} else {
return false;
}
}
/* All scripts on page match at this point, now check if no scripts are missing */
for (var script_name in versionHashes) {
if (foundFiles[script_name] === undefined) {
return script_name + " missing";
}
}
return true;
};
/**
* compareAllHashes - compares all script tags on current page
*
* @param {object} hashes expected hashes array keyed by version number
* @return {void}
*/
me.checkScriptElements = function checkScriptElements(hashes) {
var scriptElements = document.getElementsByTagName("script");
var found = false;
for (var version_number in hashes) {
if (compareSignatures(scriptElements, hashes[version_number]) === true) {
found = version_number;
break;
}
}
if (found !== false) {
console.log("Found PrivateBin v" + found);
} else {
console.log("PrivateBin did not match any version");
window.alert("It's not safe to use this PrivateBin instance, it may be serving malicious Javascript!");
}
};
return me;
})();
/**
* HashUpdater - Fetches officiasl/original hash list from the PrivateBin site.
*
* @todo save hashes offline
* @return {object} Methods: updateHashes
*/
var HashUpdater = (function () {
var me;
var hashUrlKey = 'https://gdr.name/hashes.json.gpg';
/**
* runUpdate - Updates the hashes if necessary
*
*/
me.runUpdate = function() {
console.log("Fetching new hashes via XHR");
var xhr = new XMLHttpRequest();
xhr.open('GET', hashUrl);
xhr.responseType = 'text';
xhr.onload = function(e) {
if (this.status == 200) {
kbpgp.unbox({
keyfetch: keyring,
armored: this.response,
},
// TODO: pass (private) function here instead of concenating this all
function(err, literals) {
if (err != null) {
console.log(err);
window.alert("PGP signature check failed: " + err);
} else {
if (literals.length < 1) {
window.alert("Received an empty hashes file");
return;
}
var jsonResponse = JSON.parse(literals[0].toString());
/* Heavy sanity checking for the received JSON file */
// TODO: own part -> maybe in private method (in this "class")
if (typeof jsonResponse != 'object') {
window.alert("Received a malformed hashes JSON file: root element not an object");
return;
}
if (typeof jsonResponse['last_modified'] != 'number') {
window.alert("Received a malformed hashes JSON file: last_modified invalid");
return;
}
if (typeof jsonResponse['hashes'] != 'object') {
console.log(jsonResponse);
window.alert("Received a fishy hashes file");
return;
}
if (Object.getOwnPropertyNames(jsonResponse['hashes']).length < 1) {
console.log(jsonResponse);
window.alert("Received a malformed hashes JSON file: empty hashes object");
return;
}
for (var version in jsonResponse['hashes']) {
for (var scriptName in jsonResponse['hashes'][version]) {
if (typeof scriptName != 'string') {
window.alert("Received a malformed hashes JSON file: found a non-string hashes key");
return;
}
if (typeof jsonResponse['hashes'][version][scriptName] != 'string') {
window.alert("Received a malformed hashes JSON file: found a non-string hashes value");
return;
}
}
}
Helper.getBrowser().storage.local.set({"privatebin": jsonResponse});
HashUpdater.checkScriptElements(jsonResponse['hashes']);
}
});
} else {
window.alert("Could not retrieve hashes for PrivateBin");
}
};
xhr.send();
};
return me;
})();