Skip to content

Commit

Permalink
Merge pull request #1225 from qstokkink/add_nacl_fail_fallback
Browse files Browse the repository at this point in the history
Added cryptography-based fallback when NaCL import fails
  • Loading branch information
qstokkink authored Oct 16, 2023
2 parents a1956f1 + 31b25c7 commit c44b481
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 83 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/pr-comment-validate.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
name: PR Validation Checker

on: issue_comment
on:
issue_comment:
types: [created]

jobs:
pr_commented:
name: Validate PR
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, 'validate') }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, windows-latest] # macos-latest not tested due to crashing.
version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
runs-on: ${{ matrix.os }}
steps:
Expand Down
60 changes: 60 additions & 0 deletions ipv8/keyvault/private/cryptography25519.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from binascii import hexlify
from typing import cast

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat

from ...keyvault.keys import PrivateKey
from ..public.cryptography25519 import KEY_LENGTH, Cryptography25519PK


class Cryptography25519SK(PrivateKey, Cryptography25519PK):
"""
A fallback implementation for LibNaCL secret keys.
"""

def __init__(self, binarykey: bytes = b"") -> None:
"""
Create a new LibNaCL secret key. Optionally load it from a string representation.
Otherwise, generate it from the 25519 curve.
:param binarykey: load the sk from this string (see key_to_bin())
"""
# Load the key, if specified
if binarykey:
crypt, seed = (binarykey[:KEY_LENGTH], binarykey[KEY_LENGTH: KEY_LENGTH * 2])
key = X25519PrivateKey.from_private_bytes(crypt)
self.vk = Ed25519PrivateKey.from_private_bytes(seed)
else:
key = X25519PrivateKey.generate()
self.vk = Ed25519PrivateKey.generate()
hex_vk = hexlify(self.vk.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw))

super().__init__(pk=key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw), hex_vk=hex_vk)
self.key = key # type: ignore[assignment]

def pub(self) -> Cryptography25519PK:
"""
Get the public key for this secret key.
"""
return Cryptography25519PK(pk=cast(X25519PrivateKey, self.key).public_key().public_bytes(Encoding.Raw,
PublicFormat.Raw),
hex_vk=hexlify(self.veri.public_bytes(Encoding.Raw, PublicFormat.Raw)))

def signature(self, msg: bytes) -> bytes:
"""
Create a signature for a message.
:param msg: the message to sign
:return: the signature for the message
"""
return self.vk.sign(msg)

def key_to_bin(self) -> bytes:
"""
Get the string representation of this key.
"""
return (b"LibNaCLSK:"
+ cast(X25519PrivateKey, self.key).private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
+ self.vk.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption()))
96 changes: 56 additions & 40 deletions ipv8/keyvault/private/libnaclkey.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,68 @@
import libnacl
import libnacl.dual
import libnacl.sign

from ...keyvault.keys import PrivateKey
from ...keyvault.public.libnaclkey import LibNaCLPK

try:
import libnacl
import libnacl.dual
import libnacl.sign

class LibNaCLSK(PrivateKey, LibNaCLPK):
"""
A LibNaCL implementation of a secret key.
"""

def __init__(self, binarykey: bytes = b"") -> None:
"""
Create a new LibNaCL secret key. Optionally load it from a string representation.
Otherwise generate it from the 25519 curve.

:param binarykey: load the sk from this string (see key_to_bin())
"""
# Load the key, if specified
if binarykey:
crypt, seed = (binarykey[:libnacl.crypto_box_SECRETKEYBYTES],
binarykey[libnacl.crypto_box_SECRETKEYBYTES: libnacl.crypto_box_SECRETKEYBYTES
+ libnacl.crypto_sign_SEEDBYTES])
key = libnacl.dual.DualSecret(crypt, seed)
else:
key = libnacl.dual.DualSecret()

super().__init__(pk=key.pk, hex_vk=key.hex_vk())
self.key = key

