Skip to content

Commit 2c13c3b

Browse files
committed
Implement crypto worker delegated to:
- computation of argon2 for password hashing - computation of argon2 for renewed proof of work - future crypto duties
1 parent 53938d8 commit 2c13c3b

File tree

21 files changed

+184
-77
lines changed

21 files changed

+184
-77
lines changed

backend/globaleaks/handlers/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def get_session(self):
135135
if token:
136136
try:
137137
self.token = self.state.tokens.validate(token)
138+
print(token)
138139
if self.token.session is not None:
139140
session = self.token.session
140141
except:

backend/globaleaks/handlers/staticfile.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,24 @@ def get(self, filename):
3838
b"frame-src 'self';"
3939
b"img-src 'self';"
4040
b"media-src 'self';"
41-
b"script-src 'self' 'sha256-l4srTx31TC+tE2K4jVVCnC9XfHivkiSs/v+DPWccDDM=' 'report-sample' 'wasm-unsafe-eval';"
41+
b"script-src 'self' 'sha256-l4srTx31TC+tE2K4jVVCnC9XfHivkiSs/v+DPWccDDM=' 'report-sample';"
4242
b"style-src 'self' 'report-sample';"
43+
b"worker-src 'self';"
4344
b"trusted-types angular angular#bundler dompurify default;"
4445
b"require-trusted-types-for 'script';"
4546
b"report-uri /api/report;")
4647

48+
elif filename == 'workers/crypto.worker.js':
49+
self.request.setHeader(b'Content-Security-Policy',
50+
b"base-uri 'none';"
51+
b"default-src 'none' 'report-sample';"
52+
b"form-action 'none';"
53+
b"frame-ancestors 'none';"
54+
b"sandbox;"
55+
b"script-src 'self' 'wasm-unsafe-eval';"
56+
b"trusted-types;"
57+
b"require-trusted-types-for 'script';"
58+
b"report-uri /api/report;")
59+
60+
4761
return self.write_file(filename, abspath)

backend/globaleaks/handlers/user/operation.py

-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ def change_password(session, tid, user_session, key):
2222

2323
config = models.config.ConfigFactory(session, tid)
2424

25-
if not GCE.is_base64_key(key):
26-
raise errors.InputValidationError
27-
2825
key = Base64Encoder.decode(key.encode())
2926
hash = sha512(key)
3027

backend/globaleaks/tests/handlers/auth/test_auth.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ class TestSessionHandler(helpers.TestHandlerWithPopulatedDB):
310310
def test_successful_admin_session_setup_renewal_and_logout(self):
311311
# since all logins for roles admin, receiver and custodian happen
312312
# in the same way, the following tests are performed on the admin user.
313-
314313
self._handler = auth.AuthenticationHandler
315314

316315
# Login
@@ -330,6 +329,7 @@ def test_successful_admin_session_setup_renewal_and_logout(self):
330329
session_id = response['id']
331330
session = Sessions.get(session_id)
332331
session.token.id = helpers.TOKEN
332+
session.token.salt = helpers.TOKEN_SALT
333333

334334
# Wrong Session Renewal
335335
handler = self.request({'token': 'wrong_token:666'}, headers={'x-session': session_id})

backend/globaleaks/tests/helpers.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@
7373
GCE_orig_generate_key = GCE.generate_key
7474
GCE_orig_generate_keypair = GCE.generate_keypair
7575

76-
TOKEN = b"31b780b6eb6357e324eea7c3a5d2542067c4d537f4f4de77473c93d48dd8a758"
77-
TOKEN_ANSWER = b"31b780b6eb6357e324eea7c3a5d2542067c4d537f4f4de77473c93d48dd8a758:149619"
76+
TOKEN = b"61af2d7fb2796730c9fb9e357ed4c0f9c87d8c6f6976c4ca3731238db43e87b0"
77+
TOKEN_SALT = b"eed1d4c5a8e97f4f953d4bddd62957ac5f9e94af6a025c6b95300d72ba41b57e"
78+
TOKEN_ANSWER = b"61af2d7fb2796730c9fb9e357ed4c0f9c87d8c6f6976c4ca3731238db43e87b0:142"
79+
80+
7881
def mock_nullfunction(*args, **kwargs):
7982
return
8083

