Add simple public-key encryption program `catcrypt'.
[u/mdw/catacomb] / cc-enc.c
diff --git a/cc-enc.c b/cc-enc.c
new file mode 100644 (file)
index 0000000..f44bb33
--- /dev/null
+++ b/cc-enc.c
@@ -0,0 +1,330 @@
+/* -*-c-*-
+ *
+ * $Id: cc-enc.c,v 1.1 2004/04/17 09:58:37 mdw Exp $
+ *
+ * Catcrypt data encoding
+ *
+ * (c) 2004 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ * 
+ * Catacomb 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+
+#include <mLib/alloc.h>
+#include <mLib/base64.h>
+#include <mLib/dstr.h>
+#include <mLib/report.h>
+#include <mLib/sub.h>
+
+#include "cc.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- Binary --- */
+
+static enc *bin_init(FILE *fp, const char *msg)
+  { enc *e = CREATE(enc); return (e); }
+
+static int bin_read(enc *e, void *p, size_t sz)
+{
+  size_t n;
+  
+  if (!sz) return (0);
+  n = fread(p, 1, sz, e->fp);
+  if (!n || ferror(e->fp)) return (-1);
+  return (n);
+}
+
+static int bin_write(enc *e, const void *p, size_t sz)
+  { if (sz && fwrite(p, 1, sz, e->fp) < sz) return (-1); return (0); }
+
+static int bin_done(enc *e) { return (0); }
+
+static void bin_destroy(enc *e) { DESTROY(e); }
+
+/* --- PEM --- */
+
+typedef struct pem_encctx {
+  enc e;
+  char *msg;
+  unsigned f;
+  base64_ctx b;
+  dstr d;
+  size_t n;
+#define PEMF_NL 1u
+#define PEMF_EOF 2u
+} pem_encctx;
+
+static enc *pem_encinit(FILE *fp, const char *msg)
+{
+  pem_encctx *pe = CREATE(pem_encctx);
+  base64_init(&pe->b);
+  fprintf(fp, "-----BEGIN %s-----\n", msg);
+  pe->msg = xstrdup(msg);
+  dstr_create(&pe->d);
+  pe->n = 0;
+  pe->f = 0;
+  return (&pe->e);
+}
+
+static enc *pem_decinit(FILE *fp, const char *msg)
+{
+  char buf[128];
+  int i, d;
+  pem_encctx *pe;
+  int ch;
+
+  /* --- Go until I find a newline and `-----' --- */
+
+top:
+  d = 0;
+  for (;;) {
+    if ((ch = getc(fp)) == EOF) goto fail;
+    switch (ch) {
+      case '\n': d = 0; break;
+      case '-': if (d >= 0) { d++; if (d == 5) goto banner; }; break;
+      default: d = -1; break;
+    }
+  }
+
+  /* --- See what the banner looks like --- */
+
+banner:
+  i = d = 0;
+  for (;;) {
+    if ((ch = getc(fp)) == EOF) goto fail;
+    if (ch == '-') { d++; continue; }
+    if (ch == '\n') break;
+    if (i + d + 1 >= sizeof(buf)) goto top;
+    while (d) { buf[i++] = '-'; d--; }
+    buf[i++] = ch;
+  }
+  buf[i] = 0;
+
+  /* --- Check we have the right framing --- */
+
+  if (d != 5) goto top;
+  if (strncmp(buf, "BEGIN ", 6) != 0 ||
+      (msg && strcmp(buf + 6, msg) != 0))
+    goto top;
+
+  /* --- Ready --- */
+
+  pe = CREATE(pem_encctx);
+  base64_init(&pe->b);
+  pe->msg = xstrdup(buf + 6);
+  dstr_create(&pe->d);
+  pe->n = 0;
+  pe->f = PEMF_NL;
+  return (&pe->e);
+
+  /* --- Failed --- */
+
+fail:
+  die(EXIT_FAILURE, "initial encapsulation boundary not found");
+  return (0);
+}
+
+#define PEM_CHUNKSZ 4096
+
+static int pem_read(enc *e, void *p, size_t sz)
+{
+  pem_encctx *pe = (pem_encctx *)e;
+  char buf[PEM_CHUNKSZ];
+  char *pp = p;
+  int ch;
+  size_t n;
+  int rc = 0;
+
+  for (;;) {
+    n = pe->d.len - pe->n;
+    if (n > sz) n = sz;
+    memcpy(pp, pe->d.buf + pe->n, n);
+    pe->n += n;
+    pp += n;
+    rc += n;
+    sz -= n;
+    if (!sz) break;
+    if (pe->f & PEMF_EOF) return (rc ? rc : -1);
+    dstr_reset(&pe->d);
+    n = 0;
+    for (;;) {
+      if ((ch = getc(pe->e.fp)) == EOF) return (-1);
+      if ((pe->f & PEMF_NL) && ch == '-') {
+       ungetc(ch, pe->e.fp);
+       pe->f |= PEMF_EOF;
+       break;
+      }
+      if (ch == '\n') { pe->f |= PEMF_NL; continue; }
+      pe->f &= ~PEMF_NL;
+      buf[n++] = ch;
+      if (n >= PEM_CHUNKSZ) break;
+    }
+    if (n)
+      base64_decode(&pe->b, buf, n, &pe->d);
+    if (pe->f & PEMF_EOF)
+      base64_decode(&pe->b, 0, 0, &pe->d);
+    pe->n = 0;
+  }
+  return (rc);
+}
+
+static int pem_write(enc *e, const void *p, size_t sz)
+{
+  pem_encctx *pe = (pem_encctx *)e;
+  const char *pp = p;
+  size_t n;
+
+  while (sz) {
+    n = PEM_CHUNKSZ;
+    if (n > sz) n = sz;
+    dstr_reset(&pe->d);
+    base64_encode(&pe->b, pp, n, &pe->d);
+    if (fwrite(pe->d.buf, 1, pe->d.len, pe->e.fp) < pe->d.len)
+      return (-1);
+    pp += n;
+    sz -= n;
+  }
+  return (0);
+}
+
+static int pem_encdone(enc *e)
+{
+  pem_encctx *pe = (pem_encctx *)e;
+  dstr_reset(&pe->d);
+  base64_encode(&pe->b, 0, 0, &pe->d);
+  if (fwrite(pe->d.buf, 1, pe->d.len, pe->e.fp) < pe->d.len)
+    return (-1);
+  if (pe->b.lnlen) fputc('\n', pe->e.fp);
+  fprintf(pe->e.fp, "-----END %s-----\n", pe->msg);
+  return (0);
+}
+
+static int pem_decdone(enc *e)
+{
+  pem_encctx *pe = (pem_encctx *)e;
+  char buf[128];
+  int i, d;
+  int ch;
+
+  for (d = 0; d < 5; d++)
+    if ((ch = getc(pe->e.fp)) != '-') goto fail;
+  i = d = 0;
+  for (;;) {
+    if ((ch = getc(pe->e.fp)) == EOF) goto fail;
+    if (ch == '-') { d++; continue; }
+    if (ch == '\n') break;
+    if (i + d + 1 >= sizeof(buf)) goto fail;
+    while (d) { buf[i++] = '-'; d--; }
+    buf[i++] = ch;
+  }
+  if (d != 5) goto fail;
+  buf[i] = 0;
+  if (strncmp(buf, "END ", 4) != 0 || strcmp(buf + 4, pe->msg) != 0)
+    goto fail;
+  return (0);
+
+fail:
+  die(EXIT_FAILURE, "final encapsulation boundary not found");
+  return (-1);
+}
+
+static void pem_destroy(enc *e)
+{
+  pem_encctx *pe = (pem_encctx *)e;
+  dstr_destroy(&pe->d);
+  xfree(pe->msg);
+  DESTROY(pe);
+}
+
+/* --- Encoder table --- */
+
+static const encops enctab[] = {
+  { "binary", "rb", "wb",
+    bin_init, bin_init,
+    bin_read, bin_write,
+    bin_done, bin_done,
+    bin_destroy },
+  { "pem", "r", "w",
+    pem_encinit, pem_decinit,
+    pem_read, pem_write,
+    pem_encdone, pem_decdone,
+    pem_destroy },
+  { 0 }
+};  
+
+/* --- @getenc@ --- *
+ *
+ * Arguments:  @const char *enc@ = name of wanted encoding
+ *
+ * Returns:    Pointer to encoder operations.
+ *
+ * Use:                Finds a named encoder or decoder.
+ */
+
+const encops *getenc(const char *enc)
+{
+  const encops *eo;
+
+  for (eo = enctab; eo->name; eo++) {
+    if (strcmp(eo->name, enc) == 0)
+      goto e_found;
+  }
+  die(EXIT_FAILURE, "couldn't find encoding `%s'", enc);
+e_found:
+  return (eo);
+}
+
+/* --- @initenc@ --- *
+ *
+ * Arguments:  @const encops *eo@ = operations (from @getenc@)
+ *             @FILE *fp@ = file handle to attach
+ *             @const char *msg@ = banner message
+ *             @int wantenc@ = nonzero if we want to encode
+ *
+ * Returns:    The encoder object.
+ *
+ * Use:                Initializes an encoder.
+ */
+
+enc *initenc(const encops *eo, FILE *fp, const char *msg, int wantenc)
+{
+  enc *e = (wantenc ? eo->initenc : eo->initdec)(fp, msg);
+  e->ops = eo;
+  e->fp = fp;
+  return (e);  
+}
+
+/* --- @freeenc@ --- *
+ *
+ * Arguments:  @enc *e@ = encoder object
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees an encoder object.
+ */
+
+void freeenc(enc *e) { e->ops->destroy(e); }
+
+/*----- That's all, folks -------------------------------------------------*/