summaryrefslogtreecommitdiff
path: root/appl/lib/spki
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/spki
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/lib/spki')
-rw-r--r--appl/lib/spki/mkfile21
-rw-r--r--appl/lib/spki/spki.b2109
-rw-r--r--appl/lib/spki/verifier.b188
3 files changed, 2318 insertions, 0 deletions
diff --git a/appl/lib/spki/mkfile b/appl/lib/spki/mkfile
new file mode 100644
index 00000000..9d096625
--- /dev/null
+++ b/appl/lib/spki/mkfile
@@ -0,0 +1,21 @@
+<../../../mkconfig
+
+TARG=\
+ spki.dis\
+ verifier.dis\
+
+MODULES=
+
+SYSMODULES= \
+ sys.m\
+ daytime.m\
+ keyring.m\
+ security.m\
+ bufio.m\
+ sexprs.m\
+ spki.m\
+ encoding.m\
+
+DISBIN=$ROOT/dis/lib/spki
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/lib/spki/spki.b b/appl/lib/spki/spki.b
new file mode 100644
index 00000000..302eb1aa
--- /dev/null
+++ b/appl/lib/spki/spki.b
@@ -0,0 +1,2109 @@
+implement SPKI;
+
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "keyring.m";
+ kr: Keyring;
+ IPint, Certificate, PK, SK: import kr;
+
+include "security.m";
+
+include "bufio.m";
+
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+
+include "spki.m";
+
+include "encoding.m";
+ base16: Encoding;
+ base64: Encoding;
+
+debug: con 0;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ daytime = load Daytime Daytime->PATH;
+ sexprs = load Sexprs Sexprs->PATH;
+ base16 = load Encoding Encoding->BASE16PATH;
+ base64 = load Encoding Encoding->BASE64PATH;
+
+ sexprs->init();
+}
+
+#
+# parse SPKI structures
+#
+
+parse(e: ref Sexp): (ref Toplev, string)
+{
+ if(e == nil)
+ return (nil, "nil expression");
+ if(!e.islist())
+ return (nil, "list expected");
+ case e.op() {
+ "cert" =>
+ if((c := parsecert(e)) != nil)
+ return (ref Toplev.C(c), nil);
+ return (nil, "bad certificate syntax");
+ "signature" =>
+ if((s := parsesig(e)) != nil)
+ return (ref Toplev.Sig(s), nil);
+ return (nil, "bad signature syntax");
+ "public-key" =>
+ if((k := parsekey(e)) != nil)
+ return (ref Toplev.K(k), nil);
+ return (nil, "bad public-key syntax");
+ "sequence" =>
+ if((els := parseseq(e)) != nil)
+ return (ref Toplev.Seq(els), nil);
+ return (nil, "bad sequence syntax");
+ * =>
+ return (nil, sys->sprint("unknown operation: %#q", e.op()));
+ }
+}
+
+parseseq(e: ref Sexp): list of ref Seqel
+{
+ l := mustbe(e, "sequence");
+ if(l == nil)
+ return nil;
+ rl: list of ref Seqel;
+ for(; l != nil; l = tl l){
+ se := hd l;
+ case se.op() {
+ "cert" =>
+ cert := parsecert(se);
+ if(cert == nil)
+ return nil;
+ rl = ref Seqel.C(cert) :: rl;
+ "do" =>
+ el := se.args();
+ if(el == nil)
+ return nil;
+ op := (hd el).astext();
+ if(op == nil)
+ return nil;
+ rl = ref Seqel.O(op, tl el) :: rl;
+ "public-key" =>
+ k := parsekey(se);
+ if(k == nil)
+ return nil;
+ rl = ref Seqel.K(k) :: rl;
+ "signature" =>
+ sig := parsesig(se);
+ if(sig == nil)
+ return nil;
+ rl = ref Seqel.S(sig) :: rl;
+ * =>
+ rl = ref Seqel.E(se) :: rl;
+ }
+ }
+ return rev(rl);
+}
+
+parsecert(e: ref Sexp): ref Cert
+{
+ # "(" "cert" <version>? <cert-display>? <issuer> <issuer-loc>? <subject> <subject-loc>?
+ # <deleg>? <tag> <valid>? <comment>? ")"
+ # elements can appear in any order in a top-level item, though the one above is conventional
+
+ l := mustbe(e, "cert");
+ if(l == nil)
+ return nil;
+ delegate := 0;
+ issuer: ref Name;
+ subj: ref Subject;
+ tag: ref Sexp;
+ valid: ref Valid;
+ for(; l != nil; l = tl l){
+ t := (hd l).op();
+ case t {
+ "version" or "display" or "issuer-info" or "subject-info" or "comment" =>
+ ; # skip
+ "issuer" =>
+ # <principal> | <name> [via issuer-name]
+ if(issuer != nil)
+ return nil;
+ ie := onlyarg(hd l);
+ if(ie == nil)
+ return nil;
+ issuer = parsecompound(ie);
+ if(issuer == nil)
+ return nil;
+ "subject" =>
+ # <subject>:: "(" "subject" <subj-obj> ")" ;
+ if(subj != nil)
+ return nil;
+ se := onlyarg(hd l);
+ if(se == nil)
+ return nil;
+ subj = parsesubjobj(se);
+ if(subj == nil)
+ return nil;
+ "propagate" =>
+ if(delegate)
+ return nil;
+ delegate = 1;
+ "tag" =>
+ if(tag != nil)
+ return nil;
+ tag = maketag(hd l); # can safely leave (tag ...) operation in place
+ "valid" =>
+ if(valid != nil)
+ return nil;
+ valid = parsevalid(hd l);
+ if(valid == nil)
+ return nil;
+ * =>
+ sys->print("cert component: %q unknown/ignored\n", t);
+ }
+ }
+ if(issuer == nil || subj == nil)
+ return nil;
+ pick s := subj {
+ KH =>
+ return ref Cert.KH(e, issuer, subj, valid, delegate, tag);
+ O =>
+ return ref Cert.O(e, issuer, subj, valid, delegate, tag);
+ * =>
+ if(issuer.isprincipal())
+ return ref Cert.A(e, issuer, subj, valid, delegate, tag);
+ return ref Cert.N(e, issuer, subj, valid);
+ }
+}
+
+parsesubjobj(e: ref Sexp): ref Subject
+{
+ # <subj-obj>:: <principal> | <name> | <obj-hash> | <keyholder> | <subj-thresh> ;
+ case e.op() {
+ "name" or "hash" or "public-key" =>
+ name := parsecompound(e);
+ if(name == nil)
+ return nil;
+ if(name.names == nil)
+ return ref Subject.P(name.principal);
+ return ref Subject.N(name);
+
+ "object-hash" =>
+ e = onlyarg(e);
+ if(e == nil)
+ return nil;
+ hash := parsehash(e);
+ if(hash == nil)
+ return nil;
+ return ref Subject.O(hash);
+
+ "keyholder" =>
+ e = onlyarg(e);
+ if(e == nil)
+ return nil;
+ name := parsecompound(e);
+ if(name == nil)
+ return nil;
+ return ref Subject.KH(name);
+
+ "k-of-n" =>
+ el := e.args();
+ m := len el;
+ if(m < 2)
+ return nil;
+ k := intof(hd el);
+ n := intof(hd tl el);
+ if(k < 0 || n < 0 || k > n || n != m-2)
+ return nil;
+ el = tl tl el;
+ sl: list of ref Subject;
+ for(; el != nil; el = tl el){
+ o := parsesubjobj(hd el);
+ if(o == nil)
+ return nil;
+ sl = o :: sl;
+ }
+ return ref Subject.T(k, n, rev(sl));
+
+ * =>
+ return nil;
+ }
+}
+
+parsesig(e: ref Sexp): ref Signature
+{
+ # <signature>:: "(" "signature" <hash> <principal> <sig-val> ")"
+ # <sig-val>:: "(" <pub-sig-alg-id> <sig-params> ")"
+ # <pub-sig-alg-id>:: "rsa-pkcs1-md5" | "rsa-pkcs1-sha1" | "rsa-pkcs1" | "dsa-sha1" | <uri>
+ # <sig-params>:: <byte-string> | <s-expr>+
+
+ l := mustbe(e, "signature");
+ if(len l < 3)
+ return nil;
+ # signature hash key sig
+ hash := parsehash(hd l);
+ k := parseprincipal(hd tl l);
+ if(hash == nil || k == nil)
+ return nil;
+ val := hd tl tl l;
+ if(!val.islist()){ # not in grammar but examples paper uses it
+ sigalg: string;
+ if(k != nil)
+ sigalg = k.sigalg();
+ return ref Signature(hash, k, sigalg, (nil, val.asdata()) :: nil);
+ }
+ sigalg := val.op();
+ if(sigalg == nil)
+ return nil;
+ rl: list of (string, array of byte);
+ for(els := val.args(); els != nil; els = tl els){
+ g := hd els;
+ if(g.islist()){
+ arg := onlyarg(g);
+ if(arg == nil)
+ return nil;
+ rl = (g.op(), arg.asdata()) :: rl;
+ }else
+ rl = (nil, g.asdata()) :: rl;
+ }
+ return ref Signature(hash, k, sigalg, revt(rl));
+}
+
+parsecompound(e: ref Sexp): ref Name
+{
+ if(e == nil)
+ return nil;
+ case t := e.op() {
+ "name" =>
+ return parsename(e);
+ "public-key" or "hash" =>
+ k := parseprincipal(e);
+ if(k == nil)
+ return nil;
+ return ref Name(k, nil);
+ * =>
+ return nil;
+ }
+}
+
+parsename(e: ref Sexp): ref Name
+{
+ l := mustbe(e, "name");
+ if(l == nil)
+ return nil;
+ k: ref Key;
+ if((hd l).islist()){ # must be principal: pub key or hash of key
+ k = parseprincipal(hd l);
+ if(k == nil)
+ return nil;
+ l = tl l;
+ }
+ names: list of string;
+ for(; l != nil; l = tl l){
+ s := (hd l).astext();
+ if(s == nil)
+ return nil;
+ names = s :: names;
+ }
+ return ref Name(k, rev(names));
+}
+
+parseprincipal(e: ref Sexp): ref Key
+{
+ case e.op() {
+ "public-key" =>
+ return parsekey(e);
+ "hash" =>
+ hash := parsehash(e);
+ if(hash == nil)
+ return nil;
+ return ref Key(nil, nil, 0, hash.alg, hash);
+ * =>
+ return nil;
+ }
+}
+
+parsekey(e: ref Sexp): ref Key
+{
+ l := mustbe(e, "public-key");
+ if(l == nil)
+ return nil;
+ kind := (hd l).op();
+ (nf, fld) := sys->tokenize(kind, "-");
+ if(nf < 1)
+ return nil;
+ alg := hd fld;
+ if(nf > 1)
+ enc := hd tl fld; # signature hash encoding
+ if(nf > 2)
+ mha := hd tl tl fld; # signature hash algorithm
+ kha := "md5"; # could be sha1
+ kl := (hd l).args();
+ if(kl == nil)
+ return nil;
+ els: list of (string, ref IPint);
+ for(; kl != nil; kl = tl kl){
+ t := (hd kl).op();
+ a := onlyarg(hd kl).asdata();
+ if(a == nil)
+ return nil;
+ ip := IPint.bebytestoip(a);
+ if(ip == nil)
+ return nil;
+ els = (t, ip) :: els;
+ }
+ krp := ref Keyrep.PK(alg, "sdsi", els);
+ (pk, nbits) := krp.mkpk();
+ if(pk == nil){
+ sys->print("can't convert key\n");
+ return nil;
+ }
+#(ref Key(pk,nil,kha,nil)).hashed(kha); # TEST
+ return ref Key(pk, nil, nbits, kha, ref Hash(kha, nil));
+}
+
+parsehash(e: ref Sexp): ref Hash
+{
+ # "(" "hash" <hash-alg-name> <hash-value> <uris>? ")"
+ l := mustbe(e, "hash");
+ if(len l < 2)
+ return nil;
+ return ref Hash((hd l).astext(), (hd tl l).asdata());
+}
+
+parsevalid(e: ref Sexp): ref Valid
+{
+ l := mustbe(e, "valid");
+ if(l == nil)
+ return nil;
+ el: list of ref Sexp;
+ notbefore, notafter: string;
+ (el, l) = isita(l, "not-before");
+ if(el != nil && (notafter = ckdate((hd el).astext())) == nil)
+ return nil;
+ (el, l) = isita(l, "not-after");
+ if(el != nil && (notafter = ckdate((hd el).astext())) == nil)
+ return nil;
+ for(;;){
+ (el, l) = isita(l, "online");
+ if(el == nil)
+ break;
+ }
+ if(el != nil)
+ return nil;
+ return ref Valid(notbefore, notafter);
+}
+
+isnumeric(s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(!(s[i]>='0' && s[i]<='9'))
+ return 0;
+ return s != nil;
+}
+
+ckdate(s: string): string
+{
+ if(date2epoch(s) < 0) # TO DO: prefix/suffix tests
+ return nil;
+ return s;
+}
+
+Seqel.text(se: self ref Seqel): string
+{
+ pick r := se {
+ C =>
+ return r.c.text();
+ K =>
+ return r.k.text();
+ O =>
+ e := ref Sexp.List(ref Sexp.String("do",nil) :: ref Sexp.String(r.op,nil) :: r.args);
+ return e.text();
+ S =>
+ return r.sig.text();
+ E =>
+ return r.exp.text();
+ * =>
+ return "unsupported";
+ }
+}
+
+isita(l: list of ref Sexp, s: string): (list of ref Sexp, list of ref Sexp)
+{
+ if(l == nil)
+ return (nil, nil);
+ e := hd l;
+ if(e.islist() && e.op() == s)
+ return (e.args(), tl l);
+ return (nil, l);
+}
+
+intof(e: ref Sexp): int
+{
+ # int should be plenty; don't need big
+ pick s := e {
+ List =>
+ return -1;
+ Binary =>
+ if(len s.data > 4)
+ return -1;
+ v := 0;
+ for(i := 0; i < len s.data; i++)
+ v = (v<<8) | int s.data[i];
+ return v;
+ String =>
+ if(s.s == nil || !(s.s[0]>='0' && s.s[0]<='9'))
+ return -1;
+ return int s.s;
+ }
+}
+
+onlyarg(e: ref Sexp): ref Sexp
+{
+ l := e.args();
+ if(l == nil || tl l != nil)
+ return nil;
+ return hd l;
+}
+
+mustbe(e: ref Sexp, kind: string): list of ref Sexp
+{
+ if(e != nil && e.islist() && e.op() == kind)
+ return e.args();
+ return nil;
+}
+
+checksig(c: ref Cert, sig: ref Signature): string
+{
+ if(c.e == nil)
+ return "missing S-expression for certificate";
+ if(sig.key == nil)
+ return "missing key for signature";
+ if(sig.hash == nil)
+ return "missing hash for signature";
+ if(sig.sig == nil)
+ return "missing signature value";
+ pk := sig.key.pk;
+ if(pk == nil)
+ return "missing Keyring->PK for signature"; # TO DO
+#rsacomp((hd sig.sig).t1, sig.key);
+#sys->print("nbits= %d\n", sig.key.nbits);
+ (alg, enc, hashalg) := sig.algs();
+ if(alg == nil)
+ return "unspecified signature algorithm";
+ if(hashalg == nil)
+ hashalg = "md5"; # TO DO
+ if(enc == nil)
+ h := hashbytes(c.e.pack(), hashalg);
+ else if(enc == "pkcs" || enc == "pkcs1")
+ h = pkcs1_encode(hashalg, c.e.pack(), (sig.key.nbits+7)/8);
+ else
+ return "unknown encoding algorithm "+enc;
+ if(h == nil)
+ return "unknown hash algorithm "+hashalg;
+ ip := IPint.bebytestoip(h);
+ isig := sig2icert(sig, "sdsi", 0);
+ if(isig == nil)
+ return "couldn't convert SPKI signature to Keyring form";
+ if(!kr->verifym(pk, isig, ip))
+ return "signature does not match";
+ return nil;
+}
+
+hashexp(e: ref Sexp, alg: string): array of byte
+{
+ a := e.pack();
+#dump("inp a", a);
+ hash := hashbytes(a, alg);
+#dump(alg, hash);
+#sys->print("%s = |%s|\n", alg, base64->enc(hash));
+ return hash;
+}
+
+hashbytes(a: array of byte, alg: string): array of byte
+{
+ hash: array of byte;
+ case alg {
+ "md5" =>
+ hash = array[Keyring->MD5dlen] of byte;
+ kr->md5(a, len a, hash, nil);
+ "sha" or "sha1" =>
+ hash = array[Keyring->SHA1dlen] of byte;
+ kr->sha1(a, len a, hash, nil);
+ * =>
+ return nil;
+ }
+ return hash;
+}
+
+pre0(a: array of byte): array of byte
+{
+ for(i:=0; i<len a-1; i++)
+ if(a[i] != a[i+1] && (a[i] != byte 0 || (int a[i+1] & 16r80) != 0))
+ break;
+ if(i > 0)
+ a = a[i:];
+ if(len a < 1 || (int a[0] & 16r80) == 0)
+ return a;
+ b := array[len a + 1] of byte;
+ b[0] = byte 0;
+ b[1:] = a;
+ return b;
+}
+
+dump(s: string, a: array of byte)
+{
+ s = sys->sprint("%s [%d]: ", s, len a);
+ for(i := 0; i < len a; i++)
+ s += sys->sprint(" %.2ux", int a[i]);
+ sys->print("%s\n", s);
+}
+
+Signature.algs(sg: self ref Signature): (string, string, string)
+{
+ (nf, flds) := sys->tokenize(sg.sa, "-");
+ if(nf >= 3)
+ return (hd flds, hd tl flds, hd tl tl flds);
+ if(nf >= 2)
+ return (hd flds, nil, hd tl flds);
+ if(nf >= 1)
+ return (hd flds, nil, nil);
+ return (nil, nil, nil);
+}
+
+Signature.sexp(sg: self ref Signature): ref Sexp
+{
+ sv: ref Sexp;
+ if(len sg.sig != 1){
+ l: list of ref Sexp;
+ for(els := sg.sig; els != nil; els = tl els){
+ (op, val) := hd els;
+ if(op != nil)
+ l = ref Sexp.List(ref Sexp.String(op,nil) :: ref Sexp.Binary(val,nil) :: nil) :: l;
+ else
+ l = ref Sexp.Binary(val,nil) :: l;
+ }
+ sv = ref Sexp.List(rev(l));
+ }else
+ sv = ref Sexp.Binary((hd sg.sig).t1, nil);
+ if(sg.sa != nil)
+ sv = ref Sexp.List(ref Sexp.String(sg.sa,nil) :: sv :: nil);
+ return ref Sexp.List(ref Sexp.String("signature",nil) :: sg.hash.sexp() :: sg.key.sexp() ::
+ sv :: nil);
+}
+
+Signature.text(sg: self ref Signature): string
+{
+ if(sg == nil)
+ return nil;
+ return sg.sexp().text();
+}
+
+Hash.sexp(h: self ref Hash): ref Sexp
+{
+ return ref Sexp.List(ref Sexp.String("hash",nil) ::
+ ref Sexp.String(h.alg, nil) :: ref Sexp.Binary(h.hash,nil) :: nil);
+}
+
+Hash.text(h: self ref Hash): string
+{
+ return h.sexp().text();
+}
+
+Hash.eq(h1: self ref Hash, h2: ref Hash): int
+{
+ if(h1 == h2)
+ return 1;
+ if(h1 == nil || h2 == nil || h1.alg != h2.alg)
+ return 0;
+ return cmpbytes(h1.hash, h2.hash) == 0;
+}
+
+Valid.intersect(a: self Valid, b: Valid): (int, Valid)
+{
+ c: Valid;
+ if(a.notbefore < b.notbefore)
+ c.notbefore = b.notbefore;
+ else
+ c.notbefore = a.notbefore;
+ if(a.notafter == nil)
+ c.notafter = b.notafter;
+ else if(b.notafter == nil || a.notafter < b.notafter)
+ c.notafter = a.notafter;
+ else
+ c.notafter = b.notafter;
+ if(c.notbefore > c.notafter)
+ return (0, (nil, nil));
+ return (1, c);
+}
+
+Valid.text(a: self Valid): string
+{
+ na, nb: string;
+ if(a.notbefore != nil)
+ nb = " (not-before \""+a.notbefore+"\")";
+ if(a.notafter != nil)
+ na = " (not-after \""+a.notafter+"\")";
+ return sys->sprint("(valid%s%s)", nb, na);
+}
+
+Valid.sexp(a: self Valid): ref Sexp
+{
+ nb, na: ref Sexp;
+ if(a.notbefore != nil)
+ nb = ref Sexp.List(ref Sexp.String("not-before",nil) :: ref Sexp.String(a.notbefore,nil) :: nil);
+ if(a.notafter != nil)
+ na = ref Sexp.List(ref Sexp.String("not-after",nil) :: ref Sexp.String(a.notafter,nil) :: nil);
+ if(nb == nil && na == nil)
+ return nil;
+ return ref Sexp.List(ref Sexp.String("valid",nil) :: nb :: na :: nil);
+}
+
+Cert.text(c: self ref Cert): string
+{
+ if(c == nil)
+ return "nil";
+ v: string;
+ pick d := c {
+ A or KH or O =>
+ if(d.tag != nil)
+ v += " "+d.tag.text();
+ }
+ if(c.valid != nil)
+ v += " "+(*c.valid).text();
+ return sys->sprint("(cert (issuer %s) (subject %s)%s)", c.issuer.text(), c.subject.text(), v);
+}
+
+Cert.sexp(c: self ref Cert): ref Sexp
+{
+ if(c == nil)
+ return nil;
+ ds, tag: ref Sexp;
+ pick d := c {
+ N =>
+ A or KH or O =>
+ if(d.delegate)
+ ds = ref Sexp.List(ref Sexp.String("propagate",nil) :: nil);
+ tag = d.tag;
+ }
+ if(c.valid != nil)
+ vs := (*c.valid).sexp();
+ s := ref Sexp.List(ref Sexp.String("cert",nil) ::
+ ref Sexp.List(ref Sexp.String("issuer",nil) :: c.issuer.sexp() :: nil) ::
+ c.subject.sexp() ::
+ ds ::
+ tag ::
+ vs ::
+ nil);
+ return s;
+}
+
+Subject.principal(s: self ref Subject): ref Key
+{
+ pick r := s {
+ P =>
+ return r.key;
+ N =>
+ return r.name.principal;
+ * =>
+ return nil; # TO DO
+ }
+}
+
+Subject.text(s: self ref Subject): string
+{
+ pick r := s {
+ P =>
+ return r.key.text();
+ N =>
+ return r.name.text();
+ KH =>
+ return sys->sprint("(keyholder %s)", r.holder.text());
+ O =>
+ return sys->sprint("(object-hash %s)", r.hash.text());
+ T =>
+ return s.sexp().text(); # easy way out
+ }
+}
+
+Subject.sexp(s: self ref Subject): ref Sexp
+{
+ e: ref Sexp;
+ pick r := s {
+ P =>
+ e = r.key.sexp();
+ N =>
+ e = r.name.sexp();
+ KH =>
+ e = ref Sexp.List(ref Sexp.String("keyholder",nil) :: r.holder.sexp() :: nil);
+ O =>
+ e = ref Sexp.List(ref Sexp.String("object-hash",nil) :: r.hash.sexp() :: nil);
+ T =>
+ sl: list of ref Sexp;
+ for(subs := r.subs; subs != nil; subs = tl subs)
+ sl = (hd subs).sexp() :: sl;
+ e = ref Sexp.List(ref Sexp.String("k-of-n",nil) ::
+ ref Sexp.String(string r.k,nil) :: ref Sexp.String(string r.n,nil) :: rev(sl));
+ * =>
+ return nil;
+ }
+ return ref Sexp.List(ref Sexp.String("subject",nil) :: e :: nil);
+}
+
+Subject.eq(s1: self ref Subject, s2: ref Subject): int
+{
+ if(s1 == s2)
+ return 1;
+ if(s1 == nil || s2 == nil || tagof s1 != tagof s2)
+ return 0;
+ pick r1 := s1 {
+ P =>
+ pick r2 := s2 {
+ P =>
+ return r1.key.eq(r2.key);
+ }
+ N =>
+ pick r2 := s2 {
+ N =>
+ return r1.name.eq(r2.name);
+ }
+ O =>
+ pick r2 := s2 {
+ O =>
+ return r1.hash.eq(r2.hash);
+ }
+ KH =>
+ pick r2 := s2 {
+ KH =>
+ return r1.holder.eq(r2.holder);
+ }
+ T =>
+ pick r2 := s2 {
+ T =>
+ if(r1.k != r2.k || r1.n != r2.n)
+ return 0;
+ l2 := r2.subs;
+ for(l1 := r1.subs; l1 != nil; l1 = tl l1){
+ if(l2 == nil || !(hd l1).eq(hd l2))
+ return 0;
+ l2 = tl l2;
+ }
+ }
+ }
+ return 0;
+}
+
+Name.isprincipal(n: self ref Name): int
+{
+ return n.names == nil;
+}
+
+Name.local(n: self ref Name): ref Name
+{
+ if(n.names == nil || tl n.names == nil)
+ return n;
+ return ref Name(n.principal, hd n.names :: nil);
+}
+
+Name.islocal(n: self ref Name): int
+{
+ return n.names == nil || tl n.names == nil;
+}
+
+Name.isprefix(n1: self ref Name, n2: ref Name): int
+{
+ if(n1 == nil)
+ return n2 == nil;
+ if(!n1.principal.eq(n2.principal))
+ return 0;
+ s1 := n1.names;
+ s2 := n2.names;
+ for(; s1 != nil; s1 = tl s1){
+ if(s2 == nil || hd s2 != hd s1)
+ return 0;
+ s2 = tl s2;
+ }
+ return 1;
+}
+
+Name.text(n: self ref Name): string
+{
+ if(n.principal == nil)
+ s := "$self";
+ else
+ s = n.principal.text();
+ for(nl := n.names; nl != nil; nl = tl nl)
+ s += " " + hd nl;
+ return "(name "+s+")";
+}
+
+Name.sexp(n: self ref Name): ref Sexp
+{
+ ns: list of ref Sexp;
+
+ if(n.principal != nil)
+ is := n.principal.sexp();
+ else
+ is = ref Sexp.String("$self",nil);
+ if(n.names == nil)
+ return is;
+ for(nl := n.names; nl != nil; nl = tl nl)
+ ns = ref Sexp.String(hd nl,nil) :: ns;
+ return ref Sexp.List(ref Sexp.String("name",nil) :: is :: rev(ns));
+}
+
+Name.eq(a: self ref Name, b: ref Name): int
+{
+ if(a == b)
+ return 1;
+ if(a == nil || b == nil)
+ return 0;
+ if(!a.principal.eq(b.principal))
+ return 0;
+ nb := b.names;
+ for(na := a.names; na != nil; na = tl na){
+ if(nb == nil || hd nb != hd na)
+ return 0;
+ nb = tl nb;
+ }
+ return nb == nil;
+}
+
+Key.hashed(key: self ref Key, alg: string): array of byte
+{
+ if(key.hash != nil && key.halg == alg && key.hash.hash != nil)
+ return key.hash.hash;
+ krp := Keyrep.pk(key.pk);
+ if(krp == nil)
+ return nil;
+ n := krp.getb("n");
+ e := krp.getb("e");
+ if(n == nil || e == nil)
+ return nil;
+ ex := ref Sexp.List(
+ ref Sexp.String("public-key", nil) ::
+ ref Sexp.List(
+ ref Sexp.String("rsa-pkcs1-"+alg, nil) ::
+ ref Sexp.List(ref Sexp.String("e", nil) :: ref Sexp.Binary(e, nil) :: nil) ::
+ ref Sexp.List(ref Sexp.String("n", nil) :: ref Sexp.Binary(n, nil) :: nil)
+ :: nil)
+ :: nil);
+# sys->print("=> %q %s\n", hd tl tl flds, ex.text());
+ hash := hashexp(ex, alg);
+ if((key.hash == nil || key.hash.hash == nil) && (key.halg == alg || key.halg == nil)){
+ key.halg = alg;
+ key.hash = ref Hash(alg, hash);
+ }
+ return hash;
+}
+
+Key.sigalg(k: self ref Key): string
+{
+ if(k.pk == nil || k.pk.sa == nil)
+ return nil;
+ halg := "";
+ if(k.halg != nil)
+ halg = "-"+k.halg;
+ n := k.pk.sa.name;
+ if(n == "rsa" || n == "dsa")
+ return n+"-pkcs1"+halg;
+ return n+halg;
+}
+
+Key.text(k: self ref Key): string
+{
+ e := k.sexp();
+ if(e == nil)
+ return sys->sprint("(public-key unknown)");
+ return e.text();
+}
+
+Key.sexp(k: self ref Key): ref Sexp
+{
+ if(k.hash != nil && k.hash.hash != nil)
+ return k.hash.sexp();
+ krp := Keyrep.pk(k.pk);
+ if(krp == nil)
+ return nil;
+ rl: list of ref Sexp;
+ for(el := krp.els; el != nil; el = tl el){
+ (n, v) := hd el;
+ a := pre0(v.iptobebytes());
+ rl = ref Sexp.List(ref Sexp.String(n,nil) :: ref Sexp.Binary(a,nil) :: nil) :: rl;
+ }
+ return ref Sexp.List(ref Sexp.String("public-key", nil) ::
+ ref Sexp.List(ref Sexp.String(k.sigalg(),nil) :: rev(rl)) :: nil);
+}
+
+Key.eq(k1: self ref Key, k2: ref Key): int
+{
+ if(k1 == k2)
+ return 1;
+ if(k1 == nil || k2 == nil)
+ return 0;
+ if(k1.hash != nil && k2.hash != nil && k1.hash.eq(k2.hash))
+ return 1;
+ if(k1.pk != nil && k2.pk != nil)
+ return kr->pktostr(k1.pk) == kr->pktostr(k2.pk); # TO DO
+ return 0;
+}
+
+dec(s: string, i: int, l: int): (int, int)
+{
+ l += i;
+ n := 0;
+ for(; i < l; i++){
+ c := s[i];
+ if(!(c >= '0' && c <= '9'))
+ return (-1, 0);
+ n = n*10 + (c-'0');
+ }
+ return (n, l);
+}
+
+# TO DO: any valid prefix of a date
+
+date2epoch(t: string): int
+{
+ if(len t != 19)
+ return -1;
+ tm := ref Daytime->Tm;
+ i: int;
+ (tm.year, i) = dec(t, 0, 4);
+ if(tm.year < 0 || t[i++] != '-')
+ return -1;
+ tm.year -= 1900;
+ (tm.mon, i) = dec(t, i, 2);
+ if(tm.mon <= 0 || t[i++] != '-' || tm.mon > 12)
+ return -1;
+ tm.mon--;
+ (tm.mday, i) = dec(t, i, 2);
+ if(tm.mday <= 0 || t[i++] != '_' || tm.mday >= 31)
+ return -1;
+ (tm.hour, i) = dec(t, i, 2);
+ if(tm.hour < 0 || t[i++] != ':' || tm.hour > 23)
+ return -1;
+ (tm.min, i) = dec(t, i, 2);
+ if(tm.min < 0 || t[i++] != ':' || tm.min > 59)
+ return -1;
+ (tm.sec, i) = dec(t, i, 2);
+ if(tm.sec < 0 || tm.sec > 59) # leap second(s)?
+ return -1;
+ tm.tzoff = 0;
+ return daytime->tm2epoch(tm);
+}
+
+epoch2date(t: int): string
+{
+ tm := daytime->gmt(t);
+ return sys->sprint("%.4d-%.2d-%.2d_%.2d:%.2d:%.2d",
+ tm.year+1900, tm.mon+1, tm.mday, tm.hour, tm.min, tm.sec);
+}
+
+# could use a delta-time function
+
+time2secs(s: string): int
+{
+ if(len s != 8) # HH:MM:SS
+ return -1;
+ hh, mm, ss, i: int;
+ (hh, i) = dec(s, 0, 2);
+ if(hh < 0 || hh > 24 || s[i++] != ':')
+ return -1;
+ (mm, i) = dec(s, i, 2);
+ if(mm < 0 || mm > 59 || s[i++] != ':')
+ return -1;
+ (ss, i) = dec(s, i, 2);
+ if(ss < 0 || ss > 59)
+ return -1;
+ return hh*3600 + mm*60 + ss;
+}
+
+secs2time(t: int): string
+{
+ hh := (t/60*60)%24;
+ mm := (t%3600)/60;
+ ss := t%60;
+ return sys->sprint("%.2d:%.2d:%.2d", hh, mm, ss);
+}
+
+#
+# auth tag intersection as defined by
+# ``A Formal Semantics for SPKI'', Jon Howell, David Kotz
+# its proof cases are marked by the roman numerals (I) ... (X)
+# with contributions from
+# ``A Note on SPKI's Authorisation Syntax'', Olav Bandmann, Mads Dam
+# its AIntersect cases are marked by arabic numerals
+
+maketag(e: ref Sexp): ref Sexp
+{
+ if(e == nil)
+ return e;
+ return remake(e.copy());
+}
+
+tagimplies(t1: ref Sexp, t2: ref Sexp): int
+{
+ e := tagintersect(t1, t2);
+ if(e == nil)
+ return 0;
+ return e.eq(t2);
+}
+
+Anull, Astar, Abytes, Aprefix, Asuffix, Arange, Alist, Aset: con iota;
+
+tagindex(s: ref Sexp): int
+{
+ if(s == nil)
+ return Anull;
+ pick r := s {
+ String =>
+ return Abytes;
+ Binary =>
+ return Abytes;
+ List =>
+ if(r.op() == "*"){
+ if(tl r.l == nil)
+ return Astar;
+ case (hd tl r.l).astext() {
+ "prefix" => return Aprefix;
+ "suffix" => return Asuffix;
+ "range" => return Arange;
+ "set" => return Aset;
+ * => return Anull; # unknown
+ }
+ }
+ return Alist;
+ * =>
+ return Anull; # not reached
+ }
+}
+
+#
+# 1 (*) x r = r
+# 2 r x (*) = r
+# 3 ⊥ x r = ⊥
+# 4 r x ⊥ = ⊥
+# 5 a x a = a (also a x a' = ⊥)
+# 6 a x b = a if a ∈ Val(b)
+# 7 a x b = ⊥ if a ∉ Val(b)
+# 8 a x (a' r1 ... rn)) = ⊥
+# 9 a x (* set r1 ... ri = a ... rn) = a
+# 10 a x (* set r1 ... ri = b ... rn) = a, if a ∈ Val(b)
+# 11 a x (* set r1 ... ri ... rn)) = ⊥, if neither of above two cases applies
+# 12 b x b' = b ∩ b'
+# 13 b x (a r1 ... rn) = ⊥
+# 14 b x (* set r1 ... rn) = (*set (b x r'[1]) ... (b x r'[m])), for atomic elements in r1, ..., rn
+# 15 (a r1 ... rn) x (a r'[1] ... r'[n] r'[n+1] ... r'[m]) = (a (r1 x r'[1]) ... (rn x r'[n]) r'[n+1] ... r'[m]) for m >= n
+# 16 (a r1 ... rn) x (a' r'[1] ... r'[m]) = ⊥
+# 17 (a r1 ... rn) x (* set r'[1] ... r'[i] ... r'[k]) = (a r1 ... rn) x r'[i], if r'[i] has tag a
+# 18 (a r1 ... rn) x (* set r'[1] ... r'[m]) = ⊥, if no r'[i] has tag a
+# 19 (* set r1 .. rn) x r, where r is (* set r1'[1] ... r'[m]) = (* set (r1 x r) (r2 x r) ... (rn x r))
+#
+# nil is used instead of ⊥, which works provided an incoming credential
+# with no tag has implicit tag (*)
+#
+
+# put operands in order of proof in FSS
+
+swaptag := array[] of {
+ (Abytes<<4) | Alist => (Alist<<4) | Abytes, # (IV)
+
+ (Abytes<<4) | Aset => (Aset<<4) | Abytes, # (VI)
+ (Aprefix<<4) | Aset => (Aset<<4) | Aprefix, # (VI)
+ (Arange<<4) | Aset => (Aset<<4) | Arange, # (VI)
+ (Alist<<4) | Aset => (Aset<<4) | Alist, # (VI)
+ (Asuffix<<4) | Aset => (Aset<<4) | Asuffix, # (VI) extension
+
+ (Aprefix<<4) | Abytes => (Abytes<<4) | Aprefix, # (VII)
+ (Arange<<4) | Abytes => (Abytes<<4) | Arange, # (VII)
+ (Asuffix<<4) | Abytes => (Abytes<<4) | Asuffix, # (VII) extension
+
+ * => 0,
+};
+
+tagintersect(t1, t2: ref Sexp): ref Sexp
+{
+ if(t1 == t2)
+ return t1;
+ if(t1 == nil || t2 == nil) # 3, 4; case (I)
+ return nil;
+ x1 := tagindex(t1);
+ x2 := tagindex(t2);
+ if(debug){
+ sys->print("%#q -> %d\n", t1.text(), x1);
+ sys->print("%#q -> %d\n", t2.text(), x2);
+ }
+ if(x1 == Astar) # 1; case (II)
+ return t2;
+ if(x2 == Astar) # 2; case (II)
+ return t1;
+ code := (x1 << 4) | x2; # (a[x]<<4) | a[y] in FSS
+ # reorder symmetric cases
+ if(code < len swaptag && swaptag[code]){
+ (t1, t2) = (t2, t1);
+ (x1, x2) = (x2, x1);
+ code = swaptag[code];
+ }
+ case code {
+ (Abytes<<4) | Abytes => # case (III); 5
+ if(t1.eq(t2))
+ return t1;
+
+ (Alist<<4) | Abytes => # case (IV)
+ return nil;
+
+ (Alist<<4) | Alist => # case (V); 15-16
+ if(t1.op() != t2.op())
+ return nil;
+ l1 := t1.els();
+ l2 := t2.els();
+ if(len l1 > len l2){
+ (t1, t2) = (t2, t1);
+ (l1, l2) = (l2, l1);
+ }
+ rl: list of ref Sexp;
+ for(; l1 != nil; l1 = tl l1){
+ x := tagintersect(hd l1, hd l2);
+ if(x == nil)
+ return nil;
+ rl = x :: rl;
+ l2 = tl l2;
+ }
+ for(; l2 != nil; l2 = tl l2)
+ rl = hd l2 :: rl;
+ return ref Sexp.List(rev(rl));
+
+ (Aset<<4) | Abytes => # case (VI); 9-11
+ for(el := setof(t1); el != nil; el = tl el){
+ e := hd el;
+ case tagindex(e) {
+ Abytes =>
+ if(e.eq(t2))
+ return t2;
+ Astar =>
+ return t2;
+ Arange =>
+ if(inrange(t2, e))
+ return t2;
+ Aprefix =>
+ if(isprefix(e, t2))
+ return t2;
+ Asuffix =>
+ if(issuffix(e, t2))
+ return t2;
+ }
+ }
+ # otherwise null
+
+ (Aset<<4) | Alist => # case (VI); 17-18
+ o := t2.op();
+ for(el := setof(t1); el != nil; el = tl el){
+ e := hd el;
+ if(e.islist() && e.op() == o || tagindex(e) == Astar)
+ return tagintersect(e, t2);
+ }
+ # otherwise null
+
+ (Aset<<4) | Aprefix or # case (VI); 14
+ (Aset<<4) | Arange or # case (VI); 14
+ # for Aprefix or Arange, could restrict els of t1 to atomic elements (sets A and B)
+ # here, following rule 14, but we'll let tagintersect sort it out in the general case below
+ (Aset<<4) | Aset => # case (VI); 19
+ rl: list of ref Sexp;
+ for(el := setof(t1); el != nil; el = tl el){
+ x := tagintersect(hd el, t2);
+ if(x != nil)
+ rl = x :: rl;
+ }
+ return mkset(rev(rl)); # null if empty
+
+ (Abytes<<4) | Aprefix => # case (VII)
+ if(isprefix(t2, t1))
+ return t1;
+ (Abytes<<4) | Arange => # case (VII)
+ if(inrange(t1, t2))
+ return t1;
+ (Abytes<<4) | Asuffix => # case (VII)
+ if(issuffix(t2, t1))
+ return t1;
+
+ (Aprefix<<4) | Aprefix => # case (VIII)
+ p1 := prefixof(t1);
+ p2 := prefixof(t2);
+ if(p1 == nil || p2 == nil)
+ return nil;
+ if(p1.nb < p2.nb){
+ (t1, t2) = (t2, t1);
+ (p1, p2) = (p2, p1);
+ }
+ if((*p2).isprefix(*p1))
+ return t1; # t1 is longer, thus more specific
+
+ (Asuffix<<4) | Asuffix => # case (VIII) extension
+ p1 := suffixof(t1);
+ p2 := suffixof(t2);
+ if(p1 == nil || p2 == nil)
+ return nil;
+ if(p1.nb < p2.nb){
+ (t1, t2) = (t2, t1);
+ (p1, p2) = (p2, p1);
+ }
+ if((*p2).issuffix(*p1))
+ return t1; # t1 is longer, thus more specific
+
+ (Arange<<4) | Aprefix => # case (IX)
+ return nil;
+ (Arange<<4) | Asuffix => # case (IX)
+ return nil;
+ (Arange<<4) | Arange => # case (IX)
+ v1 := rangeof(t1);
+ v2 := rangeof(t2);
+ if(v1 == nil || v2 == nil)
+ return nil; # invalid
+ (ok, v) := (*v1).intersect(*v2);
+ if(ok)
+ return mkrange(v);
+
+ (Alist<<4) | Arange or
+ (Alist<<4) | Aprefix => # case (X)
+ ;
+ }
+ return nil; # case (X), and default
+}
+
+isprefix(pat, subj: ref Sexp): int
+{
+ p := prefixof(pat);
+ if(p == nil)
+ return 0;
+ return (*p).isprefix(valof(subj));
+}
+
+issuffix(pat, subj: ref Sexp): int
+{
+ p := suffixof(pat);
+ if(p == nil)
+ return 0;
+ return (*p).issuffix(valof(subj));
+}
+
+inrange(t1, t2: ref Sexp): int
+{
+ v := valof(t1);
+ r := rangeof(t2);
+ if(r == nil)
+ return 0;
+ if(0)
+ sys->print("%s :: %s\n", v.text(), (*r).text());
+ pass := 0;
+ if(r.ge >= 0){
+ c := v.cmp(r.lb, r.order);
+ if(c < 0 || c == 0 && !r.ge)
+ return 0;
+ pass = 1;
+ }
+ if(r.le >= 0){
+ c := v.cmp(r.ub, r.order);
+ if(c > 0 || c == 0 && !r.le)
+ return 0;
+ pass = 1;
+ }
+ return pass;
+}
+
+addval(l: list of ref Sexp, s: string, v: Val): list of ref Sexp
+{
+ e: ref Sexp;
+ if(v.a != nil)
+ e = ref Sexp.Binary(v.a, v.hint);
+ else
+ e = ref Sexp.String(v.s, v.hint);
+ return ref Sexp.String(s, nil) :: e :: l;
+}
+
+mkrange(r: Vrange): ref Sexp
+{
+ l: list of ref Sexp;
+ if(r.le > 0)
+ l = addval(l, "le", r.ub);
+ else if(r.le == 0)
+ l = addval(l, "l", r.ub);
+ if(r.ge > 0)
+ l = addval(l, "ge", r.lb);
+ else if(r.ge == 0)
+ l = addval(l, "g", r.lb);
+ return ref Sexp.List(ref Sexp.String("*",nil) :: ref Sexp.String("range",nil) :: ref Sexp.String(r.otext(), nil) :: l);
+}
+
+valof(s: ref Sexp): Val
+{
+ pick r := s {
+ String =>
+ return Val.mk(r.s, nil, r.hint);
+ Binary =>
+ return Val.mk(nil, r.data, r.hint);
+ * =>
+ return Val.mk(nil, nil, nil); # can't happen
+ }
+}
+
+starop(s: ref Sexp, op: string): (string, list of ref Sexp)
+{
+ if(s == nil)
+ return (nil, nil);
+ pick r := s {
+ List =>
+ if(r.op() == "*" && tl r.l != nil){
+ pick t := hd tl r.l {
+ String =>
+ if(op != nil && t.s != op)
+ return (nil, nil);
+ return (t.s, tl tl r.l);
+ }
+ }
+ }
+ return (nil, nil);
+}
+
+isset(s: ref Sexp): (int, list of ref Sexp)
+{
+ (op, l) := starop(s, "set");
+ if(op != nil)
+ return (1, l);
+ return (0, l);
+}
+
+setof(s: ref Sexp): list of ref Sexp
+{
+ return starop(s, "set").t1;
+}
+
+prefixof(s: ref Sexp): ref Val
+{
+ return substrof(s, "prefix");
+}
+
+suffixof(s: ref Sexp): ref Val
+{
+ return substrof(s, "suffix");
+}
+
+substrof(s: ref Sexp, kind: string): ref Val
+{
+ l := starop(s, kind).t1;
+ if(l == nil)
+ return nil;
+ pick x := hd l{
+ String =>
+ return ref Val.mk(x.s, nil, x.hint);
+ Binary =>
+ return ref Val.mk(nil, x.data, x.hint);
+ }
+ return nil;
+}
+
+rangeof(s: ref Sexp): ref Vrange
+{
+ l := starop(s, "range").t1;
+ if(l == nil)
+ return nil;
+ ord: int;
+ case (hd l).astext() {
+ "alpha" => ord = Alpha;
+ "numeric" => ord = Numeric;
+ "binary" => ord = Binary;
+ "time" => ord = Time; # hh:mm:ss
+ "date" => ord = Date; # full date format
+ * => return nil;
+ }
+ l = tl l;
+ lb, ub: Val;
+ lt := -1;
+ gt := -1;
+ while(l != nil){
+ if(tl l == nil)
+ return nil;
+ o := (hd l).astext();
+ v: Val;
+ l = tl l;
+ if(l == nil)
+ return nil;
+ pick t := hd l {
+ String =>
+ v = Val.mk(t.s, nil, t.hint);
+ Binary =>
+ v = Val.mk(nil, t.data, t.hint);
+ * =>
+ return nil;
+ }
+ l = tl l;
+ case o {
+ "g" or "ge" =>
+ if(gt >= 0 || lt >= 0)
+ return nil;
+ gt = o == "ge";
+ lb = v;
+ "l" or "le" =>
+ if(lt >= 0)
+ return nil;
+ lt = o == "le";
+ ub = v;
+ * =>
+ return nil;
+ }
+ }
+ if(gt < 0 && lt < 0)
+ return nil;
+ return ref Vrange(ord, gt, lb, lt, ub);
+}
+
+Els: adt {
+ a: array of ref Sexp;
+ n: int;
+
+ add: fn(el: self ref Els, s: ref Sexp);
+ els: fn(el: self ref Els): array of ref Sexp;
+};
+
+Els.add(el: self ref Els, s: ref Sexp)
+{
+ if(el.n >= len el.a){
+ t := array[el.n+10] of ref Sexp;
+ if(el.a != nil)
+ t[0:] = el.a;
+ el.a = t;
+ }
+ el.a[el.n++] = s;
+}
+
+Els.els(el: self ref Els): array of ref Sexp
+{
+ if(el.n == 0)
+ return nil;
+ return el.a[0:el.n];
+}
+
+remake(s: ref Sexp): ref Sexp
+{
+ if(s == nil)
+ return nil;
+ pick r := s {
+ List =>
+ (is, mem) := isset(r);
+ if(is){
+ el := ref Els(array[10] of ref Sexp, 0);
+ members(mem, el);
+ if(debug)
+ sys->print("-- %#q\n", s.text());
+ y := mkset0(tolist(el.els()));
+ if(debug){
+ if(y == nil)
+ sys->print("\t=> EMPTY\n");
+ else
+ sys->print("\t=> %#q\n", y.text());
+ }
+ return y;
+ }
+ rl: list of ref Sexp;
+ for(l := r.l; l != nil; l = tl l){
+ e := remake(hd l);
+ if(e != hd l){
+ # structure changed, remake current node's list
+ for(il := r.l; il != l; il = tl il)
+ rl = hd il :: rl;
+ rl = e :: rl;
+ while((l = tl l) != nil)
+ rl = remake(hd l) :: rl;
+ return ref Sexp.List(rev(rl));
+ }
+ }
+ # unchanged
+ }
+ return s;
+}
+
+members(l: list of ref Sexp, el: ref Els)
+{
+ for(; l != nil; l = tl l){
+ e := hd l;
+ (is, mem) := isset(e);
+ if(is)
+ members(mem, el);
+ else
+ el.add(remake(e));
+ }
+}
+
+mkset(sl: list of ref Sexp): ref Sexp
+{
+ rl: list of ref Sexp;
+ for(l := sl; l != nil; l = tl l){
+ (is, mem) := isset(hd l);
+ if(is){
+ for(; mem != nil; mem = tl mem)
+ rl = hd mem :: rl;
+ }else
+ rl = hd l :: rl;
+ }
+ return mkset0(rev(rl));
+}
+
+mkset0(mem: list of ref Sexp): ref Sexp
+{
+ if(mem == nil)
+ return nil;
+ return ref Sexp.List(ref Sexp.String("*", nil) :: ref Sexp.String("set", nil) :: mem);
+}
+
+factor(a: array of ref Sexp): ref Sexp
+{
+ mergesort(a, array[len a] of ref Sexp);
+ for(i := 0; i < len a; i++){
+ case tagindex(a[i]) {
+ Astar =>
+ return a[i];
+ Alist =>
+ k := i+1;
+ if(k >= len a)
+ break;
+ if(a[k].islist() && (op := a[i].op()) != "*" && op == a[k].op()){
+ # ensure tag uniqueness within a set by: (* set (a L1) (a L2)) => (a (* set L1 L2))
+ ml := a[i].els();
+ n0 := hd ml;
+ rl := ref Sexp.List(tl ml) :: ref Sexp.String("set", nil) :: ref Sexp.String("*", nil) :: nil; # reversed
+ # gather tails of adjacent lists with op matching this one
+ for(; k < len a && a[k].islist() && a[k].op() == op; k++){
+ ml = tl a[k].els();
+ if(len ml == 1)
+ rl = hd ml :: rl;
+ else
+ rl = ref Sexp.List(ml) :: rl;
+ }
+ a[i] = ref Sexp.List(n0 :: remake(ref Sexp.List(rev(rl))) :: nil);
+ sys->print("common: %q [%d -> %d] -> %q\n", op, i, k-1, a[i].text());
+ if(k < len a)
+ a[i+1:] = a[k:];
+ a = a[0:i+1+(len a-k)];
+ }
+ }
+ }
+ return mkset0(tolist(a));
+}
+
+tolist(a: array of ref Sexp): list of ref Sexp
+{
+ l: list of ref Sexp;
+ for(i := len a; --i >= 0;)
+ l = a[i] :: l;
+ return l;
+}
+
+mergesort(a, b: array of ref Sexp)
+{
+ r := len a;
+ if(r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(b[i].islist() || !b[j].islist() && b[i].op() > b[j].op()) # a list is greater than any atom
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if(i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+Val: adt {
+ # only one of s or a is not nil
+ s: string;
+ a: array of byte;
+ hint: string;
+ nb: int; # size in bytes
+
+ mk: fn(s: string, a: array of byte, h: string): Val;
+ cmp: fn(a: self Val, b: Val, order: int): int;
+ isfloat: fn(a: self Val): int;
+ isprefix: fn(a: self Val, b: Val): int;
+ issuffix: fn(a: self Val, b: Val): int;
+ bytes: fn(a: self Val): array of byte;
+ text: fn(v: self Val): string;
+};
+
+Val.mk(s: string, a: array of byte, h: string): Val
+{
+ if(a != nil)
+ nb := len a;
+ else
+ nb = utflen(s);
+ return Val(s, a, h, nb);
+}
+
+Val.bytes(v: self Val): array of byte
+{
+ if(v.a != nil)
+ return v.a;
+ return array of byte v.s;
+}
+
+Val.isfloat(v: self Val): int
+{
+ if(v.a != nil)
+ return 0;
+ for(i := 0; i < len v.s; i++)
+ if(v.s[i] == '.')
+ return 1;
+ return 0;
+}
+
+Val.isprefix(a: self Val, b: Val): int
+{
+ if(a.hint != b.hint)
+ return 0;
+ # normalise to bytes
+ va := a.bytes();
+ vb := b.bytes();
+ for(i := 0; i < len va; i++)
+ if(i >= len vb || va[i] != vb[i])
+ return 0;
+ return 1;
+}
+
+Val.issuffix(a: self Val, b: Val): int
+{
+ if(a.hint != b.hint)
+ return 0;
+ # normalise to bytes
+ va := a.bytes();
+ vb := b.bytes();
+ for(i := 0; i < len va; i++)
+ if(i >= len vb || va[len va-i-1] != vb[len vb-i-1])
+ return 0;
+ return 1;
+}
+
+Val.cmp(a: self Val, b: Val, order: int): int
+{
+ if(a.hint != b.hint)
+ return -2;
+ case order {
+ Numeric => # TO DO: change this to use string comparisons
+ if(a.a != nil || b.a != nil)
+ return -2;
+ if(a.isfloat() || b.isfloat()){
+ fa := real a.s;
+ fb := real b.s;
+ if(fa < fb)
+ return -1;
+ if(fa > fb)
+ return 1;
+ return 0;
+ }
+ ia := big a.s;
+ ib := big b.s;
+ if(ia < ib)
+ return -1;
+ if(ia > ib)
+ return 1;
+ return 0;
+ Binary => # right-justified, unsigned binary values
+ av := a.a;
+ if(av == nil)
+ av = array of byte a.s;
+ bv := b.a;
+ if(bv == nil)
+ bv = array of byte b.s;
+ while(len av > len bv){
+ if(av[0] != byte 0)
+ return 1;
+ av = av[1:];
+ }
+ while(len bv > len av){
+ if(bv[0] != byte 0)
+ return -1;
+ bv = bv[1:];
+ }
+ return cmpbytes(av, bv);
+ }
+ # otherwise compare as strings
+ if(a.a != nil){
+ if(b.s != nil)
+ return cmpbytes(a.a, array of byte b.s);
+ return cmpbytes(a.a, b.a);
+ }
+ if(b.a != nil)
+ return cmpbytes(array of byte a.s, b.a);
+ if(a.s < b.s)
+ return -1;
+ if(a.s > b.s)
+ return 1;
+ return 0;
+}
+
+Val.text(v: self Val): string
+{
+ s: string;
+ if(v.hint != nil)
+ s = sys->sprint("[%s]", v.hint);
+ if(v.s != nil)
+ return s+v.s;
+ if(v.a != nil)
+ return sys->sprint("%s#%s#", s, base16->enc(v.a));
+ return sys->sprint("%s\"\"", s);
+}
+
+cmpbytes(a, b: array of byte): int
+{
+ n := len a;
+ if(n > len b)
+ n = len b;
+ for(i := 0; i < n; i++)
+ if(a[i] != b[i])
+ return int a[i] - int b[i];
+ return len a - len b;
+}
+
+Vrange: adt {
+ order: int;
+ ge: int;
+ lb: Val;
+ le: int;
+ ub: Val;
+
+ text: fn(v: self Vrange): string;
+ otext: fn(v: self Vrange): string;
+ intersect: fn(a: self Vrange, b: Vrange): (int, Vrange);
+};
+
+Alpha, Numeric, Time, Binary, Date: con iota; # Vrange.order
+
+Vrange.otext(r: self Vrange): string
+{
+ case r.order {
+ Alpha => return "alpha";
+ Numeric => return "numeric";
+ Time => return "time";
+ Binary => return "binary";
+ Date => return "date";
+ * => return sys->sprint("O%d", r.order);
+ }
+}
+
+Vrange.text(v: self Vrange): string
+{
+ s := sys->sprint("(* range %s", v.otext());
+ if(v.ge >= 0){
+ s += " g";
+ if(v.ge)
+ s += "e";
+ s += " "+v.lb.text();
+ }
+ if(v.le >= 0){
+ s += " l";
+ if(v.le)
+ s += "e";
+ s += " "+v.ub.text();
+ }
+ return s+")";
+}
+
+Vrange.intersect(v1: self Vrange, v2: Vrange): (int, Vrange)
+{
+ if(v1.order != v2.order)
+ return (0, v1); # incommensurate
+ v := v1;
+ if(v.ge < 0 || v2.ge >= 0 && v2.lb.cmp(v.lb, v.order) > 0)
+ v.lb = v2.lb;
+ if(v.le < 0 || v2.le >= 0 && v2.ub.cmp(v.ub, v.order) < 0)
+ v.ub = v2.ub;
+ if(v.lb.hint != v.ub.hint)
+ return (0, v1); # incommensurate
+ v.ge &= v2.ge;
+ v.le &= v2.le;
+ c := v.lb.cmp(v.ub, v.order);
+ if(c > 0 || c == 0 && !(v.ge && v.le))
+ return (0, v1); # empty range
+ return (1, v);
+}
+
+utflen(s: string): int
+{
+ return len array of byte s;
+}
+
+append[T](l1, l2: list of T): list of T
+{
+ rl1: list of T;
+ for(; l1 != nil; l1 = tl l1)
+ rl1 = hd l1 :: rl1;
+ for(; rl1 != nil; rl1 = tl rl1)
+ l2 = hd rl1 :: l2;
+ return l2;
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+revt[S,T](l: list of (S,T)): list of (S,T)
+{
+ rl: list of (S,T);
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+#
+# the following should probably be in a separate Limbo library module
+#
+
+Keyrep: adt {
+ alg: string;
+ owner: string;
+ els: list of (string, ref IPint);
+ pick{ # keeps a type distance between public and private keys
+ PK =>
+ SK =>
+ }
+
+ pk: fn(pk: ref Keyring->PK): ref Keyrep.PK;
+ sk: fn(sk: ref Keyring->SK): ref Keyrep.SK;
+ mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int);
+ mksk: fn(k: self ref Keyrep): ref Keyring->SK;
+ get: fn(k: self ref Keyrep, n: string): ref IPint;
+ getb: fn(k: self ref Keyrep, n: string): array of byte;
+ eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int;
+};
+
+keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint)
+{
+ a := array[len flds] of ref IPint;
+ for(i := 0; i < len a; i++){
+ a[i] = IPint.b64toip(hd flds);
+ flds = tl flds;
+ }
+ rl: list of (string, ref IPint);
+ for(; names != nil; names = tl names){
+ (n, p) := hd names;
+ if(p < len a)
+ rl = (n, a[p]) :: rl;
+ }
+ return revt(rl);
+}
+
+Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("e",1), ("n",0)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.get(k: self ref Keyrep, n: string): ref IPint
+{
+ for(el := k.els; el != nil; el = tl el)
+ if((hd el).t0 == n)
+ return (hd el).t1;
+ return nil;
+}
+
+Keyrep.getb(k: self ref Keyrep, n: string): array of byte
+{
+ v := k.get(n);
+ if(v == nil)
+ return nil;
+ return pre0(v.iptobebytes());
+}
+
+Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int)
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits());
+ * =>
+ raise "Keyrep: unknown algorithm";
+ }
+}
+
+Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ dk := k.get("!dk");
+ p := k.get("!p");
+ q := k.get("!q");
+ kp := k.get("!kp");
+ kq := k.get("!kq");
+ c12 := k.get("!c2");
+ return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(),
+ kp.iptob64(), kq.iptob64(), c12.iptob64()));
+ * =>
+ raise "Keyrep: unknown algorithm";
+ }
+}
+
+Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int
+{
+ # n⁲ but n is small
+ for(l1 := k1.els; l1 != nil; l1 = tl l1){
+ (n, v1) := hd l1;
+ v2 := k2.get(n);
+ if(v2 == nil || !v1.eq(v2))
+ return 0;
+ }
+ for(l2 := k2.els; l2 != nil; l2 = tl l2)
+ if(k1.get((hd l2).t0) == nil)
+ return 0;
+ return 1;
+}
+
+sig2icert(sig: ref Signature, signer: string, exp: int): ref Keyring->Certificate
+{
+ if(sig.sig == nil)
+ return nil;
+ s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1));
+#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1));
+ return kr->strtocert(s);
+}
+
+#
+# pkcs1 asn.1 DER encodings
+#
+
+pkcs1_md5_pfx := array[] of {
+ byte 16r30, byte 32, # SEQUENCE in 32 bytes
+ byte 16r30, byte 12, # SEQUENCE in 12 bytes
+ byte 6, byte 8, # OBJECT IDENTIFIER in 8 bytes
+ byte (40*1+2), # iso(1) member-body(2)
+ byte (16r80 + 6), byte 72, # US(840)
+ byte (16r80 + 6), byte (16r80 + 119), byte 13, # rsadsi(113549)
+ byte 2, # digestAlgorithm(2)
+ byte 5, # md5(5), end of OBJECT IDENTIFIER
+ byte 16r05, byte 0, # NULL parameter, end of SEQUENCE
+ byte 16r04, byte 16 #OCTET STRING in 16 bytes (MD5 length)
+} ;
+
+pkcs1_sha1_pfx := array[] of {
+ byte 16r30, byte 33, # SEQUENCE in 33 bytes
+ byte 16r30, byte 9, # SEQUENCE in 9 bytes
+ byte 6, byte 5, # OBJECT IDENTIFIER in 5 bytes
+ byte (40*1+3), # iso(1) member-body(3)
+ byte 14, # ??(14)
+ byte 3, # ??(3)
+ byte 2, # digestAlgorithm(2)
+ byte 26, # sha1(26), end of OBJECT IDENTIFIER
+ byte 16r05, byte 0, # NULL parameter, end of SEQUENCE
+ byte 16r40, byte 20 # OCTET STRING in 20 bytes (SHA1 length)
+};
+
+#
+# mlen should be key length in bytes
+#
+pkcs1_encode(ha: string, msg: array of byte, mlen: int): array of byte
+{
+ # apply hash function to message
+ hash: array of byte;
+ prefix: array of byte;
+ case ha {
+ "md5" =>
+ prefix = pkcs1_md5_pfx;
+ hash = array[Keyring->MD5dlen] of byte;
+ kr->md5(msg, len msg, hash, nil);
+ "sha" or "sha1" =>
+ prefix = pkcs1_sha1_pfx;
+ hash = array[Keyring->SHA1dlen] of byte;
+ kr->sha1(msg, len msg, hash, nil);
+ * =>
+ return nil;
+ }
+ tlen := len prefix + len hash;
+ if(mlen < tlen + 11)
+ return nil; # "intended encoded message length too short"
+ pslen := mlen - tlen - 3;
+ out := array[mlen] of byte;
+ out[0] = byte 0;
+ out[1] = byte 1;
+ for(i:=0; i<pslen; i++)
+ out[i+2] = byte 16rFF;
+ out[2+pslen] = byte 0;
+ out[2+pslen+1:] = prefix;
+ out[2+pslen+1+len prefix:] = hash;
+ return out;
+}
+
+#
+# for debugging
+#
+rsacomp(block: array of byte, akey: ref Key): array of byte
+{
+ key := Keyrep.pk(akey.pk);
+ x := kr->IPint.bebytestoip(block);
+ y := x.expmod(key.get("e"), key.get("n"));
+ ybytes := y.iptobebytes();
+#dump("rsacomp", ybytes);
+ k := 1024; # key.modlen;
+ ylen := len ybytes;
+ if(ylen < k) {
+ a := array[k] of { * => byte 0};
+ a[k-ylen:] = ybytes[0:];
+ ybytes = a;
+ }
+ else if(ylen > k) {
+ # assume it has leading zeros (mod should make it so)
+ a := array[k] of byte;
+ a[0:] = ybytes[ylen-k:];
+ ybytes = a;
+ }
+ return ybytes;
+}
diff --git a/appl/lib/spki/verifier.b b/appl/lib/spki/verifier.b
new file mode 100644
index 00000000..d712fd2a
--- /dev/null
+++ b/appl/lib/spki/verifier.b
@@ -0,0 +1,188 @@
+implement Verifier;
+
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+
+include "spki.m";
+ spki: SPKI;
+ Hash, Key, Cert, Name, Subject, Signature, Seqel, Toplev, Valid: import spki;
+ dump: import spki;
+
+include "encoding.m";
+ base64: Encoding;
+
+debug := 0;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ bufio = load Bufio Bufio->PATH;
+ sexprs = load Sexprs Sexprs->PATH;
+ spki = load SPKI SPKI->PATH;
+ base64 = load Encoding Encoding->BASE64PATH;
+
+ sexprs->init();
+ spki->init();
+}
+
+putkey(keys: list of ref Key, k: ref Key): list of ref Key
+{
+ for(kl := keys; kl != nil; kl = tl kl)
+ if(k.eq(hd kl))
+ return keys;
+ return k :: keys;
+}
+
+keybyhash(h: ref Hash, keys: list of ref Key): ref Key
+{
+ for(kl := keys; kl != nil; kl = tl kl){
+ k := hd kl;
+ if(k.hash != nil && h.eq(k.hash))
+ return k;
+ }
+ return nil;
+}
+
+verify(seq: list of ref Seqel): (ref Speaksfor, list of ref Seqel, string)
+{
+ stack: list of ref Seqel;
+ keys: list of ref Key;
+ n0: ref Name;
+ cn: ref Cert;
+ delegate := 1;
+ tag: ref Sexp;
+ val: ref Valid;
+ for(; seq != nil; seq = tl seq){
+ pick s := hd seq {
+ C =>
+ diag := checkcert(s.c);
+ if(diag != nil)
+ return (nil, seq, diag);
+ if(stack != nil){
+ pick h := hd stack {
+ C =>
+ if(!delegate)
+ return(nil, seq, "previous auth certificate did not delegate");
+ if(!h.c.subject.principal().eq(s.c.issuer.principal))
+ return (nil, seq, "certificate chain has mismatched principals");
+ if(debug)
+ sys->print("issuer %s ok\n", s.c.issuer.principal.text());
+ }
+ stack = tl stack;
+ }
+ stack = s :: stack;
+ if(n0 == nil)
+ n0 = s.c.issuer;
+ cn = s.c;
+ pick t := s.c {
+ A or KH or O =>
+ delegate = t.delegate;
+ if(tag != nil){
+ tag = spki->tagintersect(tag, t.tag);
+ if(tag == nil)
+ return (nil, seq, "certificate chain has null authority");
+ }else
+ tag = t.tag;
+ if(val != nil){
+ if(t.valid != nil){
+ (ok, iv) := (*val).intersect(*t.valid);
+ if(!ok)
+ return (nil, seq, "certificate chain is not currently valid");
+ *val = iv;
+ }
+ }else
+ val = t.valid;
+ }
+ K =>
+ stack = s :: stack;
+ O =>
+ if(s.op == "debug"){
+ debug = !debug;
+ continue;
+ }
+ if(s.op != "hash" || s.args == nil || tl s.args != nil)
+ return (nil, seq, "invalid operation to `do'");
+ alg := (hd s.args).astext();
+ if(alg != "md5" && alg != "sha1")
+ return (nil, seq, "invalid hash operation");
+ if(stack == nil)
+ return (nil, seq, "verification stack empty");
+ pick h := hd stack {
+ K =>
+ a := h.k.hashed(alg);
+ if(debug)
+ dump("do hash", a);
+ keys = putkey(keys, h.k);
+ stack = tl stack;
+ C =>
+ ;
+ * =>
+ return (nil, seq, "invalid type of operand for hash");
+ }
+ S =>
+ if(stack == nil)
+ return (nil, seq, "verification stack empty");
+ sig := s.sig;
+ if(sig.key == nil)
+ return (nil, seq, "neither hash nor key for signature");
+ if(sig.key.pk == nil){
+ k := keybyhash(sig.key.hash, keys);
+ if(k == nil)
+ return (nil, seq, "unknown key for signature");
+ sig.key = k;
+ }
+ pick c := hd stack {
+ C =>
+ if(c.c.e == nil)
+ return (nil, seq, "missing canonical expression for cert");
+ a := c.c.e.pack();
+ # verify signature ...
+ if(debug)
+ dump("cert a", a);
+ h := spki->hashbytes(a, "md5");
+ if(debug){
+ dump("hash cert", h);
+ sys->print("hash = %q\n", base64->enc(h));
+ }
+ failed := spki->checksig(c.c, sig);
+ if(debug)
+ sys->print("checksig: %q\n", failed);
+ if(failed != nil)
+ return (nil, seq, "signature verification failed: "+failed);
+ * =>
+ return (nil, seq, "invalid type of signature operand");
+ }
+ }
+ }
+ if(n0 != nil && cn != nil){
+ if(debug){
+ if(tag != nil)
+ auth := sys->sprint(" regarding %q", tag.text());
+ sys->print("%q speaks for %q%s\n", cn.subject.text(), n0.text(), auth);
+ }
+ return (ref Speaksfor(cn.subject, n0, tag, val), nil, nil);
+ }
+ return (nil, nil, nil);
+}
+
+checkcert(c: ref Cert): string
+{
+ return nil;
+}