chiark / gitweb /
pubkeys: Provide parser (and spec) for peer pubkeys files
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 29 Sep 2019 10:09:35 +0000 (11:09 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 15 Feb 2020 21:56:49 +0000 (21:56 +0000)
Nothing uses this yet; also, we don't have code in make-secnet-sites
to generate these either.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
.gitignore
Dir.sd.mk
README.make-secnet-sites
pubkeys.c [new file with mode: 0644]
pubkeys.fl.pl [new file with mode: 0755]
pubkeys.h [new file with mode: 0644]
secnet.h

index dd93a2196faa60b1f80e5e8101b2da4f5033b0a1..e73be05f789092e0122789aecde51c8adef2960f 100644 (file)
@@ -3,6 +3,8 @@
 *.pyc
 conffile.tab.[ch]
 conffile.yy.[ch]
+pubkeys.fl
+pubkeys.yy.[ch]
 /version.c
 /secnet
 /eax-*-test
index 05c39e37ffa2d1db71b0bfc112c113a67f994869..1dba14397482753fd701f3252bcbb155f000a04c 100644 (file)
--- a/Dir.sd.mk
+++ b/Dir.sd.mk
@@ -50,7 +50,7 @@ TARGETS:=secnet
 
 OBJECTS:=secnet.o util.o conffile.yy.o conffile.tab.o conffile.o modules.o \
        resolver.o random.o udp.o site.o transform-cbcmac.o transform-eax.o \
-       comm-common.o polypath.o privcache.o \
+       comm-common.o polypath.o privcache.o pubkeys.o pubkeys.yy.o \
        netlink.o rsa.o dh.o serpent.o serpentbe.o \
        md5.o sha512.o tun.o slip.o sha1.o ipaddr.o log.o \
        process.o osdep.o @LIBOBJS@ \
@@ -75,17 +75,21 @@ STALE_PYTHON_FILES= $(foreach e, py pyc, \
                        $(DESTDIR)$p/share/secnet/$l.$e \
                        )))
 
-%.c:   %.y
-
-%.yy.c:        %.fl
-       flex --header=$*.yy.h -o$@ $<
+%.yy.c %.yy.h: %.fl
+       flex --header=$*.yy.h -o$*.yy.c $<
 
 %.tab.c %.tab.h:       %.y
        bison -d -o $@ $<
 
-%.o: %.c conffile.yy.h
+%.o: %.c
        $(CC) $(CPPFLAGS) $(ALL_CFLAGS) $(CDEPS_CFLAGS) -c $< -o $@
 
+$(OBJECTS): conffile.yy.h pubkeys.yy.h base91s/base91.h
+# ^ we can't write this as a dependency on the %.o %.c rule
+#   because (say) conffile.yy.c isn't mentioned so doesn't "ought
+#   to exist" in make's mind.  But specifying it explicitly like this
+#   works.
+
 all::  $(TARGETS)
 
 ${srcdir}/config.h.in: configure.ac
@@ -96,12 +100,11 @@ MAKEFILE_TEMPLATES += config.h.in
 CONFIG_STATUS_OUTPUTS += config.h
 
 # Manual dependencies section
-conffile.yy.c: conffile.fl conffile.tab.c
-conffile.yy.h: conffile.yy.c
-conffile.tab.c:        conffile.y
+conffile.yy.c: conffile.tab.c
+%.tab.c: %.y
 # End of manual dependencies section
 
-conffile.yy.o: ALL_CFLAGS += -Wno-sign-compare
+%.yy.o: ALL_CFLAGS += -Wno-sign-compare -Wno-unused-function
 
 secnet:        $(OBJECTS)
        $(MAKE) -f main.mk version.o # *.o $(filter-out %.o, $^)
@@ -163,6 +166,11 @@ ipaddrset.confirm: ipaddrset-test.py ipaddrset.py ipaddrset-test.expected
        diff -u $(srcdir)/ipaddrset-test.expected ipaddrset-test.new
        touch $@
 
+&CLEAN += & pubkeys.fl
+
+pubkeys.fl: ${srcdir}/pubkeys.fl.pl
+       ${srcdir}/pubkeys.fl.pl >$@.tmp && mv -f $@.tmp $@
+
 .PRECIOUS: eax-%-test
 
 installdirs:
