diff --git a/__init__.py b/__init__.py index 872613ccf717aed7cde5a5bca169733d18bc6900..5b8c5ab8aeb3c9a19ea95d27cd96f4c79a75e896 100644 --- a/__init__.py +++ b/__init__.py @@ -1,16 +1,30 @@ +# 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. + """Tink package.""" from __future__ import absolute_import from __future__ import division from __future__ import google_type_annotations from __future__ import print_function -from google3.third_party.tink.python import aead -from google3.third_party.tink.python import core -from google3.third_party.tink.python import daead -from google3.third_party.tink.python import hybrid -from google3.third_party.tink.python import mac -from google3.third_party.tink.python import signature -from google3.third_party.tink.python.core import tink_config +from tink.python import aead +from tink.python import core +from tink.python import daead +from tink.python import hybrid +from tink.python import mac +from tink.python import signature +from tink.python.core import tink_config Aead = aead.Aead diff --git a/cc/python/aead.clif b/cc/python/aead.clif new file mode 100644 index 0000000000000000000000000000000000000000..ca1e653f14c3527b10edcde12a72b46f17d721a8 --- /dev/null +++ b/cc/python/aead.clif @@ -0,0 +1,37 @@ +# 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. + +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/cc/aead.h": + namespace `crypto::tink`: + # The interface for authenticated encryption with associated data. + # Implementations of this interface are secure against adaptive + # chosen ciphertext attacks. Encryption with associated data ensures + # authenticity and integrity of that data, but not its secrecy. + # (see RFC 5116, https://tools.ietf.org/html/rfc5116) + class Aead: + # Encrypts 'plaintext' with 'associated_data' as associated data, + # and returns the resulting ciphertext. + # The ciphertext allows for checking authenticity and integrity + # of the associated data , but does not guarantee its secrecy. + def `Encrypt` as encrypt(self, plaintext: bytes, associated_data: bytes) + -> StatusOr<bytes> + # Decrypts 'ciphertext' with 'associated_data' as associated data, + # and returns the resulting plaintext. + # The decryption verifies the authenticity and integrity + # of the associated data, but there are no guarantees wrt. secrecy + # of that data. + def `Decrypt` as decrypt(self, ciphertext: bytes, associated_data: bytes) + -> StatusOr<bytes> diff --git a/cc/python/deterministic_aead.clif b/cc/python/deterministic_aead.clif new file mode 100644 index 0000000000000000000000000000000000000000..e66b4cf4b08a42a43efbcd3d37338dab2f9bad2b --- /dev/null +++ b/cc/python/deterministic_aead.clif @@ -0,0 +1,45 @@ +# 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. + +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/cc/deterministic_aead.h": + namespace `crypto::tink`: + # Interface for Deterministic Authenticated Encryption with Associated Data + # (Deterministic AEAD) + # + # For why this interface is desirable and some of its use cases, see for + # example https://tools.ietf.org/html/rfc5297#section-1.3. + # + # Warning! + # Unlike Aead, implementations of this interface are not semantically + # secure, because encrypting the same plaintex always yields the same + # ciphertext. + # + # Security guarantees + # + # Implementations of this interface provide 128-bit security level against + # multi-user attacks with up to 2^32 keys. That means if an adversary + # obtains 2^32 ciphertexts of the same message encrypted under 2^32 keys, + # they need to do 2^128 computations to obtain a single key. + # + # Encryption with associated data ensures authenticity (who the sender is) + # and integrity (the data has not been tampered with) of that data, but not + # its secrecy. (see https://tools.ietf.org/html/rfc5116) + class DeterministicAead: + + def `EncryptDeterministically` as encrypt_deterministically( + self, plaintext: bytes, associated_data: bytes) -> StatusOr<bytes> + def `DecryptDeterministically` as decrypt_deterministically( + self, ciphertext: bytes, associated_data: bytes) -> StatusOr<bytes> diff --git a/cc/python/hybrid_decrypt.clif b/cc/python/hybrid_decrypt.clif new file mode 100644 index 0000000000000000000000000000000000000000..a69f8fa4f1892d83f21678f04a368c70d6c5acd7 --- /dev/null +++ b/cc/python/hybrid_decrypt.clif @@ -0,0 +1,21 @@ +# 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. + +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/cc/hybrid_decrypt.h": + namespace `crypto::tink`: + class HybridDecrypt: + def `Decrypt` as decrypt(self, ciphertext: bytes, context_info: bytes) + -> StatusOr<bytes> diff --git a/cc/python/hybrid_encrypt.clif b/cc/python/hybrid_encrypt.clif new file mode 100644 index 0000000000000000000000000000000000000000..afad001b0abc9a6bf8708f353a7de9f796371d48 --- /dev/null +++ b/cc/python/hybrid_encrypt.clif @@ -0,0 +1,21 @@ +# 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. + +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/cc/hybrid_encrypt.h": + namespace `crypto::tink`: + class HybridEncrypt: + def `Encrypt` as encrypt(self, plaintext: bytes, context_info: bytes) + -> StatusOr<bytes> diff --git a/cc/python/mac.clif b/cc/python/mac.clif new file mode 100644 index 0000000000000000000000000000000000000000..3105b3e63eddad997af4f15ccba431e56e3fec6c --- /dev/null +++ b/cc/python/mac.clif @@ -0,0 +1,30 @@ +# 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. + +from "tink/python/util/clif.h" import * # Status, StatusOr +from clif.python.postproc import DropOkStatus + +from "tink/cc/mac.h": + namespace `crypto::tink`: + # Interface for MACs (Message Authentication Codes). + # This interface should be used for authentication only, and not for other + # purposes (e.g., it should not be used to generate pseudorandom bytes). + class Mac: + # Computes and returns the message authentication code (MAC) for 'data'. + def `ComputeMac` as compute_mac(self, data: bytes) -> StatusOr<bytes> + # Verifies if 'mac' is a correct authentication code (MAC) for 'data'. + # Raises a StatusNotOk exception if the verification fails. + def `VerifyMac` as verify_mac(self, mac: bytes, data: bytes) + -> (ok: Status): + return DropOkStatus(...) diff --git a/cc/python/public_key_sign.clif b/cc/python/public_key_sign.clif new file mode 100644 index 0000000000000000000000000000000000000000..d599bd4abf3888c35fa6de2207fccdac635b03a8 --- /dev/null +++ b/cc/python/public_key_sign.clif @@ -0,0 +1,28 @@ +# 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. + +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/cc/public_key_sign.h": + namespace `crypto::tink`: + # 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. + class PublicKeySign: + # Computes the signature for 'data'. + def `Sign` as sign(self, data: bytes) -> StatusOr<bytes> diff --git a/cc/python/public_key_verify.clif b/cc/python/public_key_verify.clif new file mode 100644 index 0000000000000000000000000000000000000000..9ea3bb4bb0c6818b6413ea4a379900553d7506d8 --- /dev/null +++ b/cc/python/public_key_verify.clif @@ -0,0 +1,31 @@ +# 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. + +from "tink/python/util/clif.h" import * # Status +from clif.python.postproc import DropOkStatus + +from "tink/cc/public_key_verify.h": + namespace `crypto::tink`: + # 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. + class PublicKeyVerify: + # Verifies that signature is a digital signature for data. + def `Verify` as verify(self, signature: bytes, data: bytes) + -> (ok: Status): + return DropOkStatus(...) diff --git a/python/cc/cc_key_manager.h b/python/cc/cc_key_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..a1ceb54dd22544b9b49b0fe9dfd0b20c59a87d8a --- /dev/null +++ b/python/cc/cc_key_manager.h @@ -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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef TINK_PYTHON_CC_CC_KEY_MANAGER_H_ +#define TINK_PYTHON_CC_CC_KEY_MANAGER_H_ + +#include <algorithm> +#include <vector> + +#include "tink/key_manager.h" +#include "tink/util/errors.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" +#include "proto/tink.pb.h" +#include "tink/registry.h" + +namespace crypto { +namespace tink { + +/** + * CcKeyManager is a thin wrapper of KeyManager in + * third_party/tink/cc/key_manager.h + * It only implements the methods currently needed in Python, and slightly + * changes the interface to ease usage of CLIF. + */ +template<class P> +class CcKeyManager { + public: + // Returns a key manager from the registry. + static util::StatusOr<std::unique_ptr<CcKeyManager<P>>> + GetFromCcRegistry(const std::string& type_url) { + auto key_manager_result = Registry::get_key_manager<P>(type_url); + if (!key_manager_result.ok()) { + return ToStatusF(util::error::FAILED_PRECONDITION, + "No manager for key type '%s' found in the registry.", + type_url.c_str()); + } + return absl::make_unique<CcKeyManager<P>>( + key_manager_result.ValueOrDie()); + } + + explicit CcKeyManager(const KeyManager<P>* key_manager) + : key_manager_(key_manager) {} + + // Constructs an instance of P for the given 'key_data'. + crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive( + const google::crypto::tink::KeyData& key_data) { + return key_manager_->GetPrimitive(key_data); + } + + // Creates a new random key, based on the specified 'key_format'. + crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>> + NewKeyData(const google::crypto::tink::KeyTemplate& key_template) { + if (key_manager_->get_key_type() != key_template.type_url()) { + return ToStatusF(util::error::INVALID_ARGUMENT, + "Key type '%s' is not supported by this manager.", + key_template.type_url().c_str()); + } + return key_manager_->get_key_factory().NewKeyData(key_template.value()); + } + + // Returns public key data extracted from the given private_key_data. + crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>> + GetPublicKeyData( + const google::crypto::tink::KeyData& private_key_data) const { + const PrivateKeyFactory* factory = dynamic_cast<const PrivateKeyFactory*>( + &key_manager_->get_key_factory()); + if (factory == nullptr) { + return ToStatusF(util::error::INVALID_ARGUMENT, + "KeyManager for type '%s' does not have " + "a PrivateKeyFactory.", + key_manager_->get_key_type().c_str()); + } + auto result = factory->GetPublicKeyData(private_key_data.value()); + return result; + } + + // Returns the type_url identifying the key type handled by this manager. + std::string KeyType() { + return key_manager_->get_key_type(); + } + + private: + const KeyManager<P>* key_manager_; +}; + +} // namespace tink +} // namespace crypto +#endif // TINK_PYTHON_CC_CC_KEY_MANAGER_H_ diff --git a/python/cc/cc_tink_config.cc b/python/cc/cc_tink_config.cc new file mode 100644 index 0000000000000000000000000000000000000000..53192eefc261bc7244d32422f039f409a766f8ad --- /dev/null +++ b/python/cc/cc_tink_config.cc @@ -0,0 +1,46 @@ +// 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. +// +/////////////////////////////////////////////////////////////////////////////// + +// 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. +// +/////////////////////////////////////////////////////////////////////////////// +#include "tink/python/cc/cc_tink_config.h" + +#include "tink/config/tink_config.h" + +namespace crypto { +namespace tink { + +util::Status CcTinkConfigRegister() { + return TinkConfig::Register(); +} + +const google::crypto::tink::RegistryConfig& CcTinkConfigLatest() { + return TinkConfig::Latest(); +} + +} // namespace tink +} // namespace crypto diff --git a/python/cc/cc_tink_config.h b/python/cc/cc_tink_config.h new file mode 100644 index 0000000000000000000000000000000000000000..16eeb22acc7a13aec188cac4d4ec42ed278732b1 --- /dev/null +++ b/python/cc/cc_tink_config.h @@ -0,0 +1,34 @@ +// 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef TINK_PYTHON_CC_CC_TINK_CONFIG_H_ +#define TINK_PYTHON_CC_CC_TINK_CONFIG_H_ + +#include "tink/util/status.h" +#include "tink/registry.h" +#include "proto/config.pb.h" + +namespace crypto { +namespace tink { + +crypto::tink::util::Status CcTinkConfigRegister(); + +const google::crypto::tink::RegistryConfig& CcTinkConfigLatest(); + +} // namespace tink +} // namespace crypto + +#endif // TINK_PYTHON_CC_CC_TINK_CONFIG_H_ diff --git a/python/cc/clif/cc_key_manager.clif b/python/cc/clif/cc_key_manager.clif new file mode 100644 index 0000000000000000000000000000000000000000..01fae038a92f89075a07228f447d72ab2e2abd7d --- /dev/null +++ b/python/cc/clif/cc_key_manager.clif @@ -0,0 +1,119 @@ +# 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. + +from "tink/proto/tink_pyclif.h" import * # KeyTemplate, KeyData +from "tink/python/util/clif.h" import * # StatusOr +from "tink/cc/python/aead.h" import * # Aead +from "tink/cc/python/deterministic_aead.h" import * # DeterministicAead +from "tink/cc/python/hybrid_decrypt.h" import * # HybridDecrypt +from "tink/cc/python/hybrid_encrypt.h" import * # HybridEncrypt +from "tink/cc/python/mac.h" import * # Mac +from "tink/cc/python/public_key_sign.h" import * # PublicKeySign +from "tink/cc/python/public_key_verify.h" import * # PublicKeyVerify + +from tink.cc.python.aead import Aead +from tink.cc.python.deterministic_aead import DeterministicAead +from tink.cc.python.hybrid_decrypt import HybridDecrypt +from tink.cc.python.hybrid_encrypt import HybridEncrypt +from tink.cc.python.mac import Mac + +from "tink/python/cc/cc_key_manager.h": + namespace `crypto::tink`: + # Key Manager for AEAD (authenticated encryption with associated data). + class `CcKeyManager<Aead>` as AeadKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<AeadKeyManager> + + def `GetPrimitive` as primitive(self, key_data:KeyData) + -> StatusOr<Aead> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template:KeyTemplate) + -> StatusOr<KeyData> + + # Key Manager for Deterministic AEAD. + class `CcKeyManager<DeterministicAead>` as DeterministicAeadKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<DeterministicAeadKeyManager> + + def `GetPrimitive` as primitive(self, key_data:KeyData) + -> StatusOr<DeterministicAead> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template:KeyTemplate) + -> StatusOr<KeyData> + + # Key Manager for HybridDecrypt. + class `CcKeyManager<HybridDecrypt>` as HybridDecryptKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<HybridDecryptKeyManager> + + def `GetPrimitive` as primitive(self, key_data:KeyData) + -> StatusOr<HybridDecrypt> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template:KeyTemplate) + -> StatusOr<KeyData> + def `GetPublicKeyData` as public_key_data(self, key_data:KeyData) + -> StatusOr<KeyData> + + # Key Manager for HybridEncrypt. + class `CcKeyManager<HybridEncrypt>` as HybridEncryptKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<HybridEncryptKeyManager> + + def `GetPrimitive` as primitive(self, key_data:KeyData) + -> StatusOr<HybridEncrypt> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template:KeyTemplate) + -> StatusOr<KeyData> + + # Key Manager for MAC (message authentication code). + class `CcKeyManager<Mac>` as MacKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<MacKeyManager> + + def `GetPrimitive` as primitive(self, key_data:KeyData) + -> StatusOr<Mac> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template:KeyTemplate) + -> StatusOr<KeyData> + + # Key Manager for Public Key signing. + class `CcKeyManager<PublicKeySign>` as PublicKeySignKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<PublicKeySignKeyManager> + + def `GetPrimitive` as primitive(self, key_data: KeyData) + -> StatusOr<PublicKeySign> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template: KeyTemplate) + -> StatusOr<KeyData> + def `GetPublicKeyData` as public_key_data(self, key_data:KeyData) + -> StatusOr<KeyData> + + # Key Manager for Public Key verification. + class `CcKeyManager<PublicKeyVerify>` as PublicKeyVerifyKeyManager: + @classmethod + def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str) + -> StatusOr<PublicKeyVerifyKeyManager> + + def `GetPrimitive` as primitive(self, key_data: KeyData) + -> StatusOr<PublicKeyVerify> + def `KeyType` as key_type(self) -> str + def `NewKeyData` as new_key_data(self, key_template: KeyTemplate) + -> StatusOr<KeyData> diff --git a/python/cc/clif/cc_key_manager_test.py b/python/cc/clif/cc_key_manager_test.py new file mode 100644 index 0000000000000000000000000000000000000000..82d5543403214de9db028223205cae004a794ff9 --- /dev/null +++ b/python/cc/clif/cc_key_manager_test.py @@ -0,0 +1,331 @@ +# 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.cc.clif.py_key_manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +from tink.proto import aes_eax_pb2 +from tink.proto import aes_siv_pb2 +from tink.proto import common_pb2 +from tink.proto import ecdsa_pb2 +from tink.proto import ecies_aead_hkdf_pb2 +from tink.proto import hmac_pb2 +from tink.proto import tink_pb2 +from tink.python.aead import aead_key_templates +from tink.python.cc.clif import cc_key_manager +from tink.python.cc.clif import cc_tink_config +from tink.python.hybrid import hybrid_key_templates +from tink.util import error + + +def setUpModule(): + cc_tink_config.register() + + +class AeadKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(AeadKeyManagerTest, self).setUp() + self.key_manager = cc_key_manager.AeadKeyManager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.AesEaxKey') + + def new_aes_eax_key_template(self, iv_size, key_size): + key_format = aes_eax_pb2.AesEaxKeyFormat() + key_format.params.iv_size = iv_size + key_format.key_size = key_size + key_template = tink_pb2.KeyTemplate() + key_template.type_url = 'type.googleapis.com/google.crypto.tink.AesEaxKey' + key_template.value = key_format.SerializeToString() + return key_template + + def test_key_type(self): + self.assertEqual(self.key_manager.key_type(), + 'type.googleapis.com/google.crypto.tink.AesEaxKey') + + def test_new_key_data(self): + key_template = self.new_aes_eax_key_template(12, 16) + key_data = self.key_manager.new_key_data(key_template) + self.assertEqual(key_data.type_url, self.key_manager.key_type()) + self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC) + key = aes_eax_pb2.AesEaxKey() + key.ParseFromString(key_data.value) + self.assertEqual(key.version, 0) + self.assertEqual(key.params.iv_size, 12) + self.assertLen(key.key_value, 16) + + def test_invalid_params_throw_exception(self): + key_template = self.new_aes_eax_key_template(9, 16) + with self.assertRaises(error.StatusNotOk): + self.key_manager.new_key_data(key_template) + + def test_encrypt_decrypt(self): + aead = self.key_manager.primitive( + self.key_manager.new_key_data(self.new_aes_eax_key_template(12, 16))) + plaintext = b'plaintext' + associated_data = b'associated_data' + ciphertext = aead.encrypt(plaintext, associated_data) + self.assertEqual(aead.decrypt(ciphertext, associated_data), plaintext) + + +class DeterministicAeadKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(DeterministicAeadKeyManagerTest, self).setUp() + daead_key_manager = cc_key_manager.DeterministicAeadKeyManager + self.key_manager = daead_key_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.AesSivKey') + + def new_aes_siv_key_template(self, key_size): + key_format = aes_siv_pb2.AesSivKeyFormat() + key_format.key_size = key_size + key_template = tink_pb2.KeyTemplate() + key_template.type_url = 'type.googleapis.com/google.crypto.tink.AesSivKey' + key_template.value = key_format.SerializeToString() + return key_template + + def test_key_type(self): + self.assertEqual(self.key_manager.key_type(), + 'type.googleapis.com/google.crypto.tink.AesSivKey') + + def test_new_key_data(self): + key_template = self.new_aes_siv_key_template(64) + key_data = self.key_manager.new_key_data(key_template) + self.assertEqual(key_data.type_url, self.key_manager.key_type()) + self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC) + key = aes_siv_pb2.AesSivKey() + key.ParseFromString(key_data.value) + self.assertEqual(key.version, 0) + self.assertLen(key.key_value, 64) + + def test_invalid_params_throw_exception(self): + key_template = self.new_aes_siv_key_template(65) + with self.assertRaises(error.StatusNotOk): + self.key_manager.new_key_data(key_template) + + def test_encrypt_decrypt(self): + aead = self.key_manager.primitive( + self.key_manager.new_key_data(self.new_aes_siv_key_template(64))) + plaintext = b'plaintext' + associated_data = b'associated_data' + ciphertext = aead.encrypt_deterministically(plaintext, associated_data) + self.assertEqual( + aead.decrypt_deterministically(ciphertext, associated_data), plaintext) + + +class HybridKeyManagerTest(googletest.TestCase): + + def hybrid_decrypt_key_manager(self): + return cc_key_manager.HybridDecryptKeyManager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey') + + def hybrid_encrypt_key_manager(self): + return cc_key_manager.HybridEncryptKeyManager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey') + + def test_new_key_data(self): + key_manager = self.hybrid_decrypt_key_manager() + key_data = key_manager.new_key_data( + hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM) + self.assertEqual(key_data.type_url, key_manager.key_type()) + self.assertEqual(key_data.key_material_type, + tink_pb2.KeyData.ASYMMETRIC_PRIVATE) + key = ecies_aead_hkdf_pb2.EciesAeadHkdfPrivateKey() + key.ParseFromString(key_data.value) + self.assertLen(key.key_value, 32) + self.assertEqual(key.public_key.params.kem_params.curve_type, + common_pb2.NIST_P256) + + def test_new_key_data_invalid_params_throw_exception(self): + with self.assertRaisesRegex(error.StatusNotOk, + 'Unsupported elliptic curve'): + self.hybrid_decrypt_key_manager().new_key_data( + hybrid_key_templates.create_ecies_aead_hkdf_key_template( + curve_type=100, + ec_point_format=common_pb2.UNCOMPRESSED, + hash_type=common_pb2.SHA256, + dem_key_template=aead_key_templates.AES128_GCM)) + + def test_encrypt_decrypt(self): + decrypt_key_manager = self.hybrid_decrypt_key_manager() + encrypt_key_manager = self.hybrid_encrypt_key_manager() + key_data = decrypt_key_manager.new_key_data( + hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM) + public_key_data = decrypt_key_manager.public_key_data(key_data) + hybrid_encrypt = encrypt_key_manager.primitive(public_key_data) + ciphertext = hybrid_encrypt.encrypt(b'some plaintext', b'some context info') + hybrid_decrypt = decrypt_key_manager.primitive(key_data) + self.assertEqual(hybrid_decrypt.decrypt(ciphertext, b'some context info'), + b'some plaintext') + + def test_decrypt_fails(self): + decrypt_key_manager = self.hybrid_decrypt_key_manager() + key_data = decrypt_key_manager.new_key_data( + hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM) + hybrid_decrypt = decrypt_key_manager.primitive(key_data) + with self.assertRaisesRegex(error.StatusNotOk, 'ciphertext too short'): + hybrid_decrypt.decrypt(b'bad ciphertext', b'some context info') + + +class MacKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(MacKeyManagerTest, self).setUp() + self.key_manager = cc_key_manager.MacKeyManager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.HmacKey') + + def new_hmac_key_template(self, hash_type, tag_size, key_size): + key_format = hmac_pb2.HmacKeyFormat() + key_format.params.hash = hash_type + key_format.params.tag_size = tag_size + key_format.key_size = key_size + key_template = tink_pb2.KeyTemplate() + key_template.type_url = 'type.googleapis.com/google.crypto.tink.HmacKey' + key_template.value = key_format.SerializeToString() + return key_template + + def test_key_type(self): + self.assertEqual(self.key_manager.key_type(), + 'type.googleapis.com/google.crypto.tink.HmacKey') + + def test_new_key_data(self): + key_template = self.new_hmac_key_template(common_pb2.SHA256, 24, 16) + key_data = self.key_manager.new_key_data(key_template) + self.assertEqual(key_data.type_url, self.key_manager.key_type()) + key = hmac_pb2.HmacKey() + key.ParseFromString(key_data.value) + self.assertEqual(key.version, 0) + self.assertEqual(key.params.hash, common_pb2.SHA256) + self.assertEqual(key.params.tag_size, 24) + self.assertLen(key.key_value, 16) + + def test_invalid_params_throw_exception(self): + key_template = self.new_hmac_key_template(common_pb2.SHA256, 9, 16) + with self.assertRaises(error.StatusNotOk): + self.key_manager.new_key_data(key_template) + + def test_mac_success(self): + mac = self.key_manager.primitive( + self.key_manager.new_key_data( + self.new_hmac_key_template(common_pb2.SHA256, 24, 16))) + data = b'data' + tag = mac.compute_mac(data) + self.assertLen(tag, 24) + # No exception raised. + mac.verify_mac(tag, data) + + def test_mac_wrong(self): + mac = self.key_manager.primitive( + self.key_manager.new_key_data( + self.new_hmac_key_template(common_pb2.SHA256, 16, 16))) + with self.assertRaisesRegex(error.StatusNotOk, 'verification failed'): + mac.verify_mac(b'0123456789ABCDEF', b'data') + + +class PublicKeySignVerifyKeyManagerTest(googletest.TestCase): + + def setUp(self): + super(PublicKeySignVerifyKeyManagerTest, self).setUp() + public_key_verify_manager = cc_key_manager.PublicKeyVerifyKeyManager + self.key_manager_verify = public_key_verify_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey') + public_key_sign_manager = cc_key_manager.PublicKeySignKeyManager + self.key_manager_sign = public_key_sign_manager.from_cc_registry( + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + + def new_ecdsa_key_template(self, hash_type, curve_type, encoding, + public=False): + 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 + + def test_key_type_sign(self): + self.assertEqual(self.key_manager_sign.key_type(), + 'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey') + + def test_key_type_verify(self): + self.assertEqual(self.key_manager_verify.key_type(), + 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey') + + def test_new_key_data_sign(self): + key_template = self.new_ecdsa_key_template( + common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER) + key_data = self.key_manager_sign.new_key_data(key_template) + self.assertEqual(key_data.type_url, self.key_manager_sign.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_new_key_data_verify(self): + key_template = self.new_ecdsa_key_template( + common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, True) + with self.assertRaisesRegex(error.StatusNotOk, 'Operation not supported'): + self.key_manager_verify.new_key_data(key_template) + + def test_signature_success(self): + priv_key = self.key_manager_sign.new_key_data( + self.new_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256, + ecdsa_pb2.DER)) + pub_key = self.key_manager_sign.public_key_data(priv_key) + + verifier = self.key_manager_verify.primitive(pub_key) + signer = self.key_manager_sign.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) + + def test_signature_fails(self): + key_template = self.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_verify.primitive(pub_key) + + data = b'data' + signature = signer.sign(data) + + with self.assertRaisesRegex(error.StatusNotOk, 'Signature is not valid'): + verifier.verify(signature, 'wrongdata') + + with self.assertRaisesRegex(error.StatusNotOk, 'Signature is not valid'): + verifier.verify('wrongsignature', data) + + +if __name__ == '__main__': + googletest.main() diff --git a/python/cc/clif/cc_tink_config.clif b/python/cc/clif/cc_tink_config.clif new file mode 100644 index 0000000000000000000000000000000000000000..0eff706413be02f03e2e6082c00cde097f9a6d07 --- /dev/null +++ b/python/cc/clif/cc_tink_config.clif @@ -0,0 +1,21 @@ +# 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. + +from "tink/proto/config_pyclif.h" import * # RegistryConfig +from "tink/python/util/clif.h" import * # StatusOr + +from "tink/python/cc/cc_tink_config.h": + namespace `crypto::tink`: + def `CcTinkConfigRegister` as register() -> Status + def `CcTinkConfigLatest` as latest() -> RegistryConfig diff --git a/python/cc/clif/cc_tink_config_test.py b/python/cc/clif/cc_tink_config_test.py new file mode 100644 index 0000000000000000000000000000000000000000..e7e96de54ad847ef09b99259609e17032d9236a0 --- /dev/null +++ b/python/cc/clif/cc_tink_config_test.py @@ -0,0 +1,43 @@ +# 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.cc.clif.cc_tink_config.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +from tink.python.cc.clif import cc_tink_config + + +class CcTinkConfigTest(googletest.TestCase): + + def test_latest(self): + cc_tink_config.register() + latest = cc_tink_config.latest() + primitive_names = {entry.primitive_name for entry in latest.entry} + self.assertIn('Aead', primitive_names) + self.assertIn('Mac', primitive_names) + self.assertIn('PublicKeySign', primitive_names) + type_urls = {entry.type_url for entry in latest.entry} + self.assertIn('type.googleapis.com/google.crypto.tink.AesEaxKey', type_urls) + self.assertIn('type.googleapis.com/google.crypto.tink.AesGcmKey', type_urls) + self.assertIn('type.googleapis.com/google.crypto.tink.HmacKey', type_urls) + self.assertIn('type.googleapis.com/google.crypto.tink.EcdsaPrivateKey', + type_urls) + + +if __name__ == '__main__': + googletest.main() diff --git a/python/testing/helper.py b/python/testing/helper.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd723467c0bd01c539e61d86af193d1eeb65da8 --- /dev/null +++ b/python/testing/helper.py @@ -0,0 +1,149 @@ +# 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. + +"""This class implements helper functions for testing.""" + +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.proto import tink_pb2 +from tink.python import aead +from tink.python import daead +from tink.python import hybrid +from tink.python import mac +from tink.python import signature as pk_signature +from tink.python.core import tink_error + + +def fake_key(value: bytes = b'fakevalue', + type_url: Text = 'fakeurl', + key_material_type: tink_pb2.KeyData.KeyMaterialType = tink_pb2 + .KeyData.SYMMETRIC, + key_id: int = 1234, + status: tink_pb2.KeyStatusType = tink_pb2.ENABLED, + output_prefix_type: tink_pb2.OutputPrefixType = tink_pb2.TINK + ) -> tink_pb2.Keyset.Key: + """Returns a fake but valid key.""" + key = tink_pb2.Keyset.Key( + key_id=key_id, + status=status, + output_prefix_type=output_prefix_type) + key.key_data.type_url = type_url + key.key_data.value = value + key.key_data.key_material_type = key_material_type + return key + + +class FakeMac(mac.Mac): + """A fake MAC implementation.""" + + def __init__(self, name: Text = 'FakeMac'): + self._name = name + + def compute_mac(self, data: bytes) -> bytes: + return data + b'|' + self._name.encode() + + def verify_mac(self, mac_value: bytes, data: bytes) -> None: + if mac_value != data + b'|' + self._name.encode(): + raise tink_error.TinkError('invalid mac ' + mac_value.decode()) + + +class FakeAead(aead.Aead): + """A fake AEAD implementation.""" + + def __init__(self, name: Text = 'FakeAead'): + self._name = name + + def encrypt(self, plaintext: bytes, associated_data: bytes) -> bytes: + return plaintext + b'|' + associated_data + b'|' + self._name.encode() + + def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes: + data = ciphertext.split(b'|') + if (len(data) < 3 or data[1] != associated_data or + data[2] != self._name.encode()): + raise tink_error.TinkError('failed to decrypt ciphertext ' + + ciphertext.decode()) + return data[0] + + +class FakeDeterministicAead(daead.DeterministicAead): + """A fake Deterministic AEAD implementation.""" + + def __init__(self, name: Text = 'FakeDeterministicAead'): + self._name = name + + def encrypt_deterministically(self, plaintext: bytes, + associated_data: bytes) -> bytes: + return plaintext + b'|' + associated_data + b'|' + self._name.encode() + + def decrypt_deterministically(self, ciphertext: bytes, + associated_data: bytes) -> bytes: + data = ciphertext.split(b'|') + if (len(data) < 3 or + data[1] != associated_data or + data[2] != self._name.encode()): + raise tink_error.TinkError('failed to decrypt ciphertext ' + + ciphertext.decode()) + return data[0] + + +class FakeHybridDecrypt(hybrid.HybridDecrypt): + """A fake HybridEncrypt implementation.""" + + def __init__(self, name: Text = 'Hybrid'): + self._name = name + + def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes: + data = ciphertext.split(b'|') + if (len(data) < 3 or + data[1] != context_info or + data[2] != self._name.encode()): + raise tink_error.TinkError('failed to decrypt ciphertext ' + + ciphertext.decode()) + return data[0] + + +class FakeHybridEncrypt(hybrid.HybridEncrypt): + """A fake HybridEncrypt implementation.""" + + def __init__(self, name: Text = 'Hybrid'): + self._name = name + + def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes: + return plaintext + b'|' + context_info + b'|' + self._name.encode() + + +class FakePublicKeySign(pk_signature.PublicKeySign): + """A fake PublicKeySign implementation.""" + + def __init__(self, name: Text = 'FakePublicKeySign'): + self._name = name + + def sign(self, data: bytes) -> bytes: + return data + b'|' + self._name.encode() + + +class FakePublicKeyVerify(pk_signature.PublicKeyVerify): + """A fake PublicKeyVerify implementation.""" + + def __init__(self, name: Text = 'FakePublicKeyVerify'): + self._name = name + + def verify(self, signature: bytes, data: bytes): + if signature != data + b'|' + self._name.encode(): + raise tink_error.TinkError('invalid signature ' + signature.decode()) diff --git a/python/testing/helper_test.py b/python/testing/helper_test.py new file mode 100644 index 0000000000000000000000000000000000000000..385e4b30f2f4bfc7b002f32cf2306f8c0123317b --- /dev/null +++ b/python/testing/helper_test.py @@ -0,0 +1,139 @@ +# 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.testing.helper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +from tink.python.core import tink_error +from tink.python.testing import helper + + +class HelperTest(googletest.TestCase): + + def test_fake_mac_success(self): + mac = helper.FakeMac('Name') + mac_value = mac.compute_mac(b'data') + mac.verify_mac(mac_value, b'data') + + def test_fake_mac_fail_wrong_data(self): + mac = helper.FakeMac('Name') + mac_value = mac.compute_mac(b'data') + with self.assertRaisesRegex(tink_error.TinkError, + 'invalid mac'): + mac.verify_mac(mac_value, b'wrong data') + + def test_fake_mac_fail_wrong_primitive(self): + mac = helper.FakeMac('Name') + mac_value = mac.compute_mac(b'data') + wrong_mac = helper.FakeMac('Wrong Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'invalid mac'): + wrong_mac.verify_mac(mac_value, b'data') + + def test_fake_aead_success(self): + aead = helper.FakeAead('Name') + ciphertext = aead.encrypt(b'plaintext', b'associated_data') + self.assertEqual( + aead.decrypt(ciphertext, b'associated_data'), + b'plaintext') + + def test_fake_aead_fail_wrong_cipertext(self): + aead = helper.FakeAead('Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + aead.decrypt(b'wrong ciphertext', b'associated_data') + + def test_fake_aead_fail_wrong_associated_data(self): + aead = helper.FakeAead('Name') + ciphertext = aead.encrypt(b'plaintext', b'associated_data') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + aead.decrypt(ciphertext, b'wrong_associated_data') + + def test_fake_aead_fail_wrong_primitive(self): + aead = helper.FakeAead('Name') + ciphertext = aead.encrypt(b'plaintext', b'associated_data') + wrong_aead = helper.FakeAead('Wrong Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + wrong_aead.decrypt(ciphertext, b'associated_data') + + def test_fake_deterministic_aead_success(self): + daead = helper.FakeDeterministicAead('Name') + ciphertext = daead.encrypt_deterministically(b'plaintext', + b'associated_data') + self.assertEqual( + daead.decrypt_deterministically(ciphertext, b'associated_data'), + b'plaintext') + + def test_fake_deterministic_aead_fail_wrong_cipertext(self): + daead = helper.FakeDeterministicAead('Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + daead.decrypt_deterministically(b'wrong ciphertext', b'associated_data') + + def test_fake_deterministic_aead_fail_wrong_associated_data(self): + daead = helper.FakeDeterministicAead('Name') + ciphertext = daead.encrypt_deterministically(b'plaintext', + b'associated_data') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + daead.decrypt_deterministically(ciphertext, b'wrong_associated_data') + + def test_fake_deterministic_aead_fail_wrong_primitive(self): + daead = helper.FakeDeterministicAead('Name') + ciphertext = daead.encrypt_deterministically(b'plaintext', + b'associated_data') + wrong_daead = helper.FakeDeterministicAead('Wrong Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + wrong_daead.decrypt_deterministically(ciphertext, b'associated_data') + + def test_fake_hybrid_success(self): + enc = helper.FakeHybridEncrypt('Name') + dec = helper.FakeHybridDecrypt('Name') + ciphertext = enc.encrypt(b'plaintext', b'context_info') + self.assertEqual( + dec.decrypt(ciphertext, b'context_info'), + b'plaintext') + + def test_fake_hybrid_fail_wrong_context(self): + enc = helper.FakeHybridEncrypt('Name') + dec = helper.FakeHybridDecrypt('Name') + ciphertext = enc.encrypt(b'plaintext', b'context_info') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + dec.decrypt(ciphertext, b'other_context_info') + + def test_fake_hybrid_fail_wrong_dec(self): + enc = helper.FakeHybridEncrypt('Name') + dec = helper.FakeHybridDecrypt('Wrong Name') + ciphertext = enc.encrypt(b'plaintext', b'context_info') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + dec.decrypt(ciphertext, b'context_info') + + def test_fake_hybrid_fail_wrong_ciphertext(self): + dec = helper.FakeHybridDecrypt('Name') + with self.assertRaisesRegex(tink_error.TinkError, + 'failed to decrypt ciphertext'): + dec.decrypt(b'wrong ciphertext', b'context_info') + + +if __name__ == '__main__': + googletest.main() diff --git a/python/util/clif.cc b/python/util/clif.cc new file mode 100644 index 0000000000000000000000000000000000000000..c440e11d3424283671b28b322be5253816f9bfd6 --- /dev/null +++ b/python/util/clif.cc @@ -0,0 +1,131 @@ +// 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "tink/python/util/clif.h" + +#include "google/protobuf/message_set.pb.h" +#include "absl/strings/str_cat.h" +#include "tink/util/canonical_errors.h" + +#if !defined(PORTABLE_STATUS) +#include "tink/util/status_payload.h" +#endif + +namespace crypto { +namespace tink { +namespace util { + +PyObject* Get_UtilStatusOk() { + static PyObject* const kUtilStatusOk = _PyObject_New(&PyBaseObject_Type); + // TODO(mrovner): The following return is sligthly wrong: + // In the (unlikely) case where the allocation failed, subsequent calls to + // this function will return null without setting a Python exception. + Py_XINCREF(kUtilStatusOk); + return kUtilStatusOk; +} + +void ErrorFromStatus(const Status& status) { + /* Can't just do this + static PyObject* const kUtilStatusError = ImportFQName(... + because static creates a C++ lock around the whole statement and GIL can be + elsewhere due to Python import. */ + static PyObject* kUtilStatusError = nullptr; + if (kUtilStatusError == nullptr) { + // Worst case it will do ImportFQName K times (K==number of threads) + // which is OK. + kUtilStatusError = + clif::ImportFQName("google3.util.task.python.error.StatusNotOk"); + DCHECK(kUtilStatusError != nullptr); + } + PyObject* message_set_object; + message_set_object = Py_None; + Py_INCREF(message_set_object); + PyObject* err = Py_BuildValue( + "is#siN", status.error_code(), status.error_message().data(), + status.error_message().size(), "Not implemented!", + status.CanonicalCode(), message_set_object); + if (err != nullptr) { + PyErr_SetObject(kUtilStatusError, err); + Py_DECREF(err); + } // otherwise error is already set +} + +Status StatusFromPyException() { + if (!PyErr_Occurred()) { + return OkStatus(); + } + + if (PyErr_ExceptionMatches(PyExc_MemoryError)) { + return ::util::ResourceExhaustedError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) { + return ::util::UnimplementedError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) { + return ::util::AbortedError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_SystemError) || + PyErr_ExceptionMatches(PyExc_SyntaxError)) { + return ::util::InternalError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + return ::util::InvalidArgumentError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + return ::util::OutOfRangeError(PyExcFetch()); + } + if (PyErr_ExceptionMatches(PyExc_LookupError)) { + return ::util::NotFoundError(PyExcFetch()); + } + + return ::util::UnknownError(PyExcFetch()); +} + +PyObject* Clif_PyObjFrom(const Status& c, const clif::py::PostConv& unused) { + if (!c.ok()) { + ErrorFromStatus(c); + return nullptr; + } + return Get_UtilStatusOk(); +} + +std::string PyExcFetch() { + CHECK(PyErr_Occurred()) << "Must only call PyExcFetch after an exception."; + PyObject* ptype; + PyObject* pvalue; + PyObject* ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + std::string err = clif::ClassName(ptype); + if (pvalue) { + PyObject* str = PyObject_Str(pvalue); + if (str) { +#if PY_MAJOR_VERSION < 3 + absl::StrAppend(&err, ": ", PyString_AS_STRING(str)); +#else + absl::StrAppend(&err, ": ", PyUnicode_AsUTF8(str)); +#endif + Py_DECREF(str); + } + Py_DECREF(pvalue); + } + Py_DECREF(ptype); + Py_XDECREF(ptraceback); + return err; +} + +} // namespace util +} // namespace tink +} // namespace crypto diff --git a/python/util/clif.h b/python/util/clif.h new file mode 100644 index 0000000000000000000000000000000000000000..6137558ab29f2d9412f8af4c7fb48b6ec41f2e8b --- /dev/null +++ b/python/util/clif.h @@ -0,0 +1,126 @@ +// 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef TINK_PYTHON_UTIL_CLIF_H_ +#define TINK_PYTHON_UTIL_CLIF_H_ + +#include "base/logging.h" +#include "devtools/clif/python/types.h" +#include "util/python/pyobject.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +// CLIF use `::crypto::tink::util::Status` as Status +// CLIF use `::crypto::tink::util::StatusOr` as StatusOr + +namespace crypto { +namespace tink { +namespace util { + +PyObject* Get_UtilStatusOk(); +void ErrorFromStatus(const Status& status); +Status StatusFromPyException(); +std::string PyExcFetch(); + +PyObject* Clif_PyObjFrom(const Status& c, const ::clif::py::PostConv&); + +// Note: there is no corresponding PyObjAs for util::Status, as this class is +// represented on the Python side by an exception. This implies that it is not +// possible to hand a util::Status to C++. Pass a code and message instead. + +template <typename T> +PyObject* Clif_PyObjFrom(const StatusOr<T>& c, const ::clif::py::PostConv& pc) { + if (!c.ok()) { + ErrorFromStatus(c.status()); + return nullptr; + } else { + using ::clif::Clif_PyObjFrom; + return Clif_PyObjFrom(c.ValueOrDie(), pc.Get(0)); + } +} + +template <typename T> +PyObject* Clif_PyObjFrom(StatusOr<T>&& c, // NOLINT:c++11 + const ::clif::py::PostConv& pc) { + if (!c.ok()) { + ErrorFromStatus(c.status()); + return nullptr; + } else { + using ::clif::Clif_PyObjFrom; + return Clif_PyObjFrom(std::move(c).ValueOrDie(), pc.Get(0)); + } +} + +template<typename T> +bool Clif_PyObjAs(PyObject* p, StatusOr<T>* c) { + CHECK(c != nullptr); + + if (PyErr_Occurred()) { + *c = StatusFromPyException(); + return true; + } + + T val; + using ::clif::Clif_PyObjAs; + if (Clif_PyObjAs(p, &val)) { + *c = std::move(val); + return true; + } + + return false; +} + +} // namespace util +} // namespace tink +} // namespace crypto + +namespace clif { +namespace callback { + +// Specialization of a generic Clif Python callback handling for a +// function returning util::StatusOr. +template <typename T> +class ReturnValue<::crypto::tink::util::StatusOr<T>> { + public: + ::crypto::tink::util::StatusOr<T> FromPyValue(PyObject* result) { + ::crypto::tink::util::StatusOr<T> v; + bool ok = Clif_PyObjAs(result, &v); + Py_XDECREF(result); + if (!ok) { + v = ::crypto::tink::util::StatusFromPyException(); + } + return v; + } +}; + +// Specialization of a generic Clif Python callback handling for a +// function returning a util::Status. +template <> +class ReturnValue<::crypto::tink::util::Status> { + public: + ::crypto::tink::util::Status FromPyValue(PyObject* result) { + Py_XDECREF(result); + if (PyErr_Occurred()) { + return ::crypto::tink::util::StatusFromPyException(); + } + return ::crypto::tink::util::OkStatus(); + } +}; + +} // namespace callback +} // namespace clif + +#endif // TINK_PYTHON_UTIL_CLIF_H_