Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cryptography-based fallback when NaCL import fails #1225

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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