embulk-input-ftp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84b6cf71034980db6a9d4f78d5affdaae19aa792
4
- data.tar.gz: 104382478ca9b24c2c08cc839637083a7540b715
3
+ metadata.gz: 4777eb105f91266e7be9329f4fe7c275e3aa4753
4
+ data.tar.gz: 1d797e96b71babe96627dd68291d7b8d2d046106
5
5
  SHA512:
6
- metadata.gz: c98936c59b2c4ea47cb394462a6ab5df1a2d9fa1f4223d2c26ac9d75efde5746dac49267a668bd95657c5b19a797195a573a9984d7cdb6964725fc74cc1ade18
7
- data.tar.gz: 035fbcd64345982d6370c3e38c607121e5b97a27daf26daf9274f4de441f5a10c4a61fa667120f6d0bbed0d2854a4983c3bb701428cd12d1fb832d4e7b84ddcc
6
+ metadata.gz: aa87efee4fb5dc309366b4394a958340941f704dbd03e1cbdf4c5a29419b70a87422163c8e82e5b7d4bc2b18d1a6cd6ad1eba62aa626aab9f8c12ef8703fe03a
7
+ data.tar.gz: 2794aec32f1da6644d305d585d1fda1e18a60325d1c3dc561b6b6e5767c471a21c8c0e215d363667afcdbfa325c4beebc8ea6bcd4366691422c1ed1a970777b5
data/ChangeLog CHANGED
@@ -1,9 +1,15 @@
1
1
 
2
+ Release 0.1.2 - 2015-06-10
3
+
4
+ * Added support for SSL certificate verification
5
+
6
+
2
7
  Release 0.1.1 - 2015-04-29
3
8
 
4
9
  * Added support for SSL
5
10
  * Fixed last_path handling
6
11
 
12
+
7
13
  Release 0.1.0 - 2015-04-29
8
14
 
9
15
  * First release
data/README.md CHANGED
@@ -16,10 +16,26 @@
16
16
  - **passive_mode**: use passive mode (boolean, default: true)
17
17
  - **ascii_mode**: use ASCII mode instead of binary mode (boolean, default: false)
18
18
  - **ssl**: use FTPS (SSL encryption). (boolean, default: false)
19
- - Currently, it doesn't support certificate checking. Any certificate given by the remote host is trusted.
19
+ - **ssl_verify**: verify the certification provided by the server. By default, connection fails if the server certification is not signed by one the CAs in JVM's default trusted CA list. (boolean, default: true)
20
+ - **ssl_verify_hostname**: verify server's hostname matches with provided certificate. (boolean, default: true)
21
+ - **ssl_trusted_ca_cert_file**: if the server certification is not signed by a certificate authority, set path to the X.508 certification file (pem file) of a private CA (string, optional)
22
+ - **ssl_trusted_ca_cert_data**: similar to `ssl_trusted_ca_cert_file` but embed the contents of the PEM file as a string value instead of path to a local file (string, optional)
20
23
 
21
24
  ## Example
22
25
 
26
+ Simple FTP:
27
+
28
+ ```yaml
29
+ in:
30
+ type: ftp
31
+ host: ftp.example.net
32
+ port: 21
33
+ user: anonymous
34
+ path_prefix: /ftp/file/path/prefix
35
+ ```
36
+
37
+ FTPS encryption without server certificate verification:
38
+
23
39
  ```yaml
24
40
  in:
25
41
  type: ftp
@@ -28,6 +44,37 @@ in:
28
44
  user: anonymous
29
45
  password: "mypassword"
30
46
  path_prefix: /ftp/file/path/prefix
47
+
48
+ ssl: true
49
+ ssl_verify: false
50
+ ```
51
+
52
+ FTPS encryption with server certificate verification:
53
+
54
+ ```yaml
55
+ in:
56
+ type: ftp
57
+ host: ftp.example.net
58
+ port: 21
59
+ user: anonymous
60
+ password: "mypassword"
61
+ path_prefix: /ftp/file/path/prefix
62
+
63
+ ssl: true
64
+ ssl_verify: true
65
+
66
+ ssl_verify_hostname: false # to disable server hostname verification (optional)
67
+
68
+ # if the server use self-signed certificate, or set path to the pem file (optional)
69
+ ssl_trusted_ca_cert_file: /path/to/ca_cert.pem
70
+
71
+ # or embed contents of the pem file here (optional)
72
+ ssl_trusted_ca_cert_data: |
73
+ -----BEGIN CERTIFICATE-----
74
+ MIIFV...
75
+ ...
76
+ ...
77
+ -----END CERTIFICATE-----
31
78
  ```
32
79
 
33
80
  ## Build
data/build.gradle CHANGED
@@ -12,12 +12,13 @@ configurations {
12
12
  provided
13
13
  }
14
14
 
15
- version = "0.1.1"
15
+ version = "0.1.2"
16
16
 
17
17
  dependencies {
18
- compile "org.embulk:embulk-core:0.6.5"
19
- provided "org.embulk:embulk-core:0.6.5"
18
+ compile "org.embulk:embulk-core:0.6.8"
19
+ provided "org.embulk:embulk-core:0.6.8"
20
20
  compile files("libs/ftp4j-1.7.2.jar")
21
+ compile "org.bouncycastle:bcpkix-jdk15on:1.52"
21
22
  testCompile "junit:junit:4.+"
22
23
  }
