implement Secstore; ## interact with the Plan 9 secstore# include "sys.m"; sys: Sys; include "keyring.m"; kr: Keyring; DigestState, IPint: import kr; AESbsize, AESstate: import kr; include "security.m"; ssl: SSL; include "encoding.m"; base64: Encoding; include "secstore.m"; init(){ sys = load Sys Sys->PATH; kr = load Keyring Keyring->PATH; ssl = load SSL SSL->PATH; base64 = load Encoding Encoding->BASE64PATH; initPAKparams();} privacy(): int{ fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); if(fd == nil || sys->fprint(fd, "private") < 0) return 0; return 1;} connect(addr: string, user: string, pwhash: array of byte): (ref Sys->Connection, string, string){ conn := dial(addr); if(conn == nil){ sys->werrstr(sys->sprint("can't dial %s: %r", addr)); return (nil, nil, sys->sprint("%r")); } (sname, diag) := auth(conn, user, pwhash); if(sname == nil){ sys->werrstr(sys->sprint("can't authenticate: %s", diag)); return (nil, nil, sys->sprint("%r")); } return (conn, sname, diag);} dial(netaddr: string): ref Sys->Connection{ if(netaddr == nil) netaddr = "net!$auth!secstore"; (ok, conn) := sys->dial(netaddr, nil); if(ok < 0) return nil; (err, sslconn) := ssl->connect(conn.dfd); if(err != nil) sys->werrstr(err); return sslconn;} auth(conn: ref Sys->Connection, user: string, pwhash: array of byte): (string, string){ sname := PAKclient(conn, user, pwhash); if(sname == nil) return (nil, sys->sprint("%r")); s := readstr(conn.dfd); if(s == "STA") return (sname, "need pin"); if(s != "OK"){ if(s != nil) sys->werrstr(s); return (nil, sys->sprint("%r")); } return (sname, nil);} cansecstore(netaddr: string, user: string): int{ conn := dial(netaddr); if(conn == nil) return 0; if(sys->fprint(conn.dfd, "secstore\tPAK\nC=%s\nm=0\n", user) < 0) return 0; buf := array[128] of byte; n := sys->read(conn.dfd, buf, len buf); if(n <= 0) return 0; return string buf[0:n] == "!account exists";} sendpin(conn: ref Sys->Connection, pin: string): int{ if(sys->fprint(conn.dfd, "STA%s", pin) < 0) return -1; s := readstr(conn.dfd); if(s != "OK"){ if(s != nil) sys->werrstr(s); return -1; } return 0;} files(conn: ref Sys->Connection): list of (string, int, string, string, array of byte){ file := getfile(conn, ".", 0); if(file == nil) return nil; rl: list of (string, int, string, string, array of byte); for(linelist := lines(file); linelist != nil; linelist = tl linelist){ s := string hd linelist; # factotum\t2552 Dec 9 13:04:49 GMT 2005 n9wSk45SPDxgljOIflGQoXjOkjs= for(i := 0; i < len s && s[i] != '\t' && s[i] != ' '; i++){} # can be trailing spaces name := s[0:i]; for(; i < len s && (s[i] == ' ' || s[i] == '\t'); i++){} for(j := i; j < len s && s[j] != ' '; j++){} size := int s[i+1:j]; for(i = j; i < len s && s[i] == ' '; i++){} date := s[i:i+24]; i += 24+1; for(j = i; j < len s && s[j] != '\n'; j++){} sha1 := s[i:j]; rl = (name, int size, date, sha1, base64->dec(sha1)) :: rl; } l: list of (string, int, string, string, array of byte); for(; rl != nil; rl = tl rl) l = hd rl :: l; return l;} getfile(conn: ref Sys->Connection, name: string, maxsize: int): array of byte{ fd := conn.dfd; if(maxsize <= 0) maxsize = Maxfilesize; if(sys->fprint(fd, "GET %s\n", name) < 0 || (s := readstr(fd)) == nil){ sys->werrstr(sys->sprint("can't get %q: %r", name)); return nil; } nb := int s; if(nb == -1){ sys->werrstr(sys->sprint("remote file %q does not exist", name)); return nil; } if(nb < 0 || nb > maxsize){ sys->werrstr(sys->sprint("implausible file size %d for %q", nb, name)); return nil; } file := array[nb] of byte; for(nr := 0; nr < nb;){ n := sys->read(fd, file[nr:], nb-nr); if(n < 0){ sys->werrstr(sys->sprint("error reading %q: %r", name)); return nil; } if(n == 0){ sys->werrstr(sys->sprint("empty file chunk reading %q at offset %d", name, nr)); return nil; } nr += n; } return file;} remove(conn: ref Sys->Connection, name: string): int{ if(sys->fprint(conn.dfd, "RM %s\n", name) < 0) return -1; return 0;} bye(conn: ref Sys->Connection){ if(conn != nil){ if(conn.dfd != nil) sys->fprint(conn.dfd, "BYE"); conn.dfd = nil; conn.cfd = nil; }} mkseckey(s: string): array of byte{ key := array of byte s; skey := array[Keyring->SHA1dlen] of byte; kr->sha1(key, len key, skey, nil); erasekey(key); return skey;} Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc usesChecklen: con len Checkpat; mkfilekey(s: string): array of byte{ key := array of byte s; skey := array[Keyring->SHA1dlen] of byte; sha := kr->sha1(array of byte "aescbc file", 11, nil, nil); kr->sha1(key, len key, skey, sha); erasekey(key); erasekey(skey[AESbsize:]); return skey[0:AESbsize];} decrypt(file: array of byte, key: array of byte): array of byte{ length := len file; if(length == 0) return file; if(length < AESbsize+Checklen) return nil; state := kr->aessetup(key, file[0:AESbsize]); if(state == nil){ sys->werrstr("can't set AES state"); return nil; } kr->aescbc(state, file[AESbsize:], length-AESbsize, Keyring->Decrypt); if(string file[length-Checklen:] != Checkpat){ sys->werrstr("file did not decrypt correctly"); return nil; } return file[AESbsize: length-Checklen];} lines(file: array of byte): list of array of byte{ rl: list of array of byte; for(i := 0; i < len file;){ for(j := i; j < len file; j++) if(file[j] == byte '\n'){ j++; break; } rl = file[i:j] :: rl; i = j; } l: list of array of byte; for(; rl != nil; rl = tl rl) l = (hd rl) :: l; return l;} readstr(fd: ref Sys->FD): string{ buf := array[500] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return nil; s := string buf[0:n]; if(s[0] == '!'){ sys->werrstr(s[1:]); return nil; } return s;} writerr(fd: ref Sys->FD, s: string){ sys->fprint(fd, "!%s", s); sys->werrstr(s);} setsecret(conn: ref Sys->Connection, sigma: array of byte, direction: int): string{ secretin := array[Keyring->SHA1dlen] of byte; secretout := array[Keyring->SHA1dlen] of byte; if(direction != 0){ kr->hmac_sha1(sigma, len sigma, array of byte "one", secretout, nil); kr->hmac_sha1(sigma, len sigma, array of byte "two", secretin, nil); }else{ kr->hmac_sha1(sigma, len sigma, array of byte "two", secretout, nil); kr->hmac_sha1(sigma, len sigma, array of byte "one", secretin, nil); } return ssl->secret(conn, secretin, secretout);} erasekey(a: array of byte){ for(i := 0; i < len a; i++) a[i] = byte 0;} ## the following must only be used to talk to a Plan 9 secstore# VERSION: con "secstore"; PAKparams: adt { q: ref IPint; p: ref IPint; r: ref IPint; g: ref IPint;}; pak: ref PAKparams; # from seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E initPAKparams(){ if(pak != nil) return; lpak := ref PAKparams; lpak.q = IPint.strtoip("E0F0EF284E10796C5A2A511E94748BA03C795C13", 16); lpak.p = IPint.strtoip("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBB"+ "DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"+ "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"+ "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", 16); lpak.r = IPint.strtoip("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241"+ "CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E"+ "887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D"+ "21C4656848614D888A4", 16); lpak.g = IPint.strtoip("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D23271734"+ "44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD"+ "410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734"+ "E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", 16); pak = lpak; # atomic store} # H = (sha(ver,C,sha(passphrase)))^r mod p,# a hash function expensive to attack by brute force. longhash(ver: string, C: string, passwd: array of byte): ref IPint{ aver := array of byte ver; aC := array of byte C; Cp := array[len aver + len aC + len passwd] of byte; Cp[0:] = aver; Cp[len aver:] = aC; Cp[len aver+len aC:] = passwd; buf := array[7*Keyring->SHA1dlen] of byte; for(i := 0; i < 7; i++){ key := array[] of { byte('A'+i) }; kr->hmac_sha1(Cp, len Cp, key, buf[i*Keyring->SHA1dlen:], nil); } erasekey(Cp); return mod(IPint.bebytestoip(buf), pak.p).expmod(pak.r, pak.p); # H} mod(a, b: ref IPint): ref IPint{ return a.div(b).t1;} shaz(s: string, digest: array of byte, state: ref DigestState): ref DigestState{ a := array of byte s; state = kr->sha1(a, len a, digest, state); erasekey(a); return state;} # Hi = H^-1 mod pPAK_Hi(C: string, passhash: array of byte): (string, ref IPint, ref IPint){ H := longhash(VERSION, C, passhash); Hi := H.invert(pak.p); return (Hi.iptostr(64), H, Hi);} # another, faster, hash function for each party to# confirm that the other has the right secrets. shorthash(mess: string, C: string, S: string, m: string, mu: string, sigma: string, Hi: string): array of byte{ state := shaz(mess, nil, nil); state = shaz(C, nil, state); state = shaz(S, nil, state); state = shaz(m, nil, state); state = shaz(mu, nil, state); state = shaz(sigma, nil, state); state = shaz(Hi, nil, state); state = shaz(mess, nil, state); state = shaz(C, nil, state); state = shaz(S, nil, state); state = shaz(m, nil, state); state = shaz(mu, nil, state); state = shaz(sigma, nil, state); digest := array[Keyring->SHA1dlen] of byte; shaz(Hi, digest, state); return digest;} ## On input, conn provides an open channel to the server;# C is the name this client calls itself;# pass is the user's passphrase# On output, session secret has been set in conn# (unless return code is negative, which means failure).#PAKclient(conn: ref Sys->Connection, C: string, pwhash: array of byte): string{ dfd := conn.dfd; (hexHi, H, nil) := PAK_Hi(C, pwhash); # random 1<=x<=q-1; send C, m=g**x H x := mod(IPint.random(240, 240), pak.q); if(x.eq(IPint.inttoip(0))) x = IPint.inttoip(1); m := mod(pak.g.expmod(x, pak.p).mul(H), pak.p); hexm := m.iptostr(64); if(sys->fprint(dfd, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm) < 0) return nil; # recv g**y, S, check hash1(g**xy) s := readstr(dfd); if(s == nil){ e := sys->sprint("%r"); writerr(dfd, "couldn't read g**y"); sys->werrstr(e); return nil; } # should be: "mu=%s\nk=%s\nS=%s\n" (nf, flds) := sys->tokenize(s, "\n"); if(nf != 3){ writerr(dfd, "verifier syntax error"); return nil; } hexmu := ex("mu=", hd flds); flds = tl flds; ks := ex("k=", hd flds); flds = tl flds; S := ex("S=", hd flds); if(hexmu == nil || ks == nil || S == nil){ writerr(dfd, "verifier syntax error"); return nil; } mu := IPint.strtoip(hexmu, 64); sigma := mu.expmod(x, pak.p); hexsigma := sigma.iptostr(64); digest := shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi); kc := base64->enc(digest); if(ks != kc){ writerr(dfd, "verifier didn't match"); return nil; } # send hash2(g**xy) digest = shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi); kc = base64->enc(digest); if(sys->fprint(dfd, "k'=%s\n", kc) < 0) return nil; # set session key digest = shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi); for(i := 0; i < len hexsigma; i++) hexsigma[i] = 0; err := setsecret(conn, digest, 0); if(err != nil) return nil; erasekey(digest); if(sys->fprint(conn.cfd, "alg sha1 rc4_128") < 0) return nil; return S;} ex(tag: string, s: string): string{ if(len s < len tag || s[0:len tag] != tag) return nil; return s[len tag:];}
implement Secstore;
#
# interact with the Plan 9 secstore
#
include "sys.m";
sys: Sys;
include "keyring.m";
kr: Keyring;
DigestState, IPint: import kr;
AESbsize, AESstate: import kr;
include "security.m";
ssl: SSL;
include "encoding.m";
base64: Encoding;
include "secstore.m";
init()
{
sys = load Sys Sys->PATH;
kr = load Keyring Keyring->PATH;
ssl = load SSL SSL->PATH;
base64 = load Encoding Encoding->BASE64PATH;
initPAKparams();
}
privacy(): int
{
fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
if(fd == nil || sys->fprint(fd, "private") < 0)
return 0;
return 1;
}
connect(addr: string, user: string, pwhash: array of byte): (ref Sys->Connection, string, string)
{
conn := dial(addr);
if(conn == nil){
sys->werrstr(sys->sprint("can't dial %s: %r", addr));
return (nil, nil, sys->sprint("%r"));
}
(sname, diag) := auth(conn, user, pwhash);
if(sname == nil){
sys->werrstr(sys->sprint("can't authenticate: %s", diag));
return (nil, nil, sys->sprint("%r"));
}
return (conn, sname, diag);
}
dial(netaddr: string): ref Sys->Connection
{
if(netaddr == nil)
netaddr = "net!$auth!secstore";
(ok, conn) := sys->dial(netaddr, nil);
if(ok < 0)
return nil;
(err, sslconn) := ssl->connect(conn.dfd);
if(err != nil)
sys->werrstr(err);
return sslconn;
}
auth(conn: ref Sys->Connection, user: string, pwhash: array of byte): (string, string)
{
sname := PAKclient(conn, user, pwhash);
if(sname == nil)
return (nil, sys->sprint("%r"));
s := readstr(conn.dfd);
if(s == "STA")
return (sname, "need pin");
if(s != "OK"){
if(s != nil)
sys->werrstr(s);
return (nil, sys->sprint("%r"));
}
return (sname, nil);
}
cansecstore(netaddr: string, user: string): int
{
conn := dial(netaddr);
if(conn == nil)
return 0;
if(sys->fprint(conn.dfd, "secstore\tPAK\nC=%s\nm=0\n", user) < 0)
return 0;
buf := array[128] of byte;
n := sys->read(conn.dfd, buf, len buf);
if(n <= 0)
return 0;
return string buf[0:n] == "!account exists";
}
sendpin(conn: ref Sys->Connection, pin: string): int
{
if(sys->fprint(conn.dfd, "STA%s", pin) < 0)
return -1;
s := readstr(conn.dfd);
if(s != "OK"){
if(s != nil)
sys->werrstr(s);
return -1;
}
return 0;
}
files(conn: ref Sys->Connection): list of (string, int, string, string, array of byte)
{
file := getfile(conn, ".", 0);
if(file == nil)
return nil;
rl: list of (string, int, string, string, array of byte);
for(linelist := lines(file); linelist != nil; linelist = tl linelist){
s := string hd linelist;
# factotum\t2552 Dec 9 13:04:49 GMT 2005 n9wSk45SPDxgljOIflGQoXjOkjs=
for(i := 0; i < len s && s[i] != '\t' && s[i] != ' '; i++){} # can be trailing spaces
name := s[0:i];
for(; i < len s && (s[i] == ' ' || s[i] == '\t'); i++){}
for(j := i; j < len s && s[j] != ' '; j++){}
size := int s[i+1:j];
for(i = j; i < len s && s[i] == ' '; i++){}
date := s[i:i+24];
i += 24+1;
for(j = i; j < len s && s[j] != '\n'; j++){}
sha1 := s[i:j];
rl = (name, int size, date, sha1, base64->dec(sha1)) :: rl;
}
l: list of (string, int, string, string, array of byte);
for(; rl != nil; rl = tl rl)
l = hd rl :: l;
return l;
}
getfile(conn: ref Sys->Connection, name: string, maxsize: int): array of byte
{
fd := conn.dfd;
if(maxsize <= 0)
maxsize = Maxfilesize;
if(sys->fprint(fd, "GET %s\n", name) < 0 ||
(s := readstr(fd)) == nil){
sys->werrstr(sys->sprint("can't get %q: %r", name));
return nil;
}
nb := int s;
if(nb == -1){
sys->werrstr(sys->sprint("remote file %q does not exist", name));
return nil;
}
if(nb < 0 || nb > maxsize){
sys->werrstr(sys->sprint("implausible file size %d for %q", nb, name));
return nil;
}
file := array[nb] of byte;
for(nr := 0; nr < nb;){
n := sys->read(fd, file[nr:], nb-nr);
if(n < 0){
sys->werrstr(sys->sprint("error reading %q: %r", name));
return nil;
}
if(n == 0){
sys->werrstr(sys->sprint("empty file chunk reading %q at offset %d", name, nr));
return nil;
}
nr += n;
}
return file;
}
remove(conn: ref Sys->Connection, name: string): int
{
if(sys->fprint(conn.dfd, "RM %s\n", name) < 0)
return -1;
return 0;
}
bye(conn: ref Sys->Connection)
{
if(conn != nil){
if(conn.dfd != nil)
sys->fprint(conn.dfd, "BYE");
conn.dfd = nil;
conn.cfd = nil;
}
}
mkseckey(s: string): array of byte
{
key := array of byte s;
skey := array[Keyring->SHA1dlen] of byte;
kr->sha1(key, len key, skey, nil);
erasekey(key);
return skey;
}
Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc uses
Checklen: con len Checkpat;
mkfilekey(s: string): array of byte
{
key := array of byte s;
skey := array[Keyring->SHA1dlen] of byte;
sha := kr->sha1(array of byte "aescbc file", 11, nil, nil);
kr->sha1(key, len key, skey, sha);
erasekey(key);
erasekey(skey[AESbsize:]);
return skey[0:AESbsize];
}
decrypt(file: array of byte, key: array of byte): array of byte
{
length := len file;
if(length == 0)
return file;
if(length < AESbsize+Checklen)
return nil;
state := kr->aessetup(key, file[0:AESbsize]);
if(state == nil){
sys->werrstr("can't set AES state");
return nil;
}
kr->aescbc(state, file[AESbsize:], length-AESbsize, Keyring->Decrypt);
if(string file[length-Checklen:] != Checkpat){
sys->werrstr("file did not decrypt correctly");
return nil;
}
return file[AESbsize: length-Checklen];
}
lines(file: array of byte): list of array of byte
{
rl: list of array of byte;
for(i := 0; i < len file;){
for(j := i; j < len file; j++)
if(file[j] == byte '\n'){
j++;
break;
}
rl = file[i:j] :: rl;
i = j;
}
l: list of array of byte;
for(; rl != nil; rl = tl rl)
l = (hd rl) :: l;
return l;
}
readstr(fd: ref Sys->FD): string
{
buf := array[500] of byte;
n := sys->read(fd, buf, len buf);
if(n < 0)
return nil;
s := string buf[0:n];
if(s[0] == '!'){
sys->werrstr(s[1:]);
return nil;
}
return s;
}
writerr(fd: ref Sys->FD, s: string)
{
sys->fprint(fd, "!%s", s);
sys->werrstr(s);
}
setsecret(conn: ref Sys->Connection, sigma: array of byte, direction: int): string
{
secretin := array[Keyring->SHA1dlen] of byte;
secretout := array[Keyring->SHA1dlen] of byte;
if(direction != 0){
kr->hmac_sha1(sigma, len sigma, array of byte "one", secretout, nil);
kr->hmac_sha1(sigma, len sigma, array of byte "two", secretin, nil);
}else{
kr->hmac_sha1(sigma, len sigma, array of byte "two", secretout, nil);
kr->hmac_sha1(sigma, len sigma, array of byte "one", secretin, nil);
}
return ssl->secret(conn, secretin, secretout);
}
erasekey(a: array of byte)
{
for(i := 0; i < len a; i++)
a[i] = byte 0;
}
#
# the following must only be used to talk to a Plan 9 secstore
#
VERSION: con "secstore";
PAKparams: adt {
q: ref IPint;
p: ref IPint;
r: ref IPint;
g: ref IPint;
};
pak: ref PAKparams;
# from seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E
initPAKparams()
{
if(pak != nil)
return;
lpak := ref PAKparams;
lpak.q = IPint.strtoip("E0F0EF284E10796C5A2A511E94748BA03C795C13", 16);
lpak.p = IPint.strtoip("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBB"+
"DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"+
"3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"+
"3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", 16);
lpak.r = IPint.strtoip("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241"+
"CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E"+
"887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D"+
"21C4656848614D888A4", 16);
lpak.g = IPint.strtoip("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D23271734"+
"44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD"+
"410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734"+
"E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", 16);
pak = lpak; # atomic store
}
# H = (sha(ver,C,sha(passphrase)))^r mod p,
# a hash function expensive to attack by brute force.
longhash(ver: string, C: string, passwd: array of byte): ref IPint
{
aver := array of byte ver;
aC := array of byte C;
Cp := array[len aver + len aC + len passwd] of byte;
Cp[0:] = aver;
Cp[len aver:] = aC;
Cp[len aver+len aC:] = passwd;
buf := array[7*Keyring->SHA1dlen] of byte;
for(i := 0; i < 7; i++){
key := array[] of { byte('A'+i) };
kr->hmac_sha1(Cp, len Cp, key, buf[i*Keyring->SHA1dlen:], nil);
}
erasekey(Cp);
return mod(IPint.bebytestoip(buf), pak.p).expmod(pak.r, pak.p); # H
}
mod(a, b: ref IPint): ref IPint
{
return a.div(b).t1;
}
shaz(s: string, digest: array of byte, state: ref DigestState): ref DigestState
{
a := array of byte s;
state = kr->sha1(a, len a, digest, state);
erasekey(a);
return state;
}
# Hi = H^-1 mod p
PAK_Hi(C: string, passhash: array of byte): (string, ref IPint, ref IPint)
{
H := longhash(VERSION, C, passhash);
Hi := H.invert(pak.p);
return (Hi.iptostr(64), H, Hi);
}
# another, faster, hash function for each party to
# confirm that the other has the right secrets.
shorthash(mess: string, C: string, S: string, m: string, mu: string, sigma: string, Hi: string): array of byte
{
state := shaz(mess, nil, nil);
state = shaz(C, nil, state);
state = shaz(S, nil, state);
state = shaz(m, nil, state);
state = shaz(mu, nil, state);
state = shaz(sigma, nil, state);
state = shaz(Hi, nil, state);
state = shaz(mess, nil, state);
state = shaz(C, nil, state);
state = shaz(S, nil, state);
state = shaz(m, nil, state);
state = shaz(mu, nil, state);
state = shaz(sigma, nil, state);
digest := array[Keyring->SHA1dlen] of byte;
shaz(Hi, digest, state);
return digest;
}
#
# On input, conn provides an open channel to the server;
# C is the name this client calls itself;
# pass is the user's passphrase
# On output, session secret has been set in conn
# (unless return code is negative, which means failure).
#
PAKclient(conn: ref Sys->Connection, C: string, pwhash: array of byte): string
{
dfd := conn.dfd;
(hexHi, H, nil) := PAK_Hi(C, pwhash);
# random 1<=x<=q-1; send C, m=g**x H
x := mod(IPint.random(240, 240), pak.q);
if(x.eq(IPint.inttoip(0)))
x = IPint.inttoip(1);
m := mod(pak.g.expmod(x, pak.p).mul(H), pak.p);
hexm := m.iptostr(64);
if(sys->fprint(dfd, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm) < 0)
return nil;
# recv g**y, S, check hash1(g**xy)
s := readstr(dfd);
if(s == nil){
e := sys->sprint("%r");
writerr(dfd, "couldn't read g**y");
sys->werrstr(e);
return nil;
}
# should be: "mu=%s\nk=%s\nS=%s\n"
(nf, flds) := sys->tokenize(s, "\n");
if(nf != 3){
writerr(dfd, "verifier syntax error");
return nil;
}
hexmu := ex("mu=", hd flds); flds = tl flds;
ks := ex("k=", hd flds); flds = tl flds;
S := ex("S=", hd flds);
if(hexmu == nil || ks == nil || S == nil){
writerr(dfd, "verifier syntax error");
return nil;
}
mu := IPint.strtoip(hexmu, 64);
sigma := mu.expmod(x, pak.p);
hexsigma := sigma.iptostr(64);
digest := shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi);
kc := base64->enc(digest);
if(ks != kc){
writerr(dfd, "verifier didn't match");
return nil;
}
# send hash2(g**xy)
digest = shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi);
kc = base64->enc(digest);
if(sys->fprint(dfd, "k'=%s\n", kc) < 0)
return nil;
# set session key
digest = shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi);
for(i := 0; i < len hexsigma; i++)
hexsigma[i] = 0;
err := setsecret(conn, digest, 0);
if(err != nil)
return nil;
erasekey(digest);
if(sys->fprint(conn.cfd, "alg sha1 rc4_128") < 0)
return nil;
return S;
}
ex(tag: string, s: string): string
{
if(len s < len tag || s[0:len tag] != tag)
return nil;
return s[len tag:];
}