From: Mark Wooding Date: Tue, 7 Apr 2020 23:56:01 +0000 (+0100) Subject: Merge branch '1.2.x' into 1.3.x X-Git-Url: https://git.distorted.org.uk/~mdw/catacomb-python/commitdiff_plain/2a3f4da1c95d71e6045ddf1617754bacddfd46c3?hp=e2a9e88ceb3fdbf9904366e7b3d2f87a9bcea9d7 Merge branch '1.2.x' into 1.3.x * 1.2.x: rand.c: More `Py_ssize_t' fixes. --- diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..96fe7ad --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Mark Wooding diff --git a/MANIFEST.in b/MANIFEST.in index c09417b..33ce54a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -### Manifest template for Catacomb/Python. +### Manifest template for Catacomb/Python. -*-conf-*- ## Generated build machinery. include COPYING auto-version mdwsetup.py pysetup.mk @@ -11,8 +11,14 @@ include *.c *.h include t/*.py t/keyring include algorithms.py exclude algorithms.h + +## Scripts. +include pock include pwsafe +## Manual pages. +include *.[13] + ## Python wrapping. recursive-include catacomb *.py diff --git a/algorithms.c b/algorithms.c index edd73fd..064fb03 100644 --- a/algorithms.c +++ b/algorithms.c @@ -83,11 +83,11 @@ PyObject *keysz_pywrap(const octet *k) static PyObject *keyszany_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "default", 0 }; + static const char *const kwlist[] = { "default", 0 }; int dfl; keysz_pyobj *o; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "i:new", kwlist, &dfl)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "i:new", KWLIST, &dfl)) goto end; if (dfl < 0) VALERR("key size cannot be negative"); o = (keysz_pyobj *)ty->tp_alloc(ty, 0); @@ -100,18 +100,16 @@ end: static PyObject *keyszrange_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "default", "min", "max", "mod", 0 }; + static const char *const kwlist[] = { "default", "min", "max", "mod", 0 }; int dfl, min = 0, max = 0, mod = 1; keyszrange_pyobj *o; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|iii:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|iii:new", KWLIST, &dfl, &min, &max, &mod)) goto end; - if (dfl < 0 || min < 0 || max < 0) - VALERR("key size cannot be negative"); - if (min > dfl || (max && dfl > max)) - VALERR("bad key size bounds"); - if (mod <= 0 || dfl % mod || min % mod || max % mod) + if (dfl < 0 || min < 0) VALERR("key size cannot be negative"); + if (min > dfl || (max && dfl > max)) VALERR("bad key size bounds"); + if (mod <= 0 || dfl%mod || min%mod || max%mod) VALERR("bad key size modulus"); o = (keyszrange_pyobj *)ty->tp_alloc(ty, 0); o->dfl = dfl; @@ -126,14 +124,13 @@ end: static PyObject *keyszset_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "default", "set", 0 }; + static const char *const kwlist[] = { "default", "set", 0 }; int dfl, i, n, xx; PyObject *set = 0; PyObject *x = 0, *l = 0; keyszset_pyobj *o = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|O:new", kwlist, - &dfl, &set)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|O:new", KWLIST, &dfl, &set)) goto end; if (!set) set = PyTuple_New(0); else Py_INCREF(set); @@ -263,7 +260,7 @@ static PyTypeObject keysz_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key size constraints.", +"Key size constraints. Abstract.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -311,7 +308,8 @@ static PyTypeObject keyszany_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key size constraints. This object imposes no constraints on size.", +"KeySZAny(DEFAULT)\n\ + Key size constraints. This object imposes no constraints on size.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -359,8 +357,9 @@ static PyTypeObject keyszrange_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key size constraints. This object asserts minimum and maximum (if\n\ -sizes, and requires the key length to be a multiple of some value.", +"KeySZRange(DEFAULT, [min = 0], [max = 0], [mod = 1])\n\ + Key size constraints. Key size must be between MIN and MAX inclusive,\n\ + and be a multiple of MOD.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -408,8 +407,8 @@ static PyTypeObject keyszset_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key size constraints. This object requires the key to be one of a\n\ -few listed sizes.", +"KeySZSet(DEFAULT, SEQ)\n\ + Key size constraints. Key size must be DEFAULT or one in SEQ.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -457,29 +456,27 @@ PyTypeObject *gccipher_pytype, *gcipher_pytype; CONVFUNC(gccipher, gccipher *, GCCIPHER_CC) CONVFUNC(gcipher, gcipher *, GCIPHER_C) -PyObject *gcipher_pywrap(PyObject *cobj, gcipher *c, unsigned f) +PyObject *gcipher_pywrap(PyObject *cobj, gcipher *c) { gcipher_pyobj *g; if (!cobj) cobj = gccipher_pywrap((/*unconst*/ gccipher *)GC_CLASS(c)); else Py_INCREF(cobj); g = PyObject_NEW(gcipher_pyobj, (PyTypeObject *)cobj); g->c = c; - g->f = f; return ((PyObject *)g); } static PyObject *gcipher_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "k", 0 }; + static const char *const kwlist[] = { "k", 0 }; char *k; Py_ssize_t sz; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &k, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &sz)) goto end; if (keysz(sz, GCCIPHER_CC(ty)->keysz) != sz) VALERR("bad key length"); return (gcipher_pywrap((PyObject *)ty, - GC_INIT(GCCIPHER_CC(ty), k, sz), - f_freeme)); + GC_INIT(GCCIPHER_CC(ty), k, sz))); end: return (0); } @@ -503,8 +500,7 @@ PyObject *gccipher_pywrap(gccipher *cc) static void gcipher_pydealloc(PyObject *me) { - if (GCIPHER_F(me) & f_freeme) - GC_DESTROY(GCIPHER_C(me)); + GC_DESTROY(GCIPHER_C(me)); Py_DECREF(me->ob_type); FREEOBJ(me); } @@ -713,6 +709,1085 @@ static PyTypeObject gcipher_pytype_skel = { 0 /* @tp_is_gc@ */ }; +/*----- Authenticated encryption ------------------------------------------*/ + +PyTypeObject *gcaead_pytype, *gaeadkey_pytype; +PyTypeObject *gcaeadaad_pytype, *gaeadaad_pytype; +PyTypeObject *gcaeadenc_pytype, *gaeadenc_pytype; +PyTypeObject *gcaeaddec_pytype, *gaeaddec_pytype; + +CONVFUNC(gcaead, gcaead *, GCAEAD_AEC) +CONVFUNC(gaeadkey, gaead_key *, GAEADKEY_K) + +PyObject *gaeadkey_pywrap(PyObject *cobj, gaead_key *k) +{ + gaeadkey_pyobj *gk; + + if (!cobj) cobj = gcaead_pywrap((/*unconst*/ gcaead *)GAEAD_CLASS(k)); + else Py_INCREF(cobj); + gk = PyObject_NEW(gaeadkey_pyobj, (PyTypeObject *)cobj); + gk->k = k; + return ((PyObject *)gk); +} + +static PyObject *gaeadkey_pynew(PyTypeObject *ty, + PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "k", 0 }; + char *k; + Py_ssize_t sz; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &sz)) + goto end; + if (keysz(sz, GCAEAD_AEC(ty)->keysz) != sz) VALERR("bad key length"); + return (gaeadkey_pywrap((PyObject *)ty, + GAEAD_KEY(GCAEAD_AEC(ty), k, sz))); +end: + return (0); +} + +PyObject *gcaead_pywrap(gcaead *aec) +{ + gcaead_pyobj *gck; + gcaeadaad_pyobj *gca; + gcaeadenc_pyobj *gce; + gcaeaddec_pyobj *gcd; + +#define MKTYPE(obj, thing, newfn, namefmt) do { \ + (obj) = newtype(gcaead_pytype, 0, 0); \ + (obj)->ty.ht_name = PyString_FromFormat(namefmt, aec->name); \ + (obj)->ty.ht_type.tp_name = PyString_AS_STRING((obj)->ty.ht_name); \ + (obj)->ty.ht_type.tp_basicsize = sizeof(gaead##thing##_pyobj); \ + (obj)->ty.ht_type.tp_base = gaead##thing##_pytype; \ + Py_INCREF(gaead##thing##_pytype); \ + (obj)->ty.ht_type.tp_flags = \ + (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE); \ + (obj)->ty.ht_type.tp_alloc = PyType_GenericAlloc; \ + (obj)->ty.ht_type.tp_free = 0; \ + (obj)->ty.ht_type.tp_new = newfn; \ + typeready(&(obj)->ty.ht_type); \ +} while (0) + + MKTYPE(gck, key, gaeadkey_pynew, "%s(key)"); + MKTYPE(gca, aad, abstract_pynew, "%s(aad-hash)"); + MKTYPE(gce, enc, abstract_pynew, "%s(encrypt)"); + MKTYPE(gcd, dec, abstract_pynew, "%s(decrypt)"); + +#undef MKTYPE + + gck->aec = aec; gck->aad = gca; gck->enc = gce; gck->dec = gcd; + gca->key = gce->key = gcd->key = gck; + return ((PyObject *)gck); +} + +static void gaeadkey_pydealloc(PyObject *me) + { GAEAD_DESTROY(GAEADKEY_K(me)); Py_DECREF(me->ob_type); FREEOBJ(me); } + +static PyObject *gcaeget_name(PyObject *me, void *hunoz) + { return (PyString_FromString(GCAEAD_AEC(me)->name)); } + +static PyObject *gcaeget_keysz(PyObject *me, void *hunoz) + { return (keysz_pywrap(GCAEAD_AEC(me)->keysz)); } + +static PyObject *gcaeget_noncesz(PyObject *me, void *hunoz) + { return (keysz_pywrap(GCAEAD_AEC(me)->noncesz)); } + +static PyObject *gcaeget_tagsz(PyObject *me, void *hunoz) + { return (keysz_pywrap(GCAEAD_AEC(me)->tagsz)); } + +static PyObject *gcaeget_blksz(PyObject *me, void *hunoz) + { return (PyInt_FromLong(GCAEAD_AEC(me)->blksz)); } + +static PyObject *gcaeget_bufsz(PyObject *me, void *hunoz) + { return (PyInt_FromLong(GCAEAD_AEC(me)->bufsz)); } + +static PyObject *gcaeget_ohd(PyObject *me, void *hunoz) + { return (PyInt_FromLong(GCAEAD_AEC(me)->ohd)); } + +static PyObject *gcaeget_flags(PyObject *me, void *hunoz) + { return (PyInt_FromLong(GCAEAD_AEC(me)->f)); } + +static PyGetSetDef gcaead_pygetset[] = { +#define GETSETNAME(op, name) gcae##op##_##name + GET (keysz, "AEC.keysz -> acceptable key sizes") + GET (noncesz, "AEC.noncesz -> acceptable nonce sizes") + GET (tagsz, "AEC.tagsz -> acceptable tag sizes") + GET (blksz, "AEC.blksz -> block size, or zero") + GET (bufsz, "AEC.bufsz -> amount of data buffered internally") + GET (ohd, "AEC.ohd -> maximum encryption overhead") + GET (name, "AEC.name -> name of this kind of AEAD scheme") + GET (flags, "AEC.flags -> mask of `AEADF_...' flags") +#undef GETSETNAME + { 0 } +}; + +static PyObject *gaekmeth_aad(PyObject *me, PyObject *arg) +{ + const gaead_key *k = GAEADKEY_K(me); + PyObject *rc = 0; + + if (!PyArg_ParseTuple(arg, ":aad")) return (0); + if (k->ops->c->f&AEADF_AADNDEP) + VALERR("aad must be associated with enc/dec op"); + rc = gaeadaad_pywrap((PyObject *)GCAEAD_AAD(me->ob_type), + GAEAD_AAD(k), 0, 0); +end: + return (rc); +} + +static int check_aead_encdec(const gcaead *aec, unsigned *f_out, size_t nsz, + PyObject *hszobj, size_t *hsz_out, + PyObject *mszobj, size_t *msz_out, + PyObject *tszobj, size_t *tsz_out) +{ + unsigned f = 0, miss; + int rc = -1; + + if (hszobj != Py_None) + { f |= AEADF_PCHSZ; if (!convszt(hszobj, hsz_out)) goto end; } + if (mszobj != Py_None) + { f |= AEADF_PCMSZ; if (!convszt(mszobj, msz_out)) goto end; } + if (tszobj != Py_None) + { f |= AEADF_PCTSZ; if (!convszt(tszobj, tsz_out)) goto end; } + miss = aec->f&~f; + if (miss&AEADF_PCHSZ) VALERR("header length precommitment required"); + if (miss&AEADF_PCMSZ) VALERR("message length precommitment required"); + if (miss&AEADF_PCTSZ) VALERR("tag length precommitment required"); + if (keysz(nsz, aec->noncesz) != nsz) VALERR("bad nonce length"); + if (tszobj != Py_None && keysz(*tsz_out, aec->tagsz) != *tsz_out) + VALERR("bad tag length"); + *f_out = f | aec->f; rc = 0; +end: + return (rc); +} + +static PyObject *gaekmeth_enc(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "nonce", "hsz", "msz", "tsz", 0 }; + const gaead_key *k = GAEADKEY_K(me); + gaead_enc *e; + PyObject *rc = 0; + char *n; Py_ssize_t nsz; + PyObject *hszobj = Py_None, *mszobj = Py_None, *tszobj = Py_None; + size_t hsz = 0, msz = 0, tsz = 0; + unsigned f; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|OOO:enc", KWLIST, + &n, &nsz, &hszobj, &mszobj, &tszobj)) + goto end; + if (check_aead_encdec(k->ops->c, &f, nsz, + hszobj, &hsz, mszobj, &msz, tszobj, &tsz)) + goto end; + e = GAEAD_ENC(GAEADKEY_K(me), n, nsz, hsz, msz, tsz); + if (!e) VALERR("bad aead parameter combination"); + rc = gaeadenc_pywrap((PyObject *)GCAEAD_ENC(me->ob_type), + e, f, hsz, msz, tsz); +end: + return (rc); +} + +static PyObject *gaekmeth_dec(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "nonce", "hsz", "csz", "tsz", 0 }; + const gaead_key *k = GAEADKEY_K(me); + gaead_dec *d; + PyObject *rc = 0; + char *n; Py_ssize_t nsz; + PyObject *hszobj = Py_None, *cszobj = Py_None, *tszobj = Py_None; + size_t hsz = 0, csz = 0, tsz = 0; + unsigned f; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|OOO:dec", KWLIST, + &n, &nsz, &hszobj, &cszobj, &tszobj)) + goto end; + if (check_aead_encdec(k->ops->c, &f, nsz, + hszobj, &hsz, cszobj, &csz, tszobj, &tsz)) + goto end; + d = GAEAD_DEC(GAEADKEY_K(me), n, nsz, hsz, csz, tsz); + if (!d) VALERR("bad aead parameter combination"); + rc = gaeaddec_pywrap((PyObject *)GCAEAD_DEC(me->ob_type), + d, f, hsz, csz, tsz); +end: + return (rc); +} + +static PyMethodDef gaeadkey_pymethods[] = { +#define METHNAME(name) gaekmeth_##name + METH (aad, "KEY.aad() -> AAD") + KWMETH(enc, "KEY.enc(NONCE, [hsz], [msz], [tsz]) -> ENC") + KWMETH(dec, "KEY.dec(NONCE, [hsz], [csz], [tsz]) -> DEC") +#undef METHNAME + { 0 } +}; + +PyObject *gaeadaad_pywrap(PyObject *cobj, gaead_aad *a, + unsigned f, size_t hsz) +{ + gaeadaad_pyobj *ga; + + assert(cobj); Py_INCREF(cobj); + ga = PyObject_NEW(gaeadaad_pyobj, (PyTypeObject *)cobj); + ga->a = a; ga->f = f; ga->hsz = hsz; ga->hlen = 0; + return ((PyObject *)ga); +} + +static void gaeadaad_pydealloc(PyObject *me) +{ + gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me; + + if (ga->a) GAEAD_DESTROY(ga->a); + Py_DECREF(me->ob_type); FREEOBJ(me); +} + +static int gaea_check(PyObject *me) +{ + gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me; + int rc = -1; + + if ((ga->f&AEADF_DEAD) || !ga->a) VALERR("aad object no longer active"); + rc = 0; +end: + return (rc); +} + +static void gaea_invalidate(gaeadaad_pyobj *ga) + { if (ga) ga->f |= AEADF_DEAD; } + +static void gaea_sever(gaeadaad_pyobj **ga_inout) +{ + gaeadaad_pyobj *ga = *ga_inout; + if (ga) { ga->f |= AEADF_DEAD; ga->a = 0; Py_DECREF(ga); *ga_inout = 0; } +} + +static PyObject *gaeaget_hsz(PyObject *me, void *hunoz) +{ + if (gaea_check(me)) return (0); + else if (GAEADAAD_F(me)&AEADF_PCHSZ) return getulong(GAEADAAD_HSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaeaget_hlen(PyObject *me, void *hunoz) + { return (gaea_check(me) ? 0 : getulong(GAEADAAD_HLEN(me))); } + +static PyGetSetDef gaeadaad_pygetset[] = { +#define GETSETNAME(op, name) gaea##op##_##name + GET (hsz, "AAD.hsz -> precommitted header length or `None'") + GET (hlen, "AAD.hlen -> header length so far") +#undef GETSETNAME + { 0 } +}; + +static PyObject *gaeameth_copy(PyObject *me, PyObject *arg) +{ + PyObject *rc = 0; + + if (!PyArg_ParseTuple(arg, ":copy")) goto end; + if (gaea_check(me)) goto end; + if (GAEADAAD_F(me)&AEADF_AADNDEP) + VALERR("can't duplicate nonce-dependent aad"); + rc = gaeadaad_pywrap((PyObject *)me->ob_type, + GAEAD_DUP(GAEADAAD_A(me)), 0, 0); + GAEADAAD_HLEN(rc) = GAEADAAD_HLEN(me); +end: + return (rc); +} + +static int gaeadaad_hash(PyObject *me, const void *h, size_t hsz) +{ + gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me; + int rc = -1; + + if (gaea_check(me)) goto end; + if ((ga->f&AEADF_NOAAD) && hsz) + VALERR("header data not permitted"); + if ((ga->f&AEADF_PCHSZ) && hsz > ga->hsz - ga->hlen) + VALERR("too large for precommitted header length"); + GAEAD_HASH(ga->a, h, hsz); ga->hlen += hsz; + rc = 0; +end: + return (rc); +} + + +static PyObject *gaeameth_hash(PyObject *me, PyObject *arg) +{ + char *h; Py_ssize_t hsz; + + if (!PyArg_ParseTuple(arg, "s#:hash", &h, &hsz)) return (0); + if (gaeadaad_hash(me, h, hsz)) return (0); + RETURN_ME; +} + +#define GAEAMETH_HASHU_(n, W, w) \ + static PyObject *gaeameth_hashu##w(PyObject *me, PyObject *arg) \ + { \ + uint##n x; octet b[SZ_##W]; \ + if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \ + STORE##W(b, x); if (gaeadaad_hash(me, b, sizeof(b))) return (0); \ + RETURN_ME; \ + } +DOUINTCONV(GAEAMETH_HASHU_) + +#define GAEAMETH_HASHBUF_(n, W, w) \ + static PyObject *gaeameth_hashbuf##w(PyObject *me, PyObject *arg) \ + { \ + char *p; Py_ssize_t sz; octet b[SZ_##W]; \ + if (!PyArg_ParseTuple(arg, "s#:hashbuf" #w, &p, &sz)) goto end; \ + if (sz > MASK##n) TYERR("string too long"); \ + STORE##W(b, sz); if (gaeadaad_hash(me, b, sizeof(b))) goto end; \ + if (gaeadaad_hash(me, p, sz)) goto end; \ + RETURN_ME; \ + end: \ + return (0); \ + } +DOUINTCONV(GAEAMETH_HASHBUF_) + +static PyObject *gaeameth_hashstrz(PyObject *me, PyObject *arg) +{ + char *p; + if (!PyArg_ParseTuple(arg, "s:hashstrz", &p)) return (0); + if (gaeadaad_hash(me, p, strlen(p) + 1)) return (0); + RETURN_ME; +} + +static PyMethodDef gaeadaad_pymethods[] = { +#define METHNAME(name) gaeameth_##name + METH (copy, "AAD.copy() -> AAD'") + METH (hash, "AAD.hash(H)") +#define METHU_(n, W, w) METH(hashu##w, "AAD.hashu" #w "(WORD)") + DOUINTCONV(METHU_) +#undef METHU_ +#define METHBUF_(n, W, w) METH(hashbuf##w, "AAD.hashbuf" #w "(BYTES)") + DOUINTCONV(METHBUF_) +#undef METHBUF_ + METH (hashstrz, "AAD.hashstrz(STRING)") +#undef METHNAME + { 0 } +}; + +PyObject *gaeadenc_pywrap(PyObject *cobj, gaead_enc *e, unsigned f, + size_t hsz, size_t msz, size_t tsz) +{ + gaeadenc_pyobj *ge; + + assert(cobj); Py_INCREF(cobj); + ge = PyObject_NEW(gaeadenc_pyobj, (PyTypeObject *)cobj); + ge->e = e; ge->f = f; ge->hsz = hsz; ge->msz = msz; ge->tsz = tsz; + ge->aad = 0; ge->mlen = 0; + return ((PyObject *)ge); +} + +static void gaeadenc_pydealloc(PyObject *me) +{ + gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me; + + gaea_sever(&ge->aad); GAEAD_DESTROY(ge->e); + Py_DECREF(me->ob_type); FREEOBJ(me); +} + +static PyObject *gaeeget_hsz(PyObject *me, void *hunoz) +{ + if (GAEADENC_F(me)&AEADF_PCHSZ) return getulong(GAEADENC_HSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaeeget_msz(PyObject *me, void *hunoz) +{ + if (GAEADENC_F(me)&AEADF_PCMSZ) return getulong(GAEADENC_MSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaeeget_tsz(PyObject *me, void *hunoz) +{ + if (GAEADENC_F(me)&AEADF_PCTSZ) return getulong(GAEADENC_TSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaeeget_mlen(PyObject *me, void *hunoz) + { return getulong(GAEADENC_MLEN(me)); } + +static PyGetSetDef gaeadenc_pygetset[] = { +#define GETSETNAME(op, name) gaee##op##_##name + GET (hsz, "ENC.hsz -> precommitted header length or `None'") + GET (msz, "ENC.msz -> precommitted message length or `None'") + GET (tsz, "ENC.tsz -> precommitted tag length or `None'") + GET (mlen, "ENC.mlen -> message length so far") +#undef GETSETNAME + { 0 } +}; + +static PyObject *gaeemeth_aad(PyObject *me, PyObject *arg) +{ + gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me; + PyObject *rc = 0; + + if (!PyArg_ParseTuple(arg, ":aad")) return (0); + if (!(ge->f&AEADF_AADNDEP)) + rc = gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(ge->ob_type)->aad, + GAEAD_AAD(ge->e), 0, 0); + else { + if ((ge->f&AEADF_AADFIRST) && ge->mlen) + VALERR("too late for aad"); + if (!ge->aad) + ge->aad = (gaeadaad_pyobj *) + gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(ge->ob_type)->aad, + GAEAD_AAD(ge->e), ge->f&(AEADF_PCHSZ | AEADF_NOAAD), + ge->hsz); + Py_INCREF(ge->aad); + rc = (PyObject *)ge->aad; + } +end: + return (rc); +} + +static PyObject *gaeemeth_reinit(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "nonce", "hsz", "msz", "tsz", 0 }; + gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me; + char *n; Py_ssize_t nsz; + PyObject *hszobj = Py_None, *mszobj = Py_None, *tszobj = Py_None; + size_t hsz = 0, msz = 0, tsz = 0; + unsigned f; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|OOO:enc", KWLIST, + &n, &nsz, &hszobj, &mszobj, &tszobj)) + goto end; + if (check_aead_encdec(ge->e->ops->c, &f, nsz, + hszobj, &hsz, mszobj, &msz, tszobj, &tsz)) + goto end; + if (GAEAD_REINIT(ge->e, n, nsz, hsz, msz, tsz)) + VALERR("bad aead parameter combination"); + gaea_sever(&ge->aad); + ge->f = f; ge->hsz = hsz; ge->msz = msz; ge->tsz = tsz; +end: + return (0); +} + +static PyObject *gaeemeth_encrypt(PyObject *me, PyObject *arg) +{ + gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me; + char *m; Py_ssize_t msz; + char *c = 0; size_t csz; buf b; + int err; + PyObject *rc = 0; + + if (!PyArg_ParseTuple(arg, "s#:encrypt", &m, &msz)) goto end; + if (ge->f&AEADF_AADFIRST) { + if ((ge->f&AEADF_PCHSZ) && (ge->aad ? ge->aad->hlen : 0) != ge->hsz) + VALERR("header doesn't match precommitted length"); + gaea_invalidate(ge->aad); + } + if ((ge->f&AEADF_PCMSZ) && msz > ge->msz - ge->mlen) + VALERR("too large for precommitted message length"); + csz = msz + ge->e->ops->c->bufsz; c = xmalloc(csz); buf_init(&b, c, csz); + err = GAEAD_ENCRYPT(ge->e, m, msz, &b); assert(!err); (void)err; + buf_flip(&b); rc = bytestring_pywrapbuf(&b); ge->mlen += msz; +end: + xfree(c); + return (rc); +} + +static PyObject *gaeemeth_done(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "tsz", "aad", 0 }; + gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me; + PyObject *aad = Py_None; + char *c = 0; size_t csz; buf b; + PyObject *tszobj = Py_None; PyObject *tag; size_t tsz; + int err; + PyObject *rc = 0; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:done", KWLIST, + &tszobj, &aad)) + goto end; + if (tszobj != Py_None && !convszt(tszobj, &tsz)) goto end; + if (aad != Py_None && + !PyObject_TypeCheck(aad, + (PyTypeObject *)GCAEADENC_KEY(me->ob_type)->aad)) + TYERR("wanted aad"); + if ((ge->f&AEADF_AADNDEP) && aad != Py_None && aad != (PyObject *)ge->aad) + VALERR("mismatched aad"); + if ((ge->f&AEADF_PCHSZ) && + (aad == Py_None ? 0 : GAEADAAD_HLEN(aad)) != ge->hsz) + VALERR("header doesn't match precommitted length"); + if ((ge->f&AEADF_PCMSZ) && ge->mlen != ge->msz) + VALERR("message doesn't match precommitted length"); + if (tszobj == Py_None) { + if (ge->f&AEADF_PCTSZ) tsz = ge->tsz; + else tsz = keysz(0, ge->e->ops->c->tagsz); + } else { + if ((ge->f&AEADF_PCTSZ) && tsz != ge->tsz) + VALERR("tag length doesn't match precommitted value"); + if (keysz(tsz, ge->e->ops->c->tagsz) != tsz) VALERR("bad tag length"); + } + csz = ge->e->ops->c->bufsz; c = xmalloc(csz); buf_init(&b, c, csz); + tag = bytestring_pywrap(0, tsz); + err = GAEAD_DONE(ge->e, aad == Py_None ? 0 : GAEADAAD_A(aad), &b, + PyString_AS_STRING(tag), tsz); + assert(!err); (void)err; + buf_flip(&b); rc = Py_BuildValue("NN", bytestring_pywrapbuf(&b), tag); +end: + xfree(c); + return (rc); +} + +static PyMethodDef gaeadenc_pymethods[] = { +#define METHNAME(name) gaeemeth_##name + METH (aad, "ENC.aad() -> AAD") + KWMETH(reinit, "ENC.reinit(NONCE, [hsz], [msz], [tsz])") + METH (encrypt, "ENC.encrypt(MSG) -> CT") + KWMETH(done, "ENC.done([tsz], [aad]) -> CT, TAG") +#undef METHNAME + { 0 } +}; + +PyObject *gaeaddec_pywrap(PyObject *cobj, gaead_dec *d, unsigned f, + size_t hsz, size_t csz, size_t tsz) +{ + gaeaddec_pyobj *gd; + assert(cobj); Py_INCREF(cobj); + gd = PyObject_NEW(gaeaddec_pyobj, (PyTypeObject *)cobj); + gd->d = d; gd->f = f; gd->hsz = hsz; gd->csz = csz; gd->tsz = tsz; + gd->aad = 0; gd->clen = 0; + return ((PyObject *)gd); +} + +static void gaeaddec_pydealloc(PyObject *me) +{ + gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me; + + gaea_sever(&gd->aad); GAEAD_DESTROY(GAEADDEC_D(me)); + Py_DECREF(me->ob_type); FREEOBJ(me); +} + +static PyObject *gaedget_hsz(PyObject *me, void *hunoz) +{ + if (GAEADDEC_F(me)&AEADF_PCHSZ) return getulong(GAEADDEC_HSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaedget_csz(PyObject *me, void *hunoz) +{ + if (GAEADDEC_F(me)&AEADF_PCMSZ) return getulong(GAEADDEC_CSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaedget_tsz(PyObject *me, void *hunoz) +{ + if (GAEADDEC_F(me)&AEADF_PCTSZ) return getulong(GAEADDEC_TSZ(me)); + else RETURN_NONE; +} + +static PyObject *gaedget_clen(PyObject *me, void *hunoz) + { return getulong(GAEADDEC_CLEN(me)); } + +static PyGetSetDef gaeaddec_pygetset[] = { +#define GETSETNAME(op, name) gaed##op##_##name + GET (hsz, "DEC.hsz -> precommitted header length or `None'") + GET (csz, "DEC.csz -> precommitted ciphertext length or `None'") + GET (tsz, "DEC.tsz -> precommitted tag length or `None'") + GET (clen, "DEC.clen -> ciphertext length so far") +#undef GETSETNAME + { 0 } +}; + +static PyObject *gaedmeth_aad(PyObject *me, PyObject *arg) +{ + gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me; + + if (!PyArg_ParseTuple(arg, ":aad")) return (0); + if (!(gd->f&AEADF_AADNDEP)) + return (gaeadaad_pywrap((PyObject *)GCAEADDEC_KEY(gd->ob_type)->aad, + GAEAD_AAD(gd->d), 0, 0)); + else { + if (!gd->aad) + gd->aad = (gaeadaad_pyobj *) + gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(gd->ob_type)->aad, + GAEAD_AAD(gd->d), gd->f&(AEADF_PCHSZ | AEADF_NOAAD), + gd->hsz); + Py_INCREF(gd->aad); + return ((PyObject *)gd->aad); + } +} + +static PyObject *gaedmeth_reinit(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "nonce", "hsz", "csz", "tsz", 0 }; + gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me; + char *n; Py_ssize_t nsz; + PyObject *hszobj = Py_None, *cszobj = Py_None, *tszobj = Py_None; + size_t hsz = 0, csz = 0, tsz = 0; + unsigned f; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|OOO:enc", KWLIST, + &n, &nsz, &hszobj, &cszobj, &tszobj)) + goto end; + if (check_aead_encdec(gd->d->ops->c, &f, nsz, + hszobj, &hsz, cszobj, &csz, tszobj, &tsz)) + goto end; + if (GAEAD_REINIT(gd->d, n, nsz, hsz, csz, tsz)) + VALERR("bad aead parameter combination"); + gaea_sever(&gd->aad); + gd->f = f; gd->hsz = hsz; gd->csz = csz; gd->tsz = tsz; +end: + return (0); +} + +static PyObject *gaedmeth_decrypt(PyObject *me, PyObject *arg) +{ + gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me; + char *c; Py_ssize_t csz; + char *m = 0; size_t msz; buf b; + int err; + PyObject *rc = 0; + + if (!PyArg_ParseTuple(arg, "s#:decrypt", &c, &csz)) goto end; + if (gd->f&AEADF_AADFIRST) { + if ((gd->f&AEADF_PCHSZ) && (gd->aad ? gd->aad->hlen : 0) != gd->hsz) + VALERR("header doesn't match precommitted length"); + gaea_invalidate(gd->aad); + } + if ((gd->f&AEADF_PCMSZ) && csz > gd->csz - gd->clen) + VALERR("too large for precommitted message length"); + msz = csz + gd->d->ops->c->bufsz; m = xmalloc(msz); buf_init(&b, m, msz); + err = GAEAD_DECRYPT(gd->d, c, csz, &b); assert(!err); (void)err; + buf_flip(&b); rc = bytestring_pywrapbuf(&b); gd->clen += csz; +end: + xfree(m); + return (rc); +} + +static PyObject *gaedmeth_done(PyObject *me, PyObject *arg, PyObject *kw) +{ + static const char *const kwlist[] = { "tag", "aad", 0 }; + gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me; + PyObject *aad = Py_None; + char *t; Py_ssize_t tsz; + char *m = 0; size_t msz; buf b; + int err; + PyObject *rc = 0; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O:done", KWLIST, + &t, &tsz, &aad)) + goto end; + if (aad != Py_None && + !PyObject_TypeCheck(aad, + (PyTypeObject *)GCAEADENC_KEY(me->ob_type)->aad)) + TYERR("wanted aad"); + if ((gd->f&AEADF_AADNDEP) && aad != Py_None && aad != (PyObject *)gd->aad) + VALERR("mismatched aad"); + if ((gd->f&AEADF_PCHSZ) && + (aad == Py_None ? 0 : GAEADAAD_HLEN(aad)) != gd->hsz) + VALERR("header doesn't match precommitted length"); + if ((gd->f&AEADF_PCMSZ) && gd->clen != gd->csz) + VALERR("message doesn't match precommitted length"); + if ((gd->f&AEADF_PCTSZ) && tsz != gd->tsz) + VALERR("tag length doesn't match precommitted value"); + if (keysz(tsz, gd->d->ops->c->tagsz) != tsz) VALERR("bad tag length"); + msz = gd->d->ops->c->bufsz; m = xmalloc(msz); buf_init(&b, m, msz); + err = GAEAD_DONE(gd->d, aad == Py_None ? 0 : GAEADAAD_A(aad), &b, t, tsz); + assert(err >= 0); + if (!err) VALERR("decryption failed"); + buf_flip(&b); rc = bytestring_pywrapbuf(&b); +end: + xfree(m); + return (rc); +} + +static PyMethodDef gaeaddec_pymethods[] = { +#define METHNAME(name) gaedmeth_##name + METH (aad, "DEC.aad() -> AAD") + KWMETH(reinit, "DEC.reinit(NONCE, [hsz], [csz], [tsz])") + METH (decrypt, "DEC.decrypt(CT) -> MSG") + KWMETH(done, "DEC.done(TAG, [aad]) -> MSG | None") +#undef METHNAME + { 0 } +}; + +static PyTypeObject gcaead_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GCAEAD", /* @tp_name@ */ + sizeof(gcaead_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@ */ +"Authenticated encryption (key) metaclass.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + gcaead_pygetset, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gaeadkey_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEKey", /* @tp_name@ */ + sizeof(gaeadkey_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + gaeadkey_pydealloc, /* @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@ */ +"Authenticated encryption key.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + gaeadkey_pymethods, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gcaeadaad_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEAADClass", /* @tp_name@ */ + sizeof(gcaeadaad_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@ */ +"Authenticated encryption additional-data hash metaclass.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gaeadaad_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEAAD", /* @tp_name@ */ + sizeof(gaeadaad_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + gaeadaad_pydealloc, /* @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@ */ +"Authenticated encryption AAD hash.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + gaeadaad_pymethods, /* @tp_methods@ */ + 0, /* @tp_members@ */ + gaeadaad_pygetset, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gcaeadenc_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEEncClass", /* @tp_name@ */ + sizeof(gcaeadenc_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@ */ +"Authenticated encryption operation metaclass.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gaeadenc_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEEnc", /* @tp_name@ */ + sizeof(gaeadenc_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + gaeadenc_pydealloc, /* @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@ */ +"Authenticated encryption operation.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + gaeadenc_pymethods, /* @tp_methods@ */ + 0, /* @tp_members@ */ + gaeadenc_pygetset, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gcaeaddec_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEDecClass", /* @tp_name@ */ + sizeof(gcaeaddec_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@ */ +"Authenticated decryption operation metaclass.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static PyTypeObject gaeaddec_pytype_skel = { + PyObject_HEAD_INIT(0) 0, /* Header */ + "GAEDec", /* @tp_name@ */ + sizeof(gaeaddec_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + gaeaddec_pydealloc, /* @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@ */ +"Authenticated decryption operation.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + 0, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + gaeaddec_pymethods, /* @tp_methods@ */ + 0, /* @tp_members@ */ + gaeaddec_pygetset, /* @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@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + /*----- Hash functions ----------------------------------------------------*/ PyTypeObject *gchash_pytype, *ghash_pytype; @@ -722,10 +1797,10 @@ CONVFUNC(ghash, ghash *, GHASH_H) static PyObject *ghash_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", kwlist)) + static const char *const kwlist[] = { 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end; - return (ghash_pywrap((PyObject *)ty, GH_INIT(GCHASH_CH(ty)), f_freeme)); + return (ghash_pywrap((PyObject *)ty, GH_INIT(GCHASH_CH(ty)))); end: return (0); } @@ -747,21 +1822,19 @@ PyObject *gchash_pywrap(gchash *ch) return ((PyObject *)g); } -PyObject *ghash_pywrap(PyObject *cobj, ghash *h, unsigned f) +PyObject *ghash_pywrap(PyObject *cobj, ghash *h) { ghash_pyobj *g; if (!cobj) cobj = gchash_pywrap((/*unconst*/ gchash *)GH_CLASS(h)); else Py_INCREF(cobj); g = PyObject_NEW(ghash_pyobj, (PyTypeObject *)cobj); g->h = h; - g->f = f; return ((PyObject *)g); } static void ghash_pydealloc(PyObject *me) { - if (GHASH_F(me) & f_freeme) - GH_DESTROY(GHASH_H(me)); + GH_DESTROY(GHASH_H(me)); Py_DECREF(me->ob_type); FREEOBJ(me); } @@ -775,6 +1848,12 @@ static PyObject *gchget_hashsz(PyObject *me, void *hunoz) static PyObject *gchget_bufsz(PyObject *me, void *hunoz) { return (PyInt_FromLong(GCHASH_CH(me)->bufsz)); } +static PyObject *ghmeth_copy(PyObject *me, PyObject *arg) +{ + if (!PyArg_ParseTuple(arg, ":copy")) return (0); + return (ghash_pywrap((PyObject *)me->ob_type, GH_COPY(GHASH_H(me)))); +} + static PyObject *ghmeth_hash(PyObject *me, PyObject *arg) { char *p; @@ -788,11 +1867,9 @@ static PyObject *ghmeth_hash(PyObject *me, PyObject *arg) static PyObject *ghmeth_hashu##w(PyObject *me, PyObject *arg) \ { \ uint##n x; \ - if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) goto end; \ + if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \ GH_HASHU##W(GHASH_H(me), x); \ RETURN_ME; \ - end: \ - return (0); \ } DOUINTCONV(GHMETH_HASHU_) @@ -841,6 +1918,7 @@ static PyGetSetDef gchash_pygetset[] = { static PyMethodDef ghash_pymethods[] = { #define METHNAME(name) ghmeth_##name + METH (copy, "H.copy() -> HH") METH (hash, "H.hash(M)") #define METHU_(n, W, w) METH(hashu##w, "H.hashu" #w "(WORD)") DOUINTCONV(METHU_) @@ -960,29 +2038,27 @@ CONVFUNC(gmhash, ghash *, GHASH_H) static PyObject *gmac_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "k", 0 }; + static const char *const kwlist[] = { "k", 0 }; char *k; Py_ssize_t sz; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &k, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &sz)) goto end; if (keysz(sz, GCMAC_CM(ty)->keysz) != sz) VALERR("bad key length"); return (gmac_pywrap((PyObject *)ty, - GM_KEY(GCMAC_CM(ty), k, sz), - f_freeme)); + GM_KEY(GCMAC_CM(ty), k, sz))); end: return (0); } static PyObject *gmhash_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { 0 }; + static const char *const kwlist[] = { 0 }; ghash_pyobj *g; - if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", kwlist)) return (0); + if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) return (0); g = PyObject_NEW(ghash_pyobj, ty); g->h = GM_INIT(GMAC_M(ty)); - g->f = f_freeme; Py_INCREF(ty); return ((PyObject *)g); } @@ -1004,7 +2080,7 @@ PyObject *gcmac_pywrap(gcmac *cm) return ((PyObject *)g); } -PyObject *gmac_pywrap(PyObject *cobj, gmac *m, unsigned f) +PyObject *gmac_pywrap(PyObject *cobj, gmac *m) { gmac_pyobj *g; if (!cobj) cobj = gcmac_pywrap((/*unconst*/ gcmac *)GM_CLASS(m)); @@ -1023,14 +2099,12 @@ PyObject *gmac_pywrap(PyObject *cobj, gmac *m, unsigned f) g->ty.ht_type.tp_new = gmhash_pynew; typeready(&g->ty.ht_type); g->m = m; - g->f = f; return ((PyObject *)g); } static void gmac_pydealloc(PyObject *me) { - if (GMAC_F(me) & f_freeme) - GM_DESTROY(GMAC_M(me)); + GM_DESTROY(GMAC_M(me)); Py_DECREF(me->ob_type); PyType_Type.tp_dealloc(me); } @@ -1220,13 +2294,13 @@ CONVFUNC(poly1305hash, poly1305_ctx *, P1305_CTX) static PyObject *poly1305hash_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "mask", 0 }; + static const char *const kwlist[] = { "mask", 0 }; poly1305key_pyobj *pk = (poly1305key_pyobj *)ty; poly1305hash_pyobj *ph; char *m = 0; Py_ssize_t sz; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s#:new", kwlist, &m, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s#:new", KWLIST, &m, &sz)) return (0); if (m && sz != POLY1305_MASKSZ) VALERR("bad mask length"); ph = PyObject_NEW(poly1305hash_pyobj, ty); @@ -1242,12 +2316,12 @@ end: static PyObject *poly1305key_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "k", 0 }; + static const char *const kwlist[] = { "k", 0 }; poly1305key_pyobj *pk; char *k; Py_ssize_t sz; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &k, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &sz)) goto end; if (keysz(sz, poly1305_keysz) != sz) VALERR("bad key length"); @@ -1308,11 +2382,9 @@ static PyObject *polymeth_hash(PyObject *me, PyObject *arg) { \ uint##n x; \ octet b[SZ_##W]; \ - if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) goto end; \ + if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \ STORE##W(b, x); poly1305_hash(P1305_CTX(me), b, sizeof(b)); \ RETURN_ME; \ - end: \ - return (0); \ } DOUINTCONV(POLYMETH_HASHU_) @@ -1484,7 +2556,7 @@ static PyTypeObject poly1305key_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Poly1305 key.", +"poly1305(K): Poly1305 key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1600,8 +2672,8 @@ static PyObject *kxvik_pynew(PyTypeObject *ty, { unsigned n = 24; kxvik_pyobj *rc = 0; - char *kwlist[] = { "nround", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", kwlist, + static const char *const kwlist[] = { "nround", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convuint, &n)) goto end; rc = (kxvik_pyobj *)ty->tp_alloc(ty, 0); @@ -1611,6 +2683,16 @@ end: return ((PyObject *)rc); } +static PyObject *kxvikmeth_copy(PyObject *me, PyObject *arg) +{ + kxvik_pyobj *k = (kxvik_pyobj *)me, *rc = 0; + if (!PyArg_ParseTuple(arg, ":copy")) goto end; + rc = (kxvik_pyobj *)k->ob_type->tp_alloc(k->ob_type, 0); + rc->s = k->s; rc->n = k->n; +end: + return ((PyObject *)rc); +} + static PyObject *kxvikmeth_mix(PyObject *me, PyObject *arg) { kxvik_pyobj *k = (kxvik_pyobj *)me; @@ -1693,6 +2775,7 @@ static PyGetSetDef kxvik_pygetset[] = { static PyMethodDef kxvik_pymethods[] = { #define METHNAME(func) kxvikmeth_##func + METH (copy, "KECCAK.copy() -> KECCAK'") METH (mix, "KECCAK.mix(DATA)") METH (extract, "KECCAK.extract(NOCTETS)") METH (step, "KECCAK.step()") @@ -1725,7 +2808,7 @@ static PyTypeObject kxvik_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Keccak-p[1600, n] state.", +"Keccak1600([nround = 24]): Keccak-p[1600, n] state.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1768,9 +2851,9 @@ static PyObject *shake_dopynew(void (*initfn)(shake_ctx *, shake_pyobj *rc = 0; char *p = 0, *f = 0; Py_ssize_t psz = 0, fsz = 0; - char *kwlist[] = { "perso", "func", 0 }; + static const char *const kwlist[] = { "perso", "func", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s#s#:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s#s#:new", KWLIST, &p, &psz, &f, &fsz)) goto end; rc = (shake_pyobj *)ty->tp_alloc(ty, 0); @@ -1811,12 +2894,10 @@ static PyObject *shakemeth_hash(PyObject *me, PyObject *arg) { \ uint##n x; \ octet b[SZ_##W]; \ - if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) goto end; \ - if (shake_check(me, 0)) goto end; \ + if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \ + if (shake_check(me, 0)) return (0); \ STORE##W(b, x); shake_hash(SHAKE_H(me), b, sizeof(b)); \ RETURN_ME; \ - end: \ - return (0); \ } DOUINTCONV(SHAKEMETH_HASHU_) @@ -2022,7 +3103,7 @@ static PyTypeObject shake128_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"SHAKE128/cSHAKE128 XOF.", +"Shake128([perso = STR], [func = STR]): SHAKE128/cSHAKE128 XOF.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -2070,7 +3151,7 @@ static PyTypeObject shake256_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"SHAKE256/cSHAKE256 XOF.", +"Shake256([perso = STR], [func = STR]): SHAKE256/cSHAKE256 XOF.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -2152,13 +3233,13 @@ typedef struct prp { static PyObject *gprp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "key", 0 }; + static const char *const kwlist[] = { "key", 0 }; char *k; Py_ssize_t sz; const prpinfo *prp = GCPRP_PRP(ty); PyObject *me; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &k, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &sz)) goto end; if (keysz(sz, prp->keysz) != sz) VALERR("bad key length"); me = (PyObject *)ty->tp_alloc(ty, 0); @@ -2381,6 +3462,14 @@ void algorithms_pyinit(void) INITTYPE(keyszset, keysz); INITTYPE(gccipher, type); INITTYPE(gcipher, root); + INITTYPE(gcaead, type); + INITTYPE(gaeadkey, root); + INITTYPE(gcaeadaad, type); + INITTYPE(gaeadaad, root); + INITTYPE(gcaeadenc, type); + INITTYPE(gaeadenc, root); + INITTYPE(gcaeaddec, type); + INITTYPE(gaeaddec, root); INITTYPE(gchash, type); INITTYPE(ghash, root); INITTYPE(gcmac, type); @@ -2399,6 +3488,7 @@ void algorithms_pyinit(void) } GEN(gcciphers, cipher) +GEN(gcaeads, aead) GEN(gchashes, hash) GEN(gcmacs, mac) #define gcprp prpinfo @@ -2414,6 +3504,15 @@ void algorithms_pyinsert(PyObject *mod) INSERT("GCCipher", gccipher_pytype); INSERT("GCipher", gcipher_pytype); INSERT("gcciphers", gcciphers()); + INSERT("GCAEAD", gcaead_pytype); + INSERT("GAEKey", gaeadkey_pytype); + INSERT("GAEAADClass", gcaeadaad_pytype); + INSERT("GAEAAD", gaeadaad_pytype); + INSERT("GAEEncClass", gcaeadenc_pytype); + INSERT("GAEEnc", gaeadenc_pytype); + INSERT("GAEDecClass", gcaeaddec_pytype); + INSERT("GAEDec", gaeaddec_pytype); + INSERT("gcaeads", gcaeads()); INSERT("GCHash", gchash_pytype); INSERT("GHash", ghash_pytype); INSERT("gchashes", d = gchashes()); diff --git a/algorithms.py b/algorithms.py index 46abccb..5730c45 100644 --- a/algorithms.py +++ b/algorithms.py @@ -25,6 +25,8 @@ serpent noekeon '''.split() pmodes = ''' ecb cbc cfb ofb counter +cmac pmac1 +ccm eax gcm ocb1 ocb3 '''.split() streamciphers = ''' rc4 seal diff --git a/buffer.c b/buffer.c index ac3e56b..5b7bb64 100644 --- a/buffer.c +++ b/buffer.c @@ -55,9 +55,9 @@ static PyObject *rbuf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) char *p, *q; Py_ssize_t n; buf_pyobj *me = 0; - static char *kwlist[] = { "data", 0 }; + static const char *const kwlist[] = { "data", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &p, &n)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &p, &n)) goto end; q = xmalloc(n); memcpy(q, p, n); @@ -172,9 +172,9 @@ end: static PyObject *rbmeth_getecpt(PyObject *me, PyObject *arg, PyObject *kw) { PyObject *cobj = Py_None; - static char *kwlist[] = { "curve", 0 }; + static const char *const kwlist[] = { "curve", 0 }; ec pt = EC_INIT; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:getecpt", kwlist, &cobj)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:getecpt", KWLIST, &cobj)) goto end; if (cobj == Py_None) cobj = (PyObject *)ecpt_pytype; if (!PyType_Check(cobj) || @@ -309,7 +309,7 @@ static PyTypeObject rbuf_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "A read buffer.", +"ReadBuffer(STR): a read buffer.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -355,9 +355,9 @@ static PyObject *wbuf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) char *p; size_t n = 64; buf_pyobj *me = 0; - static char *kwlist[] = { "size", 0 }; + static const char *const kwlist[] = { "size", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convszt, &n)) goto end; me = (buf_pyobj *)ty->tp_alloc(ty, 0); @@ -486,9 +486,13 @@ static PyObject *wbmeth_putgeraw(PyObject *me, PyObject *arg) static PyObject *wbget_size(PyObject *me, void *hunoz) { return (PyInt_FromLong(BLEN(BUF_B(me)))); } +static PyObject *wbget_contents(PyObject *me, void *hunoz) + { return (bytestring_pywrap(BBASE(BUF_B(me)), BLEN(BUF_B(me)))); } + static PyGetSetDef wbuf_pygetset[] = { #define GETSETNAME(op, name) wb##op##_##name GET (size, "WBUF.size -> SIZE") + GET (contents, "WBUF.contents -> STR") #undef GETSETNAME { 0 } }; @@ -545,7 +549,7 @@ static PyTypeObject wbuf_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "A write buffer.", +"WriteBuffer([size = ?]): a write buffer.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/bytestring.c b/bytestring.c index 2b74648..82d117f 100644 --- a/bytestring.c +++ b/bytestring.c @@ -32,10 +32,12 @@ PyTypeObject *bytestring_pytype; -static PyObject *dowrap(PyTypeObject *ty, const void *p, size_t n) +static PyObject *empty, *bytev[256]; + +static PyObject *allocate(PyTypeObject *ty, size_t n) { - PyStringObject *x = (PyStringObject *)ty->tp_alloc(ty, n); - if (p) memcpy(x->ob_sval, p, n); + PyStringObject *x; + x = (PyStringObject *)ty->tp_alloc(ty, n); x->ob_sval[n] = 0; #if defined(CACHE_HASH) || PY_VERSION_HEX >= 0x02030000 x->ob_shash = -1; @@ -44,6 +46,27 @@ static PyObject *dowrap(PyTypeObject *ty, const void *p, size_t n) return ((PyObject *)x); } +static PyObject *dowrap(PyTypeObject *ty, const void *p, size_t n) +{ + PyObject *x; + int ch; + + if (p && ty == bytestring_pytype) { + if (!n) { + if (!empty) empty = allocate(ty, 0); + Py_INCREF(empty); return (empty); + } else if (n == 1 && (ch = *(unsigned char *)p) < sizeof(bytev)) { + if (!bytev[ch]) + { bytev[ch] = allocate(ty, 1); *PyString_AS_STRING(bytev[ch]) = ch; } + Py_INCREF(bytev[ch]); return (bytev[ch]); + } + } + + x = allocate(ty, n); + if (p) memcpy(PyString_AS_STRING(x), p, n); + return (x); +} + PyObject *bytestring_pywrap(const void *p, size_t n) { return (dowrap(bytestring_pytype, p, n)); } @@ -55,8 +78,8 @@ static PyObject *bytestring_pynew(PyTypeObject *ty, { const char *p; Py_ssize_t n; - static char *kwlist[] = { "data", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &p, &n)) + static const char *const kwlist[] = { "data", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &p, &n)) return (0); return (dowrap(ty, p, n)); } @@ -118,6 +141,91 @@ static PyObject *bytestring_pyrichcompare(PyObject *me, else RETURN_FALSE; } +static PyObject *bytestring_pyconcat(PyObject *x, PyObject *y) +{ + const void *xv; Py_ssize_t xsz; + const void *yv; Py_ssize_t ysz; + PyObject *z = 0; char *zp; size_t zsz; + + if (PyObject_AsReadBuffer(x, &xv, &xsz) || + PyObject_AsReadBuffer(y, &yv, &ysz)) + goto end; + zsz = (size_t)xsz + (size_t)ysz; + if (xsz < 0 || ysz < 0 || zsz < xsz) VALERR("too long"); + z = bytestring_pywrap(0, zsz); zp = PyString_AS_STRING(z); + memcpy(zp, xv, xsz); memcpy(zp + xsz, yv, ysz); +end: + return (z); +} + +static PyObject *bytestring_pyrepeat(PyObject *me, Py_ssize_t n) +{ + const unsigned char *xp; size_t xsz; + PyObject *z = 0; char *zp; size_t zsz; + + xp = (const unsigned char *)PyString_AS_STRING(me); + xsz = PyString_GET_SIZE(me); + if (n < 0 || (n && xsz >= (size_t)-1/n)) VALERR("too long"); + zsz = n*xsz; z = bytestring_pywrap(0, zsz); zp = PyString_AS_STRING(z); + if (xsz == 1) memset(zp, *xp, zsz); + else while (zsz) { memcpy(zp, xp, xsz); zp += xsz; zsz -= xsz; } +end: + return (z); +} + +static PyObject *bytestring_pyitem(PyObject *me, Py_ssize_t i) +{ + PyObject *rc = 0; + + if (i < 0 || i >= PyString_GET_SIZE(me)) IXERR("out of range"); + rc = bytestring_pywrap(PyString_AS_STRING(me) + i, 1); +end: + return (rc); +} + +static PyObject *bytestring_pyslice(PyObject *me, Py_ssize_t i, Py_ssize_t j) +{ + PyObject *rc = 0; + size_t n = PyString_GET_SIZE(me); + + if (i < 0) i = 0; + if (j < 0) j = 0; + else if (j > n) j = n; + if (j < i) i = j = 0; + if (i == 0 && j == n && me->ob_type == bytestring_pytype) + { Py_INCREF(me); rc = me; goto end; } + rc = bytestring_pywrap(PyString_AS_STRING(me) + i, j - i); +end: + return (rc); +} + +static PyObject *bytestring_pysubscript(PyObject *me, PyObject *ix) +{ + Py_ssize_t i, j, k, n; + const unsigned char *p; + unsigned char *q; + PyObject *rc = 0; + + if (PyIndex_Check(ix)) { + i = PyNumber_AsSsize_t(ix, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) return (0); + if (i < 0) i += PyString_GET_SIZE(me); + rc = bytestring_pyitem(me, i); + } else if (PySlice_Check(ix)) { + if (PySlice_GetIndicesEx((PySliceObject *)ix, PyString_GET_SIZE(me), + &i, &j, &k, &n)) + return (0); + if (k == 1) return bytestring_pyslice(me, i, j); + rc = bytestring_pywrap(0, n); + p = (unsigned char *)PyString_AS_STRING(me) + i; + q = (unsigned char *)PyString_AS_STRING(rc); + while (n--) { *q++ = *p; p += k; } + } else + TYERR("wanted integer or slice"); +end: + return (rc); +} + #define BINOP(name, op) \ static PyObject *bytestring_py##name(PyObject *x, PyObject *y) { \ const void *xv, *yv; \ @@ -183,6 +291,25 @@ static PyNumberMethods bytestring_pynumber = { 0, /* @nb_hex@ */ }; +static PySequenceMethods bytestring_pysequence = { + 0, /* @sq_length@ */ + bytestring_pyconcat, /* @sq_concat@ */ + bytestring_pyrepeat, /* @sq_repeat@ */ + bytestring_pyitem, /* @sq_item@ */ + bytestring_pyslice, /* @sq_slice@ */ + 0, /* @sq_ass_item@ */ + 0, /* @sq_ass_slice@ */ + 0, /* @sq_contains@ */ + 0, /* @sq_inplace_concat@ */ + 0, /* @sq_inplace_repeat@ */ +}; + +static PyMappingMethods bytestring_pymapping = { + 0, /* @mp_length@ */ + bytestring_pysubscript, /* @mp_subscript@ */ + 0, /* @mp_ass_subscript@ */ +}; + static PyBufferProcs bytestring_pybuffer; static PyTypeObject bytestring_pytype_skel = { @@ -198,8 +325,8 @@ static PyTypeObject bytestring_pytype_skel = { 0, /* @tp_compare@ */ 0, /* @tp_repr@ */ &bytestring_pynumber, /* @tp_as_number@ */ - 0, /* @tp_as_sequence@ */ - 0, /* @tp_as_mapping@ */ + &bytestring_pysequence, /* @tp_as_sequence@ */ + &bytestring_pymapping, /* @tp_as_mapping@ */ 0, /* @tp_hash@ */ 0, /* @tp_call@ */ 0, /* @tp_str@ */ @@ -211,7 +338,7 @@ static PyTypeObject bytestring_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Byte string class.", +"ByteString(STR): byte string class.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/catacomb-python.h b/catacomb-python.h index ad27461..6c1ae21 100644 --- a/catacomb-python.h +++ b/catacomb-python.h @@ -61,6 +61,7 @@ #include #include +#include #include #include #include @@ -135,6 +136,7 @@ #define VALERR(str) EXCERR(PyExc_ValueError, str) #define OVFERR(str) EXCERR(PyExc_OverflowError, str) #define TYERR(str) EXCERR(PyExc_TypeError, str) +#define IXERR(str) EXCERR(PyExc_IndexError, str) #define ZDIVERR(str) EXCERR(PyExc_ZeroDivisionError, str) #define SYSERR(str) EXCERR(PyExc_SystemError, str) #define NIERR(str) EXCERR(PyExc_NotImplementedError, str) @@ -234,6 +236,8 @@ MODULES(DO) return (d); \ } +#define KWLIST (/*unconst*/ char **)kwlist + struct nameval { const char *name; unsigned f; unsigned long value; }; #define CF_SIGNED 1u extern void setconstants(PyObject *, const struct nameval *); @@ -531,24 +535,130 @@ typedef struct gccipher_pyobj { extern PyTypeObject *gccipher_pytype; #define GCCIPHER_PYCHECK(o) PyObject_TypeCheck((o), gccipher_pytype) #define GCCIPHER_CC(o) (((gccipher_pyobj *)(o))->cc) -#define GCCIPHER_F(o) (((gccipher_pyobj *)(o))->f) extern PyObject *gccipher_pywrap(gccipher *); extern int convgccipher(PyObject *, void *); -extern int convgcipher(PyObject *, void *); typedef struct gcipher_pyobj { PyObject_HEAD - unsigned f; gcipher *c; } gcipher_pyobj; extern PyTypeObject *gcipher_pytype; #define GCIPHER_PYCHECK(o) PyObject_TypeCheck((o), gcipher_pytype) #define GCIPHER_C(o) (((gcipher_pyobj *)(o))->c) -#define GCIPHER_F(o) (((gcipher_pyobj *)(o))->f) -extern PyObject *gcipher_pywrap(PyObject *, gcipher *, unsigned); +extern PyObject *gcipher_pywrap(PyObject *, gcipher *); extern int convgcipher(PyObject *, void *); +typedef struct gcaead_pyobj { + PyHeapTypeObject ty; + gcaead *aec; + struct gcaeadaad_pyobj *aad; + struct gcaeadenc_pyobj *enc; + struct gcaeaddec_pyobj *dec; +} gcaead_pyobj; + +extern PyTypeObject *gcaead_pytype; +#define GCAEAD_PYCHECK(o) PyObject_TypeCheck((o), gcaead_pytype) +#define GCAEAD_AEC(o) (((gcaead_pyobj *)(o))->aec) +#define GCAEAD_AAD(o) (((gcaead_pyobj *)(o))->aad) +#define GCAEAD_ENC(o) (((gcaead_pyobj *)(o))->enc) +#define GCAEAD_DEC(o) (((gcaead_pyobj *)(o))->dec) +extern PyObject *gcaead_pywrap(gcaead *); +extern int convgcaead(PyObject *, void *); + +typedef struct gaeadkey_pyobj { + PyObject_HEAD + gaead_key *k; +} gaeadkey_pyobj; + +extern PyTypeObject *gaeadkey_pytype; +#define GAEADKEY_PYCHECK(o) PyObject_TypeCheck((o), gaeadkey_pytype) +#define GAEADKEY_K(o) (((gaeadkey_pyobj *)(o))->k) +extern PyObject *gaeadkey_pywrap(PyObject *, gaead_key *); +extern int convgaeadkey(PyObject *, void *); + +typedef struct gcaeadaad_pyobj { + PyHeapTypeObject ty; + gcaead_pyobj *key; +} gcaeadaad_pyobj; +#define GCAEADAAD_KEY(o) (((gcaeadaad_pyobj *)(o))->key) +extern PyTypeObject *gcaeadaad_pytype; + +typedef struct gaeadaad_pyobj { + PyObject_HEAD + gaead_aad *a; + unsigned f; +#define AEADF_DEAD 32768u + size_t hsz, hlen; +} gaeadaad_pyobj; + +extern PyTypeObject *gaeadaad_pytype; +#define GAEADAAD_PYCHECK(o) PyObject_TypeCheck((o), gaeadaad_pytype) +#define GAEADAAD_A(o) (((gaeadaad_pyobj *)(o))->a) +#define GAEADAAD_F(o) (((gaeadaad_pyobj *)(o))->f) +#define GAEADAAD_HSZ(o) (((gaeadaad_pyobj *)(o))->hsz) +#define GAEADAAD_HLEN(o) (((gaeadaad_pyobj *)(o))->hlen) +extern PyObject *gaeadaad_pywrap(PyObject *, gaead_aad *, unsigned, size_t); +extern int convgaeadaad(PyObject *, void *); + +typedef struct gcaeadenc_pyobj { + PyHeapTypeObject ty; + gcaead_pyobj *key; +} gcaeadenc_pyobj; +#define GCAEADENC_KEY(o) (((gcaeadenc_pyobj *)(o))->key) +extern PyTypeObject *gcaeadenc_pytype; + +typedef struct gaeadenc_pyobj { + PyObject_HEAD + gaead_enc *e; + gaeadaad_pyobj *aad; + unsigned f; + size_t hsz, msz, tsz; + size_t mlen; +} gaeadenc_pyobj; + +extern PyTypeObject *gaeadenc_pytype; +#define GAEADENC_PYCHECK(o) PyObject_TypeCheck((o), gaeadenc_pytype) +#define GAEADENC_AAD(o) (((gaeadenc_pyobj *)(o))->aad) +#define GAEADENC_E(o) (((gaeadenc_pyobj *)(o))->e) +#define GAEADENC_F(o) (((gaeadenc_pyobj *)(o))->f) +#define GAEADENC_HSZ(o) (((gaeadenc_pyobj *)(o))->hsz) +#define GAEADENC_MSZ(o) (((gaeadenc_pyobj *)(o))->msz) +#define GAEADENC_TSZ(o) (((gaeadenc_pyobj *)(o))->tsz) +#define GAEADENC_MLEN(o) (((gaeadenc_pyobj *)(o))->mlen) +extern PyObject *gaeadenc_pywrap(PyObject *, gaead_enc *, unsigned, + size_t, size_t, size_t); +extern int convgaeadenc(PyObject *, void *); + +typedef struct gcaeaddec_pyobj { + PyHeapTypeObject ty; + gcaead_pyobj *key; +} gcaeaddec_pyobj; +#define GCAEADDEC_KEY(o) (((gcaeaddec_pyobj *)(o))->key) +extern PyTypeObject *gcaeaddec_pytype; + +typedef struct gaeaddec_pyobj { + PyObject_HEAD + gaead_dec *d; + gaeadaad_pyobj *aad; + unsigned f; + size_t hsz, csz, tsz; + size_t clen; +} gaeaddec_pyobj; + +extern PyTypeObject *gaeaddec_pytype; +#define GAEADDEC_PYCHECK(o) PyObject_TypeCheck((o), gaeaddec_pytype) +#define GAEADDEC_AAD(o) (((gaeaddec_pyobj *)(o))->aad) +#define GAEADDEC_D(o) (((gaeaddec_pyobj *)(o))->d) +#define GAEADDEC_F(o) (((gaeaddec_pyobj *)(o))->f) +#define GAEADDEC_HSZ(o) (((gaeaddec_pyobj *)(o))->hsz) +#define GAEADDEC_CSZ(o) (((gaeaddec_pyobj *)(o))->csz) +#define GAEADDEC_TSZ(o) (((gaeaddec_pyobj *)(o))->tsz) +#define GAEADDEC_CLEN(o) (((gaeaddec_pyobj *)(o))->clen) +extern PyObject *gaeaddec_pywrap(PyObject *, gaead_dec *, unsigned, + size_t, size_t, size_t); +extern int convgaeaddec(PyObject *, void *); + typedef struct gchash_pyobj { PyHeapTypeObject ty; gchash *ch; @@ -557,13 +667,11 @@ typedef struct gchash_pyobj { extern PyTypeObject *gchash_pytype; #define GCHASH_PYCHECK(o) PyObject_TypeCheck((o), gchash_pytype) #define GCHASH_CH(o) (((gchash_pyobj *)(o))->ch) -#define GCHASH_F(o) (((gchash_pyobj *)(o))->f) extern PyObject *gchash_pywrap(gchash *); extern int convgchash(PyObject *, void *); typedef struct ghash_pyobj { PyObject_HEAD - unsigned f; ghash *h; } ghash_pyobj; @@ -571,8 +679,7 @@ extern PyTypeObject *ghash_pytype, *gmhash_pytype; extern PyObject *sha_pyobj, *has160_pyobj; #define GHASH_PYCHECK(o) PyObject_TypeCheck((o), ghash_pytype) #define GHASH_H(o) (((ghash_pyobj *)(o))->h) -#define GHASH_F(o) (((ghash_pyobj *)(o))->f) -extern PyObject *ghash_pywrap(PyObject *, ghash *, unsigned); +extern PyObject *ghash_pywrap(PyObject *, ghash *); extern int convghash(PyObject *, void *); extern int convgmhash(PyObject *, void *); @@ -590,7 +697,6 @@ extern int convgcmac(PyObject *, void *); typedef struct gmac_pyobj { PyHeapTypeObject ty; - unsigned f; gmac *m; } gmac_pyobj; @@ -598,7 +704,7 @@ extern PyTypeObject *gmac_pytype; #define GMAC_PYCHECK(o) PyObject_TypeCheck((o), gmac_pytype) #define GMAC_M(o) (((gmac_pyobj *)(o))->m) #define GMAC_F(o) (((gmac_pyobj *)(o))->f) -extern PyObject *gmac_pywrap(PyObject *, gmac *, unsigned); +extern PyObject *gmac_pywrap(PyObject *, gmac *); extern int convgmac(PyObject *, void *); /*----- Key generation ----------------------------------------------------*/ diff --git a/catacomb.c b/catacomb.c index e24e5ec..e9c9513 100644 --- a/catacomb.c +++ b/catacomb.c @@ -52,6 +52,8 @@ static const struct nameval consts[] = { C(ED25519_KEYSZ), C(ED25519_PUBSZ), C(ED25519_SIGSZ), C(ED25519_MAXPERSOSZ), C(ED448_KEYSZ), C(ED448_PUBSZ), C(ED448_SIGSZ), C(ED448_MAXPERSOSZ), + C(AEADF_PCHSZ), C(AEADF_PCMSZ), C(AEADF_PCTSZ), + C(AEADF_AADNDEP), C(AEADF_AADFIRST), C(AEADF_NOAAD), #define ENTRY(tag, val, str) C(KERR_##tag), KEY_ERRORS(ENTRY) #undef ENTRY diff --git a/catacomb/__init__.py b/catacomb/__init__.py index 24265ac..65695bb 100644 --- a/catacomb/__init__.py +++ b/catacomb/__init__.py @@ -112,7 +112,7 @@ def _init(): for j in b: if j[:plen] == pre: setattr(c, j[plen:], classmethod(b[j])) - for i in [gcciphers, gchashes, gcmacs, gcprps]: + for i in [gcciphers, gcaeads, gchashes, gcmacs, gcprps]: for c in i.itervalues(): d[_fixname(c.name)] = c for c in gccrands.itervalues(): @@ -194,6 +194,27 @@ ByteString.__hash__ = str.__hash__ bytes = ByteString.fromhex ###-------------------------------------------------------------------------- +### Symmetric encryption. + +class _tmp: + def encrypt(me, n, m, tsz = None, h = ByteString('')): + if tsz is None: tsz = me.__class__.tagsz.default + e = me.enc(n, len(h), len(m), tsz) + if not len(h): a = None + else: a = e.aad().hash(h) + c0 = e.encrypt(m) + c1, t = e.done(aad = a) + return c0 + c1, t + def decrypt(me, n, c, t, h = ByteString('')): + d = me.dec(n, len(h), len(c), len(t)) + if not len(h): a = None + else: a = d.aad().hash(h) + m = d.decrypt(c) + m += d.done(t, aad = a) + return m +_augment(GAEKey, _tmp) + +###-------------------------------------------------------------------------- ### Hashing. class _tmp: @@ -293,7 +314,7 @@ class _tmp: me.bytepad_after() _augment(Shake, _tmp) _augment(_ShakeBase, _tmp) -Shake._Z = _ShakeBase._Z = ByteString(200*'\0') +Shake._Z = _ShakeBase._Z = ByteString.zero(200) class KMAC (_ShakeBase): _FUNC = 'KMAC' @@ -317,21 +338,12 @@ class KMAC256 (KMAC): _SHAKE = Shake256; _TAGSZ = 32 ### NaCl `secretbox'. def secret_box(k, n, m): - E = xsalsa20(k).setiv(n) - r = E.enczero(poly1305.keysz.default) - s = E.enczero(poly1305.masksz) - y = E.encrypt(m) - t = poly1305(r)(s).hash(y).done() - return ByteString(t + y) + y, t = salsa20_naclbox(k).encrypt(n, m) + return t + y def secret_unbox(k, n, c): - E = xsalsa20(k).setiv(n) - r = E.enczero(poly1305.keysz.default) - s = E.enczero(poly1305.masksz) - y = c[poly1305.tagsz:] - if not poly1305(r)(s).hash(y).check(c[0:poly1305.tagsz]): - raise ValueError, 'decryption failed' - return E.decrypt(c[poly1305.tagsz:]) + tsz = poly1305.tagsz + return salsa20_naclbox(k).decrypt(n, c[tsz:], c[0:tsz]) ###-------------------------------------------------------------------------- ### Multiprecision integers and binary polynomials. @@ -586,6 +598,7 @@ class _tmp: def __repr__(me): return '%s(%d)' % (_clsname(me), me.default) def check(me, sz): return True def best(me, sz): return sz + def pad(me, sz): return sz _augment(KeySZAny, _tmp) class _tmp: @@ -602,11 +615,15 @@ class _tmp: pp.pretty(me.max); pp.text(','); pp.breakable() pp.pretty(me.mod) pp.end_group(ind, ')') - def check(me, sz): return me.min <= sz <= me.max and sz % me.mod == 0 + def check(me, sz): return me.min <= sz <= me.max and sz%me.mod == 0 def best(me, sz): if sz < me.min: raise ValueError, 'key too small' elif sz > me.max: return me.max - else: return sz - (sz % me.mod) + else: return sz - sz%me.mod + def pad(me, sz): + if sz > me.max: raise ValueError, 'key too large' + elif sz < me.min: return me.min + else: sz += me.mod - 1; return sz - sz%me.mod _augment(KeySZRange, _tmp) class _tmp: @@ -628,6 +645,12 @@ class _tmp: if found < i <= sz: found = i if found < 0: raise ValueError, 'key too small' return found + def pad(me, sz): + found = -1 + for i in me.set: + if sz <= i and (found == -1 or i < found): found = i + if found < 0: raise ValueError, 'key too large' + return found _augment(KeySZSet, _tmp) ###-------------------------------------------------------------------------- @@ -866,21 +889,23 @@ _augment(RSAPriv, _tmp) ### DSA and related schemes. class _tmp: - def __repr__(me): return '%s(G = %r, p = %r)' % (_clsname(me), me.G, me.p) + def __repr__(me): return '%s(G = %r, p = %r, hash = %r)' % \ + (_clsname(me), me.G, me.p, me.hash) def _repr_pretty_(me, pp, cyclep): ind = _pp_bgroup_tyname(pp, me) if cyclep: pp.text('...') else: _pp_kv(pp, 'G', me.G); pp.text(','); pp.breakable() - _pp_kv(pp, 'p', me.p) + _pp_kv(pp, 'p', me.p); pp.text(','); pp.breakable() + _pp_kv(pp, 'hash', me.hash) pp.end_group(ind, ')') _augment(DSAPub, _tmp) _augment(KCDSAPub, _tmp) class _tmp: - def __repr__(me): return '%s(G = %r, u = %s, p = %r)' % \ - (_clsname(me), me.G, _repr_secret(me.u), me.p) + def __repr__(me): return '%s(G = %r, u = %s, p = %r, hash = %r)' % \ + (_clsname(me), me.G, _repr_secret(me.u), me.p, me.hash) def _repr_pretty_(me, pp, cyclep): ind = _pp_bgroup_tyname(pp, me) if cyclep: @@ -888,7 +913,8 @@ class _tmp: else: _pp_kv(pp, 'G', me.G); pp.text(','); pp.breakable() _pp_kv(pp, 'u', me.u, True); pp.text(','); pp.breakable() - _pp_kv(pp, 'p', me.p) + _pp_kv(pp, 'p', me.p); pp.text(','); pp.breakable() + _pp_kv(pp, 'hash', me.hash) pp.end_group(ind, ')') _augment(DSAPriv, _tmp) _augment(KCDSAPriv, _tmp) diff --git a/debian/changelog b/debian/changelog index ca06b5c..0398864 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,26 @@ +catacomb-python (1.3.0.1) experimental; urgency=medium + + * Fix required Catacomb version in `setup.py' script. Only affects the + source package. + + -- Mark Wooding Sun, 22 Sep 2019 01:21:28 +0100 + +catacomb-python (1.3.0) experimental; urgency=medium + + * catacomb: Bindings for new blockcipher-based MACs, and AEAD schemes. + * catacomb: Invalidate `grand' objects passed into Python through prime- + generation events. + * catacomb: Improve class docstrings. (They're still extremely terse.) + * catacomb: Add missing `copy' methods on hash and Keccak objects. + * catacomb: Add `WriteBuffer.contents' as a more convenient way to + extract the contents than coercing to `str' or `ByteString'. + * catacomb: Set `RTLD_DEEPBIND' while loading the native module to work + around #868366. + * pock: New program for generating efficiently verifiable prime numbers, + and for verifying their certificates. + + -- Mark Wooding Sat, 21 Sep 2019 23:00:25 +0100 + catacomb-python (1.2.1.1) experimental; urgency=medium * Fixing to build against Debian `stretch'. diff --git a/debian/control b/debian/control index 61f54a8..1bde721 100644 --- a/debian/control +++ b/debian/control @@ -5,7 +5,7 @@ XS-Python-Version: >= 2.6, << 2.8 Maintainer: Mark Wooding Build-Depends: debhelper (>= 9), dh-python, pkg-config, python (>= 2.6.6-3~), python-all-dev, - mlib-dev (>= 2.2.2.1), catacomb-dev (>= 2.4.0) + mlib-dev (>= 2.2.2.1), catacomb-dev (>= 2.5.0) Standards-Version: 3.8.0 Package: python-catacomb diff --git a/debian/copyright b/debian/copyright index 050e1e1..65c9432 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,16 +1,30 @@ -Catacomb/Python is copyright (c) 2005 Straylight/Edgeware +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Copyright: 2006--2010, 2012--2017 Straylight/Edgeware +Upstream-Name: catacomb-python +Upstream-Contact: Mark Wooding +Source: https://ftp.distorted.org.uk/pub/mdw/ +License: LGPL-2.0+ -Catacomb/Python 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. +Files: * +Copyright: 2006--2010, 2012--2017 Straylight/Edgeware +License: LGPL-2.0+ -Catacomb/Python 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 a copy of the GNU Library General Public License in -/usr/share/common-licenses/LGPL-2; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, -USA. +License: LGPL-2.0+ + Catacomb/Python 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/Python 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 a copy of the GNU Library General Public License in + /usr/share/common-licenses/LGPL-2; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. + . + On Debian systems, the full text of the GNU Library General Public + License version 2 can be found in the file + `/usr/share/common-licenses/LGPL-2'. diff --git a/ec.c b/ec.c index fe5f6fb..b555431 100644 --- a/ec.c +++ b/ec.c @@ -292,9 +292,9 @@ static PyObject *epmeth_ec2osp(PyObject *me, PyObject *arg, PyObject *kw) ec pp = EC_INIT; unsigned f = EC_EXPLY; int len; - char *kwlist[] = { "flags", 0 }; + static const char *const kwlist[] = { "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:ec2osp", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:ec2osp", KWLIST, convuint, &f)) return (0); len = c->f->noctets * 2 + 1; @@ -502,9 +502,9 @@ static PyObject *ecptnc_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *x = 0, *y = 0, *z = 0; ec p = EC_INIT; - char *kwlist[] = { "x", "y", 0 }; + static const char *const kwlist[] = { "x", "y", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:new", kwlist, &x, &y) || + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:new", KWLIST, &x, &y) || ecptxl(0, &p, x, y, z)) goto end; return (ecpt_pywrapout(ty, &p)); @@ -543,9 +543,9 @@ static PyObject *ecpt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *x = 0, *y = 0, *z = 0; ec p = EC_INIT; - char *kwlist[] = { "x", "y", "z", 0 }; + static const char *const kwlist[] = { "x", "y", "z", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OOO:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OOO:new", KWLIST, &x, &y, &z) || ecptxl(ECCURVE_C(ty), &p, x, y, z)) goto end; @@ -640,7 +640,9 @@ static PyTypeObject ecpt_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Elliptic curve points, not associated with any curve.", +"ECPt([X, [Y]]): elliptic curve points, not associated with any curve.\n\ + X alone may be None, an existing point, a string 'X, Y', an\n\ + x-coordinate, or a pair (X, Y); X and Y should be a coordinate pair.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -862,9 +864,9 @@ static PyObject *meth__ECPtCurve_os2ecp(PyObject *me, ec_curve *cc; unsigned f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY; ec pp = EC_INIT; - char *kwlist[] = { "class", "buf", "flags", 0 }; + static const char *const kwlist[] = { "class", "buf", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|O&:os2ecp", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|O&:os2ecp", KWLIST, &me, &p, &len, convuint, &f)) return (0); buf_init(&b, p, len); @@ -940,11 +942,11 @@ end: static PyObject *ecmeth_rand(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; grand *r = &rand_global; ec p = EC_INIT; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", KWLIST, convgrand, &r)) return (0); ec_rand(ECCURVE_C(me), &p, r); @@ -1000,10 +1002,10 @@ static PyObject *eccurve_pynew(PyTypeObject *ty, { PyObject *fobj; PyObject *cobj = 0; - char *kwlist[] = { "field", "a", "b", 0 }; + static const char *const kwlist[] = { "field", "a", "b", 0 }; mp *aa = 0, *bb = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&O&", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&O&", KWLIST, field_pytype, &fobj, convmp, &aa, convmp, &bb)) goto end; @@ -1094,7 +1096,7 @@ static PyTypeObject eccurve_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "An elliptic curve. Abstract class.", +"An elliptic curve. Abstract class.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1148,7 +1150,8 @@ static PyTypeObject ecprimecurve_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "An elliptic curve over a prime field. Use ecprimeprojcurve.", +"ECPrimeCurve(FIELD, A, B): an elliptic curve over a prime field.\n\ + Use ECPrimeProjCurve instead.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1202,7 +1205,8 @@ static PyTypeObject ecprimeprojcurve_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "An elliptic curve over a prime field, using projective coordinates.", +"ECPrimeProjCurve(FIELD, A, B): an elliptic curve over a prime field\n\ + using projective coordinates.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1256,7 +1260,8 @@ static PyTypeObject ecbincurve_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "An elliptic curve over a binary field. Use ecbinprojcurve.", +"ECBinCurve(FIELD, A, B): an elliptic curve over a binary field.\n\ + Use ECBinProjCurve instead.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1310,7 +1315,8 @@ static PyTypeObject ecbinprojcurve_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "An elliptic curve over a binary field, using projective coordinates.", +"ECBinProjCurve(FIELD, A, B): an elliptic curve over a binary field,\n\ + using projective coordinates.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1371,10 +1377,10 @@ static PyObject *ecinfo_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { ec_info ei = { 0 }; PyObject *e, *g; - char *kwlist[] = { "curve", "G", "r", "h", 0 }; + static const char *const kwlist[] = { "curve", "G", "r", "h", 0 }; ecinfo_pyobj *rc = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!O&O&:new", KWLIST, eccurve_pytype, &e, ecpt_pytype, &g, convmp, &ei.r, convmp, &ei.h)) goto end; @@ -1442,11 +1448,11 @@ end: static PyObject *eimeth_check(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; grand *r = &rand_global; const char *p; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:check", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:check", KWLIST, convgrand, &r)) goto end; if ((p = ec_checkinfo(ECINFO_EI(me), r)) != 0) @@ -1515,7 +1521,7 @@ static PyTypeObject ecinfo_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Elliptic curve domain parameters.", +"ECInfo(CURVE, G, R, H): elliptic curve domain parameters.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/field.c b/field.c index 224363d..c23f311 100644 --- a/field.c +++ b/field.c @@ -42,9 +42,9 @@ static PyObject *fe_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *x; mp *z; - char *kwlist[] = { "x", 0 }; + static const char *const kwlist[] = { "x", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:fe", kwlist, &x)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:fe", KWLIST, &x)) return (0); if (FE_PYCHECK(x) && FE_F(x) == FIELD_F(ty)) RETURN_OBJ(x); if ((z = getmp(x)) == 0) return (0); @@ -456,10 +456,10 @@ end: static PyObject *fmeth_rand(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; grand *r = &rand_global; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", KWLIST, convgrand, &r)) return (0); return (fe_pywrap(me, F_RAND(FIELD_F(me), MP_NEW, r))); @@ -575,9 +575,9 @@ static PyObject *primefield_pynew(PyTypeObject *ty, { mp *xx = 0; field *f; - char *kwlist[] = { "p", 0 }; + static const char *const kwlist[] = { "p", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:primefield", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:primefield", KWLIST, convmp, &xx)) goto end; if ((f = field_prime(xx)) == 0) @@ -623,7 +623,7 @@ static PyTypeObject primefield_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Prime fields.", +"PrimeField(P): prime fields.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -651,10 +651,10 @@ static PyObject *niceprimefield_pynew(PyTypeObject *ty, { mp *xx = 0; field *f; - char *kwlist[] = { "p", 0 }; + static const char *const kwlist[] = { "p", 0 }; if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:niceprimefield", - kwlist, convmp, &xx)) + KWLIST, convmp, &xx)) goto end; if ((f = field_niceprime(xx)) == 0) VALERR("bad prime for niceprimefield"); @@ -690,7 +690,7 @@ static PyTypeObject niceprimefield_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Nice prime fields.", +"NicePrimeField(P): prime field using Solinas reduction.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -750,7 +750,7 @@ static PyTypeObject binfield_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Binary fields. Abstract class.", +"Binary fields. Abstract class.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -778,9 +778,9 @@ static PyObject *binpolyfield_pynew(PyTypeObject *ty, { mp *xx = 0; field *f; - char *kwlist[] = { "p", 0 }; + static const char *const kwlist[] = { "p", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:binpolyfield", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:binpolyfield", KWLIST, convgf, &xx)) goto end; if ((f = field_binpoly(xx)) == 0) VALERR("bad poly for binpolyfield"); @@ -826,7 +826,7 @@ static PyTypeObject binpolyfield_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Binary fields with polynomial basis representation.", +"BinPolyField(P): binary fields with polynomial basis representation.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -854,10 +854,10 @@ static PyObject *binnormfield_pynew(PyTypeObject *ty, { mp *xx = 0, *yy = 0; field *f; - char *kwlist[] = { "p", "beta", 0 }; + static const char *const kwlist[] = { "p", "beta", 0 }; if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:binnormfield", - kwlist, convgf, &xx, convgf, &yy)) + KWLIST, convgf, &xx, convgf, &yy)) goto end; if ((f = field_binnorm(xx, yy)) == 0) VALERR("bad args for binnormfield"); MP_DROP(xx); MP_DROP(yy); @@ -908,7 +908,7 @@ static PyTypeObject binnormfield_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Binary fields with normal basis representation.", +"BinNormField(P, BETA): binary fields with normal basis representation.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/group.c b/group.c index e00e3fb..3d29070 100644 --- a/group.c +++ b/group.c @@ -40,11 +40,11 @@ PyObject *fginfo_pywrap(gprime_param *dp, PyTypeObject *ty) static PyObject *fginfo_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "p", "r", "g", 0 }; + static const char *const kwlist[] = { "p", "r", "g", 0 }; gprime_param dp = { 0 }; fginfo_pyobj *z = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&:new", KWLIST, convmp, &dp.p, convmp, &dp.q, convmp, &dp.g)) @@ -94,12 +94,12 @@ static PyObject *meth__DHInfo_generate(PyObject *me, grand *r = &rand_global; struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; - char *kwlist[] = + static const char *const kwlist[] = { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 }; PyObject *rc = 0; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST, &me, convuint, &pl, convuint, &ql, convpgev, &evt, convgrand, &r, convuint, &steps)) @@ -123,15 +123,17 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me, pypgev oe = { { 0 } }, ie = { { 0 } }; int subgroupp = 1; unsigned f = 0; - char *kwlist[] = { "class", "pbits", "qbits", "event", "ievent", - "rng", "nsteps", "subgroupp", 0 }; + static const char *const kwlist[] = { + "class", "pbits", "qbits", "event", "ievent", + "rng", "nsteps", "subgroupp", 0 + }; size_t i, nf; mp **v = 0; PyObject *rc = 0, *vec = 0; oe.exc = ie.exc = &exc; if (!PyArg_ParseTupleAndKeywords(arg, kw, - "OO&O&|O&O&O&O&O&:genlimlee", kwlist, + "OO&O&|O&O&O&O&O&:genlimlee", KWLIST, &me, convuint, &pl, convuint, &ql, convpgev, &oe, convpgev, &ie, convgrand, &r, convuint, &steps, @@ -160,13 +162,13 @@ static PyObject *meth__DHInfo_genkcdsa(PyObject *me, grand *r = &rand_global; struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; - char *kwlist[] = { "class", "pbits", "qbits", - "event", "rng", "nsteps", 0 }; + static const char *const kwlist[] = + { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 }; mp *v = MP_NEW; PyObject *rc = 0; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&O&:genkcdsa", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&O&:genkcdsa", KWLIST, &me, convuint, &pl, convuint, &ql, convpgev, &evt, convgrand, &r, convuint, &steps)) @@ -193,12 +195,12 @@ static PyObject *meth__DHInfo_gendsa(PyObject *me, Py_ssize_t ksz; struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; - char *kwlist[] = + static const char *const kwlist[] = { "class", "pbits", "qbits", "seed", "event", "nsteps", 0 }; PyObject *rc = 0; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&s#|O&O&:gendsa", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&s#|O&O&:gendsa", KWLIST, &me, convuint, &pl, convuint, &ql, &k, &ksz, convpgev, &evt, convuint, &steps)) @@ -333,7 +335,7 @@ static PyTypeObject fginfo_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Abstract base class for field-group information objects.", +"Abstract base class for field-group information objects.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -381,7 +383,7 @@ static PyTypeObject dhinfo_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Standard (integer) Diffie-Hellman group information.", +"DHInfo(P, R, G): standard (integer) Diffie-Hellman group information.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -429,7 +431,7 @@ static PyTypeObject bindhinfo_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Binary-field Diffie-Hellman group information.", +"BinDHInfo(P, R, G): binary-field Diffie-Hellman group information.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -501,7 +503,7 @@ PyObject *ge_pywrap(PyObject *gobj, ge *x) static PyObject *ge_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "x", 0 }; + static const char *const kwlist[] = { "x", 0 }; PyObject *x; group *g; ec p = EC_INIT; @@ -510,7 +512,7 @@ static PyObject *ge_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) mptext_stringctx sc; g = GROUP_G(ty); - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", kwlist, &x)) goto end; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &x)) goto end; xx = G_CREATE(g); if (ECPT_PYCHECK(x)) { getecptout(&p, x); @@ -724,14 +726,14 @@ end: static PyObject *gemeth_toec(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "curve", 0 }; + static const char *const kwlist[] = { "curve", 0 }; PyTypeObject *cty = 0; PyObject *rc = 0; group *g; ec_curve *c; ec p = EC_INIT; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:toec", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:toec", KWLIST, &cty)) goto end; g = GROUP_G(GE_GOBJ(me)); if (cty) { @@ -808,11 +810,11 @@ static PyObject *gmeth_mexp(PyObject *me, PyObject *arg) static PyObject *gmeth_checkgroup(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; grand *r = &rand_global; const char *p; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:checkgroup", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:checkgroup", KWLIST, convgrand, &r)) goto end; if ((p = G_CHECK(GROUP_G(me), r)) != 0) @@ -1161,9 +1163,9 @@ static PyObject *primegroup_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *i; - char *kwlist[] = { "info", 0 }; + static const char *const kwlist[] = { "info", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST, dhinfo_pytype, &i)) return (0); return (group_dopywrap(ty, group_prime(FGINFO_DP(i)))); @@ -1194,7 +1196,7 @@ static PyTypeObject primegroup_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Subgroups of prime fields.", +"PrimeGroup(INFO): subgroups of prime fields.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1238,9 +1240,9 @@ static PyObject *bingroup_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *i; - char *kwlist[] = { "info", 0 }; + static const char *const kwlist[] = { "info", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST, bindhinfo_pytype, &i)) return (0); return (group_dopywrap(ty, group_binary(FGINFO_DP(i)))); @@ -1271,7 +1273,7 @@ static PyTypeObject bingroup_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Subgroups of binary fields.", +"BinGroup(INFO): subgroups of binary fields.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1315,9 +1317,9 @@ static PyObject *ecgroup_pynew(PyTypeObject *ty, { PyObject *i; ec_info ei; - char *kwlist[] = { "info", 0 }; + static const char *const kwlist[] = { "info", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST, ecinfo_pytype, &i)) return (0); ecinfo_copy(&ei, ECINFO_EI(i)); @@ -1349,7 +1351,7 @@ static PyTypeObject ecgroup_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Elliptic curve groups.", +"ECGroup(INFO): elliptic curve groups.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/key.c b/key.c index 99c25ab..87462ab 100644 --- a/key.c +++ b/key.c @@ -353,10 +353,10 @@ static PyObject *kdmeth_split(PyObject *me, PyObject *arg) static PyObject *kdmeth_copy(PyObject *me, PyObject *arg, PyObject *kw) { key_filter f = { 0, 0 }; - static char *kwlist[] = { "filter", 0 }; + static const char *const kwlist[] = { "filter", 0 }; key_data *kd; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:copy", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:copy", KWLIST, convfilter, &f)) return (0); if ((kd = key_copydata(KEYDATA_KD(me), &f)) == 0) @@ -370,9 +370,9 @@ static PyObject *kdmeth_write(PyObject *me, PyObject *arg, PyObject *kw) key_filter f = { 0, 0 }; dstr d = DSTR_INIT; PyObject *rc = 0; - static char *kwlist[] = { "filter", 0 }; + static const char *const kwlist[] = { "filter", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:write", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:write", KWLIST, convfilter, &f)) return (0); key_write(KEYDATA_KD(me), &d, &f); @@ -386,9 +386,9 @@ static PyObject *kdmeth_encode(PyObject *me, PyObject *arg, PyObject *kw) key_filter f = { 0, 0 }; dstr d = DSTR_INIT; PyObject *rc = 0; - static char *kwlist[] = { "filter", 0 }; + static const char *const kwlist[] = { "filter", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:encode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:encode", KWLIST, convfilter, &f)) return (0); key_encode(KEYDATA_KD(me), &d, &f); @@ -538,9 +538,9 @@ static PyObject *keydatabin_pynew(PyTypeObject *ty, Py_ssize_t n; unsigned f = 0; keydata_pyobj *me = 0; - static char *kwlist[] = { "key", "flags", 0 }; + static const char *const kwlist[] = { "key", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:new", KWLIST, &p, &n, convflags, &f)) goto end; me = (keydata_pyobj *)ty->tp_alloc(ty, 0); @@ -585,7 +585,7 @@ static PyTypeObject keydatabin_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for binary keys.", +"KeyDataBinary(KEY, [flags = 0]): key data for binary keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -615,9 +615,9 @@ static PyObject *keydataenc_pynew(PyTypeObject *ty, Py_ssize_t n; unsigned f = 0; keydata_pyobj *me = 0; - static char *kwlist[] = { "key", "flags", 0 }; + static const char *const kwlist[] = { "key", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:new", KWLIST, &p, &n, convflags, &f)) goto end; me = (keydata_pyobj *)ty->tp_alloc(ty, 0); @@ -722,7 +722,7 @@ static PyTypeObject keydataenc_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for encrypted keys.", +"KeyDataEncrypted(KEY, [flags = 0]): key data for encrypted keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -751,9 +751,9 @@ static PyObject *keydatamp_pynew(PyTypeObject *ty, mp *x = 0; unsigned f = 0; keydata_pyobj *me = 0; - static char *kwlist[] = { "key", "flags", 0 }; + static const char *const kwlist[] = { "key", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST, convmp, &x, convflags, &f)) goto end; me = (keydata_pyobj *)ty->tp_alloc(ty, 0); @@ -798,7 +798,7 @@ static PyTypeObject keydatamp_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for large-integer keys.", +"KeyDataMP(KEY, [flags = 0]): key data for large-integer keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -827,9 +827,9 @@ static PyObject *keydatastr_pynew(PyTypeObject *ty, char *p; unsigned f = 0; keydata_pyobj *me = 0; - static char *kwlist[] = { "key", "flags", 0 }; + static const char *const kwlist[] = { "key", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:new", KWLIST, &p, convflags, &f)) goto end; me = (keydata_pyobj *)ty->tp_alloc(ty, 0); @@ -873,7 +873,7 @@ static PyTypeObject keydatastr_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for string keys.", +"KeyDataString(KEY, [flags = 0]): key data for string keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -902,9 +902,9 @@ static PyObject *keydataec_pynew(PyTypeObject *ty, ec x = EC_INIT; unsigned f = 0; keydata_pyobj *me = 0; - static char *kwlist[] = { "key", "flags", 0 }; + static const char *const kwlist[] = { "key", "flags", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST, convecpt, &x, convflags, &f)) goto end; me = (keydata_pyobj *)ty->tp_alloc(ty, 0); @@ -953,7 +953,7 @@ static PyTypeObject keydataec_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for elliptic-curve keys.", +"KeyDataECPt(KEY, [flags = 0]): key data for elliptic-curve keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1055,10 +1055,10 @@ static PyObject *keydatastruct_pynew(PyTypeObject *ty, char *p; keydata_pyobj *me = 0; key_data *kd = 0; - static char *kwlist[] = { "subkeys", 0 }; + static const char *const kwlist[] = { "subkeys", 0 }; Py_XINCREF(arg); Py_XINCREF(kw); - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:new", kwlist, &sub)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:new", KWLIST, &sub)) goto end; kd = key_newstruct(); if (sub) { @@ -1159,7 +1159,7 @@ static PyTypeObject keydatastruct_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key data for structured keys.", +"KeyDataStructured([subkeys = []]): key data for structured keys.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1388,11 +1388,12 @@ static PyObject *key_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) uint32 id; char *type; unsigned long exptime = KEXP_FOREVER; - static char *kwlist[] = { "keyfile", "id", "type", "exptime", 0 }; + static const char *const kwlist[] = + { "keyfile", "id", "type", "exptime", 0 }; key *k; int err; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&s|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&s|O&:new", KWLIST, keyfile_pytype, &kfobj, convu32, &id, &type, convulong, &exptime)) goto end; @@ -1450,9 +1451,9 @@ static PyObject *kmeth_extract(PyObject *me, PyObject *arg, PyObject *kw) PyObject *nameobj; char *name; FILE *fp; - static char *kwlist[] = { "file", "filter", 0 }; + static const char *const kwlist[] = { "file", "filter", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O&:extract", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O&:extract", KWLIST, &PyFile_Type, &file, convfilter, &f) || (fp = PyFile_AsFile(file)) == 0 || @@ -1471,9 +1472,9 @@ static PyObject *kmeth_fingerprint(PyObject *me, { ghash *h; key_filter f = { KF_NONSECRET, KF_NONSECRET }; - static char *kwlist[] = { "hash", "filter", 0 }; + static const char *const kwlist[] = { "hash", "filter", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:fingerprint", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:fingerprint", KWLIST, convghash, &h, convfilter, &f)) return (0); return (getbool(key_fingerprint(KEY_K(me), h, &f))); @@ -1649,7 +1650,7 @@ static PyTypeObject key_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Key object.", +"Key(KF, ID, TYPE, [exptime = KEXP_FOREVER]): key object.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1777,10 +1778,10 @@ static PyObject *keyfile_pynew(PyTypeObject *ty, char *file = 0; unsigned how = KOPEN_READ; keyfile_pyobj *rc = 0; - static char *kwlist[] = { "file", "how", "report", 0 }; + static const char *const kwlist[] = { "file", "how", "report", 0 }; Py_XINCREF(arg); Py_XINCREF(kw); - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|iO:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|iO:new", KWLIST, &file, &how, &ri.func)) goto end; if (ri.func && !PyCallable_Check(ri.func)) @@ -1836,10 +1837,10 @@ static PyObject *kfmeth_merge(PyObject *me, PyObject *arg, PyObject *kw) PyObject *x = 0; FILE *fp = 0; int rc; - static char *kwlist[] = { "file", "report", 0 }; + static const char *const kwlist[] = { "file", "report", 0 }; Py_XINCREF(arg); Py_XINCREF(kw); - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O:merge", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O:merge", KWLIST, &PyFile_Type, &x, &ri.func)) goto end; if (ri.func && !PyCallable_Check(ri.func)) @@ -1935,11 +1936,11 @@ static PyObject *kfmeth_newkey(PyObject *me, PyObject *arg, PyObject *kw) uint32 id; char *type; long exptime = KEXP_FOREVER; - static char *kwlist[] = { "id", "type", "exptime", 0 }; + static const char *const kwlist[] = { "id", "type", "exptime", 0 }; key *k; int err; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&s|l:newkey", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&s|l:newkey", KWLIST, convu32, &id, &type, &exptime)) goto end; if ((err = key_new(KEYFILE_KF(me), id, type, exptime, &k)) != 0) @@ -1957,9 +1958,9 @@ static PyObject *kfmeth_qtag(PyObject *me, PyObject *arg, PyObject *kw) char *tag; dstr d = DSTR_INIT; PyObject *rc = 0; - static char *kwlist[] = { "tag", "new", 0 }; + static const char *const kwlist[] = { "tag", "new", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O!:qtag", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O!:qtag", KWLIST, &tag, keydata_pytype, &newkdobj)) goto end; if (key_qtag(KEYFILE_KF(me), tag, &d, &k, &kd)) @@ -2045,7 +2046,8 @@ static PyTypeObject keyfile_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Keyring file.", +"KeyFile(FILE, [how = KOPEN_READ], [report = ?]): Keyring file.\n\ + calls REPORT(FILE, LINE, MSG) on problems", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/mp.c b/mp.c index ab15d3f..31eec24 100644 --- a/mp.c +++ b/mp.c @@ -528,9 +528,9 @@ static PyObject *mp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) mp *z; mp_pyobj *zz = 0; int radix = 0; - char *kwlist[] = { "x", "radix", 0 }; + static const char *const kwlist[] = { "x", "radix", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:new", kwlist, &x, &radix)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:new", KWLIST, &x, &radix)) goto end; if (MP_PYCHECK(x)) RETURN_OBJ(x); if (!good_radix_p(radix, 1)) VALERR("bad radix"); @@ -662,8 +662,8 @@ end: static PyObject *mpmeth_tostring(PyObject *me, PyObject *arg, PyObject *kw) { int radix = 10; - char *kwlist[] = { "radix", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|i:tostring", kwlist, &radix)) + static const char *const kwlist[] = { "radix", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|i:tostring", KWLIST, &radix)) goto end; if (!good_radix_p(radix, 0)) VALERR("bad radix"); return (mp_topystring(MP_X(me), radix, 0, 0, 0)); @@ -703,11 +703,11 @@ end: PyObject *arg, PyObject *kw) \ { \ long len = -1; \ - char *kwlist[] = { "len", 0 }; \ + static const char *const kwlist[] = { "len", 0 }; \ PyObject *rc = 0; \ \ if (!PyArg_ParseTupleAndKeywords(arg, kw, "|l:" #name, \ - kwlist, &len)) \ + KWLIST, &len)) \ goto end; \ if (len < 0) { \ len = mp_octets##c(MP_X(me)); \ @@ -766,10 +766,10 @@ static PyObject *mpmeth_tobuf(PyObject *me, PyObject *arg) static PyObject *mpmeth_primep(PyObject *me, PyObject *arg, PyObject *kw) { grand *r = &rand_global; - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; PyObject *rc = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&", kwlist, convgrand, &r)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&", KWLIST, convgrand, &r)) goto end; rc = getbool(pgen_primep(MP_X(me), r)); end: @@ -905,7 +905,7 @@ with an integer conversion.\n\ \n\ Notes:\n\ \n\ - * Use `//' for division. MPs don't have `/' division.", + * Use `//' for integer division: `/' gives exact rational division.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -937,10 +937,10 @@ static PyObject *meth__MP_fromstring(PyObject *me, PyObject *z = 0; mp *zz; mptext_stringctx sc; - char *kwlist[] = { "class", "x", "radix", 0 }; + static const char *const kwlist[] = { "class", "x", "radix", 0 }; if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|i:fromstring", - kwlist, &me, &p, &len, &r)) + KWLIST, &me, &p, &len, &r)) goto end; if (!good_radix_p(r, 1)) VALERR("bad radix"); sc.buf = p; sc.lim = p + len; @@ -1108,7 +1108,7 @@ static PyTypeObject *mpmul_pytype, mpmul_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"An object for multiplying many small integers.", +"MPMul(N_0, N_1, ....): an object for multiplying many small integers.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1301,10 +1301,10 @@ static void mpmont_pydealloc(PyObject *me) static PyObject *mpmont_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mpmont_pyobj *mm = 0; - char *kwlist[] = { "m", 0 }; + static const char *const kwlist[] = { "m", 0 }; mp *xx = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convmp, &xx)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx)) goto end; if (!MP_POSP(xx) || !MP_ODDP(xx)) VALERR("m must be positive and odd"); mm = (mpmont_pyobj *)ty->tp_alloc(ty, 0); @@ -1375,7 +1375,7 @@ static PyTypeObject *mpmont_pytype, mpmont_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"A Montgomery reduction context.", +"MPMont(N): a Montgomery reduction context.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1455,10 +1455,10 @@ static PyObject *mpbarrett_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mpbarrett_pyobj *mb = 0; - char *kwlist[] = { "m", 0 }; + static const char *const kwlist[] = { "m", 0 }; mp *xx = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convmp, &xx)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx)) goto end; if (!MP_POSP(xx)) VALERR("m must be positive"); mb = (mpbarrett_pyobj *)ty->tp_alloc(ty, 0); @@ -1514,7 +1514,7 @@ static PyTypeObject *mpbarrett_pytype, mpbarrett_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"A Barrett reduction context.", +"MPBarrett(N): a Barrett reduction context.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1585,10 +1585,10 @@ static PyObject *mpreduce_pynew(PyTypeObject *ty, { mpreduce_pyobj *mr = 0; mpreduce r; - char *kwlist[] = { "m", 0 }; + static const char *const kwlist[] = { "m", 0 }; mp *xx = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convmp, &xx)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx)) goto end; if (!MP_POSP(xx)) VALERR("m must be positive"); if (mpreduce_create(&r, xx)) VALERR("bad modulus (must be 2^k - ...)"); @@ -1642,7 +1642,7 @@ static PyTypeObject *mpreduce_pytype, mpreduce_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"A reduction context for reduction modulo primes of special form.", +"MPReduce(N): a reduction context for reduction modulo Solinas primes.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1721,7 +1721,7 @@ static PyObject *mpcrt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mpcrt_mod *v = 0; Py_ssize_t n, i = 0, j; - char *kwlist[] = { "mv", 0 }; + static const char *const kwlist[] = { "mv", 0 }; PyObject *q = 0, *x; mp *xx = MP_NEW, *y = MP_NEW, *g = MP_NEW; mpmul mm; @@ -1729,7 +1729,7 @@ static PyObject *mpcrt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) if (PyTuple_Size(arg) > 1) q = arg; - else if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", kwlist, &q)) + else if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &q)) goto end; Py_INCREF(q); if (!PySequence_Check(q)) TYERR("want a sequence of moduli"); @@ -1824,7 +1824,7 @@ static PyTypeObject *mpcrt_pytype, mpcrt_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"A context for the solution of Chinese Remainder Theorem problems.", +"MPCRT(SEQ): a context for solving Chinese Remainder Theorem problems.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1884,9 +1884,9 @@ static PyObject *gf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) mp *z; mp_pyobj *zz = 0; int radix = 0; - char *kwlist[] = { "x", "radix", 0 }; + static const char *const kwlist[] = { "x", "radix", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:gf", kwlist, &x, &radix)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:gf", KWLIST, &x, &radix)) goto end; if (GF_PYCHECK(x)) RETURN_OBJ(x); if (!good_radix_p(radix, 1)) VALERR("radix out of range"); @@ -2112,7 +2112,7 @@ but it's much easier to type than `p2' or `c2' or whatever.\n\ \n\ Notes:\n\ \n\ - * Use `//' for division. GFs don't have `/' division.", + * Use `//' for Euclidean division: `/' gives exact rational division.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -2144,10 +2144,10 @@ static PyObject *meth__GF_fromstring(PyObject *me, PyObject *z = 0; mp *zz; mptext_stringctx sc; - char *kwlist[] = { "class", "x", "radix", 0 }; + static const char *const kwlist[] = { "class", "x", "radix", 0 }; if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|i:fromstring", - kwlist, &me, &p, &len, &r)) + KWLIST, &me, &p, &len, &r)) goto end; if (!good_radix_p(r, 1)) VALERR("bad radix"); sc.buf = p; sc.lim = p + len; @@ -2262,10 +2262,10 @@ static PyObject *gfreduce_pynew(PyTypeObject *ty, { gfreduce_pyobj *mr = 0; gfreduce r; - char *kwlist[] = { "m", 0 }; + static const char *const kwlist[] = { "m", 0 }; mp *xx = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convgf, &xx)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convgf, &xx)) goto end; if (MP_ZEROP(xx)) ZDIVERR("modulus is zero!"); gfreduce_create(&r, xx); @@ -2323,7 +2323,7 @@ static PyTypeObject *gfreduce_pytype, gfreduce_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"A reduction context for reduction modulo sparse irreducible polynomials.", +"GFReduce(N): a context for reduction modulo sparse polynomials.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -2364,9 +2364,9 @@ static PyObject *gfn_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mp *p = 0, *beta = 0; gfn_pyobj *gg = 0; - char *kwlist[] = { "p", "beta", 0 }; + static const char *const kwlist[] = { "p", "beta", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST, convgf, &p, convgf, &beta)) goto end; gg = PyObject_New(gfn_pyobj, ty); @@ -2461,8 +2461,8 @@ static PyTypeObject gfn_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"An object for transforming elements of binary fields between polynomial\n\ -and normal basis representations.", +"GFN(P, BETA): an object for transforming elements of binary fields\n\ + between polynomial and normal basis representations.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/passphrase.c b/passphrase.c index 7e28f93..1796629 100644 --- a/passphrase.c +++ b/passphrase.c @@ -54,11 +54,11 @@ end: static PyObject *pixie_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { pixie_pyobj *rc = 0; - char *kwlist[] = { "socket", 0 }; + static const char *const kwlist[] = { "socket", 0 }; char *sock = 0; int fd; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s:new", kwlist, &sock)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s:new", KWLIST, &sock)) goto end; if ((fd = pixie_open(sock)) < 0) OSERR(sock); @@ -78,12 +78,12 @@ static PyObject *pixmeth_read(PyObject *me, PyObject *arg, PyObject *kw) { unsigned mode = PMODE_READ; char *tag; - char *kwlist[] = { "tag", "mode", 0 }; + static const char *const kwlist[] = { "tag", "mode", 0 }; PyObject *rc = 0; int r; char buf[1024]; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:read", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:read", KWLIST, &tag, convuint, &mode)) goto end; r = pixie_read(PIXIE_FD(me), tag, mode, buf, sizeof(buf)); @@ -152,7 +152,7 @@ static PyTypeObject pixie_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Passphrase pixie connection.", +"Pixie([socket = ?]): passphrase pixie connection.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -182,10 +182,10 @@ static PyObject *meth_ppread(PyObject *me, PyObject *arg, PyObject *kw) char *tag; unsigned f = PMODE_READ; PyObject *rc = 0; - char *kwlist[] = { "tag", "mode", 0 }; + static const char *const kwlist[] = { "tag", "mode", 0 }; char buf[1024]; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:ppread", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:ppread", KWLIST, &tag, convuint, &f)) goto end; if (passphrase_read(tag, f, buf, sizeof(buf))) diff --git a/pgen.c b/pgen.c index 9460310..9f9e1a2 100644 --- a/pgen.c +++ b/pgen.c @@ -57,10 +57,10 @@ end: static PyObject *pfilt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "x", 0 }; + static const char *const kwlist[] = { "x", 0 }; PyObject *xobj; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", kwlist, &xobj)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &xobj)) return (0); return (pfilt_pymake(ty, xobj)); } @@ -219,7 +219,7 @@ static PyTypeObject pfilt_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Small-primes filter.", +"PrimeFilter(X): small-primes filter.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -256,9 +256,9 @@ static PyObject *rabin_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mp *x = 0; rabin_pyobj *o = 0; - char *kwlist[] = { "x", 0 }; + static const char *const kwlist[] = { "x", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convmp, &x)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &x)) goto end; if (!MP_POSP(x) || MP_EVENP(x)) VALERR("must be positive and odd"); o = (rabin_pyobj *)ty->tp_alloc(ty, 0); @@ -352,7 +352,7 @@ static PyTypeObject rabin_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Rabin-Miller strong primality test.", +"RabinMiller(X): Rabin-Miller strong primality test.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -549,14 +549,14 @@ static int pgev_python(int rq, pgen_event *ev, void *p) PyObject *rc = 0; int st = PGEN_ABORT; long l; - char *meth[] = { - "pg_abort", "pg_done", "pg_begin", "pg_try", "pg_fail", "pg_pass" - }; + static const char *const meth[] = + { "pg_abort", "pg_done", "pg_begin", "pg_try", "pg_fail", "pg_pass" }; rq++; if (rq > N(meth)) SYSERR("event code out of range"); pyev = pgevent_pywrap(ev); - if ((rc = PyObject_CallMethod(pg->obj, meth[rq], "(O)", pyev)) == 0) + if ((rc = PyObject_CallMethod(pg->obj, (/*unconst*/ char *)meth[rq], + "(O)", pyev)) == 0) goto end; if (rc == Py_None) st = PGEN_TRY; @@ -696,9 +696,9 @@ static PyObject *pgstep_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mpw s; pgstep_pyobj *rc = 0; - char *kwlist[] = { "step", 0 }; + static const char *const kwlist[] = { "step", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", kwlist, convmpw, &s)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmpw, &s)) goto end; rc = (pgstep_pyobj *)ty->tp_alloc(ty, 0); rc->f.step = s; @@ -743,7 +743,7 @@ static PyTypeObject pgstep_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ - "Simple prime-number stepper with small-factors filter.", +"PrimeGenStepper(STEP): simple stepper with small-factors filter.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -770,9 +770,9 @@ static PyObject *pgjump_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { PyObject *o, *fobj; pgjump_pyobj *rc = 0; - char *kwlist[] = { "jump", 0 }; + static const char *const kwlist[] = { "jump", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", kwlist, &o) || + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &o) || (fobj = pfilt_pymake(pfilt_pytype, o)) == 0) goto end; rc = (pgjump_pyobj *)ty->tp_alloc(ty, 0); @@ -825,7 +825,7 @@ static PyTypeObject pgjump_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Stepper for larger steps, with small-factors filter.", +"PrimeGenJumper(JUMP): stepper for larger steps with small-factors filter.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -851,9 +851,9 @@ static PyTypeObject pgjump_pytype_skel = { static PyObject *pgtest_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { pgtest_pyobj *rc = 0; - char *kwlist[] = { 0 }; + static const char *const kwlist[] = { 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", kwlist)) goto end; + if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end; rc = (pgtest_pyobj *)ty->tp_alloc(ty, 0); rc->pg.proc = pgen_test; rc->pg.ctx = &rc->r; @@ -886,7 +886,7 @@ static PyTypeObject pgtest_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Rabin-Miller tester.", +"PrimeGenTester(): Rabin-Miller tester.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -928,13 +928,13 @@ static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw) struct excinfo exc = EXCINFO_INIT; pypgev step = { { 0 } }, test = { { 0 } }, evt = { { 0 } }; unsigned nsteps = 0, ntests = 0; - char *kwlist[] = { "start", "name", "stepper", "tester", "event", - "nsteps", "ntests", 0 }; + static const char *const kwlist[] = + { "start", "name", "stepper", "tester", "event", "nsteps", "ntests", 0 }; step.exc = &exc; step.ev.proc = pgen_filter; step.ev.ctx = &fc; test.exc = &exc; test.ev.proc = pgen_test; test.ev.ctx = &tc; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&O&O&:pgen", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&O&O&:pgen", KWLIST, convmp, &x, &p, convpgev, &step, convpgev, &test, convpgev, &evt, convuint, &nsteps, convuint, &ntests)) @@ -963,10 +963,11 @@ static PyObject *meth_strongprime_setup(PyObject *me, struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; PyObject *rc = 0; - char *kwlist[] = { "nbits", "name", "event", "rng", "nsteps", 0 }; + static const char *const kwlist[] = + { "nbits", "name", "event", "rng", "nsteps", 0 }; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", KWLIST, convuint, &nbits, &name, convpgev, &evt, convgrand, &r, convuint, &n)) @@ -992,10 +993,11 @@ static PyObject *meth_strongprime(PyObject *me, PyObject *arg, PyObject *kw) struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; PyObject *rc = 0; - char *kwlist[] = { "nbits", "name", "event", "rng", "nsteps", 0 }; + static const char *const kwlist[] = + { "nbits", "name", "event", "rng", "nsteps", 0 }; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", KWLIST, convuint, &nbits, &name, convpgev, &evt, convgrand, &r, convuint, &n)) @@ -1021,12 +1023,12 @@ static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw) unsigned on = 0; size_t i, nf = 0; PyObject *rc = 0, *vec; - char *kwlist[] = { "pbits", "qbits", "name", "event", "ievent", - "rng", "nsteps", 0 }; + static const char *const kwlist[] = + { "pbits", "qbits", "name", "event", "ievent", "rng", "nsteps", 0 }; mp *x = 0, **v = 0; ie.exc = oe.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|sO&O&O&O&:limlee", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|sO&O&O&O&:limlee", KWLIST, convuint, &pl, convuint, &ql, &p, convpgev, &oe, convpgev, &ie, convgrand, &r, convuint, &on)) diff --git a/pock b/pock new file mode 100644 index 0000000..ff62d8b --- /dev/null +++ b/pock @@ -0,0 +1,1065 @@ +#! /usr/bin/python +### -*- mode: python, coding: utf-8 -*- +### +### Tool for generating and verifying primality certificates +### +### (c) 2017 Straylight/Edgeware +### + +###----- Licensing notice --------------------------------------------------- +### +### This file is part of the Python interface to Catacomb. +### +### Catacomb/Python 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 2 of the License, or +### (at your option) any later version. +### +### Catacomb/Python 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 +### along with Catacomb/Python; if not, write to the Free Software Foundation, +### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +###-------------------------------------------------------------------------- +### Imported modules. + +from sys import argv, stdin, stdout, stderr +import os as OS +import itertools as I +import math as M +import optparse as OP + +import catacomb as C + +###-------------------------------------------------------------------------- +### Utilities. + +class ExpectedError (Exception): + """ + I represent an expected error, which should be reported in a friendly way. + """ + pass + +def prod(ff, one = 1): + """ + Return ONE times the product of the elements of FF. + + This is not done very efficiently. + """ + return reduce(lambda prod, f: prod*f, ff, one) + +def parse_label(line): + """ + Split LINE at an `=' character and return the left and right halves. + + The returned pieces have leading and trailing whitespace trimmed. + """ + eq = line.find('=') + if eq < 0: raise ExpectedError('expected `LABEL = ...\'') + return line[:eq].strip(), line[eq + 1:].strip() + +def parse_list(s, n): + l = s.split(',', n - 1) + if n is not None and len(l) != n: + raise ExpectedError('expected `,\'-separated list of %d items' % n) + return l + +def conv_int(s): + """Convert S to a integer.""" + try: return C.MP(s, 0) + except TypeError: raise ExpectedError('invalid integer `%s\'' % s) + +VERBOSITY = 1 + +class ProgressReporter (object): + """ + I keep users amused while the program looks for large prime numbers. + + My main strategy is the printing of incomprehensible runes. I can be + muffled by lowering by verbosity level. + + Prime searches are recursive in nature. When a new recursive level is + started, call `push'; and call `pop' when the level is finished. This must + be done around the top level too. + """ + def __init__(me): + """Initialize the ProgressReporter.""" + me._level = -1 + me._update() + def _update(me): + """ + Update our idea of whether we're active. + + We don't write inscrutable runes when inactive. The current policy is to + write nothing if verbosity is zero, to write runes for the top level only + if verbosity is 1, and to write runes always if verbosity is higher than + that. + """ + me._active = VERBOSITY >= 2 or (VERBOSITY == 1 and me._level == 0) + def push(me): + """Push a new search level.""" + me._level += 1 + me._update() + if me._level > 0: me.p('[') + else: me.p(';; ') + def pop(me): + """Pop a search level.""" + if me._level > 0: me.p(']') + else: me.p('\n') + me._level -= 1 + me._update() + def p(me, ch): + """Print CH as a progress rune.""" + if me._active: stderr.write(ch); stderr.flush() + +def combinations(r, v): + """ + Return an iterator which yields all combinations of R elements from V. + + V must be an indexable sequence. The each combination is returned as a + list, containing elements from V in their original order. + """ + + ## Set up the selection vector. C will contain the indices of the items of + ## V we've selected for the current combination. At all times, C contains + ## a strictly increasing sequence of integers in the interval [0, N). + n = len(v) + c = range(r) + + while True: + + ## Yield up the current combination. + vv = [v[i] for i in c] + yield vv + + ## Now advance to the next one. Find the last index in C which we can + ## increment subject to the rules. As we iterate downwards, i will + ## contain the index into C, and j will be the maximum acceptable value + ## for the corresponding item. We'll step the last index until it + ## reaches the limit, and then step the next one down, resetting the last + ## index, and so on. + i, j = r, n + while True: + + ## If i is zero here, then we've advanced everything as far as it will + ## go. We're done. + if i == 0: return + + ## Move down to the next index. + i -= 1; j -= 1 + + ## If this index isn't at its maximum value, then we've found the place + ## to step. + if c[i] != j: break + + ## Step this index on by one, and set the following indices to the + ## immediately following values. + j = c[i] + 1 + while i < r: c[i] = j; i += 1; j += 1 + +class ArgFetcher (object): + """ + I return arguments from a list, reporting problems when they occur. + """ + def __init__(me, argv, errfn): + """ + Initialize, returning successive arguments from ARGV. + + Errors are reported to ERRFN. + """ + me._argv = argv + me._argc = len(argv) + me._errfn = errfn + me._i = 0 + def arg(me, default = None, must = True): + """ + Return the next argument. + + If MUST is true, then report an error (to the ERRFN) if there are no more + arguments; otherwise, return the DEFAULT. + """ + if me._i >= me._argc: + if must: me._errfn('missing argument') + return default + arg = me._argv[me._i]; me._i += 1 + return arg + def int(me, default = None, must = True, min = None, max = None): + """ + Return the next argument converted to an integer. + + If MUST is true, then report an error (to the ERRFN) if there are no more + arguments; otherwise return the DEFAULT. Report an error if the next + argument is not a valid integer, or if the integer is beyond the MIN and + MAX bounds. + """ + arg = me.arg(default = None, must = must) + if arg is None: return default + try: arg = int(arg) + except ValueError: me._errfn('bad integer') + if (min is not None and arg < min) or (max is not None and arg > max): + me._errfn('out of range') + return arg + +###-------------------------------------------------------------------------- +### Sieving for small primes. + +class Sieve (object): + """ + I represent a collection of small primes, up to some chosen limit. + + The limit is available as the `limit' attribute. Let L be this limit; + then, if N < L^2 is some composite, then N has at least one prime factor + less than L. + """ + + ## Figure out the number of bits in a (nonnegative) primitive `int'. We'll + ## use a list of these as our sieve. + _NBIT = 15 + while type(1 << (_NBIT + 1)) == int: _NBIT += 1 + + def __init__(me, limit): + """ + Initialize a sieve holding all primes below LIMIT. + """ + + ## The sieve is maintained in the `_bits' attribute. This is a list of + ## integers, used as a bitmask: let 2 < n < L be an odd integer; then bit + ## (n - 3)/2 will be clear iff n is prime. Let W be the value of + ## `_NBIT', above; then bit W i + j in the sieve is stored in bit j of + ## `_bits[i]'. + + ## Store the limit for later inspection. + me.limit = limit + + ## Calculate the size of sieve we'll need and initialize the bit list. + n = (limit - 2)/2 + sievesz = (n + me._NBIT - 1)/me._NBIT + me._sievemax = sievesz*me._NBIT + me._bits = sievesz*[0] + + ## This is standard Sieve of Eratosthenes. For each index i: if + ## bit i is clear, then p = 2 i + 3 is prime, so set the bits + ## corresponding to each multiple of p, i.e., bits (k p - 3)/2 = + ## (2 k i + 3 - 3)/2 = k i for k > 1. + for i in xrange(me._sievemax): + if me._bitp(i): i += 1; continue + p = 2*i + 3 + if p >= limit: break + for j in xrange(i + p, me._sievemax, p): me._setbit(j) + i += 1 + + def _bitp(me, i): i, j = divmod(i, me._NBIT); return (me._bits[i] >> j)&1 + def _setbit(me, i): i, j = divmod(i, me._NBIT); me._bits[i] |= 1 << j + + def smallprimes(me): + """ + Return an iterator over the known small primes. + """ + yield 2 + n = 3 + for b in me._bits: + for j in xrange(me._NBIT): + if not (b&1): yield n + b >>= 1; n += 2 + +## We generate the sieve on demand. +SIEVE = None + +def initsieve(sievebits): + """ + Generate the sieve. + + Ensure that it can be used to check the primality of numbers up to (but not + including) 2^SIEVEBITS. + """ + global SIEVE + if SIEVE is not None: raise ValueError('sieve already defined') + if sievebits < 6: sievebits = 6 + SIEVE = Sieve(1 << (sievebits + 1)/2) + +###-------------------------------------------------------------------------- +### Primality checking. + +def small_test(p): + """ + Check that P is a small prime. + + If not, raise an `ExpectedError'. The `SIEVE' variable must have been + initialized. + """ + if p < 2: raise ExpectedError('%d too small' % p) + if SIEVE.limit*SIEVE.limit < p: + raise ExpectedError('%d too large for small prime' % p) + for q in SIEVE.smallprimes(): + if q*q > p: return + if p%q == 0: raise ExpectedError('%d divides %d' % (q, p)) + +def pock_test(p, a, qq): + """ + Check that P is prime using Pocklington's criterion. + + If not, raise an `ExpectedError'. + + Let Q be the product of the elements of the sequence QQ. The test works as + follows. Suppose p is the smallest prime factor of P. If A^{P-1} /== 1 + (mod P) then P is certainly composite (Fermat's test); otherwise, we have + established that the order of A in (Z/pZ)^* divides P - 1. Next, let t = + A^{(P-1)/q} for some prime factor q of Q, and let g = gcd(t - 1, P). If g + = P then the proof is inconclusive; if 1 < g < P then g is a nontrivial + factor of P, so P is composite; otherwise, t has order q in (Z/pZ)^*, so + (Z/pZ)^* contains a subgroup of size q, and therefore q divides p - 1. If + QQ is a sequence of distinct primes, and the preceding criterion holds for + all q in QQ, then Q divides p - 1. If Q^2 < P then the proof is + inconclusive; otherwise, let p' be any prime dividing P/p. Then p' >= p > + Q, so p p' > Q^2 > P; but p p' divides P, so this is a contradiction. + Therefore P/p has no prime factors, and P is prime. + """ + + ## We don't actually need the distinctness criterion. Suppose that q^e + ## divides Q. Then gcd(t - 1, P) = 1 implies that A^{(P-1)/q^{e-1}} has + ## order q^e in (Z/pZ)^*, which accounts for the multiplicity. + + Q = prod(qq) + if p < 2: raise ExpectedError('%d too small' % p) + if Q*Q <= p: + raise ExpectedError('too few Pocklington factors for %d' % p) + if pow(a, p - 1, p) != 1: + raise ExpectedError('%d is Fermat witness for %d' % (a, p)) + for q in qq: + if Q%(q*q) == 0: + raise ExpectedError('duplicate Pocklington factor %d for %d' % (q, p)) + g = p.gcd(pow(a, (p - 1)/q, p) - 1) + if g == p: + raise ExpectedError('%d order not multiple of %d mod %d' % (a, q, p)) + elif g != 1: + raise ExpectedError('%d divides %d' % (g, p)) + +def ecpp_test(p, a, b, x, y, qq): + """ + Check that P is prime using Goldwasser and Kilian's ECPP method. + + If not, raise an `ExpectedError'. + + Let Q be the product of the elements of the sequence QQ. Suppose p is the + smallest prime factor of P. Let g = gcd(4 A^3 + 27 B^2, P). If g = P then + the test is inconclusive; otherwise, if g /= 1 then g is a nontrivial + factor of P. Define E(GF(p)) = { (x, y) | y^2 = x^3 + A x + B } U { inf } + to be the elliptic curve over p with short-Weierstraß coefficients A and B; + we have just checked that this curve is not singular. If R = (X, Y) is not + a point on this curve, then the test is inconclusive. If Q R is not the + point at infinity, then the test fails; otherwise we deduce that P has + Q-torsion in E. Let S = (Q/q) R for some prime factor q of Q. If S is the + point at infinity then the test is inconclusive; otherwise, q divides the + order of S in E. If QQ is a sequence of distinct primes, and the preceding + criterion holds for all q in QQ, then Q divides the order of S. Therefore + #E(p) >= Q. If Q <= (qrrt(P) + 1)^2 then the test is inconclusive. + Otherwise, Hasse's theorem tells us that |p + 1 - #E(p)| <= 2 sqrt(p); + hence we must have p + 1 + 2 sqrt(p) = (sqrt(p) + 1)^2 >= #E(p) >= Q > + (qrrt(P) + 1)^2; so sqrt(p) + 1 > qrrt(P) + 1, i.e., p^2 > P. As for + Pocklington above, if p' is any prime factor of P/p, then p p' >= p^2 > P, + which is a contradiction, and we conclude that P is prime. + """ + + ## This isn't going to work if gcd(P, 6) /= 1: we're going to use the + ## large-characteristic addition formulae. + g = p.gcd(6) + if g != 1: raise ExpectedError('%d divides %d' % (g, p)) + + ## We want to check that Q > (qrrt(P) + 1)^2 iff sqrt(Q) > qrrt(P) + 1; but + ## calculating square roots is not enjoyable (partly because we have to + ## deal with the imprecision). Fortunately, some algebra will help: the + ## condition holds iff qrrt(P) < sqrt(Q) - 1 iff P < Q^2 - 4 Q sqrt(Q) + + ## 6 Q - 4 sqrt(Q) + 1 = Q (Q + 6) + 1 - 4 sqrt(Q) (Q + 1) iff Q (Q + 6) - + ## P + 1 > 4 sqrt(Q) (Q + 1) iff (Q (Q + 6) - P + 1)^2 > 16 Q (Q + 1)^2 + Q = prod(qq) + t, u = Q*(Q + 6) - p + 1, 4*(Q + 1) + if t*t <= Q*u*u: raise ExpectedError('too few subgroups for ECPP') + + ## Construct the curve. + E = C.PrimeField(p).ec(a, b) # careful: may not be a prime! + + ## Find the base point. + R = E(x, y) + if not R.oncurvep(): + raise ExpectedError('(%d, %d) is not on the curve' % (x, y)) + + ## Check that it has Q-torsion. + if Q*R: raise ExpectedError('(%d, %d) not a %d-torsion point' % (x, y, Q)) + + ## Now check the individual factors. + for q in qq: + if Q%(q*q) == 0: + raise ExpectedError('duplicate ECPP factor %d for %d' % (q, p)) + S = (Q/q)*R + if not S: + raise ExpectedError('(%d, %d) order not a multiple of %d' % (x, y, q)) + g = p.gcd(S._z) + if g != 1: + raise ExpectedError('%d divides %d' % (g, p)) + +###-------------------------------------------------------------------------- +### Proof steps and proofs. + +class BaseStep (object): + """ + I'm a step in a primality proof. + + I assert that a particular number is prime, and can check this. + + This class provides basic protocol for proof steps, mostly to do with + handling labels. + + The step's label is kept in its `label' attribute. It can be set by the + constructor, and is `None' by default. Users can modify this attribute if + they like. Labels beginning `$' are assumed to be internal and + uninteresting; other labels cause `check' lines to be written to the output + listing the actual number of interest. + + Protocol that proof steps should provide: + + label A string labelling the proof step and the associated prime + number. + + p The prime number which this step proves to be prime. + + check() Check that the proof step is actually correct, assuming that + any previous steps have already been verified. + + out(FILE) Write an appropriate encoding of the proof step to the output + FILE. + """ + def __init__(me, label = None, *arg, **kw): + """Initialize a proof step, setting a default label if necessary.""" + super(BaseStep, me).__init__(*arg, **kw) + me.label = label + def out(me, file): + """ + Write the proof step to an output FILE. + + Subclasses must implement a method `_out' which actually does the work. + Here, we write a `check' line to verify that the proof actually applies + to the number we wanted, if the label says that this is an interesting + step. + """ + me._out(file) + if me.label is not None and not me.label.startswith('$'): + file.write('check %s, %d, %d\n' % (me.label, me.p.nbits, me.p)) + +class SmallStep (BaseStep): + """ + I represent a claim that a number is a small prime. + + Such claims act as the base cases in a complicated primality proof. When + verifying, the claim is checked by trial division using a collection of + known small primes. + """ + def __init__(me, pp, p, *arg, **kw): + """ + Initialize a small-prime step. + + PP is the overall PrimeProof object of which this is a step; P is the + small number whose primality is asserted. + """ + super(SmallStep, me).__init__(*arg, **kw) + me.p = p + def check(me): + """Check that the number is indeed a small prime.""" + return small_test(me.p) + def _out(me, file): + """Write a small-prime step to the FILE.""" + file.write('small %s = %d\n' % (me.label, me.p)) + def __repr__(me): return 'SmallStep(%d)' % (me.p) + @classmethod + def parse(cls, pp, line): + """ + Parse a small-prime step from a LINE in a proof file. + + SMALL-STEP ::= `small' LABEL `=' P + + PP is a PrimeProof object holding the results from the previous steps. + """ + if SIEVE is None: raise ExpectedError('missing `sievebits\' line') + label, p = parse_label(line) + return cls(pp, conv_int(p), label = label) + +class PockStep (BaseStep): + """ + I represent a Pocklington certificate for a number. + + The number is not explicitly represented in a proof file. See `pock_test' + for the underlying mathematics. + """ + def __init__(me, pp, a, R, qqi, *arg, **kw): + """ + Inititialize a Pocklington step. + + PP is the overall PrimeProof object of which this is a step; A is the + generator of a substantial subgroup of units; R is a cofactor; and QQI is + a sequence of labels for previous proof steps. If Q is the product of + the primes listed in QQI, then the number whose primality is asserted is + 2 Q R + 1. + """ + super(PockStep, me).__init__(*arg, **kw) + me._a = a + me._R = R + me._qqi = qqi + me._qq = [pp.get_step(qi).p for qi in qqi] + me.p = prod(me._qq, 2*R) + 1 + def check(me): + """Verify a proof step based on Pocklington's theorem.""" + return pock_test(me.p, me._a, me._qq) + def _out(me, file): + """Write a Pocklington step to the FILE.""" + file.write('pock %s = %d, %d, [%s]\n' % \ + (me.label, me._a, + me._R, ', '.join('%s' % qi for qi in me._qqi))) + def __repr__(me): return 'PockStep(%d, %d, %s)' % (me._a, me._R, me._qqi) + @classmethod + def parse(cls, pp, line): + """ + Parse a Pocklington step from a LINE in a proof file. + + POCK-STEP ::= `pock' LABEL `=' A `,' R `,' `[' Q-LIST `]' + Q-LIST ::= Q [`,' Q-LIST] + + PP is a PrimeProof object holding the results from the previous steps. + """ + label, rest = parse_label(line) + a, R, qq = parse_list(rest, 3) + qq = qq.strip() + if not qq.startswith('[') or not qq.endswith(']'): + raise ExpectedError('missing `[...]\' around Pocklington factors') + return cls(pp, conv_int(a), conv_int(R), + [q.strip() for q in qq[1:-1].split(',')], label = label) + +class ECPPStep (BaseStep): + """ + I represent a Goldwasser--Kilian ECPP certificate for a number. + """ + def __init__(me, pp, p, a, b, x, y, qqi, *arg, **kw): + """ + Inititialize an ECPP step. + + PP is the overall PrimeProof object of which this is a step; P is the + number whose primality is asserted; A and B are the short Weierstraß + curve coefficients; X and Y are the base point coordinates; and QQI is a + sequence of labels for previous proof steps. + """ + super(ECPPStep, me).__init__(*arg, **kw) + me._a, me._b = a, b + me._x, me._y = x, y + me._qqi = qqi + me._qq = [pp.get_step(qi).p for qi in qqi] + me.p = p + def check(me): + """Verify a proof step based on Goldwasser and Kilian's theorem.""" + return ecpp_test(me.p, me._a, me._b, me._x, me._y, me._qq) + def _out(me, file): + """Write an ECPP step to the FILE.""" + file.write('ecpp %s = %d, %d, %d, %d, %d, [%s]\n' % \ + (me.label, me.p, me._a, me._b, me._x, me._y, + ', '.join('%s' % qi for qi in me._qqi))) + def __repr__(me): + return 'ECPPstep(%d, %d, %d, %d, %d, %s)' % \ + (me.p, me._a, me._b, me._x, me._y, me._qqi) + @classmethod + def parse(cls, pp, line): + """ + Parse an ECPP step from a LINE in a proof file. + + ECPP-STEP ::= `ecpp' LABEL `=' P `,' A `,' B `,' X `,' Y `,' + `[' Q-LIST `]' + Q-LIST ::= Q [`,' Q-LIST] + + PP is a PrimeProof object holding the results from the previous steps. + """ + label, rest = parse_label(line) + p, a, b, x, y, qq = parse_list(rest, 6) + qq = qq.strip() + if not qq.startswith('[') or not qq.endswith(']'): + raise ExpectedError('missing `[...]\' around ECPP factors') + return cls(pp, conv_int(p), conv_int(a), conv_int(b), + conv_int(x), conv_int(y), + [q.strip() for q in qq[1:-1].split(',')], label = label) + +def check(pp, line): + """ + Handle a `check' line in a proof file. + + CHECK ::= `check' LABEL, B, N + + Verify that the proof step with the given LABEL asserts the primality of + the integer N, and that 2^{B-1} <= N < 2^B. + """ + label, nb, p = parse_list(line, 3) + label, nb, p = label.strip(), conv_int(nb), conv_int(p) + pi = pp.get_step(label).p + if pi != p: + raise ExpectedError('check failed: %s = %d /= %d' % (label, pi, p)) + if p.nbits != nb: + raise ExpectedError('check failed: nbits(%s) = %d /= %d' % \ + (label, p.nbits, nb)) + if VERBOSITY: print ';; %s = %d [%d]' % (label, p, nb) + +def setsievebits(pp, line): + """ + Handle a `sievebits' line in a proof file. + + SIEVEBITS ::= `sievebits' N + + Ensure that the verifier is willing to accept small primes up to 2^N. + """ + initsieve(int(line)) + +class PrimeProof (object): + """ + I represent a proof of primality for one or more numbers. + + I can encode my proof as a line-oriented text file, in a simple format, and + read such a proof back to check it. + """ + + ## A table to dispatch on keywords read from a file. + STEPMAP = { 'small': SmallStep.parse, + 'pock': PockStep.parse, + 'ecpp': ECPPStep.parse, + 'sievebits': setsievebits, + 'check': check } + + def __init__(me): + """ + Initialize a proof object. + """ + me._steps = {} # Maps labels to steps. + me._stepseq = [] # Sequence of labels, in order. + me._pmap = {} # Maps primes to steps. + me._i = 0 + + def addstep(me, step): + """ + Add a new STEP to the proof. + + The STEP may have a label already. If not, a new internal label is + chosen. The proof step is checked before being added to the proof. The + label is returned. + """ + + ## If there's already a step for this prime, and the new step doesn't + ## have a label, then return the old one instead. + if step.label is None: + try: return me._pmap[step.p] + except KeyError: pass + + ## Make sure the step is actually correct. + step.check() + + ## Generate a label if the step doesn't have one already. + if step.label is None: step.label = '$t%d' % me._i; me._i += 1 + + ## If the label is already taken then we have a problem. + if step.label in me._steps: + raise ExpectedError('duplicate label `%s\'' % step.label) + + ## Store the proof step. + me._pmap[step.p] = step.label + me._steps[step.label] = step + me._stepseq.append(step.label) + return step.label + + def get_step(me, label): + """ + Check that LABEL labels a known step, and return that step. + """ + try: return me._steps[label] + except KeyError: raise ExpectedError('unknown label `%s\'' % label) + + def write(me, file): + """ + Write the proof to the given FILE. + """ + + ## Prefix the main steps with a `sievebits' line. + file.write('sievebits %d\n' % (2*(SIEVE.limit.bit_length() - 1))) + + ## Write the steps out one by one. + for label in me._stepseq: me._steps[label].out(file) + + def read(me, file): + """ + Read a proof from a given FILE. + + FILE ::= {STEP | CHECK | SIEVEBITS} [FILE] + STEP ::= SMALL-STEP | POCK-STEP + + Comments (beginning `;') and blank lines are ignored. Other lines begin + with a keyword. + """ + lastp = None + for lno, line in enumerate(file, 1): + line = line.strip() + if line.startswith(';'): continue + ww = line.split(None, 1) + if not ww: continue + w = ww[0] + if len(ww) > 1: tail = ww[1] + else: tail = '' + try: + try: op = me.STEPMAP[w] + except KeyError: + raise ExpectedError('unrecognized keyword `%s\'' % w) + step = op(me, tail) + if step is not None: + me.addstep(step) + lastp = step.p + except ExpectedError, e: + raise ExpectedError('%s:%d: %s' % (file.name, lno, e.message)) + return lastp + +###-------------------------------------------------------------------------- +### Finding provable primes. + +class BasePrime (object): + """ + I represent a prime number which has been found and can be proven. + + This object can eventually be turned into a sequence of proof steps and + added to a PrimeProof. This isn't done immediately, because some + prime-search strategies want to build a pool of provable primes and will + then select some subset of them to actually construct the number of final + interest. This way, we avoid cluttering the output proof with proofs of + uninteresting numbers. + + Protocol required. + + p The prime number in question. + + label(LABEL) Associate LABEL with this prime, and the corresponding proof + step. A label can be set in the constructor, or later using + this method. + + register(PP) Register the prime with a PrimeProof, adding any necessary + proof steps. Returns the label of the proof step for this + number. + + _mkstep(PP, **KW) + Return a proof step for this prime. + """ + def __init__(me, label = None, *args, **kw): + """Initialize a provable prime number object.""" + super(BasePrime, me).__init__(*args, **kw) + me._index = me._pp = None + me._label = label + def label(me, label): + """Set this number's LABEL.""" + me._label = label + def register(me, pp): + """ + Register the prime's proof steps with PrimeProof PP. + + Return the final step's label. + """ + if me._pp is not None: + assert me._pp == pp + else: + me._pp = pp + me._index = pp.addstep(me._mkstep(pp, label = me._label)) + ##try: me._index = pp.addstep(me._mkstep(pp, label = me._label)) + ##except: raise RuntimeError('generated proof failed sanity check') + return me._index + +class SmallPrime (BasePrime): + """I represent a prime small enough to be checked in isolation.""" + def __init__(me, p, *args, **kw): + super(SmallPrime, me).__init__(*args, **kw) + me.p = p + def _mkstep(me, pp, **kw): + return SmallStep(pp, me.p, **kw) + +class PockPrime (BasePrime): + """I represent a prime proven using Pocklington's theorem.""" + def __init__(me, p, a, qq, *args, **kw): + super(PockPrime, me).__init__(*args, **kw) + me.p = p + me._a = a + me._qq = qq + def _mkstep(me, pp, **kw): + return PockStep(pp, me._a, (me.p - 1)/prod((q.p for q in me._qq), 2), + [q.register(pp) for q in me._qq], **kw) + +def gen_small(nbits, label = None, p = None): + """ + Return a new small prime. + + The prime will be exactly NBITS bits long. The proof step will have the + given LABEL attached. Report progress to the ProgressReporter P. + """ + while True: + + ## Pick a random NBITS-bit number. + n = C.rand.mp(nbits, 1) + assert n.nbits == nbits + + ## If it's probably prime, then check it against the small primes we + ## know. If it passes then we're done. Otherwise, try again. + if n.primep(): + for q in SIEVE.smallprimes(): + if q*q > n: return SmallPrime(n, label = label) + if n%q == 0: break + +def gen_pock(nbits, nsubbits = 0, label = None, p = ProgressReporter()): + """ + Return a new prime provable using Pocklington's theorem. + + The prime N will be exactly NBITS long, of the form N = 2 Q R + 1. If + NSUBBITS is nonzero, then each prime factor of Q will be NSUBBITS bits + long; otherwise a suitable default will be chosen. The proof step will + have the given LABEL attached. Report progress to the ProgressReporter P. + + The prime numbers this function returns are a long way from being uniformly + distributed. + """ + + ## Pick a suitable value for NSUBBITS if we don't have one. + if not nsubbits: + + ## This is remarkably tricky. Picking about 1/3 sqrt(NBITS) factors + ## seems about right for large numbers, but there's serious trouble + ## lurking for small sizes. + nsubbits = int(3*M.sqrt(nbits)) + if nbits < nsubbits + 3: nsubbits = nbits//2 + 1 + if nbits == 2*nsubbits: nsubbits += 1 + + ## Figure out how many subgroups we'll need. + npiece = ((nbits + 1)//2 + nsubbits - 1)//nsubbits + p.push() + + ## Keep searching... + while True: + + ## Come up with a collection of known prime factors. + p.p('!'); qq = [gen(nsubbits, p = p) for i in xrange(npiece)] + Q = prod(q.p for q in qq) + + ## Come up with bounds on the cofactor. If we're to have N = 2 Q R + 1, + ## and 2^{B-1} <= N < 2^B, then we must have 2^{B-2}/Q <= R < 2^{B-1}/Q. + Rbase = (C.MP(0).setbit(nbits - 2) + Q - 1)//Q + Rwd = C.MP(0).setbit(nbits - 2)//Q + + ## Probe the available space of cofactors. If the space is kind of + ## narrow, then we want to give up quickly if we're not finding anything + ## suitable. + step = 0 + while step < Rwd: + step += 1 + + ## Pick a random cofactor and examine the number we ended up with. + ## Make sure it really does have the length we expect. + R = C.rand.range(Rwd) + Rbase + n = 2*Q*R + 1 + assert n.nbits == nbits + + ## As a complication, if NPIECE is 1, it's just about possible that Q^2 + ## <= n, in which case this isn't going to work. + if Q*Q < n: continue + + ## If n has small factors, then pick another cofactor. + if C.PrimeFilter.smallfactor(n) == C.PGEN_FAIL: continue + + ## Work through the small primes to find a suitable generator. The + ## value 2 is almost always acceptable, so don't try too hard here. + for a in I.islice(SIEVE.smallprimes(), 16): + + ## First, try the Fermat test. If that fails, then n is definitely + ## composite. + if pow(a, n - 1, n) != 1: p.p('.'); break + p.p('*') + + ## Work through the subgroup orders, checking that suitable powers of + ## a generate the necessary subgroups. + for q in qq: + if n.gcd(pow(a, (n - 1)/q.p, n) - 1) != 1: + p.p('@'); ok = False; break + else: + ok = True + + ## we're all good. + if ok: p.pop(); return PockPrime(n, a, qq, label = label) + +def gen(nbits, label = None, p = ProgressReporter()): + """ + Generate a prime number with NBITS bits. + + Give it the LABEL, and report progress to P. + """ + if SIEVE.limit >> (nbits + 1)/2: g = gen_small + else: g = gen_pock + return g(nbits, label = label, p = p) + +def gen_limlee(nbits, nsubbits, + label = None, qlfmt = None, p = ProgressReporter()): + """ + Generate a Lim--Lee prime with NBITS bits. + + Let p be the prime. Then we'll have p = 2 q_0 q_1 ... q_k, with all q_i at + least NSUBBITS bits long, and all but q_k exactly that long. + + The prime will be given the LABEL; progress is reported to P. The factors + q_i will be labelled by filling in the `printf'-style format string QLFMT + with the argument i. + """ + + ## Figure out how many factors (p - 1)/2 will have. + npiece = nbits//nsubbits + if npiece < 2: raise ExpectedError('too few pieces') + + ## Decide how big to make the pool of factors. + poolsz = max(3*npiece + 5, 25) # Heuristic from GnuPG + + ## Prepare for the main loop. + disp = nstep = 0 + qbig = None + p.push() + + ## Try to make a prime. + while True: + p.p('!') + + ## Construct a pool of NSUBBITS-size primes. There's a problem with very + ## small sizes: we might not be able to build a pool of distinct primes. + pool = []; qmap = {} + for i in xrange(poolsz): + for j in xrange(64): + q = gen(nsubbits, p = p) + if q.p not in qmap: break + else: + raise ExpectedError('insufficient diversity') + qmap[q.p] = q + pool.append(q) + + ## Work through combinations of factors from the pool. + for qq in combinations(npiece - 1, pool): + + ## Construct the product of the selected factors. + qsmall = prod(q.p for q in qq) + + ## Maybe we'll need to replace the large factor. Try not to do this + ## too often. DISP measures the large factor's performance at + ## producing candidates with the right length. If it looks bad then + ## we'll have to replace it. + if 3*disp*disp > nstep*nstep: + qbig = None + if disp < 0: p.p('<') + else: p.p('>') + + ## If we don't have a large factor, then make one. + if qbig is None: + qbig = gen(nbits - qsmall.nbits, p = p) + disp = 0; nstep = 0 + + ## We have a candidate. Calculate it and make sure it has the right + ## length. + n = 2*qsmall*qbig.p + 1 + nstep += 1 + if n.nbits < nbits: disp -= 1 + elif n.nbits > nbits: disp += 1 + elif C.PrimeFilter.smallfactor(n) == C.PGEN_FAIL: pass + else: + + ## The candidate has passed the small-primes test. Now check it + ## against Pocklington. + for a in I.islice(SIEVE.smallprimes(), 16): + + ## Fermat test. + if pow(a, n - 1, n) != 1: p.p('.'); break + p.p('*') + + ## Find a generator of a sufficiently large subgroup. + if n.gcd(pow(a, (n - 1)/qbig.p, n) - 1) != 1: p.p('@'); continue + ok = True + for q in qq: + if n.gcd(pow(a, (n - 1)/q.p, n) - 1) != 1: + p.p('@'); ok = False; break + + ## We're done. + if ok: + + ## Label the factors. + qq.append(qbig) + if qlfmt: + for i, q in enumerate(qq): q.label(qlfmt % i) + + ## Return the number we found. + p.pop(); return PockPrime(n, a, qq, label = label) + +###-------------------------------------------------------------------------- +### Main program. + +def __main__(): + global VERBOSITY + + ## Prepare an option parser. + op = OP.OptionParser( + usage = '''\ +pock [-qv] [-s SIEVEBITS] CMD ARGS... + gen NBITS + ll NBITS NSUBBITS + check [FILE]''', + description = 'Generate or verify certified prime numbers.') + op.add_option('-v', '--verbose', dest = 'verbosity', + action = 'count', default = 1, + help = 'print mysterious runes while looking for prime numbers') + op.add_option('-q', '--quiet', dest = 'quietude', + action = 'count', default = 0, + help = 'be quiet while looking for prime numbers') + op.add_option('-s', '--sievebits', dest = 'sievebits', + type = 'int', default = 32, + help = 'size (in bits) of largest small prime') + opts, argv = op.parse_args() + VERBOSITY = opts.verbosity - opts.quietude + p = ProgressReporter() + a = ArgFetcher(argv, op.error) + + ## Process arguments and do what the user asked. + w = a.arg() + + if w == 'gen': + ## Generate a prime with no special structure. + initsieve(opts.sievebits) + nbits = a.int(min = 4) + pp = PrimeProof() + p = gen(nbits, 'p', p = p) + p.register(pp) + pp.write(stdout) + + elif w == 'll': + ## Generate a Lim--Lee prime. + initsieve(opts.sievebits) + nbits = a.int(min = 4) + nsubbits = a.int(min = 4, max = nbits) + pp = PrimeProof() + p = gen_limlee(nbits, nsubbits, 'p', 'q_%d', p = p) + p.register(pp) + pp.write(stdout) + + elif w == 'check': + ## Check an existing certificate. + fn = a.arg(default = '-', must = False) + if fn == '-': f = stdin + else: f = open(fn, 'r') + pp = PrimeProof() + p = pp.read(f) + + else: + raise ExpectedError("unknown command `%s'" % w) + +if __name__ == '__main__': + prog = OS.path.basename(argv[0]) + try: __main__() + except ExpectedError, e: exit('%s: %s' % (prog, e.message)) + except IOError, e: exit('%s: %s' % (prog, e)) + +###----- That's all, folks -------------------------------------------------- diff --git a/pock.1 b/pock.1 new file mode 100644 index 0000000..6879bc5 --- /dev/null +++ b/pock.1 @@ -0,0 +1,853 @@ +.\" -*-nroff-*- +.\" +.\" Describe the primality certificate generator and checker +.\" +.\" (c) 2016 Straylight/Edgeware +.\" +. +.\"----- Licensing notice --------------------------------------------------- +.\" +.\" This file is part of the Python interface to Catacomb. +.\" +.\" Catacomb/Python 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 2 of the License, or +.\" (at your option) any later version. +.\" +.\" Catacomb/Python 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 +.\" along with Catacomb/Python; if not, write to the Free Software Foundation, +.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +. +.ie t \{\ +. if \n(.g \{\ +. fam P +. \} +. ds o \(bu +. ds ss \s7\v'-4p' +. ds se \v'4p'\s0 +. ds us \s7\v'2p' +. ds ue \v'-2p'\s0 +. ds *e \(*e +. ds mo \(mo +. ds sr \(sr +. ds cu \(cu +. ds ca \(ca +. ds iy \(if +. ds == \(== +. ds .. \&.\h'2p'.\h'2p'.\& +. ds /= \h'(\w'\*(=='-\w'/')/2'/\h'-(\w'\*(=='+\w'/')/2'\*(== +.\} +.el \{\ +. ds o o +. ds ss ^ +. ds se +. ds us _ +. ds ue +. ds *e \fIepsilon\fP +. ds mo in +. ds sr sqrt +. ds cu union +. ds ca isect +. ds iy infty +. ds == == +. ds .. \&...\& +. ds /= /== +.\} +.de hP +.IP +\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c +.. +. +.TH pock 1 "28 May 2017" "Straylight/Edgeware" "Catacomb cryptographic library" +.SH NAME +pock \- generate and verify primality certificates +. +.\"-------------------------------------------------------------------------- +.SH SYNOPSIS +. +.B pock +.RB [ \-qv ] +.I command +.IR [ arguments ...] +.PP +Subcommands: +.RS +.br +.B gen +.I nbits +.br +.B ll +.I nbits +.I nsubbits +.br +.B check +.RI [ file ] +.RE +. +.\"-------------------------------------------------------------------------- +.SH DESCRIPTION +. +Many cryptographic protocols make use of large prime numbers. +The usual way of determining primality in such circumstances +is due to Rabin and Miller. +Given a candidate +.I n +and a +.I witness +2 \(<= +.I a +< +.IR n , +the test answers either `composite' or `unknown'. +If +.I n +is prime then the test answers `unknown' for every witness +.IR a ; +if +.I n +is in fact composite +then the test answers `composite' +for at least three quarters of the possible witnesses. +If +.I n +is a composite, +then the witnesses +.I a +for which the test reports `unknown' +are called +.IR liars . +.PP +Naively, then, +to reduce the probability of falsely accepting a composite +below some bound \*(*e, +one must perform +\-(log\*(us2\*(ue \*(*e)/2 +iterations of the test, +with independent, uniformly distributed witnesses. +This is especially slow when high security levels are wanted, +both because tests take longer on larger candidate numbers, +and because one must do more tests +to reach the necessary higher confidence level. +.PP +The above is a worst-case bound: +very few composite numbers +.I n +have anywhere near +.IR n /4 +liars. +In situations such as RSA key generation, +the user generating the prime number is the only one +who must be convinced of the number's primality, +and they have valuable additional knowledge: +specifically that the candidate has been chosen at random +according to some suitable probability distribution. +In this case, one needs many fewer iterations, +and the number of iterations needed +.I decreases +with the size of the candidate being tested. +.PP +But in cases where many users must share some large prime parameter, +each of them must test the proposed prime separately, +and often they must pessimistically assume +that the number was chosen maliciously, +and the worst-case +.IR n /4 +bound is the best one can do using the Rabin\(enMiller test. +For large candidates, +this is inconveniently slow. +(Also, many implementations incorrectly use +a number of iterations suitable for randomly chosen primes +for testing candidates of unknown provenance.) +.PP +There +.I are +stronger probabilistic tests. +A combination of Rabin\(enMiller and +Grantham's Frobenius test +is known as the +Baillie\(enPSW test +(after Baillie, Pomerance, Selfridge, and Wagstaff); +there are +.I no +known composites which pass this test, +nor is it known how to construct any. +On the other hand, it's been conjectured that +infinitely many Baillie\(enPSW pseudoprimes exist. +While it may be reasonable to assume +the strength of the Baillie\(enPSW test, +it must be borne in mind that this +.I does +constitute a security assumption. +.PP +By contrast,the +.B pock +program will generate prime numbers +of sizes suitable for use in cryptography, +along with a +.I certificate of primality +which can be independently verified fairly efficiently. +Specifically, verifying a proof takes a little longer +than a single iteration of the Rabin\(enMiller probabilistic test. +It can also verify such certificates. +.PP +Note that the primes selected by +.B pock +are a long way from uniformly distributed. +Indeed, they have somewhat unusual structure, +but it seems unlikely that this structure +renders them unsuitable for e.g., discrete-logarithm cryptography. +. +.SS "Command line" +The following options are recognized. +.TP +.B "\-h, \-\-help" +Write help text to standard output and exit with status 0. +.TP +.B "\-q, \-\-quiet" +Be less verbose during prime generation or certificate verification. +.TP +.B "\-v, \-\-verbose" +Be more verbose during prime generation or certificate verification. +.TP +.BI "\-s, \-\-sievebits " b +When generating certificates, +require that the verifier can check numbers smaller than +.RI 2\*(ss b \*(se +without assistance. +Larger values lead to sightly shorter proofs, +but spend more time at startup constructing a sieve; +smaller values lead to somewhat longer proofs, +but spend less time constructing the sieve. +The default is 32, +which seems to work well in practice. +. +.SS "gen" +The +.B gen +command generates a prime +.I p +which is +.I nbits +long (i.e., +.RI 2\*(ss nbits \-1\*(se +< +.I p +< +.RI 2\*(ss nbits \*(se, +and writes a certificate to standard output. +By default, mysterious runes are written to standard error +to keep the user amused and informed about the operation's progress; +the +.B \-q +option suppresses the runes; +the +.B \-v +option produces more detailed runes. +. +.SS "ll" +The +.B ll +command generates a Lim\(enLee prime +.I p += +2 +.IR q \*(us0\*(ue +.IR q \*(us1\*(ue +\*(.. +.IR q \*(us k \*(ue ++ +1 +which is +.I nbits +long (i.e., +.RI 2\*(ss nbits \-1\*(se +< +.I p +< +.RI 2\*(ss nbits \*(se, +such that each +.IR q \*(us i \*(ue +is an +.IR nsubbits -bit +prime, for +0 \(<= +.I i +< +.IR k , +and +.IR q \*(us k \*(ue +is an +.IR nsubbits -bit +prime, +and writes a certificate to standard output. +By default, mysterious runes are written to standard error +to keep the user amused and informed about the operation's progress; +the +.B \-q +option suppresses the runes; +the +.B \-v +option produces more detailed runes. +. +.SS "check" +The +.B check +command reads a primality certificate from the named +.I file +or from standard input, +and checks it. +By default, +each +.B check +line in the certificate causes a line +.IP +.B ;; +.I label +.B = +.I n +.BI [ b ] +.PP +to be printed to standard output, +listing the prime's +.IR label , +value +.IR n , +and length +.I b +in bits; +this behaviour is inhibited by the +.B \-q +option. +. +.SS Runes +The following mysterious runes are printed during prime searches. +This information is provided for diagnostic purposes +and to satisfy idle curiosity: +later versions may print different runes, +or use the same rune characters to mean different things. +.TP +.B ! +Started to generate a large prime. +The next step is to generate a number of smaller primes. +Usually, this will only need to be done once. +.TP +.B . +Candidate failed a Fermat test. +.TP +.B * +Candidate passed a Fermat test. +This is usually the last rune for a prime search. +.TP +.B @ +A candidate generator failed to generate the necessary subgroup. +This is unusual. +.TP +.B < +For Lim\(enLee primes, +discarding the large prime +because it produces results which are too small. +.TP +.B > +For Lim\(enLee primes, +discarding the large prime +because it produces results which are too large. +.TP +.B [ +Starting a subsidiary prime search. +.TP +.B ] +Finished a subsidiary prime search. +. +.\"-------------------------------------------------------------------------- +.SH CERTIFICATE FORMAT +. +A certificate consists of a number of lines. +Blank lines, +and lines beginning with a +.RB ` ; ', +are ignored. +Other lines are as follows. +.TP +.BI "sievebits " b +Declares that the verifier is expected to be able to check +primes smaller than +.RI 2\*(ss b \*(se +for primality for itself. +A +.B sievebits +line must appear before the first +.B small +line. +.TP +.BI "small " label " = " p +Asserts that +.I p +is a small prime, +and associates it with the +.IR label . +It is an error if the label has been assigned by a previous line. +It is required that +1 < +.I p +< +.RI 2\*(ss b \*(se +and that +.I p +is prime. +Such small primes constitute the leaves of a proof tree. +.TP +.BI "pock " label " = " a ", " R ", [" i ", " j ", \fR\*(..\fB]" +Asserts that a number +.I n +(defined below) is prime by Pocklington's criterion, +and associates it with the +.IR label . +It is an error if the label has been assigned by a previous line. +.RS +.PP +The strings +.IR i , +.IR j , +\*(.. +must be labels assigned by earlier lines. +For each such label +.IR i , +let +.IR q \*(us i \*(ue +be the associated prime. +Let +.I Q += +.IR q \*(us i \*(ue +.IR q \*(us j \*(ue +\*(.. +be the product of these primes. +Let +.I n += +.RI 2 QR ++ +1. +It is required that: +.hP 1. +The +.IR q \*(us i \*(ue +are distinct. +.hP 2. +.IR Q \*(ss2\*(se +\(>= +.IR n . +.hP 3. +.IR a \*(ss n \-1\*(se +\*(== +1 +(mod +.IR n ). +.hP 4. +.RI gcd( a \*(ss( n \-1)/ q \*(se +\- +1, +.IR n ) += +1 +for each prime +.IR q +dividing +.IR Q . +.PP +To see why this works, let +.I p +be the smallest prime factor of +.IR n . +From +.B 3 +we see that +the order of +.I a +in +.RB ( Z /\fIp Z )\*(ss\(**\*(se +divides +.I n +\- +1. +Consider some prime +.I q +dividing +.I Q +and let +.I t += +.IR a \*(ss( n \-1)/ q \*(se; +then +.I t +has order dividing +.IR q . +From +.BR 4 , +we have +.I t +\*(/= +1 +(mod +.IR p ), +and hence +.I t +has order precisely +.I q +in +.RB ( Z /\fIp Z )\*(ss\(**\*(se. +This implies that +.I q +divides +.I p +\- +1. +Since this holds for each prime +.I q +dividing +.IR Q , +and, +from +.BR 1 , +these primes are distinct, +we deduce that +.I Q +divides +.I p +\- +1 +and hence that +.I p +> +.IR Q . +Let +.IR p \(fm +be any prime factor of +.IR n / p . +Then +.IR p \(fm +\(>= +.I p +> +.I Q +so, +from +.BR 2 , +.IR pp \(fm +> +.IR Q \*(ss2\*(se +\(>= +.IR n ; +but +.IR pp \(fm +divides +.I n +so this is a contradiction. +We must conclude that +.IR p \(fm +does not exist, +and +.I n +must be prime. +.RE +.TP +.BI "ecpp " label " = " n ", " A ", " B ", " x ", " y ", [" i ", " j ", \fR\*(..\fB]" +Asserts that the number +.I n +is prime by Goldwasser and Kilian's ECPP method, +and associates it with the +.IR label . +It is an error if the label has been assigned by a previous line. +.RS +.PP +The strings +.IR i , +.IR j , +\*.. +must be labels assigned by earlier lines. +For each such label +.IR i , +let +.IR q \*(us i \*(ue +be the associated prime. +Let +.I Q += +.IR q \*(us i \*(ue +.IR q \*(us j \*(ue +\*(.. +be the product of these primes. +Define +.I E\*(usn\*(ue += { +.RI ( x ", " y ) +\*(mo +.RB ( Z /\fIn Z )\*(ss2\*(se +| +.IR y \*(ss2\*(se += +.IR x \*(ss3\*(se ++ +.I Ax ++ +.I B +} +\*(cu +{ \*(iy }; +if +.I n +is prime and the curve is not singular +then this is the elliptic curve over +.RI GF( n ) +with short-Weierstrass coefficients +.I A +and +.IR B . +Let +.I R += +.RI ( x , +.IR y ). +It is required that: +.hP 1. +.I g += +.RI gcd(4 a \*(ss3\*(se ++ +.RI 27 b \*(ss2\*(se, +.IR n ) += 1. +.hP 2. +.I R +\*(mo +.IR E\*(usn\*(ue ; +i.e., +.IR y \*(ss2\*(se +\*(== +.IR x \*(ss3\*(se ++ +.I Ax ++ +.I B +(mod +.IR n ). +.hP 3. The +.I q\*(usi\*(ue +are distinct. +.hP 4. +.IR QR , +the elliptic-curve scalar product of the point +.I R +by the integer +.IR Q , +calculated as if +.I E +is a true elliptic curve, +is the point at infinity. +.hP 5. +.RI ( Q / q ) R +is finite for each prime +.I q +dividing +.IR Q . +.hP 6. +.I Q +> +.RI ( n \*(ss1/4\*(se ++ 1)\*(ss2\*(se. +.PP +To see why this test works, let +.I p +be the smallest prime factor of +.IR n , +and let +.I E\*(usp\*(ue += { +.RI ( x ", " y ) +\*(mo +.RI GF( p )\*(ss2\*(se +| +.IR y \*(ss2\*(se += +.IR x \*(ss3\*(se ++ +.I Ax ++ +.I B +} +\*(cu +{ \*(iy }. +From +.BR 1 , +.I g += 1, +.I E\*(usp\*(ue +is an elliptic curve. +(If 1 < +.I g +< +.I n +then +.I g +is a proper factor of +.I n +and +.I n +is certainly not prime; +if +.I g += +.I n +then the curve will be singular and the test fails.) +From +.BR 2 , +.I R +is a point on +.IR E\*(usp\*(ue . +From +.BR 4 , +.I R +has +.IR Q -torsion +in +.IR E\*(usp\*(ue . +Consider some prime +.I q +dividing +.I Q +and let +.I T += +.RI ( Q/q ) R ; +then +.I T +has torsion dividing +.IR q . +From +.BR 5 , +.RI ( Q/q ) R +\(!= 0, +and hence +.I T +has order precisely +.I q +in +.IR E\*(usp\*(ue . +This implies that +.I q +divides +.RI # E\*(usp\*(ue . +Since this holds for each prime +.I q +dividing +.IR Q , +and, from +.BR 3 , +the +.I q +are distinct, +we deduce that +.I Q +divides +.RI # E\*(usp\*(ue +and hence that +.RI # E\*(usp\*(ue +\(>= +.IR Q . +Hasse's theorem tells us that +.RI | p ++ 1 \- +.RI # E\*(usp\*(ue | +\(<= +.RI 2\*(sr p , +so, in particular, +.RI # E\*(usp\*(ue +\- +.I p +\- 1 +\(<= +.RI 2\*(sr p , +whence +.I p ++ 1 + +.RI 2\*(sr p += +.RI (\*(sr p ++ 1)\*(ss2\*(se +\(>= +.RI # E\*(usp\*(ue +\(>= +.IR Q +> +.RI ( n \*(ss1/4\*(se ++ 1)\*(ss2\*(se +(from +.BR 6 ); +so +.IR p\*(ss2\*(se +> +.IR n . +Let +.IR p \(fm +be any prime factor of +.IR n / p . +Then +.IR p \(fm +\(>= +.I p +and +.IR pp \(fm +\(>= +.IR p \*(ss2\*(se +> +.IR n ; +but +.IR pp \(fm +divides +.I n +so this is a contradiction. +We must conclude that +.IR p \(fm +does not exist, +and +.I n +must be prime. +.PP +Note that +.B pock +currently cannot generate proofs which use +.BR ecpp , +though it will verify them. +.RE +.TP +.BI "check " label ", " b ", " p +Verify that the prime associated with the +.I label +is equal to +.I p +and that it is +.I b +bits long; +i.e., that +.RI 2\*(ss b \-1\*(se +\(<= +.I p +< +.RI 2\*(ss b \*(se. +Unless +inhibited by +.BR \-q , +the label and value are printed to stdout during verification. +. +.\"-------------------------------------------------------------------------- +.SH "SEE ALSO" +.BR key (1). +.SH AUTHOR +Mark Wooding, +. +.\"----- That's all, folks -------------------------------------------------- diff --git a/pubkey.c b/pubkey.c index 0abb011..5680429 100644 --- a/pubkey.c +++ b/pubkey.c @@ -96,9 +96,9 @@ static PyObject *dsapub_pynew(PyTypeObject *ty, { PyObject *G, *p, *rng = rand_pyobj, *hash = sha_pyobj; PyObject *rc = 0; - char *kwlist[] = { "G", "p", "hash", "rng", 0 }; + static const char *const kwlist[] = { "G", "p", "hash", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", KWLIST, group_pytype, &G, ge_pytype, &p, gchash_pytype, &hash, @@ -112,7 +112,7 @@ end: static PyObject *dsameth_beginhash(PyObject *me, PyObject *arg) { if (!PyArg_ParseTuple(arg, ":beginhash")) return (0); - return (ghash_pywrap(DSA_HASH(me), gdsa_beginhash(DSA_D(me)), f_freeme)); + return (ghash_pywrap(DSA_HASH(me), gdsa_beginhash(DSA_D(me)))); } static PyObject *dsameth_endhash(PyObject *me, PyObject *arg) @@ -135,9 +135,9 @@ static PyObject *dsameth_sign(PyObject *me, PyObject *arg, PyObject *kw) Py_ssize_t n; mp *k = 0; PyObject *rc = 0; - char *kwlist[] = { "msg", "k", 0 }; + static const char *const kwlist[] = { "msg", "k", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:sign", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:sign", KWLIST, &p, &n, convmp, &k)) goto end; if (n != DSA_D(me)->h->hashsz) @@ -175,9 +175,9 @@ static PyObject *dsapriv_pynew(PyTypeObject *ty, { PyObject *G, *p = 0, *u, *rng = rand_pyobj, *hash = sha_pyobj; PyObject *rc = 0; - char *kwlist[] = { "G", "u", "p", "hash", "rng", 0 }; + static const char *const kwlist[] = { "G", "u", "p", "hash", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", KWLIST, group_pytype, &G, &u, ge_pytype, &p, @@ -247,7 +247,7 @@ static PyTypeObject dsapub_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"DSA public key information.", +"DSAPub(GROUP, P, [hash = sha], [rng = rand]): DSA public key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -295,7 +295,7 @@ static PyTypeObject dsapriv_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"DSA private key information.", +"DSAPriv(GROUP, U, [p = u G], [hash = sha], [rng = rand]): DSA private key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -323,9 +323,9 @@ static PyObject *kcdsapub_pynew(PyTypeObject *ty, { PyObject *G, *p, *rng = rand_pyobj, *hash = has160_pyobj; PyObject *rc = 0; - char *kwlist[] = { "G", "p", "hash", "rng", 0 }; + static const char *const kwlist[] = { "G", "p", "hash", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", KWLIST, group_pytype, &G, ge_pytype, &p, gchash_pytype, &hash, @@ -348,9 +348,9 @@ static PyObject *kcdsapriv_pynew(PyTypeObject *ty, { PyObject *G, *u, *p = 0, *rng = rand_pyobj, *hash = has160_pyobj; PyObject *rc = 0; - char *kwlist[] = { "G", "u", "p", "hash", "rng", 0 }; + static const char *const kwlist[] = { "G", "u", "p", "hash", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", KWLIST, group_pytype, &G, &u, ge_pytype, &p, @@ -366,7 +366,7 @@ end: static PyObject *kcdsameth_beginhash(PyObject *me, PyObject *arg) { if (!PyArg_ParseTuple(arg, ":beginhash")) return (0); - return (ghash_pywrap(DSA_HASH(me), gkcdsa_beginhash(DSA_D(me)), f_freeme)); + return (ghash_pywrap(DSA_HASH(me), gkcdsa_beginhash(DSA_D(me)))); } static PyObject *kcdsameth_endhash(PyObject *me, PyObject *arg) @@ -389,9 +389,9 @@ static PyObject *kcdsameth_sign(PyObject *me, PyObject *arg, PyObject *kw) Py_ssize_t n; mp *k = 0; PyObject *r = 0, *rc = 0; - char *kwlist[] = { "msg", "k", 0 }; + static const char *const kwlist[] = { "msg", "k", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:sign", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|O&:sign", KWLIST, &p, &n, convmp, &k)) goto end; if (n != DSA_D(me)->h->hashsz) @@ -467,7 +467,7 @@ static PyTypeObject kcdsapub_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"KCDSA public key information.", +"KCDSAPub(GROUP, P, [hash = sha], [rng = rand]): KCDSA public key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -515,7 +515,7 @@ static PyTypeObject kcdsapriv_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"KCDSA private key information.", +"KCDSAPriv(GROUP, U, [p = u G], [hash = sha], [rng = rand]): KCDSA private key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -569,9 +569,9 @@ static PyObject *rsapub_pynew(PyTypeObject *ty, { rsa_pub rp = { 0 }; rsapub_pyobj *o; - char *kwlist[] = { "n", "e", 0 }; + static const char *const kwlist[] = { "n", "e", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST, convmp, &rp.n, convmp, &rp.e)) goto end; if (!MP_ODDP(rp.n)) VALERR("RSA modulus must be even"); @@ -636,10 +636,10 @@ static PyObject *rsapriv_pynew(PyTypeObject *ty, { rsa_priv rp = { 0 }; PyObject *rng = Py_None; - char *kwlist[] = + static const char *const kwlist[] = { "n", "e", "d", "p", "q", "dp", "dq", "q_inv", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&O&O&O&O&O:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&O&O&O&O&O:new", KWLIST, convmp, &rp.n, convmp, &rp.e, convmp, &rp.d, convmp, &rp.p, convmp, &rp.q, @@ -711,9 +711,9 @@ static PyObject *rsameth_privop(PyObject *me, PyObject *arg, PyObject *kw) PyObject *rng = RSA_RNG(me); mp *x = 0; PyObject *rc = 0; - char *kwlist[] = { "x", "rng", 0 }; + static const char *const kwlist[] = { "x", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:privop", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:privop", KWLIST, convmp, &x, &rng)) goto end; if (rng != Py_None && !GRAND_PYCHECK(rng)) @@ -735,11 +735,12 @@ static PyObject *meth__RSAPriv_generate(PyObject *me, mp *e = 0; struct excinfo exc = EXCINFO_INIT; pypgev evt = { { 0 } }; - char *kwlist[] = { "class", "nbits", "event", "rng", "nsteps", "e", 0 }; + static const char *const kwlist[] = + { "class", "nbits", "event", "rng", "nsteps", "e", 0 }; PyObject *rc = 0; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST, &me, convuint, &nbits, convpgev, &evt, convgrand, &r, convuint, &n, convmp, &e)) @@ -815,7 +816,7 @@ static PyTypeObject rsapub_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"RSA public key information.", +"RSAPub(N, E): RSA public key.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -863,7 +864,8 @@ static PyTypeObject rsapriv_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"RSA private key information.", +"RSAPriv(..., [rng = rand]): RSA private key.\n\ + Keywords: n, e, d, p, q, dp, dq, q_inv; must provide enough", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -899,10 +901,10 @@ static PyObject *meth__p1crypt_encode(PyObject *me, octet *b = 0; size_t sz; mp *x; - char *kwlist[] = { "msg", "nbits", "ep", "rng", 0 }; + static const char *const kwlist[] = { "msg", "nbits", "ep", "rng", 0 }; p1.r = &rand_global; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|s#O&:encode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|s#O&:encode", KWLIST, &m, &msz, convulong, &nbits, &ep, &epsz, convgrand, &p1.r)) goto end; @@ -929,10 +931,10 @@ static PyObject *meth__p1crypt_decode(PyObject *me, octet *b = 0; size_t sz; mp *x = 0; - char *kwlist[] = { "ct", "nbits", "ep", "rng", 0 }; + static const char *const kwlist[] = { "ct", "nbits", "ep", "rng", 0 }; p1.r = &rand_global; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|s#O&:decode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|s#O&:decode", KWLIST, convmp, &x, convulong, &nbits, &ep, &epsz, convgrand, &p1.r)) goto end; @@ -960,10 +962,10 @@ static PyObject *meth__p1sig_encode(PyObject *me, octet *b = 0; size_t sz; mp *x; - char *kwlist[] = { "msg", "nbits", "ep", "rng", 0 }; + static const char *const kwlist[] = { "msg", "nbits", "ep", "rng", 0 }; p1.r = &rand_global; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|s#O&:encode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|s#O&:encode", KWLIST, &m, &msz, convulong, &nbits, &ep, &epsz, convgrand, &p1.r)) goto end; @@ -991,10 +993,11 @@ static PyObject *meth__p1sig_decode(PyObject *me, octet *b = 0; size_t sz; mp *x = 0; - char *kwlist[] = { "msg", "sig", "nbits", "ep", "rng", 0 }; + static const char *const kwlist[] = + { "msg", "sig", "nbits", "ep", "rng", 0 }; p1.r = &rand_global; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|s#O&:decode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|s#O&:decode", KWLIST, &hukairz, convmp, &x, convulong, &nbits, &ep, &epsz, convgrand, &p1.r)) goto end; @@ -1022,10 +1025,11 @@ static PyObject *meth__oaep_encode(PyObject *me, octet *b = 0; size_t sz; mp *x; - char *kwlist[] = { "msg", "nbits", "mgf", "hash", "ep", "rng", 0 }; + static const char *const kwlist[] = + { "msg", "nbits", "mgf", "hash", "ep", "rng", 0 }; o.r = &rand_global; o.cc = &sha_mgf; o.ch = &sha; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|O&O&s#O&:encode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|O&O&s#O&:encode", KWLIST, &m, &msz, convulong, &nbits, convgccipher, &o.cc, convgchash, &o.ch, @@ -1055,10 +1059,11 @@ static PyObject *meth__oaep_decode(PyObject *me, octet *b = 0; size_t sz; mp *x = 0; - char *kwlist[] = { "ct", "nbits", "mgf", "hash", "ep", "rng", 0 }; + static const char *const kwlist[] = + { "ct", "nbits", "mgf", "hash", "ep", "rng", 0 }; o.r = &rand_global; o.cc = &sha_mgf; o.ch = &sha; ep = 0; epsz = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&s#O&:decode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&s#O&:decode", KWLIST, convmp, &x, convulong, &nbits, convgccipher, &o.cc, convgchash, &o.ch, @@ -1089,10 +1094,11 @@ static PyObject *meth__pss_encode(PyObject *me, octet *b = 0; size_t sz; mp *x = 0; - char *kwlist[] = { "msg", "nbits", "mgf", "hash", "saltsz", "rng", 0 }; + static const char *const kwlist[] = + { "msg", "nbits", "mgf", "hash", "saltsz", "rng", 0 }; p.cc = &sha_mgf; p.ch = &sha; p.r = &rand_global; p.ssz = (size_t)-1; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|O&O&O&O&:encode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&|O&O&O&O&:encode", KWLIST, &m, &msz, convulong, &nbits, convgccipher, &p.cc, convgchash, &p.ch, @@ -1122,11 +1128,11 @@ static PyObject *meth__pss_decode(PyObject *me, size_t sz; int n; mp *x = 0; - char *kwlist[] = + static const char *const kwlist[] = { "msg", "sig", "nbits", "mgf", "hash", "saltsz", "rng", 0 }; p.cc = &sha_mgf; p.ch = &sha; p.r = &rand_global; p.ssz = (size_t)-1; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&O&|O&O&O&O&:decode", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&O&|O&O&O&O&:decode", KWLIST, &m, &msz, convmp, &x, convulong, &nbits, convgccipher, &p.cc, convgchash, &p.ch, @@ -1202,10 +1208,11 @@ XDHS(DEFXDH) int ph = phdflt; \ PyObject *rc = 0; \ octet pp[ED##_PUBSZ]; \ - char *kwlist[] = { "key", "msg", "pub", "perso", "phflag", 0 }; \ + static const char *const kwlist[] = \ + { "key", "msg", "pub", "perso", "phflag", 0 }; \ if (!PyArg_ParseTupleAndKeywords(arg, kw, \ "s#s#|s#s#O&:" #ed "_sign", \ - kwlist, \ + KWLIST, \ &k, &ksz, &m, &msz, &p, &psz, \ &c, &csz, convbool, &ph)) \ goto end; \ @@ -1229,10 +1236,11 @@ XDHS(DEFXDH) Py_ssize_t psz, csz = 0, msz, ssz; \ int ph = phdflt; \ PyObject *rc = 0; \ - char *kwlist[] = { "pub", "msg", "sig", "perso", "phflag", 0 }; \ + static const char *const kwlist[] = \ + { "pub", "msg", "sig", "perso", "phflag", 0 }; \ if (!PyArg_ParseTupleAndKeywords(arg, kw, \ "s#s#s#|s#O&:" #ed "_verify", \ - kwlist, \ + KWLIST, \ &p, &psz, &m, &msz, &s, &ssz, \ &c, &csz, convbool, &ph)) \ goto end; \ diff --git a/pwsafe b/pwsafe index c12f856..9de0b31 100644 --- a/pwsafe +++ b/pwsafe @@ -59,8 +59,8 @@ def die(msg): def cmd_create(av): ## Default crypto-primitive selections. - cipher = 'blowfish-cbc' - hash = 'rmd160' + cipher = 'rijndael-cbc' + hash = 'sha256' mac = None ## Parse the options. diff --git a/pwsafe.1 b/pwsafe.1 new file mode 100644 index 0000000..361ba95 --- /dev/null +++ b/pwsafe.1 @@ -0,0 +1,878 @@ +.\" -*-nroff-*- +.\" +.\" Describe the password safe +.\" +.\" (c) 2016 Straylight/Edgeware +.\" +. +.\"----- Licensing notice --------------------------------------------------- +.\" +.\" This file is part of the Python interface to Catacomb. +.\" +.\" Catacomb/Python 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 2 of the License, or +.\" (at your option) any later version. +.\" +.\" Catacomb/Python 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 +.\" along with Catacomb/Python; if not, write to the Free Software Foundation, +.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +. +.ie t \{\ +. if \n(.g \{\ +. fam P +. \} +. ds o \(bu +.\} +.el \{\ +. ds o o +.\} +. +.de hP +.IP +\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c +.. +. +.TH pwsafe 1 "13 May 2016" "Straylight/Edgeware" "Catacomb cryptographic library" +.SH NAME +pwsafe \- store passwords somewhat securely +. +.\"-------------------------------------------------------------------------- +.SH SYNOPSIS +. +.B pwsafe +.RB [ \-f +.IR file ] +.I command +.RI [ arguments +\&...] +.PP +Subcommands: +'RS +.B changepp +.br +.B copy +.I dest-file +.RI [ glob-pattern ] +.br +.B create +.RB [ \-c +.IR cipher ] +.RB [ \-d +.IR db-type ] +.RB [ \-h +.IR hash ] +.RB [ \-m +.IR mac ] +.RI [ pp-tag ] +.br +.B delete +.I tag +.br +.RB [ find ] +.I label +.br +.B list +.RI [ glob-pattern ] +.br +.B store +.I label +.RI [ value ] +.br +.B to-pixie +.RI [ label +.RI [ pixie-tag ]] +.br +.B xfer +.RB [ \-d +.IR db-type ] +.I dest-file +.RE +. +.\"-------------------------------------------------------------------------- +.SH DESCRIPTION +. +The +.B pwsafe +command maintains a database of passwords +or other short-ish textual secrets. +Each password is identified by a +.IR label . +The database is ultimately protected by a master password. +It should quite difficult for an adversary +who has the database file(s) +but not your master password +to find out what any of the stored passwords are, +or even what the labels are. +.PP +The +.B pwsafe +program will prompt you for the password as necessary. +If you are running the Catacomb +.BR pixie (1) +then it will first ask the pixie for any necessary passwords, +and use the pixie to remember the master password for a short while. +.PP +A number of database backends are available. +.TP +.B gdbm +Uses the GDBM database library to store a database as a single file. +Provided for compatibility; +not recommended for new databases. +.TP +.B sqlite +Uses the SQLite3 library to store a database as a single file. +SQLite3 has good performance and integrity properties. +.TP +.B flat +Stores the password database as a flat text file. +Not recommended for large databases because performance will be bad, +but the simple format admits easy hacking. +.TP +.B dir +Stores the password database as a directory structure. +This uses much more disk space than the alternatives, +and enumerating passwords is slow, +but the directory structure can easily be managed by +a version control system such as Git. +.PP +The following command-line options are available. +.TP +.B "\-h, \-\-help" +Print a help summary to standard output, +and exit with status zero. +.TP +.B "\-v, \-\-version" +Print the program's version number to standard output, +and exit with status zero. +.TP +.B "\-u, \-\-usage" +Print a very terse usage summary to standard output, +and exit with status zero. +.TP +.BI "\-f, \-\-file=" file +Use +.I file +as the password database file or directory. +If this is not specified, +then the value of the +.B PWSAFE +environment variable is used; +if that too is unset, then the default +.IB home /.pwsafe \fR, +is used, where +.I home +is the value of the +.B HOME +environment variable. +It is a fatal error if +.B HOME +is unset. +.PP +The provided commands are described in their own sections below. +. +.SS "create" +Create a new, empty password database. +As a safety check, +the file/directory named by the top-level +.B \-f +option (or default value) +must not exist. +.PP +You will be prompted (twice) for a master password for the database. +.PP +The optional positional argument is the tag +by which the database's master password +will be known to the passphrase pixie. +The default tag is +.BR pwsafe . +.PP +The following options are accepted. +.TP +.BI "\-c, \-\-cipher=" cipher +Use the Catacomb +.I cipher +to encrypt blobs. +Run +.B catcrypt show cipher +for a list. +The default is +.BR rijndael-cbc . +.TP +.BI "\-d, \-\-database=" db-type +Use the +.I db-type +database backend. +See above for a list of the supported backends. +Note that +.B gdbm +and +.B sqlite +depend on external modules, and may not be available. +The default is +.BR flat . +.TP +.BI "\-h, \-\-hash=" hash +Use the Catacomb +.I hash +for key derivation and password-label mangling. +Run +.B catcrypt show hash +for a list. +The default is +.BR sha256 . +.TP +.BI "\-m, \-\-mac=" mac +Use the Catacomb +.I mac +to protect the integrity of blobs. +Run +.B catcrypt show mac +for a list. +The default is +.IR hash -hmac +where +.I hash +is the hash function chosen by the +.B \-h +option. +. +.SS "changepp" +Change the master password for a database. +This will +.I not +re-encrypt all of the records, +so its utility is somewhat limited. +See also the +.B copy +command. +The program will prompt you for +the existing master password (if it's not known by the pixie) +and the new one (twice, always). +. +.SS "copy" +Copy password records from the +.I file +to the +.I dest-file +which must be an existing password database. +If a +.B glob-pattern +is given, +then only records whose +.I label +matches the pattern are copied; +otherwise all password records are copied. +Any existing passwords in the destination database with the same labels +will be overwritten. +.PP +The destination need not use the same database backend +or cryptographic parameters as the source. +.PP +You will be prompted for the necessary master passwords. +. +.SS "delete" +Delete the password with the given +.I label +from the database. +An error is reported if there is no such password. +.PP +You will be prompted for master password if necessary. +. +.SS "find" +Write the password with the given label to standard output, +followed by a newline. +.PP +You will be prompted for master password if necessary. +.PP +This is the default operation: +as a convenience, +you can write +.IP +.B pwsafe +.I label +.PP +rather than +.IP +.B pwsafe +.B find +.I label +.PP +if the +.I label +isn't the same as any of +.BR pwsafe 's +command names. +. +.SS "list" +Write the labels of the passwords in the database, +one per line, +to standard output. +(If labels contain newline characters, +you will end up with a mess.) +If a +.I glob-pattern +is supplied, +then only labels which match the pattern are written. +.PP +You will be prompted for master password if necessary. +. +.SS "store" +Store a password, associating it with the given +.IR label . +.PP +If a +.I value +is supplied on the command line, +then it is used as the password value. +(Note that command-line arguments can be seen +by other users of the system, +and may be recorded by the shell. +This is usually a bad idea.) +.PP +As a special case, if the +.I value +is +.BR \- , +then the password is read from the first line of standard input; +the trailing newline is removed. +The author commonly writes +.IP +.BI "gorp -fbase64 | pwsafe store " label " \-" +.PP +to set random passwords. +.PP +Finally, if no +.I value +is given, +then +.B pwsafe +will prompt twice for the password. +.PP +You will be prompted for the master password if necessary +.I before +the new password value is fetched. +.PP +If there is an existing password with the same +.I label +then it is overwritten. +. +.SS "topixie" +With no arguments, +store +.I all +of the passwords in the database in the pixie, +with correspondingly named tags. +This is probably a bad idea. +.PP +With a +.IR label , +store only the labelled password in the pixie. +With a +.IR pixie-tag , +use that as the tag; +otherwise use the +.IR label . +.PP +You will be prompted for the master password if necessary. +. +.SS "xfer" +Create a new database containing all of the records of an existing one. +.PP +This works at the storage-backend level. +The new database contains exactly the same metadata and passwords +as the original. +It is +.I not +necessary to enter a password: +the blobs are simply copied over without being decrypted. +.PP +The following options are accepted. +.TP +.BI "\-d, \-\-database=" db-type +Use the +.I db-type +database backend. +See above for a list of the supported backends. +Note that +.B gdbm +and +.B sqlite +depend on external modules, and may not be available. +The default is +.BR flat . +. +.\"-------------------------------------------------------------------------- +.SH TECHNICAL DETAILS +. +The password database contains two kinds of records: +.I metadata records +hold important information about the database itself, +and particularly the various cryptographic options +chosen when the database was created, +and the various internal keys used to secure the database; +while +.I password records +actually store your encrypted passwords. +The various backends store these kinds records in different ways; +see below for the gory details. +. +.SS Metadata +The metadata records are as follows. +.TP +.B cipher +The symmetric cipher used to encrypt data. +This names a Catacomb +.B cipher +class. +.TP +.B hash +The hash function used in various places. +This names a Catacomb +.B hash +class. +.TP +.B key +A blob, +protected by the +.I derived +keys (see below), +containing the +.I main +secrecy and integrity keys. +The blob payload consists of the main secrecy and integrity keys, +each prefixed by its 16-bit length (in network byte order) +and concatenated. +.TP +.B mac +The message authentication code used to protect integrity. +This names a Catacomb +.B mac +class. +.TP +.B magic +A blob containing a string +used to construct the database keys for password records; +see below. +The magic string is chosen at random +when the database is created, +and never changes; +it is the same length as the chosen hash function's output. +The blob is protected by the +.I main +keys. +.TP +.B salt +A random string +mixed into the key derivation process. +.TP +.B tag +The passphrase tag, +used to identify the master password +to the passphrase +.BR pixie (1). +. +.SS Keys +The following keys are used. +.TP +The \fImaster password\fP +Remembered (hopefully) by the user; +used to unlock the +.I main +keys. +.TP +The \fIderived\fP keys +A secrecy and integrity pair, +derived from the +.I master password +and +salt using a hash function. +.TP +The \fImain\fP keys +A secrecy and integrity pair, +kept in a blob in the database +(the +.B key +metadata item) +protected by the +.I derived +keys. +The main keys are generated at random +when the database is created +and they never change; +the Catacomb default key lengths are used. +. +.SS Deriving keys from the master password +The keys used for protecting the +.I main +secrecy and integrity keys +are derived by hashing strings of the form +.IB purpose : password \e0 salt \fR, +where +.I purpose +is +.B cipher +or +.B mac +to derive the secrecy and integrity keys, respectively. +The +.I salt +string is the value of the +.B salt +metadata item described below. +.PP +No attempt is made to make the key derivation slow; +.B pwsafe +takes the view that you are have been specifically targetted for attack +by a well-resourced adversary, +and that you +.I will +lose if your password is guessable. +. +.SS Making a blob +A +.I blob +contains a +.IR payload , +protecting its secrecy and integrity. +A blob is constructed using a pair of secrecy and integrity keys; +most blobs are protected with the +.I main +keys; +the main keys themselves are protected with the +.I derived +keys. +.PP +The steps to construct a blob are as follows. +.hP 1. +Choose a random IV of the appropriate length for the chosen +.BR cipher . +.hP 2. +Encrypt the blob payload +using the chosen +.B cipher +with the secrecy key +and the IV from step 1, +to form a ciphertext. +Prefix the ciphertext with the IV +to form an augmented ciphertext. +.hP 3. +Compute a tag over the augmented ciphertext from step 2 +using the chosen +.B mac +with the integrity key. +Prefix the augmented ciphertext with the tag +to form the blob. +.PP +(It seems more usual to put the tag on the end of the ciphertext, +but that turned out to be pointlessly harder to implement.) +. +.SS Password records +Conceptually, +password records are indexed with a textual +.I label +chosen by the user. +But users may want to not only keep their passwords secret, +but also information about +.I which +passwords they have. +The +.B pwsafe +program attempts to maintain the privacy of password record labels, +but it isn't completely successful, as we shall see. +Most critically, +the database backends tend to leak information about +the +.I order +in which records were added into the database. +.PP +At the database backend, +the key used for looking up a password record is a hash, +in binary: +specifically, it's a hash of +the record label +prefixed by the +.I magic +string which is the payload of the blob stored in the +.B magic +metadata record. +.PP +The value of the password record is a blob, +protected by the +.I main +keys, +whose payload consists of +.hP \*o +the 16-bit network-byte-order length of the record label; +.hP \*o +the record label itself; +.hP \*o +the 16-bit network-byte-order length of the password; +.hP \*o +the password itself; and +.hP \*o +zero or more zero-valued octets, +so as to make the payload a multiple of 256 octets long. +.PP +The padding serves to conceal the length of the label and password +from an adversary who has obtained a copy of the database. +. +.SS Backend formats +The various password-database backends +represent the records described above as follows. +.TP +.B gdbm +A GDBM-backed database is stored in a single file. +A metadata record with key +.I r +and value +.I v +is stored in a GDBM record also named +.IR r , +and with value +.I v ; +a password record with hash +.I h +and blob +.I b +is stored in a GDBM record named +.BI $ h +with value +.IR b , +both in raw binary. +.TP +.B sqlite +A SQLite-backed database is stored in a single file. +It contains two tables, +named +.B meta +and +.BR passwd . +The +.B meta +table has a primary key +.B name +and a further column +.BR value ; +a metadata record with key +.I r +and value +.I v +is held in a +.B meta +record +with +.B name +.I r +and +.B value +.IR v ; +additionally, there is a record with +.B name +.B $version +whose +.B value +is the schema version; +this is currently always 0. +The +.B passwd +table has a primary key +.B label +and a further column +.BR payload ; +a password record with hash +.I h +and blob +.I b +is stored in a +.B passwd +record with +.B label +.IR h +and +.B payload +.IR b , +both in raw binary. +.TP +.B flat +A flat-file-backed database is stored in a single file, +with one record per line. +The first line must be exactly +.RS +.IP +.B "### pwsafe password database" +.PP +Blank lines and lines beginning with a +.RB ` # ' +are ignored. +.PP +A metadata record named +.I r +with value +.I v +is stored as a line of the form +.IB r\fR\(fm = v\fR\(fm +where +.IR r \fR\(fm +and +.IR v \fR\(fm +are encodings of the strings +.I r +and +.I v +respectively. +If +.I r +consists only of letters, digits, +and the punctuation characters +.RB ` \- ', +.RB ` _ ', +.RB ` : ', +.RB ` . ', +and +.RB ` / ' +then +.IR r \fR\(fm +is simply +.IR r ; +otherwise +.IR r \fR\(fm +is formed by (simultaneously) replacing +each space character in +.I r +with +.RB ` + ' +and each other character +which is not a letter, digit, or +one of the punctuation characters listed above +with +.RB ` % ' +followed by that character's ASCII code in hex, +and prefixing the whole lot by +.RB ` ! '. +Similarly, +if +.I v +consists of letters, digits, +and the punctuation characters listed above, +then +.IR v \fR\(fm +is simply +.IR v ; +otherwise +.IR v \fR\(fm +consists of a +.RB ` ? ' +followed by the base64 encoding of +.IR v , +without any trailing +.RB ` = ' +characters. +.PP +A password record with hash +.I h +and blob +.I b +is stored as a line of the form +.BI $ h\fR\(fm = b\fR\(fm +where +.IR h \(fm +and +.IR b \(fm +are the base64 encodings of +.I h +and +.I b +respectively, +without trailing +.RB ` = ' +characters. +.PP +The records may appear in any order. +The file is completely rewritten when any change is made; +if the file is named +.I f +then this is done by writing the new contents to +.IB f .new +and then renaming +.IB f .new +to +.IR f . +.RE +.TP +.B dir +A directory-backed database is stored as a directory, +named +.I d +in the sequel. +The directory must contain a file +.IB d /meta +whose first line is +.RS +.IP +.B "### pwsafe password directory metadata" +.PP +and directories +.IB d /pw +and +.IB d /tmp \fR. +.PP +Metadata records are stored in file +.IB d /meta +with one record per line, +exactly as for the +.B file +backend described above. +.PP +A password record with hash +.I h +and blob +.I b +is stored as file named +.IB d /pw/ h \fR\(fm +where +.IR h \(fm +is the base64 encodings of +.I h +without trailing +.RB ` = ' +characters, +and with all +.RB ` / ' +characters +replaced by +.RB ` . 's, +whose content is +.IR b . +.PP +The directory +.IB d /tmp/ +is used in an unspecified manner +when creating new password-record files. +The +.IB d /meta +and +.IB d /pw/ h \fR\(fm +files are updated by creating a new temporary file and renaming. +.RE +. +.\"-------------------------------------------------------------------------- +. +.SH BUGS +This is quite an old program, +though the manpage is new. +It provides more footguns than is ideal. +. +.SH SEE ALSO +.BR catcrypt (1), +.BR pixie (1). +. +.SH AUTHOR +Mark Wooding, +. +.\"----- That's all, folks -------------------------------------------------- diff --git a/rand.c b/rand.c index 641b9bd..841a402 100644 --- a/rand.c +++ b/rand.c @@ -124,9 +124,9 @@ static PyObject *grmeth_mp(PyObject *me, PyObject *arg, PyObject *kw) { size_t l; mpw o = 0; - char *kwlist[] = { "bits", "or", 0 }; + static const char *const kwlist[] = { "bits", "or", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:mp", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:mp", KWLIST, convszt, &l, convmpw, &o)) goto end; if (grand_check(me)) return (0); @@ -214,10 +214,10 @@ end: static PyObject *grmeth_seedrand(PyObject *me, PyObject *arg, PyObject *kw) { - char *kwlist[] = { "rng", 0 }; + static const char *const kwlist[] = { "rng", 0 }; grand *r = GRAND_R(me); grand *rr = &rand_global; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:seedrand", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:seedrand", KWLIST, convgrand, &rr) || grand_check(me) || checkop(r, GRAND_SEEDRAND, "seedrand")) goto end; @@ -332,8 +332,8 @@ static PyTypeObject grand_pytype_skel = { static PyObject *lcrand_pynew(PyTypeObject *me, PyObject *arg, PyObject *kw) { uint32 n = 0; - char *kwlist[] = { "seed", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", kwlist, convu32, &n)) + static const char *const kwlist[] = { "seed", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convu32, &n)) return (0); return (grand_dopywrap(lcrand_pytype, lcrand_create(n), f_freeme)); } @@ -363,7 +363,7 @@ static PyTypeObject lcrand_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Linear congruential generator.", +"LCRand([seed = 0]): linear congruential generator.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -389,8 +389,8 @@ static PyTypeObject lcrand_pytype_skel = { static PyObject *fibrand_pynew(PyTypeObject *me, PyObject *arg, PyObject *kw) { uint32 n = 0; - char *kwlist[] = { "seed", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", kwlist, convu32, &n)) + static const char *const kwlist[] = { "seed", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convu32, &n)) return (0); return (grand_dopywrap(fibrand_pytype, fibrand_create(n), f_freeme)); } @@ -420,7 +420,7 @@ static PyTypeObject fibrand_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Fibonacci generator.", +"FibRand([seed = 0]): Fibonacci generator.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -503,10 +503,10 @@ static PyObject *trmeth_timer(PyObject *me, PyObject *arg) static PyObject *truerand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { - char *kwlist[] = { 0 }; + static const char *const kwlist[] = { 0 }; grand *r; PyObject *rc = 0; - if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", kwlist)) goto end; + if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end; r = rand_create(); r->ops->misc(r, RAND_NOISESRC, &noise_source); r->ops->misc(r, RAND_SEED, 160); @@ -565,7 +565,7 @@ static PyTypeObject truerand_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"True random number source.", +"TrueRand(): true random number source.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -639,11 +639,11 @@ static const gccrand_info *const gcrandtab[] = { static PyObject *gcrand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { const gccrand_info *info = GCCRAND_INFO(ty); - static char *kwlist[] = { "key", 0 }; + static const char *const kwlist[] = { "key", 0 }; char *k; Py_ssize_t n; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &k, &n)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &k, &n)) goto end; if (keysz(n, info->keysz) != n) VALERR("bad key length"); return (grand_dopywrap(ty, info->func(k, n), f_freeme)); @@ -655,11 +655,11 @@ static PyObject *gcirand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { const gccrand_info *info = GCCRAND_INFO(ty); uint32 i = 0; - static char *kwlist[] = { "key", "i", 0 }; + static const char *const kwlist[] = { "key", "i", 0 }; char *k; Py_ssize_t n; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#O&:new", KWLIST, &k, &n, convu32, &i)) goto end; if (keysz(n, info->keysz) != n) VALERR("bad key length"); @@ -673,11 +673,11 @@ end: static PyObject *gcnrand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { const gccrand_info *info = GCCRAND_INFO(ty); - static char *kwlist[] = { "key", "nonce", 0 }; + static const char *const kwlist[] = { "key", "nonce", 0 }; char *k, *n; Py_ssize_t ksz, nsz; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#:new", KWLIST, &k, &ksz, &n, &nsz)) goto end; if (keysz(ksz, info->keysz) != ksz) VALERR("bad key length"); @@ -693,15 +693,18 @@ static PyObject *gcshakyrand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { const gccrand_info *info = GCCRAND_INFO(ty); - static char *kwlist_shake[] = { "key", "func", "perso", 0 }; - static char *kwlist_func[] = { "key", "perso", 0 }; + static const char + *const kwlist_shake[] = { "key", "func", "perso", 0 }, + *const kwlist_func[] = { "key", "perso", 0 }; char *k, *f = 0, *p = 0; Py_ssize_t ksz, fsz = 0, psz = 0; if ((info->f&RNGF_MASK) == RNG_SHAKE - ? !PyArg_ParseTupleAndKeywords(arg, kw, "s#|s#s#:new", kwlist_shake, + ? !PyArg_ParseTupleAndKeywords(arg, kw, "s#|s#s#:new", + (/*unconst*/ char **)kwlist_shake, &k, &ksz, &f, &fsz, &p, &psz) - : !PyArg_ParseTupleAndKeywords(arg, kw, "s#|s#:new", kwlist_func, + : !PyArg_ParseTupleAndKeywords(arg, kw, "s#|s#:new", + (/*unconst*/ char **)kwlist_func, &k, &ksz, &p, &psz)) goto end; if (keysz(ksz, info->keysz) != ksz) VALERR("bad key length"); @@ -938,9 +941,9 @@ static PyObject *sslprf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) Py_ssize_t ksz, ssz; const gchash *hco = &md5, *hci = &sha; PyObject *rc = 0; - char *kwlist[] = { "key", "seed", "ohash", "ihash", 0 }; + static const char *const kwlist[] = { "key", "seed", "ohash", "ihash", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&O&:new", KWLIST, &k, &ksz, &s, &ssz, convgchash, &hco, convgchash, &hci)) goto end; @@ -955,9 +958,9 @@ static PyObject *tlsdx_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) Py_ssize_t ksz, ssz; const gcmac *mc = &sha_hmac; PyObject *rc = 0; - char *kwlist[] = { "key", "seed", "mac", 0 }; + static const char *const kwlist[] = { "key", "seed", "mac", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&:new", KWLIST, &k, &ksz, &s, &ssz, convgcmac, &mc)) goto end; @@ -972,9 +975,9 @@ static PyObject *tlsprf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) Py_ssize_t ksz, ssz; const gcmac *mcl = &md5_hmac, *mcr = &sha_hmac; PyObject *rc = 0; - char *kwlist[] = { "key", "seed", "lmac", "rmac", 0 }; + static const char *const kwlist[] = { "key", "seed", "lmac", "rmac", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#s#|O&O&:new", KWLIST, &k, &ksz, &s, &ssz, convgcmac, &mcl, convgcmac, &mcr)) goto end; @@ -1008,7 +1011,8 @@ static PyTypeObject sslprf_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Random number generator for SSL master secret.", +"SSLRand(KEY, SEED, [ohash = md5], [ihash = sha]):\n\ + RNG for SSL master secret.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1056,7 +1060,8 @@ static PyTypeObject tlsdx_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"TLS data expansion function.", +"TLSDataExpansion(KEY, SEED, [mac = sha_hmac]):\n\ + TLS data expansion function.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1104,7 +1109,8 @@ static PyTypeObject tlsprf_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"TLS pseudorandom function.", +"TLSPRF(KEY, SEED, [lmac = md5_hmac], [rmac = sha_hmac]):\n\ + TLS pseudorandom function.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1134,9 +1140,9 @@ static PyObject *dsarand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) char *p; Py_ssize_t sz; PyObject *rc = 0; - char *kwlist[] = { "seed", 0 }; + static const char *const kwlist[] = { "seed", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", kwlist, &p, &sz)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#:new", KWLIST, &p, &sz)) goto end; rc = grand_dopywrap(ty, dsarand_create(p, sz), f_freeme); end: @@ -1184,7 +1190,7 @@ static PyTypeObject dsarand_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Pseudorandom number generator for constructing DSA parameters.", +"DSARand(SEED): pseudorandom number generator for DSA parameters.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1213,9 +1219,9 @@ static PyObject *bbs_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { mp *n = 0, *x = MP_TWO; PyObject *rc = 0; - char *kwlist[] = { "n", "x", 0 }; + static const char *const kwlist[] = { "n", "x", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST, convmp, &n, convmp, &x)) goto end; rc = grand_dopywrap(ty, bbs_rand(n, x), f_freeme); @@ -1262,7 +1268,8 @@ static PyObject *bbsget_x(PyObject *me, void *hunoz) static int bbsset_x(PyObject *me, PyObject *val, void *hunoz) { mp *x = 0; grand *r = GRAND_R(me); int rc = -1; if (!val) NIERR("__del__"); - if ((x = getmp(val)) == 0) goto end; r->ops->misc(r, BBS_SET, x); rc = 0; + if ((x = getmp(val)) == 0) goto end; + r->ops->misc(r, BBS_SET, x); rc = 0; end: mp_drop(x); return (rc); } @@ -1315,7 +1322,7 @@ static PyTypeObject bbs_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Blum-Blum-Shub strong pseudorandom number generator.", +"BlumBlumShub(N, [x = 2]): Blum-Blum-Shub pseudorandom number generator.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -1350,9 +1357,9 @@ static PyObject *bbspriv_pynew(PyTypeObject *ty, { mp *p = 0, *q = 0, *n = 0, *x = MP_TWO; bbspriv_pyobj *rc = 0; - char *kwlist[] = { "n", "p", "q", "seed", 0 }; + static const char *const kwlist[] = { "n", "p", "q", "seed", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&:new", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&:new", KWLIST, convmp, &n, convmp, &p, convmp, &q, convmp, &x)) goto end; @@ -1380,11 +1387,12 @@ static PyObject *meth__BBSPriv_generate(PyObject *me, pypgev evt = { { 0 } }; unsigned nbits, n = 0; grand *r = &rand_global; - char *kwlist[] = { "class", "nbits", "event", "rng", "nsteps", "seed", 0 }; + static const char *const kwlist[] = + { "class", "nbits", "event", "rng", "nsteps", "seed", 0 }; bbspriv_pyobj *rc = 0; evt.exc = &exc; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", kwlist, + if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST, &me, convuint, &nbits, convpgev, &evt, convgrand, &r, convuint, &n, convmp, &x)) goto end; @@ -1475,7 +1483,8 @@ static PyTypeObject bbspriv_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Blum-Blum-Shub strong pseudorandom generator, with private key.", +"BBSPriv(..., [seed = 2]): Blum-Blum-Shub, with private key.\n\ + Keywords: n, p, q; must provide at least two", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/setup.py b/setup.py index 80a1547..d778177 100755 --- a/setup.py +++ b/setup.py @@ -3,8 +3,8 @@ import distutils.core as DC import mdwsetup as MS -MS.pkg_config('catacomb', '2.3.0.1+4') -MS.pkg_config('mLib', '2.0.4') +MS.pkg_config('catacomb', '2.5.0') +MS.pkg_config('mLib', '2.2.2.1') cat = DC.Extension('catacomb._base', ['catacomb.c', 'bytestring.c', 'buffer.c', @@ -23,7 +23,8 @@ MS.setup(name = 'catacomb-python', author_email = 'mdw@distorted.org.uk', license = 'GNU General Public License', packages = ['catacomb'], - scripts = ['pwsafe'], + scripts = ['pock', 'pwsafe'], + data_files = [('share/man/man1', ['pock.1', 'pwsafe.1'])], genfiles = [MS.Generate('algorithms.h')], unittest_dir = "t", unittests = ["t-misc", "t-algorithms", "t-bytes", "t-buffer", diff --git a/share.c b/share.c index 2caa718..77da423 100644 --- a/share.c +++ b/share.c @@ -117,8 +117,8 @@ static PyObject *gfsharesplit_pynew(PyTypeObject *ty, unsigned t; grand *r = &rand_global; gfshare_pyobj *s; - char *kwlist[] = { "threshold", "secret", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&s#|O&:new", kwlist, + static const char *const kwlist[] = { "threshold", "secret", "rng", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&s#|O&:new", KWLIST, convuint, &t, &p, &n, convgrand, &r)) goto end; if (!t || t > 255) VALERR("threshold must be nonzero and < 256"); @@ -174,7 +174,8 @@ static PyTypeObject gfsharesplit_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Binary field secret sharing: split secret into shares.", +"GFShareSplit(THRESHOLD, SECRET, [rng = rand]): binary-field sharing:\n\ + split secret into shares.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -202,8 +203,8 @@ static PyObject *gfsharejoin_pynew(PyTypeObject *ty, { unsigned t, sz; gfshare_pyobj *s; - char *kwlist[] = { "threshold", "size", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", kwlist, + static const char *const kwlist[] = { "threshold", "size", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST, convuint, &t, convuint, &sz)) goto end; if (!t || t > 255) VALERR("threshold must be nonzero and < 256"); @@ -295,7 +296,8 @@ static PyTypeObject gfsharejoin_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Binary field secret sharing: join shares to recover secret.", +"GFShareJoin(THRESHOLD, SIZE): binary field sharing:\n\ + join shares to recover secret.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -407,8 +409,10 @@ static PyObject *sharesplit_pynew(PyTypeObject *ty, grand *r = &rand_global; mp *m = 0; share_pyobj *s; - char *kwlist[] = { "threshold", "secret", "modulus", "rng", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:new", kwlist, + static const char *const kwlist[] = + { "threshold", "secret", "modulus", "rng", 0 }; + + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:new", KWLIST, convuint, &t, convmp, &sec, convmp, &m, convgrand, &r)) goto end; @@ -467,7 +471,8 @@ static PyTypeObject sharesplit_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Prime field secret sharing: split secret into shares.", +"ShareSplit(THRESHOLD, SECRET, [modulus = ?], [rng = rand]):\n\ + prime field secret sharing: split secret into shares.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ @@ -496,8 +501,8 @@ static PyObject *sharejoin_pynew(PyTypeObject *ty, unsigned t; mp *m = 0; share_pyobj *s; - char *kwlist[] = { "threshold", "modulus", 0 }; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", kwlist, + static const char *const kwlist[] = { "threshold", "modulus", 0 }; + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST, convuint, &t, convmp, &m)) goto end; if (!t) VALERR("threshold must be nonzero"); @@ -590,7 +595,8 @@ static PyTypeObject sharejoin_pytype_skel = { Py_TPFLAGS_BASETYPE, /* @tp_doc@ */ -"Prime field secret sharing: join shares to recover secret.", +"ShareJoin(THRESHOLD, MODULUS): prime field secret sharing:\n\ + join shares to recover secret.", 0, /* @tp_traverse@ */ 0, /* @tp_clear@ */ diff --git a/t/t-algorithms.py b/t/t-algorithms.py index 8e073f2..d7b97cb 100644 --- a/t/t-algorithms.py +++ b/t/t-algorithms.py @@ -149,6 +149,7 @@ class TestKeysize (U.TestCase): for n in [0, 12, 20, 5000]: me.assertTrue(ksz.check(n)) me.assertEqual(ksz.best(n), n) + me.assertEqual(ksz.pad(n), n) ## A typical two-byte spec. (No published algorithms actually /need/ a ## two-byte key-size spec, but all of the HMAC variants use one anyway.) @@ -160,6 +161,7 @@ class TestKeysize (U.TestCase): for n in [0, 12, 20, 5000]: me.assertTrue(ksz.check(n)) me.assertEqual(ksz.best(n), n) + me.assertEqual(ksz.pad(n), n) ## Check construction. ksz = C.KeySZAny(15) @@ -186,6 +188,8 @@ class TestKeysize (U.TestCase): else: me.assertFalse(ksz.check(x)) if best is None: me.assertRaises(ValueError, ksz.best, x) else: me.assertEqual(ksz.best(x), best) + if pad is None: me.assertRaises(ValueError, ksz.pad, x) + else: me.assertEqual(ksz.pad(x), pad) ## Check construction. ksz = C.KeySZSet(7) @@ -210,13 +214,15 @@ class TestKeysize (U.TestCase): me.assertEqual(ksz.min, 4) me.assertEqual(ksz.max, 32) me.assertEqual(ksz.mod, 4) - for x, best in [(3, None), (4, 4), (5, 4), - (15, 12), (16, 16), (17, 16), - (31, 28), (32, 32), (33, 32)]: - if x == best: me.assertTrue(ksz.check(x)) + for x, best, pad in [(3, None, 4), (4, 4, 4), (5, 4, 8), + (15, 12, 16), (16, 16, 16), (17, 16, 20), + (31, 28, 32), (32, 32, 32), (33, 32, None)]: + if x == best == pad: me.assertTrue(ksz.check(x)) else: me.assertFalse(ksz.check(x)) if best is None: me.assertRaises(ValueError, ksz.best, x) else: me.assertEqual(ksz.best(x), best) + if pad is None: me.assertRaises(ValueError, ksz.pad, x) + else: me.assertEqual(ksz.pad(x), pad) ## Check construction. ksz = C.KeySZRange(28, 21, 35, 7) @@ -310,6 +316,280 @@ TestCipher.generate_testcases((name, C.gcciphers[name]) for name in "blowfish-counter", "rc4", "seal", "salsa20/8", "shake128-xof"]) ###-------------------------------------------------------------------------- +class TestAuthenticatedEncryption \ + (HashBufferTestMixin, T.GenericTestMixin): + """Test authenticated encryption schemes.""" + + def _test_aead(me, aecls): + + ## Check the class properties. + me.assertEqual(type(aecls.name), str) + me.assertTrue(isinstance(aecls.keysz, C.KeySZ)) + me.assertTrue(isinstance(aecls.noncesz, C.KeySZ)) + me.assertTrue(isinstance(aecls.tagsz, C.KeySZ)) + me.assertEqual(type(aecls.blksz), int) + me.assertEqual(type(aecls.bufsz), int) + me.assertEqual(type(aecls.ohd), int) + me.assertEqual(type(aecls.flags), int) + + ## Check round-tripping, with full precommitment. First, select some + ## parameters. (It's conceivable that some AEAD schemes are more + ## restrictive than advertised by the various properties, but this works + ## out OK in practice.) + k = T.span(aecls.keysz.default) + n = T.span(aecls.noncesz.default) + if aecls.flags&C.AEADF_NOAAD: h = T.span(0) + else: h = T.span(131) + m = T.span(253) + tsz = aecls.tagsz.default + key = aecls(k) + + ## Next, encrypt a message, checking that things are proper as we go. + enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + me.assertEqual(enc.hsz, len(h)) + me.assertEqual(enc.msz, len(m)) + me.assertEqual(enc.mlen, 0) + me.assertEqual(enc.tsz, tsz) + aad = enc.aad() + if aecls.flags&C.AEADF_AADNDEP: me.assertEqual(aad.hsz, len(h)) + else: me.assertEqual(aad.hsz, None) + me.assertEqual(aad.hlen, 0) + if not aecls.flags&C.AEADF_NOAAD: + aad.hash(h[0:83]) + me.assertEqual(aad.hlen, 83) + aad.hash(h[83:131]) + me.assertEqual(aad.hlen, 131) + c0 = enc.encrypt(m[0:57]) + me.assertEqual(enc.mlen, 57) + me.assertTrue(57 - aecls.bufsz <= len(c0) <= 57 + aecls.ohd) + c1 = enc.encrypt(m[57:189]) + me.assertEqual(enc.mlen, 189) + me.assertTrue(132 - aecls.bufsz <= len(c1) <= + 132 + aecls.bufsz + aecls.ohd) + c2 = enc.encrypt(m[189:253]) + me.assertEqual(enc.mlen, 253) + me.assertTrue(64 - aecls.bufsz <= len(c2) <= + 64 + aecls.bufsz + aecls.ohd) + c3, t = enc.done(aad = aad) + me.assertTrue(len(c3) <= aecls.bufsz + aecls.ohd) + c = c0 + c1 + c2 + c3 + me.assertTrue(len(m) <= len(c) <= len(m) + aecls.ohd) + me.assertEqual(len(t), tsz) + + ## And now decrypt it again, with different record boundaries. + dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz) + me.assertEqual(dec.hsz, len(h)) + me.assertEqual(dec.csz, len(c)) + me.assertEqual(dec.clen, 0) + me.assertEqual(dec.tsz, tsz) + aad = dec.aad() + if aecls.flags&C.AEADF_AADNDEP: me.assertEqual(aad.hsz, len(h)) + else: me.assertEqual(aad.hsz, None) + me.assertEqual(aad.hlen, 0) + aad.hash(h) + m0 = dec.decrypt(c[0:156]) + me.assertTrue(156 - aecls.bufsz <= len(m0) <= 156) + m1 = dec.decrypt(c[156:]) + me.assertTrue(len(c) - 156 - aecls.bufsz <= len(m1) <= + len(c) - 156 + aecls.bufsz) + m2 = dec.done(tag = t, aad = aad) + me.assertEqual(m0 + m1 + m2, m) + + ## And again, with the wrong tag. + dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz) + aad = dec.aad(); aad.hash(h) + _ = dec.decrypt(c) + me.assertRaises(ValueError, dec.done, tag = t ^ tsz*C.bytes("55")) + + ## Check that the all-in-one methods work. + me.assertEqual((c, t), + key.encrypt(n = n, h = h, m = m, tsz = tsz)) + me.assertEqual(m, + key.decrypt(n = n, h = h, c = c, t = t)) + + ## Check that bad key, nonce, and tag lengths are rejected. + badlen = bad_key_size(aecls.keysz) + if badlen is not None: me.assertRaises(ValueError, aecls, T.span(badlen)) + badlen = bad_key_size(aecls.noncesz) + if badlen is not None: + me.assertRaises(ValueError, key.enc, nonce = T.span(badlen), + hsz = len(h), msz = len(m), tsz = tsz) + me.assertRaises(ValueError, key.dec, nonce = T.span(badlen), + hsz = len(h), csz = len(c), tsz = tsz) + if not aecls.flags&C.AEADF_PCTSZ: + enc = key.enc(nonce = n, hsz = 0, msz = len(m)) + _ = enc.encrypt(m) + me.assertRaises(ValueError, enc.done, tsz = badlen) + badlen = bad_key_size(aecls.tagsz) + if badlen is not None: + me.assertRaises(ValueError, key.enc, nonce = n, + hsz = len(h), msz = len(m), tsz = badlen) + me.assertRaises(ValueError, key.dec, nonce = n, + hsz = len(h), csz = len(c), tsz = badlen) + + ## Check that we can't get a loose `aad' object from a scheme which has + ## nonce-dependent AAD processing. + if aecls.flags&C.AEADF_AADNDEP: me.assertRaises(ValueError, key.aad) + + ## Check the menagerie of AAD hashing methods. + if not aecls.flags&C.AEADF_NOAAD: + def mkhash(hsz): + enc = key.enc(nonce = n, hsz = hsz, msz = 0, tsz = tsz) + aad = enc.aad() + return aad, lambda: enc.done(aad = aad)[1] + me.check_hashbuffer(mkhash) + + ## Check that encryption/decryption works with the given precommitments. + def quick_enc_check(**kw): + enc = key.enc(**kw) + aad = enc.aad().hash(h) + c0 = enc.encrypt(m); c1, tt = enc.done(aad = aad, tsz = tsz) + me.assertEqual((c, t), (c0 + c1, tt)) + def quick_dec_check(**kw): + dec = key.dec(**kw) + aad = dec.aad().hash(h) + m0 = dec.decrypt(c); m1 = dec.done(aad = aad, tag = t) + me.assertEqual(m, m0 + m1) + + ## Check that we can get away without precommitting to the header length + ## if and only if the AEAD scheme says it will let us. + if aecls.flags&C.AEADF_PCHSZ: + me.assertRaises(ValueError, key.enc, nonce = n, + msz = len(m), tsz = tsz) + me.assertRaises(ValueError, key.dec, nonce = n, + csz = len(c), tsz = tsz) + else: + quick_enc_check(nonce = n, msz = len(m), tsz = tsz) + quick_dec_check(nonce = n, csz = len(c), tsz = tsz) + + ## Check that we can get away without precommitting to the message/ + ## ciphertext length if and only if the AEAD scheme says it will let us. + if aecls.flags&C.AEADF_PCMSZ: + me.assertRaises(ValueError, key.enc, nonce = n, + hsz = len(h), tsz = tsz) + me.assertRaises(ValueError, key.dec, nonce = n, + hsz = len(h), tsz = tsz) + else: + quick_enc_check(nonce = n, hsz = len(h), tsz = tsz) + quick_dec_check(nonce = n, hsz = len(h), tsz = tsz) + + ## Check that we can get away without precommitting to the tag length if + ## and only if the AEAD scheme says it will let us. + if aecls.flags&C.AEADF_PCTSZ: + me.assertRaises(ValueError, key.enc, nonce = n, + hsz = len(h), msz = len(m)) + me.assertRaises(ValueError, key.dec, nonce = n, + hsz = len(h), csz = len(c)) + else: + quick_enc_check(nonce = n, hsz = len(h), msz = len(m)) + quick_dec_check(nonce = n, hsz = len(h), csz = len(c)) + + ## Check that if we precommit to the header length, we're properly held + ## to the commitment. + if not aecls.flags&C.AEADF_NOAAD: + + ## First, check encryption with underrun. If we must supply AAD first, + ## then the underrun will be reported when we start trying to encrypt; + ## otherwise, checking is delayed until `done'. + enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + aad = enc.aad().hash(h[0:83]) + if aecls.flags&C.AEADF_AADFIRST: + me.assertRaises(ValueError, enc.encrypt, m) + else: + _ = enc.encrypt(m) + me.assertRaises(ValueError, enc.done, aad = aad) + + ## Next, check decryption with underrun. If we must supply AAD first, + ## then the underrun will be reported when we start trying to encrypt; + ## otherwise, checking is delayed until `done'. + dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz) + aad = dec.aad().hash(h[0:83]) + if aecls.flags&C.AEADF_AADFIRST: + me.assertRaises(ValueError, dec.decrypt, c) + else: + _ = dec.decrypt(c) + me.assertRaises(ValueError, dec.done, tag = t, aad = aad) + + ## If AAD processing is nonce-dependent then an overrun will be + ## detected imediately. + if aecls.flags&C.AEADF_AADNDEP: + enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + aad = enc.aad().hash(h[0:83]) + me.assertRaises(ValueError, aad.hash, h[82:131]) + dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz) + aad = dec.aad().hash(h[0:83]) + me.assertRaises(ValueError, aad.hash, h[82:131]) + + ## Some additional tests for nonce-dependent `aad' objects. + if aecls.flags&C.AEADF_AADNDEP: + + ## Check that `aad' objects can't be used once their parents are gone. + enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + aad = enc.aad() + del enc + me.assertRaises(ValueError, aad.hash, h) + + ## Check that they can't be crossed over. + enc0 = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + enc1 = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz) + enc0.aad().hash(h) + aad1 = enc1.aad().hash(h) + _ = enc0.encrypt(m) + me.assertRaises(ValueError, enc0.done, tsz = tsz, aad = aad1) + + ## Test copying AAD. + if not aecls.flags&C.AEADF_AADNDEP and not aecls.flags&C.AEADF_NOAAD: + aad0 = key.aad() + aad0.hash(h[0:83]) + aad1 = aad0.copy() + aad2 = aad1.copy() + aad0.hash(h[83:131]) + aad1.hash(h[83:131]) + aad2.hash(h[83:131] ^ 48*C.bytes("ff")) + me.assertEqual(key.enc(nonce = n, hsz = len(h), + msz = 0, tsz = tsz).done(aad = aad0), + key.enc(nonce = n, hsz = len(h), + msz = 0, tsz = tsz).done(aad = aad1)) + me.assertNotEqual(key.enc(nonce = n, hsz = len(h), + msz = 0, tsz = tsz).done(aad = aad0), + key.enc(nonce = n, hsz = len(h), + msz = 0, tsz = tsz).done(aad = aad2)) + + ## Check that if we precommit to the message length, we're properly held + ## to the commitment. (Fortunately, this is way simpler than the AAD + ## case above.) First, try an underrun. + enc = key.enc(nonce = n, hsz = 0, msz = len(m), tsz = tsz) + _ = enc.encrypt(m[0:183]) + me.assertRaises(ValueError, enc.done, tsz = tsz) + dec = key.dec(nonce = n, hsz = 0, csz = len(c), tsz = tsz) + _ = dec.decrypt(c[0:183]) + me.assertRaises(ValueError, dec.done, tag = t) + + ## And now an overrun. + enc = key.enc(nonce = n, hsz = 0, msz = 183, tsz = tsz) + me.assertRaises(ValueError, enc.encrypt, m) + dec = key.dec(nonce = n, hsz = 0, csz = 183, tsz = tsz) + me.assertRaises(ValueError, dec.decrypt, c) + + ## Finally, check that if we precommit to a tag length, we're properly + ## held to the commitment. This depends on being able to find a tag size + ## which isn't the default. + tsz1 = different_key_size(aecls.tagsz, tsz) + if tsz1 is not None: + enc = key.enc(nonce = n, hsz = 0, msz = len(m), tsz = tsz1) + _ = enc.encrypt(m) + me.assertRaises(ValueError, enc.done, tsz = tsz) + dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz1) + aad = dec.aad().hash(h) + _ = dec.decrypt(c) + me.assertRaises(ValueError, enc.done, tsz = tsz, aad = aad) + +TestAuthenticatedEncryption.generate_testcases \ + ((name, C.gcaeads[name]) for name in + ["des3-ccm", "blowfish-ocb1", "square-ocb3", "rijndael-gcm", + "serpent-eax", "salsa20-naclbox", "chacha20-poly1305"]) + +###-------------------------------------------------------------------------- class BaseTestHash (HashBufferTestMixin): """Base class for testing hash functions.""" @@ -432,7 +712,7 @@ class TestHLatin (U.TestCase): """Test the `hsalsa20' and `hchacha20' functions.""" def test_hlatin(me): - kk = [T.span(sz) for sz in [32]] + kk = [T.span(sz) for sz in [10, 16, 32]] n = T.span(16) bad_k = T.span(18) bad_n = T.span(13) @@ -462,6 +742,13 @@ class TestKeccak (HashBufferTestMixin): st1.mix(m0).step() me.assertNotEqual(st0.extract(32), st1.extract(32)) + ## Check state copying. + st1 = st0.copy() + mask = st1.extract(len(m1)) + st0.mix(m1) + st1.mix(m1) + me.assertEqual(st0.extract(32), st1.extract(32)) + ## Check error conditions. _ = st0.extract(200) me.assertRaises(ValueError, st0.extract, 201) @@ -501,7 +788,7 @@ class TestKeccak (HashBufferTestMixin): ## Check masking. x = xcls().hash(m).xof() - me.assertEqual(x.mask(m), C.ByteString(m) ^ C.ByteString(h[0:len(m)])) + me.assertEqual(x.mask(m), m ^ h[0:len(m)]) ## Check the `check' method. me.assertTrue(xcls().hash(m).check(h0)) diff --git a/t/t-buffer.py b/t/t-buffer.py index 24823c6..096e35b 100644 --- a/t/t-buffer.py +++ b/t/t-buffer.py @@ -180,11 +180,10 @@ class TestWriteBuffer (U.TestCase): buf.putu8(0x00) putfn(buf, T.bytes_as_int(w, bigendp)) buf.putu8(w + 1) - me.assertEqual(C.ByteString(buf), T.span(w + 2)) - me.assertEqual(C.ByteString(putfn(C.WriteBuffer(), (1 << 8*w) - 1)), + me.assertEqual(buf.contents, T.span(w + 2)) + me.assertEqual(putfn(C.WriteBuffer(), (1 << 8*w) - 1).contents, w*C.bytes("ff")) - me.assertEqual(C.ByteString(putfn(C.WriteBuffer(), C.MP(0))), - w*C.bytes("00")) + me.assertEqual(putfn(C.WriteBuffer(), C.MP(0)).contents, w*C.bytes("00")) ## Check overflow detection. me.assertRaises((OverflowError, ValueError), @@ -196,8 +195,8 @@ class TestWriteBuffer (U.TestCase): ## Go through a number of different sizes. for n in [0, 1, 7, 8, 19, 255, 12345, 65535, 123456]: if n >= 1 << 8*w: continue - me.assertEqual(C.ByteString(putfn(C.WriteBuffer().putu8(0x00), - T.span(n)).putu8(0xff)), + me.assertEqual(putfn(C.WriteBuffer().putu8(0x00), + T.span(n)).putu8(0xff).contents, T.prep_lenseq(w, n, bigendp, True)) ## Check blocks which are too large for the length prefix. @@ -244,7 +243,7 @@ class TestWriteBuffer (U.TestCase): buf.zero(17) buf.put(T.span(23)) me.assertEqual(buf.size, 40) - me.assertEqual(C.ByteString(buf), C.ByteString.zero(17) + T.span(23)) + me.assertEqual(buf.contents, C.ByteString.zero(17) + T.span(23)) ###----- That's all, folks -------------------------------------------------- diff --git a/t/t-bytes.py b/t/t-bytes.py index 6755e96..36a3f9f 100644 --- a/t/t-bytes.py +++ b/t/t-bytes.py @@ -48,6 +48,7 @@ class TestByteString (U.TestCase): x = C.ByteString(T.bin("once upon a time there was a string")) ## Check that simple indexing works. + me.assertEqual(type(x[3]), C.ByteString) me.assertEqual(x[3], 'e') me.assertEqual(x[-5], 't') @@ -55,7 +56,8 @@ class TestByteString (U.TestCase): x[34]; me.assertRaises(IndexError, lambda: x[35]) x[-35]; me.assertRaises(IndexError, lambda: x[-36]) - ## Check slicing. + ## Check slicing. This should always give us bytes. + me.assertEqual(type(x[7:17]), C.ByteString) me.assertEqual(x[7:17], T.bin("on a time ")) ## Complex slicing is also supported. @@ -143,13 +145,18 @@ class TestByteString (U.TestCase): me.assertEqual(~x, C.bytes("fc5a03")) ## Concatenation. + me.assertEqual(type(x + y), C.ByteString) me.assertEqual(x + y, C.bytes("03a5fc5fac30")) ## Replication (asymmetric but commutative). + me.assertEqual(type(3*x), C.ByteString) + me.assertEqual(type(x*3), C.ByteString) me.assertEqual(3*x, C.bytes("03a5fc03a5fc03a5fc")) me.assertEqual(x*3, C.bytes("03a5fc03a5fc03a5fc")) ## Replication by zero (regression test). + me.assertEqual(type(0*x), C.ByteString) + me.assertEqual(type(x*0), C.ByteString) me.assertEqual(0*x, C.ByteString(T.bin(""))) me.assertEqual(x*0, C.ByteString(T.bin(""))) diff --git a/t/t-ec.py b/t/t-ec.py index ef80b90..870297e 100644 --- a/t/t-ec.py +++ b/t/t-ec.py @@ -194,21 +194,23 @@ class TestCurves (T.GenericTestMixin): Z1 = C.ByteString.zero(1) me.assertEqual(O.ec2osp(), Z1) me.assertEqual(E.os2ecp(Z1), (O, Z0)) - t = C.ByteString(C.WriteBuffer() - .putu8(0x04) - .put(P.ix.storeb(k.noctets)) - .put(P.iy.storeb(k.noctets))) + t = C.WriteBuffer() \ + .putu8(0x04) \ + .put(P.ix.storeb(k.noctets)) \ + .put(P.iy.storeb(k.noctets)) \ + .contents me.assertEqual(P.ec2osp(), t) - me.assertEqual(C.ByteString(C.WriteBuffer().putecptraw(P)), t) + me.assertEqual(C.WriteBuffer().putecptraw(P).contents, t) me.assertEqual(E.os2ecp(t), (P, Z0)) me.assertEqual(C.ReadBuffer(t).getecptraw(E), P) if isinstance(k, C.PrimeField): ybit = int(P.iy&1) else: try: ybit = int((P.y/P.x).value&C.GF(1)) except ZeroDivisionError: ybit = 0 - t = C.ByteString(C.WriteBuffer() - .putu8(0x02 | ybit) - .put(P.ix.storeb(k.noctets))) + t = C.WriteBuffer() \ + .putu8(0x02 | ybit) \ + .put(P.ix.storeb(k.noctets)) \ + .contents me.assertEqual(P.ec2osp(C.EC_LSB), t) me.assertEqual(E.os2ecp(t, C.EC_LSB), (P, Z0)) diff --git a/t/t-group.py b/t/t-group.py index 203ca16..e91de94 100644 --- a/t/t-group.py +++ b/t/t-group.py @@ -111,14 +111,14 @@ class TestGroups (T.GenericTestMixin): ## Encoding. t = y.tobuf() - me.assertEqual(t, C.ByteString(C.WriteBuffer().putmp(i))) + me.assertEqual(t, C.WriteBuffer().putmp(i).contents) me.assertEqual(G.frombuf(t)[0], y) me.assertEqual(id.tobuf(), C.bytes("000101")) t = y.toraw() me.assertEqual(t, i.storeb(G.noctets)) me.assertEqual(G.fromraw(t)[0], y) me.assertEqual(id.toraw(), - C.ByteString(C.WriteBuffer().zero(G.noctets - 1).putu8(1))) + C.WriteBuffer().zero(G.noctets - 1).putu8(1).contents) ## String conversion. ystr = str(y) diff --git a/util.c b/util.c index 29f7d12..723c819 100644 --- a/util.c +++ b/util.c @@ -737,13 +737,15 @@ end: return (rc); } -static char *def_kwlist[] = { "key", "default", 0 }; +static const char *const def_kwlist[] = { "key", "default", 0 }; PyObject *gmapmeth_get(PyObject *me, PyObject *arg, PyObject *kw) { PyObject *k, *def = Py_None, *v; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:get", def_kwlist, &k, &def)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:get", + (/*unconst*/ char **)def_kwlist, + &k, &def)) return (0); if ((v = PyObject_GetItem(me, k)) != 0) return (v); PyErr_Clear(); @@ -755,7 +757,8 @@ PyObject *gmapmeth_setdefault(PyObject *me, PyObject *arg, PyObject *kw) PyObject *k, *def = Py_None, *v; if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:setdefault", - def_kwlist, &k, &def)) + (/*unconst*/ char **)def_kwlist, + &k, &def)) return (0); if ((v = PyObject_GetItem(me, k)) != 0) return (v); PyErr_Clear(); @@ -767,7 +770,9 @@ PyObject *gmapmeth_pop(PyObject *me, PyObject *arg, PyObject *kw) { PyObject *k, *def = 0, *v; - if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:pop", def_kwlist, &k, &def)) + if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:pop", + (/*unconst*/ char **)def_kwlist, + &k, &def)) return (0); if ((v = PyObject_GetItem(me, k)) != 0) { PyObject_DelItem(me, k);