From 52f74d22234bc769fcaa6ed5fa6285822dd8801b Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 21 Jan 2016 16:52:42 -0800 Subject: [PATCH 1/3] Add sample for asserting identity to third-party services --- .../appidentity/SignForAppServlet.java | 114 ++++++++++++++++++ .../src/main/webapp/WEB-INF/web.xml | 8 ++ .../appidentity/IdentityServletTest.java | 15 +-- .../appidentity/SignForAppServletTest.java | 71 +++++++++++ 4 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java create mode 100644 appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java new file mode 100644 index 00000000000..5cd1798a001 --- /dev/null +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java @@ -0,0 +1,114 @@ +/** + * Copyright 2016 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. + */ +package com.example.appengine.appidentity; + +import com.google.appengine.api.appidentity.PublicCertificate; +import com.google.appengine.api.appidentity.AppIdentityService; +import com.google.appengine.api.appidentity.AppIdentityServiceFactory; +import com.google.apphosting.api.ApiProxy; +import com.google.apphosting.api.ApiProxy.Environment; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Collection; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@SuppressWarnings("serial") +public class SignForAppServlet extends HttpServlet { + private final AppIdentityService appIdentity; + + public SignForAppServlet() { + appIdentity = AppIdentityServiceFactory.getAppIdentityService(); + } + + // [START asserting_identity_to_other_services] + // Note that the algorithm used by AppIdentity.signForApp() and + // getPublicCertificatesForApp() is "SHA256withRSA" + + private byte[] signBlob(byte[] blob) { + AppIdentityService.SigningResult result = appIdentity.signForApp(blob); + return result.getSignature(); + } + + private byte[] getPublicCertificate() throws UnsupportedEncodingException { + Collection certs = appIdentity.getPublicCertificatesForApp(); + PublicCertificate publicCert = certs.iterator().next(); + return publicCert.getX509CertificateInPemFormat().getBytes("UTF-8"); + } + + private Certificate parsePublicCertificate(byte[] publicCert) + throws CertificateException, NoSuchAlgorithmException { + InputStream stream = new ByteArrayInputStream(publicCert); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return cf.generateCertificate(stream); + } + + private boolean verifySignature(byte[] blob, byte[] blobSignature, PublicKey pk) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initVerify(pk); + signature.update(blob); + return signature.verify(blobSignature); + } + + private String simulateIdentityAssertion() + throws CertificateException, UnsupportedEncodingException, NoSuchAlgorithmException, + InvalidKeyException, SignatureException { + // Simulate the sending app. + String message = "abcdefg"; + byte[] blob = message.getBytes(); + byte[] blobSignature = signBlob(blob); + byte[] publicCert = getPublicCertificate(); + + // Simulate the receiving app, which gets the certificate, blob, and signature. + Certificate cert = parsePublicCertificate(publicCert); + PublicKey pk = cert.getPublicKey(); + boolean isValid = verifySignature(blob, blobSignature, pk); + + return String.format( + "isValid=%b for message: %s\n\tsignature: %s\n\tpublic cert: %s", + isValid, + message, + Arrays.toString(blobSignature), + Arrays.toString(publicCert)); + } + // [END asserting_identity_to_other_services] + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain"); + try { + resp.getWriter().println(simulateIdentityAssertion()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/appengine/appidentity/src/main/webapp/WEB-INF/web.xml b/appengine/appidentity/src/main/webapp/WEB-INF/web.xml index 3ebbce0e290..3296a0799f4 100644 --- a/appengine/appidentity/src/main/webapp/WEB-INF/web.xml +++ b/appengine/appidentity/src/main/webapp/WEB-INF/web.xml @@ -7,6 +7,10 @@ appidentity com.example.appengine.appidentity.IdentityServlet + + signforapp + com.example.appengine.appidentity.SignForAppServlet + urlshortener com.example.appengine.appidentity.UrlShortenerServlet @@ -15,6 +19,10 @@ appidentity / + + signforapp + /sign + urlshortener /shorten diff --git a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java index c0d32159cfe..7df6e871367 100644 --- a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java +++ b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java @@ -1,13 +1,13 @@ /** * Copyright 2015 Google Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (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 + *

http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software + *

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 @@ -16,16 +16,13 @@ package com.example.appengine.appidentity; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.google.appengine.tools.development.ApiProxyLocal; import com.google.appengine.tools.development.testing.LocalServiceTestHelper; -import com.google.apphosting.api.ApiProxy; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -62,6 +59,10 @@ public void setUp() throws Exception { servletUnderTest = new IdentityServlet(); } + @After public void tearDown() { + helper.tearDown(); + } + @Test public void doGet_defaultEnvironment_writesResponse() throws Exception { servletUnderTest.doGet(mockRequest, mockResponse); diff --git a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java new file mode 100644 index 00000000000..b245ef11e7b --- /dev/null +++ b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java @@ -0,0 +1,71 @@ +/** + * Copyright 2016 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. + */ +package com.example.appengine.appidentity; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** Unit tests for {@link SignForAppServlet}. */ +@RunWith(JUnit4.class) +public class SignForAppServletTest { + + private final LocalServiceTestHelper helper = new LocalServiceTestHelper(); + + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private SignForAppServlet servletUnderTest; + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new SignForAppServlet(); + } + + @After public void tearDown() { + helper.tearDown(); + } + + @Test public void doGet_defaultEnvironment_successfullyVerifiesSignature() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertThat(responseWriter.toString()) + .named("SignForAppServlet response") + .contains("isValid=true for message: abcdefg"); + } +} From a034ddbdff1194a88d74c0439cbac872baaab3a2 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 21 Jan 2016 17:06:24 -0800 Subject: [PATCH 2/3] Fix style errors in the App Identity samples. I checked for style errors using the following command: mvn checkstyle:checkstyle \ -Dcheckstyle.config.location=google_checks.xml \ -Dcheckstyle.consoleOutput=true --- .../appidentity/IdentityServlet.java | 3 +- .../appengine/appidentity/UrlShortener.java | 9 ++--- .../appidentity/UrlShortenerServlet.java | 34 ++++++++++--------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/IdentityServlet.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/IdentityServlet.java index 3464ed47e17..d10cf3efed5 100644 --- a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/IdentityServlet.java +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/IdentityServlet.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.example.appengine.appidentity; import com.google.apphosting.api.ApiProxy; diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java index cecebe82d6a..94f08768948 100644 --- a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.example.appengine.appidentity; import com.google.appengine.api.appidentity.AppIdentityService; @@ -41,8 +42,8 @@ class UrlShortener { public String createShortUrl(String longUrl) throws Exception { ArrayList scopes = new ArrayList(); scopes.add("https://www.googleapis.com/auth/urlshortener"); - AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService(); - AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes); + final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService(); + final AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes); // The token asserts the identity reported by appIdentity.getServiceAccountName() JSONObject request = new JSONObject(); request.put("longUrl", longUrl); @@ -61,7 +62,7 @@ public String createShortUrl(String longUrl) throws Exception { if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { // Note: Should check the content-encoding. // Any JSON parser can be used; this one is used for illustrative purposes. - JSONTokener response_tokens = new JSONTokener(connection.getInputStream()); + JSONTokener responseTokens = new JSONTokener(connection.getInputStream()); JSONObject response = new JSONObject(response_tokens); return (String) response.get("id"); } else { diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java index 8bab40e8115..2778ed2f325 100644 --- a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.example.appengine.appidentity; import com.google.appengine.api.users.UserService; @@ -36,15 +37,16 @@ public UrlShortenerServlet() { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { - PrintWriter w = resp.getWriter(); - w.println(""); - w.println(""); - w.println("Asserting Identity to Google APIs - App Engine App Identity Example"); - w.println("

