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

Implement TAP 9 (Mandatory metadata signing schemes) #48

Merged
merged 35 commits into from
Aug 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a4143ac
Add argument for signature schemes to generate_rsa_key()
vladimir-v-diaz Aug 7, 2017
710c696
Add schemas for signature schemes
vladimir-v-diaz Aug 7, 2017
0ed5802
Test for the signature schemes
vladimir-v-diaz Aug 7, 2017
4978009
Add schemeta for specific signature schemes
vladimir-v-diaz Aug 7, 2017
6a5fd86
Ensure signature schemes can be set on imported keys
vladimir-v-diaz Aug 8, 2017
55f261c
Add ecdsa signature schemata
vladimir-v-diaz Aug 8, 2017
211e6a4
Edit ecdsa_keys.py to accept signature schemes [WIP]
vladimir-v-diaz Aug 8, 2017
b5cd93b
Begin refactoring the create_signature functions
vladimir-v-diaz Aug 9, 2017
523fb18
Fix schema for sig scheme: rsassa-pss-256->rsassa-pss-sha256
vladimir-v-diaz Aug 9, 2017
9305757
Fix doctests for pycrypto_keys.create_signature()
vladimir-v-diaz Aug 9, 2017
c8bb55b
Fix argument name: signature_method->signature_scheme
vladimir-v-diaz Aug 9, 2017
94b1ff5
Refactor signature functions
vladimir-v-diaz Aug 9, 2017
0c9b4ba
Fix default argument in keys.py
vladimir-v-diaz Aug 9, 2017
f7e398d
Change verify_rsa_signare() and verify_signature() argument name: met…
vladimir-v-diaz Aug 9, 2017
35be45f
Add scheme argument to ed25519_keys.py signature functions
vladimir-v-diaz Aug 9, 2017
96afcbe
Change remaining keys.py argument names: method->scheme
vladimir-v-diaz Aug 9, 2017
d7770a5
Change ecdsa_keys.py signature functions to use scheme argument
vladimir-v-diaz Aug 9, 2017
5e888e1
Fix remaining doctest failures in keys.py
vladimir-v-diaz Aug 9, 2017
b76b3d9
Update/resolve pyca_crypto_key.py unit test failures
vladimir-v-diaz Aug 10, 2017
5d7425c
Fix unit tests for pycrypto_keys.py
vladimir-v-diaz Aug 10, 2017
92099a0
Resolve unit tests failures in ed25519_keys.py
vladimir-v-diaz Aug 10, 2017
c2d4f00
Resolve unit tests failures in test_ecdsa_keys.py
vladimir-v-diaz Aug 10, 2017
3f03dc9
Change rsa signature scheme in formats.py
vladimir-v-diaz Aug 10, 2017
990404c
Verify signature scheme in keys.verify_signature()
vladimir-v-diaz Aug 10, 2017
bcccd97
Resolve unit test failures in test_keys.py
vladimir-v-diaz Aug 10, 2017
7593196
Add signature scheme argument to interface.py
vladimir-v-diaz Aug 10, 2017
92d24f5
Fix typo in keys.py docstring
vladimir-v-diaz Aug 10, 2017
a3dc965
Add missing scheme argument to test_util.py
vladimir-v-diaz Aug 10, 2017
343ef5c
Replace remaining instances of signature 'method' in docstrings
vladimir-v-diaz Aug 10, 2017
fb5e623
Resolve conflicts
vladimir-v-diaz Aug 10, 2017
573f5c4
Fix code coverage for keys.py
vladimir-v-diaz Aug 11, 2017
c2a3c4b
Address code coverage for pycrypto_keys.py
vladimir-v-diaz Aug 11, 2017
b2ef419
Address code coverage for pyca_crypto_keys.py
vladimir-v-diaz Aug 11, 2017
c28d552
Resolve code coverage for ecdsa_keys.py
vladimir-v-diaz Aug 11, 2017
1628118
Resolve code coverage for ed25519_keys.py
vladimir-v-diaz Aug 11, 2017
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
107 changes: 64 additions & 43 deletions securesystemslib/ecdsa_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@
import securesystemslib.formats
import securesystemslib.exceptions

_SUPPORTED_ECDSA_ALGORITHMS = ['ecdsa-sha2-nistp256']
_SUPPORTED_ECDSA_SCHEMES = ['ecdsa-sha2-nistp256']

logger = logging.getLogger('securesystemslib_ecdsa_keys')


