Merge lp:~nicolas-lunatech/play/play-ssl into lp:play/1.1

Proposed by Nicolas Leroux
Status: Merged
Merged at revision: 1091
Proposed branch: lp:~nicolas-lunatech/play/play-ssl
Merge into: lp:play/1.1
Diff against target: 400 lines (+276/-8)
8 files modified
framework/src/play/server/HttpServerPipelineFactory.java (+1/-1)
framework/src/play/server/PlayHandler.java (+56/-1)
framework/src/play/server/Server.java (+11/-5)
framework/src/play/server/ssl/SslHttpServerContextFactory.java (+121/-0)
framework/src/play/server/ssl/SslHttpServerPipelineFactory.java (+45/-0)
samples-and-tests/forum/conf/application.conf (+2/-1)
samples-and-tests/forum/conf/host.cert (+22/-0)
samples-and-tests/forum/conf/host.key (+18/-0)
To merge this branch: bzr merge lp:~nicolas-lunatech/play/play-ssl
Reviewer Review Type Date Requested Status
Nicolas Leroux (community) Approve
Review via email: mp+35751@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Nicolas Leroux (nicolas-lunatech) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'framework/lib/bcprov-jdk16-145.jar'
2Binary files framework/lib/bcprov-jdk16-145.jar 1970-01-01 00:00:00 +0000 and framework/lib/bcprov-jdk16-145.jar 2010-09-16 22:21:44 +0000 differ
3=== modified file 'framework/src/play/server/HttpServerPipelineFactory.java'
4--- framework/src/play/server/HttpServerPipelineFactory.java 2010-09-16 20:29:16 +0000
5+++ framework/src/play/server/HttpServerPipelineFactory.java 2010-09-16 22:21:44 +0000
6@@ -22,7 +22,7 @@
7 pipeline.addLast("encoder", new HttpResponseEncoder());
8 pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
9
10- pipeline.addLast("handler", new PlayHandler());
11+ pipeline.addLast("handler", new PlayHandler(false));
12
13
14 return pipeline;
15
16=== modified file 'framework/src/play/server/PlayHandler.java'
17--- framework/src/play/server/PlayHandler.java 2010-09-16 20:52:26 +0000
18+++ framework/src/play/server/PlayHandler.java 2010-09-16 22:21:44 +0000
19@@ -7,6 +7,7 @@
20 import org.jboss.netty.buffer.ChannelBuffers;
21 import org.jboss.netty.channel.*;
22 import org.jboss.netty.handler.codec.http.*;
23+import org.jboss.netty.handler.ssl.SslHandler;
24 import org.jboss.netty.handler.stream.ChunkedFile;
25 import org.jboss.netty.handler.stream.ChunkedStream;
26 import play.Invoker;
27@@ -32,6 +33,7 @@
28 import play.vfs.VirtualFile;
29
30 import java.io.*;
31+import java.net.InetAddress;
32 import java.net.InetSocketAddress;
33 import java.net.URLDecoder;
34 import java.net.URLEncoder;
35@@ -40,11 +42,51 @@
36
37 import play.data.validation.Validation;
38
39+import javax.net.ssl.SSLException;
40+
41 import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
42
43 public class PlayHandler extends SimpleChannelUpstreamHandler {
44
45 private final static String signature = "Play! Framework;" + Play.version + ";" + Play.mode.name().toLowerCase();
46+ private boolean isSecure = false;
47+
48+ public PlayHandler(boolean isSecure) {
49+ this.isSecure = isSecure;
50+ }
51+
52+ @Override
53+ public void channelConnected(
54+ ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
55+ if (isSecure) {
56+ ctx.setAttachment(e.getValue());
57+ // Get the SslHandler in the current pipeline.
58+ final SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
59+ sslHandler.setEnableRenegotiation(false);
60+ // Get notified when SSL handshake is done.
61+ ChannelFuture handshakeFuture = sslHandler.handshake();
62+ handshakeFuture.addListener(new SslListener(sslHandler));
63+ }
64+ }
65+
66+ private static final class SslListener implements ChannelFutureListener {
67+
68+ private final SslHandler sslHandler;
69+
70+ SslListener(SslHandler sslHandler) {
71+ this.sslHandler = sslHandler;
72+ }
73+
74+ public void operationComplete(ChannelFuture future) throws Exception {
75+ if (!future.isSuccess()) {
76+ Logger.warn(future.getCause(), "Invalid certificate ");
77+
78+ if (future.getChannel().isOpen()) {
79+ future.getChannel().close();
80+ }
81+ }
82+ }
83+ }
84
85 @Override
86 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
87@@ -493,7 +535,20 @@
88 @Override
89 public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
90 throws Exception {
91- e.getChannel().close();
92+ Logger.error(e.getCause(), "");
93+ // We have to redirect to https://, as it was targeting http://
94+ // Redirect to the root as we don't know the url at that point
95+ if (e.getCause() instanceof javax.net.ssl.SSLException) {
96+ InetSocketAddress inet = ((InetSocketAddress) ctx.getAttachment());
97+ ctx.getPipeline().remove("ssl");
98+ HttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.TEMPORARY_REDIRECT);
99+ nettyResponse.setHeader(LOCATION, "https://" + inet.getHostName() + ":" + Play.configuration.getProperty("http.port") + "/");
100+ nettyResponse.setHeader(SERVER, signature);
101+ ChannelFuture writeFuture = ctx.getChannel().write(nettyResponse);
102+ writeFuture.addListener(ChannelFutureListener.CLOSE);
103+ } else {
104+ e.getChannel().close();
105+ }
106 }
107
108 public static void serve404(NotFound e, ChannelHandlerContext ctx, Request request, HttpRequest nettyRequest) {
109
110=== modified file 'framework/src/play/server/Server.java'
111--- framework/src/play/server/Server.java 2010-09-16 20:29:16 +0000
112+++ framework/src/play/server/Server.java 2010-09-16 22:21:44 +0000
113@@ -6,6 +6,7 @@
114 import play.Logger;
115 import play.Play;
116 import play.Play.Mode;
117+import play.server.ssl.SslHttpServerPipelineFactory;
118
119 import java.io.File;
120 import java.net.InetAddress;
121@@ -18,6 +19,7 @@
122 public Server() {
123 final Properties p = Play.configuration;
124 int httpPort = Integer.parseInt(p.getProperty("http.port", "9000"));
125+ boolean isSecure = Boolean.parseBoolean(p.getProperty("http.secure", "false"));
126 InetAddress address = null;
127 if (System.getProperties().containsKey("http.port")) {
128 httpPort = Integer.parseInt(System.getProperty("http.port"));
129@@ -35,12 +37,16 @@
130 }
131
132 ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
133- Executors.newCachedThreadPool(), Executors.newCachedThreadPool())
134+ Executors.newCachedThreadPool(), Executors.newCachedThreadPool())
135 );
136- try {
137- bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
138- bootstrap.bind(new InetSocketAddress(address, httpPort));
139- bootstrap.setOption("child.tcpNoDelay", true);
140+ try {
141+ if (isSecure) {
142+ bootstrap.setPipelineFactory(new SslHttpServerPipelineFactory());
143+ } else {
144+ bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
145+ }
146+ bootstrap.bind(new InetSocketAddress(address, httpPort));
147+ bootstrap.setOption("child.tcpNoDelay", true);
148
149 if (Play.mode == Mode.DEV) {
150 if (address == null) {
151
152=== added directory 'framework/src/play/server/ssl'
153=== added file 'framework/src/play/server/ssl/SslHttpServerContextFactory.java'
154--- framework/src/play/server/ssl/SslHttpServerContextFactory.java 1970-01-01 00:00:00 +0000
155+++ framework/src/play/server/ssl/SslHttpServerContextFactory.java 2010-09-16 22:21:44 +0000
156@@ -0,0 +1,121 @@
157+package play.server.ssl;
158+
159+import org.bouncycastle.openssl.PEMReader;
160+import org.bouncycastle.openssl.PasswordFinder;
161+import play.Logger;
162+import play.Play;
163+
164+import java.security.cert.X509Certificate;
165+import javax.net.ssl.*;
166+import java.io.FileInputStream;
167+import java.io.FileReader;
168+import java.io.IOException;
169+import java.net.Socket;
170+import java.security.*;
171+import java.util.Enumeration;
172+
173+public class SslHttpServerContextFactory {
174+
175+ private static final String PROTOCOL = "SSL";
176+ private static final SSLContext SERVER_CONTEXT;
177+
178+ static {
179+
180+ String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
181+ if (algorithm == null) {
182+ algorithm = "SunX509";
183+ }
184+
185+ SSLContext serverContext = null;
186+ KeyStore ks = null;
187+ try {
188+
189+ // Look if we have key and cert files. If we do, we use our own keymanager
190+ if (Play.getFile(System.getProperty("certificate.file", "conf/host.key")).exists() && Play.getFile(System.getProperty("certificate.file", "conf/host.cert")).exists()) {
191+ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
192+ } else {
193+ // Try to load it from the keystore
194+ ks = KeyStore.getInstance(System.getProperty("certificate.algorithm", "JKS"));
195+ // Load the file from the conf
196+ char[] certificatePassword = System.getProperty("certificate.password", "secret").toCharArray();
197+
198+ ks.load(new FileInputStream(Play.getFile(System.getProperty("certificate.file", "conf/certificate.jks"))),
199+ certificatePassword);
200+
201+ // Set up key manager factory to use our key store
202+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
203+ char[] keyStorePassword = System.getProperty("keystore.password", "secret").toCharArray();
204+
205+ kmf.init(ks, keyStorePassword);
206+
207+ }
208+ // Initialize the SSLContext to work with our key managers.
209+ serverContext = SSLContext.getInstance(PROTOCOL);
210+ serverContext.init(new KeyManager[]{PEMKeyManager.instance}, null, null);
211+
212+ } catch (Exception e) {
213+ throw new Error(
214+ "Failed to initialize the server-side SSLContext", e);
215+ }
216+
217+ SERVER_CONTEXT = serverContext;
218+ }
219+
220+ public static SSLContext getServerContext() {
221+ return SERVER_CONTEXT;
222+ }
223+
224+ public static class PEMKeyManager extends X509ExtendedKeyManager {
225+
226+ static PEMKeyManager instance = new PEMKeyManager();
227+ PrivateKey key;
228+ X509Certificate cert;
229+
230+ public PEMKeyManager() {
231+ try {
232+ PEMReader keyReader = new PEMReader(new FileReader(Play.getFile(System.getProperty("certificate.file", "conf/host.key"))), new PasswordFinder() {
233+ public char[] getPassword() {
234+ return System.getProperty("certificate.password", "secret").toCharArray();
235+ }
236+ });
237+ key = ((KeyPair) keyReader.readObject()).getPrivate();
238+
239+ PEMReader reader = new PEMReader(new FileReader(Play.getFile(System.getProperty("certificate.file", "conf/host.cert"))));
240+ cert = (X509Certificate) reader.readObject();
241+ } catch (Exception e) {
242+ e.printStackTrace();
243+ Logger.error(e, "");
244+ }
245+ }
246+
247+ public String chooseEngineServerAlias(java.lang.String s, java.security.Principal[] principals, javax.net.ssl.SSLEngine sslEngine) {
248+ return "";
249+ }
250+
251+
252+ public String[] getClientAliases(String s, Principal[] principals) {
253+ return new String[]{""};
254+ }
255+
256+ public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
257+ return "";
258+ }
259+
260+ public String[] getServerAliases(String s, Principal[] principals) {
261+ return new String[]{""};
262+ }
263+
264+ public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
265+ return "";
266+ }
267+
268+ public java.security.cert.X509Certificate[] getCertificateChain(String s) {
269+ return new java.security.cert.X509Certificate[]{cert};
270+ }
271+
272+ public PrivateKey getPrivateKey(String s) {
273+ return key;
274+ }
275+ }
276+
277+}
278
279=== added file 'framework/src/play/server/ssl/SslHttpServerPipelineFactory.java'
280--- framework/src/play/server/ssl/SslHttpServerPipelineFactory.java 1970-01-01 00:00:00 +0000
281+++ framework/src/play/server/ssl/SslHttpServerPipelineFactory.java 2010-09-16 22:21:44 +0000
282@@ -0,0 +1,45 @@
283+package play.server.ssl;
284+
285+import org.jboss.netty.channel.ChannelPipeline;
286+import org.jboss.netty.channel.ChannelPipelineFactory;
287+import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
288+import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
289+import org.jboss.netty.handler.ssl.SslHandler;
290+import org.jboss.netty.handler.stream.ChunkedWriteHandler;
291+import play.Logger;
292+import play.Play;
293+import play.server.PlayHandler;
294+import play.server.StreamChunkAggregator;
295+
296+import javax.net.ssl.SSLEngine;
297+
298+import static org.jboss.netty.channel.Channels.pipeline;
299+
300+public class SslHttpServerPipelineFactory implements ChannelPipelineFactory {
301+
302+ public ChannelPipeline getPipeline() throws Exception {
303+
304+ Integer max = Integer.valueOf(Play.configuration.getProperty("play.netty.maxContentLength", "-1"));
305+
306+ ChannelPipeline pipeline = pipeline();
307+
308+ // Add SSL handler first to encrypt and decrypt everything.
309+ SSLEngine engine =
310+ SslHttpServerContextFactory.getServerContext().createSSLEngine();
311+ engine.setUseClientMode(false);
312+ engine.setNeedClientAuth(true);
313+ engine.setWantClientAuth(true);
314+ engine.setEnableSessionCreation(true);
315+
316+ pipeline.addLast("ssl", new SslHandler(engine));
317+ pipeline.addLast("decoder", new HttpRequestDecoder());
318+ pipeline.addLast("aggregator", new StreamChunkAggregator(max));
319+ pipeline.addLast("encoder", new HttpResponseEncoder());
320+ pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
321+
322+ pipeline.addLast("handler", new PlayHandler(true));
323+
324+ return pipeline;
325+ }
326+}
327+
328
329=== modified file 'samples-and-tests/forum/conf/application.conf'
330--- samples-and-tests/forum/conf/application.conf 2010-09-16 20:29:16 +0000
331+++ samples-and-tests/forum/conf/application.conf 2010-09-16 22:21:44 +0000
332@@ -33,6 +33,7 @@
333 # If you need to change the HTTP port, uncomment this (default is set to 9000)
334 #
335 http.port=9000
336+#http.secure=true
337
338
339 # Database configuration
340@@ -72,7 +73,7 @@
341 # Specify log level for your application.
342 # If you want a very customized log, create a log4j.properties file in the conf directory
343 #
344-# application.log=INFO
345+application.log=TRACE
346
347
348 # JPA Configuration (hibernate)
349
350=== added file 'samples-and-tests/forum/conf/certificate.jks'
351Binary files samples-and-tests/forum/conf/certificate.jks 1970-01-01 00:00:00 +0000 and samples-and-tests/forum/conf/certificate.jks 2010-09-16 22:21:44 +0000 differ
352=== added file 'samples-and-tests/forum/conf/host.cert'
353--- samples-and-tests/forum/conf/host.cert 1970-01-01 00:00:00 +0000
354+++ samples-and-tests/forum/conf/host.cert 2010-09-16 22:21:44 +0000
355@@ -0,0 +1,22 @@
356+-----BEGIN CERTIFICATE-----
357+MIIDkDCCAvmgAwIBAgIJAL53x0ZWC+dlMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD
358+VQQGEwJOTDEMMAoGA1UECBMDYmxhMRIwEAYDVQQHEwlSb3R0ZXJkYW0xDzANBgNV
359+BAoTBkJsIGJsYTENMAsGA1UECxMEYmxvZTEXMBUGA1UEAxMOTmljb2xhcyBMZXJv
360+dXgxIzAhBgkqhkiG9w0BCQEWFG5pY29sYXNAbHVuYXRlY2guY29tMB4XDTEwMDkx
361+NjE5MTc0OVoXDTExMDkxNjE5MTc0OVowgY0xCzAJBgNVBAYTAk5MMQwwCgYDVQQI
362+EwNibGExEjAQBgNVBAcTCVJvdHRlcmRhbTEPMA0GA1UEChMGQmwgYmxhMQ0wCwYD
363+VQQLEwRibG9lMRcwFQYDVQQDEw5OaWNvbGFzIExlcm91eDEjMCEGCSqGSIb3DQEJ
364+ARYUbmljb2xhc0BsdW5hdGVjaC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
365+AoGBANqoWYHygUWuXdPqr+ylzMQ6hsOHZrTnlIzMLBCML6cKNF5/UXrJi2balxRY
366+X3qtqiybqb892DePrPeqf5YkKQFhK3X36gja7MLevQE33aVyHUL3vk0ukap+Imlx
367+qyU8vP+XYqLsFNeNsZk5ja8CFGJNIDvewcan9lbF2ORTVxs7AgMBAAGjgfUwgfIw
368+HQYDVR0OBBYEFJFi+d3AofaeS9i0a4yI7ido/i2eMIHCBgNVHSMEgbowgbeAFJFi
369++d3AofaeS9i0a4yI7ido/i2eoYGTpIGQMIGNMQswCQYDVQQGEwJOTDEMMAoGA1UE
370+CBMDYmxhMRIwEAYDVQQHEwlSb3R0ZXJkYW0xDzANBgNVBAoTBkJsIGJsYTENMAsG
371+A1UECxMEYmxvZTEXMBUGA1UEAxMOTmljb2xhcyBMZXJvdXgxIzAhBgkqhkiG9w0B
372+CQEWFG5pY29sYXNAbHVuYXRlY2guY29tggkAvnfHRlYL52UwDAYDVR0TBAUwAwEB
373+/zANBgkqhkiG9w0BAQUFAAOBgQAn4iUXzZNpJ2SSBJaMLxWUlYlERFtAuTCh2yYy
374+50YwrpzwupTZuC0IkMivY7YOvuSi5yRG+Ez83D28HNH+5OwVqpcecUuEjmUw96NN
375+XysBNWf9uCYZhCizCxHcX6I+96T8y2jOcq9fdY3nc5YV6uCe4flnO1HbZ1QJeTqv
376+UzjByw==
377+-----END CERTIFICATE-----
378
379=== added file 'samples-and-tests/forum/conf/host.key'
380--- samples-and-tests/forum/conf/host.key 1970-01-01 00:00:00 +0000
381+++ samples-and-tests/forum/conf/host.key 2010-09-16 22:21:44 +0000
382@@ -0,0 +1,18 @@
383+-----BEGIN RSA PRIVATE KEY-----
384+Proc-Type: 4,ENCRYPTED
385+DEK-Info: DES-EDE3-CBC,60E301A3F6B3843F
386+
387+5NL1bfRscfkczeOCrv9sbPXN3na9L1Vsy3OoV7GVkzalfO8HolUR8GewhDhhX5w5
388+oAis7OPBA7045taH4fzWC0hWxwfHJkFvXTHmQrf6u8hRe7b6pkfmHDPQauFFL1eS
389+LRtEotL9naVLZSGycE/tmoYOEy9xEhTzdM4G+uB3BpGA5e/JiibiEBxgoUWnLJBr
390+vN98BH4PuN8eUMntInOWiJbn7Lo0+YuNQo2kUYsIZ2rLlTw8FVQKLfm9Ekz7iw55
391+Zh4+b+S+fIpn4YSy2Fafzivq9mbWyzMzi0fSRyP8FZ/TeP7MAbj/D/uaVZFGD65l
392+gkX9mEmt1FF9J4DEukkKVrXljbHhXQu87SYKE717GLWBeK4U34G+KkiiRlK9iba2
393+x7bEki4PvFkf83j9KjhPUNiS1wzWJWG4JDz1tXIwrbCYPatldtWs9k3qcJgwXjDo
394+FbC4ccW6qbcBp8fmsaoOKM0ImN9jHS8TaGUpZhyWAf3b4usGAri5759O9e4sEdu3
395+cCV7m9OeZgRXGXo4odo32MB6IDg/BtfvtFgee0rIBrv6GbuH13pm5N4tQPTg2vg0
396+66VULPpIcxZ5Qnd8yICtLJ4av/EBjCXZircy5g1V3WmbphDFb+WcVJTrnb76XsbB
397+7Q8SFH6v7ug5JgF3tyv8EoRV7sKGk/hvBadu2gXuw7h9+UqFUbXjWHzbq1Hb9aaG
398+5zpURtV/d7vYMjpiImJQre3oMFDXGgHUcH8iFE1fiz0uvGzJlZ4H7nX9bpxamWpx
399+j60x+wr49SyoxurrYnzSLpjNEurocn91lO9CmtfcksoLXwXxsBDKQA==
400+-----END RSA PRIVATE KEY-----

Subscribers

People subscribed via source and target branches