@@ -179,6 +182,7 @@ def get_token():
179182
token = State.tokens.new(1)
180183
State.tokens.pop(token.id)
181184
token.id = TOKEN
185+
token.salt = TOKEN_SALT
182186
State.tokens[token.id] = token
183187
return TOKEN_ANSWER
184188

backend/globaleaks/utils/token.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# -*- coding: utf-8
22
# Implement a proof of work token to prevent resources exhaustion
3+
from nacl.encoding import Base64Encoder
34

45
from globaleaks.rest import errors
5-
from globaleaks.utils.crypto import sha256, generateRandomKey
6+
from globaleaks.utils.crypto import GCE, generateRandomKey
67
from globaleaks.utils.tempdict import TempDict
78
from globaleaks.utils.utility import datetime_now
89

@@ -11,18 +12,20 @@ class Token(object):
1112
def __init__(self, tid):
1213
self.tid = tid
1314
self.id = generateRandomKey().encode()
15+
self.salt = generateRandomKey().encode()
1416
self.session = None
1517
self.creation_date = datetime_now()
1618

1719
def serialize(self):
1820
return {
1921
'id': self.id.decode(),
22+
'salt': self.salt.decode(),
2023
'creation_date': self.creation_date
2124
}
2225

2326
def validate(self, answer):
2427
try:
25-
if not sha256(self.id + answer).endswith(b'00'):
28+
if not Base64Encoder.decode(GCE.argon2id(self.id + answer, self.salt, 1, 1 << 20))[31] == 0:
2629
raise errors.InternalServerError("TokenFailure: Invalid Token")
2730
except:
2831
raise errors.InternalServerError("TokenFailure: Invalid token")

client/Gruntfile.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,27 @@ module.exports = function(grunt) {
181181
},
182182

