From 3b9249cd240392a507fde81676b7b9770b211c45 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 10 Nov 2015 16:39:15 -0800 Subject: [PATCH] Adding app identity blob signing example. --- appengine/app_identity/__init__.py | 0 appengine/app_identity/signing/__init__.py | 0 appengine/app_identity/signing/app.yaml | 11 +++ appengine/app_identity/signing/main.py | 82 +++++++++++++++++++++ appengine/app_identity/signing/main_test.py | 30 ++++++++ tests/utils.py | 1 + 6 files changed, 124 insertions(+) create mode 100644 appengine/app_identity/__init__.py create mode 100644 appengine/app_identity/signing/__init__.py create mode 100644 appengine/app_identity/signing/app.yaml create mode 100644 appengine/app_identity/signing/main.py create mode 100644 appengine/app_identity/signing/main_test.py diff --git a/appengine/app_identity/__init__.py b/appengine/app_identity/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/app_identity/signing/__init__.py b/appengine/app_identity/signing/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/app_identity/signing/app.yaml b/appengine/app_identity/signing/app.yaml new file mode 100644 index 000000000000..9f51c8eff367 --- /dev/null +++ b/appengine/app_identity/signing/app.yaml @@ -0,0 +1,11 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: .* + script: main.app + +libraries: +- name: pycrypto + version: latest diff --git a/appengine/app_identity/signing/main.py b/appengine/app_identity/signing/main.py new file mode 100644 index 000000000000..1baf28bc2f9a --- /dev/null +++ b/appengine/app_identity/signing/main.py @@ -0,0 +1,82 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Sample Google App Engine application that demonstrates usage of the app +identity API. +""" + +# [START all] + +import base64 + +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA +from Crypto.Signature import PKCS1_v1_5 +from Crypto.Util.asn1 import DerSequence +from google.appengine.api import app_identity +import webapp2 + + +def verify_signature(data, signature, x509_certificate): + """Verifies a signature using the given x.509 public key certificate.""" + + # PyCrypto 2.6 doesn't support x.509 certificates directly, so we'll need + # to extract the public key from it manually. + # This code is based on https://github.com/google/oauth2client/blob/master + # /oauth2client/_pycrypto_crypt.py + pem_lines = x509_certificate.replace(b' ', b'').split() + cert_der = base64.urlsafe_b64decode(b''.join(pem_lines[1:-1])) + cert_seq = DerSequence() + cert_seq.decode(cert_der) + tbs_seq = DerSequence() + tbs_seq.decode(cert_seq[0]) + public_key = RSA.importKey(tbs_seq[6]) + + signer = PKCS1_v1_5.new(public_key) + digest = SHA256.new(data) + + return signer.verify(digest, signature) + + +def verify_signed_by_app(data, signature): + """Checks the signature and data against all currently valid certificates + for the application.""" + public_certificates = app_identity.get_public_certificates() + + for cert in public_certificates: + if verify_signature(data, signature, cert.x509_certificate_pem): + return True + + return False + + +class MainPage(webapp2.RequestHandler): + def get(self): + message = 'Hello, world!' + signing_key_name, signature = app_identity.sign_blob(message) + verified = verify_signed_by_app(message, signature) + + self.response.content_type = 'text/plain' + self.response.write('Message: {}\n'.format(message)) + self.response.write( + 'Signature: {}\n'.format(base64.b64encode(signature))) + self.response.write('Verified: {}\n'.format(verified)) + + +app = webapp2.WSGIApplication([ + ('/', MainPage) +], debug=True) + +# [END all] diff --git a/appengine/app_identity/signing/main_test.py b/appengine/app_identity/signing/main_test.py new file mode 100644 index 000000000000..fdb5a75b9e80 --- /dev/null +++ b/appengine/app_identity/signing/main_test.py @@ -0,0 +1,30 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# 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 tests import AppEngineTestbedCase +import webtest + +from . import main + + +class TestAppIdentityHandler(AppEngineTestbedCase): + def setUp(self): + super(TestAppIdentityHandler, self).setUp() + + self.app = webtest.TestApp(main.app) + + def test_get(self): + response = self.app.get('/') + self.assertEqual(response.status_int, 200) + self.assertTrue('Verified: True' in response.text) diff --git a/tests/utils.py b/tests/utils.py index 41741591b495..9ea605622b26 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -88,6 +88,7 @@ def setUp(self): self.testbed.init_memcache_stub() # Setup remaining stubs. + self.testbed.init_app_identity_stub() self.testbed.init_blobstore_stub() self.testbed.init_user_stub() self.testbed.init_taskqueue_stub(root_path='tests/resources')