def generate_public_and_private(algorithm='ecdsa-sha2-nistp256'):
def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
"""
<Purpose>
Generate a pair of ECDSA public and private keys with one of the supported,
Expand Down Expand Up @@ -99,7 +99,7 @@ def generate_public_and_private(algorithm='ecdsa-sha2-nistp256'):
True

<Arguments>
algorithm:
scheme:
A string indicating which algorithm to use for the generation of the
public and private ECDSA keys. 'ecdsa-sha2-nistp256' is the only
currently supported ECDSA algorithm, which is supported by OpenSSH and
Expand All @@ -126,21 +126,21 @@ def generate_public_and_private(algorithm='ecdsa-sha2-nistp256'):
# supported ECDSA algorithms. It must conform to
# 'securesystemslib.formats.ECDSAALGORITHMS_SCHEMA'. Raise
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still a couple of mentionings of algorithm that probably should be scheme now in comments, also the UnsupportedAlgorithmError is still used for unsupported schemes. I guess this is not a big deal.

# 'securesystemslib.exceptions.FormatError' if the check fails.
securesystemslib.formats.ECDSAALGORITHMS_SCHEMA.check_match(algorithm)
securesystemslib.formats.ECDSA_SIG_SCHEMA.check_match(scheme)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be ECDSA_SIG_SCHEME_SCHEMA now? (just kidding)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually not purely a joke. Currently the schemas for signatures and signature schemes are not really distinguishable by their names. So *_SIG_SCHEME_SCHEMA might not be the worst of ideas? Especially since there already is a SIG_SCHEME_SCHEMA. :)


public_key = None
private_key = None

if algorithm == 'ecdsa-sha2-nistp256':
if scheme == 'ecdsa-sha2-nistp256':
private_key = ec.generate_private_key(ec.SECP256R1, default_backend())
public_key = private_key.public_key()

# The formats ECDSAALGORITHMS_SCHEMA check above should have detected any
# invalid 'algorithm'.
else: #pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError('An unsupported'
' algorithm was specified: ' + repr(algorithm) + '.\n Supported'
' algorithms: ' + repr(_SUPPORTED_ECDSA_ALGORITHMS))
' algorithm was specified: ' + repr(scheme) + '.\n Supported'
' algorithms: ' + repr(_SUPPORTED_ECDSA_SCHEMES))

private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
Expand All @@ -155,18 +155,18 @@ def generate_public_and_private(algorithm='ecdsa-sha2-nistp256'):



def create_signature(public_key, private_key, data):
def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'):
"""
<Purpose>
Return a (signature, method) tuple.
Return a (signature, scheme) tuple.

>>> algorithm = 'ecdsa-sha2-nistp256'
>>> public, private = generate_public_and_private(algorithm)
>>> requested_scheme = 'ecdsa-sha2-nistp256'
>>> public, private = generate_public_and_private(requested_scheme)
>>> data = b'The quick brown fox jumps over the lazy dog'
>>> signature, method = create_signature(public, private, data)
>>> signature, scheme = create_signature(public, private, data, requested_scheme)
>>> securesystemslib.formats.ECDSASIGNATURE_SCHEMA.matches(signature)
True
>>> method == algorithm
>>> requested_scheme == scheme
True

<Arguments>
Expand All @@ -179,12 +179,19 @@ def create_signature(public_key, private_key, data):
data:
Byte data used by create_signature() to generate the signature returned.

scheme:
The signature scheme used to generate the signature. For example:
'ecesa-sha2-nistp256'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ecdsa-sha2-nistp256


<Exceptions>
securesystemslib.exceptions.FormatError, if the arguments are improperly
formatted.

securesystemslib.exceptions.CryptoError, if a signature cannot be created.

securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is
an not one of the supported signature schemes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-an


<Side Effects>
None.

Expand All @@ -203,47 +210,56 @@ def create_signature(public_key, private_key, data):
# Is 'private_key' properly formatted?
securesystemslib.formats.PEMECDSA_SCHEMA.check_match(private_key)

method = 'ecdsa-sha2-nistp256'
# Is 'scheme' properly formatted?
securesystemslib.formats.ECDSA_SIG_SCHEMA.check_match(scheme)

try:
private_key = load_pem_private_key(private_key.encode('utf-8'),
password=None, backend=default_backend())
# A defensive check for a valid 'scheme'. The check_match() above
# should have already validated it...
if scheme == 'ecdsa-sha2-nistp256': #pragma: no cover
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a reason for doing the exact same check twice in a row. I don't see a big problem either, but it feels strange to me to redo what ECDSA_SIG_SCHEMA.check_match(scheme) already does.

