]> www.wagner.pp.ru Git - oss/ctypescrypto.git/commitdiff
Added support for MAC
authorVictor Wagner <vitus@wagner.pp.ru>
Sun, 21 Dec 2014 19:27:10 +0000 (22:27 +0300)
committerVictor Wagner <vitus@wagner.pp.ru>
Sun, 21 Dec 2014 19:27:10 +0000 (22:27 +0300)
README.md
ctypescrypto/exception.py
ctypescrypto/mac.py [new file with mode: 0644]
ctypescrypto/pkey.py
tests/testmac.py [new file with mode: 0644]

index 4b6d5d872d38203535571ded095024990b2a033b..4a22761f640be6885465f6d362bdf891457a7241 100644 (file)
--- 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
index 43ba6a0b5a8858402f5fc0902d5b596bb54f1bdb..30c138e43aa5ba52651243267abe0cdf974244fa 100644 (file)
@@ -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 (file)
index 0000000..e5e8f55
--- /dev/null
@@ -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
index 9257dfbcce2a4ccd3546dfa493f4e1eb873ed86f..510ffcaa85ea395723ecc07ad337119b6bc43e57 100644 (file)
@@ -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 (file)
index 0000000..1083b48
--- /dev/null
@@ -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()