Skip to content

Commit b80b688

Browse files
author
Jonathan Wayne Parrott
committed
Merge pull request #139 from GoogleCloudPlatform/appengine-identity
Adding app identity blob signing example.
2 parents 19d830f + 3b9249c commit b80b688

File tree

6 files changed

+124
-0
lines changed

6 files changed

+124
-0
lines changed

appengine/app_identity/__init__.py

Whitespace-only changes.

appengine/app_identity/signing/__init__.py

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
runtime: python27
2+
threadsafe: yes
3+
api_version: 1
4+
5+
handlers:
6+
- url: .*
7+
script: main.app
8+
9+
libraries:
10+
- name: pycrypto
11+
version: latest
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2015 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Sample Google App Engine application that demonstrates usage of the app
17+
identity API.
18+
"""
19+
20+
# [START all]
21+
22+
import base64
23+
24+
from Crypto.Hash import SHA256
25+
from Crypto.PublicKey import RSA
26+
from Crypto.Signature import PKCS1_v1_5
27+
from Crypto.Util.asn1 import DerSequence
28+
from google.appengine.api import app_identity
29+
import webapp2
30+
31+
32+
def verify_signature(data, signature, x509_certificate):
33+
"""Verifies a signature using the given x.509 public key certificate."""
34+
35+
# PyCrypto 2.6 doesn't support x.509 certificates directly, so we'll need
36+
# to extract the public key from it manually.
37+
# This code is based on https://github.com/google/oauth2client/blob/master
38+
# /oauth2client/_pycrypto_crypt.py
39+
pem_lines = x509_certificate.replace(b' ', b'').split()
40+
cert_der = base64.urlsafe_b64decode(b''.join(pem_lines[1:-1]))
41+
cert_seq = DerSequence()
42+
cert_seq.decode(cert_der)
43+
tbs_seq = DerSequence()
44+
tbs_seq.decode(cert_seq[0])
45+
public_key = RSA.importKey(tbs_seq[6])
46+
47+
signer = PKCS1_v1_5.new(public_key)
48+
digest = SHA256.new(data)
49+
50+
return signer.verify(digest, signature)
51+
52+
53+
def verify_signed_by_app(data, signature):
54+
"""Checks the signature and data against all currently valid certificates
55+
for the application."""
56+
public_certificates = app_identity.get_public_certificates()
57+
58+
for cert in public_certificates:
59+
if verify_signature(data, signature, cert.x509_certificate_pem):
60+
return True
61+
62+
return False
63+
64+
65+
class MainPage(webapp2.RequestHandler):
66+
def get(self):
67+
message = 'Hello, world!'
68+
signing_key_name, signature = app_identity.sign_blob(message)
69+
verified = verify_signed_by_app(message, signature)
70+
71+
self.response.content_type = 'text/plain'
72+
self.response.write('Message: {}\n'.format(message))
73+
self.response.write(
74+
'Signature: {}\n'.format(base64.b64encode(signature)))
75+
self.response.write('Verified: {}\n'.format(verified))
76+
77+
78+
app = webapp2.WSGIApplication([
79+
('/', MainPage)
80+
], debug=True)
81+
82+
# [END all]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2015 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from tests import AppEngineTestbedCase
16+
import webtest
17+
18+
from . import main
19+
20+
21+
class TestAppIdentityHandler(AppEngineTestbedCase):
22+
def setUp(self):
23+
super(TestAppIdentityHandler, self).setUp()
24+
25+
self.app = webtest.TestApp(main.app)
26+
27+
def test_get(self):
28+
response = self.app.get('/')
29+
self.assertEqual(response.status_int, 200)
30+
self.assertTrue('Verified: True' in response.text)

tests/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def setUp(self):
8888
self.testbed.init_memcache_stub()
8989

9090
# Setup remaining stubs.
91+
self.testbed.init_app_identity_stub()
9192
self.testbed.init_blobstore_stub()
9293
self.testbed.init_user_stub()
9394
self.testbed.init_taskqueue_stub(root_path='tests/resources')

0 commit comments

Comments
 (0)