183183
webpack: {
184-
build: {
184+
crypto_worker: {
185+
entry: {
186+
'crypto.worker.js': './app/workers/crypto.worker.ts',
187+
},
188+
output: {
189+
filename: 'crypto.worker.js',
190+
path: path.resolve('build/workers/'),
191+
libraryTarget: 'umd',
192+
globalObject: 'this',
193+
},
194+
mode: 'production',
195+
resolve: {
196+
fallback: {
197+
fs: false,
198+
crypto: false,
199+
path: false,
200+
stream: false
201+
}
202+
}
203+
},
204+
pdfjs: {
185205
entry: {
186206
'pdf.min': './node_modules/pdfjs-dist/legacy/build/pdf.min.mjs',
187207
'pdf.worker.min': './node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs',
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export class TokenResponse {
22
id: string;
3+
salt: string;
34
answer: string;
4-
}
5+
}

client/app/src/pages/admin/users/user-editor/user-editor.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class UserEditorComponent implements OnInit {
8686

8787
async setPassword(setPasswordArgs: { user_id: string, password: string }) {
8888
this.appDataService.updateShowLoadingPanel(true);
89-
setPasswordArgs.password = await this.cryptoService.hashArgon2(setPasswordArgs.password, this.appDataService.public.node.receipt_salt, this.user.username);
89+
setPasswordArgs.password = await this.cryptoService.hashArgon2(setPasswordArgs.password, this.user.salt);
9090
this.appDataService.updateShowLoadingPanel(false);
9191

9292
this.utilsService.runAdminOperation("set_user_password", setPasswordArgs, false).subscribe();

client/app/src/pages/app/app.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export class AppComponent implements AfterViewInit, OnInit, OnDestroy{
144144
this.keepalive.onPing.subscribe(() => {
145145
if (this.authenticationService.session) {
146146
const token = this.authenticationService.session.token;
147-
this.cryptoService.proofOfWork(token.id).subscribe((result:any) => {
147+
this.cryptoService.proofOfWork(token).subscribe((result:any) => {
148148
const param = {'token': token.id + ":" + result};
149149
this.httpService.requestRefreshUserSession(param).subscribe(((result:any) => {
150150
this.authenticationService.session.token = result.token;

client/app/src/pages/recipient/tip/tip.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ export class TipComponent implements OnInit {
388388
(
389389
{
390390
next: async token => {
391-
this.cryptoService.proofOfWork(token.id).subscribe(
391+
this.cryptoService.proofOfWork(token).subscribe(
392392
(result: number) => {
393393
window.open("api/recipient/rtips/" + tipId + "/export" + "?token=" + token.id + ":" + result);
394394
this.appDataService.updateShowLoadingPanel(false);

client/app/src/pages/whistleblower/submission/submission.component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ export class SubmissionComponent implements OnInit {
308308
return;
309309
}
310310

311+
clearInterval(intervalId);
312+
311313
this.authenticationService.session.receipt = this.cryptoService.generateReceipt();
312314

313315
const res = await firstValueFrom(this.httpService.requestAuthType(JSON.stringify({'username': "" /* whistleblower */ })));
@@ -326,8 +328,6 @@ export class SubmissionComponent implements OnInit {
326328
this.titleService.setPage("receiptpage");
327329
}
328330
});
329-
330-
clearInterval(intervalId);
331331
}, 1000);
332332
}
333333

client/app/src/pages/wizard/wizard/wizard.component.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ export class WizardComponent implements OnInit {
9494
this.completed = true;
9595
this.wizard.node_language = this.translationService.language;
9696
this.appDataService.updateShowLoadingPanel(true);
97-
this.wizard.admin_password = this.admin_password ? await this.cryptoService.hashArgon2(this.admin_password, this.appDataService.public.node.receipt_salt, this.wizard.admin_username) : "";
98-
this.wizard.receiver_password = this.receiver_password ? await this.cryptoService.hashArgon2(this.receiver_password, this.appDataService.public.node.receipt_salt, this.wizard.receiver_username) : "";
97+
const admin_salt = await this.cryptoService.generateSalt(this.appDataService.public.node.receipt_salt + ":" + this.wizard.admin_username);
98+
const receiver_salt = await this.cryptoService.generateSalt(this.appDataService.public.node.receipt_salt + ":" + this.wizard.receiver_username);
99+
this.wizard.admin_password = this.admin_password ? await this.cryptoService.hashArgon2(this.admin_password, admin_salt) : "";
100+
this.wizard.receiver_password = this.receiver_password ? await this.cryptoService.hashArgon2(this.receiver_password, receiver_salt) : '';
99101
this.appDataService.updateShowLoadingPanel(false);
100102

101103
const param = JSON.stringify(this.wizard);

client/app/src/services/helper/trusted-types.service.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ export class TrustedTypesService {
1717
throw new Error('Scripts are not allowed by this policy.');
1818
},
1919
createScriptURL: (input: string) => {
20-
throw new Error('Script URLs are not allowed by this policy.');
20+
if (input === '/workers/crypto.worker.js') {
21+
return input;
22+
} else {
23+
throw new Error('Script URLs are not allowed by this policy.');
24+
}
2125
}
2226
});
2327
}

client/app/src/services/root/app-interceptor.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class appInterceptor implements HttpInterceptor {
7171
if (httpRequest.url.includes("api/signup") || httpRequest.url.endsWith("api/auth/receiptauth") && !this.authenticationService.session || protectedUrls.includes(httpRequest.url)) {
7272
return this.httpClient.post("api/auth/token", {}).pipe(
7373
switchMap((response) =>
74-
from(this.cryptoService.proofOfWork(Object.assign(new TokenResponse(), response).id)).pipe(
74+
from(this.cryptoService.proofOfWork(Object.assign(new TokenResponse(), response))).pipe(
7575
switchMap((ans) => next.handle(httpRequest.clone({
7676
headers: httpRequest.headers.set("x-token", `${Object.assign(new TokenResponse(), response).id}:${ans}`)
7777
.set("Accept-Language", this.getAcceptLanguageHeader() || ""),

client/app/src/shared/partials/tip-files-whistleblower/tip-files-whistleblower.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class TipFilesWhistleblowerComponent {
3939
(
4040
{
4141
next: async token => {
42-
this.cryptoService.proofOfWork(token.id).subscribe(
42+
this.cryptoService.proofOfWork(token).subscribe(
4343
(ans) => {
4444
window.open("api/whistleblower/wbtip/wbfiles/" + wbFile.id + "?token=" + token.id + ":" + ans);
4545
this.appDataService.updateShowLoadingPanel(false);

client/app/src/shared/partials/wbfiles/wb-files.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class WbFilesComponent implements OnInit {
5050
(
5151
{
5252
next: async token => {
53-
this.cryptoService.proofOfWork(token.id).subscribe(
53+
this.cryptoService.proofOfWork(token).subscribe(
5454
(ans) => {
5555
if (this.authenticationService.session.role === "receiver") {
5656
window.open("api/recipient/rfiles/" + wbFile.id + "?token=" + token.id + ":" + ans);

0 commit comments

Comments
 (0)