| 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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 ... 292 293 294 295 296 297 298 299 300 301 302 303 304 305 .... 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 .... 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 |
$11::integer,$4::bigint
) returning id
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name from parsav_auth as a
left join parsav_actors as u on u.id = a.uid
where (a.uid is null or u.handle = $1::text or (
a.uid = 0 and a.name = $1::text
)) and
(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
(a.netmask is null or a.netmask >> $4::inet)
order by blacklist desc limit 1
]];
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
knownsince::bigint,
................................................................................
auth_create_pw = {
params = {uint64, binblob, int64, pstring}, sql = [[
insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values (
$1::bigint,
(select handle from parsav_actors where id = $1::bigint),
'pw-sha256', $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 = [[
................................................................................
(cs.trust << r:bool(0,3))
return cs, true
end];
actor_auth_pw = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: lib.mem.ptr(int8),
cred: lib.mem.ptr(int8)
): {uint64, uint64, pstring}
[ checksha(`src, 256, ip, username, cred) ] -- most common
[ checksha(`src, 512, ip, username, cred) ] -- most secure
[ checksha(`src, 384, ip, username, cred) ] -- weird
[ checksha(`src, 224, ip, username, cred) ] -- weirdest
-- TODO: check pbkdf2-hmac
-- TODO: check OTP
return 0, 0, pstring.null()
end];
actor_stats = [terra(src: &lib.store.source, uid: uint64)
var r = queries.actor_stats.exec(src, uid)
if r.sz == 0 then lib.bail('error fetching actor stats!') end
var s: lib.store.actor_stats
s.posts = r:int(uint64, 0, 0)
s.follows = r:int(uint64, 0, 1)
................................................................................
if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end
var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment)
if r.sz == 0 then return 0 end
var aid = r:int(uint64,0,0)
r:free()
return aid
end];
auth_privs_set = [terra(
src: &lib.store.source,
aid: uint64,
set: lib.store.privset
): {}
var map = array([lib.store.privmap])
|
| > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 ... 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 .... 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 .... 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 |
$11::integer,$4::bigint
) returning id
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name, a.blacklist from parsav_auth as a
left join parsav_actors as u on u.id = a.uid
where (a.uid is null or u.handle = $1::text or (
a.uid = 0 and a.name = $1::text
)) and
(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
(a.netmask is null or a.netmask >> $4::inet)
order by blacklist desc limit 1
]];
};
actor_auth_challenge = {
params = {pstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name, a.blacklist, a.cred
from parsav_auth as a
left join parsav_actors as u on u.id = a.uid
where a.kind = 'challenge-' || $1::text and
(a.netmask is null or a.netmask >> $3::inet) and
(a.uid is null or u.handle = $2::text or
(a.uid = 0 and a.name = $2::text))
order by blacklist desc
]];
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
knownsince::bigint,
................................................................................
auth_create_pw = {
params = {uint64, binblob, int64, pstring}, sql = [[
insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values (
$1::bigint,
(select handle from parsav_actors where id = $1::bigint),
'pw-sha256', $2::bytea,
$3::bigint, $4::text
) on conflict (name,kind,cred) do update set comment = $4::text returning aid
]]
};
auth_create_rsa = {
params = {uint64, binblob, int64, pstring}, sql = [[
insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values (
$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 = [[
................................................................................
(cs.trust << r:bool(0,3))
return cs, true
end];
actor_auth_pw = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: pstring,
cred: pstring
): {uint64, uint64, pstring}
[ checksha(`src, 256, ip, username, cred) ] -- most common
[ checksha(`src, 512, ip, username, cred) ] -- most secure
[ checksha(`src, 384, ip, username, cred) ] -- weird
[ checksha(`src, 224, ip, username, cred) ] -- weirdest
-- TODO: check pbkdf2-hmac
-- TODO: check OTP
return 0, 0, pstring.null()
end];
actor_auth_challenge = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: pstring,
sig: binblob,
token: pstring
): {uint64, uint64, pstring}
-- we need to iterate through all the challenge types. right now that's just RSA
lib.dbg('checking against token ', {token.ptr,token.ct})
var rsakeys = queries.actor_auth_challenge.exec(src, 'rsa', username, ip)
var toprops = [terra(res: &pqr, i: intptr)
return {
aid = res:int(uint64, i, 0);
uid = res:int(uint64, i, 1);
name = res:_string(i, 2);
blacklist = res:bool(i, 3);
pubkey = res:bin(i, 4);
}
end]
if rsakeys.sz > 0 then defer rsakeys:free()
for i=0, rsakeys.sz do var props = toprops(&rsakeys, i)
lib.dbg('loading next RSA pubkey')
var pub = lib.crypt.loadpub(props.pubkey.ptr, props.pubkey.ct)
if pub.ok then defer pub.val:free()
lib.dbg('checking pubkey against response')
var vfy, secl = lib.crypt.verify(&pub.val, token.ptr, token.ct, sig.ptr, sig.ct)
if vfy then
lib.dbg('signature verified')
if props.blacklist then lib.dbg('key blacklisted!') goto fail end
var dupname = lib.str.dup(props.name.ptr)
return props.aid, props.uid, pstring {dupname, props.name.ct}
end
else lib.warn('invalid pubkey in authentication table for user ',{props.name.ptr, props.name.ct}) end
end
end
-- and so on
lib.dbg('no challenges were successful')
::fail::return 0, 0, pstring.null()
end];
actor_stats = [terra(src: &lib.store.source, uid: uint64)
var r = queries.actor_stats.exec(src, uid)
if r.sz == 0 then lib.bail('error fetching actor stats!') end
var s: lib.store.actor_stats
s.posts = r:int(uint64, 0, 0)
s.follows = r:int(uint64, 0, 1)
................................................................................
if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end
var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment)
if r.sz == 0 then return 0 end
var aid = r:int(uint64,0,0)
r:free()
return aid
end];
auth_attach_rsa = [terra(
src: &lib.store.source,
uid: uint64,
reset: bool,
pub: binblob,
comment: pstring
): uint64
if reset then queries.auth_purge_type.exec(src, nil, uid, 'challenge-%') end
var r = queries.auth_create_rsa.exec(src, uid, pub, lib.osclock.time(nil), comment)
if r.sz == 0 then return 0 end
var aid = r:int(uint64,0,0)
r:free()
return aid
end];
auth_privs_set = [terra(
src: &lib.store.source,
aid: uint64,
set: lib.store.privset
): {}
var map = array([lib.store.privmap])
|
Modified crypt.t from [f5b057e4fa] to [034fdb64c7].
10 11 12 13 14 15 16 17 18 19 20 21 22 23 .. 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 ... 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 ... 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 ... 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 ... 186 187 188 189 190 191 192 193 194 195 196 197 198 199 ... 200 201 202 203 204 205 206 207 208 |
end;
toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE;
}
const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to
const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one
local ctx = lib.pk.mbedtls_pk_context
local struct hashalg { id: uint8 bytes: intptr }
local m = {
pemfile = uint8[const.maxpemsz];
const = const;
algsz = {
sha1 = 160/8;
................................................................................
if pub then
return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
else
return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
end
end
terra m.der(pub: bool, key: &ctx, buf: &uint8): intptr
if pub then
return lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz)
else
return lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz)
end
end
m.destroy = lib.dispatch {
[ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end;
[false] = function(ptr) return `ptr:free() end;
}
................................................................................
lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA))
var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx)
lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537)
return pk
end
terra m.loadpriv(buf: &uint8, len: intptr): ctx
lib.dbg('parsing saved keypair')
var pk: ctx
lib.pk.mbedtls_pk_init(&pk)
lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0)
return pk
end
terra m.sign(pk: &ctx, txt: rawstring, len: intptr)
lib.dbg('signing message')
var osz: intptr = 0
var sig = lib.mem.heapa(int8, 2048)
var hash: uint8[32]
................................................................................
if ret ~= 0 then lib.bail('could not sign message hash')
else sig:resize(osz) end
return sig
end
terra m.verify(pk: &ctx, txt: rawstring, len: intptr,
sig: rawstring, siglen: intptr): {bool, uint8}
lib.dbg('verifying signature')
var osz: intptr = 0
var hash: uint8[64]
-- there does not appear to be any way to extract the hash algorithm
-- from the message, so we just have to try likely algorithms until
-- we find one that fits or give up. a security level is attached
................................................................................
{lib.md.MBEDTLS_MD_SHA256, 'sha256', 2},
{lib.md.MBEDTLS_MD_SHA512, 'sha512', 3},
{lib.md.MBEDTLS_MD_SHA1, 'sha1', 1},
-- uncommon hashes
{lib.md.MBEDTLS_MD_SHA384, 'sha384', 2},
{lib.md.MBEDTLS_MD_SHA224, 'sha224', 2},
-- bad hashes
{lib.md.MBEDTLS_MD_MD5, 'md5', 0},
{lib.md.MBEDTLS_MD_MD4, 'md4', 0},
{lib.md.MBEDTLS_MD_MD2, 'md2', 0}
)
for i = 0, [algs.type.N] do
var hk, aname, secl = algs[i]
lib.dbg('(1/2) trying hash algorithm ',aname)
if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then
................................................................................
end
terra m.hmaca(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8))
var buf = lib.mem.heapa(uint8, alg.bytes)
m.hmac(alg, key, txt, buf.ptr)
return buf
end
terra m.hotp(key: &(uint8[10]), counter: uint64)
var hmac: uint8[20]
var ctr = [lib.mem.ptr(int8)]{ptr = [&int8](&counter), ct = 8}
m.hmac(m.alg.sha1,
[lib.mem.ptr(uint8)]{ptr = [&uint8](key), ct = 10},
ctr, hmac)
................................................................................
var ofs = hmac[19] and 0x0F
var p: uint8[4]
for i=0,4 do p[i] = hmac[ofs + i] end
return (@[&uint32](&p)) and 0x7FFFFFFF -- one hopes it's that easy
end
return m
|
> > | > | | > > > > | | | | > > > > > > > > > > > > > > > > > > > | | | | > > > > > > > > > > > > > > > > > > > > > > > > > |
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 .. 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 ... 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 ... 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 ... 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 ... 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 ... 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
end; toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE; } const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one local ctx = lib.pk.mbedtls_pk_context terra ctx:free() lib.pk.mbedtls_pk_free(self) end local struct hashalg { id: uint8 bytes: intptr } local m = { pemfile = uint8[const.maxpemsz]; const = const; algsz = { sha1 = 160/8; ................................................................................ if pub then return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0 else return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0 end end local binblob = lib.mem.ptr(uint8) terra m.der(pub: bool, key: &ctx, buf: &uint8): binblob var ofs: intptr if pub then ofs = lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz) else ofs = lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz) end return binblob { ptr = buf + (const.maxdersz - ofs); ct = ofs; } end m.destroy = lib.dispatch { [ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end; [false] = function(ptr) return `ptr:free() end; } ................................................................................ lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx) lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537) return pk end terra m.loadpriv(buf: &uint8, len: intptr): lib.stat(ctx) lib.dbg('parsing saved private key') var pk: ctx lib.pk.mbedtls_pk_init(&pk) var rt = lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0) if rt == 0 then return [lib.stat(ctx)] { ok = true, val = pk } else lib.pk.mbedtls_pk_free(&pk) return [lib.stat(ctx)] { ok = false } end end terra m.loadpub(buf: &uint8, len: intptr): lib.stat(ctx) lib.dbg('parsing saved key') var pk: ctx lib.pk.mbedtls_pk_init(&pk) var rt = lib.pk.mbedtls_pk_parse_public_key(&pk, buf, len) if rt == 0 then return [lib.stat(ctx)] { ok = true, val = pk } else lib.pk.mbedtls_pk_free(&pk) return [lib.stat(ctx)] { ok = false, error = rt } end end terra m.sign(pk: &ctx, txt: rawstring, len: intptr) lib.dbg('signing message') var osz: intptr = 0 var sig = lib.mem.heapa(int8, 2048) var hash: uint8[32] ................................................................................ if ret ~= 0 then lib.bail('could not sign message hash') else sig:resize(osz) end return sig end terra m.verify(pk: &ctx, txt: rawstring, len: intptr, sig: &uint8, siglen: intptr): {bool, uint8} lib.dbg('verifying signature') var osz: intptr = 0 var hash: uint8[64] -- there does not appear to be any way to extract the hash algorithm -- from the message, so we just have to try likely algorithms until -- we find one that fits or give up. a security level is attached ................................................................................ {lib.md.MBEDTLS_MD_SHA256, 'sha256', 2}, {lib.md.MBEDTLS_MD_SHA512, 'sha512', 3}, {lib.md.MBEDTLS_MD_SHA1, 'sha1', 1}, -- uncommon hashes {lib.md.MBEDTLS_MD_SHA384, 'sha384', 2}, {lib.md.MBEDTLS_MD_SHA224, 'sha224', 2}, -- bad hashes {lib.md.MBEDTLS_MD_MD5, 'md5', 0} --{lib.md.MBEDTLS_MD_MD4, 'md4', 0}, --{lib.md.MBEDTLS_MD_MD2, 'md2', 0} ) for i = 0, [algs.type.N] do var hk, aname, secl = algs[i] lib.dbg('(1/2) trying hash algorithm ',aname) if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then ................................................................................ end terra m.hmaca(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8)) var buf = lib.mem.heapa(uint8, alg.bytes) m.hmac(alg, key, txt, buf.ptr) return buf end terra m.hmacp(p: &lib.mem.pool, alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8)) var buf = p:alloc(uint8, alg.bytes) m.hmac(alg, key, txt, buf.ptr) return buf end terra m.hotp(key: &(uint8[10]), counter: uint64) var hmac: uint8[20] var ctr = [lib.mem.ptr(int8)]{ptr = [&int8](&counter), ct = 8} m.hmac(m.alg.sha1, [lib.mem.ptr(uint8)]{ptr = [&uint8](key), ct = 10}, ctr, hmac) ................................................................................ var ofs = hmac[19] and 0x0F var p: uint8[4] for i=0,4 do p[i] = hmac[ofs + i] end return (@[&uint32](&p)) and 0x7FFFFFFF -- one hopes it's that easy end local splitwords = macro(function(str) local words = {} for w in str:asvalue():gmatch('(%g+)') do words[#words + 1] = w end return `arrayof(lib.str.t, [words]) end) terra m.cryptogram(a: &lib.str.acc, len: intptr) var words = splitwords [[ alpha beta gamma delta epsilon psi eta nu omicron omega red crimson green verdant golden silver blue cyan navy carnelian opal sapphire amethyst ruby jade emerald chalice peacock cabernet windmill saxony tunnel waterspout ]] for i = 0, len do a:ppush(words[m.random(intptr,0,[words.type.N])]):lpush '-' end a:ipush(m.random(uint32,0,99999)) end return m |
Added doc/auth.md version [ac7a8d6f57].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# credentials & authentication 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. ## mechanisms 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. ### password auth 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. 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. 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. ### challenge auth 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. 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. 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: $ openssl genrsa 2048 -out private.pem # creates a reasonably secure 2048-bit private key $ openssl genrsa 4096 -out private.pem # creates an *extremely secure 4096-bit key $ openssl genrsa 2048 -aes256 -out private.pem # pass -aes256 to encrypt your key 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!) $ openssl rsa -in private.pem -pubout -out public.pem `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. finally, you'll need to use this key to actually sign things: $ echo -n "this is the string that will be signed" | openssl dgst -sha256 -sign private.pem | openssl base64 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: $ xsel -bo | openssl dgst -sha256 -sign private.pem | openssl base64 | xsel -bi if you later want to change the password on your private key, you can use this command to do so: $ openssl rsa -in private.pem -aes256 -out private.pem # omit the -aes256 to remove the encryption ## managing credentials 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. 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. 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
local path = ...
local sources = {
-- user section
acl = {title = 'access control lists'};
-- admin section
--overview = {title = 'server overview', priv = 'config'};
invocation = {title = 'daemon invocation', priv = 'config'};
usr = {title = 'user accounting', priv = {'elevate','demote','purge','herald'}};
--srvcfg = {title = 'server configuration policies', priv = 'config'};
--discipline = {title = 'disciplinary measures', priv = 'discipline'};
--backends = {title = 'storage backends', priv = 'config'};
--pgsql = {title = 'pgsql', priv = 'config', parent = 'backends'};
}
local util = dofile 'common.lua'
local ingest = function(filename)
return (util.exec { 'cmark', '--smart', '--unsafe', (path..'/'..filename) }):gsub('\n','')
end
local doc = {}
for n,meta in pairs(sources) do doc[n] = {
name = n;
text = ingest(n .. '.md');
meta = meta;
} end
return doc
|
> | > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
local path = ...
local sources = {
-- user section
acl = {title = 'access control lists'};
auth = {title = 'credentials & authentication', priv = 'account'};
-- admin section
--overview = {title = 'server overview', priv = 'config'};
invocation = {title = 'daemon invocation', priv = 'config'};
usr = {title = 'user accounting', priv = {'elevate','demote','purge','herald'}};
--srvcfg = {title = 'server configuration policies', priv = 'config'};
--discipline = {title = 'disciplinary measures', priv = 'discipline'};
--backends = {title = 'storage backends', priv = 'config'};
--pgsql = {title = 'pgsql', priv = 'config', parent = 'backends'};
}
local util = dofile 'common.lua'
local ingest = function(filename)
return (util.exec { 'cmark', '--smart', '--unsafe', (path..'/'..filename) })
--:gsub('\n','')
end
local doc = {}
for n,meta in pairs(sources) do doc[n] = {
name = n;
text = ingest(n .. '.md');
meta = meta;
} end
return doc
|
Modified mgtool.t from [600c3c1067] to [4fc07a70c1].
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
dlg:tx_enter() if dlg:dbsetup() then srv:conprep(lib.store.prepmode.conf) do var newkp = lib.crypt.genkp() -- generate server privkey var kbuf: uint8[lib.crypt.const.maxdersz] var privsz = lib.crypt.der(false,&newkp, kbuf) dlg:server_setup_self(dbmode.arglist(1), [lib.mem.ptr(uint8)] { ptr = &kbuf[0], ct = privsz }) end dlg:conf_set('instance-name', dbmode.arglist(1)) dlg:conf_set('domain', dbmode.arglist(1)) do var sec: int8[65] gensec(&sec[0]) dlg:conf_set('server-secret', &sec[0]) end |
| | < < |
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
dlg:tx_enter() if dlg:dbsetup() then srv:conprep(lib.store.prepmode.conf) do var newkp = lib.crypt.genkp() -- generate server privkey var kbuf: uint8[lib.crypt.const.maxdersz] var derkey = lib.crypt.der(false,&newkp, kbuf) dlg:server_setup_self(dbmode.arglist(1), derkey) end dlg:conf_set('instance-name', dbmode.arglist(1)) dlg:conf_set('domain', dbmode.arglist(1)) do var sec: int8[65] gensec(&sec[0]) dlg:conf_set('server-secret', &sec[0]) end |
Modified parsav.md from [af14d66fc5] to [23851a52a8].
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
* ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2
* ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server
* ☐ pw-extern-cyrus: try to authenticate against saslauthd
* ☐ pw-extern-dovecot: try to authenticate against a dovecot SASL socket
* ☐ pw-extern-krb5: abuse MIT kerberos as a password verifier
* ☐ pw-extern-imap: abuse an email server as a password verifier
* (extra credit) ☐ pw-extern-radius: verify a user against a radius server
* ☐ 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`.
* ☐ 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
* ☐ tls-cert-fp: a fingerprint of a client certificate
* ☐ 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
* ☐ 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.
* ☐ 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.
* ☐ 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.
* ☑ trust: authentication always succeeds (or fails, if blacklisted). only use in combination with netmask!!!
## legal
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.
## code of conduct
|
> > > > > | | | > > |
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
* ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2
* ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server
* ☐ pw-extern-cyrus: try to authenticate against saslauthd
* ☐ pw-extern-dovecot: try to authenticate against a dovecot SASL socket
* ☐ pw-extern-krb5: abuse MIT kerberos as a password verifier
* ☐ pw-extern-imap: abuse an email server as a password verifier
* (extra credit) ☐ pw-extern-radius: verify a user against a radius server
* ☐ 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
* ☐ http-gssapi: log in with a kerberos principle through the http-authenticate "negotiate" mechanism. do any browsers actually support this??
* ☐ 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.
* ☐ 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.
* ☐ 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`.
* ☐ 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-*`!
* ☐ 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
* ☐ tls-cert-fp: a fingerprint of a client certificate
* ☐ 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
* ☐ 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.
* ☐ challenge-ecc a Curve25519 public key. the user is presented with a challenge and must sign it with a supported hash algorithm
* ☐ 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.
* ☑ trust: authentication always succeeds (or fails, if blacklisted). only use in combination with netmask!!!
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.
## legal
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.
## code of conduct
|
Modified parsav.t from [04e1a3fbb9] to [392d24dbd5].
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
self._store[i/8] = self._store[i/8] and not (1 << (i % 8))
end
end
set.bits = {}
set.idvmap = {}
for i,v in ipairs(tbl) do
set.idvmap[v] = i
set.bits[v] = quote var b: set b:clear() b:setbit(i, true) in b end
end
set.metamethods.__add = macro(function(self,other)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] or other._store[i]
|
| |
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
self._store[i/8] = self._store[i/8] and not (1 << (i % 8))
end
end
set.bits = {}
set.idvmap = {}
for i,v in ipairs(tbl) do
set.idvmap[v] = i
set.bits[v] = quote var b: set b:clear() b:setbit([i-1], true) in b end
end
set.metamethods.__add = macro(function(self,other)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] or other._store[i]
|
Modified render/conf/sec.t from [157639932a] to [3bc273639f].
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
end end credmgr.credlist = cl:finalize() end credmgr:append(&a) --if credmgr.credlist.ct > 0 then credmgr.credlist:free() end else if new:cmp('pw') then var d: data.view.conf_sec_pwnew var time = lib.osclock.time(nil) var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) var cmt = co:stra(48) cmt:lpush('enrolled over http on '):push(×tr[0],0) d.comment = cmt:finalize() var st = d:poolstr(&co.srv.pool) --d.comment:free() return st elseif new:cmp('challenge') then -- we're going to break the rules a bit and do database munging from -- the rendering code, because doing otherwise in this case would be -- genuinely nightmarish elseif new:cmp('otp') then elseif new:cmp('api') then else return pstr.null() end end |
< < | | | | > > | > > > > > > > > > > > > > |
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
end
end
credmgr.credlist = cl:finalize()
end
credmgr:append(&a)
--if credmgr.credlist.ct > 0 then credmgr.credlist:free() end
else
var time = lib.osclock.time(nil)
var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0])
var cmt = co:stra(48)
cmt:lpush('enrolled over http on '):push(×tr[0],0)
if new:cmp('pw') then
var d: data.view.conf_sec_pwnew
d.comment = cmt:finalize()
var st = d:poolstr(&co.srv.pool)
--d.comment:free()
return st
elseif new:cmp('rsa') then
var c = co:stra(64)
lib.crypt.cryptogram(&c, 8)
var cptr = c:finalize();
var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), cptr); -- TODO should expire after 10min
var hmacte: int8[lib.math.shorthand.maxlen]
var hmacte_len = lib.math.shorthand.gen(lib.math.truncate64(hmac.ptr, hmac.ct), &hmacte[0])
var d = data.view.conf_sec_keynew {
comment = cmt:finalize();
nonce = cptr;
noncevld = pstr { ptr = &hmacte[0], ct = hmacte_len };
}
return d:poolstr(&co.srv.pool)
-- we're going to break the rules a bit and do database munging from
-- the rendering code, because doing otherwise in this case would be
-- genuinely nightmarish
elseif new:cmp('otp') then
elseif new:cmp('api') then
else return pstr.null() end
end
|
Modified render/login.t from [434636bebc] to [da67d9c6fd].
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr)
var doc = [lib.srv.convo.page] {
title = 'instance logon';
class = 'login';
cache = false;
}
if user == nil then
var form = data.view.login_username {
loginmsg = msg;
}
if form.loginmsg.ptr == nil then
form.loginmsg = 'identify yourself for access to this instance.'
end
doc.body = form:tostr()
elseif creds:sz() == 0 then
co:complain(403,'access denied','your host is not eligible to authenticate as this user')
return
elseif creds:sz() == 1 then
if creds.trust() then
-- TODO log in immediately
return
end
var ch = data.view.login_challenge {
handle = user.handle;
name = lib.coalesce(user.nym, user.handle);
}
if creds.pw() then
ch.challenge = 'enter the password associated with your account'
ch.label = 'password'
ch.method = 'pw'
ch.auto = 'current-password';
elseif creds.otp() then
ch.challenge = 'enter a valid one-time password for your account'
ch.label = 'OTP code'
ch.method = 'otp'
ch.auto = 'one-time-code';
elseif creds.challenge() then
ch.challenge = 'sign the challenge token: <code>...</code>'
ch.label = 'digest'
ch.method = 'challenge'
ch.auto = 'one-time-code';
else
co:complain(500,'login failure','unknown login method')
return
end
doc.body = ch:tostr()
else
-- pick a method
end
co:stdpage(doc)
doc.body:free()
end
return login_form
|
> > | > > > > > > > > > > | | > > > > > > | > > > > < > > > > > > > > > > > > > > < > | < | > > > > > > > > > | |
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr)
var doc = [lib.srv.convo.page] {
title = 'instance logon';
class = 'login';
cache = false;
}
var how = co:ppostv('how')
if user == nil then
var form = data.view.login_username {
loginmsg = msg;
}
if form.loginmsg.ptr == nil then
form.loginmsg = 'identify yourself for access to this instance.'
end
doc.body = form:tostr()
elseif creds:sz() == 0 then
co:complain(403,'access denied','your host is not eligible to authenticate as this user')
return
elseif creds:sz() == 1 or how:ref() then
var newcreds: lib.store.credset
if how:ref() then
if how:cmp('pw') then newcreds = creds and [lib.store.credset.bits.pw]
elseif how:cmp('chlg') then newcreds = creds and [lib.store.credset.bits.challenge]
elseif how:cmp('otp') then newcreds = creds and [lib.store.credset.bits.otp]
elseif how:cmp('trust') then newcreds = creds and [lib.store.credset.bits.trust]
else co:complain(400, 'bad request', 'the requested authentication method is not available') return end
creds = &newcreds
end
if creds.trust() then
-- TODO log in immediately
return
end
var ch = data.view.login_challenge {
handle = user.handle;
name = lib.coalesce(user.nym, user.handle);
}
if creds.pw() then
ch.challenge = 'enter the password associated with your account'
ch.label = 'password'
ch.method = 'pw'
ch.inputfield = '<input type="password" autocomplete="current-password" name="response" id="response" autofocus required>';
elseif creds.otp() then
ch.challenge = 'enter a valid one-time password for your account'
ch.label = 'OTP code'
ch.method = 'otp'
ch.inputfield = '<input type="text" autocomplete="one-time-code" name="response" id="response" autofocus required>';
elseif creds.challenge() then
var tok = co:stra(128)
var chlg = co:stra(128)
var input = co:stra(256)
var time = lib.osclock.time(nil)
lib.crypt.cryptogram(&tok,6)
chlg:lpush 'sign the challenge token <code style="display:block;user-select: all">'
:push(tok.buf,tok.sz)
:lpush '</code>'
ch.challenge = chlg:finalize()
ch.label = 'digest'
ch.method = 'challenge'
input:lpush '<textarea autocomplete="one-time-code" name="response" id="response" autofocus required></textarea><input type="hidden" name="time" value="'
:shpush(time)
:lpush '"><input type="hidden" name="token" value="'
:push(tok.buf,tok.sz)
tok:shpush(time)
var hmac = lib.crypt.hmacp(&co.srv.pool,
lib.crypt.alg.sha256,
co.srv.cfg.secret:blob(),
tok:finalize())
input:lpush '"><input type="hidden" name="vfy" value="'
:shpush(lib.math.truncate64(hmac.ptr, hmac.ct)) -- FIXME this is probably not very secure...
:lpush '">'
ch.inputfield = input:finalize()
else
co:complain(400,'login failure','no usable login methods are available')
return
end
doc.body = ch:poolstr(&co.srv.pool)
else -- pick a method
var a = co:stra(400)
var username = lib.html.sanitize(&co.srv.pool, pstr{user.handle,0}, true)
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'">'
if creds.trust() then a:lpush '<button name="how" value="trust">trust</button>' end
if creds.pw() then a:lpush '<button name="how" value="pw">password</button>' end
if creds.otp() then a:lpush '<button name="how" value="otp">TOTP code</button>' end
if creds.challenge() then a:lpush '<button name="how" value="chlg">challenge</button>' end
a:lpush '</menu></form>'
doc.body = a:finalize()
end
co:stdpage(doc)
--doc.body:free()
end
return login_form
|
Modified route.t from [6caa1366ba] to [cd7a14ae6e].
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
...
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
-- pick an auth method
lib.render.login(co, act.ptr, &cs, pstring.null())
else var aid: uint64 = 0
lib.dbg('authentication attempt beginning')
-- attempt login with provided method
if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
aid = co.srv:actor_auth_pw(co.peer,
[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
lib.dbg('using otp auth')
-- ··· --
else lib.dbg('invalid auth method') end
-- error out
if aid == 0 then
lib.render.login(co, nil, nil, 'authentication failure')
................................................................................
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 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)
else
var key = co:ppostv('newkey')
if key:ref() then
end
end
if aid ~= 0 then
lib.dbg('setting credential restrictions')
var privs = [(function()
local check = quote end
local me = symbol(lib.store.privset)
for i,v in ipairs(lib.store.privset.members) do
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
...
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
|
-- pick an auth method
lib.render.login(co, act.ptr, &cs, pstring.null())
else var aid: uint64 = 0
lib.dbg('authentication attempt beginning')
-- attempt login with provided method
if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
aid = co.srv:actor_auth_pw(co.peer,
pstring {ptr=usn,ct=usnl},
pstring {ptr=chrs,ct=chrsl})
elseif lib.str.ncmp('challenge', am, lib.math.biggest(9,aml)) == 0 and chrs ~= nil then
lib.dbg('challenge attempt beginning')
var s_time = co:ppostv('time')
var s_vfy = co:ppostv('vfy')
var token = co:ppostv('token')
if s_time:ref() and s_vfy:ref() and token:ref() then
lib.dbg('checking hmac validity')
var vftok = co:stra(128) vftok:ppush(token):ppush(s_time)
var hmac = lib.crypt.hmacp(&co.srv.pool, lib.crypt.alg.sha256, co.srv.cfg.secret:blob(), vftok:finalize())
var vfy, vfyok = lib.math.shorthand.parse(s_vfy.ptr, s_vfy.ct)
if vfyok and lib.math.truncate64(hmac.ptr,hmac.ct) == vfy then
lib.dbg('checking expiration time')
var time, timeok = lib.math.shorthand.parse(s_time.ptr, s_time.ct)
if timeok and lib.osclock.time(nil) - time < [2 * 60] then -- two minutes
lib.dbg('decoding base64')
var bin = co.srv.pool:alloc(uint8, chrsl)
var binlen: intptr
if lib.b64.mbedtls_base64_decode(bin.ptr, bin.ct, &binlen, [&uint8](chrs), chrsl) == 0 then
lib.dbg('running signature <',{chrs,chrsl},'> against challenge keys for token [', {token.ptr,token.ct}, ']')
aid = co.srv:actor_auth_challenge(co.peer,
pstring {usn,usnl}, binblob{bin.ptr,binlen}, token)
end
end
end
end
elseif lib.str.ncmp('otp', am, lib.math.biggest(3,aml)) == 0 and chrs ~= nil then
lib.dbg('using otp auth')
-- ··· --
else lib.dbg('invalid auth method') end
-- error out
if aid == 0 then
lib.render.login(co, nil, nil, 'authentication failure')
................................................................................
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
if aid ~= 0 then
lib.dbg('setting credential restrictions')
var privs = [(function()
local check = quote end
local me = symbol(lib.store.privset)
for i,v in ipairs(lib.store.privset.members) do
|
Modified srv.t from [2ff93305a1] to [826b7b2edb].
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 |
cs = cs + set ok = iok end end return cs, ok end terra srv:actor_auth_pw(ip: lib.store.inet, user: pstring, pw: pstring): uint64 for i=0,self.sources.ct do if self.sources(i).backend ~= nil and self.sources(i).backend.actor_auth_pw ~= nil then var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) if aid ~= 0 then if uid == 0 then lib.dbg('new user just logged in, creating account entry') var kbuf: uint8[lib.crypt.const.maxdersz] var na = lib.store.actor.mk(&kbuf[0]) na.handle = newhnd.ptr var newuid: uint64 if self.sources(i).backend.actor_create ~= nil then newuid = self.sources(i):actor_create(&na) else newuid = self:actor_create(&na) end if self.sources(i).backend.actor_auth_register_uid ~= nil then self.sources(i):actor_auth_register_uid(aid,newuid) end end return aid end end end return 0 end terra cfgcache.methods.load :: {&cfgcache} -> {} terra cfgcache:init(o: &srv) self.overlord = o self:load() end |
> > > | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 |
cs = cs + set ok = iok end end return cs, ok end local function mk_auth_fn(suffix,...) local syms = {...} local name = 'actor_auth_' .. suffix srv.methods[name] = terra(self: &srv, ip: lib.store.inet, user: pstring, [syms]): uint64 for i=0,self.sources.ct do if self.sources(i).backend ~= nil and self.sources(i).backend.[name] ~= nil then var aid,uid,newhnd = self.sources(i):[name](ip,user, [syms]) if aid ~= 0 then if uid == 0 then lib.dbg('new user just logged in, creating account entry') var kbuf: uint8[lib.crypt.const.maxdersz] var na = lib.store.actor.mk(&kbuf[0]) na.handle = newhnd.ptr var newuid: uint64 if self.sources(i).backend.actor_create ~= nil then newuid = self.sources(i):actor_create(&na) else newuid = self:actor_create(&na) end if self.sources(i).backend.actor_auth_register_uid ~= nil then self.sources(i):actor_auth_register_uid(aid,newuid) end end return aid end end end return 0 end srv.methods[name].name = name end mk_auth_fn('pw', symbol(pstring)) mk_auth_fn('challenge', symbol(lib.mem.ptr(uint8)), symbol(pstring)) --terra srv:actor_auth_pw(ip: lib.store.inet, user: pstring, pw: pstring): uint64 -- for i=0,self.sources.ct do -- if self.sources(i).backend ~= nil and -- self.sources(i).backend.actor_auth_pw ~= nil then -- var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) -- if aid ~= 0 then -- if uid == 0 then -- lib.dbg('new user just logged in, creating account entry') -- var kbuf: uint8[lib.crypt.const.maxdersz] -- var na = lib.store.actor.mk(&kbuf[0]) -- na.handle = newhnd.ptr -- var newuid: uint64 -- if self.sources(i).backend.actor_create ~= nil then -- newuid = self.sources(i):actor_create(&na) -- else newuid = self:actor_create(&na) end -- -- if self.sources(i).backend.actor_auth_register_uid ~= nil then -- self.sources(i):actor_auth_register_uid(aid,newuid) -- end -- end -- return aid -- end -- end -- end -- -- return 0 --end terra cfgcache.methods.load :: {&cfgcache} -> {} terra cfgcache:init(o: &srv) self.overlord = o self:load() end |
Modified static/style.scss from [e23a7affc6] to [048cf30682].
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 ... 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 ... 697 698 699 700 701 702 703 704 705 706 707 708 709 710 |
margin:auto;
padding: 0.5in;
text-align: center;
menu:first-of-type { margin-top: 0.3in; }
img.icon { width: 1.875in; height: 1.875in; }
}
div.login {
@extend %box;
width: 4in;
padding: 0.4in;
> .msg {
text-align: center;
padding: 0.3in;
}
> .msg:first-child { padding-top: 0; }
> .user {
width: max-content; margin: auto;
background: tone(-20%,-0.3);
border: 1px solid black;
color: tone(-50%);
padding: 0.1in;
> img { display: block; width: 1in; height: 1in; margin: auto; border: 1px solid black; }
> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
}
>form {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1.2em 1fr 1fr;
grid-gap: 5px;
> label, input, button { display: block; }
> label { grid-column: 1 / 3; grid-row: 1/2; font-weight: bold }
> input { grid-column: 1 / 3; grid-row: 2/3; }
> button { grid-column: 2 / 3; grid-row: 3/4; }
> a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; }
}
}
form.compose {
@extend %box;
display: grid;
grid-template-columns: 1.1in 2fr min-content 1fr 1.5fr;
................................................................................
position: absolute;
top: -0.3in;
right: 0.1in;
margin: 0.1in;
padding: 0.1in;
&:hover { font-weight: bold; }
}
}
code {
@extend %teletype;
background: tone(-55%);
border: 1px inset tone(-20%);
padding: 2px 6px;
font-size: 1.5ex !important;
letter-spacing: 1.3px;
padding-bottom: 3px;
border-radius: 2px;
vertical-align: baseline;
box-shadow: 1px 1px 1px black;
}
pre { @extend %teletype; white-space: pre-wrap; }
div.thread {
margin-left: 0.3in;
& + article.post { margin-top: 0.3in; }
}
a[href].username {
................................................................................
> label,summary { display:block; font-weight: bold; padding: 0.03in 0; }
> .txtbox {
@extend %serif;
box-sizing: border-box;
padding: 0.08in 0.1in;
border: 1px solid black;
background: tone(-55%);
}
> input, textarea, .txtbox {
display: block;
width: 100%;
}
> textarea { resize: vertical; min-height: 2in; }
}
|
> > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > | | > > | > > > > > > |
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 433 434 435 ... 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 ... 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 |
margin:auto;
padding: 0.5in;
text-align: center;
menu:first-of-type { margin-top: 0.3in; }
img.icon { width: 1.875in; height: 1.875in; }
}
body.login {
form.auth-select {
@extend %box;
width: 3in;
padding: 0.4in;
p { text-align: center; }
menu {
%button {
display: block;
width: 100%;
& + %button { border-top: none; }
}
}
}
div.login {
@extend %box;
width: 4in;
padding: 0.4in;
> .msg {
text-align: center;
padding: 0.3in;
}
> .msg:first-child { padding-top: 0; }
> .user {
width: max-content; margin: auto;
background: tone(-20%,-0.3);
border: 1px solid black;
color: tone(-50%);
padding: 0.1in;
> img { display: block; width: 1in; height: 1in; margin: auto; border: 1px solid black; }
> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
}
>form {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1.2em max-content max-content;
grid-gap: 5px;
> label, input, button { display: block; }
> label { grid-column: 1 / 3; grid-row: 1/2; font-weight: bold }
> input, textarea { grid-column: 1 / 3; grid-row: 2/3; }
> button { grid-column: 2 / 3; grid-row: 3/4; }
> a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; }
}
}
}
form.compose {
@extend %box;
display: grid;
grid-template-columns: 1.1in 2fr min-content 1fr 1.5fr;
................................................................................
position: absolute;
top: -0.3in;
right: 0.1in;
margin: 0.1in;
padding: 0.1in;
&:hover { font-weight: bold; }
}
p { text-align: justify; }
}
code {
@extend %teletype;
background: tone(-55%);
// border: 1px inset tone(-20%);
padding: 2px 6px;
font-size: 1.5ex !important;
letter-spacing: 1.3px;
padding-bottom: 3px;
border-radius: 4px;
vertical-align: baseline;
box-shadow: 1px 1px 1px black;
}
pre {
@extend %teletype;
white-space: pre-wrap;
> code:only-child {
display: block;
padding: 0.1in;
}
}
div.thread {
margin-left: 0.3in;
& + article.post { margin-top: 0.3in; }
}
a[href].username {
................................................................................
> label,summary { display:block; font-weight: bold; padding: 0.03in 0; }
> .txtbox {
@extend %serif;
box-sizing: border-box;
padding: 0.08in 0.1in;
border: 1px solid black;
background: tone(-55%);
user-select: all;
}
> input, textarea, .txtbox {
display: block;
width: 100%;
}
> textarea { resize: vertical; min-height: 2in; }
}
|
Modified store.t from [9b6251fcb3] to [53eb63c414].
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 ... 384 385 386 387 388 389 390 391 392 393 394 395 396 397 ... 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
end -- TODO validate fully return true end terra m.actor.methods.mk(kbuf: &uint8) var newkp = lib.crypt.genkp() var privsz = lib.crypt.der(false,&newkp,kbuf) return m.actor { id = 0; nym = nil; handle = nil; origin = 0; bio = nil; avatar = nil; knownsince = lib.osclock.time(nil); rights = m.rights_default(); avatarid = 0; epithet = nil, key = [lib.mem.ptr(uint8)] { ptr = &kbuf[0], ct = privsz }; } end struct m.actor_stats { posts: intptr follows: intptr followers: intptr ................................................................................ -> {uint64, uint64, pstr} actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } -> {uint64, uint64, pstr} -- handles password-based logins against hashed passwords -- origin: inet -- handle: rawstring -- token: rawstring actor_auth_tls: {&m.source, m.inet, rawstring} -> {uint64, uint64, pstr} -- handles implicit authentication performed as part of an TLS connection -- origin: inet -- fingerprint: rawstring actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 -> {uint64, uint64, pstr} ................................................................................ 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_key: {&m.source, uint64, bool, pstr, pstr} -> {} -- 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} -> {} |
| | < < > > > > > > | |
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 ... 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 ... 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
end -- TODO validate fully return true end terra m.actor.methods.mk(kbuf: &uint8) var newkp = lib.crypt.genkp() var derkey = lib.crypt.der(false,&newkp,kbuf) return m.actor { id = 0; nym = nil; handle = nil; origin = 0; bio = nil; avatar = nil; knownsince = lib.osclock.time(nil); rights = m.rights_default(); avatarid = 0; epithet = nil, key = derkey; } end struct m.actor_stats { posts: intptr follows: intptr followers: intptr ................................................................................ -> {uint64, uint64, pstr} actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } -> {uint64, uint64, pstr} -- handles password-based logins against hashed passwords -- origin: inet -- handle: rawstring -- token: rawstring actor_auth_challenge: {&m.source, m.inet, pstr, lib.mem.ptr(uint8), pstr } -> {uint64, uint64, pstr} -- origin: inet -- handle: rawstring -- response: rawstring -- challenge token: pstring actor_auth_tls: {&m.source, m.inet, rawstring} -> {uint64, uint64, pstr} -- handles implicit authentication performed as part of an TLS connection -- origin: inet -- fingerprint: rawstring actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 -> {uint64, uint64, pstr} ................................................................................ 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} -> {} |
Modified str.t from [c25d641c64] to [2798df18ea].
58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
end
return true
end
terra ty:ffw()
var newp = m.ffw(self.ptr,self.ct)
var newct = self.ct - (newp - self.ptr)
return ty { ptr = newp, ct = newct }
end
end
install_funcs(strptr)
install_funcs(strref)
--strptr.methods.cmpl = macro(function(self,other)
-- return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
|
> > > > > > |
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
end
return true
end
terra ty:ffw()
var newp = m.ffw(self.ptr,self.ct)
var newct = self.ct - (newp - self.ptr)
return ty { ptr = newp, ct = newct }
end
terra ty:blob()
return byteptr {
ptr = [&uint8](self.ptr);
ct = self.ct;
}
end
end
install_funcs(strptr)
install_funcs(strref)
--strptr.methods.cmpl = macro(function(self,other)
-- return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
|
Modified view/conf-sec-credmg.tpl from [c30c9309b5] to [a2babc26c2].
3
4
5
6
7
8
9
10
11
12
13
14
15
16
..
25
26
27
28
29
30
31
32
33
34
35
36
|
<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> <select size="6" name="cred"> @credlist </select> <menu class="horizontal choice"> <button name="act" value="reset">reset</button> <button name="act" value="revoke">revoke</button> </menu> </form> <hr> <form method="get"> <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> <div class="check-panel"> <label><input type="checkbox" name="allow-post"> post</label> ................................................................................ <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> <div class="elem"> <label for="netmask">netmask</label> <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> </div> <menu class="vertical choice"> <button name="new" value="pw">new password</button> <button name="new" value="otp">new OTP key</button> <button name="new" value="api">new API token</button> <button name="new" value="challenge">new challenge key</button> </menu> </form> |
>
>
<
|
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
26
27
28
29
30
31
32
33
34
35
36
37
|
<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> <select size="6" name="cred"> @credlist </select> <menu class="horizontal choice"> <button name="act" value="reset">reset</button> <button name="act" value="revoke">revoke</button> @?auth </menu> </form> <hr> <form method="get"> <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> <div class="check-panel"> <label><input type="checkbox" name="allow-post"> post</label> ................................................................................ <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> <div class="elem"> <label for="netmask">netmask</label> <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> </div> <menu class="vertical choice"> <button name="new" value="pw">new password</button> <button name="new" value="rsa">new RSA key</button> <button name="new" value="otp">new OTP key</button> <button name="new" value="api">new API token</button> </menu> </form> |
Added view/conf-sec-keynew.tpl version [039cf710c4].
> > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<form method="post"> <div class="elem"> <label for="comment">comment</label> <input type="text" id="comment" name="comment" value="@comment" required> </div> <div class="elem"> <label for="newkey">public key in PEM format</label> <textarea id="newkey" name="newrsa" required></textarea> </div> <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> <code style="display:block; user-select: all">echo -n @nonce | openssl dgst -sha256 -sign privkey.pem | openssl base64</code> <div class="elem"> <label>nonce</label> <div class="txtbox">@nonce</div> <input type="hidden" name="nonce" value="@nonce"> <input type="hidden" name="noncevld" value="@noncevld"> </div> <div class="elem"> <label for="sig">nonce signature</label> <textarea id="sig" name="sig" required></textarea> </div> <menu class="choice horizontal"> <button name="act" value="newcred">enroll</button> <a class="button" href="?">cancel</a> </menu> </form> |
Modified view/load.lua from [15344e4760] to [3d222d2b19].
20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
'login-challenge';
'conf';
'conf-profile';
'conf-sec';
'conf-sec-credmg';
'conf-sec-pwnew';
'conf-user-ctl';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
|
> |
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
'login-challenge';
'conf';
'conf-profile';
'conf-sec';
'conf-sec-credmg';
'conf-sec-pwnew';
'conf-sec-keynew';
'conf-user-ctl';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
|
Modified view/login-challenge.tpl from [84fccbb367] to [cc4592543f].
3 4 5 6 7 8 9 10 11 12 13 14 |
<img src="/avi/@handle">
<div class="name">@!name</div>
</div>
<div class="msg">@challenge</div>
<form action="/login" method="post">
<label for="response">@label</label>
<input type="hidden" name="user" value="@:handle">
<input type="password" autocomplete="@auto" name="response" id="response" autofocus required>
<button type="submit" name="authmethod" value="@method">authenticate</button>
<a href="/login">cancel</a>
</form>
</div>
|
| |
3 4 5 6 7 8 9 10 11 12 13 14 |
<img src="/avi/@handle">
<div class="name">@!name</div>
</div>
<div class="msg">@challenge</div>
<form action="/login" method="post">
<label for="response">@label</label>
<input type="hidden" name="user" value="@:handle">
@inputfield
<button type="submit" name="authmethod" value="@method">authenticate</button>
<a href="/login">cancel</a>
</form>
</div>
|
Modified view/login-username.tpl from [8c165f8ae9] to [c0dae1cbbc].
1 2 3 4 5 6 7 8 |
<div class="login">
<div class="msg">@loginmsg</div>
<form action="/login" method="post">
<label for="user">local handle</label>
<input type="text" name="user" id="user" autocomplete="username" autofocus required>
<button type="submit">log on</button>
</form>
</div>
|
| |
1 2 3 4 5 6 7 8 |
<div class="login"> <div class="msg">@loginmsg</div> <form action="/login" method="post"> <label for="user">local handle</label> <input type="text" name="user" id="user" autocomplete="username" autofocus required> <button>log on</button> </form> </div> |