From: Ian Jackson Date: Sun, 29 Sep 2019 10:09:35 +0000 (+0100) Subject: pubkeys: Provide parser (and spec) for peer pubkeys files X-Git-Tag: v0.6.0~194 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;ds=sidebyside;h=20c35278822db437d832e47166c5936a93e891fd;p=secnet.git pubkeys: Provide parser (and spec) for peer pubkeys files Nothing uses this yet; also, we don't have code in make-secnet-sites to generate these either. Signed-off-by: Ian Jackson --- diff --git a/.gitignore b/.gitignore index dd93a21..e73be05 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.pyc conffile.tab.[ch] conffile.yy.[ch] +pubkeys.fl +pubkeys.yy.[ch] /version.c /secnet /eax-*-test diff --git a/Dir.sd.mk b/Dir.sd.mk index 05c39e3..1dba143 100644 --- 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: diff --git a/README.make-secnet-sites b/README.make-secnet-sites index ea767e9..4fd594e 100644 --- a/README.make-secnet-sites +++ b/README.make-secnet-sites @@ -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 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; kinkeys; 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 index 0000000..19af65e --- /dev/null +++ b/pubkeys.fl.pl @@ -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 () { + last if m/^\%\%\s*$/; + if (m/^!SUBSTCHECKS\s*$/) { + foreach (keys %subst) { + $do .= <"; +} + +while () { + 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++; +} + +.*\n { + c->lno++; + BEGIN(0); +} + +<> { return 0; } + +<*>. { FAIL("syntax error"); } +<*>\n { FAIL("syntax error - unexpected newline"); } +<> { 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 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 */ diff --git a/secnet.h b/secnet.h index afc635a..7c650a9 100644 --- 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 *****/