Skip to content

Commit 1d9521e

Browse files
Bind out ec crypto (#692)
1 parent 4bad1a6 commit 1d9521e

File tree

7 files changed

+487
-6
lines changed

7 files changed

+487
-6
lines changed

awscrt/crypto.py

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import _awscrt
55
from awscrt import NativeResource
6-
from typing import Union
6+
from typing import Union, NamedTuple
77
from enum import IntEnum
88

99

@@ -210,3 +210,114 @@ def export_private_key(self, export_format: ED25519ExportFormat) -> bytes:
210210
Exports public part of the key in specified format.
211211
"""
212212
return _awscrt.ed25519_export_private_key(self._binding, export_format)
213+
214+
215+
class ECType(IntEnum):
216+
"""Elliptic Curve Type"""
217+
218+
P_256 = 0
219+
"""
220+
P-256 curve aka secp256r1
221+
"""
222+
223+
P_384 = 1
224+
"""
225+
P-384 curve aka secp384r1
226+
"""
227+
228+
229+
class ECExportFormat(IntEnum):
230+
"""EC Export format"""
231+
232+
SEC1 = 0
233+
"""
234+
Raw bytes for the private key as defined in Sec1 ("EC Private Key" in pem)
235+
"""
236+
237+
PKCS8 = 1
238+
"""
239+
Raw bytes for the private key as defined in PKCS8 ("Private Key" in pem)
240+
"""
241+
242+
SPKI = 2
243+
"""
244+
Raw bytes for the public key as defined in x509/SPKI ("EC Public Key" or "Public Key" in pem)
245+
"""
246+
247+
248+
class ECRawSignature(NamedTuple):
249+
r: bytes
250+
s: bytes
251+
252+
253+
class ECPublicCoords(NamedTuple):
254+
x: bytes
255+
y: bytes
256+
257+
258+
class EC(NativeResource):
259+
def __init__(self, binding):
260+
super().__init__()
261+
self._binding = binding
262+
263+
@staticmethod
264+
def new_generate(type: ECType) -> 'EC':
265+
"""
266+
Generates a new instance of EC key pair.
267+
"""
268+
return EC(binding=_awscrt.ec_new_generate(type))
269+
270+
@staticmethod
271+
def new_key_from_der_data(der_data: Union[bytes, bytearray, memoryview]) -> 'EC':
272+
"""
273+
Creates a new instance of EC key pair from der data.
274+
Will figure out what type of key it is without hint (i.e. pem header).
275+
Supports all formats specified in ECExportFormat.
276+
Expects raw bytes (i.e. strip b64 you get when reading pem).
277+
Raises ValueError if pem does not have private key object.
278+
"""
279+
return EC(binding=_awscrt.ec_key_from_der_data(der_data))
280+
281+
@staticmethod
282+
def decode_der_signature(signature: bytes) -> ECRawSignature:
283+
"""
284+
Decodes ec signature into raw r and s.
285+
"""
286+
(r, s) = _awscrt.ec_decode_signature(signature)
287+
return ECRawSignature(r=r, s=s)
288+
289+
@staticmethod
290+
def encode_raw_signature(signature: ECRawSignature) -> bytes:
291+
"""
292+
Encodes raw signature into der.
293+
"""
294+
return _awscrt.ec_encode_signature(signature)
295+
296+
def export_key(self, export_format: ECExportFormat) -> bytes:
297+
"""
298+
Exports the key in specified format.
299+
"""
300+
return _awscrt.ec_export_key(self._binding, export_format)
301+
302+
def get_public_coords(self) -> ECPublicCoords:
303+
"""
304+
Get public coords of the key
305+
"""
306+
(x, y) = _awscrt.ec_get_public_coords(self._binding)
307+
return ECPublicCoords(x=x, y=y)
308+
309+
def sign(self, digest: Union[bytes, bytearray, memoryview]) -> bytes:
310+
"""
311+
Signs data using a given algorithm.
312+
Returns DER encoded signature.
313+
Note: function expects digest of the message, ex sha256
314+
"""
315+
return _awscrt.ec_sign(self._binding, digest)
316+
317+
def verify(self, digest: Union[bytes, bytearray, memoryview],
318+
signature: Union[bytes, bytearray, memoryview]) -> bool:
319+
"""
320+
Verifies signature against digest.
321+
Returns True if signature matches and False if not.
322+
"""
323+
return _awscrt.ec_verify(self._binding, digest, signature)

setup.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,33 @@ def _build_dependencies(self):
375375

376376
self.library_dirs.insert(0, os.path.join(install_path, lib_dir))
377377

378+
def build_extension(self, ext):
379+
380+
# Warning: very hacky. feel free to replace with something cleaner
381+
# Problem: if you install python through homebrew, python config ldflags
382+
# will point to homebrew lib folder.
383+
# setuptools puts python ldflags before any of our lib paths, so if there is openssl or
384+
# another libcrypto in homebrew libs, it will get picked up before aws-lc we are building against.
385+
# And then we have fun failures due to lib mismatch.
386+
# I could not find a cleaner way, so lets just hook into linker command and make sure
387+
# our libs appear before other libs.
388+
if sys.platform == 'darwin' and using_libcrypto() and not using_system_libs() and not using_system_libcrypto():
389+
390+
orig_linker_so = self.compiler.linker_so[:]
391+
392+
for i, item in enumerate(self.compiler.linker_so):
393+
if item.startswith('-L'):
394+
self.compiler.linker_so[i:i] = [
395+
f"-L{item}" for item in self.library_dirs] + ['-Wl,-search_paths_first']
396+
break
397+
398+
try:
399+
super().build_extension(ext)
400+
finally:
401+
self.compiler.linker_so = orig_linker_so
402+
else:
403+
super().build_extension(ext)
404+
378405
def run(self):
379406
if using_system_libs():
380407
print("Skip building dependencies")

0 commit comments

Comments
 (0)