From c2e923297f26ecd3a033c98fc67317b78f2743a2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 10:49:39 -0700 Subject: [PATCH 1/8] Add google.auth.crypt --- docs/reference/google.auth.crypt.rst | 7 + docs/reference/google.auth.rst | 7 + docs/reference/google.oauth2.rst | 8 + google/auth/_helpers.py | 65 ++++++++ google/auth/crypt.py | 237 +++++++++++++++++++++++++++ tests/data/other_cert.pem | 33 ++++ tests/data/pem_from_pkcs12.pem | 32 ++++ tests/data/privatekey.p12 | Bin 0 -> 2452 bytes tests/data/privatekey.pem | 27 +++ tests/data/privatekey.pub | 8 + tests/data/public_cert.pem | 19 +++ tests/test__helpers.py | 50 ++++++ tests/test_crypt.py | 172 +++++++++++++++++++ tox.ini | 4 +- 14 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 docs/reference/google.auth.crypt.rst create mode 100644 docs/reference/google.oauth2.rst create mode 100644 google/auth/_helpers.py create mode 100644 google/auth/crypt.py create mode 100644 tests/data/other_cert.pem create mode 100644 tests/data/pem_from_pkcs12.pem create mode 100644 tests/data/privatekey.p12 create mode 100644 tests/data/privatekey.pem create mode 100644 tests/data/privatekey.pub create mode 100644 tests/data/public_cert.pem create mode 100644 tests/test__helpers.py create mode 100644 tests/test_crypt.py diff --git a/docs/reference/google.auth.crypt.rst b/docs/reference/google.auth.crypt.rst new file mode 100644 index 000000000..7f8c0f837 --- /dev/null +++ b/docs/reference/google.auth.crypt.rst @@ -0,0 +1,7 @@ +google.auth.crypt module +======================== + +.. automodule:: google.auth.crypt + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/google.auth.rst b/docs/reference/google.auth.rst index 0533449e0..4c39cb96b 100644 --- a/docs/reference/google.auth.rst +++ b/docs/reference/google.auth.rst @@ -6,3 +6,10 @@ google.auth package :undoc-members: :show-inheritance: +Submodules +---------- + +.. toctree:: + + google.auth.crypt + diff --git a/docs/reference/google.oauth2.rst b/docs/reference/google.oauth2.rst new file mode 100644 index 000000000..fb67d279c --- /dev/null +++ b/docs/reference/google.oauth2.rst @@ -0,0 +1,8 @@ +google.oauth2 package +===================== + +.. automodule:: google.oauth2 + :members: + :undoc-members: + :show-inheritance: + diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py new file mode 100644 index 000000000..2d3b6534c --- /dev/null +++ b/google/auth/_helpers.py @@ -0,0 +1,65 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for commonly used utilities.""" + +import six + + +def to_bytes(value, encoding='utf-8'): + """Converts a string value to bytes, if necessary. + + Unfortunately, ``six.b`` is insufficient for this task since in + Python 2 because it does not modify ``unicode`` objects. + + Args: + value (Union[str, bytes]): The value to be converted. + encoding (str): The encoding to use to convert unicode to bytes. + Defaults to "utf-8". + + Returns: + bytes: The original value converted to bytes (if unicode) or as + passed in if it started out as bytes. + + Raises: + ValueError: If the value could not be converted to bytes. + """ + result = (value.encode(encoding) + if isinstance(value, six.text_type) else value) + if isinstance(result, six.binary_type): + return result + else: + raise ValueError('{0!r} could not be converted to bytes'.format(value)) + + +def from_bytes(value): + """Converts bytes to a string value, if necessary. + + Args: + value (Union[str, bytes]): The value to be converted. + + Returns: + str: The original value converted to unicode (if bytes) or as passed in + if it started out as unicode. + + Raises: + ValueError: If the value could not be converted to unicode. + """ + result = (value.decode('utf-8') + if isinstance(value, six.binary_type) else value) + if isinstance(result, six.text_type): + return result + else: + raise ValueError( + '{0!r} could not be converted to unicode'.format(value)) diff --git a/google/auth/crypt.py b/google/auth/crypt.py new file mode 100644 index 000000000..b914e458e --- /dev/null +++ b/google/auth/crypt.py @@ -0,0 +1,237 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cryptography helpers for verifying and signing messages. + +Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages +to parse PEM files storing PKCS#1 or PKCS#8 keys as well as +certificates. There is no support for p12 files. + +The simplest way to verify signatures is using :func:`verify_signature`:: + + cert = open('certs.pem').read() + valid = crypt.verify_signature(message, signature, cert) + +If you're going to verify many messages with the same certificate, you can use +:class:`Verifier`:: + + cert = open('certs.pem').read() + verifier = crypt.Verifier.from_string(cert) + valid = verifier.verify(message, signature) + + +To sign messages use :class:`Signer` with a private key:: + + private_key = open('private_key.pem').read() + signer = crypt.Signer(private_key) + signature = signer.sign(message) + +""" + +from pyasn1.codec.der import decoder +from pyasn1_modules import pem +from pyasn1_modules.rfc2459 import Certificate +from pyasn1_modules.rfc5208 import PrivateKeyInfo +import rsa +import six + +from google.auth import _helpers + + +_PKCS12_ERROR = r"""\ +PKCS12 format is not supported by the RSA library. +Please convert .p12 format to .pem format: + $ cat key.p12 | \ + > openssl pkcs12 -nodes -nocerts -passin pass:notasecret | \ + > openssl rsa > key.pem +""" + +_POW2 = (128, 64, 32, 16, 8, 4, 2, 1) +_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----', + '-----END RSA PRIVATE KEY-----') +_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', + '-----END PRIVATE KEY-----') +_PKCS8_SPEC = PrivateKeyInfo() + + +def _bit_list_to_bytes(bit_list): + """Converts an iterable of 1s and 0s to bytes. + + Combines the list 8 at a time, treating each group of 8 bits + as a single byte. + + Args: + bit_list (Sequence): Sequence of 1s and 0s. + + Returns: + bytes: The decoded bytes. + """ + num_bits = len(bit_list) + byte_vals = bytearray() + for start in six.moves.xrange(0, num_bits, 8): + curr_bits = bit_list[start:start + 8] + char_val = sum(val * digit + for val, digit in zip(_POW2, curr_bits)) + byte_vals.append(char_val) + return bytes(byte_vals) + + +class Verifier(object): + """This object is used to verify cryptographic signatures. + + Args: + public_key (rsa.key.PublicKey): The public key used to verify + signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + def verify(self, message, signature): + """Verifies a message against a cryptographic signature. + + Args: + message (Union[str, bytes]): The message to verify. + signature (Union[str, bytes]): The cryptography signature to check. + + Returns: + bool: True if message was signed by the private key associated + with the public key that this object was constructed with. + """ + message = _helpers.to_bytes(message, encoding='utf-8') + try: + return rsa.pkcs1.verify(message, signature, self._pubkey) + except (ValueError, rsa.pkcs1.VerificationError): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + """ + public_key = _helpers.to_bytes(public_key) + is_x509_cert = b'-----BEGIN CERTIFICATE-----' in public_key + + # If this is a certificate, extract the public key info. + if is_x509_cert: + der = rsa.pem.load_pem(public_key, 'CERTIFICATE') + asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate()) + if remaining != b'': + raise ValueError('Unused bytes', remaining) + + cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo'] + key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey']) + pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER') + else: + pubkey = rsa.PublicKey.load_pkcs1(public_key, 'PEM') + return cls(pubkey) + + +def verify_signature(message, signature, certs): + """Verify a cryptographic signature. + + Checks that the provided ``signature`` was generated from ``bytes`` using + the private key associated with the ``cert``. + + Args: + message (Union[str, bytes]): The plaintext message. + signature (Union[str, bytes]): The cryptographic signature to check. + certs (Union[Sequence, str, bytes]): The certificate or certificates + to use to check the signature. + + Returns: + bool: True if the signature is valid, otherwise False. + """ + if isinstance(certs, six.binary_type): + certs = [certs] + + for cert in certs: + verifier = Verifier.from_string(cert) + if verifier.verify(message, signature): + return True + return False + + +class Signer(object): + """Signs messages with a private key. + + Args: + private_key (rsa.key.PrivateKey): The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self.key_id = key_id + + def sign(self, message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message for the given key. + """ + message = _helpers.to_bytes(message, encoding='utf-8') + return rsa.pkcs1.sign(message, self._key, 'SHA-256') + + @classmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + """ + key = _helpers.from_bytes(key) # PEM expects str in Python 3 + marker_id, key_bytes = pem.readPemBlocksFromFile( + six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER) + + # Key is in pkcs1 format. + if marker_id == 0: + private_key = rsa.key.PrivateKey.load_pkcs1( + key_bytes, format='DER') + # Key is in pkcs8. + elif marker_id == 1: + key_info, remaining = decoder.decode( + key_bytes, asn1Spec=_PKCS8_SPEC) + if remaining != b'': + raise ValueError('Unused bytes', remaining) + private_key_info = key_info.getComponentByName('privateKey') + private_key = rsa.key.PrivateKey.load_pkcs1( + private_key_info.asOctets(), format='DER') + else: + raise ValueError('No key could be detected.') + + return cls(private_key, key_id=key_id) diff --git a/tests/data/other_cert.pem b/tests/data/other_cert.pem new file mode 100644 index 000000000..6895d1e7b --- /dev/null +++ b/tests/data/other_cert.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- diff --git a/tests/data/pem_from_pkcs12.pem b/tests/data/pem_from_pkcs12.pem new file mode 100644 index 000000000..2d77e10c1 --- /dev/null +++ b/tests/data/pem_from_pkcs12.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: key + localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi +tUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p +oJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR +aIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt +w21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE +GKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp ++qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN +TzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4 +QoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG +Dy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo +f1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR ++DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p +IwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a +c3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7 +SgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0 +jGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY +iuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5 +sdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO +GCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk +Brn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk +t7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2 +DwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS +LZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB +WGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa +XUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB +VL5h7N0VstYhGgycuPpcIUQa +-----END PRIVATE KEY----- diff --git a/tests/data/privatekey.p12 b/tests/data/privatekey.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c369ecb6e664cafddf5a817ce0d50c2059867042 GIT binary patch literal 2452 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 Kf`EbF Date: Tue, 4 Oct 2016 10:58:26 -0700 Subject: [PATCH 2/8] Remove unused constant --- google/auth/crypt.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/google/auth/crypt.py b/google/auth/crypt.py index b914e458e..d325d8a02 100644 --- a/google/auth/crypt.py +++ b/google/auth/crypt.py @@ -48,15 +48,6 @@ from google.auth import _helpers - -_PKCS12_ERROR = r"""\ -PKCS12 format is not supported by the RSA library. -Please convert .p12 format to .pem format: - $ cat key.p12 | \ - > openssl pkcs12 -nodes -nocerts -passin pass:notasecret | \ - > openssl rsa > key.pem -""" - _POW2 = (128, 64, 32, 16, 8, 4, 2, 1) _PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----') From 6b69d1944a65bd21b053d0d913208c42a503a3bf Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 11:25:28 -0700 Subject: [PATCH 3/8] Address review comments --- google/auth/crypt.py | 5 +++-- tests/test_crypt.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/google/auth/crypt.py b/google/auth/crypt.py index d325d8a02..7fd108e62 100644 --- a/google/auth/crypt.py +++ b/google/auth/crypt.py @@ -49,6 +49,7 @@ from google.auth import _helpers _POW2 = (128, 64, 32, 16, 8, 4, 2, 1) +_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' _PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----') _PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', @@ -122,7 +123,7 @@ def from_string(cls, public_key): ValueError: If the public_key can't be parsed. """ public_key = _helpers.to_bytes(public_key) - is_x509_cert = b'-----BEGIN CERTIFICATE-----' in public_key + is_x509_cert = _CERTIFICATE_MARKER in public_key # If this is a certificate, extract the public key info. if is_x509_cert: @@ -154,7 +155,7 @@ def verify_signature(message, signature, certs): Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, six.binary_type): + if isinstance(certs, (six.string_type, six.binary_type)): certs = [certs] for cert in certs: diff --git a/tests/test_crypt.py b/tests/test_crypt.py index 580729e30..29e01d173 100644 --- a/tests/test_crypt.py +++ b/tests/test_crypt.py @@ -52,11 +52,11 @@ def test_verify_signature(): signature = signer.sign(to_sign) assert crypt.verify_signature( - to_sign, signature, PUBLIC_CERT_BYTES) is True + to_sign, signature, PUBLIC_CERT_BYTES) # List of certs assert crypt.verify_signature( - to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) is True + to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) def test_verify_signature_failure(): @@ -64,18 +64,18 @@ def test_verify_signature_failure(): signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) signature = signer.sign(to_sign) - assert crypt.verify_signature( - to_sign, signature, OTHER_CERT_BYTES) is False + assert not crypt.verify_signature( + to_sign, signature, OTHER_CERT_BYTES) -class TestVerifier: +class TestVerifier(object): def test_verify_success(self): to_sign = b'foo' signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) - assert verifier.verify(to_sign, actual_signature) is True + assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): to_sign = u'foo' @@ -83,14 +83,14 @@ def test_verify_unicode_success(self): actual_signature = signer.sign(to_sign) verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) - assert verifier.verify(to_sign, actual_signature) is True + assert verifier.verify(to_sign, actual_signature) def test_verify_failure(self): verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) bad_signature1 = b'' - assert verifier.verify(b'foo', bad_signature1) is False + assert not verifier.verify(b'foo', bad_signature1) bad_signature2 = b'a' - assert verifier.verify(b'foo', bad_signature2) is False + assert not verifier.verify(b'foo', bad_signature2) def test_from_string_pub_key(self): verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) @@ -124,7 +124,7 @@ def test_from_string_pub_cert_failure(self): load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') -class TestSigner: +class TestSigner(object): def test_from_string_pkcs1(self): signer = crypt.Signer.from_string(PKCS1_KEY_BYTES) assert isinstance(signer, crypt.Signer) From 3ca610fef3a600b38cdcaec0e934fcbcb8a771a8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 11:26:58 -0700 Subject: [PATCH 4/8] Fix six.string_type -> six.text_type. --- google/auth/crypt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt.py b/google/auth/crypt.py index 7fd108e62..2a131393f 100644 --- a/google/auth/crypt.py +++ b/google/auth/crypt.py @@ -155,7 +155,7 @@ def verify_signature(message, signature, certs): Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, (six.string_type, six.binary_type)): + if isinstance(certs, (six.text_type, six.binary_type)): certs = [certs] for cert in certs: From 632df97e6cfaa8915209a743795d5955daa61b8e Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 12:52:51 -0700 Subject: [PATCH 5/8] Switch to six.moves.zip --- google/auth/crypt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/crypt.py b/google/auth/crypt.py index 2a131393f..17ff060d2 100644 --- a/google/auth/crypt.py +++ b/google/auth/crypt.py @@ -74,7 +74,7 @@ def _bit_list_to_bytes(bit_list): for start in six.moves.xrange(0, num_bits, 8): curr_bits = bit_list[start:start + 8] char_val = sum(val * digit - for val, digit in zip(_POW2, curr_bits)) + for val, digit in six.moves.zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) From ac1b7c90c6251090de5c2002ab87d49256dafc86 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 13:29:15 -0700 Subject: [PATCH 6/8] Remove explicit encoding --- google/auth/crypt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/auth/crypt.py b/google/auth/crypt.py index 17ff060d2..d347600f0 100644 --- a/google/auth/crypt.py +++ b/google/auth/crypt.py @@ -101,7 +101,7 @@ def verify(self, message, signature): bool: True if message was signed by the private key associated with the public key that this object was constructed with. """ - message = _helpers.to_bytes(message, encoding='utf-8') + message = _helpers.to_bytes(message) try: return rsa.pkcs1.verify(message, signature, self._pubkey) except (ValueError, rsa.pkcs1.VerificationError): @@ -188,7 +188,7 @@ def sign(self, message): Returns: bytes: The signature of the message for the given key. """ - message = _helpers.to_bytes(message, encoding='utf-8') + message = _helpers.to_bytes(message) return rsa.pkcs1.sign(message, self._key, 'SHA-256') @classmethod From dea37784ddd32c13280d4760b3d5228aafa1d699 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 13:56:15 -0700 Subject: [PATCH 7/8] Add datafile generation commands --- tests/test_crypt.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_crypt.py b/tests/test_crypt.py index 29e01d173..f15129dc6 100644 --- a/tests/test_crypt.py +++ b/tests/test_crypt.py @@ -26,18 +26,32 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +# To generate privatekey.pem, privatekey.pub, and public_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ +# -keyout privatekey.pem +# $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES + with open(os.path.join(DATA_DIR, 'privatekey.pub'), 'rb') as fh: PUBLIC_KEY_BYTES = fh.read() with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: PUBLIC_CERT_BYTES = fh.read() +# To generate other_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out other_cert.pem + with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: OTHER_CERT_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: - PRIVATE_KEY_BYTES = fh.read() - PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES +# To generate pem_from_pkcs12.pem and privatekey.p12: +# $ openssl pkcs12 -export -out privatekey.p12 -inkey privatekey.pem \ +# -in public_cert.pem +# $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ +# -out pem_from_pkcs12.pem with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: PKCS8_KEY_BYTES = fh.read() From 796f18c3af761cf24b87abc91119e9e876af109c Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 13:58:51 -0700 Subject: [PATCH 8/8] Add > in command samples. --- tests/test_crypt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_crypt.py b/tests/test_crypt.py index f15129dc6..90692166d 100644 --- a/tests/test_crypt.py +++ b/tests/test_crypt.py @@ -28,7 +28,7 @@ # To generate privatekey.pem, privatekey.pub, and public_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ -# -keyout privatekey.pem +# > -keyout privatekey.pem # $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: @@ -49,9 +49,9 @@ # To generate pem_from_pkcs12.pem and privatekey.p12: # $ openssl pkcs12 -export -out privatekey.p12 -inkey privatekey.pem \ -# -in public_cert.pem +# > -in public_cert.pem # $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ -# -out pem_from_pkcs12.pem +# > -out pem_from_pkcs12.pem with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: PKCS8_KEY_BYTES = fh.read()