parsav  Check-in [41cbbca855]

Overview
Comment:enable revoking credentials
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 41cbbca855246c9c87507f3e79546b33ee2fe9417e69be83b450c65cfc54c33c
User & Date: lexi on 2021-01-11 02:17:11
Other Links: manifest | tags
Context
2021-01-11
23:33
various cleanups, notices no longer originated from self check-in: 1ba4bbc92f user: lexi tags: trunk
02:17
enable revoking credentials check-in: 41cbbca855 user: lexi tags: trunk
01:53
add auth docs and rsa auth check-in: 0d10a378e9 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [7d329a757a] to [05f7cb1a74].

319
320
321
322
323
324
325












326
327
328
329
330
331
332
...
357
358
359
360
361
362
363






364
365
366
367
368
369
370
....
1636
1637
1638
1639
1640
1641
1642






















1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
....
1724
1725
1726
1727
1728
1729
1730











1731
1732
1733
1734
1735
1736
1737
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'challenge-rsa', $2::bytea,
				$3::bigint, $4::text
			) on conflict (name,kind,cred) do update set comment = $4::text returning aid
		]]
	};













	auth_privs_clear = {
		params = {uint64}, cmd = true, sql = [[
			update parsav_auth set restrict = array[]::text[] where aid = $1::bigint
		]];
	};

................................................................................
	};

	auth_enum_handle = {
		params = {rawstring}, sql = [[
			select aid, kind, comment, netmask, blacklist from parsav_auth where name = $1::text
		]];
	};







	post_save = {
		params = {
			uint64, uint32, int64;
			rawstring, rawstring, rawstring;
		}, cmd = true, sql = [[
			update parsav_posts set
................................................................................
				var react = r:_string(i,5)
				lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N]))
			end
		end

		return notes
	end];























	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
		var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
		for i=0, r.sz do
			var kind = r:_string(i, 1)
			var comment = r:_string(i, 2)
			var a = [ lib.str.encapsulate(lib.store.auth, {
				kind = {`kind.ptr, `kind.ct+1};
				comment = {`comment.ptr, `comment.ct+1};
................................................................................
		queries.auth_purge_type.exec(src, handle, uid, 'otp-%')
	end];

	auth_purge_trust = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
		queries.auth_purge_type.exec(src, handle, uid, 'trust')
	end];












	artifact_quicksearch = [terra(
		src: &lib.store.source,
		hash: binblob
	): {uint64, bool}
		var srec = queries.artifact_quicksearch.exec(src, hash)
		if srec.sz > 0 then
			defer srec:free()







>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|

|







 







>
>
>
>
>
>
>
>
>
>
>







319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
....
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
....
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'challenge-rsa', $2::bytea,
				$3::bigint, $4::text
			) on conflict (name,kind,cred) do update set comment = $4::text returning aid
		]]
	};

	auth_destroy_aid = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_auth where aid = $1::bigint
		]];
	};

	auth_destroy_aid_uid = {
		params = {uint64,uint64}, cmd = true, sql = [[
			delete from parsav_auth where aid = $1::bigint and uid = $2::bigint
		]];
	};

	auth_privs_clear = {
		params = {uint64}, cmd = true, sql = [[
			update parsav_auth set restrict = array[]::text[] where aid = $1::bigint
		]];
	};

