Merge lp:~nicolas-lunatech/play/play-ssl into lp:play/1.1
- play-ssl
- Merge into 1.1-unstable
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nicolas Leroux (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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' |
2 | Binary 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' |
351 | Binary 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----- |