From 92df3e73921ba7b8756bfab1af4189dab7cc610e Mon Sep 17 00:00:00 2001 From: Victor Wagner Date: Sun, 21 Dec 2014 22:27:10 +0300 Subject: [PATCH] Added support for MAC --- README.md | 20 +++++++-- ctypescrypto/exception.py | 15 +++++-- ctypescrypto/mac.py | 95 +++++++++++++++++++++++++++++++++++++++ ctypescrypto/pkey.py | 3 +- tests/testmac.py | 43 ++++++++++++++++++ 5 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 ctypescrypto/mac.py create mode 100644 tests/testmac.py diff --git a/README.md b/README.md index 4b6d5d8..4a22761 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Also, this extension takes some care of correctly converting textual information from ASN.1 structures into unicode. - Digest calculation ------------------ @@ -45,6 +44,23 @@ ctypescrypto modules, so it would work with engine-provided digests. Additionally there is **DigestType** class which may be needed to construct CMS SignedData objects or add signatures to them. + +MAC calculation +--------------- + +Mac is Message Authentication Code - it is like keyed digest, which +depends not only on message, but also on key, which should be used both +when initially computing MAC and when verifying it. MACs can be viewed +as "digital signatures with symmetric keys". + +Most common type of MAC is HMAC (i.e. hash-based MAC), described in +[RFC 2104](https://tools.ietf.org/html/rfc2104), but there are other, +for instance [GOST 28147-89](https://tools.ietf.org/html/rfc5830) defines MAC based on symmetric cipher. +Also GSM 0348 uses DES symmetric cipher as MAC. OpenSSL supports +GOST mac via loadable engine module, but doesn't seem to support any +non-HMAC MAC in the core. So, MAC is only test in the test suite which +requires loadable engine. + Symmetric ciphers ----------------- @@ -201,7 +217,5 @@ Possible future enhancements 3. OCSP ([RFC 6960](http://tools.ietf.org/html/rfc6960))request creation and response parsing 4. Timestamping ([RFC 3161](http://tools.ietf.org/html/rfc3161)) support. -6. MAC support. Few people know that there is more MACs than just HMAC, -and even fewer, that OpenSSL supports them. vim: spelllang=en tw=72 diff --git a/ctypescrypto/exception.py b/ctypescrypto/exception.py index 43ba6a0..30c138e 100644 --- a/ctypescrypto/exception.py +++ b/ctypescrypto/exception.py @@ -7,6 +7,15 @@ strings_loaded=False __all__ = ['LibCryptoError','clear_err_stack'] +def _check_null(s): + """ + Handle transparently NULL returned from error reporting functions + instead of strings + """ + if s is None: + return "" + return s + class LibCryptoError(Exception): """ Exception for libcrypto errors. Adds all the info, which can be @@ -21,9 +30,9 @@ class LibCryptoError(Exception): e=libcrypto.ERR_get_error() m = msg while e != 0: - m+="\n\t"+libcrypto.ERR_lib_error_string(e)+":"+\ - libcrypto.ERR_func_error_string(e)+":"+\ - libcrypto.ERR_reason_error_string(e) + m+="\n\t"+_check_null(libcrypto.ERR_lib_error_string(e))+":"+\ + _check_null(libcrypto.ERR_func_error_string(e))+":"+\ + _check_null(libcrypto.ERR_reason_error_string(e)) e=libcrypto.ERR_get_error() self.args=(m,) diff --git a/ctypescrypto/mac.py b/ctypescrypto/mac.py new file mode 100644 index 0000000..e5e8f55 --- /dev/null +++ b/ctypescrypto/mac.py @@ -0,0 +1,95 @@ +# -*- encoding: utf-8 -*- +""" +This module provides interface to OpenSSL MAC functions. + +It has not only HMAC support, but can support other types of MAC. + +""" + +from ctypescrypto.digest import Digest,DigestType,DigestError +from ctypescrypto.oid import Oid +from ctypescrypto import libcrypto +from ctypes import c_int,c_char_p, c_void_p, c_size_t,POINTER,create_string_buffer,pointer + +__all__ = ['MAC','DigestError'] +class MAC(Digest): + """ + This object represents MAC context. It is quite simular + to digest algorithm. It is simular to hmac objects provided + by standard library + """ + def __init__(self,algorithm,key,digest=None,**kwargs): + """ + Constructor has to obligatory arguments: + + @param algorithm - which is name of MAC algorithm i.e 'hmac' or + 'gost-mac' or equivalent Oid object + @param key - byte buffer with key. + + Optional parameters are: + digest - Oid or name of the digest algorithm to use. If none + specified, OpenSSL will try to derive one from the MAC + algorithm (or if algorithm is hmac, we'll substititute md5 + for compatibility with standard hmac module + + any other keyword argument is passed to EVP_PKEY_CTX as string + option. + + """ + if isinstance(algorithm,str): + self.algorithm=Oid(algorithm) + elif isinstance(algorithm,Oid): + self.algorithm=algorithm + else: + raise TypeError("Algorthm must be string or Oid") + if self.algorithm==Oid('hmac') and digest is None: + digest='md5' + self.name=self.algorithm.shortname().lower() + if digest is not None: + self.digest_type=DigestType(digest) + self.name+='-'+self.digest_type.digest_name + d=self.digest_type.digest + else: + self.digest_type=None + d=None + self.key=libcrypto.EVP_PKEY_new_mac_key(self.algorithm.nid,None,key,len(key)) + if self.key is None: + raise DigestError("EVP_PKEY_new_mac_key") + pctx=c_void_p() + self.ctx = libcrypto.EVP_MD_CTX_create() + if self.ctx == 0: + raise DigestError("Unable to create digest context") + if libcrypto.EVP_DigestSignInit(self.ctx,pointer(pctx),d,None,self.key) <= 0: + raise DigestError("Unable to intialize digest context") + self.digest_finalized=False + if self.digest_type is None: + self.digest_type=DigestType(Oid(libcrypto.EVP_MD_type(libcrypto.EVP_MD_CTX_md(self.ctx)))) + for (name,val) in kwargs.items(): + if EVP_PKEY_CTX_ctrl_str(ctx,name,val)<=0: + raise DigestError("Unable to set mac parameter") + self.digest_size = self.digest_type.digest_size() + self.block_size = self.digest_type.block_size() + def digest(self,data=None): + """ + Method digest is redefined to return keyed MAC value instead of + just digest. + """ + if data is not None: + self.update(data) + b=create_string_buffer(256) + size=c_size_t(256) + if libcrypto.EVP_DigestSignFinal(self.ctx,b,pointer(size))<=0: + raise DigestError('SignFinal') + self.digest_finalized=True + return b.raw[:size.value] + +libcrypto.EVP_DigestSignFinal.argtypes=(c_void_p,c_char_p,POINTER(c_size_t)) +libcrypto.EVP_DigestSignFinal.restype=c_int +libcrypto.EVP_DigestSignInit.argtypes=(c_void_p,POINTER(c_void_p),c_void_p,c_void_p,c_void_p) +libcrypto.EVP_DigestSignInit.restype=c_int +libcrypto.EVP_PKEY_CTX_ctrl_str.argtypes=(c_void_p,c_char_p,c_char_p) +libcrypto.EVP_PKEY_CTX_ctrl_str.restype=c_int +libcrypto.EVP_PKEY_new_mac_key.argtypes=(c_int,c_void_p,c_char_p,c_int) +libcrypto.EVP_PKEY_new_mac_key.restype=c_void_p +libcrypto.EVP_MD_CTX_md.argtypes=(c_void_p,) +libcrypto.EVP_MD_CTX_md.restype=c_void_p diff --git a/ctypescrypto/pkey.py b/ctypescrypto/pkey.py index 9257dfb..510ffca 100644 --- a/ctypescrypto/pkey.py +++ b/ctypescrypto/pkey.py @@ -172,7 +172,7 @@ class PKey(object): clear_err_stack() pkey_id=c_int(0) libcrypto.EVP_PKEY_asn1_get0_info(byref(pkey_id),None,None,None,None,ameth) - libcrypto.ENGINE_finish(tmpeng) + #libcrypto.ENGINE_finish(tmpeng) if "paramsfrom" in kwargs: ctx=libcrypto.EVP_PKEY_CTX_new(kwargs["paramsfrom"].key,None) else: @@ -291,3 +291,4 @@ libcrypto.PEM_write_bio_PrivateKey.argtypes=(c_void_p,c_void_p,c_void_p,c_char_p libcrypto.PEM_write_bio_PUBKEY.argtypes=(c_void_p,c_void_p) libcrypto.i2d_PUBKEY_bio.argtypes=(c_void_p,c_void_p) libcrypto.i2d_PrivateKey_bio.argtypes=(c_void_p,c_void_p) +libcrypto.ENGINE_finish.argtypes=(c_void_p,) diff --git a/tests/testmac.py b/tests/testmac.py new file mode 100644 index 0000000..1083b48 --- /dev/null +++ b/tests/testmac.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +from ctypescrypto.oid import Oid +from base64 import b16decode,b16encode +from ctypescrypto.mac import * +from ctypescrypto.engine import set_default +import unittest + +class TestMac(unittest.TestCase): + def test_hmac_default(self): + d=MAC('hmac',key='1234'*4) + d.update('The Quick brown fox jumps over the lazy dog\n') + self.assertEqual(d.name,'hmac-md5') + self.assertEqual(d.hexdigest(),'A9C16D91CDF2A99273B72336D0D16B56') + def test_hmac_digestdataa(self): + d=MAC('hmac',key='1234'*4) + h=d.hexdigest('The Quick brown fox jumps over the lazy dog\n') + self.assertEqual(d.name,'hmac-md5') + self.assertEqual(h,'A9C16D91CDF2A99273B72336D0D16B56') + def test_hmac_byoid(self): + d=MAC(Oid('hmac'),key='1234'*4) + d.update('The Quick brown fox jumps over the lazy dog\n') + self.assertEqual(d.name,'hmac-md5') + self.assertEqual(d.hexdigest(),'A9C16D91CDF2A99273B72336D0D16B56') + def test_mac_wrongtype(self): + with self.assertRaises(TypeError): + d=MAC(Oid('hmac').nid,key='1234'*4) + def test_hmac_sha256(self): + d=MAC('hmac',key='1234'*16,digest='sha256') + d.update('The Quick brown fox jumps over the lazy dog\n') + self.assertEqual(d.name,'hmac-sha256') + self.assertEqual(d.hexdigest(),'BEBA086E1C67200664DCDEEC697D99DB1A8DAA72933A36B708FC5FD568173095') + def test_gostmac(self): + set_default('gost') + d=MAC('gost-mac',key='1234'*8) + d.update('The Quick brown fox jumps over the lazy dog\n') + self.assertEqual(d.name,'gost-mac') + self.assertEqual(d.digest_size,4) + self.assertEqual(d.hexdigest(),'76F25AE3') + with self.assertRaisesRegexp(DigestError,"invalid mac key length"): + d=MAC('gost-mac',key='1234'*4) + +if __name__ == "__main__": + unittest.main() -- 2.39.2