................................................................................
	};

	auth_enum_handle = {
		params = {rawstring}, sql = [[
			select aid, kind, comment, netmask, blacklist from parsav_auth where name = $1::text
		]];
	};

	auth_fetch_aid = {
		params = {uint64}, sql = [[
			select aid, uid, kind, comment, netmask, blacklist from parsav_auth where aid = $1::bigint
		]];
	};

	post_save = {
		params = {
			uint64, uint32, int64;
			rawstring, rawstring, rawstring;
		}, cmd = true, sql = [[
			update parsav_posts set
................................................................................
				var react = r:_string(i,5)
				lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N]))
			end
		end

		return notes
	end];

	auth_fetch_aid = [terra(
		src: &lib.store.source,
		aid: uint64
	): lib.mem.ptr(lib.store.auth)
		var r = queries.auth_fetch_aid.exec(src,aid)
		if r.sz == 0 then return [lib.mem.ptr(lib.store.auth)].null() end
		var kind = r:_string(0, 2)
		var comment = r:_string(0, 3)
		var a = [ lib.str.encapsulate(lib.store.auth, {
			kind = {`kind.ptr, `kind.ct+1};
			comment = {`comment.ptr, `comment.ct+1};
		}) ]
		a.ptr.aid = r:int(uint64, 0, 0)
		a.ptr.uid = r:int(uint64, 0, 1)
		if r:null(0,3)
			then a.ptr.netmask.pv = 0
			else a.ptr.netmask = r:cidr(0, 4)
		end
		a.ptr.blacklist = r:bool(0, 5)
		return a
	end];

	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.lstptr(lib.store.auth)
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.lstptr(lib.store.auth)].null() end
		var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
		for i=0, r.sz do
			var kind = r:_string(i, 1)
			var comment = r:_string(i, 2)
			var a = [ lib.str.encapsulate(lib.store.auth, {
				kind = {`kind.ptr, `kind.ct+1};
				comment = {`comment.ptr, `comment.ct+1};
................................................................................
		queries.auth_purge_type.exec(src, handle, uid, 'otp-%')
	end];

	auth_purge_trust = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
		queries.auth_purge_type.exec(src, handle, uid, 'trust')
	end];

	auth_destroy_aid = [terra(
		src: &lib.store.source,
		aid: uint64
	): {} queries.auth_destroy_aid.exec(src,aid) end];

	auth_destroy_aid_uid = [terra(
		src: &lib.store.source,
		aid: uint64,
		uid: uint64
	): {} queries.auth_destroy_aid_uid.exec(src,aid,uid) end];

	artifact_quicksearch = [terra(
		src: &lib.store.source,
		hash: binblob
	): {uint64, bool}
		var srec = queries.artifact_quicksearch.exec(src, hash)
		if srec.sz > 0 then
			defer srec:free()

Modified route.t from [cd7a14ae6e] to [57b2f599e2].

354
355
356
357
358
359
360
361

362
363
364
365
366









367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
...
446
447
448
449
450
451
452
453
454
455

456
457
458
459
460
461
462
...
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
...
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end

local terra 
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
	var act = co:ppostv('act')
	lib.dbg('showing credentials')

	if act:cmp( 'invalidate') then
		lib.dbg('setting user\'s cookie validation time to now')
		co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil))
		-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
		co:installkey('?',co.aid)









		return
	elseif act:cmp( 'newcred') then
		var cmt = co:ppostv('comment')
		var pw = co:ppostv('newpw')
		var rsapub = co:ppostv('newrsa'):blob()
		var aid: uint64 = 0
		if pw:ref() then
			var cpw = co:ppostv('rptpw')
			if not pw:cmp(cpw) then
				co:complain(400,'enrollment failure','the passwords you supplied do not match')
				return
			end
			aid = co.srv:auth_attach_pw(uid, false, pw, cmt)
		elseif rsapub:ref() then
			var sig = co:ppostv('sig')
			var nonce = co:ppostv('nonce')
			var s_noncevld = co:ppostv('noncevld')
			var noncevld, ok = lib.math.shorthand.parse(s_noncevld.ptr, s_noncevld.ct)
			if not ok then
				co:complain(403,'try harder next time','you call that cryptanalysis?')
				return
			end

			var fr = co.srv.pool:frame()
			var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), nonce)
			if not lib.math.truncate64(hmac.ptr, hmac.ct) == noncevld then
				co:complain(403,'nice try','what exactly are you trying to accomplish here, buddy')
				return
			end

			var pkres = lib.crypt.loadpub(rsapub.ptr,rsapub.ct+1) -- needs NUL
			if not pkres.ok then
				co:complain(400,'invalid key','the key you have supplied is not a valid PEM or DER file')
				return
			end
			var pk = pkres.val
			defer pk:free()

			var decoded = co.srv.pool:alloc(uint8,sig.ct)
			var decoded_sz: intptr = 0
			if lib.b64.mbedtls_base64_decode(decoded.ptr,sig.ct,&decoded_sz,[&uint8](sig.ptr),sig.ct) ~= 0 then
				co:complain(400,'invalid signature','the signature you supplied is not encoded in valid base64')
				return
			end

			var vfy, secl = lib.crypt.verify(&pk, nonce.ptr, nonce.ct, decoded.ptr, decoded_sz)
			if not vfy then
				co:complain(403,'verification failed','the signature you supplied does not match the required nonce')
				return
			end

			var dbuf: uint8[lib.crypt.const.maxdersz]
			var derkey = lib.crypt.der(true, &pk, &dbuf[0])
			aid = co.srv:auth_attach_rsa(co.who.id, false, derkey, cmt)
			co.srv.pool:reset(fr)
		end
