diff --git a/python/signature/__init__.py b/python/signature/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..75424f704d4cf473c0a68d53ce90602c43321d9d --- /dev/null +++ b/python/signature/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Signature package.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +from tink.python.signature import public_key_sign +from tink.python.signature import public_key_sign_key_manager +from tink.python.signature import public_key_sign_wrapper +from tink.python.signature import public_key_verify +from tink.python.signature import public_key_verify_key_manager +from tink.python.signature import public_key_verify_wrapper +from tink.python.signature import signature_key_templates + + +PublicKeySign = public_key_sign.PublicKeySign +PublicKeyVerify = public_key_verify.PublicKeyVerify +PublicKeySignWrapper = public_key_sign_wrapper.PublicKeySignWrapper +PublicKeyVerifyWrapper = public_key_verify_wrapper.PublicKeyVerifyWrapper diff --git a/python/signature/public_key_sign.py b/python/signature/public_key_sign.py new file mode 100644 index 0000000000000000000000000000000000000000..b7ce1126245f191f9797a1298686b5ce205d25f4 --- /dev/null +++ b/python/signature/public_key_sign.py @@ -0,0 +1,47 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Interface for PublicKeySign.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +import abc + + +class PublicKeySign(object): + """Interface for public key signing. + + Digital Signatures provide functionality of signing data and verification of + the signatures. They are represented by a pair of primitives (interfaces) + 'PublicKeySign' for signing of data, and 'PublicKeyVerify' for verification + of signatures. Implementations of these interfaces are secure against + adaptive chosen-message attacks. Signing data ensures the authenticity and + the integrity of that data, but not its secrecy. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def sign(self, data: bytes) -> bytes: + """Computes the signature for data. + + Args: + data: bytes, the input data. + Returns: + The signature as bytes. + """ + raise NotImplementedError() diff --git a/python/signature/public_key_sign_key_manager.py b/python/signature/public_key_sign_key_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..befd6216442a457556577567710f44d3100f234d --- /dev/null +++ b/python/signature/public_key_sign_key_manager.py @@ -0,0 +1,47 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Python wrapper of the CLIF-wrapped C++ Public Key Signature key manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +from typing import Text + +from tink.cc.python import public_key_sign as cc_public_key_sign +from tink.python.cc.clif import cc_key_manager +from tink.python.core import key_manager +from tink.python.core import tink_error +from tink.python.signature import public_key_sign + + +class _PublicKeySignCcToPyWrapper(public_key_sign.PublicKeySign): + """Transforms cliffed C++ PublicKeySign into a Python primitive.""" + + def __init__(self, cc_primitive: cc_public_key_sign.PublicKeySign): + self._public_key_sign = cc_primitive + + @tink_error.use_tink_errors + def sign(self, data: bytes) -> bytes: + return self._public_key_sign.sign(data) + + +def from_cc_registry( + type_url: Text +) -> key_manager.PrivateKeyManager[public_key_sign.PublicKeySign]: + return key_manager.PrivateKeyManagerCcToPyWrapper( + cc_key_manager.PublicKeySignKeyManager.from_cc_registry(type_url), + public_key_sign.PublicKeySign, _PublicKeySignCcToPyWrapper) diff --git a/python/signature/public_key_sign_key_manager_test.py b/python/signature/public_key_sign_key_manager_test.py new file mode 100644 index 0000000000000000000000000000000000000000..80ba54f87fa20643a48d614b8b278516ecd9ffe1 --- /dev/null +++ b/python/signature/public_key_sign_key_manager_test.py @@ -0,0 +1,100 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Tests for tink.python.public_key_sign_key_manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +from tink.proto import common_pb2 +from tink.proto import ecdsa_pb2 +from tink.proto import tink_pb2 +from tink.python.core import tink_config +from tink.python.signature import public_key_sign +from tink.python.signature import public_key_sign_key_manager +from tink.python.signature import public_key_verify_key_manager + + +def setUpModule(): + tink_config.register() + + +def new_ecdsa_key_template(hash_type, curve_type, encoding): + key_format = ecdsa_pb2.EcdsaKeyFormat() + key_format.params.hash_type = hash_type + key_format.params.curve = curve_type + key_format.params.encoding = encoding + key_template = tink_pb2.KeyTemplate() + key_template.type_url = ( + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + key_template.value = key_format.SerializeToString() + return key_template + + +class PublicKeySignKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(PublicKeySignKeyManagerTest, self).setUp() + self.key_manager = public_key_sign_key_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + self.key_manager_verify = public_key_verify_key_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey') + + def test_primitive_class(self): + self.assertEqual(self.key_manager.primitive_class(), + public_key_sign.PublicKeySign) + + def test_key_type(self): + self.assertEqual(self.key_manager.key_type(), + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + + def test_new_key_data(self): + key_template = new_ecdsa_key_template(common_pb2.SHA256, + common_pb2.NIST_P256, ecdsa_pb2.DER) + key_data = self.key_manager.new_key_data(key_template) + self.assertEqual(key_data.type_url, self.key_manager.key_type()) + key = ecdsa_pb2.EcdsaPrivateKey() + key.ParseFromString(key_data.value) + public_key = key.public_key + self.assertEqual(key.version, 0) + self.assertEqual(public_key.version, 0) + self.assertEqual(public_key.params.hash_type, common_pb2.SHA256) + self.assertEqual(public_key.params.curve, common_pb2.NIST_P256) + self.assertEqual(public_key.params.encoding, ecdsa_pb2.DER) + self.assertLen(key.key_value, 32) + + def test_signature_success(self): + + priv_key = self.key_manager.new_key_data( + new_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256, + ecdsa_pb2.DER)) + pub_key = self.key_manager.public_key_data(priv_key) + + verifier = self.key_manager_verify.primitive(pub_key) + signer = self.key_manager.primitive(priv_key) + + data = b'data' + signature = signer.sign(data) + + # Starts with a DER sequence + self.assertEqual(bytearray(signature)[0], 0x30) + + verifier.verify(signature, data) + + +if __name__ == '__main__': + googletest.main() diff --git a/python/signature/public_key_sign_wrapper.py b/python/signature/public_key_sign_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..b90d802c3c33639cc5a4a447115e23f3428a8933 --- /dev/null +++ b/python/signature/public_key_sign_wrapper.py @@ -0,0 +1,73 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Public Key Sign wrapper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +from typing import Type + +from tink.proto import tink_pb2 +from tink.python.core import crypto_format +from tink.python.core import primitive_set +from tink.python.core import primitive_wrapper +from tink.python.core import tink_error +from tink.python.signature import public_key_sign + + +class _WrappedPublicKeySign(public_key_sign.PublicKeySign): + """Implements PublicKeySign for a set of PublicKeySign primitives.""" + + def __init__(self, primitives_set: primitive_set.PrimitiveSet): + self._primitive_set = primitives_set + + def sign(self, data: bytes) -> bytes: + """Computes the signature for data using the primary primitive. + + Args: + data: The input data. + + Returns: + The signature. + """ + primary = self._primitive_set.primary() + + if not primary: + raise tink_error.TinkError('primary primitive not set') + + sign_data = data + if primary.output_prefix_type == tink_pb2.LEGACY: + sign_data = sign_data + crypto_format.LEGACY_START_BYTE + + return primary.identifier + primary.primitive.sign(sign_data) + + +class PublicKeySignWrapper( + primitive_wrapper.PrimitiveWrapper[public_key_sign.PublicKeySign]): + """A PrimitiveWrapper for the PublicKeySign primitive. + + The returned primitive works with a keyset (rather than a single key). To sign + a message, it uses the primary key in the keyset, and prepends to the + signature a certain prefix associated with the primary key. + """ + + def wrap(self, primitives_set: primitive_set.PrimitiveSet + ) -> _WrappedPublicKeySign: + return _WrappedPublicKeySign(primitives_set) + + def primitive_class(self) -> Type[public_key_sign.PublicKeySign]: + return public_key_sign.PublicKeySign diff --git a/python/signature/public_key_sign_wrapper_test.py b/python/signature/public_key_sign_wrapper_test.py new file mode 100644 index 0000000000000000000000000000000000000000..a0a0d77516d65d29fffca99f7e7c2eedf4902d1e --- /dev/null +++ b/python/signature/public_key_sign_wrapper_test.py @@ -0,0 +1,79 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Tests for tink.python.public_key_sign_wrapper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +from google3.testing.pybase import parameterized + +from tink.proto import tink_pb2 +from tink.python.core import primitive_set +from tink.python.core import tink_error +from tink.python.signature import public_key_sign +from tink.python.signature import public_key_sign_wrapper +from tink.python.signature import public_key_verify +from tink.python.signature import public_key_verify_wrapper +from tink.python.testing import helper + + +def new_primitive_key_pair(key_id, output_prefix_type): + fake_key = helper.fake_key( + key_id=key_id, + key_material_type=tink_pb2.KeyData.ASYMMETRIC_PRIVATE, + output_prefix_type=output_prefix_type) + fake_sign = helper.FakePublicKeySign('fakePublicKeySign {}'.format(key_id)) + return fake_sign, fake_key, + + +def to_verify_key_pair(key): + fake_verify = helper.FakePublicKeyVerify('fakePublicKeySign {}'.format( + key.key_id)) + return fake_verify, key, + + +class PublicKeySignWrapperTest(parameterized.TestCase): + + @parameterized.named_parameters(('tink', tink_pb2.TINK), + ('legacy', tink_pb2.LEGACY)) + def test_signature(self, output_prefix_type): + pair0 = new_primitive_key_pair(1234, output_prefix_type) + pair1 = new_primitive_key_pair(5678, output_prefix_type) + pset = primitive_set.new_primitive_set(public_key_sign.PublicKeySign) + pset_verify = primitive_set.new_primitive_set( + public_key_verify.PublicKeyVerify) + + pset.add_primitive(*pair0) + pset.set_primary(pset.add_primitive(*pair1)) + + pset_verify.add_primitive(*to_verify_key_pair(pair0[1])) + entry = pset_verify.add_primitive(*to_verify_key_pair(pair1[1])) + pset_verify.set_primary(entry) + + wrapped_pk_sign = public_key_sign_wrapper.PublicKeySignWrapper().wrap(pset) + wrapped_pk_verify = public_key_verify_wrapper.PublicKeyVerifyWrapper().wrap( + pset_verify) + signature = wrapped_pk_sign.sign(b'data') + + wrapped_pk_verify.verify(signature, b'data') + + with self.assertRaisesRegex(tink_error.TinkError, 'invalid signature'): + wrapped_pk_verify.verify(signature, b'invalid') + + +if __name__ == '__main__': + googletest.main() diff --git a/python/signature/public_key_verify.py b/python/signature/public_key_verify.py new file mode 100644 index 0000000000000000000000000000000000000000..5de96996ea13629ffc2ad26e01faa3780beec7f7 --- /dev/null +++ b/python/signature/public_key_verify.py @@ -0,0 +1,50 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Interface for PublicKeyVerify.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +import abc + + +class PublicKeyVerify(object): + """Interface for public key verifying. + + Digital Signatures provide functionality of signing data and verification of + the signatures. They are represented by a pair of primitives (interfaces) + 'PublicKeySign' for signing of data, and 'PublicKeyVerify' for verification + of signatures. Implementations of these interfaces are secure against + adaptive chosen-message attacks. Signing data ensures the authenticity and + the integrity of that data, but not its secrecy. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def verify(self, signature: bytes, data: bytes) -> bytes: + """Verifies that signature is a digital signature for data. + + Args: + signature: The signature bytes to be checked. + data: The data bytes to be checked. + + Raises: + google3.third_party.tink.python.tink_error.TinkError if the verification + fails. + """ + raise NotImplementedError() diff --git a/python/signature/public_key_verify_key_manager.py b/python/signature/public_key_verify_key_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..39e100ea723d98600a200ddd126c0dd0db5f5e69 --- /dev/null +++ b/python/signature/public_key_verify_key_manager.py @@ -0,0 +1,47 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Python wrapper of the CLIF-wrapped C++ Public Key Verify key manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +from typing import Text + +from tink.cc.python import public_key_verify as cc_public_key_verify +from tink.python.cc.clif import cc_key_manager +from tink.python.core import key_manager +from tink.python.core import tink_error +from tink.python.signature import public_key_verify + + +class _PublicKeyVerifyCcToPyWrapper(public_key_verify.PublicKeyVerify): + """Transforms cliffed C++ PublicKeyVerify into a Python primitive.""" + + def __init__(self, cc_primitive: cc_public_key_verify.PublicKeyVerify): + self._public_key_verify = cc_primitive + + @tink_error.use_tink_errors + def verify(self, signature: bytes, data: bytes) -> None: + self._public_key_verify.verify(signature, data) + + +def from_cc_registry( + type_url: Text +) -> key_manager.KeyManager[public_key_verify.PublicKeyVerify]: + return key_manager.KeyManagerCcToPyWrapper( + cc_key_manager.PublicKeyVerifyKeyManager.from_cc_registry(type_url), + public_key_verify.PublicKeyVerify, _PublicKeyVerifyCcToPyWrapper) diff --git a/python/signature/public_key_verify_key_manager_test.py b/python/signature/public_key_verify_key_manager_test.py new file mode 100644 index 0000000000000000000000000000000000000000..f808fcd6bdb200b4d419dfb9900346c389b20ced --- /dev/null +++ b/python/signature/public_key_verify_key_manager_test.py @@ -0,0 +1,105 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Tests for tink.python.public_key_verify_key_manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +from tink.proto import common_pb2 +from tink.proto import ecdsa_pb2 +from tink.proto import tink_pb2 +from tink.python.core import tink_config +from tink.python.core import tink_error +from tink.python.signature import public_key_sign_key_manager +from tink.python.signature import public_key_verify_key_manager + + +def setUpModule(): + tink_config.register() + + +def new_ecdsa_key_template(hash_type, curve_type, encoding, public=True): + params = ecdsa_pb2.EcdsaParams( + hash_type=hash_type, curve=curve_type, encoding=encoding) + key_format = ecdsa_pb2.EcdsaKeyFormat(params=params) + key_template = tink_pb2.KeyTemplate() + if public: + append = 'EcdsaPublicKey' + else: + append = 'EcdsaPrivateKey' + key_template.type_url = 'type.googleapis.com/google.crypto.tink.' + append + key_template.value = key_format.SerializeToString() + + return key_template + + +class PublicKeyVerifyKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(PublicKeyVerifyKeyManagerTest, self).setUp() + self.key_manager = public_key_verify_key_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey') + self.key_manager_sign = public_key_sign_key_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + + def test_key_type(self): + self.assertEqual(self.key_manager.key_type(), + 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey') + + def test_new_key_data(self): + key_template = new_ecdsa_key_template( + common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, True) + with self.assertRaisesRegex(tink_error.TinkError, + 'Operation not supported'): + self.key_manager.new_key_data(key_template) + + def test_verify_success(self): + key_template = new_ecdsa_key_template( + common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, False) + priv_key = self.key_manager_sign.new_key_data(key_template) + pub_key = self.key_manager_sign.public_key_data(priv_key) + + signer = self.key_manager_sign.primitive(priv_key) + verifier = self.key_manager.primitive(pub_key) + + data = b'data' + signature = signer.sign(data) + + verifier.verify(signature, data) + + def test_verify_wrong(self): + key_template = new_ecdsa_key_template( + common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, False) + priv_key = self.key_manager_sign.new_key_data(key_template) + pub_key = self.key_manager_sign.public_key_data(priv_key) + + signer = self.key_manager_sign.primitive(priv_key) + verifier = self.key_manager.primitive(pub_key) + + data = b'data' + signature = signer.sign(data) + + with self.assertRaisesRegex(tink_error.TinkError, 'Signature is not valid'): + verifier.verify(signature, 'wrongdata') + + with self.assertRaisesRegex(tink_error.TinkError, 'Signature is not valid'): + verifier.verify('wrongsignature', data) + + +if __name__ == '__main__': + googletest.main() diff --git a/python/signature/public_key_verify_wrapper.py b/python/signature/public_key_verify_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..22b5e13d7455e22228a1cdac90c8bcc522f5a177 --- /dev/null +++ b/python/signature/public_key_verify_wrapper.py @@ -0,0 +1,102 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Public Key Verify wrapper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +import logging +from typing import Type + +from tink.proto import tink_pb2 +from tink.python.core import crypto_format +from tink.python.core import primitive_set +from tink.python.core import primitive_wrapper +from tink.python.core import tink_error +from tink.python.signature import public_key_verify + + +class _WrappedPublicKeyVerify(public_key_verify.PublicKeyVerify): + """Implements PublicKeyVerify for a set of PublicKeyVerify primitives.""" + + def __init__(self, primitives_set: primitive_set.PrimitiveSet): + self._primitive_set = primitives_set + + def verify(self, signature: bytes, data: bytes): + """Verifies that signature is a digital signature for data. + + Args: + signature: The signature bytes to be checked. + data: The data bytes to be checked. + + Raises: + tink_error.TinkError if the verification fails. + """ + if len(signature) <= crypto_format.NON_RAW_PREFIX_SIZE: + # This also rejects raw signatures with size of 4 bytes or fewer. + # We're not aware of any schemes that output signatures that small. + raise tink_error.TinkError('signature too short') + + key_id = signature[:crypto_format.NON_RAW_PREFIX_SIZE] + raw_sig = signature[crypto_format.NON_RAW_PREFIX_SIZE:] + + for entry in self._primitive_set.primitive_from_identifier(key_id): + try: + if entry.output_prefix_type == tink_pb2.LEGACY: + entry.primitive.verify(raw_sig, + data + crypto_format.LEGACY_START_BYTE) + else: + entry.primitive.verify(raw_sig, data) + # Signature is valid, we can return + return + except tink_error.TinkError as err: + logging.info('signature prefix matches a key, but cannot verify: %s', + err) + + # No matching key succeeded with verification, try all RAW keys + for entry in self._primitive_set.raw_primitives(): + try: + entry.primitive.verify(signature, data) + # Signature is valid, we can return + return + except tink_error.TinkError: + pass + + raise tink_error.TinkError('invalid signature') + + +class PublicKeyVerifyWrapper( + primitive_wrapper.PrimitiveWrapper[public_key_verify.PublicKeyVerify]): + """WrappedPublicKeyVerify is the PrimitiveWrapper for PublicKeyVerify. + + The returned primitive works with a keyset (rather than a single key). To sign + a message, it uses the primary key in the keyset, and prepends to the + signature a certain prefix associated with the primary key. + + The returned primitive works with a keyset (rather than a single key). To + verify a signature, the primitive uses the prefix of the signature to + efficiently select the right key in the set. If there is no key associated + with the prefix or if the keys associated with the prefix do not work, the + primitive tries all keys with tink_pb2.OutputPrefixType = tink_pb2.RAW. + """ + + def wrap(self, primitives_set: primitive_set.PrimitiveSet + ) -> _WrappedPublicKeyVerify: + return _WrappedPublicKeyVerify(primitives_set) + + def primitive_class(self) -> Type[public_key_verify.PublicKeyVerify]: + return public_key_verify.PublicKeyVerify diff --git a/python/signature/public_key_verify_wrapper_test.py b/python/signature/public_key_verify_wrapper_test.py new file mode 100644 index 0000000000000000000000000000000000000000..a744a7f33851ff4eae5dcbdecaf66e71e84d1893 --- /dev/null +++ b/python/signature/public_key_verify_wrapper_test.py @@ -0,0 +1,70 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Tests for tink.python.public_key_verify_wrapper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +from tink.proto import tink_pb2 +from tink.python.core import primitive_set +from tink.python.signature import public_key_sign +from tink.python.signature import public_key_sign_wrapper +from tink.python.signature import public_key_verify +from tink.python.signature import public_key_verify_wrapper +from tink.python.testing import helper + + +def new_primitive_key_pair(key_id, output_prefix_type): + fake_key = helper.fake_key( + key_id=key_id, + key_material_type=tink_pb2.KeyData.ASYMMETRIC_PRIVATE, + output_prefix_type=output_prefix_type) + fake_sign = helper.FakePublicKeyVerify('fakePublicKeySign {}'.format(key_id)) + return fake_sign, fake_key, + + +class PublicKeyVerifyWrapperTest(googletest.TestCase): + + def test_verify_signature(self): + pair0 = new_primitive_key_pair(1234, tink_pb2.RAW) + pair1 = new_primitive_key_pair(5678, tink_pb2.TINK) + pair2 = new_primitive_key_pair(9012, tink_pb2.LEGACY) + pset = primitive_set.new_primitive_set(public_key_verify.PublicKeyVerify) + + pset.add_primitive(*pair0) + pset.add_primitive(*pair1) + pset.set_primary(pset.add_primitive(*pair2)) + + # Check all keys work + for unused_primitive, key in (pair0, pair1, pair2): + pset_sign = primitive_set.new_primitive_set(public_key_sign.PublicKeySign) + pset_sign.set_primary( + pset_sign.add_primitive( + helper.FakePublicKeySign('fakePublicKeySign {}'.format( + key.key_id)), key)) + + wrapped_pk_verify = public_key_verify_wrapper.PublicKeyVerifyWrapper( + ).wrap(pset) + wrapped_pk_sign = public_key_sign_wrapper.PublicKeySignWrapper().wrap( + pset_sign) + + wrapped_pk_verify.verify(wrapped_pk_sign.sign(b'data'), b'data') + + +if __name__ == '__main__': + googletest.main() diff --git a/python/signature/signature_key_templates.py b/python/signature/signature_key_templates.py new file mode 100644 index 0000000000000000000000000000000000000000..10915025ed1b2ff9336113f2ce3630eb2b5e44da --- /dev/null +++ b/python/signature/signature_key_templates.py @@ -0,0 +1,141 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Pre-generated KeyTemplate for PublicKeySign and PublicKeyVerify. + +One can use these templates to generate new tink_pb2.Keyset with +tink_pb2.KeysetHandle. To generate a new keyset that contains a single +EcdsaPrivateKey, one can do: + +handle = keyset_handle.KeysetHandle(signature_key_templates.ECDSA_P256); +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import google_type_annotations +from __future__ import print_function + +from tink.proto import common_pb2 +from tink.proto import ecdsa_pb2 +from tink.proto import rsa_ssa_pkcs1_pb2 +from tink.proto import rsa_ssa_pss_pb2 +from tink.proto import tink_pb2 + +_prefix = 'type.googleapis.com/google.crypto.tink.' +_ECDSA_KEY_TYPE_URL = _prefix + 'EcdsaPrivateKey' +_ED25519_KEY_TYPE_URL = _prefix + 'Ed25519PrivateKey' +_RSA_PKCS1_KEY_TYPE_URL = _prefix + 'RsaSsaPkcs1PrivateKey' +_RSA_PSS_KEY_TYPE_URL = _prefix + 'RsaSsaPssPrivateKey' +_RSA_F4 = 65537 + + +def _num_to_bytes(n: int) -> bytes: + """Converts a number to bytes.""" + if n < 0: + raise OverflowError("number can't be negative") + + if n == 0: + return b'\x00' + + octets = bytearray() + while n: + octets.append(n % 256) + n //= 256 + + return bytes(octets[::-1]) + + +def create_ecdsa_key_template(hash_type: common_pb2.HashType, + curve: common_pb2.EllipticCurveType, + encoding: ecdsa_pb2.EcdsaSignatureEncoding + ) -> tink_pb2.KeyTemplate: + """Creates a KeyTemplate containing an EcdsaKeyFormat.""" + params = ecdsa_pb2.EcdsaParams( + hash_type=hash_type, curve=curve, encoding=encoding) + key_format = ecdsa_pb2.EcdsaKeyFormat(params=params) + key_template = tink_pb2.KeyTemplate( + value=key_format.SerializeToString(), + type_url=_ECDSA_KEY_TYPE_URL, + output_prefix_type=tink_pb2.TINK) + + return key_template + + +def create_rsa_ssa_pkcs1_key_template(hash_type: common_pb2.HashType, + modulus_size: int, public_exponent: int + ) -> tink_pb2.KeyTemplate: + """Creates a KeyTemplate containing an RsaSsaPkcs1KeyFormat.""" + + params = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1Params(hash_type=hash_type) + key_format = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1KeyFormat( + params=params, + modulus_size_in_bits=modulus_size, + public_exponent=_num_to_bytes(public_exponent)) + key_template = tink_pb2.KeyTemplate( + value=key_format.SerializeToString(), + type_url=_RSA_PKCS1_KEY_TYPE_URL, + output_prefix_type=tink_pb2.TINK) + + return key_template + + +def create_rsa_ssa_pss_key_template(sig_hash: common_pb2.HashType, + mgf1_hash: common_pb2.HashType, + salt_length: int, modulus_size: int, + public_exponent: int + ) -> tink_pb2.KeyTemplate: + """Creates a KeyTemplate containing an RsaSsaPssKeyFormat.""" + params = rsa_ssa_pss_pb2.RsaSsaPssParams( + sig_hash=sig_hash, mgf1_hash=mgf1_hash, salt_length=salt_length) + key_format = rsa_ssa_pss_pb2.RsaSsaPssKeyFormat( + params=params, + modulus_size_in_bits=modulus_size, + public_exponent=_num_to_bytes(public_exponent)) + key_template = tink_pb2.KeyTemplate( + value=key_format.SerializeToString(), + type_url=_RSA_PSS_KEY_TYPE_URL, + output_prefix_type=tink_pb2.TINK) + + return key_template + + +ECDSA_P256 = create_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256, + ecdsa_pb2.DER) +ECDSA_P384 = create_ecdsa_key_template(common_pb2.SHA512, common_pb2.NIST_P384, + ecdsa_pb2.DER) +ECDSA_P521 = create_ecdsa_key_template(common_pb2.SHA512, common_pb2.NIST_P521, + ecdsa_pb2.DER) + +ECDSA_P256_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA256, + common_pb2.NIST_P256, + ecdsa_pb2.IEEE_P1363) +ECDSA_P384_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA512, + common_pb2.NIST_P384, + ecdsa_pb2.IEEE_P1363) +ECDSA_P521_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA512, + common_pb2.NIST_P521, + ecdsa_pb2.IEEE_P1363) + +ED25519 = tink_pb2.KeyTemplate( + type_url=_ED25519_KEY_TYPE_URL, output_prefix_type=tink_pb2.TINK) + +RSA_SSA_PKCS1_3072_SHA256_F4 = create_rsa_ssa_pkcs1_key_template( + common_pb2.SHA256, 3072, _RSA_F4) +RSA_SSA_PKCS1_4096_SHA512_F4 = create_rsa_ssa_pkcs1_key_template( + common_pb2.SHA512, 4096, _RSA_F4) + +RSA_SSA_PSS_3072_SHA256_SHA256_32_F4 = create_rsa_ssa_pss_key_template( + common_pb2.SHA256, common_pb2.SHA256, 32, 3072, _RSA_F4) +RSA_SSA_PSS_4096_SHA512_SHA512_64_F4 = create_rsa_ssa_pss_key_template( + common_pb2.SHA512, common_pb2.SHA512, 64, 4096, _RSA_F4) diff --git a/python/signature/signature_key_templates_test.py b/python/signature/signature_key_templates_test.py new file mode 100644 index 0000000000000000000000000000000000000000..bac00150556cb7aba2816e16c6e59fd211e9ae6b --- /dev/null +++ b/python/signature/signature_key_templates_test.py @@ -0,0 +1,204 @@ +# Copyright 2019 Google LLC +# +# 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. + +"""Tests for tink.python.signature_key_templates.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +from google3.testing.pybase import parameterized + +from tink.proto import common_pb2 +from tink.proto import ecdsa_pb2 +from tink.proto import rsa_ssa_pkcs1_pb2 +from tink.proto import rsa_ssa_pss_pb2 +from tink.proto import tink_pb2 +from tink.python.core import tink_config +from tink.python.signature import public_key_sign_key_manager +from tink.python.signature import signature_key_templates + + +ECDSA_DER_PARAMS_P256 = [ + signature_key_templates.ECDSA_P256, common_pb2.SHA256, common_pb2.NIST_P256 +] +ECDSA_DER_PARAMS_P384 = [ + signature_key_templates.ECDSA_P384, common_pb2.SHA512, common_pb2.NIST_P384 +] +ECDSA_DER_PARAMS_P521 = [ + signature_key_templates.ECDSA_P521, common_pb2.SHA512, common_pb2.NIST_P521 +] + +ECDSA_IEEE_PARAMS_P256 = [ + signature_key_templates.ECDSA_P256_IEEE_P1363, common_pb2.SHA256, + common_pb2.NIST_P256 +] +ECDSA_IEEE_PARAMS_P384 = [ + signature_key_templates.ECDSA_P384_IEEE_P1363, common_pb2.SHA512, + common_pb2.NIST_P384 +] +ECDSA_IEEE_PARAMS_P521 = [ + signature_key_templates.ECDSA_P521_IEEE_P1363, common_pb2.SHA512, + common_pb2.NIST_P521 +] + +RSA_PKCS1_PARAMS_3072 = [ + signature_key_templates.RSA_SSA_PKCS1_3072_SHA256_F4, common_pb2.SHA256, + 3072, 65537 +] +RSA_PKCS1_PARAMS_4096 = [ + signature_key_templates.RSA_SSA_PKCS1_4096_SHA512_F4, common_pb2.SHA512, + 4096, 65537 +] + +RSA_PSS_PARAMS_3072 = [ + signature_key_templates.RSA_SSA_PSS_3072_SHA256_SHA256_32_F4, + common_pb2.SHA256, 3072, 65537 +] +RSA_PSS_PARAMS_4096 = [ + signature_key_templates.RSA_SSA_PSS_4096_SHA512_SHA512_64_F4, + common_pb2.SHA512, 4096, 65537 +] + + +def bytes_to_num(data): + res = 0 + + for b in bytearray(data): + res <<= 8 + res |= b + + return res + + +def setUpModule(): + tink_config.register() + + +class SignatureKeyTemplatesTest(parameterized.TestCase): + + def test_bytes_to_num(self): + for i in range(100000): + res = bytes_to_num(signature_key_templates._num_to_bytes(i)) + self.assertEqual(res, i) + + @parameterized.named_parameters(('0', 0, b'\x00'), ('256', 256, b'\x01\x00'), + ('65537', 65537, b'\x01\x00\x01')) + def test_num_to_bytes(self, number, expected): + self.assertEqual(signature_key_templates._num_to_bytes(number), expected) + + with self.assertRaises(OverflowError): + signature_key_templates._num_to_bytes(-1) + + @parameterized.named_parameters( + ['ecdsa_p256'] + ECDSA_DER_PARAMS_P256, + ['ecdsa_p384'] + ECDSA_DER_PARAMS_P384, + ['ecdsa_p521'] + ECDSA_DER_PARAMS_P521, + ) + def test_ecdsa_der(self, key_template, hash_type, curve): + self.assertEqual(key_template.type_url, + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK) + + key_format = ecdsa_pb2.EcdsaKeyFormat() + key_format.ParseFromString(key_template.value) + self.assertEqual(key_format.params.hash_type, hash_type) + self.assertEqual(key_format.params.curve, curve) + self.assertEqual(key_format.params.encoding, ecdsa_pb2.DER) + + # Check that the template works with the key manager + key_manager = public_key_sign_key_manager.from_cc_registry( + key_template.type_url) + key_manager.new_key_data(key_template) + + @parameterized.named_parameters( + ['ecdsa_p256'] + ECDSA_IEEE_PARAMS_P256, + ['ecdsa_p384'] + ECDSA_IEEE_PARAMS_P384, + ['ecdsa_p521'] + ECDSA_IEEE_PARAMS_P521, + ) + def test_ecdsa_ieee(self, key_template, hash_type, curve): + self.assertEqual(key_template.type_url, + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK) + + key_format = ecdsa_pb2.EcdsaKeyFormat() + key_format.ParseFromString(key_template.value) + self.assertEqual(key_format.params.hash_type, hash_type) + self.assertEqual(key_format.params.curve, curve) + self.assertEqual(key_format.params.encoding, ecdsa_pb2.IEEE_P1363) + + # Check that the template works with the key manager + key_manager = public_key_sign_key_manager.from_cc_registry( + key_template.type_url) + key_manager.new_key_data(key_template) + + def test_ed25519(self): + key_template = signature_key_templates.ED25519 + self.assertEqual( + key_template.type_url, + 'type.googleapis.com/google.crypto.tink.Ed25519PrivateKey') + self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK) + + # Check that the template works with the key manager + key_manager = public_key_sign_key_manager.from_cc_registry( + key_template.type_url) + key_manager.new_key_data(key_template) + + @parameterized.named_parameters( + ['rsa_pkcs1_3072'] + RSA_PKCS1_PARAMS_3072, + ['rsa_pkcs1_4096'] + RSA_PKCS1_PARAMS_4096, + ) + def test_rsa_pkcs1(self, key_template, hash_algo, modulus_size, exponent): + self.assertEqual( + key_template.type_url, + 'type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey') + self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK) + + key_format = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1KeyFormat() + key_format.ParseFromString(key_template.value) + self.assertEqual(key_format.modulus_size_in_bits, modulus_size) + self.assertEqual(key_format.params.hash_type, hash_algo) + self.assertEqual(bytes_to_num(key_format.public_exponent), exponent) + + # Check that the template works with the key manager + key_manager = public_key_sign_key_manager.from_cc_registry( + key_template.type_url) + key_manager.new_key_data(key_template) + + @parameterized.named_parameters( + ['rsa_pss_3072'] + RSA_PSS_PARAMS_3072, + ['rsa_pss_4096'] + RSA_PSS_PARAMS_4096, + ) + def test_rsa_pss(self, key_template, hash_algo, modulus_size, exponent): + self.assertEqual( + key_template.type_url, + 'type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey') + self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK) + + key_format = rsa_ssa_pss_pb2.RsaSsaPssKeyFormat() + key_format.ParseFromString(key_template.value) + self.assertEqual(key_format.modulus_size_in_bits, modulus_size) + self.assertEqual(key_format.params.sig_hash, hash_algo) + self.assertEqual(key_format.params.mgf1_hash, hash_algo) + self.assertEqual(bytes_to_num(key_format.public_exponent), exponent) + + # Check that the template works with the key manager + key_manager = public_key_sign_key_manager.from_cc_registry( + key_template.type_url) + key_manager.new_key_data(key_template) + + +if __name__ == '__main__': + googletest.main()