def pub(self) -> LibNaCLPK:
class LibNaCLSK(PrivateKey, LibNaCLPK):
"""
Get the public key for this secret key.
A LibNaCL implementation of a secret key.
"""
return LibNaCLPK(pk=self.key.pk, hex_vk=self.veri.hex_vk())

def signature(self, msg: bytes) -> bytes:
"""
Create a signature for a message.
def __init__(self, binarykey: bytes = b"") -> None:
"""
Create a new LibNaCL secret key. Optionally load it from a string representation.
Otherwise, generate it from the 25519 curve.
:param msg: the message to sign
:return: the signature for the message
"""
return self.key.signature(msg)
:param binarykey: load the sk from this string (see key_to_bin())
"""
# Load the key, if specified
if binarykey:
crypt, seed = (binarykey[:libnacl.crypto_box_SECRETKEYBYTES],
binarykey[libnacl.crypto_box_SECRETKEYBYTES: libnacl.crypto_box_SECRETKEYBYTES
+ libnacl.crypto_sign_SEEDBYTES])
key = libnacl.dual.DualSecret(crypt, seed)
else:
key = libnacl.dual.DualSecret()

super().__init__(pk=key.pk, hex_vk=key.hex_vk())
self.key = key

def key_to_bin(self) -> bytes:
def pub(self) -> LibNaCLPK:
"""
Get the public key for this secret key.
"""
return LibNaCLPK(pk=self.key.pk, hex_vk=self.veri.hex_vk())

def signature(self, msg: bytes) -> bytes:
"""
Create a signature for a message.
:param msg: the message to sign
:return: the signature for the message
"""
return self.key.signature(msg)

def key_to_bin(self) -> bytes:
"""
Get the string representation of this key.
"""
return b"LibNaCLSK:" + self.key.sk + self.key.seed

except (ImportError, OSError):
from .cryptography25519 import Cryptography25519SK


class LibNaCLSK(Cryptography25519SK): # type: ignore[no-redef]
"""
Get the string representation of this key.
Fallback class, actually Cryptography25519SK.
"""
return b"LibNaCLSK:" + self.key.sk + self.key.seed

def pub(self) -> LibNaCLPK: # type: ignore[override]
"""
Get the public key for this secret key.
"""
return LibNaCLPK(super().key_to_bin()[10:])
63 changes: 63 additions & 0 deletions ipv8/keyvault/public/cryptography25519.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

from binascii import unhexlify
from typing import cast

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat

from ...keyvault.keys import PublicKey

KEY_LENGTH = 32


class Cryptography25519PK(PublicKey):
"""
A fallback implementation for LibNaCL public keys.
"""

def __init__(self, binarykey: bytes = b"", pk: bytes | None = None, hex_vk: bytes | None = None) -> None:
"""
Create a new LibNaCL public key. Optionally load it from a string representation or
using a public key and verification key.
:param binarykey: load the pk from this string (see key_to_bin())
:param pk: the libnacl public key to use in byte format
:param hex_vk: a verification key in hex format
"""
# Load the key, if specified
if binarykey:
pk, vk = (binarykey[:KEY_LENGTH], binarykey[KEY_LENGTH: KEY_LENGTH * 2])
else:
vk = unhexlify(cast(bytes, hex_vk))
# Construct the public key and verifier objects
self.key = Ed25519PublicKey.from_public_bytes(cast(bytes, pk))
self.veri = Ed25519PublicKey.from_public_bytes(vk)

def verify(self, signature: bytes, msg: bytes) -> bool:
"""
Verify whether a given signature is correct for a message.
:param signature: the given signature
:param msg: the given message
"""
try:
self.veri.verify(signature, msg)
return True
except InvalidSignature:
return False

def key_to_bin(self) -> bytes:
"""
Get the string representation of this key.
"""
return (b"LibNaCLPK:"
+ self.key.public_bytes(Encoding.Raw, PublicFormat.Raw)
+ self.veri.public_bytes(Encoding.Raw, PublicFormat.Raw))