................................................................................
				co.srv:auth_privs_set(aid, privs)
			end

			lib.dbg('setting netmask restrictions')
			var nm = co:pgetv('netmask')
		end
		co:reroute('?')
		return
	end
	co:complain(400,'bad request','the operation you have requested is not meaningful in this context')

end

terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
	var msg = pstring.null()
	-- first things first, do priv checks
	if path.ct >= 2 then
		if not co.who.rights.powers.config() and (
................................................................................
				co.ui_hue = co.srv.cfg.ui_hue
			end

			msg = 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually

		elseif path(1):cmp('sec') then
			credsec_for_uid(co, co.who.id)
		elseif path(1):cmp('avi') then
			var act = co:ppostv('act')
			if act:ref() and act:cmp('clear') then
				co.who.avatarid = 0
				co.who.source:actor_save(co.who)
				msg = 'avatar reset to default'
			else goto badop end
................................................................................
							goto nopriv
						end
					else goto badop end
					defer usr:free()

					if path.ct == 4 then
						if path(3):cmp(lib.str.lit 'cred') then
							credsec_for_uid(co, userid)
						end
					elseif path.ct == 3 then
						var purgestr = co:ppostv("purgestr")
						var purgekey = co:ppostv("purgekey")
						if purgestr:ref() and purgekey:ref() and purgestr(0) ~= 0 then
							if purgestr:cmp(purgekey) then -- destroying account! :O
								co.srv:actor_purge_uid(userid)







|
>





>
>
>
>
>
>
>
>
>
|
|








|









|






|





|








|





|







 







|


>







 







|







 







|







354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
...
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
...
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
...
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end

local terra 
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
	var act = co:ppostv('act')
	if not act then return true end
	lib.dbg('handling credential action')
	if act:cmp( 'invalidate') then
		lib.dbg('setting user\'s cookie validation time to now')
		co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil))
		-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
		co:installkey('?',co.aid)
		return false
	elseif act:cmp('revoke') then
		var s_cred = co:ppostv('cred')
		if s_cred:ref() then
			var cred, ok = lib.math.shorthand.parse(s_cred.ptr, s_cred.ct)
			if ok then
				co.srv:auth_destroy_aid_uid(cred,co.who.id)
			end
		end
		return true
	elseif act:cmp('newcred') then
		var cmt = co:ppostv('comment')
		var pw = co:ppostv('newpw')
		var rsapub = co:ppostv('newrsa'):blob()
		var aid: uint64 = 0
		if pw:ref() then
			var cpw = co:ppostv('rptpw')
			if not pw:cmp(cpw) then
				co:complain(400,'enrollment failure','the passwords you supplied do not match')
				return false
			end
			aid = co.srv:auth_attach_pw(uid, false, pw, cmt)
		elseif rsapub:ref() then
			var sig = co:ppostv('sig')
			var nonce = co:ppostv('nonce')
			var s_noncevld = co:ppostv('noncevld')
			var noncevld, ok = lib.math.shorthand.parse(s_noncevld.ptr, s_noncevld.ct)
			if not ok then
				co:complain(403,'try harder next time','you call that cryptanalysis?')
				return false
			end

			var fr = co.srv.pool:frame()
			var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), nonce)
			if not lib.math.truncate64(hmac.ptr, hmac.ct) == noncevld then
				co:complain(403,'nice try','what exactly are you trying to accomplish here, buddy')
				return false
			end

			var pkres = lib.crypt.loadpub(rsapub.ptr,rsapub.ct+1) -- needs NUL
			if not pkres.ok then
				co:complain(400,'invalid key','the key you have supplied is not a valid PEM or DER file')
				return false
			end
			var pk = pkres.val
			defer pk:free()

			var decoded = co.srv.pool:alloc(uint8,sig.ct)
			var decoded_sz: intptr = 0
			if lib.b64.mbedtls_base64_decode(decoded.ptr,sig.ct,&decoded_sz,[&uint8](sig.ptr),sig.ct) ~= 0 then
				co:complain(400,'invalid signature','the signature you supplied is not encoded in valid base64')
				return false
			end

			var vfy, secl = lib.crypt.verify(&pk, nonce.ptr, nonce.ct, decoded.ptr, decoded_sz)
			if not vfy then
				co:complain(403,'verification failed','the signature you supplied does not match the required nonce')
				return false
			end

			var dbuf: uint8[lib.crypt.const.maxdersz]
			var derkey = lib.crypt.der(true, &pk, &dbuf[0])
			aid = co.srv:auth_attach_rsa(co.who.id, false, derkey, cmt)
			co.srv.pool:reset(fr)
		end
