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

Split crypt into a package to allow alternative implementations #189

Merged
merged 2 commits into from
Aug 11, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
79 changes: 79 additions & 0 deletions google/auth/crypt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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.

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:`RSAVerifier`::

cert = open('certs.pem').read()
verifier = crypt.RSAVerifier.from_string(cert)
valid = verifier.verify(message, signature)

To sign messages use :class:`RSASigner` with a private key::

private_key = open('private_key.pem').read()
signer = crypt.RSASigner(private_key)
signature = signer.sign(message)
"""

import six

from google.auth.crypt import base
from google.auth.crypt import rsa


__all__ = [
'RSASigner',
'RSAVerifier',
'Signer',
'Verifier',
]

# Aliases to maintain the v1.0.0 interface, as the crypt module was split
# into submodules.
Signer = base.Signer
Verifier = base.Verifier
RSASigner = rsa.RSASigner
RSAVerifier = rsa.RSAVerifier


def verify_signature(message, signature, certs):
"""Verify an RSA 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.text_type, six.binary_type)):
certs = [certs]

for cert in certs:
verifier = rsa.RSAVerifier.from_string(cert)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

if verifier.verify(message, signature):
return True
return False
105 changes: 8 additions & 97 deletions google/auth/crypt.py → google/auth/crypt/_python_rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Cryptography helpers for verifying and signing messages.
"""Pure-Python RSA cryptography implementation.

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:`RSAVerifier`::

cert = open('certs.pem').read()
verifier = crypt.RSAVerifier.from_string(cert)
valid = verifier.verify(message, signature)


To sign messages use :class:`RSASigner` with a private key::

private_key = open('private_key.pem').read()
signer = crypt.RSASigner(private_key)
signature = signer.sign(message)

"""
import abc

import io
import json

Expand All @@ -50,6 +30,7 @@
import six

from google.auth import _helpers
from google.auth.crypt import base

_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----'
Expand Down Expand Up @@ -84,28 +65,7 @@ def _bit_list_to_bytes(bit_list):
return bytes(byte_vals)


@six.add_metaclass(abc.ABCMeta)
class Verifier(object):
"""Abstract base class for crytographic signature verifiers."""

@abc.abstractmethod
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.
"""
# pylint: disable=missing-raises-doc,redundant-returns-doc
# (pylint doesn't recognize that this is abstract)
raise NotImplementedError('Verify must be implemented')


class RSAVerifier(Verifier):
class RSAVerifier(base.Verifier):
"""Verifies RSA cryptographic signatures using public keys.

Args:
Expand All @@ -116,7 +76,7 @@ class RSAVerifier(Verifier):
def __init__(self, public_key):
self._pubkey = public_key

@_helpers.copy_docstring(Verifier)
@_helpers.copy_docstring(base.Verifier)
def verify(self, message, signature):
message = _helpers.to_bytes(message)
try:
Expand Down Expand Up @@ -157,56 +117,7 @@ def from_string(cls, public_key):
return cls(pubkey)


def verify_signature(message, signature, certs):
"""Verify an RSA 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.text_type, six.binary_type)):
certs = [certs]

for cert in certs:
verifier = RSAVerifier.from_string(cert)
if verifier.verify(message, signature):
return True
return False


@six.add_metaclass(abc.ABCMeta)
class Signer(object):
"""Abstract base class for cryptographic signers."""

@abc.abstractproperty
def key_id(self):
"""Optional[str]: The key ID used to identify this private key."""
raise NotImplementedError('Key id must be implemented')

@abc.abstractmethod
def sign(self, message):
"""Signs a message.

Args:
message (Union[str, bytes]): The message to be signed.

Returns:
bytes: The signature of the message.
"""
# pylint: disable=missing-raises-doc,redundant-returns-doc
# (pylint doesn't recognize that this is abstract)
raise NotImplementedError('Sign must be implemented')


class RSASigner(Signer):
class RSASigner(base.Signer):
"""Signs messages with an RSA private key.

Args:
Expand All @@ -221,11 +132,11 @@ def __init__(self, private_key, key_id=None):
self._key_id = key_id

@property
@_helpers.copy_docstring(Signer)
@_helpers.copy_docstring(base.Signer)
def key_id(self):
return self._key_id

@_helpers.copy_docstring(Signer)
@_helpers.copy_docstring(base.Signer)
def sign(self, message):
message = _helpers.to_bytes(message)
return rsa.pkcs1.sign(message, self._key, 'SHA-256')
Expand Down
64 changes: 64 additions & 0 deletions google/auth/crypt/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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.

"""Base classes for cryptographic signers and verifiers."""

import abc

import six


@six.add_metaclass(abc.ABCMeta)
class Verifier(object):
"""Abstract base class for crytographic signature verifiers."""

@abc.abstractmethod
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.
"""
# pylint: disable=missing-raises-doc,redundant-returns-doc
# (pylint doesn't recognize that this is abstract)
raise NotImplementedError('Verify must be implemented')


@six.add_metaclass(abc.ABCMeta)
class Signer(object):
"""Abstract base class for cryptographic signers."""

@abc.abstractproperty
def key_id(self):
"""Optional[str]: The key ID used to identify this private key."""
raise NotImplementedError('Key id must be implemented')

@abc.abstractmethod
def sign(self, message):
"""Signs a message.

Args:
message (Union[str, bytes]): The message to be signed.

Returns:
bytes: The signature of the message.
"""
# pylint: disable=missing-raises-doc,redundant-returns-doc
# (pylint doesn't recognize that this is abstract)
raise NotImplementedError('Sign must be implemented')
20 changes: 20 additions & 0 deletions google/auth/crypt/rsa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 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.

"""RSA cryptography signer and verifier."""

from google.auth.crypt import _python_rsa

RSASigner = _python_rsa.RSASigner
RSAVerifier = _python_rsa.RSAVerifier
Empty file added tests/crypt/__init__.py
Empty file.
Loading