View Javadoc
1   /*
2    * This file is part of dependency-check-utils.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.utils;
19  
20  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21  import org.apache.commons.lang3.StringUtils;
22  
23  import java.io.IOException;
24  import java.net.Authenticator;
25  import java.net.HttpURLConnection;
26  import java.net.InetSocketAddress;
27  import java.net.PasswordAuthentication;
28  import java.net.Proxy;
29  import java.net.SocketAddress;
30  import java.net.URL;
31  import java.util.Base64;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  import static java.nio.charset.StandardCharsets.UTF_8;
35  
36  /**
37   * A URLConnection Factory to create new connections. This encapsulates several
38   * configuration checks to ensure that the connection uses the correct proxy
39   * settings.
40   *
41   * @author Jeremy Long
42   */
43  public final class URLConnectionFactory {
44  
45      /**
46       * The logger.
47       */
48      private static final Logger LOGGER = LoggerFactory.getLogger(URLConnectionFactory.class);
49      /**
50       * The configured settings.
51       */
52      private final Settings settings;
53  
54      /**
55       * Private constructor for this factory.
56       *
57       * @param settings reference to the configured settings
58       */
59      public URLConnectionFactory(Settings settings) {
60          this.settings = settings;
61      }
62  
63      /**
64       * Utility method to create an HttpURLConnection. If the application is
65       * configured to use a proxy this method will retrieve the proxy settings
66       * and use them when setting up the connection.
67       *
68       * @param url the URL to connect to
69       * @return an HttpURLConnection
70       * @throws org.owasp.dependencycheck.utils.URLConnectionFailureException
71       * thrown if there is an exception
72       */
73      @SuppressWarnings("squid:S2583")
74      @SuppressFBWarnings(justification = "yes, there is a redundant null check in the catch - to suppress warnings we are leaving the null check",
75              value = {"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
76      public HttpURLConnection createHttpURLConnection(URL url) throws URLConnectionFailureException {
77          HttpURLConnection conn = null;
78          final String proxyHost = settings.getString(Settings.KEYS.PROXY_SERVER);
79  
80          try {
81              if (proxyHost != null && !matchNonProxy(url)) {
82                  final int proxyPort = settings.getInt(Settings.KEYS.PROXY_PORT);
83                  final SocketAddress address = new InetSocketAddress(proxyHost, proxyPort);
84  
85                  final String username = settings.getString(Settings.KEYS.PROXY_USERNAME);
86                  final String password = settings.getString(Settings.KEYS.PROXY_PASSWORD);
87  
88                  if (username != null && password != null) {
89                      final Authenticator auth = new Authenticator() {
90                          @Override
91                          public PasswordAuthentication getPasswordAuthentication() {
92                              if (proxyHost.equals(getRequestingHost()) || getRequestorType().equals(Authenticator.RequestorType.PROXY)) {
93                                  LOGGER.debug("Using the configured proxy username and password");
94                                  if (settings.getBoolean(Settings.KEYS.PROXY_DISABLE_SCHEMAS, true)) {
95                                      System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
96                                  }
97                                  return new PasswordAuthentication(username, password.toCharArray());
98                              }
99                              return super.getPasswordAuthentication();
100                         }
101                     };
102                     Authenticator.setDefault(auth);
103                 }
104 
105                 final Proxy proxy = new Proxy(Proxy.Type.HTTP, address);
106                 conn = (HttpURLConnection) url.openConnection(proxy);
107             } else {
108                 conn = (HttpURLConnection) url.openConnection();
109             }
110             final int connectionTimeout = settings.getInt(Settings.KEYS.CONNECTION_TIMEOUT, 10000);
111             // set a conservative long default timeout to compensate for MITM-proxies that return the (final) bytes only
112             // after all security checks passed
113             final int readTimeout = settings.getInt(Settings.KEYS.CONNECTION_READ_TIMEOUT, 60_000);
114             conn.setConnectTimeout(connectionTimeout);
115             conn.setReadTimeout(readTimeout);
116             conn.setInstanceFollowRedirects(true);
117         } catch (IOException ex) {
118             if (conn != null) {
119                 try {
120                     conn.disconnect();
121                 } finally {
122                     conn = null;
123                 }
124             }
125             throw new URLConnectionFailureException("Error getting connection.", ex);
126         }
127         addAuthenticationIfPresent(conn);
128         return conn;
129     }
130 
131     /**
132      * Adds the basic authorization header if the URL contains a username and
133      * password. Example URL that will have the basic authorization header
134      * added:
135      * <code>http://username:password@passwordprotectednvdsite.internal/feeds/json/cve/1.1/nvdcve-1.1-modified.json.gz</code>;
136      *
137      * @param conn the connection
138      */
139     private void addAuthenticationIfPresent(HttpURLConnection conn) {
140         final String userInfo = conn.getURL().getUserInfo();
141         if (userInfo != null) {
142             final String basicAuth = "Basic " + Base64.getEncoder().encodeToString(userInfo.getBytes(UTF_8));
143             if (LOGGER.isDebugEnabled()) {
144                 LOGGER.debug("Adding user info as basic authorization");
145             }
146             conn.addRequestProperty("Authorization", basicAuth);
147         }
148     }
149 
150     /**
151      * Adds a basic authentication header if the values in the settings are not
152      * null.
153      *
154      * @param conn the connection to add the basic auth header
155      * @param userKey the settings key for the username
156      * @param passwordKey the settings key for the password
157      */
158     public void addBasicAuthentication(HttpURLConnection conn, String userKey, String passwordKey) {
159         if (StringUtils.isNotEmpty(settings.getString(userKey))
160                 && StringUtils.isNotEmpty(settings.getString(passwordKey))) {
161             final String user = settings.getString(userKey);
162             final String password = settings.getString(passwordKey);
163 
164             if (user.isEmpty() || password.isEmpty()) {
165                 if (LOGGER.isDebugEnabled()) {
166                     LOGGER.debug("Skip authentication as user and/or password is empty");
167                 }
168             } else {
169                 final String userColonPassword = user + ":" + password;
170                 final String basicAuth = "Basic " + Base64.getEncoder().encodeToString(userColonPassword.getBytes(UTF_8));
171                 if (LOGGER.isDebugEnabled()) {
172                     LOGGER.debug("Adding user/password from settings.xml as basic authorization");
173                 }
174                 conn.addRequestProperty("Authorization", basicAuth);
175             }
176         }
177     }
178 
179     /**
180      * Check if host name matches nonProxy settings
181      *
182      * @param url the URL to connect to
183      * @return matching result. true: match nonProxy
184      */
185     @SuppressWarnings("StringSplitter")
186     private boolean matchNonProxy(final URL url) {
187         final String host = url.getHost();
188 
189         // code partially from org.apache.maven.plugins.site.AbstractDeployMojo#getProxyInfo
190         final String nonProxyHosts = settings.getString(Settings.KEYS.PROXY_NON_PROXY_HOSTS);
191         if (null != nonProxyHosts) {
192             final String[] nonProxies = nonProxyHosts.split("(,)|(;)|(\\|)");
193             for (final String nonProxyHost : nonProxies) {
194                 //if ( StringUtils.contains( nonProxyHost, "*" ) )
195                 if (null != nonProxyHost && nonProxyHost.contains("*")) {
196                     // Handle wildcard at the end, beginning or middle of the nonProxyHost
197                     final int pos = nonProxyHost.indexOf('*');
198                     final String nonProxyHostPrefix = nonProxyHost.substring(0, pos);
199                     final String nonProxyHostSuffix = nonProxyHost.substring(pos + 1);
200                     // prefix*
201                     if (!StringUtils.isBlank(nonProxyHostPrefix) && host.startsWith(nonProxyHostPrefix) && StringUtils.isBlank(nonProxyHostSuffix)) {
202                         return true;
203                     }
204                     // *suffix
205                     if (StringUtils.isBlank(nonProxyHostPrefix) && !StringUtils.isBlank(nonProxyHostSuffix) && host.endsWith(nonProxyHostSuffix)) {
206                         return true;
207                     }
208                     // prefix*suffix
209                     if (!StringUtils.isBlank(nonProxyHostPrefix) && host.startsWith(nonProxyHostPrefix) && !StringUtils.isBlank(nonProxyHostSuffix)
210                             && host.endsWith(nonProxyHostSuffix)) {
211                         return true;
212                     }
213                 } else if (host.equals(nonProxyHost)) {
214                     return true;
215                 }
216             }
217         }
218         return false;
219     }
220 
221     /**
222      * Utility method to create an HttpURLConnection. The use of a proxy here is
223      * optional as there may be cases where a proxy is configured but we don't
224      * want to use it (for example, if there's an internal repository
225      * configured)
226      *
227      * @param url the URL to connect to
228      * @param proxy whether to use the proxy (if configured)
229      * @return a newly constructed HttpURLConnection
230      * @throws org.owasp.dependencycheck.utils.URLConnectionFailureException
231      * thrown if there is an exception
232      */
233     public HttpURLConnection createHttpURLConnection(URL url, boolean proxy) throws URLConnectionFailureException {
234         if (proxy) {
235             return createHttpURLConnection(url);
236         }
237         final HttpURLConnection conn;
238         try {
239             conn = (HttpURLConnection) url.openConnection();
240             final int timeout = settings.getInt(Settings.KEYS.CONNECTION_TIMEOUT, 10000);
241             conn.setConnectTimeout(timeout);
242             conn.setInstanceFollowRedirects(true);
243         } catch (IOException ioe) {
244             throw new URLConnectionFailureException("Error getting connection.", ioe);
245         }
246         addAuthenticationIfPresent(conn);
247         return conn;
248     }
249 
250 }