| Comment: | add auth docs and rsa auth |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
0d10a378e90159932431518d4fa74296 |
| User & Date: | lexi on 2021-01-11 01:53:23 |
| Other Links: | manifest | tags |
|
2021-01-11
| ||
| 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 | |
|
2021-01-10
| ||
| 16:44 | add avatar panel check-in: 8398fcda5a user: lexi tags: trunk | |
Modified backend/pgsql.t from [b388ba7244] to [7d329a757a].
116 116 $11::integer,$4::bigint 117 117 ) returning id 118 118 ]]; 119 119 }; 120 120 121 121 actor_auth_pw = { 122 122 params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[ 123 - select a.aid, a.uid, a.name from parsav_auth as a 123 + select a.aid, a.uid, a.name, a.blacklist from parsav_auth as a 124 124 left join parsav_actors as u on u.id = a.uid 125 125 where (a.uid is null or u.handle = $1::text or ( 126 126 a.uid = 0 and a.name = $1::text 127 127 )) and 128 128 (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and 129 129 (a.netmask is null or a.netmask >> $4::inet) 130 130 order by blacklist desc limit 1 131 131 ]]; 132 + }; 133 + actor_auth_challenge = { 134 + params = {pstring,pstring,lib.store.inet}, sql = [[ 135 + select a.aid, a.uid, a.name, a.blacklist, a.cred 136 + from parsav_auth as a 137 + left join parsav_actors as u on u.id = a.uid 138 + where a.kind = 'challenge-' || $1::text and 139 + (a.netmask is null or a.netmask >> $3::inet) and 140 + (a.uid is null or u.handle = $2::text or 141 + (a.uid = 0 and a.name = $2::text)) 142 + order by blacklist desc 143 + ]]; 132 144 }; 133 145 134 146 actor_enum_local = { 135 147 params = {}, sql = [[ 136 148 select id, nym, handle, origin, bio, 137 149 null::text, rank, quota, key, epithet, 138 150 knownsince::bigint, ................................................................................ 292 304 293 305 auth_create_pw = { 294 306 params = {uint64, binblob, int64, pstring}, sql = [[ 295 307 insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values ( 296 308 $1::bigint, 297 309 (select handle from parsav_actors where id = $1::bigint), 298 310 'pw-sha256', $2::bytea, 311 + $3::bigint, $4::text 312 + ) on conflict (name,kind,cred) do update set comment = $4::text returning aid 313 + ]] 314 + }; 315 + 316 + auth_create_rsa = { 317 + params = {uint64, binblob, int64, pstring}, sql = [[ 318 + insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values ( 319 + $1::bigint, 320 + (select handle from parsav_actors where id = $1::bigint), 321 + 'challenge-rsa', $2::bytea, 299 322 $3::bigint, $4::text 300 323 ) on conflict (name,kind,cred) do update set comment = $4::text returning aid 301 324 ]] 302 325 }; 303 326 304 327 auth_privs_clear = { 305 328 params = {uint64}, cmd = true, sql = [[ ................................................................................ 1292 1315 (cs.trust << r:bool(0,3)) 1293 1316 return cs, true 1294 1317 end]; 1295 1318 1296 1319 actor_auth_pw = [terra( 1297 1320 src: &lib.store.source, 1298 1321 ip: lib.store.inet, 1299 - username: lib.mem.ptr(int8), 1300 - cred: lib.mem.ptr(int8) 1322 + username: pstring, 1323 + cred: pstring 1301 1324 ): {uint64, uint64, pstring} 1302 1325 1303 1326 [ checksha(`src, 256, ip, username, cred) ] -- most common 1304 1327 [ checksha(`src, 512, ip, username, cred) ] -- most secure 1305 1328 [ checksha(`src, 384, ip, username, cred) ] -- weird 1306 1329 [ checksha(`src, 224, ip, username, cred) ] -- weirdest 1307 1330 1308 1331 -- TODO: check pbkdf2-hmac 1309 1332 -- TODO: check OTP 1310 1333 return 0, 0, pstring.null() 1311 1334 end]; 1335 + 1336 + actor_auth_challenge = [terra( 1337 + src: &lib.store.source, 1338 + ip: lib.store.inet, 1339 + username: pstring, 1340 + sig: binblob, 1341 + token: pstring 1342 + ): {uint64, uint64, pstring} 1343 + -- we need to iterate through all the challenge types. right now that's just RSA 1344 + lib.dbg('checking against token ', {token.ptr,token.ct}) 1345 + var rsakeys = queries.actor_auth_challenge.exec(src, 'rsa', username, ip) 1346 + var toprops = [terra(res: &pqr, i: intptr) 1347 + return { 1348 + aid = res:int(uint64, i, 0); 1349 + uid = res:int(uint64, i, 1); 1350 + name = res:_string(i, 2); 1351 + blacklist = res:bool(i, 3); 1352 + pubkey = res:bin(i, 4); 1353 + } 1354 + end] 1355 + if rsakeys.sz > 0 then defer rsakeys:free() 1356 + for i=0, rsakeys.sz do var props = toprops(&rsakeys, i) 1357 + lib.dbg('loading next RSA pubkey') 1358 + var pub = lib.crypt.loadpub(props.pubkey.ptr, props.pubkey.ct) 1359 + if pub.ok then defer pub.val:free() 1360 + lib.dbg('checking pubkey against response') 1361 + var vfy, secl = lib.crypt.verify(&pub.val, token.ptr, token.ct, sig.ptr, sig.ct) 1362 + if vfy then 1363 + lib.dbg('signature verified') 1364 + if props.blacklist then lib.dbg('key blacklisted!') goto fail end 1365 + var dupname = lib.str.dup(props.name.ptr) 1366 + return props.aid, props.uid, pstring {dupname, props.name.ct} 1367 + end 1368 + else lib.warn('invalid pubkey in authentication table for user ',{props.name.ptr, props.name.ct}) end 1369 + end 1370 + end 1371 + 1372 + -- and so on 1373 + 1374 + lib.dbg('no challenges were successful') 1375 + ::fail::return 0, 0, pstring.null() 1376 + end]; 1377 + 1312 1378 1313 1379 actor_stats = [terra(src: &lib.store.source, uid: uint64) 1314 1380 var r = queries.actor_stats.exec(src, uid) 1315 1381 if r.sz == 0 then lib.bail('error fetching actor stats!') end 1316 1382 var s: lib.store.actor_stats 1317 1383 s.posts = r:int(uint64, 0, 0) 1318 1384 s.follows = r:int(uint64, 0, 1) ................................................................................ 1615 1681 if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end 1616 1682 var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment) 1617 1683 if r.sz == 0 then return 0 end 1618 1684 var aid = r:int(uint64,0,0) 1619 1685 r:free() 1620 1686 return aid 1621 1687 end]; 1688 + 1689 + auth_attach_rsa = [terra( 1690 + src: &lib.store.source, 1691 + uid: uint64, 1692 + reset: bool, 1693 + pub: binblob, 1694 + comment: pstring 1695 + ): uint64 1696 + if reset then queries.auth_purge_type.exec(src, nil, uid, 'challenge-%') end 1697 + var r = queries.auth_create_rsa.exec(src, uid, pub, lib.osclock.time(nil), comment) 1698 + if r.sz == 0 then return 0 end 1699 + var aid = r:int(uint64,0,0) 1700 + r:free() 1701 + return aid 1702 + end]; 1622 1703 1623 1704 auth_privs_set = [terra( 1624 1705 src: &lib.store.source, 1625 1706 aid: uint64, 1626 1707 set: lib.store.privset 1627 1708 ): {} 1628 1709 var map = array([lib.store.privmap])
Modified crypt.t from [f5b057e4fa] to [034fdb64c7].
10 10 end; 11 11 toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE; 12 12 } 13 13 const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to 14 14 const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one 15 15 16 16 local ctx = lib.pk.mbedtls_pk_context 17 +terra ctx:free() lib.pk.mbedtls_pk_free(self) end 17 18 18 19 local struct hashalg { id: uint8 bytes: intptr } 19 20 local m = { 20 21 pemfile = uint8[const.maxpemsz]; 21 22 const = const; 22 23 algsz = { 23 24 sha1 = 160/8; ................................................................................ 78 79 if pub then 79 80 return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0 80 81 else 81 82 return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0 82 83 end 83 84 end 84 85 85 -terra m.der(pub: bool, key: &ctx, buf: &uint8): intptr 86 +local binblob = lib.mem.ptr(uint8) 87 +terra m.der(pub: bool, key: &ctx, buf: &uint8): binblob 88 + var ofs: intptr 86 89 if pub then 87 - return lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz) 90 + ofs = lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz) 88 91 else 89 - return lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz) 92 + ofs = lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz) 90 93 end 94 + return binblob { 95 + ptr = buf + (const.maxdersz - ofs); 96 + ct = ofs; 97 + } 91 98 end 92 99 93 100 m.destroy = lib.dispatch { 94 101 [ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end; 95 102 96 103 [false] = function(ptr) return `ptr:free() end; 97 104 } ................................................................................ 104 111 lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) 105 112 var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx) 106 113 lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537) 107 114 108 115 return pk 109 116 end 110 117 111 -terra m.loadpriv(buf: &uint8, len: intptr): ctx 112 - lib.dbg('parsing saved keypair') 118 +terra m.loadpriv(buf: &uint8, len: intptr): lib.stat(ctx) 119 + lib.dbg('parsing saved private key') 120 + 121 + var pk: ctx 122 + lib.pk.mbedtls_pk_init(&pk) 123 + var rt = lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0) 124 + if rt == 0 then 125 + return [lib.stat(ctx)] { ok = true, val = pk } 126 + else 127 + lib.pk.mbedtls_pk_free(&pk) 128 + return [lib.stat(ctx)] { ok = false } 129 + end 130 +end 131 + 132 +terra m.loadpub(buf: &uint8, len: intptr): lib.stat(ctx) 133 + lib.dbg('parsing saved key') 113 134 114 135 var pk: ctx 115 136 lib.pk.mbedtls_pk_init(&pk) 116 - lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0) 117 - return pk 137 + var rt = lib.pk.mbedtls_pk_parse_public_key(&pk, buf, len) 138 + if rt == 0 then 139 + return [lib.stat(ctx)] { ok = true, val = pk } 140 + else 141 + lib.pk.mbedtls_pk_free(&pk) 142 + return [lib.stat(ctx)] { ok = false, error = rt } 143 + end 118 144 end 119 145 120 146 terra m.sign(pk: &ctx, txt: rawstring, len: intptr) 121 147 lib.dbg('signing message') 122 148 var osz: intptr = 0 123 149 var sig = lib.mem.heapa(int8, 2048) 124 150 var hash: uint8[32] ................................................................................ 133 159 if ret ~= 0 then lib.bail('could not sign message hash') 134 160 else sig:resize(osz) end 135 161 136 162 return sig 137 163 end 138 164 139 165 terra m.verify(pk: &ctx, txt: rawstring, len: intptr, 140 - sig: rawstring, siglen: intptr): {bool, uint8} 166 + sig: &uint8, siglen: intptr): {bool, uint8} 141 167 lib.dbg('verifying signature') 142 168 var osz: intptr = 0 143 169 var hash: uint8[64] 144 170 145 171 -- there does not appear to be any way to extract the hash algorithm 146 172 -- from the message, so we just have to try likely algorithms until 147 173 -- we find one that fits or give up. a security level is attached ................................................................................ 151 177 {lib.md.MBEDTLS_MD_SHA256, 'sha256', 2}, 152 178 {lib.md.MBEDTLS_MD_SHA512, 'sha512', 3}, 153 179 {lib.md.MBEDTLS_MD_SHA1, 'sha1', 1}, 154 180 -- uncommon hashes 155 181 {lib.md.MBEDTLS_MD_SHA384, 'sha384', 2}, 156 182 {lib.md.MBEDTLS_MD_SHA224, 'sha224', 2}, 157 183 -- bad hashes 158 - {lib.md.MBEDTLS_MD_MD5, 'md5', 0}, 159 - {lib.md.MBEDTLS_MD_MD4, 'md4', 0}, 160 - {lib.md.MBEDTLS_MD_MD2, 'md2', 0} 184 + {lib.md.MBEDTLS_MD_MD5, 'md5', 0} 185 + --{lib.md.MBEDTLS_MD_MD4, 'md4', 0}, 186 + --{lib.md.MBEDTLS_MD_MD2, 'md2', 0} 161 187 ) 162 188 163 189 for i = 0, [algs.type.N] do 164 190 var hk, aname, secl = algs[i] 165 191 166 192 lib.dbg('(1/2) trying hash algorithm ',aname) 167 193 if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then ................................................................................ 186 212 end 187 213 188 214 terra m.hmaca(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8)) 189 215 var buf = lib.mem.heapa(uint8, alg.bytes) 190 216 m.hmac(alg, key, txt, buf.ptr) 191 217 return buf 192 218 end 219 + 220 +terra m.hmacp(p: &lib.mem.pool, alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8)) 221 + var buf = p:alloc(uint8, alg.bytes) 222 + m.hmac(alg, key, txt, buf.ptr) 223 + return buf 224 +end 193 225 194 226 terra m.hotp(key: &(uint8[10]), counter: uint64) 195 227 var hmac: uint8[20] 196 228 var ctr = [lib.mem.ptr(int8)]{ptr = [&int8](&counter), ct = 8} 197 229 m.hmac(m.alg.sha1, 198 230 [lib.mem.ptr(uint8)]{ptr = [&uint8](key), ct = 10}, 199 231 ctr, hmac) ................................................................................ 200 232 201 233 var ofs = hmac[19] and 0x0F 202 234 var p: uint8[4] 203 235 for i=0,4 do p[i] = hmac[ofs + i] end 204 236 205 237 return (@[&uint32](&p)) and 0x7FFFFFFF -- one hopes it's that easy 206 238 end 239 + 240 +local splitwords = macro(function(str) 241 + local words = {} 242 + for w in str:asvalue():gmatch('(%g+)') do words[#words + 1] = w end 243 + return `arrayof(lib.str.t, [words]) 244 +end) 245 + 246 +terra m.cryptogram(a: &lib.str.acc, len: intptr) 247 + var words = splitwords [[ 248 + alpha beta gamma delta epsilon psi eta nu omicron omega 249 + red crimson green verdant golden silver blue cyan navy 250 + carnelian opal sapphire amethyst ruby jade emerald 251 + chalice peacock cabernet windmill saxony tunnel waterspout 252 + ]] 253 + for i = 0, len do 254 + a:ppush(words[m.random(intptr,0,[words.type.N])]):lpush '-' 255 + end 256 + a:ipush(m.random(uint32,0,99999)) 257 +end 207 258 208 259 return m
Added doc/auth.md version [ac7a8d6f57].
1 +# credentials & authentication 2 + 3 +parsav features a highly flexibly authentication system, and provides alternatives to simple per-user passwords to help users keep their accounts secure. you can create as many credentials for you account as you wish, and tie them to specific IP addresses or regions. you can even restrict the capabilities of a given credential -- for instance, you could have one password that allows full access and one that only allows posting new tweets when logged in with it. 4 + 5 +## mechanisms 6 + 7 +you're not limited to passwords, however. parsav intends to support a very wide range of authentication mechanisms; this page lists all the mechanisms that have been implemented so far. 8 + 9 +### password auth 10 + 11 +of course, parsav lets you access your account with passwords/passphrases like most other software. when you create a new password, you'll be asked to enter it twice to confirm that you haven't mistyped it; you'll then be able to use it by typing it into the login screen. 12 + 13 +passwords have many disadvantages, however. they're easy to steal, easy to forget, they get more and more difficult to use the more secure they are, and people have an awful habit of *writing them down.* if you don't have any problems memorizing long, secure passwords, this may not be a problem for you. but even then, there's no way around the fundamental problem that passwords are *static* -- you only have to slip up once (say, by typing it into a username field by mistake just as the black helicopters are passing overhead) and then the password is compromised forever. 14 + 15 +of course, if you have a static IP address, you can get around some of the insecurity by setting a netmask on the password -- it won't do mongolian bitcoin scammers much good if only IPs from mecklenburg-vorpommern are allowed to use it. netmasks are however best used on VPNs, LANs, and similar arrangements where you have an absolute guarantee of a static IP address. for other circumstances, *challenge auth* may be a worthwhile means of improving your security. 16 + 17 +### challenge auth 18 + 19 +parsav also supports *challenge auth,* which is a form of authentication where are presented with a *challenge token* at login and have to provide with a response digest based on the token to authenticate yourself. this mechanism has the very useful property that the same digest can only be used for a very short period of time, after which they are permanently deactivated, giving you a bit of protection even if your HTTP session is exposed to a man-in-the-middle. due to the way they're implemented, they're effectively immune to shouldersurfing. challenge auth is generally based on cryptographic keys. 20 + 21 +right now, the only form of challenge authentication supported is RSA asymmetric keypairs, though other methods based on elliptic curve cryptography and shared secrets are planned. an RSA keypair is a pair of very long numbers -- called the *public key* and the *private key* -- with special mathematical properties: anyone who holds the public key can encrypt data such that only the person with the private key can read it, and whoever holds the public key can place a digital signature on a piece of data such that anyone with the public key can confirm the data was endorsed by the holder of the private key. (private keys can of course be encrypted with a password; the advantage this has over normal passwords is that the password never leaves your computer's memory.) so when you log in with RSA challenge auth, you'll be given a short string to sign with your private key. all you have to do is paste the signature into the "digest" box and you'll be logged in. 22 + 23 +keypairs are bit more complex to use than passwords, however. you have to use a special tool to create them. on linux and other unix-like systems, you can do this with the `openssl` command: 24 + 25 + $ openssl genrsa 2048 -out private.pem 26 + # creates a reasonably secure 2048-bit private key 27 + 28 + $ openssl genrsa 4096 -out private.pem 29 + # creates an *extremely secure 4096-bit key 30 + 31 + $ openssl genrsa 2048 -aes256 -out private.pem 32 + # pass -aes256 to encrypt your key 33 + 34 +once you've created your private key with a command like one of the above, you'll need to separate out a public key. if you used the `-aes256` flag, you'll be prompted for your password. (keep in mind, this password *cannot* be recovered if it is forgotten!) 35 + 36 + $ openssl rsa -in private.pem -pubout -out public.pem 37 + 38 +`public.pem` is the file you'll want to copy and paste into the text box when you add this keypair as a credential to your parsav account. do *not* ever upload `private.pem` anywhere! if you ever do so by accident, delete the keypair credential from every account that uses it immediately, as you have irreversibly compromised their security. 39 + 40 +finally, you'll need to use this key to actually sign things: 41 + 42 + $ echo -n "this is the string that will be signed" | openssl dgst -sha256 -sign private.pem | openssl base64 43 + 44 +this command is somewhat complex, so you may want to write a short script to save yourself some time. on computers with the X windows system, you can use the following convenient command to encrypt whatever is currently in the clipboard: 45 + 46 + $ xsel -bo | openssl dgst -sha256 -sign private.pem | openssl base64 | xsel -bi 47 + 48 +if you later want to change the password on your private key, you can use this command to do so: 49 + 50 + $ openssl rsa -in private.pem -aes256 -out private.pem 51 + # omit the -aes256 to remove the encryption 52 + 53 +## managing credentials 54 + 55 +you can use the "security" panel in the configuration menu to manage your credentials. this panel has a wide range of options. firstly, if you suspect someone may have unwanted access to your account, you can press the "invalidate other sessions" button to instantly log out every computer but your own. of course, this will only briefly inconvenience evildoers if they have your password -- it's mainly useful for instances where you forgot to log out of a public computer, or one that belongs to someone else. 56 + 57 +you can manage existing credentials with the "revoke" button, which wipes out a selected credential so it can no longer be used to log in (and logs out every device logged in under it!), or `reset`, which lets you change the credentials without affecting their privilege sets. 58 + 59 +finally, you can create new credentials by picking the desired properties (what privileges and netmask they are restricted to, if any) and pressing the relevant button.
Modified doc/load.lua from [783a358256] to [e78e99763f].
1 1 local path = ... 2 2 local sources = { 3 3 -- user section 4 4 acl = {title = 'access control lists'}; 5 + auth = {title = 'credentials & authentication', priv = 'account'}; 5 6 -- admin section 6 7 --overview = {title = 'server overview', priv = 'config'}; 7 8 invocation = {title = 'daemon invocation', priv = 'config'}; 8 9 usr = {title = 'user accounting', priv = {'elevate','demote','purge','herald'}}; 9 10 --srvcfg = {title = 'server configuration policies', priv = 'config'}; 10 11 --discipline = {title = 'disciplinary measures', priv = 'discipline'}; 11 12 --backends = {title = 'storage backends', priv = 'config'}; 12 13 --pgsql = {title = 'pgsql', priv = 'config', parent = 'backends'}; 13 14 } 14 15 15 16 local util = dofile 'common.lua' 16 17 local ingest = function(filename) 17 - return (util.exec { 'cmark', '--smart', '--unsafe', (path..'/'..filename) }):gsub('\n','') 18 + return (util.exec { 'cmark', '--smart', '--unsafe', (path..'/'..filename) }) 19 + --:gsub('\n','') 18 20 end 19 21 20 22 local doc = {} 21 23 for n,meta in pairs(sources) do doc[n] = { 22 24 name = n; 23 25 text = ingest(n .. '.md'); 24 26 meta = meta; 25 27 } end 26 28 return doc
Modified mgtool.t from [600c3c1067] to [4fc07a70c1].
291 291 dlg:tx_enter() 292 292 if dlg:dbsetup() then 293 293 srv:conprep(lib.store.prepmode.conf) 294 294 295 295 do var newkp = lib.crypt.genkp() 296 296 -- generate server privkey 297 297 var kbuf: uint8[lib.crypt.const.maxdersz] 298 - var privsz = lib.crypt.der(false,&newkp, kbuf) 299 - dlg:server_setup_self(dbmode.arglist(1), [lib.mem.ptr(uint8)] { 300 - ptr = &kbuf[0], ct = privsz 301 - }) 298 + var derkey = lib.crypt.der(false,&newkp, kbuf) 299 + dlg:server_setup_self(dbmode.arglist(1), derkey) 302 300 end 303 301 304 302 dlg:conf_set('instance-name', dbmode.arglist(1)) 305 303 dlg:conf_set('domain', dbmode.arglist(1)) 306 304 do var sec: int8[65] gensec(&sec[0]) 307 305 dlg:conf_set('server-secret', &sec[0]) 308 306 end
Modified parsav.md from [af14d66fc5] to [23851a52a8].
102 102 * ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 103 103 * ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server 104 104 * ☐ pw-extern-cyrus: try to authenticate against saslauthd 105 105 * ☐ pw-extern-dovecot: try to authenticate against a dovecot SASL socket 106 106 * ☐ pw-extern-krb5: abuse MIT kerberos as a password verifier 107 107 * ☐ pw-extern-imap: abuse an email server as a password verifier 108 108 * (extra credit) ☐ pw-extern-radius: verify a user against a radius server 109 +* ☐ http-oauth: automatically created when a user grants access to an oauth application, consisting of a series of TLVs. these generally should not be created or fiddled with manually 110 +* ☐ http-gssapi: log in with a kerberos principle through the http-authenticate "negotiate" mechanism. do any browsers actually support this?? 111 +* ☐ http-extern-header: a value of `H=V` where `H` is a header passed by an app server such as nginx, and `V` is the required value. could be used to e.g. tie parsav into an existing client certificate verification infrastructure with minimal effort. 112 +* ☐ http-extern-header: a value of `H=V` where `H` is a header passed by an app server such as nginx, and `V` is the required value. could be used to tie parsav into an existing client certificate verification infrastructure with minimal effort. 109 113 * ☐ api-digest-sha{…}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. 114 +* ☐ api-token-sha{…}: a password (ideally a very long, randomly generated one) that can be sent in the headers to automatically authenticate the user. far less secure than `api-digest-*`! 110 115 * ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key 111 116 * ☐ tls-cert-fp: a fingerprint of a client certificate 112 117 * ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user 113 -* ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. 114 -* ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. 115 -* ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. 118 +* ☐ challenge-rsa: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using any one of the supported hash algorithms, ideally SHA512 or -256. 119 +* ☐ challenge-ecc a Curve25519 public key. the user is presented with a challenge and must sign it with a supported hash algorithm 120 +* ☐ challenge-ecc448: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using a supported hash algorithm. 116 121 * ☑ trust: authentication always succeeds (or fails, if blacklisted). only use in combination with netmask!!! 122 + 123 +we should also look into support for various kinds of hardware auth. we already have TPM support through RSA auth, but external devices like security keys should be supported as well. 117 124 118 125 ## legal 119 126 120 127 parsav is released under the terms of the EUPL v1.2. copies of this license are included in the repository. by contributing any intellectual property to this project, you reassign ownership and all attendant rights over that intellectual property to the current maintainer. this is to ensure that the project can be relicensed without difficulty in the unlikely event that it is necessary. 121 128 122 129 ## code of conduct 123 130
Modified parsav.t from [04e1a3fbb9] to [392d24dbd5].
310 310 self._store[i/8] = self._store[i/8] and not (1 << (i % 8)) 311 311 end 312 312 end 313 313 set.bits = {} 314 314 set.idvmap = {} 315 315 for i,v in ipairs(tbl) do 316 316 set.idvmap[v] = i 317 - set.bits[v] = quote var b: set b:clear() b:setbit(i, true) in b end 317 + set.bits[v] = quote var b: set b:clear() b:setbit([i-1], true) in b end 318 318 end 319 319 set.metamethods.__add = macro(function(self,other) 320 320 local new = symbol(set) 321 321 local q = quote var [new] new:clear() end 322 322 for i = 0, bytes - 1 do 323 323 q = quote [q] 324 324 new._store[i] = self._store[i] or other._store[i]
Modified render/conf/sec.t from [157639932a] to [3bc273639f].
35 35 end 36 36 end 37 37 credmgr.credlist = cl:finalize() 38 38 end 39 39 credmgr:append(&a) 40 40 --if credmgr.credlist.ct > 0 then credmgr.credlist:free() end 41 41 else 42 + var time = lib.osclock.time(nil) 43 + var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) 44 + var cmt = co:stra(48) 45 + cmt:lpush('enrolled over http on '):push(×tr[0],0) 42 46 if new:cmp('pw') then 43 47 var d: data.view.conf_sec_pwnew 44 - var time = lib.osclock.time(nil) 45 - var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) 46 - var cmt = co:stra(48) 47 - cmt:lpush('enrolled over http on '):push(×tr[0],0) 48 48 d.comment = cmt:finalize() 49 49 50 50 var st = d:poolstr(&co.srv.pool) 51 51 --d.comment:free() 52 52 return st 53 - elseif new:cmp('challenge') then 53 + elseif new:cmp('rsa') then 54 + var c = co:stra(64) 55 + lib.crypt.cryptogram(&c, 8) 56 + var cptr = c:finalize(); 57 + var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), cptr); -- TODO should expire after 10min 58 + var hmacte: int8[lib.math.shorthand.maxlen] 59 + var hmacte_len = lib.math.shorthand.gen(lib.math.truncate64(hmac.ptr, hmac.ct), &hmacte[0]) 60 + var d = data.view.conf_sec_keynew { 61 + comment = cmt:finalize(); 62 + nonce = cptr; 63 + noncevld = pstr { ptr = &hmacte[0], ct = hmacte_len }; 64 + } 65 + 66 + return d:poolstr(&co.srv.pool) 54 67 -- we're going to break the rules a bit and do database munging from 55 68 -- the rendering code, because doing otherwise in this case would be 56 69 -- genuinely nightmarish 57 70 elseif new:cmp('otp') then 58 71 elseif new:cmp('api') then 59 72 else return pstr.null() end 60 73 end
Modified render/login.t from [434636bebc] to [da67d9c6fd].
4 4 login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr) 5 5 var doc = [lib.srv.convo.page] { 6 6 title = 'instance logon'; 7 7 class = 'login'; 8 8 cache = false; 9 9 } 10 10 11 + var how = co:ppostv('how') 12 + 11 13 if user == nil then 12 14 var form = data.view.login_username { 13 15 loginmsg = msg; 14 16 } 15 17 if form.loginmsg.ptr == nil then 16 18 form.loginmsg = 'identify yourself for access to this instance.' 17 19 end 18 20 doc.body = form:tostr() 19 21 elseif creds:sz() == 0 then 20 22 co:complain(403,'access denied','your host is not eligible to authenticate as this user') 21 23 return 22 - elseif creds:sz() == 1 then 24 + elseif creds:sz() == 1 or how:ref() then 25 + var newcreds: lib.store.credset 26 + if how:ref() then 27 + if how:cmp('pw') then newcreds = creds and [lib.store.credset.bits.pw] 28 + elseif how:cmp('chlg') then newcreds = creds and [lib.store.credset.bits.challenge] 29 + elseif how:cmp('otp') then newcreds = creds and [lib.store.credset.bits.otp] 30 + elseif how:cmp('trust') then newcreds = creds and [lib.store.credset.bits.trust] 31 + else co:complain(400, 'bad request', 'the requested authentication method is not available') return end 32 + creds = &newcreds 33 + end 34 + 23 35 if creds.trust() then 24 36 -- TODO log in immediately 25 37 return 26 38 end 27 39 28 40 var ch = data.view.login_challenge { 29 41 handle = user.handle; 30 42 name = lib.coalesce(user.nym, user.handle); 31 43 } 32 44 if creds.pw() then 33 45 ch.challenge = 'enter the password associated with your account' 34 46 ch.label = 'password' 35 47 ch.method = 'pw' 36 - ch.auto = 'current-password'; 48 + ch.inputfield = '<input type="password" autocomplete="current-password" name="response" id="response" autofocus required>'; 37 49 elseif creds.otp() then 38 50 ch.challenge = 'enter a valid one-time password for your account' 39 51 ch.label = 'OTP code' 40 52 ch.method = 'otp' 41 - ch.auto = 'one-time-code'; 53 + ch.inputfield = '<input type="text" autocomplete="one-time-code" name="response" id="response" autofocus required>'; 42 54 elseif creds.challenge() then 43 - ch.challenge = 'sign the challenge token: <code>...</code>' 55 + var tok = co:stra(128) 56 + var chlg = co:stra(128) 57 + var input = co:stra(256) 58 + var time = lib.osclock.time(nil) 59 + 60 + lib.crypt.cryptogram(&tok,6) 61 + chlg:lpush 'sign the challenge token <code style="display:block;user-select: all">' 62 + :push(tok.buf,tok.sz) 63 + :lpush '</code>' 64 + 65 + ch.challenge = chlg:finalize() 44 66 ch.label = 'digest' 45 67 ch.method = 'challenge' 46 - ch.auto = 'one-time-code'; 68 + input:lpush '<textarea autocomplete="one-time-code" name="response" id="response" autofocus required></textarea><input type="hidden" name="time" value="' 69 + :shpush(time) 70 + :lpush '"><input type="hidden" name="token" value="' 71 + :push(tok.buf,tok.sz) 72 + tok:shpush(time) 73 + var hmac = lib.crypt.hmacp(&co.srv.pool, 74 + lib.crypt.alg.sha256, 75 + co.srv.cfg.secret:blob(), 76 + tok:finalize()) 77 + input:lpush '"><input type="hidden" name="vfy" value="' 78 + :shpush(lib.math.truncate64(hmac.ptr, hmac.ct)) -- FIXME this is probably not very secure... 79 + :lpush '">' 80 + 81 + ch.inputfield = input:finalize() 47 82 else 48 - co:complain(500,'login failure','unknown login method') 83 + co:complain(400,'login failure','no usable login methods are available') 49 84 return 50 85 end 51 86 52 - doc.body = ch:tostr() 53 - else 54 - -- pick a method 87 + doc.body = ch:poolstr(&co.srv.pool) 88 + else -- pick a method 89 + var a = co:stra(400) 90 + var username = lib.html.sanitize(&co.srv.pool, pstr{user.handle,0}, true) 91 + a:lpush '<form action="/login" method="post" class="auth-select"><p>multiple authentication mechanisms are available. select one to continue.</p><menu><input type="hidden" name="user" value="':ppush(username):lpush'">' 92 + if creds.trust() then a:lpush '<button name="how" value="trust">trust</button>' end 93 + if creds.pw() then a:lpush '<button name="how" value="pw">password</button>' end 94 + if creds.otp() then a:lpush '<button name="how" value="otp">TOTP code</button>' end 95 + if creds.challenge() then a:lpush '<button name="how" value="chlg">challenge</button>' end 96 + a:lpush '</menu></form>' 97 + doc.body = a:finalize() 55 98 end 56 99 57 100 co:stdpage(doc) 58 - doc.body:free() 101 + --doc.body:free() 59 102 end 60 103 61 104 return login_form
Modified route.t from [6caa1366ba] to [cd7a14ae6e].
140 140 -- pick an auth method 141 141 lib.render.login(co, act.ptr, &cs, pstring.null()) 142 142 else var aid: uint64 = 0 143 143 lib.dbg('authentication attempt beginning') 144 144 -- attempt login with provided method 145 145 if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 146 146 aid = co.srv:actor_auth_pw(co.peer, 147 - [lib.mem.ptr(int8)]{ptr=usn,ct=usnl}, 148 - [lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl}) 149 - elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 147 + pstring {ptr=usn,ct=usnl}, 148 + pstring {ptr=chrs,ct=chrsl}) 149 + elseif lib.str.ncmp('challenge', am, lib.math.biggest(9,aml)) == 0 and chrs ~= nil then 150 + lib.dbg('challenge attempt beginning') 151 + var s_time = co:ppostv('time') 152 + var s_vfy = co:ppostv('vfy') 153 + var token = co:ppostv('token') 154 + if s_time:ref() and s_vfy:ref() and token:ref() then 155 + lib.dbg('checking hmac validity') 156 + var vftok = co:stra(128) vftok:ppush(token):ppush(s_time) 157 + var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), vftok:finalize()) 158 + var vfy, vfyok = lib.math.shorthand.parse(s_vfy.ptr, s_vfy.ct) 159 + if vfyok and lib.math.truncate64(hmac.ptr,hmac.ct) == vfy then 160 + lib.dbg('checking expiration time') 161 + var time, timeok = lib.math.shorthand.parse(s_time.ptr, s_time.ct) 162 + if timeok and lib.osclock.time(nil) - time < [2 * 60] then -- two minutes 163 + lib.dbg('decoding base64') 164 + var bin = co.srv.pool:alloc(uint8, chrsl) 165 + var binlen: intptr 166 + if lib.b64.mbedtls_base64_decode(bin.ptr, bin.ct, &binlen, [&uint8](chrs), chrsl) == 0 then 167 + lib.dbg('running signature <',{chrs,chrsl},'> against challenge keys for token [', {token.ptr,token.ct}, ']') 168 + aid = co.srv:actor_auth_challenge(co.peer, 169 + pstring {usn,usnl}, binblob{bin.ptr,binlen}, token) 170 + end 171 + end 172 + end 173 + end 174 + elseif lib.str.ncmp('otp', am, lib.math.biggest(3,aml)) == 0 and chrs ~= nil then 150 175 lib.dbg('using otp auth') 151 176 -- ··· -- 152 177 else lib.dbg('invalid auth method') end 153 178 154 179 -- error out 155 180 if aid == 0 then 156 181 lib.render.login(co, nil, nil, 'authentication failure') ................................................................................ 339 364 co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil)) 340 365 -- 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 341 366 co:installkey('?',co.aid) 342 367 return 343 368 elseif act:cmp( 'newcred') then 344 369 var cmt = co:ppostv('comment') 345 370 var pw = co:ppostv('newpw') 371 + var rsapub = co:ppostv('newrsa'):blob() 346 372 var aid: uint64 = 0 347 373 if pw:ref() then 348 374 var cpw = co:ppostv('rptpw') 349 375 if not pw:cmp(cpw) then 350 376 co:complain(400,'enrollment failure','the passwords you supplied do not match') 351 377 return 352 378 end 353 379 aid = co.srv:auth_attach_pw(uid, false, pw, cmt) 354 - else 355 - var key = co:ppostv('newkey') 356 - if key:ref() then 380 + elseif rsapub:ref() then 381 + var sig = co:ppostv('sig') 382 + var nonce = co:ppostv('nonce') 383 + var s_noncevld = co:ppostv('noncevld') 384 + var noncevld, ok = lib.math.shorthand.parse(s_noncevld.ptr, s_noncevld.ct) 385 + if not ok then 386 + co:complain(403,'try harder next time','you call that cryptanalysis?') 387 + return 388 + end 357 389 390 + var fr = co.srv.pool:frame() 391 + var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), nonce) 392 + if not lib.math.truncate64(hmac.ptr, hmac.ct) == noncevld then 393 + co:complain(403,'nice try','what exactly are you trying to accomplish here, buddy') 394 + return 395 + end 396 + 397 + var pkres = lib.crypt.loadpub(rsapub.ptr,rsapub.ct+1) -- needs NUL 398 + if not pkres.ok then 399 + co:complain(400,'invalid key','the key you have supplied is not a valid PEM or DER file') 400 + return 401 + end 402 + var pk = pkres.val 403 + defer pk:free() 404 + 405 + var decoded = co.srv.pool:alloc(uint8,sig.ct) 406 + var decoded_sz: intptr = 0 407 + if lib.b64.mbedtls_base64_decode(decoded.ptr,sig.ct,&decoded_sz,[&uint8](sig.ptr),sig.ct) ~= 0 then 408 + co:complain(400,'invalid signature','the signature you supplied is not encoded in valid base64') 409 + return 410 + end 411 + 412 + var vfy, secl = lib.crypt.verify(&pk, nonce.ptr, nonce.ct, decoded.ptr, decoded_sz) 413 + if not vfy then 414 + co:complain(403,'verification failed','the signature you supplied does not match the required nonce') 415 + return 358 416 end 417 + 418 + var dbuf: uint8[lib.crypt.const.maxdersz] 419 + var derkey = lib.crypt.der(true, &pk, &dbuf[0]) 420 + aid = co.srv:auth_attach_rsa(co.who.id, false, derkey, cmt) 421 + co.srv.pool:reset(fr) 359 422 end 360 423 if aid ~= 0 then 361 424 lib.dbg('setting credential restrictions') 362 425 var privs = [(function() 363 426 local check = quote end 364 427 local me = symbol(lib.store.privset) 365 428 for i,v in ipairs(lib.store.privset.members) do
Modified srv.t from [2ff93305a1] to [826b7b2edb].
818 818 cs = cs + set 819 819 ok = iok 820 820 end 821 821 end 822 822 return cs, ok 823 823 end 824 824 825 -terra srv:actor_auth_pw(ip: lib.store.inet, user: pstring, pw: pstring): uint64 826 - for i=0,self.sources.ct do 827 - if self.sources(i).backend ~= nil and 828 - self.sources(i).backend.actor_auth_pw ~= nil then 829 - var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) 830 - if aid ~= 0 then 831 - if uid == 0 then 832 - lib.dbg('new user just logged in, creating account entry') 833 - var kbuf: uint8[lib.crypt.const.maxdersz] 834 - var na = lib.store.actor.mk(&kbuf[0]) 835 - na.handle = newhnd.ptr 836 - var newuid: uint64 837 - if self.sources(i).backend.actor_create ~= nil then 838 - newuid = self.sources(i):actor_create(&na) 839 - else newuid = self:actor_create(&na) end 825 +local function mk_auth_fn(suffix,...) 826 + local syms = {...} 827 + local name = 'actor_auth_' .. suffix 828 + srv.methods[name] = terra(self: &srv, ip: lib.store.inet, user: pstring, [syms]): uint64 829 + for i=0,self.sources.ct do 830 + if self.sources(i).backend ~= nil and 831 + self.sources(i).backend.[name] ~= nil then 832 + var aid,uid,newhnd = self.sources(i):[name](ip,user, [syms]) 833 + if aid ~= 0 then 834 + if uid == 0 then 835 + lib.dbg('new user just logged in, creating account entry') 836 + var kbuf: uint8[lib.crypt.const.maxdersz] 837 + var na = lib.store.actor.mk(&kbuf[0]) 838 + na.handle = newhnd.ptr 839 + var newuid: uint64 840 + if self.sources(i).backend.actor_create ~= nil then 841 + newuid = self.sources(i):actor_create(&na) 842 + else newuid = self:actor_create(&na) end 840 843 841 - if self.sources(i).backend.actor_auth_register_uid ~= nil then 842 - self.sources(i):actor_auth_register_uid(aid,newuid) 844 + if self.sources(i).backend.actor_auth_register_uid ~= nil then 845 + self.sources(i):actor_auth_register_uid(aid,newuid) 846 + end 843 847 end 848 + return aid 844 849 end 845 - return aid 846 850 end 847 851 end 852 + 853 + return 0 848 854 end 849 - 850 - return 0 855 + srv.methods[name].name = name 851 856 end 857 + 858 +mk_auth_fn('pw', symbol(pstring)) 859 +mk_auth_fn('challenge', symbol(lib.mem.ptr(uint8)), symbol(pstring)) 860 + 861 +--terra srv:actor_auth_pw(ip: lib.store.inet, user: pstring, pw: pstring): uint64 862 +-- for i=0,self.sources.ct do 863 +-- if self.sources(i).backend ~= nil and 864 +-- self.sources(i).backend.actor_auth_pw ~= nil then 865 +-- var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) 866 +-- if aid ~= 0 then 867 +-- if uid == 0 then 868 +-- lib.dbg('new user just logged in, creating account entry') 869 +-- var kbuf: uint8[lib.crypt.const.maxdersz] 870 +-- var na = lib.store.actor.mk(&kbuf[0]) 871 +-- na.handle = newhnd.ptr 872 +-- var newuid: uint64 873 +-- if self.sources(i).backend.actor_create ~= nil then 874 +-- newuid = self.sources(i):actor_create(&na) 875 +-- else newuid = self:actor_create(&na) end 876 +-- 877 +-- if self.sources(i).backend.actor_auth_register_uid ~= nil then 878 +-- self.sources(i):actor_auth_register_uid(aid,newuid) 879 +-- end 880 +-- end 881 +-- return aid 882 +-- end 883 +-- end 884 +-- end 885 +-- 886 +-- return 0 887 +--end 852 888 853 889 terra cfgcache.methods.load :: {&cfgcache} -> {} 854 890 terra cfgcache:init(o: &srv) 855 891 self.overlord = o 856 892 self:load() 857 893 end 858 894
Modified static/style.scss from [e23a7affc6] to [048cf30682].
379 379 margin:auto; 380 380 padding: 0.5in; 381 381 text-align: center; 382 382 menu:first-of-type { margin-top: 0.3in; } 383 383 img.icon { width: 1.875in; height: 1.875in; } 384 384 } 385 385 386 -div.login { 387 - @extend %box; 388 - width: 4in; 389 - padding: 0.4in; 390 - > .msg { 391 - text-align: center; 392 - padding: 0.3in; 393 - } 394 - > .msg:first-child { padding-top: 0; } 395 - > .user { 396 - width: max-content; margin: auto; 397 - background: tone(-20%,-0.3); 398 - border: 1px solid black; 399 - color: tone(-50%); 400 - padding: 0.1in; 401 - > img { display: block; width: 1in; height: 1in; margin: auto; border: 1px solid black; } 402 - > .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; } 403 - } 404 - >form { 405 - display: grid; 406 - grid-template-columns: 1fr 1fr; 407 - grid-template-rows: 1.2em 1fr 1fr; 408 - grid-gap: 5px; 409 - > label, input, button { display: block; } 410 - > label { grid-column: 1 / 3; grid-row: 1/2; font-weight: bold } 411 - > input { grid-column: 1 / 3; grid-row: 2/3; } 412 - > button { grid-column: 2 / 3; grid-row: 3/4; } 413 - > a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; } 386 +body.login { 387 + form.auth-select { 388 + @extend %box; 389 + width: 3in; 390 + padding: 0.4in; 391 + p { text-align: center; } 392 + menu { 393 + %button { 394 + display: block; 395 + width: 100%; 396 + & + %button { border-top: none; } 397 + } 398 + } 399 + } 400 + div.login { 401 + @extend %box; 402 + width: 4in; 403 + padding: 0.4in; 404 + > .msg { 405 + text-align: center; 406 + padding: 0.3in; 407 + } 408 + > .msg:first-child { padding-top: 0; } 409 + > .user { 410 + width: max-content; margin: auto; 411 + background: tone(-20%,-0.3); 412 + border: 1px solid black; 413 + color: tone(-50%); 414 + padding: 0.1in; 415 + > img { display: block; width: 1in; height: 1in; margin: auto; border: 1px solid black; } 416 + > .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; } 417 + } 418 + >form { 419 + display: grid; 420 + grid-template-columns: 1fr 1fr; 421 + grid-template-rows: 1.2em max-content max-content; 422 + grid-gap: 5px; 423 + > label, input, button { display: block; } 424 + > label { grid-column: 1 / 3; grid-row: 1/2; font-weight: bold } 425 + > input, textarea { grid-column: 1 / 3; grid-row: 2/3; } 426 + > button { grid-column: 2 / 3; grid-row: 3/4; } 427 + > a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; } 428 + } 414 429 } 415 430 } 416 431 417 432 form.compose { 418 433 @extend %box; 419 434 display: grid; 420 435 grid-template-columns: 1.1in 2fr min-content 1fr 1.5fr; ................................................................................ 480 495 position: absolute; 481 496 top: -0.3in; 482 497 right: 0.1in; 483 498 margin: 0.1in; 484 499 padding: 0.1in; 485 500 &:hover { font-weight: bold; } 486 501 } 502 + p { text-align: justify; } 487 503 } 488 504 489 505 code { 490 506 @extend %teletype; 491 507 background: tone(-55%); 492 - border: 1px inset tone(-20%); 508 + // border: 1px inset tone(-20%); 493 509 padding: 2px 6px; 494 510 font-size: 1.5ex !important; 495 511 letter-spacing: 1.3px; 496 512 padding-bottom: 3px; 497 - border-radius: 2px; 513 + border-radius: 4px; 498 514 vertical-align: baseline; 499 515 box-shadow: 1px 1px 1px black; 500 516 } 501 517 502 -pre { @extend %teletype; white-space: pre-wrap; } 518 +pre { 519 + @extend %teletype; 520 + white-space: pre-wrap; 521 + > code:only-child { 522 + display: block; 523 + padding: 0.1in; 524 + } 525 +} 503 526 504 527 div.thread { 505 528 margin-left: 0.3in; 506 529 & + article.post { margin-top: 0.3in; } 507 530 } 508 531 509 532 a[href].username { ................................................................................ 697 720 > label,summary { display:block; font-weight: bold; padding: 0.03in 0; } 698 721 > .txtbox { 699 722 @extend %serif; 700 723 box-sizing: border-box; 701 724 padding: 0.08in 0.1in; 702 725 border: 1px solid black; 703 726 background: tone(-55%); 727 + user-select: all; 704 728 } 705 729 > input, textarea, .txtbox { 706 730 display: block; 707 731 width: 100%; 708 732 } 709 733 > textarea { resize: vertical; min-height: 2in; } 710 734 }
Modified store.t from [9b6251fcb3] to [53eb63c414].
162 162 end 163 163 -- TODO validate fully 164 164 return true 165 165 end 166 166 167 167 terra m.actor.methods.mk(kbuf: &uint8) 168 168 var newkp = lib.crypt.genkp() 169 - var privsz = lib.crypt.der(false,&newkp,kbuf) 169 + var derkey = lib.crypt.der(false,&newkp,kbuf) 170 170 return m.actor { 171 171 id = 0; nym = nil; handle = nil; 172 172 origin = 0; bio = nil; avatar = nil; 173 173 knownsince = lib.osclock.time(nil); 174 174 rights = m.rights_default(); 175 175 avatarid = 0; 176 - epithet = nil, key = [lib.mem.ptr(uint8)] { 177 - ptr = &kbuf[0], ct = privsz 178 - }; 176 + epithet = nil, key = derkey; 179 177 } 180 178 end 181 179 182 180 struct m.actor_stats { 183 181 posts: intptr 184 182 follows: intptr 185 183 followers: intptr ................................................................................ 384 382 -> {uint64, uint64, pstr} 385 383 actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } 386 384 -> {uint64, uint64, pstr} 387 385 -- handles password-based logins against hashed passwords 388 386 -- origin: inet 389 387 -- handle: rawstring 390 388 -- token: rawstring 389 + actor_auth_challenge: {&m.source, m.inet, pstr, lib.mem.ptr(uint8), pstr } 390 + -> {uint64, uint64, pstr} 391 + -- origin: inet 392 + -- handle: rawstring 393 + -- response: rawstring 394 + -- challenge token: pstring 391 395 actor_auth_tls: {&m.source, m.inet, rawstring} 392 396 -> {uint64, uint64, pstr} 393 397 -- handles implicit authentication performed as part of an TLS connection 394 398 -- origin: inet 395 399 -- fingerprint: rawstring 396 400 actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 397 401 -> {uint64, uint64, pstr} ................................................................................ 416 420 actor_rel_create: {&m.source, uint16, uint64, uint64} -> {} 417 421 actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {} 418 422 actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship 419 423 420 424 auth_enum_uid: {&m.source, uint64} -> lib.mem.lstptr(m.auth) 421 425 auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth) 422 426 auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> uint64 423 - auth_attach_key: {&m.source, uint64, bool, pstr, pstr} -> {} 427 + auth_attach_rsa: {&m.source, uint64, bool, lib.mem.ptr(uint8), pstr} -> uint64 424 428 -- uid: uint64 425 429 -- reset: bool (delete other passwords?) 426 430 -- pw: pstring 427 431 -- comment: pstring 428 432 auth_privs_set: {&m.source, uint64, m.privset} -> {} 429 433 auth_purge_pw: {&m.source, uint64, rawstring} -> {} 430 434 auth_purge_otp: {&m.source, uint64, rawstring} -> {}
Modified str.t from [c25d641c64] to [2798df18ea].
58 58 end 59 59 return true 60 60 end 61 61 terra ty:ffw() 62 62 var newp = m.ffw(self.ptr,self.ct) 63 63 var newct = self.ct - (newp - self.ptr) 64 64 return ty { ptr = newp, ct = newct } 65 + end 66 + terra ty:blob() 67 + return byteptr { 68 + ptr = [&uint8](self.ptr); 69 + ct = self.ct; 70 + } 65 71 end 66 72 end 67 73 install_funcs(strptr) 68 74 install_funcs(strref) 69 75 70 76 --strptr.methods.cmpl = macro(function(self,other) 71 77 -- return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
Modified view/conf-sec-credmg.tpl from [c30c9309b5] to [a2babc26c2].
3 3 <p>this account can currently be accessed with the credentials listed below. if you fear a credential has been compromised, you can revoke or reset it.</p> 4 4 <select size="6" name="cred"> 5 5 @credlist 6 6 </select> 7 7 <menu class="horizontal choice"> 8 8 <button name="act" value="reset">reset</button> 9 9 <button name="act" value="revoke">revoke</button> 10 + @?auth 10 11 </menu> 11 12 </form> 12 13 <hr> 13 14 <form method="get"> 14 15 <p>you can associate extra credentials with this account. you can also limit how much of this account’s authority these credentials can be used to exercise — for instance, it might be useful to create API keys that can read the account timeline, but not post as the account owner or access any of his administrative powers. if you don't select a capability set, the credential will be able to wield the full scope of the associated account‘s powers.</p> 15 16 <div class="check-panel"> 16 17 <label><input type="checkbox" name="allow-post"> post</label> ................................................................................ 25 26 <p>you can also specify an IP address range in CIDR format to associate with this credential. if you do so, this credential will only be usable when connecting from an IP address in that range. otherwise, it will be valid when connecting from anywhere on the internet.</p> 26 27 <div class="elem"> 27 28 <label for="netmask">netmask</label> 28 29 <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> 29 30 </div> 30 31 <menu class="vertical choice"> 31 32 <button name="new" value="pw">new password</button> 33 + <button name="new" value="rsa">new RSA key</button> 32 34 <button name="new" value="otp">new OTP key</button> 33 35 <button name="new" value="api">new API token</button> 34 - <button name="new" value="challenge">new challenge key</button> 35 36 </menu> 36 37 </form>
Added view/conf-sec-keynew.tpl version [039cf710c4].
1 +<form method="post"> 2 + <div class="elem"> 3 + <label for="comment">comment</label> 4 + <input type="text" id="comment" name="comment" value="@comment" required> 5 + </div> 6 + <div class="elem"> 7 + <label for="newkey">public key in PEM format</label> 8 + <textarea id="newkey" name="newrsa" required></textarea> 9 + </div> 10 + <p>to confirm your ownership of the private key, you'll need to sign the nonce provided below before it expires in 10 minutes. on unix-like OSes, you can usually use the openssl utility for this.</p> 11 + <code style="display:block; user-select: all">echo -n @nonce | openssl dgst -sha256 -sign privkey.pem | openssl base64</code> 12 + <div class="elem"> 13 + <label>nonce</label> 14 + <div class="txtbox">@nonce</div> 15 + <input type="hidden" name="nonce" value="@nonce"> 16 + <input type="hidden" name="noncevld" value="@noncevld"> 17 + </div> 18 + <div class="elem"> 19 + <label for="sig">nonce signature</label> 20 + <textarea id="sig" name="sig" required></textarea> 21 + </div> 22 + <menu class="choice horizontal"> 23 + <button name="act" value="newcred">enroll</button> 24 + <a class="button" href="?">cancel</a> 25 + </menu> 26 +</form>
Modified view/load.lua from [15344e4760] to [3d222d2b19].
20 20 'login-challenge'; 21 21 22 22 'conf'; 23 23 'conf-profile'; 24 24 'conf-sec'; 25 25 'conf-sec-credmg'; 26 26 'conf-sec-pwnew'; 27 + 'conf-sec-keynew'; 27 28 'conf-user-ctl'; 28 29 } 29 30 30 31 local ingest = function(filename) 31 32 local hnd = io.open(path..'/'..filename) 32 33 local txt = hnd:read('*a') 33 34 io.close(hnd)
Modified view/login-challenge.tpl from [84fccbb367] to [cc4592543f].
3 3 <img src="/avi/@handle"> 4 4 <div class="name">@!name</div> 5 5 </div> 6 6 <div class="msg">@challenge</div> 7 7 <form action="/login" method="post"> 8 8 <label for="response">@label</label> 9 9 <input type="hidden" name="user" value="@:handle"> 10 - <input type="password" autocomplete="@auto" name="response" id="response" autofocus required> 10 + @inputfield 11 11 <button type="submit" name="authmethod" value="@method">authenticate</button> 12 12 <a href="/login">cancel</a> 13 13 </form> 14 14 </div>
Modified view/login-username.tpl from [8c165f8ae9] to [c0dae1cbbc].
1 1 <div class="login"> 2 2 <div class="msg">@loginmsg</div> 3 3 <form action="/login" method="post"> 4 4 <label for="user">local handle</label> 5 5 <input type="text" name="user" id="user" autocomplete="username" autofocus required> 6 - <button type="submit">log on</button> 6 + <button>log on</button> 7 7 </form> 8 8 </div>