algorithms.c: Add bindings for STROBE. mdw/python3
authorMark Wooding <mdw@distorted.org.uk>
Tue, 26 Nov 2019 22:23:10 +0000 (22:23 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 11:57:34 +0000 (12:57 +0100)
algorithms.c
catacomb-python.h
t/t-algorithms.py

index be07fb7..de8fcd6 100644 (file)
@@ -3578,6 +3578,365 @@ static const PyTypeObject kmac256_pytype_skel = {
   0                                    /* @tp_is_gc@ */
 };
 
+static PyTypeObject *strobe_pytype;
+
+typedef struct strobe_pyobj {
+  PyObject_HEAD
+  strobe_ctx ctx;
+} strobe_pyobj;
+#define STROBE_CTX(o) (&((strobe_pyobj *)(o))->ctx)
+
+static int convstrbf(PyObject *x, void *p)
+{
+  unsigned *ff = p, f;
+  const char *q;
+  size_t sz;
+  int rc = 0;
+
+  if (TEXT_CHECK(x)) {
+    TEXT_PTRLEN(x, q, sz); f = 0;
+    while (sz--) switch (*q++) {
+      case 'I': f |= STRBF_I; break;
+      case 'A': f |= STRBF_A; break;
+      case 'C': f |= STRBF_C; break;
+      case 'T': f |= STRBF_T; break;
+      case 'M': f |= STRBF_M; break;
+      default: if (!ISSPACE(q[-1])) VALERR("unknown flag character");
+    }
+    *ff = f; rc = 1;
+  } else if (convuint(x, ff))
+    rc = 1;
+  else {
+    PyErr_Clear();
+    TYERR("expected string or int");
+  }
+end:
+  return (rc);
+}
+
+
+static PyObject *strobe_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  strobe_pyobj *rc = 0;
+  unsigned lambda = 128;
+  static const char *const kwlist[] = { "l", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST,
+                                  convuint, &lambda))
+    goto end;
+  if (lambda%32 || lambda > 704) VALERR("invalid security parameter");
+  rc = (strobe_pyobj *)ty->tp_alloc(ty, 0);
+  strobe_init(&rc->ctx, lambda);
+end:
+  return ((PyObject *)rc);
+}
+
+static PyObject *strobemeth_copy(PyObject *me)
+{
+  strobe_pyobj *strobe = (strobe_pyobj *)me, *rc;
+
+  rc = PyObject_NEW(strobe_pyobj, me->ob_type);
+  rc->ctx = strobe->ctx;
+  return ((PyObject *)rc);
+}
+
+static int strobe_checkinactive(PyObject *me)
+{
+  int rc = -1;
+
+  if (STROBE_CTX(me)->f&STRBF_ACTIVE) VALERR("operation already active");
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int strobe_checkactive(PyObject *me)
+{
+  int rc = -1;
+
+  if (!(STROBE_CTX(me)->f&STRBF_ACTIVE)) VALERR("operation not active");
+  rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *strobemeth_begin(PyObject *me, PyObject *arg)
+{
+  unsigned f;
+
+  if (!PyArg_ParseTuple(arg, "O&:begin", convstrbf, &f)) goto end;
+  if (strobe_checkinactive(me)) goto end;
+  if (f&~STRBF_VALIDMASK) VALERR("bad flags");
+  strobe_begin(STROBE_CTX(me), f);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *strobe_pyprocess(PyObject *me,
+                                 const void *p0, size_t sz0,
+                                 const void *p1, size_t sz1)
+{
+  char *q;
+  PyObject *rc = 0;
+
+  if (strobe_checkactive(me)) goto end;
+  if (!(STROBE_CTX(me)->f&STRBF_WANTIN) && p0)
+    VALERR("no input expected for current operation");
+  if (!(STROBE_CTX(me)->f&STRBF_WANTOUT)) q = 0;
+  else { rc = bytestring_pywrap(0, sz0 + sz1); q = BIN_PTR(rc); }
+  strobe_process(STROBE_CTX(me), p0, q, sz0);
+  if (sz1) strobe_process(STROBE_CTX(me), p1, q ? q + sz0 : 0, sz1);
+  if (!q) RETURN_ME;
+end:
+  return (rc);
+}
+
+static PyObject *strobemeth_process(PyObject *me, PyObject *arg)
+{
+  PyObject *inobj;
+  struct bin in;
+  PyObject *rc = 0;
+
+  in.p = 0;
+  if (!PyArg_ParseTuple(arg, "O:process", &inobj)) goto end;
+  if (!convszt(inobj, &in.sz) && (PyErr_Clear(), !convbin(inobj, &in))) {
+    PyErr_Clear();
+    TYERR("expected integer, string, unicode, or single-segment buffer");
+  }
+  rc = strobe_pyprocess(me, in.p, in.sz, 0, 0);
+end:
+  return (rc);
+}
+
+static PyObject *strobemeth_done(PyObject *me)
+{
+  unsigned f;
+  int rc;
+
+  if (strobe_checkactive(me)) return (0);
+  f = STROBE_CTX(me)->f; rc = strobe_done(STROBE_CTX(me));
+  if (f&STRBF_VRFOUT) return (getbool(!rc));
+  else RETURN_ME;
+}
+
+#define STROBEMETH_PROCESSU_(n, W, w)                                  \
+  static PyObject *strobemeth_processu##w(PyObject *me, PyObject *arg) \
+  {                                                                    \
+    uint##n x;                                                         \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:processu" #w, convu##n, &x)) return (0); \
+    STORE##W(b, x); return (strobe_pyprocess(me, b, sizeof(b), 0, 0)); \
+  }
+DOUINTCONV(STROBEMETH_PROCESSU_)
+
+#define STROBEMETH_PROCESSBUF_(n, W, w)                                        \
+  static PyObject *strobemeth_processbuf##w(PyObject *me, PyObject *arg) \
+  {                                                                    \
+    struct bin in;                                                     \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:processbuf" #w, convbin, &in)) goto end; \
+    if (in.sz > MASK##n) VALERR("too large");                          \
+    STORE##W(b, in.sz);                                                        \
+    return (strobe_pyprocess(me, b, sizeof(b), in.p, in.sz));          \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(STROBEMETH_PROCESSBUF_)
+
+static PyObject *strobemeth_processstrz(PyObject *me, PyObject *arg)
+{
+  char *p;
+  if (!PyArg_ParseTuple(arg, "s:processstrz", &p)) return (0);
+  return (strobe_pyprocess(me, p, strlen(p) + 1, 0, 0));
+}
+
+#define STROBEMETH_INONLY_(meth, arg)                                  \
+  static PyObject *strobemeth_##meth(PyObject *me,                     \
+                                    PyObject *arg, PyObject *kw)       \
+  {                                                                    \
+    struct bin in; unsigned f = 0;                                     \
+    static const char *const kwlist[] = { #arg, "f", 0 };              \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:" #meth, KWLIST,  \
+                                    convbin, &in, convstrbf, &f))      \
+      goto end;                                                                \
+    if (f&~STRBF_M) VALERR("bad flags");                               \
+    if (strobe_checkinactive(me)) goto end;                            \
+    strobe_##meth(STROBE_CTX(me), f, in.p, in.sz);                     \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+
+#define STROBEMETH_OUTONLY_(meth)                                      \
+  static PyObject *strobemeth_##meth(PyObject *me,                     \
+                                    PyObject *arg, PyObject *kw)       \
+  {                                                                    \
+    size_t sz; unsigned f = 0; PyObject *rc;                           \
+    static const char *const kwlist[] = { "sz", "f", 0 };              \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:" #meth, KWLIST,  \
+                         convszt, &sz, convstrbf, &f))                 \
+      goto end;                                                                \
+    if (f&~STRBF_M) VALERR("bad flags");                               \
+    if (strobe_checkinactive(me)) goto end;                            \
+    rc = bytestring_pywrap(0, sz);                                     \
+    strobe_##meth(STROBE_CTX(me), f, BIN_PTR(rc), sz);                 \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+
+#define STROBEMETH_INOUT_(meth, arg)                                   \
+  static PyObject *strobemeth_##meth(PyObject *me,                     \
+                                    PyObject *arg, PyObject *kw)       \
+  {                                                                    \
+    struct bin in; unsigned f = 0; PyObject *rc;                       \
+    static const char *const kwlist[] = { #arg, "f", 0 };              \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:" #meth, KWLIST,  \
+                                    convbin, &in, convstrbf, &f))      \
+      goto end;                                                                \
+    if (f&~STRBF_M) VALERR("bad flags");                               \
+    if (strobe_checkinactive(me)) goto end;                            \
+    rc = bytestring_pywrap(0, in.sz);                                  \
+    strobe_##meth(STROBE_CTX(me), f, in.p, BIN_PTR(rc), in.sz);                \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+
+STROBEMETH_INONLY_(key, key)
+STROBEMETH_INONLY_(ad, msg)
+STROBEMETH_OUTONLY_(prf)
+STROBEMETH_INONLY_(clrout, msg)
+STROBEMETH_INONLY_(clrin, msg)
+STROBEMETH_INOUT_(encout, msg)
+STROBEMETH_INOUT_(encin, ct)
+STROBEMETH_OUTONLY_(macout)
+
+static PyObject *strobemeth_macin(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  struct bin in; unsigned f = 0;
+  static const char *const kwlist[] = { "tag", "f", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:macin", KWLIST,
+                                  convbin, &in, convstrbf, &f))
+    goto end;
+  if (f&~STRBF_M) VALERR("bad flags");
+  if (strobe_checkinactive(me)) goto end;
+  return (getbool(!strobe_macin(STROBE_CTX(me), f, in.p, in.sz)));
+end:
+  return (0);
+}
+
+static PyObject *strobemeth_ratchet(PyObject *me,
+                                   PyObject *arg, PyObject *kw)
+{
+  size_t sz; unsigned f = 0;
+  static const char *const kwlist[] = { "sz", "f", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:ratchet", KWLIST,
+                                  convszt, &sz, convstrbf, &f))
+    goto end;
+  if (f&~STRBF_M) VALERR("bad flags");
+  if (strobe_checkinactive(me)) goto end;
+  strobe_ratchet(STROBE_CTX(me), f, sz); RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *strobeget_l(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(4*(200 - STROBE_CTX(me)->r))); }
+
+static PyObject *strobeget_role(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(STROBE_CTX(me)->f&STROBE_ROLEMASK)); }
+
+static PyObject *strobeget_activep(PyObject *me, void *hunoz)
+  { return (getbool(STROBE_CTX(me)->f&STRBF_ACTIVE)); }
+
+static const PyGetSetDef strobe_pygetset[] = {
+#define GETSETNAME(op, name) strobe##op##_##name
+  GET   (l,            "STROBE.l -> security parameter")
+  GET  (role,          "STROBE.role -> STRBRL_...")
+  GET   (activep,      "STROBE.activep -> operation active?")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef strobe_pymethods[] = {
+#define METHNAME(func) strobemeth_##func
+  NAMETH(copy,         "S.copy() -> SS")
+  METH (begin,         "S.begin(MSGTY)")
+  METH (process,       "S.process(MSG | SZ) [-> OUT]")
+#define METHU_(n, W, w)                                                        \
+    METH(processu##w,  "S.processu" #w "(WORD) [-> OUT]")
+  DOUINTCONV(METHU_)
+#undef METHU_
+#define METHBUF_(n, W, w)                                              \
+    METH(processbuf##w,        "S.processbuf" #w "(BYTES) [-> OUT]")
+  DOUINTCONV(METHBUF_)
+#undef METHBUF_
+  METH (processstrz,   "S.processstrz(STRING) [-> OUT]")
+  NAMETH(done,         "S.done() [-> OK?]")
+  KWMETH(key,          "S.key(KEY, [f = 0])")
+  KWMETH(ad,           "S.ad(MSG, [f = 0])")
+  KWMETH(prf,          "S.prf(SZ, [f = 0]) -> BYTES")
+  KWMETH(clrout,       "S.clrout(MSG, [f = 0])")
+  KWMETH(clrin,                "S.clrin(MSG, [f = 0])")
+  KWMETH(encout,       "S.encout(MSG, [f = 0]) -> CT")
+  KWMETH(encin,                "S.encin(CT, [f = 0]) -> MSG")
+  KWMETH(macout,       "S.macout(SZ, [f = 0]) -> TAG")
+  KWMETH(macin,                "S.macin(TAG, [f = 0]) -> OK?")
+  KWMETH(ratchet,      "S.ratchet(SZ, [f = 0])")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject strobe_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Strobe",                            /* @tp_name@ */
+  sizeof(strobe_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+"STROBE symmetric-crypto framework.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(strobe),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(strobe),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  strobe_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
 /*----- Pseudorandom permutations -----------------------------------------*/
 
 static PyTypeObject *gcprp_pytype, *gprp_pytype;
@@ -3824,6 +4183,11 @@ static const PyTypeObject gprp_pytype_skel = {
 static const struct nameval consts[] = {
   CONST(AEADF_PCHSZ), CONST(AEADF_PCMSZ), CONST(AEADF_PCTSZ),
   CONST(AEADF_AADNDEP), CONST(AEADF_AADFIRST), CONST(AEADF_NOAAD),
+  CONST(STRBF_I), CONST(STRBF_A), CONST(STRBF_C), CONST(STRBF_T),
+  CONST(STRBF_M), CONST(STROBE_KEY), CONST(STROBE_AD), CONST(STROBE_PRF),
+  CONST(STROBE_CLROUT), CONST(STROBE_CLRIN), CONST(STROBE_ENCOUT),
+  CONST(STROBE_ENCIN), CONST(STRBRL_UNDCD), CONST(STRBRL_INIT),
+  CONST(STRBRL_RESP),
   { 0 }
 };
 
@@ -3873,6 +4237,7 @@ void algorithms_pyinit(void)
   INITTYPE(kmac, shake);
   INITTYPE(kmac128, kmac);
   INITTYPE(kmac256, kmac);
+  INITTYPE(strobe, root);
   INITTYPE(gcprp, type);
   INITTYPE(gprp, root);
   addmethods(methods);
@@ -3929,6 +4294,7 @@ void algorithms_pyinsert(PyObject *mod)
   INSERT("KMAC", kmac_pytype);
   INSERT("KMAC128", kmac128_pytype);
   INSERT("KMAC256", kmac256_pytype);
+  INSERT("Strobe", strobe_pytype);
   INSERT("GCPRP", gcprp_pytype);
   INSERT("GPRP", gprp_pytype);
   INSERT("gcprps", make_algtab(gprptab, sizeof(gcprp *),
index 6128cc8..e74e048 100644 (file)
@@ -70,6 +70,7 @@ PUBLIC_SYMBOLS;
 #include <catacomb/sha-hmac.h>
 #include <catacomb/keccak1600.h>
 #include <catacomb/sha3.h>
+#include <catacomb/strobe.h>
 
 #include <catacomb/mp.h>
 #include <catacomb/mpint.h>
index 856470a..fd239fb 100644 (file)
@@ -845,6 +845,96 @@ class TestKeccak (HashBufferTestMixin):
   def test_kmac256(me): me.check_kmac(C.KMAC256, 64)
 
 ###--------------------------------------------------------------------------
+class TestStrobe (HashBufferTestMixin):
+
+  HASHMETH = "process"
+
+  def test_strobe(me):
+
+    ## Construction.
+    s0 = C.Strobe()
+    s1 = C.Strobe(128)
+    me.assertEqual(s0.l, 128)
+    me.assertEqual(s1.l, 128)
+    C.Strobe(704)
+    me.assertRaises(ValueError, C.Strobe, 127)
+    me.assertRaises(ValueError, C.Strobe, 736)
+    me.assertEqual(s0.role, C.STRBRL_UNDCD)
+    me.assertEqual(s1.role, C.STRBRL_UNDCD)
+
+    ## `process' vs operation-specific functions.  (This follows Hamburg's
+    ## `Concurrence' test.)
+    h = T.bin("testing")
+    s0.ad(h, f = "M"); s1.begin(C.STROBE_AD | C.STRBF_M).process(h).done()
+
+    t = s1.begin(C.STRBF_I | C.STRBF_A | C.STRBF_C).process(10); s1.done()
+    me.assertEqual(s0.prf(10), t)
+    me.assertEqual(t, C.bytes("8a13a189683bf5678170"))
+
+    h = T.bin("Hello")
+    s0.ad(h); s1.begin(" A   ").process(h).done()
+
+    m = T.bin("World"); c = s0.encout(m)
+    me.assertFalse(s1.activep)
+    m1 = s1.begin("IACT ").process(c); me.assertTrue(s1.activep);
+    s1.done(); me.assertFalse(s1.activep)
+    me.assertEqual(c, C.bytes("123bfbee34"))
+    me.assertEqual(m1, m)
+    me.assertEqual(s0.role, C.STRBRL_INIT)
+    me.assertEqual(s1.role, C.STRBRL_RESP)
+
+    m = T.bin("foo"); s0.clrout(m); s1.begin("IA T ").process(m).done()
+    m = T.bin("bar"); s0.clrin(m);  s1.begin(" A T ").process(m).done()
+
+    c = T.bin("baz"); m = s0.encin(c)
+    c1 = s1.begin(" ACT ").process(m); s1.done()
+    me.assertEqual(m, C.bytes("15e518"))
+    me.assertEqual(c1, c)
+
+    xxx = T.bin(199*"X")
+    for i in T.range(200):
+      c = s0.begin(" ACT ").process(xxx[:i]); s0.done(); s1.encin(c)
+
+    t = s1.begin("IAC  ").process(123); s1.done()
+    me.assertEqual(t, C.bytes
+      ("45476fc0806aee35e864c4f18e6ba62bd3eb1b1e8bef9042b30b0f15d00c3e9f"
+       "5d5904ab789d4c67eaed582473c15aa4424f11d52b21a296b36db3392e2ecbb2"
+       "dc6963bafba3b23882d061f1d335e86e470e8d819591bf0c223e24b925751d04"
+       "f789fc73bc55f7d2b3ed4881c625aa6321d31511b13f6d5e4ce54a"))
+    me.assertEqual(s0.prf(123), t)
+
+    ## Copying and MAC.
+    s2 = s0.copy()
+    t = s0.macout(16)
+    me.assertEqual(t, C.bytes("171419608e11e7c907d493209e17f26b"))
+    me.assertEqual(s2.begin("  CT ").process(16), t); s2.done()
+    s3 = s1.copy(); me.assertFalse(s3.macin(~t))
+    s3 = s1.copy(); me.assertTrue(s3.macin(t))
+    s3 = s1.copy(); me.assertFalse(s3.begin("I CT ").process(~t).done())
+    me.assertTrue(s1.begin("I CT ").process(t).done())
+
+    ## Test the remaining operations.  (From the Catacomb test vectors.)
+    k = T.bin("this is my super-secret key")
+    s0.key(k); s1.begin(" AC   ").process(k).done()
+
+    t = s1.begin(C.STROBE_PRF).process(32); s1.done()
+    me.assertEqual(t, C.bytes
+      ("5418e0f0ee7f982bbbdc2b4bbf49425d0088abfa98ee21d8ad8a3610d15ebb68"))
+    me.assertEqual(s0.prf(32), t)
+
+    s0.ratchet(32); s1.begin("C").process(32).done()
+
+    t = s1.begin("  CT ").process(16); s1.done()
+    me.assertEqual(t, C.bytes("b2084ebdfabd50768c91eebc190132cc"))
+    me.assertTrue(s0.macin(t))
+
+  def test_hashbuffer(me):
+    def mkhash(hsz):
+      s = C.Strobe(256).begin(C.STROBE_AD)
+      return s, lambda: s.done().macout(16)
+    me.check_hashbuffer(mkhash)
+
+###--------------------------------------------------------------------------
 class TestPRP (T.GenericTestMixin):
   """Test pseudorandom permutations (PRPs)."""