-rw-r--r-- | backend/node/src/clipperz.js | 2 |
1 files changed, 1 insertions, 1 deletions
diff --git a/backend/node/src/clipperz.js b/backend/node/src/clipperz.js index 6c13f16..6bf56bb 100644 --- a/backend/node/src/clipperz.js +++ b/backend/node/src/clipperz.js | |||
@@ -1,165 +1,165 @@ | |||
1 | var FS = require('fs'); | 1 | var FS = require('fs'); |
2 | var CRYPTO = require('crypto'); | 2 | var CRYPTO = require('crypto'); |
3 | var BIGNUM = require('bignum'); | 3 | var BIGNUM = require('bignum'); |
4 | var ASYNC = require('async'); | 4 | var ASYNC = require('async'); |
5 | 5 | ||
6 | function clipperz_hash(v) { | 6 | function clipperz_hash(v) { |
7 | return CRYPTO.createHash('sha256').update( | 7 | return CRYPTO.createHash('sha256').update( |
8 | CRYPTO.createHash('sha256').update(v).digest('binary') | 8 | CRYPTO.createHash('sha256').update(v).digest('binary') |
9 | ).digest('hex'); | 9 | ).digest('hex'); |
10 | }; | 10 | }; |
11 | function clipperz_random() { | 11 | function clipperz_random() { |
12 | for(var r = '';r.length<64;r+=''+BIGNUM(Math.floor(Math.random()*1e18)).toString(16)); | 12 | for(var r = '';r.length<64;r+=''+BIGNUM(Math.floor(Math.random()*1e18)).toString(16)); |
13 | return r.substr(0,64); | 13 | return r.substr(0,64); |
14 | }; | 14 | }; |
15 | var srp_g = BIGNUM(2); | 15 | var srp_g = BIGNUM(2); |
16 | var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16); | 16 | var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16); |
17 | var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'; | 17 | var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'; |
18 | 18 | ||
19 | 19 | ||
20 | var CLIPPERZ = module.exports = function(CONFIG) { | 20 | var CLIPPERZ = module.exports = function(CONFIG) { |
21 | 21 | ||
22 | var LOGGER = CONFIG.logger||{trace:function(){}}; | 22 | var LOGGER = CONFIG.logger||{trace:function(){}}; |
23 | 23 | ||
24 | var PG = { | 24 | var PG = { |
25 | url: CONFIG.psql, | 25 | url: CONFIG.psql, |
26 | PG: require('pg').native, | 26 | PG: require('pg').native, |
27 | Q: function(q,a,cb) { | 27 | Q: function(q,a,cb) { |
28 | if('function'===typeof a) cb=a,a=[]; | 28 | if('function'===typeof a) cb=a,a=[]; |
29 | LOGGER.trace({query:q,args:a},'SQL: %s',q); | 29 | LOGGER.trace({query:q,args:a},'SQL: %s',q); |
30 | PG.PG.connect(PG.url,function(e,C,D) { | 30 | PG.PG.connect(PG.url,function(e,C,D) { |
31 | if(e) return cb(e); | 31 | if(e) return cb(e); |
32 | var t0=new Date(); | 32 | var t0=new Date(); |
33 | C.query(q,a,function(e,r) { | 33 | C.query(q,a,function(e,r) { |
34 | var t1=new Date(), dt=t1-t0; | 34 | var t1=new Date(), dt=t1-t0; |
35 | D(); | 35 | D(); |
36 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); | 36 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); |
37 | cb(e,r); | 37 | cb(e,r); |
38 | }); | 38 | }); |
39 | }); | 39 | }); |
40 | }, | 40 | }, |
41 | T: function(cb) { | 41 | T: function(cb) { |
42 | PG.PG.connect(PG.url,function(e,C,D) { | 42 | PG.PG.connect(PG.url,function(e,C,D) { |
43 | if(e) return cb(e); | 43 | if(e) return cb(e); |
44 | C.query('BEGIN',function(e){ | 44 | C.query('BEGIN',function(e){ |
45 | if(e) return D(),cb(e); | 45 | if(e) return D(),cb(e); |
46 | cb(null,{ | 46 | cb(null,{ |
47 | Q: function(q,a,cb) { | 47 | Q: function(q,a,cb) { |
48 | LOGGER.trace({query:q,args:a},'SQL: %s',q); | 48 | LOGGER.trace({query:q,args:a},'SQL: %s',q); |
49 | if(this.over) return cb(new Error('game over')); | 49 | if(this.over) return cb(new Error('game over')); |
50 | if('function'===typeof a) cb=a,a=[]; | 50 | if('function'===typeof a) cb=a,a=[]; |
51 | var t0=new Date(); | 51 | var t0=new Date(); |
52 | C.query(q,a,function(e,r) { | 52 | C.query(q,a,function(e,r) { |
53 | var t1=new Date(), dt=t1-t0; | 53 | var t1=new Date(), dt=t1-t0; |
54 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); | 54 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); |
55 | cb(e,r); | 55 | cb(e,r); |
56 | }); | 56 | }); |
57 | }, | 57 | }, |
58 | commit: function(cb) { | 58 | commit: function(cb) { |
59 | LOGGER.trace('SQL: commit'); | 59 | LOGGER.trace('SQL: commit'); |
60 | if(this.over) return cb(new Error('game over')); | 60 | if(this.over) return cb(new Error('game over')); |
61 | return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)}); | 61 | return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)}); |
62 | }, | 62 | }, |
63 | rollback: function(cb) { | 63 | rollback: function(cb) { |
64 | LOGGER.trace('SQL: rollback'); | 64 | LOGGER.trace('SQL: rollback'); |
65 | if(this.over) return cb(new Error('game over')); | 65 | if(this.over) return cb(new Error('game over')); |
66 | return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); | 66 | return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); |
67 | }, | 67 | }, |
68 | end: function(e,cb) { | 68 | end: function(e,cb) { |
69 | if(e) LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); | 69 | if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); |
70 | this.commit(cb); | 70 | this.commit(cb); |
71 | } | 71 | } |
72 | }); | 72 | }); |
73 | }); | 73 | }); |
74 | }); | 74 | }); |
75 | } | 75 | } |
76 | }; | 76 | }; |
77 | 77 | ||
78 | 78 | ||
79 | return { | 79 | return { |
80 | 80 | ||
81 | json: function clipperz_json(req,res,cb) { | 81 | json: function clipperz_json(req,res,cb) { |
82 | var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; | 82 | var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; |
83 | var message = pp.message; | 83 | var message = pp.message; |
84 | var ppp = pp.parameters; | 84 | var ppp = pp.parameters; |
85 | res.res = function(o) { return res.json({result:o}) }; | 85 | res.res = function(o) { return res.json({result:o}) }; |
86 | LOGGER.trace({method:method,parameters:pp},"JSON request"); | 86 | LOGGER.trace({method:method,parameters:pp},"JSON request"); |
87 | 87 | ||
88 | switch(method) { | 88 | switch(method) { |
89 | case 'registration': | 89 | case 'registration': |
90 | switch(message) { | 90 | switch(message) { |
91 | case 'completeRegistration': return PG.Q( | 91 | case 'completeRegistration': return PG.Q( |
92 | "INSERT INTO clipperz.theuser" | 92 | "INSERT INTO clipperz.theuser" |
93 | +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" | 93 | +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" |
94 | +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", | 94 | +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", |
95 | [pp.credentials.C, pp.credentials.s, pp.credentials.v, | 95 | [pp.credentials.C, pp.credentials.s, pp.credentials.v, |
96 | pp.credentials.version,pp.user.header, pp.user.statistics, | 96 | pp.credentials.version,pp.user.header, pp.user.statistics, |
97 | pp.user.version, pp.user.lock], function(e,r) { | 97 | pp.user.version, pp.user.lock], function(e,r) { |
98 | if(e) return cb(e); | 98 | if(e) return cb(e); |
99 | res.res({lock:pp.user.lock,result:'done'}); | 99 | res.res({lock:pp.user.lock,result:'done'}); |
100 | }); | 100 | }); |
101 | } | 101 | } |
102 | break; | 102 | break; |
103 | 103 | ||
104 | case 'handshake': | 104 | case 'handshake': |
105 | switch(message) { | 105 | switch(message) { |
106 | case 'connect': return ASYNC.auto({ | 106 | case 'connect': return ASYNC.auto({ |
107 | u: function(cb) { PG.Q( | 107 | u: function(cb) { PG.Q( |
108 | "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", | 108 | "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", |
109 | [ppp.C], function(e,r) { | 109 | [ppp.C], function(e,r) { |
110 | if(e) return cb(e); | 110 | if(e) return cb(e); |
111 | if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); | 111 | if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); |
112 | cb(null,r.rows[0]); | 112 | cb(null,r.rows[0]); |
113 | }) }, | 113 | }) }, |
114 | otp: ['u',function(cb,r) { | 114 | otp: ['u',function(cb,r) { |
115 | if(!req.session.otp) return cb(); | 115 | if(!req.session.otp) return cb(); |
116 | if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); | 116 | if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); |
117 | PG.Q( | 117 | PG.Q( |
118 | "UPDATE clipperz.theotp AS otp" | 118 | "UPDATE clipperz.theotp AS otp" |
119 | +" SET" | 119 | +" SET" |
120 | +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" | 120 | +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" |
121 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" | 121 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" |
122 | +" ) ELSE otp.otps_id END," | 122 | +" ) ELSE otp.otps_id END," |
123 | +" otp_utime=current_timestamp" | 123 | +" otp_utime=current_timestamp" |
124 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" | 124 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" |
125 | +" WHERE" | 125 | +" WHERE" |
126 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" | 126 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" |
127 | +" AND otp.otp_id=$1 AND otp.u_id=$2" | 127 | +" AND otp.otp_id=$1 AND otp.u_id=$2" |
128 | +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", | 128 | +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", |
129 | [ req.session.otp, req.session.u ], | 129 | [ req.session.otp, req.session.u ], |
130 | function(e,r) { | 130 | function(e,r) { |
131 | if(e) return cb(e); | 131 | if(e) return cb(e); |
132 | if(!r.rowCount) return cb(new Error('no OTP found')); | 132 | if(!r.rowCount) return cb(new Error('no OTP found')); |
133 | r=r.rows[0]; | 133 | r=r.rows[0]; |
134 | if(!r.yes) return cb(new Error('OTP is in a sorry state')); | 134 | if(!r.yes) return cb(new Error('OTP is in a sorry state')); |
135 | cb(null,{ref:r.otp_ref}); | 135 | cb(null,{ref:r.otp_ref}); |
136 | }); | 136 | }); |
137 | }] | 137 | }] |
138 | },function(e,r) { | 138 | },function(e,r) { |
139 | if(e) return cb(e); | 139 | if(e) return cb(e); |
140 | req.session.C = ppp.C; req.session.A = ppp.A; | 140 | req.session.C = ppp.C; req.session.A = ppp.A; |
141 | req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; | 141 | req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; |
142 | req.session.u = r.u.u_id; | 142 | req.session.u = r.u.u_id; |
143 | req.session.b = clipperz_random(); | 143 | req.session.b = clipperz_random(); |
144 | req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); | 144 | req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); |
145 | var rv = {s:req.session.s,B:req.session.B} | 145 | var rv = {s:req.session.s,B:req.session.B} |
146 | if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; | 146 | if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; |
147 | res.res(rv); | 147 | res.res(rv); |
148 | }); | 148 | }); |
149 | 149 | ||
150 | case 'credentialCheck': | 150 | case 'credentialCheck': |
151 | var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10)); | 151 | var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10)); |
152 | var A = BIGNUM(req.session.A,16); | 152 | var A = BIGNUM(req.session.A,16); |
153 | var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm( | 153 | var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm( |
154 | BIGNUM(req.session.b,16), srp_n); | 154 | BIGNUM(req.session.b,16), srp_n); |
155 | var K = clipperz_hash(S.toString(10)); | 155 | var K = clipperz_hash(S.toString(10)); |
156 | var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16)); | 156 | var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16)); |
157 | if(M1!=ppp.M1) return res.res({error:'?'}); | 157 | if(M1!=ppp.M1) return res.res({error:'?'}); |
158 | req.session.K = K; | 158 | req.session.K = K; |
159 | var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); | 159 | var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); |
160 | return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeededd:false,lock:'----'}); | 160 | return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeededd:false,lock:'----'}); |
161 | 161 | ||
162 | case 'oneTimePassword': return PG.Q( | 162 | case 'oneTimePassword': return PG.Q( |
163 | "UPDATE clipperz.theotp AS otp" | 163 | "UPDATE clipperz.theotp AS otp" |
164 | +" SET" | 164 | +" SET" |
165 | +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" | 165 | +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" |