Skip to content
Closed
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
29 changes: 29 additions & 0 deletions docs/asymmetric.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ API:
- [ECDSA](#ecdsa)
- [`ecdsa_sign()`](#ecdsa_sign-function)
- [`ecdsa_verify()`](#ecdsa_verify-function)
- [EDDSA](#eddsa)
- [`eddsa_verify()`](#eddsa_verify-function)

## Keys/Certificates

Expand Down Expand Up @@ -643,3 +645,30 @@ API:
> ```
>
> Verifies an ECDSA signature

### `eddsa_verify()` function

> ```python
> def eddsa_verify(certificate_or_public_key, signature, data, hash_algorithm="raw"):
> """
> :param certificate_or_public_key:
> A Certificate or PublicKey instance to verify the signature with
>
> :param signature:
> A byte string of the signature to verify
>
> :param data:
> A byte string of the data the signature is for
>
> :param hash_algorithm:
> A unicode string of "raw"
>
> :raises:
> oscrypto.errors.SignatureError - when the signature is determined to be invalid
> ValueError - when any of the parameters contain an invalid value
> TypeError - when any of the parameters are of the wrong type
> OSError - when an error is returned by the OS crypto library
> """
> ```
>
> Verifies an EdDSA signature
2 changes: 2 additions & 0 deletions oscrypto/_openssl/_libcrypto_cffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@

int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey);
int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx, const char *sig, size_t siglen);
int EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret,
size_t siglen, const unsigned char *tbs, size_t tbslen);

int EVP_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, int p1, void *p2);
""")
9 changes: 9 additions & 0 deletions oscrypto/_openssl/_libcrypto_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,15 @@
]
libcrypto.EVP_DigestVerifyInit.restype = c_int

libcrypto.EVP_DigestVerify.argtypes = [
P_EVP_MD_CTX,
c_char_p,
c_size_t,
c_char_p,
c_size_t
]
libcrypto.EVP_DigestVerify.restype = c_int

libcrypto.EVP_DigestVerifyFinal.argtypes = [
P_EVP_MD_CTX,
c_char_p,
Expand Down
59 changes: 55 additions & 4 deletions oscrypto/_openssl/asymmetric.py
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,44 @@ def ecdsa_verify(certificate_or_public_key, signature, data, hash_algorithm):
return _verify(certificate_or_public_key, signature, data, hash_algorithm)


def eddsa_verify(certificate_or_public_key, signature, data, hash_algorithm="raw"):
"""
Verifies an EdDSA signature

:param certificate_or_public_key:
A Certificate or PublicKey instance to verify the signature with

:param signature:
A byte string of the signature to verify

:param data:
A byte string of the data the signature is for

:param hash_algorithm:
A unicode string of "raw"

:raises:
oscrypto.errors.SignatureError - when the signature is determined to be invalid
ValueError - when any of the parameters contain an invalid value
TypeError - when any of the parameters are of the wrong type
OSError - when an error is returned by the OS crypto library
"""

if certificate_or_public_key.algorithm not in ['ed25519', 'ed448']:
raise ValueError(pretty_message(
'''
The key specified is not an Edwards curve public key, but %s
''',
certificate_or_public_key.algorithm.upper()
))
if hash_algorithm != "raw":
raise ValueError(pretty_message(
'''Only pure EdDSA signature verification is supported, but %s hashing wanted''',
str(hash_algorithm).upper()
))
return _verify(certificate_or_public_key, signature, data, hash_algorithm)


def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_padding=False):
"""
Verifies an RSA, DSA or ECDSA signature
Expand Down Expand Up @@ -1285,11 +1323,15 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
valid_hash_algorithms = set(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])
if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms |= set(['raw'])
if cp_alg in ['ed25519', 'ed448']:
valid_hash_algorithms = set(['raw'])

if hash_algorithm not in valid_hash_algorithms:
valid_hash_algorithms_error = '"md5", "sha1", "sha224", "sha256", "sha384", "sha512"'
if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms_error += ', "raw"'
if cp_alg in ['ed25519', 'ed448']:
valid_hash_algorithms_error = '"raw"'
raise ValueError(pretty_message(
'''
hash_algorithm must be one of %s, not %s
Expand Down Expand Up @@ -1366,7 +1408,8 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
'sha224': libcrypto.EVP_sha224,
'sha256': libcrypto.EVP_sha256,
'sha384': libcrypto.EVP_sha384,
'sha512': libcrypto.EVP_sha512
'sha512': libcrypto.EVP_sha512,
'raw': null
}[hash_algorithm]()

if libcrypto_version_info < (1,):
Expand Down Expand Up @@ -1476,10 +1519,18 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
)
handle_openssl_error(res)

res = libcrypto.EVP_DigestUpdate(evp_md_ctx, data, len(data))
handle_openssl_error(res)
if cp_alg in ['ed25519', 'ed448']:
res = libcrypto.EVP_DigestVerify(
evp_md_ctx,
signature, len(signature),
data, len(data)
)

else:
res = libcrypto.EVP_DigestUpdate(evp_md_ctx, data, len(data))
handle_openssl_error(res)

res = libcrypto.EVP_DigestVerifyFinal(evp_md_ctx, signature, len(signature))
res = libcrypto.EVP_DigestVerifyFinal(evp_md_ctx, signature, len(signature))

if res < 1:
raise SignatureError('Signature is invalid')
Expand Down
6 changes: 4 additions & 2 deletions oscrypto/asymmetric.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
dsa_verify,
ecdsa_sign,
ecdsa_verify,
eddsa_verify,
generate_pair,
generate_dh_parameters,
load_certificate,
Expand Down Expand Up @@ -114,6 +115,7 @@
'dump_public_key',
'ecdsa_sign',
'ecdsa_verify',
'eddsa_verify',
'generate_pair',
'generate_dh_parameters',
'load_certificate',
Expand Down Expand Up @@ -277,7 +279,7 @@ def dump_private_key(private_key, passphrase, encoding='pem', target_ms=200):
ValueError - when a blank string is provided for the passphrase

:return:
A byte string of the encoded and encrypted public key
A byte string of the encoded and encrypted private key
"""

if encoding not in set(['pem', 'der']):
Expand Down Expand Up @@ -396,7 +398,7 @@ def dump_openssl_private_key(private_key, passphrase):
ValueError - when a blank string is provided for the passphrase

:return:
A byte string of the encoded and encrypted public key
A byte string of the encoded and encrypted private key
"""

if passphrase is not None:
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/ed25519_signature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
���q�����~w#|P��6��H3>�Z�ٖ;��n��&O��-��/")b�?Q��^���
Binary file added tests/fixtures/ed448_signature
Binary file not shown.
3 changes: 3 additions & 0 deletions tests/fixtures/keys/test-ed25519.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEILIGXMO7tlgSEKCNxQlWmA8yax3kVaILTs8m6hn5zF5K
-----END PRIVATE KEY-----
4 changes: 4 additions & 0 deletions tests/fixtures/keys/test-ed448.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MEcCAQAwBQYDK2VxBDsEOXe7vOE+J7yQyCLqabvfDpb4SkcdYxLkgjS2C1+EJ5Te
hdFIcRDsov5uM5Cv74WBjrtrjxygaa8mGA==
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions tests/fixtures/keys/test-public-ed25519.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAlDf2YSQOKbx+Mr2jL7csS+ylJh/zg2/QaqSvOLB7/7M=
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions tests/fixtures/keys/test-public-ed448.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MEMwBQYDK2VxAzoABfCHZW35+uv1Zkvg9ktT/Ju2iKQ07dCwE06yMRFBHYv6ZZPs
WclPWA58tGy/i2xJRS0znShFrXMA
-----END PUBLIC KEY-----
58 changes: 58 additions & 0 deletions tests/test_asymmetric.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,64 @@ def test_ecdsa_verify_fail_each_byte(self):
with self.assertRaises(errors.SignatureError):
asymmetric.ecdsa_verify(public, signature, original_data + b'1', 'sha1')

def test_ed25519_verify(self):
with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f:
original_data = f.read()
with open(os.path.join(fixtures_dir, 'ed25519_signature'), 'rb') as f:
signature = f.read()
public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-public-ed25519.key'))
asymmetric.eddsa_verify(public, signature, original_data)

def test_ed25519_verify_fail_each_byte(self):
with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f:
original_data = f.read()
with open(os.path.join(fixtures_dir, 'ed25519_signature'), 'rb') as f:
original_signature = f.read()
public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-public-ed25519.key'))
for i in range(0, len(original_signature)):
if i == 0:
signature = b'\xab' + original_signature[1:]
elif i == len(original_signature) - 1:
signature = original_signature[0:-1] + b'\xab'
else:
signature = original_signature[0:i] + b'\xab' + original_signature[i+1:]
with self.assertRaises(errors.SignatureError):
asymmetric.eddsa_verify(public, signature, original_data + b'1')

def test_ed448_verify(self):
with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f:
original_data = f.read()
with open(os.path.join(fixtures_dir, 'ed448_signature'), 'rb') as f:
signature = f.read()
public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-public-ed448.key'))
asymmetric.eddsa_verify(public, signature, original_data)

def test_ed448_verify_fail_each_byte(self):
with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f:
original_data = f.read()
with open(os.path.join(fixtures_dir, 'ed448_signature'), 'rb') as f:
original_signature = f.read()
public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-public-ed448.key'))
for i in range(0, len(original_signature)):
if i == 0:
signature = b'\xab' + original_signature[1:]
elif i == len(original_signature) - 1:
signature = original_signature[0:-1] + b'\xab'
else:
signature = original_signature[0:i] + b'\xab' + original_signature[i+1:]
with self.assertRaises(errors.SignatureError):
asymmetric.eddsa_verify(public, signature, original_data + b'1', 'raw')

def test_hashed_ed25519(self):
"Only pure EdDSA is supported"
with open(os.path.join(fixtures_dir, 'message.txt'), 'rb') as f:
original_data = f.read()
with open(os.path.join(fixtures_dir, 'ed448_signature'), 'rb') as f:
signature = f.read()
public = asymmetric.load_public_key(os.path.join(fixtures_dir, 'keys/test-public-ed448.key'))
with self.assertRaises(ValueError):
asymmetric.eddsa_verify(public, signature, original_data, "sha256")

def test_rsa_pkcs1v15_encrypt(self):
original_data = b'This is data to encrypt'
private = asymmetric.load_private_key(os.path.join(fixtures_dir, 'keys/test.key'))
Expand Down