23
24
 
@@ -9,14 +9,6 @@ import java.io.IOException;
9
9
  import java.io.InterruptedIOException;
10
10
  import java.io.InputStream;
11
11
  import java.nio.channels.Channels;
12
- import java.security.SecureRandom;
13
- import java.security.KeyManagementException;
14
- import java.security.NoSuchAlgorithmException;
15
- import java.security.cert.X509Certificate;
16
- import javax.net.ssl.SSLContext;
17
- import javax.net.ssl.SSLSocketFactory;
18
- import javax.net.ssl.TrustManager;
19
- import javax.net.ssl.X509TrustManager;
20
12
  import org.slf4j.Logger;
21
13
  import com.google.common.util.concurrent.ThreadFactoryBuilder;
22
14
  import com.google.common.collect.ImmutableList;
@@ -46,11 +38,14 @@ import org.embulk.spi.Exec;
46
38
  import org.embulk.spi.FileInputPlugin;
47
39
  import org.embulk.spi.TransactionalFileInput;
48
40
  import org.embulk.spi.util.InputStreamFileInput;
41
+ import org.embulk.spi.util.ResumableInputStream;
42
+ import org.embulk.spi.util.RetryExecutor.Retryable;
43
+ import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
49
44
  import org.embulk.input.ftp.BlockingTransfer;
50
- import org.embulk.input.ftp.RetryableInputStream;
51
- import org.embulk.input.ftp.RetryExecutor.Retryable;
52
- import org.embulk.input.ftp.RetryExecutor.RetryGiveupException;
53
- import static org.embulk.input.ftp.RetryExecutor.retryExecutor;
45
+ import org.embulk.input.ftp.SSLPlugins;
46
+ import org.embulk.input.ftp.SSLPlugins.SSLPluginTask;
47
+ import org.embulk.input.ftp.SSLPlugins.SSLPluginConfig;
48
+ import static org.embulk.spi.util.RetryExecutor.retryExecutor;
54
49
 
55
50
  public class FtpFileInputPlugin
56
51
  implements FileInputPlugin
@@ -58,7 +53,7 @@ public class FtpFileInputPlugin
58
53
  private final Logger log = Exec.getLogger(FtpFileInputPlugin.class);
59
54
 
60
55
  public interface PluginTask
61
- extends Task
56
+ extends Task, SSLPlugins.SSLPluginTask
62
57
  {
63
58
  @Config("path_prefix")
64
59
  public String getPathPrefix();
@@ -97,6 +92,9 @@ public class FtpFileInputPlugin
97
92
  public List<String> getFiles();
98
93
  public void setFiles(List<String> files);
99
94
 
95
+ public SSLPluginConfig getSSLConfig();
96
+ public void setSSLConfig(SSLPluginConfig config);
97
+
100
98
  @ConfigInject
101
99
  public BufferAllocator getBufferAllocator();
102
100
  }
@@ -106,6 +104,8 @@ public class FtpFileInputPlugin
106
104
  {
107
105
  PluginTask task = config.loadConfig(PluginTask.class);
108
106
 
107
+ task.setSSLConfig(SSLPlugins.configure(task));
108
+
109
109
  // list files recursively
110
110
  List<String> files = listFiles(log, task);
111
111
  task.setFiles(files);
@@ -157,7 +157,7 @@ public class FtpFileInputPlugin
157
157
  FTPClient client = new FTPClient();
158
158
  try {
159
159
  if (task.getSsl()) {
160
- client.setSSLSocketFactory(newSSLSocketFactory(task));
160
+ client.setSSLSocketFactory(SSLPlugins.newSSLSocketFactory(task.getSSLConfig(), task.getHost()));
161
161
  client.setSecurity(FTPClient.SECURITY_FTPS);
162
162
  }
163
163
 
@@ -242,40 +242,6 @@ public class FtpFileInputPlugin
242
242
  }
243
243
  }
244
244
 
245
- private static SSLSocketFactory newSSLSocketFactory(PluginTask task)
246
- {
247
- // TODO certificate check
248
-
249
- TrustManager[] trustManager = new TrustManager[] {
250
- new X509TrustManager() {
251
- public X509Certificate[] getAcceptedIssuers()
252
- {
253
- return null;
254
- }
255
-
256
- public void checkClientTrusted(X509Certificate[] certs, String authType)
257
- {
258
- }
259
-
260
- public void checkServerTrusted(X509Certificate[] certs, String authType)
261
- {
262
- }
263
- }
264
- };
265
-
266
- try {
267
- SSLContext context = SSLContext.getInstance("TLS");
268
- context.init(null, trustManager, new SecureRandom());
269
- return context.getSocketFactory();
270
-
271
- } catch (NoSuchAlgorithmException ex) {
272
- throw new RuntimeException(ex);
273
-
274
- } catch (KeyManagementException ex) {
275
- throw new RuntimeException(ex);
276
- }
277
- }
278
-
279
245
  private List<String> listFiles(Logger log, PluginTask task)