"); - w.println(""); - w.println(""); - w.println(""); - w.println("
"); + PrintWriter writer = resp.getWriter(); + writer.println(""); + writer.println(""); + writer.println( + "Asserting Identity to Google APIs - App Engine App Identity Example"); + writer.println("
"); + writer.println(""); + writer.println(""); + writer.println(""); + writer.println("
"); } @Override @@ -57,19 +59,19 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx } String shortUrl; - PrintWriter w = resp.getWriter(); + PrintWriter writer = resp.getWriter(); try { shortUrl = shortener.createShortUrl(longUrl); } catch (Exception e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - w.println("error shortening URL: " + longUrl); + writer.println("error shortening URL: " + longUrl); e.printStackTrace(w); return; } - w.print("long URL: "); - w.println(longUrl); - w.print("short URL: "); - w.println(shortUrl); + writer.print("long URL: "); + writer.println(longUrl); + writer.print("short URL: "); + writer.println(shortUrl); } } From abdf69a10535c08c3bdde5dd68a962d685fd88b2 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 21 Jan 2016 17:31:08 -0800 Subject: [PATCH 3/3] Style fixes for App Identity samples. --- .../appidentity/SignForAppServlet.java | 22 +++++++++---------- .../appidentity/UrlShortenerServlet.java | 4 ---- .../appidentity/IdentityServletTest.java | 11 +++++----- .../appidentity/SignForAppServletTest.java | 2 -- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java index 5cd1798a001..8a632aa78d5 100644 --- a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/SignForAppServlet.java @@ -1,39 +1,37 @@ -/** +/* * Copyright 2016 Google Inc. All Rights Reserved. * - *