index ea767e912f1616d05de31b8872280c1c18cf0d81..4fd594ea539bddeb050aac853ea5031613d85608 100644 (file)
@@ -186,6 +186,28 @@ INPUT SYNTAX
                Assigns BOOL to the `mobile' key.  Acceptable only at
                site level, but optional.
 
+       Properties which can also appear in public key files.
+       These are acceptable to make-secnet-sites only at
+       site level.  See also `Site long-term keys' in NOTES.
+
+       pub ALG DATAB91S
+               Defines a public key.  ALG is an algorithm name and
+               DATA91S is the public key data, encoded according to
+               secnet-base91 (see below).
+               Not yet suported in make-secnet-sites.
+
+       serial SETIDHEX
+               Specifies the key set id (8 hex digits representing
+               4 bytes: each pair is the value of the next byte).
+               May appear at most once.  If not present, 00000000.
+               Not yet suported in make-secnet-sites.
+
+       pkg GROUPIDHEX
+               Specifies the key group id for subsequent keys.
+               May be repeated (with different id values).
+               If not specified, 00000000.
+               Not yet suported in make-secnet-sites.
+
 
 OUTPUT STRUCTURE
 
diff --git a/pubkeys.c b/pubkeys.c
new file mode 100644 (file)
index 0000000..7ba9482
--- /dev/null
+++ b/pubkeys.c
@@ -0,0 +1,45 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include "pubkeys.h"
+#include "pubkeys.yy.h"
+
+void keyset_dispose(struct peer_keyset **ks_io)
+{
+    struct peer_keyset *ks=*ks_io;
+    if (!ks) return;
+    *ks_io=0;
+    ks->refcount--;
+    assert(ks->refcount>=0);
+    if (ks->refcount) return;
+    for (int ki=0; ki<ks->nkeys; ki++) {
+       struct sigpubkey_if *pk=ks->keys[ki].pubkey;
+       pk->dispose(pk->st);
+    }
+    free(ks);
+}
+
+const struct sigscheme_info *sigscheme_lookup(const char *name)
+{
+    const struct sigscheme_info *scheme;
+    for (scheme=sigschemes; scheme->name; scheme++)
+       if (!strcmp(name,scheme->name))
+           return scheme;
+    return 0;
+}
diff --git a/pubkeys.fl.pl b/pubkeys.fl.pl
new file mode 100755 (executable)
index 0000000..19af65e
--- /dev/null
@@ -0,0 +1,313 @@
+#!/usr/bin/perl -w
+# -*- C -*-
+#
+# secnet - pubkeys.fl.pl
+#
+# This file is part of secnet.
+# See README for full list of copyright holders.
+#
+# secnet is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# secnet is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# version 3 along with secnet; if not, see
+# https://www.gnu.org/licenses/gpl.html.
+
+# We process __DATA__ of this file first through the perl code,
+# and then through flex.  We do it like this because directives
+# with positional arguments are otherwise rather tedious to specify
+# in flex.  Of course we could have used bison too but this seems
+# better overall.
+
+use strict;
+
+our $do = '';
+our $co = '';
+our $kw;
+our $kwid;
+our @next_kw;
+our $in_s;
+our $data_off;
+
+our %subst = (GRPIDSZ => 4, SERIALSZ => 4);
+
+our $last_lno = -1;
+sub lineno (;$$) {
+    my ($always, $delta) = @_;
+    my $o = '';
+    $delta //= 0;
+    if ($always || $. != $last_lno+1) {
+       $o .= sprintf "#line %d \"%s\"\n", $delta+$data_off+$., $0;
+    }
+    $last_lno = $.;
+    $o;
+}
+
+while (<DATA>) {
+       last if m/^\%\%\s*$/;
+       if (m/^!SUBSTCHECKS\s*$/) {
+               foreach (keys %subst) {
+                       $do .= <<END
+#if $_ != $subst{$_}
+# error $_ value disagrees between pubkeys.fl.pl and C headers
+#endif
+END
+               }
+               next;
+       }
+       $do .= lineno();
+       $do .= $_;
+}
+
+sub inst ($) {
+       $do .= "%x $_[0]\n";
+       "<$_[0]>";
+}
+
+while (<DATA>) {
+    s#\{!2(\w+)\}# '{'.(2 * ($subst{$1}//die "$1 ?")).'}' #ge;
+    if (m/^!KEYWORD ([-0-9a-z]+)$/) {
+       die if $kw;
+       $kw = $1;
+       $kwid = $kw; $kwid =~ y/-/_/;
+       $in_s = "HK_${kwid}";
+       $co .= "{L}$kw { BEGIN($in_s); }\n";
+       next;
+    }
+    if (m/^!ARG (\w+) (\S.*\S) \{\s*$/) {
+       die unless $kw;
+       die if @next_kw;
+       $co .= inst("$in_s")."{S} { BEGIN(D_${kwid}_$1); }\n";
+       $co .= inst("D_${kwid}_$1")."$2 {\n";
+       $in_s = "HA_${kwid}_$1";
+       $co .= "\tBEGIN($in_s);\n";
+       @next_kw = ($kw);
+       $co .= lineno(1,1);
+       next;
+    }
+    if (m/^!\}\s*$/) {
+       die unless @next_kw;
+       $co .= lineno(1,0);
+       $co .= "}\n";
+       $kw = shift @next_kw;
+       next;
+    }
+    if (m/^!FINAL \{\s*$/) {
+       die unless $kw;
+       die if @next_kw;
+       $co .= inst("FIN_$kwid")."\\n { BEGIN(0); c->lno++; }\n";
+       $co .= inst("$in_s")."{L}/\\n {\n";
+       $co .= "\tBEGIN(FIN_$kwid);\n";
+       $co .= lineno(1,1);
+       @next_kw = (undef);
+       next;
+    }
+    if (m/^!/) {
+       die;
+    }
+    $co .= $_;
+    if (m/^\%\%\s*$/) {
+       $co .= lineno(1,1);
+    }
+}
+
+print $do, "%%\n", $co or die $!;
+
+BEGIN { $data_off = __LINE__ + 1; }
+__DATA__
+
+L      [ \t]*
+S      [ \t]+
+BASE91S        []-~!#-&(-[]+
+%x SKIPNL
+
+%option yylineno
+%option noyywrap
+%option batch
+%option 8bit
+%option nodefault
+%option never-interactive
+
+%option prefix="pkyy"
+
+%option warn
+
+%{
+
+#include "secnet.h"
+#include "pubkeys.h"
+#include "util.h"
+#include "unaligned.h"
+#include "base91s/base91.h"
+
+!SUBSTCHECKS
+
+struct pubkeyset_context {
+    /* filled in during setup: */
+    struct log_if *log;
+    struct buffer_if *data_buf;
+    struct peer_keyset *building;
+    /* runtime: */
+    bool_t had_serial;
+    int lno;
+    const struct sigscheme_info *scheme;
+    uint8_t grpid[GRPIDSZ];
+    serialt serial;
+};
+
+static struct pubkeyset_context c[1];
+
+#define LI (c->log)
+#define HEX2BIN(v,l) ({                                                        \
+       int32_t outlen;                                                 \
+       bool_t ok=hex_decode((v), ((l)), &outlen, yytext, False);       \
+       assert(ok);                                                     \
+       assert(outlen==((l)));                                          \
+    })
+#define HEX2BIN_ARRAY(v) HEX2BIN((v),sizeof((v)))
+#define DOSKIPQ ({                                     \
+        BEGIN(SKIPNL);                                 \
+        break;                                         \
+    })
+#define DOSKIP(m) ({                                   \
+        slilog(LI,M_INFO,"l.%d: " m, c->lno);  \
+        DOSKIPQ;                                       \
+    })
+#define FAIL(m) do{                                    \
+       slilog(LI,M_ERR,"l.%d: " m, c->lno);    \
+       return -1;                                      \
+    }while(0)
+
+%}
+
+%%
+
+!KEYWORD pkg
+!ARG id [0-9a-f]{!2GRPIDSZ} {
+    HEX2BIN_ARRAY(c->grpid);
+!}
+!FINAL {
+!}
+!KEYWORD pub
+!ARG algo [-0-9a-z]+ {
+    c->scheme = sigscheme_lookup(yytext);
+    if (!c->scheme) DOSKIP("unknown pk algorithm");
+!}
+!ARG data {BASE91S} {
+    /* baseE91 and thus base91s can sometimes store 14 bits per
+     * character pair, so the max decode ratio is 14/16. */
+    size_t maxl = base91s_decode_maxlen(yyleng);
+    buffer_init(c->data_buf,0);
+    if (buf_remaining_space(c->data_buf) < maxl) DOSKIP("pk data too long");
+    struct base91s b91;
+    base91s_init(&b91);
+    size_t l = base91s_decode(&b91, yytext, yyleng, c->data_buf->start);
+    l += base91s_decode_end(&b91, c->data_buf->start + l);
+    assert(l <= maxl);
+    buf_append(c->data_buf,l);
+!}
+!FINAL {
+    if (c->building->nkeys >= MAX_SIG_KEYS) DOSKIP("too many public keys");
+    struct sigpubkey_if *pubkey;
+    bool_t ok=c->scheme->loadpub(c->scheme,c->data_buf,
+                                &pubkey,c->log);
+    if (!ok) break;
+    memcpy(c->building->keys[c->building->nkeys].id.b,
+          c->grpid,
+           GRPIDSZ);
+    assert(ALGIDSZ==1); /* otherwise need htons or htonl or something */
+    c->building->keys[c->building->nkeys].id.b[GRPIDSZ]=
+      c->scheme->algid;
+    c->building->keys[c->building->nkeys++].pubkey=pubkey;
+!}
+
+!KEYWORD serial
+!ARG id [0-9a-f]{!2SERIALSZ} {
+    if (c->had_serial) FAIL("`serial' repeated");
+    c->had_serial = 1;
+    uint8_t sb[SERIALSZ];
+    HEX2BIN_ARRAY(sb);
+    c->serial=get_uint32(sb);
+!}
+!FINAL {
+!}
+
+{L}[-0-9a-z]+ {
+    DOSKIP("unknown directive");
+}
+
+{L}\# {
+    BEGIN(SKIPNL);
+}
+{L}\n {
+    c->lno++;
+}
+
+<SKIPNL>.*\n {
+    c->lno++;
+    BEGIN(0);
+}
+
+<INITIAL><<EOF>>       { return 0; }
+
+<*>. { FAIL("syntax error"); }
+<*>\n { FAIL("syntax error - unexpected newline"); }
+<<EOF>> { FAIL("syntax error - unexpected eof"); }
+
+%%
+
+extern struct peer_keyset *
+keyset_load(const char *path, struct buffer_if *data_buf,
+           struct log_if *log, int logcl_enoent) {
+    assert(!c->building);
+    c->log=log;
+    pkyyin = fopen(path, "r");
+    if (!pkyyin) {
+       slilog(LI,
+              errno==ENOENT ? logcl_enoent : M_ERR,
+              "could not open keyset file %s: %s",
+              path,strerror(errno));
+       goto err;
+    }
+
+    pkyyrestart(pkyyin);
+    BEGIN(0);
+    c->data_buf=data_buf;
+    NEW(c->building);
+    c->building->nkeys=0;
+    c->building->refcount=1;
+    c->had_serial=0;
+    c->lno=1;
+    FILLZERO(c->grpid);
+    FILLZERO(c->serial);
+    int r=pkyylex();
+    if (r) goto err_bad;
+
+    if (!c->had_serial) {
+       slilog(LI,M_ERR,"missing serial number in %s",path);
+       goto err_bad;
+    }
+    if (!c->building->nkeys) {
+       slilog(LI,M_ERR,"no useable keys in %s",path);
+       goto err_bad;
+    }
+    fclose(pkyyin);
+    struct peer_keyset *built=c->building;
+    c->building=0;
+    return built;
+
+ err_bad:
+    errno=EBADMSG;
+ err:
+    if (c->building) { free(c->building); c->building=0; }
+    if (pkyyin) { fclose(pkyyin); pkyyin=0; }
+    return 0;
+}
+
diff --git a/pubkeys.h b/pubkeys.h
new file mode 100644 (file)
index 0000000..0901baa
--- /dev/null
+++ b/pubkeys.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#ifndef pubkeys_h
+#define pubkeys_h
+
+#include "secnet.h"
+
+/*----- shared with site.c -----*/
+
+struct peer_pubkey {
+    struct sigkeyid id;
+    struct sigpubkey_if *pubkey; /* does not need ->sethash calling */
+};
+
+struct peer_keyset {
+    int refcount;
+    serialt serial;
+    int nkeys;
+    struct peer_pubkey keys[MAX_SIG_KEYS];
+};
+
+extern struct peer_keyset *
+keyset_load(const char *path, struct buffer_if *data_buf,
+           struct log_if *log, int logcl_enoent);
+
+extern void keyset_dispose(struct peer_keyset **ks);
+
+static inline struct peer_keyset *keyset_dup(struct peer_keyset *in) {
+    in->refcount++;
+    return in;
+}
+
+extern bool_t
+pubkey_want(struct peer_keyset *building /* refcount and serial undef */,
+           struct sigkeyid *id, const struct sigscheme_info *scheme);
+
+#endif /* pubkeys_h */
index afc635a7a6c3e12c07e055e037349b712719ca89..7c650a9459f54e9ef2d0684fc1a4cad6911b6509 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -431,6 +431,8 @@ struct sigscheme_info {
 extern const struct sigscheme_info rsa1_sigscheme;
 extern const struct sigscheme_info sigschemes[]; /* sentinel has name==0 */
 
+const struct sigscheme_info *sigscheme_lookup(const char *name);
+
 /***** END of signature schemes *****/
 
 /***** CLOSURE TYPES and interface definitions *****/