280
246
  {
281
247
  FTPClient client = newFTPClient(log, task);
@@ -507,15 +473,15 @@ public class FtpFileInputPlugin
507
473
  return Channels.newInputStream(t.getReaderChannel());
508
474
  }
509
475
 
510
- private static class FtpRetryableOpener
511
- implements RetryableInputStream.Opener
476
+ private static class FtpInputStreamReopener
477
+ implements ResumableInputStream.Reopener
512
478
  {
513
479
  private final Logger log;
514
480
  private final FTPClient client;
515
481
  private final ExecutorService executor;
516
482
  private final String path;
517
483
 
518
- public FtpRetryableOpener(Logger log, FTPClient client, ExecutorService executor, String path)
484
+ public FtpInputStreamReopener(Logger log, FTPClient client, ExecutorService executor, String path)
519
485
  {
520
486
  this.log = log;
521
487
  this.client = client;
@@ -524,7 +490,7 @@ public class FtpFileInputPlugin
524
490
  }
525
491
 
526
492
  @Override
527
- public InputStream open(final long offset, final Exception exception) throws IOException
493
+ public InputStream reopen(final long offset, final Exception closedCause) throws IOException
528
494
  {
529
495
  try {
530
496
  return retryExecutor()
@@ -535,7 +501,7 @@ public class FtpFileInputPlugin
535
501
  @Override
536
502
  public InputStream call() throws InterruptedIOException
537
503
  {
538
- log.warn(String.format("FTP read failed. Retrying GET request with %,d bytes offset", offset), exception);
504
+ log.warn(String.format("FTP read failed. Retrying GET request with %,d bytes offset", offset), closedCause);
539
505
  return startDownload(log, client, path, offset, executor);
540
506
  }
541
507
 
@@ -603,9 +569,9 @@ public class FtpFileInputPlugin
603
569
  }
604
570
  opened = true;
605
571
 
606
- return new RetryableInputStream(
572
+ return new ResumableInputStream(
607
573
  startDownload(log, client, path, 0L, executor),
608
- new FtpRetryableOpener(log, client, executor, path));
574
+ new FtpInputStreamReopener(log, client, executor, path));
609
575
  }
610
576
 
611
577
  @Override
@@ -0,0 +1,245 @@
1
+ package org.embulk.input.ftp;
2
+
3
+ import java.util.List;
4
+ import java.io.Reader;
5
+ import java.io.FileReader;
6
+ import java.io.StringReader;
7
+ import java.io.ByteArrayInputStream;
8
+ import java.io.IOException;
9
+ import java.io.FileNotFoundException;
10
+ import java.security.KeyStore;
11
+ import java.security.NoSuchAlgorithmException;
12
+ import java.security.GeneralSecurityException;
13
+ import java.security.cert.CertificateFactory;
14
+ import java.security.cert.X509Certificate;
15
+ import java.security.cert.CertificateException;
16
+ import java.security.cert.CertificateEncodingException;
17
+ import java.security.KeyManagementException;
18
+ import javax.net.ssl.SSLSocketFactory;
19
+ import javax.net.ssl.X509TrustManager;
20
+ import com.google.common.base.Optional;
21
+ import com.google.common.base.Function;
22
+ import com.google.common.collect.ImmutableList;
23
+ import com.google.common.collect.Lists;
24
+ import com.fasterxml.jackson.annotation.JsonCreator;
25
+ import com.fasterxml.jackson.annotation.JsonProperty;
26
+ import com.fasterxml.jackson.annotation.JsonIgnore;
27
+ import org.embulk.config.Config;
28
+ import org.embulk.config.ConfigDefault;
29
+ import org.embulk.config.ConfigException;
30
+
31
+ public class SSLPlugins
32
+ {
33
+ // SSLPlugins is only for SSL clients. SSL server implementation is out ouf scope.
34
+
35
+ public interface SSLPluginTask
36
+ {
37
+ @Config("ssl_verify")
38
+ @ConfigDefault("null")
39
+ public Optional<Boolean> getSslVerify();
40
+
41
+ @Config("ssl_verify_hostname")
42
+ @ConfigDefault("true")
43
+ public boolean getSslVerifyHostname();
44
+
45
+ @Config("ssl_trusted_ca_cert_file")
46
+ @ConfigDefault("null")
47
+ public Optional<String> getSslTrustedCaCertFile();
48
+
49
+ @Config("ssl_trusted_ca_cert_data")
50
+ @ConfigDefault("null")
51
+ public Optional<String> getSslTrustedCaCertData();
52
+ }
53
+
54
+ private static enum VerifyMode
55
+ {
56
+ NO_VERIFY,
57
+ CERTIFICATES,
58
+ JVM_DEFAULT;
59
+ }
60
+
61
+ public static class SSLPluginConfig
62
+ {
63
+ static SSLPluginConfig NO_VERIFY = new SSLPluginConfig(VerifyMode.NO_VERIFY, false, ImmutableList.<byte[]>of());
64
+
65
+ private final VerifyMode verifyMode;
66
+ private final boolean verifyHostname;
67
+ private final List<X509Certificate> certificates;
68
+
69
+ @JsonCreator
70
+ private SSLPluginConfig(
71
+ @JsonProperty("verifyMode") VerifyMode verifyMode,
72
+ @JsonProperty("verifyHostname") boolean verifyHostname,
73
+ @JsonProperty("certificates") List<byte[]> certificates)
74
+ {
75
+ this.verifyMode = verifyMode;
76
+ this.verifyHostname = verifyHostname;
77
+ this.certificates = ImmutableList.copyOf(
78
+ Lists.transform(certificates, new Function<byte[], X509Certificate>() {
79
+ public X509Certificate apply(byte[] data)
80
+ {
81
+ try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
82
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
83
+ return (X509Certificate) cf.generateCertificate(in);
84
+ } catch (IOException | CertificateException ex) {
85
+ throw new RuntimeException(ex);
86
+ }
87
+ }
88
+ })
89
+ );
90
+ }
91
+
92
+ SSLPluginConfig(List<X509Certificate> certificates, boolean verifyHostname)
93
+ {
94
+ this.verifyMode = VerifyMode.CERTIFICATES;
95
+ this.verifyHostname = verifyHostname;
96
+ this.certificates = certificates;
97
+ }
98
+
99
+ static SSLPluginConfig useJvmDefault(boolean verifyHostname)
100
+ {
101
+ return new SSLPluginConfig(VerifyMode.JVM_DEFAULT, verifyHostname, ImmutableList.<byte[]>of());
102
+ }
103
+
104
+ @JsonProperty("verifyMode")
105
+ private VerifyMode getVerifyMode()
106
+ {
107
+ return verifyMode;
108
+ }
109
+
110
+ @JsonProperty("verifyHostname")
111
+ private boolean getVerifyHostname()
112
+ {
113
+ return verifyHostname;
114
+ }
115
+
116
+ @JsonProperty("certificates")
117
+ private List<byte[]> getCertData()
118
+ {
119
+ return Lists.transform(certificates, new Function<X509Certificate, byte[]>() {
120
+ public byte[] apply(X509Certificate cert)
121
+ {
122
+ try {
123
+ return cert.getEncoded();
124
+ } catch (CertificateEncodingException ex) {
125
+ throw new RuntimeException(ex);
126
+ }
127
+ }
128
+ });
129
+ }
130
+
131
+ @JsonIgnore
132
+ public X509TrustManager[] newTrustManager()
133
+ {
134
+ try {
135
+ switch (verifyMode) {
136
+ case NO_VERIFY:
137
+ return new X509TrustManager[] { getNoVerifyTrustManager() };
138
+ case CERTIFICATES:
139
+ return TrustManagers.newTrustManager(certificates);
140
+ default: // JVM_DEFAULT
141
+ return TrustManagers.newDefaultJavaTrustManager();
142
+ }
143
+ } catch (IOException | GeneralSecurityException ex) {
144
+ throw new RuntimeException(ex);
145
+ }
146
+ }
147
+ }
148
+
149
+ public static enum DefaultVerifyMode
150
+ {
151
+ VERIFY_BY_JVM_TRUSTED_CA_CERTS,
152
+ NO_VERIFY;
153
+ };
154
+
155
+ public static SSLPluginConfig configure(SSLPluginTask task)
156
+ {
157
+ return configure(task, DefaultVerifyMode.VERIFY_BY_JVM_TRUSTED_CA_CERTS);
158
+ }
159
+
160
+ public static SSLPluginConfig configure(SSLPluginTask task, DefaultVerifyMode defaultVerifyMode)
161
+ {
162
+ boolean verify = task.getSslVerify().or(defaultVerifyMode != DefaultVerifyMode.NO_VERIFY);
163
+ if (verify) {
164
+ Optional<List<X509Certificate>> certs = readTrustedCertificates(task);
165
+ if (certs.isPresent()) {
166
+ return new SSLPluginConfig(certs.get(), task.getSslVerifyHostname());
167
+ } else {
168
+ return SSLPluginConfig.useJvmDefault(task.getSslVerifyHostname());
169
+ }
170
+ } else {
171
+ return SSLPluginConfig.NO_VERIFY;
172
+ }
173
+ }
174
+
175
+ private static Optional<List<X509Certificate>> readTrustedCertificates(SSLPluginTask task)
176
+ {
177
+ String optionName;
178
+ Reader reader;
179
+ if (task.getSslTrustedCaCertData().isPresent()) {
180
+ optionName = "ssl_trusted_ca_cert_data";
181
+ reader = new StringReader(task.getSslTrustedCaCertData().get());
182
+ } else if (task.getSslTrustedCaCertFile().isPresent()) {
183
+ optionName = "ssl_trusted_ca_cert_file '" + task.getSslTrustedCaCertFile().get() + "'";
184
+ try {
185
+ reader = new FileReader(task.getSslTrustedCaCertFile().get());
186
+ } catch (IOException ex) {
187
+ throw new ConfigException("Failed to open "+optionName, ex);
188
+ }
189
+ } else {
190
+ return Optional.absent();
191
+ }
192
+
193
+ List<X509Certificate> certs;
194
+ try (Reader r = reader) {
195
+ certs = TrustManagers.readPemEncodedX509Certificates(r);
196
+ if (certs.isEmpty()) {
197
+ throw new ConfigException(optionName + " does not include valid X.509 PEM certificates");
198
+ }
199
+ } catch (CertificateException | IOException ex) {
200
+ throw new ConfigException("Failed to read "+optionName, ex);
201
+ }
202
+
203
+ return Optional.of(certs);
204
+ }
205
+
206
+ public static SSLSocketFactory newSSLSocketFactory(SSLPluginConfig config, String hostname)
207
+ {
208
+ try {
209
+ return TrustManagers.newSSLSocketFactory(
210
+ null, // TODO sending client certificate is not implemented yet
211
+ config.newTrustManager(),
212
+ config.getVerifyHostname() ? hostname : null);
213
+ } catch (KeyManagementException ex) {
214
+ throw new RuntimeException(ex);
215
+ }
216
+ }
217
+
218
+ private static class NoVerifyTrustManager
219
+ implements X509TrustManager
220
+ {
221
+ static final NoVerifyTrustManager INSTANCE = new NoVerifyTrustManager();
222
+
223
+ private NoVerifyTrustManager()
224
+ { }
225
+
226
+ @Override
227
+ public X509Certificate[] getAcceptedIssuers()
228
+ {
229
+ return null;
230
+ }
231
+
232
+ @Override
233
+ public void checkClientTrusted(X509Certificate[] certs, String authType)
234
+ { }
235
+
236
+ @Override
237
+ public void checkServerTrusted(X509Certificate[] certs, String authType)
238
+ { }
239
+ }
240
+
241
+ private static X509TrustManager getNoVerifyTrustManager()
242
+ {
243
+ return NoVerifyTrustManager.INSTANCE;
244
+ }
245
+ }
@@ -0,0 +1,276 @@
1
+ package org.embulk.input.ftp;
2
+
3
+ import java.util.List;
4
+ import java.util.ArrayList;
5
+ import java.io.File;
6
+ import java.io.FileInputStream;
7
+ import java.io.Reader;
8
+ import java.io.IOException;
9
+ import java.net.Socket;
10
+ import java.net.InetAddress;
11
+ import java.net.UnknownHostException;
12
+ import java.security.KeyStore;
13
+ import java.security.SecureRandom;
14
+ import java.security.KeyStoreException;
15
+ import java.security.KeyManagementException;
16
+ import java.security.NoSuchAlgorithmException;
17
+ import java.security.InvalidAlgorithmParameterException;
18
+ import java.security.cert.Certificate;
19
+ import java.security.cert.TrustAnchor;
20
+ import java.security.cert.PKIXParameters;
21
+ import java.security.cert.X509Certificate;
22
+ import java.security.cert.CertificateException;
23
+ import java.security.cert.CertificateParsingException;
24
+ import javax.net.ssl.SSLContext;
25
+ import javax.net.ssl.SSLSession;
26
+ import javax.net.ssl.SSLParameters;
27
+ import javax.net.ssl.TrustManager;
28
+ import javax.net.ssl.KeyManager;
29
+ import javax.net.ssl.X509TrustManager;
30
+ import javax.net.ssl.TrustManagerFactory;
31
+ import javax.net.ssl.SSLSocket;
32
+ import javax.net.ssl.SSLSocketFactory;
33
+ import javax.net.ssl.HostnameVerifier;
34
+ import org.bouncycastle.openssl.PEMParser;
35
+ import org.bouncycastle.openssl.PEMException;
36
+ import org.bouncycastle.cert.X509CertificateHolder;
37
+ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
38
+ import sun.security.ssl.SSLSocketImpl;
39
+
40
+ public class TrustManagers
41
+ {
42
+ public static KeyStore readDefaultJavaKeyStore()
43
+ throws IOException, KeyStoreException, CertificateException
44
+ {
45
+ String path = (System.getProperty("java.home") + "/lib/security/cacerts").replace('/', File.separatorChar);
46
+ try {
47
+ KeyStore keyStore = KeyStore.getInstance("JKS");
48
+ try (FileInputStream in = new FileInputStream(path)) {
49
+ keyStore.load(in, null); // password=null because cacerts file is not encrypted
50
+ }
51
+ return keyStore;
52
+ } catch (NoSuchAlgorithmException ex) {
53
+ throw new RuntimeException(ex); // TODO assertion exception?
54
+ }
55
+ }
56
+
57
+ public static List<X509Certificate> readDefaultJavaTrustedCertificates()
58
+ throws IOException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException
59
+ {
60
+ KeyStore keyStore = readDefaultJavaKeyStore();
61
+ PKIXParameters params = new PKIXParameters(keyStore);
62
+ List<X509Certificate> certs = new ArrayList<>();
63
+ for (TrustAnchor trustAnchor : params.getTrustAnchors() ) {
64
+ certs.add(trustAnchor.getTrustedCert());
65
+ }
66
+ return certs;
67
+ }
68
+
69
+ public static List<X509Certificate> readPemEncodedX509Certificates(Reader reader)
70
+ throws IOException, CertificateException
71
+ {
72
+ // this method abuses CertificateParsingException because its javadoc says
73
+ // CertificateParsingException is only for DER-encoded formats.
74
+
75
+ JcaX509CertificateConverter conv = new JcaX509CertificateConverter();
76
+ List<X509Certificate> certs = new ArrayList<>();
77
+
78
+ try {
79
+ PEMParser pemParser = new PEMParser(reader);
80
+ // PEMParser#close is unnecessary because it just closes underlying reader
81
+
82
+ while (true) {
83
+ Object pem = pemParser.readObject();
84
+
85
+ if (pem == null) {
86
+ break;
87
+ }
88
+
89
+ if (pem instanceof X509CertificateHolder) {
90
+ X509Certificate cert = conv.getCertificate((X509CertificateHolder) pem);
91
+ certs.add(cert);
92
+ }
93
+ }
94
+
95
+ } catch (PEMException ex) {
96
+ // throw when parsing PemObject to Object fails
97
+ throw new CertificateParsingException(ex);
98
+
99
+ } catch (IOException ex) {
100
+ if (ex.getClass().equals(IOException.class)) {
101
+ String message = ex.getMessage();
102
+ if (message.startsWith("unrecognised object: ")) {
103
+ // thrown at org.bouncycastle.openssl.PemParser.readObject when key type (header of a pem) is
104
+ // unknown.
105
+ throw new CertificateParsingException(ex);
106
+ } else if (message.startsWith("-----END ") && message.endsWith(" not found")) {
107
+ // thrown at org.bouncycastle.util.io.pem.PemReader.loadObject when a pem file format is invalid
108
+ throw new CertificateParsingException(ex);
109
+ }
110
+ } else {
111
+ throw ex;
112
+ }
113
+ }
114
+
115
+ return certs;
116
+ }
117
+
118
+ public static KeyStore buildKeyStoreFromTrustedCertificates(List<X509Certificate> certificates)
119
+ throws KeyStoreException
120
+ {
121
+ KeyStore keyStore = KeyStore.getInstance("JKS");
122
+ try {
123
+ keyStore.load(null);
124
+ } catch (IOException | CertificateException | NoSuchAlgorithmException ex) {
125
+ throw new RuntimeException(ex);
126
+ }
127
+ int i = 0;
128
+ for (X509Certificate cert : certificates) {
129
+ keyStore.setCertificateEntry("cert_" + i, cert);
130
+ i++;
131
+ }
132
+ return keyStore;
133
+ }
134
+
135
+ public static X509TrustManager[] newTrustManager(List<X509Certificate> trustedCertificates)
136
+ throws KeyStoreException
137
+ {
138
+ try {
139
+ TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
140
+ KeyStore keyStore = buildKeyStoreFromTrustedCertificates(trustedCertificates);
141
+ factory.init(keyStore);
142
+ List<X509TrustManager> tms = new ArrayList<>();
143
+ for (TrustManager tm : factory.getTrustManagers()) {
144
+ if (tm instanceof X509TrustManager) {
145
+ tms.add((X509TrustManager) tm);
146
+ }
147
+ }
148
+ return tms.toArray(new X509TrustManager[tms.size()]);
149
+ } catch (NoSuchAlgorithmException ex) {
150
+ throw new RuntimeException(ex); // TODO assertion exception?
151
+ }
152
+ }
153
+
154
+ public static X509TrustManager[] newDefaultJavaTrustManager()
155
+ throws IOException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException
156
+ {
157
+ return newTrustManager(readDefaultJavaTrustedCertificates());
158
+ }
159
+
160
+ public static SSLContext newSSLContext(KeyManager[] keyManager, X509TrustManager[] trustManager)
161
+ throws KeyManagementException
162
+ {
163
+ try {
164
+ SSLContext context = SSLContext.getInstance("TLS");
165
+ context.init(
166
+ keyManager,
167
+ trustManager,
168
+ new SecureRandom());
169
+ return context;
170
+
171
+ } catch (NoSuchAlgorithmException ex) {
172
+ throw new RuntimeException(ex);
173
+ }
174
+ }
175
+
176
+ public static SSLSocketFactory newSSLSocketFactory(KeyManager[] keyManager, X509TrustManager[] trustManager, String verifyHostname)
177
+ throws KeyManagementException
178
+ {
179
+ SSLContext context = newSSLContext(keyManager, trustManager);
180
+ SSLSocketFactory factory = context.getSocketFactory();
181
+ if (verifyHostname == null) {
182
+ return factory;
183
+ } else {
184
+ return new VerifyHostNameSSLSocketFactory(factory, verifyHostname);
185
+ }
186
+ }
187
+
188
+ private static class VerifyHostNameSSLSocketFactory
189
+ extends SSLSocketFactory
190
+ {
191
+ private final SSLSocketFactory next;
192
+ private final String hostname;
193
+
194
+ public VerifyHostNameSSLSocketFactory(SSLSocketFactory next, String hostname)
195
+ {
196
+ this.next = next;
197
+ this.hostname = hostname;
198
+ }
199
+
200
+ @Override
201
+ public String[] getDefaultCipherSuites()
202
+ {
203
+ return next.getDefaultCipherSuites();
204
+ }
205
+
206
+ @Override
207
+ public String[] getSupportedCipherSuites()
208
+ {
209
+ return next.getSupportedCipherSuites();
210
+ }
211
+
212
+ @Override
213
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
214
+ throws IOException
215
+ {
216
+ Socket sock = next.createSocket(s, host, port, autoClose);
217
+ setSSLParameters(sock, false);
218
+ return sock;
219
+ }
220
+
221
+ @Override
222
+ public Socket createSocket(String host, int port)
223
+ throws IOException, UnknownHostException
224
+ {
225
+ Socket sock = next.createSocket(host, port);
226
+ setSSLParameters(sock, false);
227
+ return sock;
228
+ }
229
+
230
+ @Override
231
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
232
+ throws IOException, UnknownHostException
233
+ {
234
+ Socket sock = next.createSocket(host, port, localHost, localPort);
235
+ setSSLParameters(sock, false);
236
+ return sock;
237
+ }
238
+
239
+ @Override
240
+ public Socket createSocket(InetAddress host, int port)
241
+ throws IOException
242
+ {
243
+ Socket sock = next.createSocket(host, port);
244
+ setSSLParameters(sock, true);
245
+ return sock;
246
+ }
247
+
248
+ @Override
249
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
250
+ throws IOException
251
+ {
252
+ Socket sock = next.createSocket(address, port, localAddress, localPort);
253
+ setSSLParameters(sock, true);
254
+ return sock;
255
+ }
256
+
257
+ private void setSSLParameters(Socket sock, boolean setHostname)
258
+ {
259
+ if (sock instanceof SSLSocket) {
260
+ SSLSocket s = (SSLSocket) sock;
261
+ String identAlgorithm = s.getSSLParameters().getEndpointIdentificationAlgorithm();
262
+ if (identAlgorithm != null && identAlgorithm.equalsIgnoreCase("HTTPS")) {
263
+ // hostname verification is already configured.
264
+ } else {
265
+ if (setHostname && s instanceof SSLSocketImpl) {
266
+ ((SSLSocketImpl) s).setHost(hostname);
267
+ }
268
+ SSLParameters params = s.getSSLParameters();
269
+ params.setEndpointIdentificationAlgorithm("HTTPS");
270
+ s.setSSLParameters(params);
271
+ // s.startHandshake
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embulk-input-ftp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-29 00:00:00.000000000 Z
11
+ date: 2015-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,10 +57,12 @@ files:
57
57
  - libs/ftp4j-1.7.2.jar
58
58
  - src/main/java/org/embulk/input/FtpFileInputPlugin.java
59
59
  - src/main/java/org/embulk/input/ftp/BlockingTransfer.java
60
- - src/main/java/org/embulk/input/ftp/RetryExecutor.java
61
- - src/main/java/org/embulk/input/ftp/RetryableInputStream.java
60
+ - src/main/java/org/embulk/input/ftp/SSLPlugins.java
61
+ - src/main/java/org/embulk/input/ftp/TrustManagers.java
62
62
  - src/test/java/org/embulk/input/TestFtpFileInputPlugin.java
63
- - classpath/embulk-input-ftp-0.1.1.jar
63
+ - classpath/bcpkix-jdk15on-1.52.jar
64
+ - classpath/bcprov-jdk15on-1.52.jar
65
+ - classpath/embulk-input-ftp-0.1.2.jar
64
66
  - classpath/ftp4j-1.7.2.jar
65
67
  homepage: https://github.com/embulk/embulk-input-ftp
66
68
  licenses:
@@ -1,131 +0,0 @@
1
- // TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
2
- package org.embulk.input.ftp;
3
-
4
- import java.util.concurrent.Callable;
5
- import java.util.concurrent.ExecutionException;
6
-
7
- public class RetryExecutor
8
- {
9
- public static RetryExecutor retryExecutor()
10
- {
11
- // TODO default configuration
12
- return new RetryExecutor(3, 500, 30*60*1000);
13
- }
14
-
15
- public static class RetryGiveupException
16
- extends ExecutionException
17
- {
18
- public RetryGiveupException(String message, Exception cause)
19
- {
20
- super(cause);
21
- }
22
-
23
- public RetryGiveupException(Exception cause)
24
- {
25
- super(cause);
26
- }
27
-
28
- public Exception getCause()
29
- {
30
- return (Exception) super.getCause();
31
- }
32
- }
33
-
34
- public static interface Retryable<T>
35
- extends Callable<T>
36
- {
37
- public T call()
38
- throws Exception;
39
-
40
- public boolean isRetryableException(Exception exception);
41
-
42
- public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait)
43
- throws RetryGiveupException;
44
-
45
- public void onGiveup(Exception firstException, Exception lastException)
46
- throws RetryGiveupException;
47
- }
48
-
49
- private final int retryLimit;
50
- private final int initialRetryWait;
51
- private final int maxRetryWait;
52
-
53
- private RetryExecutor(int retryLimit, int initialRetryWait, int maxRetryWait)
54
- {
55
- this.retryLimit = retryLimit;
56
- this.initialRetryWait = initialRetryWait;
57
- this.maxRetryWait = maxRetryWait;
58
- }
59
-
60
- public RetryExecutor withRetryLimit(int count)
61
- {
62
- return new RetryExecutor(count, initialRetryWait, maxRetryWait);
63
- }
64
-
65
- public RetryExecutor withInitialRetryWait(int msec)
66
- {
67
- return new RetryExecutor(retryLimit, msec, maxRetryWait);
68
- }
69
-
70
- public RetryExecutor withMaxRetryWait(int msec)
71
- {
72
- return new RetryExecutor(retryLimit, initialRetryWait, msec);
73
- }
74
-
75
- public <T> T runInterruptible(Retryable<T> op)
76
- throws InterruptedException, RetryGiveupException
77
- {
78
- return run(op, true);
79
- }
80
-
81
- public <T> T run(Retryable<T> op)
82
- throws RetryGiveupException
83
- {
84
- try {
85
- return run(op, false);
86
- } catch (InterruptedException ex) {
87
- throw new RetryGiveupException("Unexpected interruption", ex);
88
- }
89
- }
90
-
91
- private <T> T run(Retryable<T> op, boolean interruptible)
92
- throws InterruptedException, RetryGiveupException
93
- {
94
- int retryWait = initialRetryWait;
95
- int retryCount = 0;
96
-
97
- Exception firstException = null;
98
-
99
- while(true) {
100
- try {
101
- return op.call();
102
- } catch (Exception exception) {
103
- if (firstException == null) {
104
- firstException = exception;
105
- }
106
- if (!op.isRetryableException(exception) || retryCount >= retryLimit) {
107
- op.onGiveup(firstException, exception);
108
- throw new RetryGiveupException(firstException);
109
- }
110
-
111
- retryCount++;
112
- op.onRetry(exception, retryCount, retryLimit, retryWait);
113
-
114
- try {
115
- Thread.sleep(retryWait);
116
- } catch (InterruptedException ex) {
117
- if (interruptible) {
118
- throw ex;
119
- }
120
- }
121
-
122
- // exponential back-off with hard limit
123
- retryWait *= 2;
124
- if (retryWait > maxRetryWait) {
125
- retryWait = maxRetryWait;
126
- }
127
- }
128
- }
129
- }
130
- }
131
-
@@ -1,129 +0,0 @@
1
- // TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
2
- package org.embulk.input.ftp;
3
-
4
- import java.io.InputStream;
5
- import java.io.IOException;
6
-
7
- public class RetryableInputStream
8
- extends InputStream
9
- {
10
- public interface Opener
11
- {
12
- public InputStream open(long offset, Exception exception) throws IOException;
13
- }
14
-
15
- private final Opener opener;
16
- protected InputStream in;
17
- private long offset;
18
- private long markedOffset;
19
-
20
- public RetryableInputStream(InputStream initialInputStream, Opener reopener)
21
- {
22
- this.opener = reopener;
23
- this.in = initialInputStream;
24
- this.offset = 0L;
25
- this.markedOffset = 0L;
26
- }
27
-
28
- public RetryableInputStream(Opener opener) throws IOException
29
- {
30
- this(opener.open(0, null), opener);
31
- }
32
-
33
- private void reopen(Exception exception) throws IOException
34
- {
35
- if (in != null) {
36
- in.close();
37
- in = null;
38
- }
39
- in = opener.open(offset, exception);
40
- }
41
-
42
- @Override
43
- public int read() throws IOException
44
- {
45
- while (true) {
46
- try {
47
- int v = in.read();
48
- offset += 1;
49
- return v;
50
- } catch (IOException | RuntimeException ex) {
51
- reopen(ex);
52
- }
53
- }
54
- }
55
-
56
- @Override
57
- public int read(byte[] b) throws IOException
58
- {
59
- while (true) {
60
- try {
61
- int r = in.read(b);
62
- offset += r;
63
- return r;
64
- } catch (IOException | RuntimeException ex) {
65
- reopen(ex);
66
- }
67
- }
68
- }
69
-
70
- @Override
71
- public int read(byte[] b, int off, int len) throws IOException
72
- {
73
- while (true) {
74
- try {
75
- int r = in.read(b, off, len);
76
- offset += r;
77
- return r;
78
- } catch (IOException | RuntimeException ex) {
79
- reopen(ex);
80
- }
81
- }
82
- }
83
-
84
- @Override
85
- public long skip(long n) throws IOException
86
- {
87
- while (true) {
88
- try {
89
- long r = in.skip(n);
90
- offset += r;
91
- return r;
92
- } catch (IOException | RuntimeException ex) {
93
- reopen(ex);
94
- }
95
- }
96
- }
97
-
98
- @Override
99
- public int available() throws IOException
100
- {
101
- return in.available();
102
- }
103
-
104
- @Override
105
- public void close() throws IOException
106
- {
107
- in.close();
108
- }
109
-
110
- @Override
111
- public void mark(int readlimit)
112
- {
113
- in.mark(readlimit);
114
- markedOffset = offset;
115
- }
116
-
117
- @Override
118
- public void reset() throws IOException
119
- {
120
- in.reset();
121
- offset = markedOffset;
122
- }
123
-
124
- @Override
125
- public boolean markSupported()
126
- {
127
- return in.markSupported();
128
- }
129
- }