From a29f54261b7727506f73fa9e89fddf6603c609cc Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Tue, 26 Nov 2019 22:23:10 +0000 Subject: [PATCH] algorithms.c: Add bindings for STROBE. --- algorithms.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ catacomb-python.h | 1 + t/t-algorithms.py | 90 ++++++++++++++ 3 files changed, 457 insertions(+) diff --git a/algorithms.c b/algorithms.c index be07fb7..de8fcd6 100644 --- a/algorithms.c +++ b/algorithms.c @@ -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 *), diff --git a/catacomb-python.h b/catacomb-python.h index 6128cc8..e74e048 100644 --- a/catacomb-python.h +++ b/catacomb-python.h @@ -70,6 +70,7 @@ PUBLIC_SYMBOLS; #include #include #include +#include #include #include diff --git a/t/t-algorithms.py b/t/t-algorithms.py index 856470a..fd239fb 100644 --- a/t/t-algorithms.py +++ b/t/t-algorithms.py @@ -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).""" -- 2.11.0