However, I would definitely remove the #pragma: no cover here, because that disables the decreased coverage alert when you remove tests that touch code inside the if-block.

Also, I'm not sure if it is a good idea to fail with different errors FormatError vs. UnsupportedAlgorithmError.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can wrapECDSA_SIG_SCHEMA.check_match(scheme) in a try/except block and raise UnsupportedAlgorithmError on FormatError?

try:
private_key = load_pem_private_key(private_key.encode('utf-8'),
password=None, backend=default_backend())

signer = private_key.signer(ec.ECDSA(hashes.SHA256()))
signer.update(data)
signature = signer.finalize()

signer = private_key.signer(ec.ECDSA(hashes.SHA256()))
signer.update(data)
signature = signer.finalize()
except TypeError as e:
raise securesystemslib.exceptions.CryptoError('Could not create'
' signature: ' + str(e))

except TypeError as e:
raise securesystemslib.exceptions.CryptoError('Could not create'
' signature: ' + str(e))
else: #pragma: no cover
raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported'
' signature scheme is specified: ' + repr(scheme))

return signature, method
return signature, scheme





def verify_signature(public_key, method, signature, data):
def verify_signature(public_key, scheme, signature, data):
"""
<Purpose>
...
Verify that 'signature' was produced by the private key associated with
'public_key'.

>>> algorithm = 'ecdsa-sha2-nistp256'
>>> public, private = generate_public_and_private(algorithm)
>>> scheme = 'ecdsa-sha2-nistp256'
>>> public, private = generate_public_and_private(scheme)
>>> data = b'The quick brown fox jumps over the lazy dog'
>>> signature, method = create_signature(public, private, data)
>>> verify_signature(public, method, signature, data)
>>> signature, scheme = create_signature(public, private, data, scheme)
>>> verify_signature(public, scheme, signature, data)
True
>>> verify_signature(public, method, signature, b'bad data')
>>> verify_signature(public, scheme, signature, b'bad data')
False

<Arguments>
public_key:
The ECDSA public key in PEM format. The publi key is needed to verify
The ECDSA public key in PEM format. The public key is needed to verify
'signature'.

method:
The signature method used to generate 'signature'. For example:
scheme:
The signature scheme used to generate 'signature'. For example:
'ecdsa-sha2-nistp256'.

signature:
Expand All @@ -254,8 +270,11 @@ def verify_signature(public_key, method, signature, data):
Byte data that was used by create_signature() to generate 'signature'.

<Exceptions>
securesystemslib.exceptions.FormatError, if any of the arguments are improperly
formatted.
securesystemslib.exceptions.FormatError, if any of the arguments are
improperly formatted.

securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is
not one of the supported signature schemes.

<Side Effects>
None.
Expand All @@ -268,11 +287,13 @@ def verify_signature(public_key, method, signature, data):
# Are the arguments properly formatted?
# If not, raise 'securesystemslib.exceptions.FormatError'.
securesystemslib.formats.PEMECDSA_SCHEMA.check_match(public_key)
securesystemslib.formats.NAME_SCHEMA.check_match(method)
securesystemslib.formats.ECDSA_SIG_SCHEMA.check_match(scheme)
securesystemslib.formats.ECDSASIGNATURE_SCHEMA.check_match(signature)

# Is 'method' one of the supported ECDSA algorithms?
if method in _SUPPORTED_ECDSA_ALGORITHMS:
# Is 'scheme' one of the supported ECDSA signature schemes? A defensive
# check for a valid 'scheme'. The check_match() above should have validated
# it...
if scheme in _SUPPORTED_ECDSA_SCHEMES: #pragma: no cover
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about redundant checks with different Errors and #pragma: no cover. Btw. it seems that this comment applies to various places throughout the PR.

ecdsa_key = load_pem_public_key(public_key.encode('utf-8'), backend=default_backend())

if not isinstance(ecdsa_key, ec.EllipticCurvePublicKey):
Expand All @@ -299,10 +320,10 @@ def verify_signature(public_key, method, signature, data):
except cryptography.exceptions.InvalidSignature:
return False

else:
raise securesystemslib.exceptions.UnknownMethodError('Unsupported signing'
' method given: ' + repr(method) + '. \nSupported'
' methods: ' + repr(_SUPPORTED_ECDSA_ALGORITHMS))
else: #pragma: no cover
raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported'
' signature scheme is given: ' + repr(scheme) + '. \nSupported'
' schemes: ' + repr(_SUPPORTED_ECDSA_SCHEMES))



Expand Down
Loading