1#!/usr/bin/env python 2# 3# Copyright 2017 John-Mark Gurney. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27# 28 29'''This is a wrapper around Ed448-Goldilocks. 30 31This module does not follow the standard Crypto modular method 32of signing due to the complexity of integration w/ the library, and 33that things should be more simple to use.''' 34 35__author__ = 'John-Mark Gurney' 36__copyright__ = 'Copyright 2017 John-Mark Gurney''' 37__license__ = 'BSD' 38__version__ = '0.1' 39__status__ = 'alpha' 40 41import array 42import os 43import os.path 44import sys 45import unittest 46import warnings 47 48from ctypes import * 49 50try: 51 _dname = os.path.dirname(__file__) 52 if not _dname: 53 _dname = '.' 54 _path = os.path.join(_dname, 'libdecaf.so') 55 decaf = CDLL(_path) 56except OSError as e: # pragma: no cover 57 import warnings 58 warnings.warn('libdecaf.so not installed.') 59 raise ImportError(str(e)) 60 61DECAF_EDDSA_448_PUBLIC_BYTES = 57 62DECAF_EDDSA_448_PRIVATE_BYTES = DECAF_EDDSA_448_PUBLIC_BYTES 63DECAF_EDDSA_448_SIGNATURE_BYTES = DECAF_EDDSA_448_PUBLIC_BYTES + DECAF_EDDSA_448_PRIVATE_BYTES 64 65# Types 66 67ed448_pubkey_t = c_uint8 * DECAF_EDDSA_448_PUBLIC_BYTES 68ed448_privkey_t = c_uint8 * DECAF_EDDSA_448_PRIVATE_BYTES 69ed448_sig_t = c_uint8 * DECAF_EDDSA_448_SIGNATURE_BYTES 70 71c_uint8_p = POINTER(c_uint8) 72 73decaf_error_t = c_int 74 75# Data 76try: 77 DECAF_ED448_NO_CONTEXT = POINTER(c_uint8).in_dll(decaf, 'DECAF_ED448_NO_CONTEXT') 78except ValueError: 79 DECAF_ED448_NO_CONTEXT = None 80 81funs = { 82 'decaf_ed448_derive_public_key': (None, [ ed448_pubkey_t, ed448_privkey_t]), 83 'decaf_ed448_sign': (None, [ ed448_sig_t, ed448_privkey_t, ed448_pubkey_t, c_uint8_p, c_size_t, c_uint8, c_uint8_p, c_uint8 ]), 84 'decaf_ed448_verify': (decaf_error_t, [ ed448_sig_t, ed448_pubkey_t, c_uint8_p, c_size_t, c_uint8, c_uint8_p, c_uint8 ]), 85} 86 87for i in funs: 88 f = getattr(decaf, i) 89 f.restype, f.argtypes = funs[i] 90 91def _makeba(s): 92 r = (c_ubyte * len(s))() 93 r[:] = array.array('B', s) 94 return r 95 96def _makestr(a): 97 # XXX - because python3 sucks, and unittest doesn't offer 98 # ability to silence stupid warnings, hide the tostring 99 # DeprecationWarning. 100 with warnings.catch_warnings(): 101 warnings.simplefilter('ignore') 102 return array.array('B', a).tostring() 103 104 105def _ed448_privkey(): 106 return _makeba(os.urandom(DECAF_EDDSA_448_PRIVATE_BYTES)) 107 108class EDDSA448(object): 109 _PUBLIC_SIZE = DECAF_EDDSA_448_PUBLIC_BYTES 110 _PRIVATE_SIZE = DECAF_EDDSA_448_PRIVATE_BYTES 111 _SIG_SIZE = DECAF_EDDSA_448_SIGNATURE_BYTES 112 113 def __init__(self, priv=None, pub=None): 114 '''Generate a new sign or verify object. At least one 115 of priv or pub MUST be specified. 116 117 If pub is not specified, it will be generated from priv. 118 If both are specified, there is no verification that pub 119 is the public key for priv. 120 121 It is recommended that you use the generate method to 122 generate a new key.''' 123 124 if priv is None and pub is None: 125 raise ValueError('at least one of priv or pub must be specified.') 126 127 if priv is not None: 128 try: 129 priv = _makeba(priv) 130 except Exception as e: 131 raise ValueError('priv must be a byte string', e) 132 133 self._priv = priv 134 135 if self._priv is not None and pub is None: 136 self._pub = ed448_pubkey_t() 137 decaf.decaf_ed448_derive_public_key(self._pub, self._priv) 138 else: 139 self._pub = _makeba(pub) 140 141 @classmethod 142 def generate(cls): 143 '''Generate a signing object w/ a newly generated key.''' 144 145 return cls(priv=_ed448_privkey()) 146 147 def has_private(self): 148 '''Returns True if object has private key.''' 149 150 return self._priv is not None 151 152 def public_key(self): 153 '''Returns a new object w/o the private key. This new 154 object will have the public part and can be used for 155 verifying messages''' 156 157 return self.__class__(pub=self._pub) 158 159 def export_key(self, format): 160 '''Export the key. The only format supported is 'raw'. 161 162 If has_private is True, then the private part will be 163 exported. If it is False, then the public part will be. 164 There is no indication on the output if the key is 165 public or private. It must be tracked independantly 166 of the data.''' 167 168 if format == 'raw': 169 if self._priv is None: 170 return _makestr(self._pub) 171 else: 172 return _makestr(self._priv) 173 else: 174 raise ValueError('unsupported format: %s' % repr(format)) 175 176 @staticmethod 177 def _makectxargs(ctx): 178 if ctx is None: 179 ctxargs = (DECAF_ED448_NO_CONTEXT, 0) 180 else: 181 ctxargs = (_makeba(ctx), len(ctx)) 182 183 return ctxargs 184 185 def sign(self, msg, ctx=None): 186 '''Returns a signature over the message. Requires that has_private returns True.''' 187 188 sig = ed448_sig_t() 189 ctxargs = self._makectxargs(ctx) 190 decaf.decaf_ed448_sign(sig, self._priv, self._pub, _makeba(msg), len(msg), 0, *ctxargs) 191 192 return _makestr(sig) 193 194 def verify(self, sig, msg, ctx=None): 195 '''Raises an error if sig is not valid for msg.''' 196 197 _sig = ed448_sig_t() 198 _sig[:] = array.array('B', sig) 199 ctxargs = self._makectxargs(ctx) 200 if not decaf.decaf_ed448_verify(_sig, self._pub, _makeba(msg), len(msg), 0, *ctxargs): 201 raise ValueError('signature is not valid') 202 203def generate(curve='ed448'): 204 return EDDSA448.generate() 205 206class TestEd448(unittest.TestCase): 207 def test_init(self): 208 self.assertRaises(ValueError, EDDSA448) 209 210 def test_gen(self): 211 key = generate(curve='ed448') 212 self.assertIsInstance(key, EDDSA448) 213 214 self.assertTrue(key.has_private()) 215 216 pubkey = key.public_key() 217 self.assertFalse(pubkey.has_private()) 218 219 def test_keyexport(self): 220 # Generate key and export 221 key = generate(curve='ed448') 222 privkey = key.export_key('raw') 223 224 # Generate signature 225 message = b'sdlkfjsdf' 226 sig = key.sign(message) 227 228 # Verify that the key can be imported and verifies 229 key2 = EDDSA448(privkey) 230 key2.verify(sig, message) 231 232 # Export the public key 233 keypub = key.public_key() 234 pubkey = keypub.export_key('raw') 235 236 # Verify that the public key can be imported and verifies 237 key3 = EDDSA448(pub=pubkey) 238 key3.verify(sig, message) 239 240 self.assertRaises(ValueError, key.export_key, 'PEM') 241 242 def test_keyimportexport(self): 243 privkey = b'1' * DECAF_EDDSA_448_PRIVATE_BYTES 244 key = EDDSA448(privkey) 245 246 self.assertEqual(key.export_key(format='raw'), privkey) 247 248 key = EDDSA448(pub=b'1' * DECAF_EDDSA_448_PUBLIC_BYTES) 249 250 self.assertRaises(ValueError, EDDSA448, priv=u'1' * DECAF_EDDSA_448_PRIVATE_BYTES) 251 252 def test_sig(self): 253 key = generate() 254 255 message = b'this is a test message for signing' 256 sig = key.sign(message) 257 258 # Make sure sig is a string of bytes 259 self.assertIsInstance(sig, bytes) 260 self.assertEqual(len(sig), EDDSA448._SIG_SIZE) 261 262 # Make sure sig is valid 263 key.verify(sig, message) 264 265 # Make sure sig is valid for public only version 266 pubkey = key.public_key() 267 pubkey.verify(sig, message) 268 269 # Ensure that the wrong message fails 270 message = b'this is the wrong message' 271 self.assertRaises(ValueError, pubkey.verify, sig, message) 272 273 def test_ctx(self): 274 key = generate() 275 276 message = b'foobar' 277 ctx = b'contexta' 278 sig = key.sign(message, ctx) 279 280 # Make sure it verifies correctly 281 key.verify(sig, message, ctx) 282 283 # Make sure it fails w/o context 284 self.assertRaises(ValueError, key.verify, sig, message) 285 286 # Make sure it fails w/ invalid/different context 287 self.assertRaises(ValueError, key.verify, sig, message, ctx + b'a') 288 289class TestBasicLib(unittest.TestCase): 290 def test_basic(self): 291 priv = _ed448_privkey() 292 pub = ed448_pubkey_t() 293 294 decaf.decaf_ed448_derive_public_key(pub, priv) 295 296 message = b'this is a test message' 297 298 sig = ed448_sig_t() 299 decaf.decaf_ed448_sign(sig, priv, pub, _makeba(message), len(message), 0, None, 0) 300 301 r = decaf.decaf_ed448_verify(sig, pub, _makeba(message), len(message), 0, None, 0) 302 self.assertTrue(r) 303 304 message = b'aofeijseflj' 305 r = decaf.decaf_ed448_verify(sig, pub, _makeba(message), len(message), 0, None, 0) 306 self.assertFalse(r) 307