X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-tcl.git;a=blobdiff_plain;f=crypto%2Fcrypto.c;h=cf50712979a9beb73d6e2f1477070c164d9a0657;hp=1866a6578504be8232074ecdd19d6f47322b6953;hb=da1c2c3cbb87a600f19e250f93ef9fa4f89844fc;hpb=9b7d11070d3e9dc1eb61cbccd5155f47a27047c3;ds=sidebyside diff --git a/crypto/crypto.c b/crypto/crypto.c index 1866a65..cf50712 100644 --- a/crypto/crypto.c +++ b/crypto/crypto.c @@ -5,13 +5,445 @@ #include "hbytes.h" #include "tables.h" -#include "serpent.h" -void memxor(Byte *dest, const Byte *src, int l) { - while (l--) *dest++ ^= *src++; +const PadOp padops[]= { + { "un", 0, 0 }, + { "ua", 0, 1 }, + { "pn", 1, 0 }, + { "pa", 1, 1 }, + { 0 } +}; + +typedef struct { + HBytes_Value *hb; + int pad, blocksize; /* 0 or 1 */ +} PadMethodClientData; + +int do_hbytes_pad(ClientData cd, Tcl_Interp *ip, const PadOp *op, + HBytes_Var v, Tcl_Obj *blocksz, const PadMethodInfo *meth, + int methargsc, Tcl_Obj *const *methargsv) { + PadMethodClientData pmcd; + int rc; + + if (op->use_algname) { + const BlockCipherAlgInfo *alg; + alg= enum_lookup_cached(ip,blocksz,blockcipheralginfos, + "blockcipher alg for pad"); + if (!alg) return TCL_ERROR; + pmcd.blocksize= alg->blocksize; + } else { + rc= Tcl_GetIntFromObj(ip, blocksz, &pmcd.blocksize); if (rc) return rc; + if (pmcd.blocksize < 1) staticerr(ip, "block size must be at least 1", 0); + } + + pmcd.hb= v.hb; + pmcd.pad= op->pad; + + return meth->func(&pmcd,ip,methargsc,methargsv); +} + +int do_padmethodinfo_rfc2406(ClientData cd, Tcl_Interp *ip, + Tcl_Obj *nxthdr_arg, int *ok) { + const PadMethodClientData *pmcd= (const void*)cd; + int i, rc, padlen, old_len; + + if (pmcd->blocksize > 256) + return staticerr(ip, "block size too large for RFC2406 padding", 0); + + if (pmcd->pad) { + Byte *padding; + HBytes_Value nxthdr; + + rc= pat_hb(ip,nxthdr_arg,&nxthdr); + if (rc) return rc; + + if (hbytes_len(&nxthdr) != 1) return + staticerr(ip, "RFC2406 next header field must be exactly 1 byte", 0); + padlen= pmcd->blocksize-1 - ((hbytes_len(pmcd->hb)+1) % pmcd->blocksize); + padding= hbytes_append(pmcd->hb, padlen+2); + for (i=1; i<=padlen; i++) + *padding++ = i; + *padding++ = padlen; + *padding++ = hbytes_data(&nxthdr)[0]; + *ok= 1; + + } else { + const Byte *padding, *trailer; + HBytes_Value nxthdr; + Tcl_Obj *nxthdr_valobj, *ro; + + *ok= 0; + old_len= hbytes_len(pmcd->hb); if (old_len % pmcd->blocksize) goto quit; + trailer= hbytes_unappend(pmcd->hb, 2); if (!trailer) goto quit; + + padlen= trailer[0]; + hbytes_array(&nxthdr,trailer+1,1); + nxthdr_valobj= ret_hb(ip,nxthdr); + ro= Tcl_ObjSetVar2(ip,nxthdr_arg,0,nxthdr_valobj,TCL_LEAVE_ERR_MSG); + if (!ro) { Tcl_DecrRefCount(nxthdr_valobj); return TCL_ERROR; } + + padding= hbytes_unappend(pmcd->hb, padlen); + for (i=1; i<=padlen; i++) + if (*padding++ != i) goto quit; + + *ok= 1; + + quit:; + + } + + return TCL_OK; +} + +int do_padmethodinfo_pkcs5(ClientData cd, Tcl_Interp *ip, int *ok) { + const PadMethodClientData *pmcd= (const void*)cd; + int padlen, old_len, i; + + if (pmcd->blocksize > 255) + return staticerr(ip, "block size too large for pkcs#5", 0); + + if (pmcd->pad) { + + Byte *padding; + + padlen= pmcd->blocksize - (hbytes_len(pmcd->hb) % pmcd->blocksize); + padding= hbytes_append(pmcd->hb, padlen); + memset(padding, padlen, padlen); + + } else { + + const Byte *padding; + + old_len= hbytes_len(pmcd->hb); if (old_len % pmcd->blocksize) goto bad; + padding= hbytes_unappend(pmcd->hb, 1); if (!padding) goto bad; + padlen= *padding; + if (padlen < 1 || padlen > pmcd->blocksize) goto bad; + padding= hbytes_unappend(pmcd->hb, padlen-1); if (!padding) goto bad; + + for (i=0; ihashsize); + alg->oneshot(dest, hbytes_data(&message), hbytes_len(&message)); + return TCL_OK; +} + +#define OBJ_CIPHKEY(o) ((CiphKeyValue*)(o)->internalRep.otherValuePtr) + +typedef struct { + int valuelen, bufferslen; + Byte *value, *buffers; + const void *alg; + void *alpha, *beta; /* key schedules etc.; each may be 0 */ +} CiphKeyValue; + +static void freealg(CiphKeyValue *key) { + TFREE(key->alpha); + TFREE(key->beta); +} + +static void key_t_free(Tcl_Obj *obj) { + CiphKeyValue *key= OBJ_CIPHKEY(obj); + freealg(key); + TFREE(key->value); + TFREE(key->buffers); +} + +static void noalg(CiphKeyValue *key) { + key->alg= 0; + key->alpha= key->beta= 0; +} + +static void key_t_dup(Tcl_Obj *src_obj, Tcl_Obj *dup_obj) { + CiphKeyValue *src= OBJ_CIPHKEY(src_obj); + CiphKeyValue *dup= TALLOC(sizeof(*dup)); + dup->valuelen= src->valuelen; + dup->value= src->valuelen ? TALLOC(src->valuelen) : 0; + dup->buffers= 0; dup->bufferslen= 0; + memcpy(dup->value, src->value, src->valuelen); + noalg(dup); + dup_obj->internalRep.otherValuePtr= dup; + dup_obj->typePtr= &blockcipherkey_type; +} + +static void key_t_ustr(Tcl_Obj *o) { + obj_updatestr_array(o, OBJ_CIPHKEY(o)->value, OBJ_CIPHKEY(o)->valuelen); +} + +static int key_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) { + int rc, l; + CiphKeyValue *val; + + rc= Tcl_ConvertToType(ip,o,&hbytes_type); if (rc) return rc; + val= TALLOC(sizeof(*val)); + val->valuelen= l= hbytes_len(OBJ_HBYTES(o)); + val->value= TALLOC(l); + val->buffers= 0; + val->bufferslen= 0; + memcpy(val->value, hbytes_data(OBJ_HBYTES(o)), l); + noalg(val); + + objfreeir(o); + o->internalRep.otherValuePtr= val; + o->typePtr= &blockcipherkey_type; + + return TCL_OK; +} + +Tcl_ObjType blockcipherkey_type = { + "blockcipher-key", + key_t_free, key_t_dup, key_t_ustr, key_t_sfa +}; + +static CiphKeyValue *get_key(Tcl_Interp *ip, Tcl_Obj *key_obj, + const void *alg, int want_bufferslen) { + CiphKeyValue *key; + int rc; + + rc= Tcl_ConvertToType(ip,key_obj,&blockcipherkey_type); if (rc) return 0; + key= OBJ_CIPHKEY(key_obj); + + if (key->alg != alg) { + freealg(key); + noalg(key); + key->alg= alg; + } + + if (key->bufferslen < want_bufferslen) { + TFREE(key->buffers); + key->buffers= TALLOC(want_bufferslen); + key->bufferslen= want_bufferslen; + } + return key; +} + +int do_hbytes_blockcipher(ClientData cd, Tcl_Interp *ip, + const BlockCipherOp *op, + int objc, Tcl_Obj *const *objv) { + return op->func((void*)op,ip,objc,objv); +} + +static int blockcipher_prep(Tcl_Interp *ip, Tcl_Obj *key_obj, + const HBytes_Value *iv, int decrypt, + const BlockCipherAlgInfo *alg, + const BlockCipherModeInfo *mode, int data_len, + const CiphKeyValue **key_r, const void **sched_r, + const Byte **iv_r, int *iv_lenbytes_r, + Byte **buffers_r, int *nblocks_r) { + void *sched, **schedp; + int want_bufferslen, want_iv; + int rc; + CiphKeyValue *key; + + if (data_len % alg->blocksize) + return staticerr(ip, "block cipher input not whole number of blocks", + "HBYTES BLOCKCIPHER LENGTH"); + + want_bufferslen= alg->blocksize * (mode->buf_blocks + mode->iv_blocks); + key= get_key(ip, key_obj, alg, want_bufferslen); if (!key) return TCL_ERROR; + + schedp= (alg->decrypt.make_schedule==alg->encrypt.make_schedule + || !decrypt) ? &key->alpha : &key->beta; + sched= *schedp; + if (!sched) { + if (key->valuelen < alg->key_min) + return staticerr(ip, "key too short", "HBYTES BLOCKCIPHER PARAMS"); + if (key->valuelen > alg->key_max) + return staticerr(ip, "key too long", "HBYTES BLOCKCIPHER PARAMS"); + + sched= TALLOC(alg->schedule_size); + (decrypt ? &alg->decrypt : &alg->encrypt)->make_schedule + (sched, key->value, key->valuelen); + *schedp= sched; + } + + want_iv= alg->blocksize * mode->iv_blocks; + if (!want_iv) { + if (!hbytes_issentinel(iv)) + return staticerr(ip,"iv supplied but mode does not take one", 0); + } else if (hbytes_issentinel(iv)) { + if (decrypt) return staticerr(ip,"must supply iv when decrypting", 0); + rc= get_urandom(ip, key->buffers, want_iv); + if (rc) return rc; + } else { + int iv_supplied= hbytes_len(iv); + if (iv_supplied > want_iv) + return staticerr(ip, "iv too large for algorithm and mode", + "HBYTES BLOCKCIPHER PARAMS"); + memcpy(key->buffers, hbytes_data(iv), iv_supplied); + memset(key->buffers + iv_supplied, 0, want_iv - iv_supplied); + } + + *key_r= key; + *sched_r= sched; + + *iv_r= key->buffers; + *iv_lenbytes_r= want_iv; + + *buffers_r= key->buffers + want_iv; + *nblocks_r= data_len / alg->blocksize; + + return TCL_OK; +} + +int do_blockcipherop_d(ClientData cd, Tcl_Interp *ip, + HBytes_Var v, const BlockCipherAlgInfo *alg, + Tcl_Obj *key_obj, const BlockCipherModeInfo *mode, + HBytes_Value iv, HBytes_Value *result) { + return do_blockcipherop_e(cd,ip,v,alg,key_obj,mode,iv,result); +} + +int do_blockcipherop_e(ClientData cd, Tcl_Interp *ip, + HBytes_Var v, const BlockCipherAlgInfo *alg, + Tcl_Obj *key_obj, const BlockCipherModeInfo *mode, + HBytes_Value iv, HBytes_Value *result) { + const BlockCipherOp *op= (const void*)cd; + int encrypt= op->encrypt; + int rc, iv_lenbytes; + const CiphKeyValue *key; + const char *failure; + const Byte *ivbuf; + Byte *buffers; + const void *sched; + int nblocks; + + if (!mode->encrypt) + return staticerr(ip, "mode does not support encrypt/decrypt", 0); + + rc= blockcipher_prep(ip,key_obj,&iv,!encrypt, + alg,mode, hbytes_len(v.hb), + &key,&sched, + &ivbuf,&iv_lenbytes, + &buffers,&nblocks); + if (rc) return rc; + + failure= + (encrypt ? mode->encrypt : mode->decrypt) + (hbytes_data(v.hb), nblocks, ivbuf, buffers, alg, encrypt, sched); + + if (failure) + return staticerr(ip, failure, "HBYTES BLOCKCIPHER CRYPTFAIL CRYPT"); + + hbytes_array(result, ivbuf, iv_lenbytes); + + return TCL_OK; +} + +int do_blockcipherop_mac(ClientData cd, Tcl_Interp *ip, + HBytes_Value msg, const BlockCipherAlgInfo *alg, + Tcl_Obj *key_obj, const BlockCipherModeInfo *mode, + HBytes_Value iv, HBytes_Value *result) { + const CiphKeyValue *key; + const char *failure; + const Byte *ivbuf; + Byte *buffers; + const void *sched; + int nblocks, iv_lenbytes; + int rc; + + if (!mode->mac) + return staticerr(ip, "mode does not support mac generation", 0); + + rc= blockcipher_prep(ip,key_obj,&iv,0, + alg,mode, hbytes_len(&msg), + &key,&sched, + &ivbuf,&iv_lenbytes, + &buffers,&nblocks); + if (rc) return rc; + + failure= mode->mac(hbytes_data(&msg), nblocks, ivbuf, buffers, alg, sched); + if (failure) + return staticerr(ip,failure, "HBYTES BLOCKCIPHER CRYPTFAIL MAC"); + + hbytes_array(result, buffers, alg->blocksize * mode->mac_blocks); + + return TCL_OK; +} + +int do_hbytes_hmac(ClientData cd, Tcl_Interp *ip, const HashAlgInfo *alg, + HBytes_Value message, Tcl_Obj *key_obj, + Tcl_Obj *maclen_obj, HBytes_Value *result) { + /* key->alpha = state after H(K XOR ipad + * key->beta = state after H(K XOR opad + * key->buffers = room for one block, or one state + */ + CiphKeyValue *key; + Byte *dest; + int i, ml, rc; + + if (maclen_obj) { + rc= Tcl_GetIntFromObj(ip, maclen_obj, &ml); if (rc) return rc; + if (ml<0 || ml>alg->hashsize) + return staticerr(ip, "requested hmac output size out of range", + "HBYTES HMAC PARAMS"); + } else { + ml= alg->hashsize; + } + + key= get_key(ip, key_obj, alg, + alg->blocksize > alg->statesize + ? alg->blocksize : alg->statesize); + + if (!key->alpha) { + assert(!key->beta); + + if (key->valuelen > alg->blocksize) + return staticerr(ip, "key to hmac longer than hash block size", + "HBYTES HMAC PARAMS"); + + memcpy(key->buffers, key->value, key->valuelen); + memset(key->buffers + key->valuelen, 0, alg->blocksize - key->valuelen); + for (i=0; iblocksize; i++) key->buffers[i] ^= 0x36; + + key->alpha= TALLOC(alg->statesize); + alg->init(key->alpha); + alg->update(key->alpha, key->buffers, alg->blocksize); + + key->beta= TALLOC(alg->statesize); + alg->init(key->beta); + for (i=0; iblocksize; i++) key->buffers[i] ^= (0x5c ^ 0x36); + alg->update(key->beta, key->buffers, alg->blocksize); + } + assert(key->beta); + + dest= hbytes_arrayspace(result, alg->hashsize); + + memcpy(key->buffers, key->alpha, alg->statesize); + alg->update(key->buffers, hbytes_data(&message), hbytes_len(&message)); + alg->final(key->buffers, dest); + + memcpy(key->buffers, key->beta, alg->statesize); + alg->update(key->buffers, dest, alg->hashsize); + alg->final(key->buffers, dest); + + hbytes_unappend(result, alg->hashsize - ml); + + return TCL_OK; +} + +int do_blockcipherop_prop(ClientData cd, Tcl_Interp *ip, + const BlockCipherPropInfo *prop, + const BlockCipherAlgInfo *alg, int *result) { + *result= *(const int*)((const char*)alg + prop->int_offset); + return TCL_OK; } -int do_hbytes_pkcs5(ClientData cd, Tcl_Interp *ip, - const PadMethod *meth, int objc, Tcl_Obj *const *objv) { - return meth->func((void*)meth, ip, objc, objv); +int do_hbytes_hash_prop(ClientData cd, Tcl_Interp *ip, + const HashAlgPropInfo *prop, + const HashAlgInfo *alg, int *result) { + *result= *(const int*)((const char*)alg + prop->int_offset); + return TCL_OK; }