You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TransportHttp: shared SSLContext during fetch or push TransportHttp makes several HTTP requests. The SSLContext and socket factory must be shared over these requests, otherwise authentication information may not be propagated correctly from one request to the next. This is important for authentication mechanisms that rely on client-side state, like NEGOTIATE (either NTLM, if the underlying HTTP library supports it, or Kerberos). In particular, SPNEGO cannot authenticate on a POST request; the authentication must come from the initial GET request, which implies that the POST request must use the same SSLContext and socket factory that was used for the GET. Change the way HTTPS connections are configured. Introduce the concept of a GitSession, which is a client-side HTTP session over several HTTPS requests. TransportHttp creates such a session and uses it to configure all HTTP requests during that session (fetch or push). This gives a way to abstract away the differences between JDK and Apache HTTP connections and to configure SSL setup outside. A GitSession can maintain state and thus give all HTTP requests in a session the same socket factory. Introduce an extension interface HttpConnectionFactory2 that adds a method to obtain a new GitSession. Implement this for both existing HTTP connection factories. Change TransportHttp to use the new GitSession to configure HTTP connections. The old methods for disabling SSL verification still exist to support possibly external connection and connection factory implementations that do not make use of the new GitSession yet. Bug: 535850 Change-Id: Iedf67464e4e353c1883447c13c86b5a838e678f1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
преди 3 години
TransportHttp: shared SSLContext during fetch or push TransportHttp makes several HTTP requests. The SSLContext and socket factory must be shared over these requests, otherwise authentication information may not be propagated correctly from one request to the next. This is important for authentication mechanisms that rely on client-side state, like NEGOTIATE (either NTLM, if the underlying HTTP library supports it, or Kerberos). In particular, SPNEGO cannot authenticate on a POST request; the authentication must come from the initial GET request, which implies that the POST request must use the same SSLContext and socket factory that was used for the GET. Change the way HTTPS connections are configured. Introduce the concept of a GitSession, which is a client-side HTTP session over several HTTPS requests. TransportHttp creates such a session and uses it to configure all HTTP requests during that session (fetch or push). This gives a way to abstract away the differences between JDK and Apache HTTP connections and to configure SSL setup outside. A GitSession can maintain state and thus give all HTTP requests in a session the same socket factory. Introduce an extension interface HttpConnectionFactory2 that adds a method to obtain a new GitSession. Implement this for both existing HTTP connection factories. Change TransportHttp to use the new GitSession to configure HTTP connections. The old methods for disabling SSL verification still exist to support possibly external connection and connection factory implementations that do not make use of the new GitSession yet. Bug: 535850 Change-Id: Iedf67464e4e353c1883447c13c86b5a838e678f1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
преди 3 години
TransportHttp: shared SSLContext during fetch or push TransportHttp makes several HTTP requests. The SSLContext and socket factory must be shared over these requests, otherwise authentication information may not be propagated correctly from one request to the next. This is important for authentication mechanisms that rely on client-side state, like NEGOTIATE (either NTLM, if the underlying HTTP library supports it, or Kerberos). In particular, SPNEGO cannot authenticate on a POST request; the authentication must come from the initial GET request, which implies that the POST request must use the same SSLContext and socket factory that was used for the GET. Change the way HTTPS connections are configured. Introduce the concept of a GitSession, which is a client-side HTTP session over several HTTPS requests. TransportHttp creates such a session and uses it to configure all HTTP requests during that session (fetch or push). This gives a way to abstract away the differences between JDK and Apache HTTP connections and to configure SSL setup outside. A GitSession can maintain state and thus give all HTTP requests in a session the same socket factory. Introduce an extension interface HttpConnectionFactory2 that adds a method to obtain a new GitSession. Implement this for both existing HTTP connection factories. Change TransportHttp to use the new GitSession to configure HTTP connections. The old methods for disabling SSL verification still exist to support possibly external connection and connection factory implementations that do not make use of the new GitSession yet. Bug: 535850 Change-Id: Iedf67464e4e353c1883447c13c86b5a838e678f1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
преди 3 години
TransportHttp: shared SSLContext during fetch or push TransportHttp makes several HTTP requests. The SSLContext and socket factory must be shared over these requests, otherwise authentication information may not be propagated correctly from one request to the next. This is important for authentication mechanisms that rely on client-side state, like NEGOTIATE (either NTLM, if the underlying HTTP library supports it, or Kerberos). In particular, SPNEGO cannot authenticate on a POST request; the authentication must come from the initial GET request, which implies that the POST request must use the same SSLContext and socket factory that was used for the GET. Change the way HTTPS connections are configured. Introduce the concept of a GitSession, which is a client-side HTTP session over several HTTPS requests. TransportHttp creates such a session and uses it to configure all HTTP requests during that session (fetch or push). This gives a way to abstract away the differences between JDK and Apache HTTP connections and to configure SSL setup outside. A GitSession can maintain state and thus give all HTTP requests in a session the same socket factory. Introduce an extension interface HttpConnectionFactory2 that adds a method to obtain a new GitSession. Implement this for both existing HTTP connection factories. Change TransportHttp to use the new GitSession to configure HTTP connections. The old methods for disabling SSL verification still exist to support possibly external connection and connection factory implementations that do not make use of the new GitSession yet. Bug: 535850 Change-Id: Iedf67464e4e353c1883447c13c86b5a838e678f1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
преди 3 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Copyright (C) 2010, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.util;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import java.io.IOException;
  14. import java.io.UnsupportedEncodingException;
  15. import java.net.ConnectException;
  16. import java.net.Proxy;
  17. import java.net.ProxySelector;
  18. import java.net.URI;
  19. import java.net.URISyntaxException;
  20. import java.net.URL;
  21. import java.net.URLEncoder;
  22. import java.security.KeyManagementException;
  23. import java.security.NoSuchAlgorithmException;
  24. import java.text.MessageFormat;
  25. import java.util.Arrays;
  26. import java.util.Collections;
  27. import java.util.LinkedHashSet;
  28. import java.util.Set;
  29. import javax.net.ssl.SSLSocket;
  30. import javax.net.ssl.TrustManager;
  31. import org.eclipse.jgit.internal.JGitText;
  32. import org.eclipse.jgit.transport.http.HttpConnection;
  33. import org.eclipse.jgit.transport.http.NoCheckX509TrustManager;
  34. import org.slf4j.Logger;
  35. import org.slf4j.LoggerFactory;
  36. /**
  37. * Extra utilities to support usage of HTTP.
  38. */
  39. public class HttpSupport {
  40. private final static Logger LOG = LoggerFactory
  41. .getLogger(HttpSupport.class);
  42. /** The {@code GET} HTTP method. */
  43. public static final String METHOD_GET = "GET"; //$NON-NLS-1$
  44. /** The {@code HEAD} HTTP method.
  45. * @since 4.3 */
  46. public static final String METHOD_HEAD = "HEAD"; //$NON-NLS-1$
  47. /** The {@code POST} HTTP method.
  48. * @since 4.3 */
  49. public static final String METHOD_PUT = "PUT"; //$NON-NLS-1$
  50. /** The {@code POST} HTTP method. */
  51. public static final String METHOD_POST = "POST"; //$NON-NLS-1$
  52. /** The {@code Cache-Control} header. */
  53. public static final String HDR_CACHE_CONTROL = "Cache-Control"; //$NON-NLS-1$
  54. /** The {@code Pragma} header. */
  55. public static final String HDR_PRAGMA = "Pragma"; //$NON-NLS-1$
  56. /** The {@code User-Agent} header. */
  57. public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$
  58. /**
  59. * The {@code Server} header.
  60. * @since 4.0
  61. */
  62. public static final String HDR_SERVER = "Server"; //$NON-NLS-1$
  63. /** The {@code Date} header. */
  64. public static final String HDR_DATE = "Date"; //$NON-NLS-1$
  65. /** The {@code Expires} header. */
  66. public static final String HDR_EXPIRES = "Expires"; //$NON-NLS-1$
  67. /** The {@code ETag} header. */
  68. public static final String HDR_ETAG = "ETag"; //$NON-NLS-1$
  69. /** The {@code If-None-Match} header. */
  70. public static final String HDR_IF_NONE_MATCH = "If-None-Match"; //$NON-NLS-1$
  71. /** The {@code Last-Modified} header. */
  72. public static final String HDR_LAST_MODIFIED = "Last-Modified"; //$NON-NLS-1$
  73. /** The {@code If-Modified-Since} header. */
  74. public static final String HDR_IF_MODIFIED_SINCE = "If-Modified-Since"; //$NON-NLS-1$
  75. /** The {@code Accept} header. */
  76. public static final String HDR_ACCEPT = "Accept"; //$NON-NLS-1$
  77. /** The {@code Content-Type} header. */
  78. public static final String HDR_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$
  79. /** The {@code Content-Length} header. */
  80. public static final String HDR_CONTENT_LENGTH = "Content-Length"; //$NON-NLS-1$
  81. /** The {@code Content-Encoding} header. */
  82. public static final String HDR_CONTENT_ENCODING = "Content-Encoding"; //$NON-NLS-1$
  83. /** The {@code Content-Range} header. */
  84. public static final String HDR_CONTENT_RANGE = "Content-Range"; //$NON-NLS-1$
  85. /** The {@code Accept-Ranges} header. */
  86. public static final String HDR_ACCEPT_RANGES = "Accept-Ranges"; //$NON-NLS-1$
  87. /** The {@code If-Range} header. */
  88. public static final String HDR_IF_RANGE = "If-Range"; //$NON-NLS-1$
  89. /** The {@code Range} header. */
  90. public static final String HDR_RANGE = "Range"; //$NON-NLS-1$
  91. /** The {@code Accept-Encoding} header. */
  92. public static final String HDR_ACCEPT_ENCODING = "Accept-Encoding"; //$NON-NLS-1$
  93. /**
  94. * The {@code Location} header.
  95. * @since 4.7
  96. */
  97. public static final String HDR_LOCATION = "Location"; //$NON-NLS-1$
  98. /** The {@code gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}. */
  99. public static final String ENCODING_GZIP = "gzip"; //$NON-NLS-1$
  100. /**
  101. * The {@code x-gzip} encoding value for {@link #HDR_ACCEPT_ENCODING}.
  102. * @since 4.6
  103. */
  104. public static final String ENCODING_X_GZIP = "x-gzip"; //$NON-NLS-1$
  105. /** The standard {@code text/plain} MIME type. */
  106. public static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$
  107. /** The {@code Authorization} header. */
  108. public static final String HDR_AUTHORIZATION = "Authorization"; //$NON-NLS-1$
  109. /** The {@code WWW-Authenticate} header. */
  110. public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$
  111. /**
  112. * The {@code Cookie} header.
  113. *
  114. * @since 5.4
  115. */
  116. public static final String HDR_COOKIE = "Cookie"; //$NON-NLS-1$
  117. /**
  118. * The {@code Set-Cookie} header.
  119. *
  120. * @since 5.4
  121. */
  122. public static final String HDR_SET_COOKIE = "Set-Cookie"; //$NON-NLS-1$
  123. /**
  124. * The {@code Set-Cookie2} header.
  125. *
  126. * @since 5.4
  127. */
  128. public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$
  129. private static Set<String> configuredHttpsProtocols;
  130. /**
  131. * URL encode a value string into an output buffer.
  132. *
  133. * @param urlstr
  134. * the output buffer.
  135. * @param key
  136. * value which must be encoded to protected special characters.
  137. */
  138. public static void encode(StringBuilder urlstr, String key) {
  139. if (key == null || key.length() == 0)
  140. return;
  141. try {
  142. urlstr.append(URLEncoder.encode(key, UTF_8.name()));
  143. } catch (UnsupportedEncodingException e) {
  144. throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e);
  145. }
  146. }
  147. /**
  148. * Get the HTTP response code from the request.
  149. * <p>
  150. * Roughly the same as <code>c.getResponseCode()</code> but the
  151. * ConnectException is translated to be more understandable.
  152. *
  153. * @param c
  154. * connection the code should be obtained from.
  155. * @return r HTTP status code, usually 200 to indicate success. See
  156. * {@link org.eclipse.jgit.transport.http.HttpConnection} for other
  157. * defined constants.
  158. * @throws java.io.IOException
  159. * communications error prevented obtaining the response code.
  160. * @since 3.3
  161. */
  162. public static int response(HttpConnection c) throws IOException {
  163. try {
  164. return c.getResponseCode();
  165. } catch (ConnectException ce) {
  166. final URL url = c.getURL();
  167. final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
  168. // The standard J2SE error message is not very useful.
  169. //
  170. if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
  171. throw new ConnectException(MessageFormat.format(JGitText.get().connectionTimeOut, host));
  172. throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$
  173. }
  174. }
  175. /**
  176. * Get the HTTP response code from the request.
  177. * <p>
  178. * Roughly the same as <code>c.getResponseCode()</code> but the
  179. * ConnectException is translated to be more understandable.
  180. *
  181. * @param c
  182. * connection the code should be obtained from.
  183. * @return r HTTP status code, usually 200 to indicate success. See
  184. * {@link org.eclipse.jgit.transport.http.HttpConnection} for other
  185. * defined constants.
  186. * @throws java.io.IOException
  187. * communications error prevented obtaining the response code.
  188. */
  189. public static int response(java.net.HttpURLConnection c)
  190. throws IOException {
  191. try {
  192. return c.getResponseCode();
  193. } catch (ConnectException ce) {
  194. final URL url = c.getURL();
  195. final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
  196. // The standard J2SE error message is not very useful.
  197. //
  198. if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
  199. throw new ConnectException(MessageFormat.format(
  200. JGitText.get().connectionTimeOut, host));
  201. throw new ConnectException(ce.getMessage() + " " + host); //$NON-NLS-1$
  202. }
  203. }
  204. /**
  205. * Extract a HTTP header from the response.
  206. *
  207. * @param c
  208. * connection the header should be obtained from.
  209. * @param headerName
  210. * the header name
  211. * @return the header value
  212. * @throws java.io.IOException
  213. * communications error prevented obtaining the header.
  214. * @since 4.7
  215. */
  216. public static String responseHeader(final HttpConnection c,
  217. final String headerName) throws IOException {
  218. return c.getHeaderField(headerName);
  219. }
  220. /**
  221. * Determine the proxy server (if any) needed to obtain a URL.
  222. *
  223. * @param proxySelector
  224. * proxy support for the caller.
  225. * @param u
  226. * location of the server caller wants to talk to.
  227. * @return proxy to communicate with the supplied URL.
  228. * @throws java.net.ConnectException
  229. * the proxy could not be computed as the supplied URL could not
  230. * be read. This failure should never occur.
  231. */
  232. public static Proxy proxyFor(ProxySelector proxySelector, URL u)
  233. throws ConnectException {
  234. try {
  235. URI uri = new URI(u.getProtocol(), null, u.getHost(), u.getPort(),
  236. null, null, null);
  237. return proxySelector.select(uri).get(0);
  238. } catch (URISyntaxException e) {
  239. final ConnectException err;
  240. err = new ConnectException(MessageFormat.format(JGitText.get().cannotDetermineProxyFor, u));
  241. err.initCause(e);
  242. throw err;
  243. }
  244. }
  245. /**
  246. * Disable SSL and hostname verification for given HTTP connection
  247. *
  248. * @param conn
  249. * a {@link org.eclipse.jgit.transport.http.HttpConnection}
  250. * object.
  251. * @throws java.io.IOException
  252. * @since 4.3
  253. */
  254. public static void disableSslVerify(HttpConnection conn)
  255. throws IOException {
  256. TrustManager[] trustAllCerts = {
  257. new NoCheckX509TrustManager() };
  258. try {
  259. conn.configure(null, trustAllCerts, null);
  260. conn.setHostnameVerifier((name, session) -> true);
  261. } catch (KeyManagementException | NoSuchAlgorithmException e) {
  262. throw new IOException(e.getMessage(), e);
  263. }
  264. }
  265. /**
  266. * Enables all supported TLS protocol versions on the socket given. If
  267. * system property "https.protocols" is set, only protocols specified there
  268. * are enabled.
  269. * <p>
  270. * This is primarily a mechanism to deal with using TLS on IBM JDK. IBM JDK
  271. * returns sockets that support all TLS protocol versions but have only the
  272. * one specified in the context enabled. Oracle or OpenJDK return sockets
  273. * that have all available protocols enabled already, up to the one
  274. * specified.
  275. * <p>
  276. * <table>
  277. * <tr>
  278. * <td>SSLContext.getInstance()</td>
  279. * <td>OpenJDK</td>
  280. * <td>IDM JDK</td>
  281. * </tr>
  282. * <tr>
  283. * <td>"TLS"</td>
  284. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br />
  285. * Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td>
  286. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  287. * Enabled: TLSv1</td>
  288. * </tr>
  289. * <tr>
  290. * <td>"TLSv1.2"</td>
  291. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  292. * Enabled: TLSv1, TLSV1.1, TLSv1.2</td>
  293. * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
  294. * Enabled: TLSv1.2</td>
  295. * </tr>
  296. * </table>
  297. *
  298. * @param socket
  299. * to configure
  300. * @see <a href=
  301. * "https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html">Behavior
  302. * of SSLContext.getInstance("TLS") on IBM JDK</a>
  303. * @see <a href=
  304. * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization">Customizing
  305. * JSSE about https.protocols</a>
  306. * @since 5.7
  307. */
  308. public static void configureTLS(SSLSocket socket) {
  309. // 1. Enable all available TLS protocol versions
  310. Set<String> enabled = new LinkedHashSet<>(
  311. Arrays.asList(socket.getEnabledProtocols()));
  312. for (String s : socket.getSupportedProtocols()) {
  313. if (s.startsWith("TLS")) { //$NON-NLS-1$
  314. enabled.add(s);
  315. }
  316. }
  317. // 2. Respect the https.protocols system property
  318. Set<String> configured = getConfiguredProtocols();
  319. if (!configured.isEmpty()) {
  320. enabled.retainAll(configured);
  321. }
  322. if (!enabled.isEmpty()) {
  323. socket.setEnabledProtocols(enabled.toArray(new String[0]));
  324. }
  325. }
  326. private static Set<String> getConfiguredProtocols() {
  327. Set<String> result = configuredHttpsProtocols;
  328. if (result == null) {
  329. String configured = getProperty("https.protocols"); //$NON-NLS-1$
  330. if (StringUtils.isEmptyOrNull(configured)) {
  331. result = Collections.emptySet();
  332. } else {
  333. result = new LinkedHashSet<>(
  334. Arrays.asList(configured.split("\\s*,\\s*"))); //$NON-NLS-1$
  335. }
  336. configuredHttpsProtocols = result;
  337. }
  338. return result;
  339. }
  340. private static String getProperty(String property) {
  341. try {
  342. return SystemReader.getInstance().getProperty(property);
  343. } catch (SecurityException e) {
  344. LOG.warn(JGitText.get().failedReadHttpsProtocols, e);
  345. return null;
  346. }
  347. }
  348. /**
  349. * Scan a RFC 7230 token as it appears in HTTP headers.
  350. *
  351. * @param header
  352. * to scan in
  353. * @param from
  354. * index in {@code header} to start scanning at
  355. * @return the index after the token, that is, on the first non-token
  356. * character or {@code header.length}
  357. * @throws IndexOutOfBoundsException
  358. * if {@code from < 0} or {@code from > header.length()}
  359. *
  360. * @see <a href="https://tools.ietf.org/html/rfc7230#appendix-B">RFC 7230,
  361. * Appendix B: Collected Grammar; "token" production</a>
  362. * @since 5.10
  363. */
  364. public static int scanToken(String header, int from) {
  365. int length = header.length();
  366. int i = from;
  367. if (i < 0 || i > length) {
  368. throw new IndexOutOfBoundsException();
  369. }
  370. while (i < length) {
  371. char c = header.charAt(i);
  372. switch (c) {
  373. case '!':
  374. case '#':
  375. case '$':
  376. case '%':
  377. case '&':
  378. case '\'':
  379. case '*':
  380. case '+':
  381. case '-':
  382. case '.':
  383. case '^':
  384. case '_':
  385. case '`':
  386. case '|':
  387. case '~':
  388. case '0':
  389. case '1':
  390. case '2':
  391. case '3':
  392. case '4':
  393. case '5':
  394. case '6':
  395. case '7':
  396. case '8':
  397. case '9':
  398. i++;
  399. break;
  400. default:
  401. if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  402. i++;
  403. break;
  404. }
  405. return i;
  406. }
  407. }
  408. return i;
  409. }
  410. private HttpSupport() {
  411. // Utility class only.
  412. }
  413. }