................................................................................
				co.srv:auth_privs_set(aid, privs)
			end

			lib.dbg('setting netmask restrictions')
			var nm = co:pgetv('netmask')
		end
		co:reroute('?')
		return false
	end
	co:complain(400,'bad request','the operation you have requested is not meaningful in this context')
	return false
end

terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
	var msg = pstring.null()
	-- first things first, do priv checks
	if path.ct >= 2 then
		if not co.who.rights.powers.config() and (
................................................................................
				co.ui_hue = co.srv.cfg.ui_hue
			end

			msg = 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually

		elseif path(1):cmp('sec') then
			if not credsec_for_uid(co, co.who.id) then return end
		elseif path(1):cmp('avi') then
			var act = co:ppostv('act')
			if act:ref() and act:cmp('clear') then
				co.who.avatarid = 0
				co.who.source:actor_save(co.who)
				msg = 'avatar reset to default'
			else goto badop end
................................................................................
							goto nopriv
						end
					else goto badop end
					defer usr:free()

					if path.ct == 4 then
						if path(3):cmp(lib.str.lit 'cred') then
							if not credsec_for_uid(co, userid) then return end 
						end
					elseif path.ct == 3 then
						var purgestr = co:ppostv("purgestr")
						var purgekey = co:ppostv("purgekey")
						if purgestr:ref() and purgekey:ref() and purgestr(0) ~= 0 then
							if purgestr:cmp(purgekey) then -- destroying account! :O
								co.srv:actor_purge_uid(userid)

Modified store.t from [53eb63c414] to [bb9260aa8d].

419
420
421
422
423
424
425

426
427
428
429
430
431
432


433
434
435
436
437
438
439
	actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice)
	actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship

	auth_enum_uid:    {&m.source, uint64}    -> lib.mem.lstptr(m.auth)
	auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth)

	auth_attach_pw:  {&m.source, uint64, bool, pstr, pstr} -> uint64
	auth_attach_rsa: {&m.source, uint64, bool, lib.mem.ptr(uint8), pstr} -> uint64
		-- uid: uint64
		-- reset: bool (delete other passwords?)
		-- pw: pstring
		-- comment: pstring
	auth_privs_set: {&m.source, uint64, m.privset} -> {}


	auth_purge_pw: {&m.source, uint64, rawstring} -> {}
	auth_purge_otp: {&m.source, uint64, rawstring} -> {}
	auth_purge_trust: {&m.source, uint64, rawstring} -> {}
	auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint
		-- authentication tokens and accounts have a property that controls
		-- whether auth cookies dated to a certain point are valid. cookies
		-- that are generated before the timepoint are considered invalid.







>







>
>







419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
	actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice)
	actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship

	auth_enum_uid:    {&m.source, uint64}    -> lib.mem.lstptr(m.auth)
	auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth)
	auth_fetch_aid : {&m.source, uint64} -> lib.mem.ptr(m.auth)
	auth_attach_pw:  {&m.source, uint64, bool, pstr, pstr} -> uint64
	auth_attach_rsa: {&m.source, uint64, bool, lib.mem.ptr(uint8), pstr} -> uint64
		-- uid: uint64
		-- reset: bool (delete other passwords?)
		-- pw: pstring
		-- comment: pstring
	auth_privs_set: {&m.source, uint64, m.privset} -> {}
	auth_destroy_aid: {&m.source, uint64} -> {}
	auth_destroy_aid_uid: {&m.source, uint64, uint64} -> {}
	auth_purge_pw: {&m.source, uint64, rawstring} -> {}
	auth_purge_otp: {&m.source, uint64, rawstring} -> {}
	auth_purge_trust: {&m.source, uint64, rawstring} -> {}
	auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint
		-- authentication tokens and accounts have a property that controls
		-- whether auth cookies dated to a certain point are valid. cookies
		-- that are generated before the timepoint are considered invalid.