def get_signature_length(self) -> int:
"""
Returns the length, in bytes, of each signature made using EC.
"""
return KEY_LENGTH
92 changes: 51 additions & 41 deletions ipv8/keyvault/public/libnaclkey.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
from __future__ import annotations

import libnacl
import libnacl.encode
import libnacl.public
import libnacl.sign

from ...keyvault.keys import PublicKey

try:
import libnacl
import libnacl.encode
import libnacl.public
import libnacl.sign

class LibNaCLPK(PublicKey):
"""
A LibNaCL implementation of a public key.
"""

def __init__(self, binarykey: bytes = b"", pk: bytes | None = None, hex_vk: bytes | None = None) -> None:
"""
Create a new LibNaCL public key. Optionally load it from a string representation or
using a public key and verification key.

:param binarykey: load the pk from this string (see key_to_bin())
:param pk: the libnacl public key to use in byte format
:param hex_vk: a verification key in hex format
class LibNaCLPK(PublicKey):
"""
# Load the key, if specified
if binarykey:
pk, vk = (binarykey[:libnacl.crypto_box_SECRETKEYBYTES],
binarykey[libnacl.crypto_box_SECRETKEYBYTES: libnacl.crypto_box_SECRETKEYBYTES
+ libnacl.crypto_sign_SEEDBYTES])
hex_vk = libnacl.encode.hex_encode(vk)
# Construct the public key and verifier objects
self.key = libnacl.public.PublicKey(pk)
self.veri = libnacl.sign.Verifier(hex_vk)

def verify(self, signature: bytes, msg: bytes) -> bool:
A LibNaCL implementation of a public key.
"""
Verify whether a given signature is correct for a message.

:param signature: the given signature
:param msg: the given message
"""
return self.veri.verify(signature + msg)
def __init__(self, binarykey: bytes = b"", pk: bytes | None = None, hex_vk: bytes | None = None) -> None:
"""
Create a new LibNaCL public key. Optionally load it from a string representation or
using a public key and verification key.
:param binarykey: load the pk from this string (see key_to_bin())
:param pk: the libnacl public key to use in byte format
:param hex_vk: a verification key in hex format
"""
# Load the key, if specified
if binarykey:
pk, vk = (binarykey[:libnacl.crypto_box_SECRETKEYBYTES],
binarykey[libnacl.crypto_box_SECRETKEYBYTES: libnacl.crypto_box_SECRETKEYBYTES
+ libnacl.crypto_sign_SEEDBYTES])
hex_vk = libnacl.encode.hex_encode(vk)
# Construct the public key and verifier objects
self.key = libnacl.public.PublicKey(pk)
self.veri = libnacl.sign.Verifier(hex_vk)

def verify(self, signature: bytes, msg: bytes) -> bool:
"""
Verify whether a given signature is correct for a message.
:param signature: the given signature
:param msg: the given message
"""
return self.veri.verify(signature + msg)

def key_to_bin(self) -> bytes:
"""
Get the string representation of this key.
"""
return b"LibNaCLPK:" + self.key.pk + self.veri.vk

def get_signature_length(self) -> int:
"""
Returns the length, in bytes, of each signature made using EC.
"""
return libnacl.crypto_sign_BYTES

except (ImportError, OSError):
from .cryptography25519 import Cryptography25519PK

def key_to_bin(self) -> bytes:
"""
Get the string representation of this key.
"""
return b"LibNaCLPK:" + self.key.pk + self.veri.vk

def get_signature_length(self) -> int:
class LibNaCLPK(Cryptography25519PK): # type: ignore[no-redef]
"""
Returns the length, in bytes, of each signature made using EC.
Fallback class, actually Cryptography25519PK.
"""
return libnacl.crypto_sign_BYTES
Loading

0 comments on commit c44b481

Please sign in to comment.