Licensed under the Apache License, Version 2.0 (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 + * http://www.apache.org/licenses/LICENSE-2.0 * - *

Unless required by applicable law or agreed to in writing, software + * 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. */ + package com.example.appengine.appidentity; -import com.google.appengine.api.appidentity.PublicCertificate; import com.google.appengine.api.appidentity.AppIdentityService; import com.google.appengine.api.appidentity.AppIdentityServiceFactory; -import com.google.apphosting.api.ApiProxy; -import com.google.apphosting.api.ApiProxy.Environment; +import com.google.appengine.api.appidentity.PublicCertificate; import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.io.IOException; -import java.io.PrintWriter; +import java.io.InputStream; import java.io.UnsupportedEncodingException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.Arrays; import java.util.Collection; diff --git a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java index 2778ed2f325..aa355ad6f9a 100644 --- a/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java +++ b/appengine/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortenerServlet.java @@ -16,12 +16,8 @@ package com.example.appengine.appidentity; -import com.google.appengine.api.users.UserService; -import com.google.appengine.api.users.UserServiceFactory; - import java.io.IOException; import java.io.PrintWriter; -import java.net.URLDecoder; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; diff --git a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java index 7df6e871367..53fdebf79bf 100644 --- a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java +++ b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/IdentityServletTest.java @@ -1,22 +1,22 @@ -/** +/* * Copyright 2015 Google Inc. All Rights Reserved. * - *

Licensed under the Apache License, Version 2.0 (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 + * http://www.apache.org/licenses/LICENSE-2.0 * - *

Unless required by applicable law or agreed to in writing, software + * 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. */ + package com.example.appengine.appidentity; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.appengine.tools.development.testing.LocalServiceTestHelper; @@ -28,7 +28,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java index b245ef11e7b..c340def6b32 100644 --- a/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java +++ b/appengine/appidentity/src/test/java/com/example/appengine/appidentity/SignForAppServletTest.java @@ -16,7 +16,6 @@ package com.example.appengine.appidentity; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.appengine.tools.development.testing.LocalServiceTestHelper; @@ -28,7 +27,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.File; import java.io.PrintWriter; import java.io.StringWriter;