1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.nodeaudit;
19
20 import java.io.BufferedInputStream;
21 import java.io.BufferedOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.HttpURLConnection;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.nio.charset.StandardCharsets;
29 import java.security.SecureRandom;
30 import java.util.List;
31 import javax.annotation.concurrent.ThreadSafe;
32
33 import org.json.JSONObject;
34 import org.owasp.dependencycheck.utils.Settings;
35 import org.owasp.dependencycheck.utils.URLConnectionFactory;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import javax.json.Json;
40 import javax.json.JsonObject;
41 import javax.json.JsonReader;
42 import org.apache.commons.jcs3.access.exception.CacheException;
43
44 import static org.owasp.dependencycheck.analyzer.NodeAuditAnalyzer.DEFAULT_URL;
45
46 import org.owasp.dependencycheck.analyzer.exception.SearchException;
47 import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
48 import org.owasp.dependencycheck.data.cache.DataCache;
49 import org.owasp.dependencycheck.data.cache.DataCacheFactory;
50 import org.owasp.dependencycheck.utils.Checksum;
51 import org.owasp.dependencycheck.utils.URLConnectionFailureException;
52
53
54
55
56
57
58 @ThreadSafe
59 public class NodeAuditSearch {
60
61
62
63
64 private final URL nodeAuditUrl;
65
66
67
68
69 private final boolean useProxy;
70
71
72
73 private final Settings settings;
74
75
76
77 private static final Logger LOGGER = LoggerFactory.getLogger(NodeAuditSearch.class);
78
79
80
81 private DataCache<List<Advisory>> cache;
82
83
84
85
86
87
88
89
90 public NodeAuditSearch(Settings settings) throws MalformedURLException {
91 final String searchUrl = settings.getString(Settings.KEYS.ANALYZER_NODE_AUDIT_URL, DEFAULT_URL);
92 LOGGER.debug("Node Audit Search URL: {}", searchUrl);
93 this.nodeAuditUrl = new URL(searchUrl);
94 this.settings = settings;
95 if (null != settings.getString(Settings.KEYS.PROXY_SERVER)) {
96 useProxy = true;
97 LOGGER.debug("Using proxy");
98 } else {
99 useProxy = false;
100 LOGGER.debug("Not using proxy");
101 }
102 if (settings.getBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, true)) {
103 try {
104 final DataCacheFactory factory = new DataCacheFactory(settings);
105 cache = factory.getNodeAuditCache();
106 } catch (CacheException ex) {
107 settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, false);
108 LOGGER.debug("Error creating cache, disabling caching", ex);
109 }
110 }
111 }
112
113
114
115
116
117
118
119
120
121
122
123 public List<Advisory> submitPackage(JsonObject packageJson) throws SearchException, IOException {
124 String key = null;
125 if (cache != null) {
126 key = Checksum.getSHA256Checksum(packageJson.toString());
127 final List<Advisory> cached = cache.get(key);
128 if (cached != null) {
129 LOGGER.debug("cache hit for node audit: " + key);
130 return cached;
131 }
132 }
133 return submitPackage(packageJson, key, 0);
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148 private List<Advisory> submitPackage(JsonObject packageJson, String key, int count) throws SearchException, IOException {
149 try {
150 if (LOGGER.isTraceEnabled()) {
151 LOGGER.trace("----------------------------------------");
152 LOGGER.trace("Node Audit Payload:");
153 LOGGER.trace(packageJson.toString());
154 LOGGER.trace("----------------------------------------");
155 LOGGER.trace("----------------------------------------");
156 }
157 final byte[] packageDatabytes = packageJson.toString().getBytes(StandardCharsets.UTF_8);
158 final URLConnectionFactory factory = new URLConnectionFactory(settings);
159 final HttpURLConnection conn = factory.createHttpURLConnection(nodeAuditUrl, useProxy);
160 conn.setDoOutput(true);
161 conn.setDoInput(true);
162 conn.setRequestMethod("POST");
163 conn.setRequestProperty("user-agent", "npm/6.1.0 node/v10.5.0 linux x64");
164 conn.setRequestProperty("npm-in-ci", "false");
165 conn.setRequestProperty("npm-scope", "");
166 conn.setRequestProperty("npm-session", generateRandomSession());
167 conn.setRequestProperty("content-type", "application/json");
168 conn.setRequestProperty("Content-Length", Integer.toString(packageDatabytes.length));
169 conn.connect();
170
171 try (OutputStream os = new BufferedOutputStream(conn.getOutputStream())) {
172 os.write(packageDatabytes);
173 os.flush();
174 }
175
176 switch (conn.getResponseCode()) {
177 case 200:
178 try (InputStream in = new BufferedInputStream(conn.getInputStream());
179 JsonReader jsonReader = Json.createReader(in)) {
180 final JSONObject jsonResponse = new JSONObject(jsonReader.readObject().toString());
181 final NpmAuditParser parser = new NpmAuditParser();
182 final List<Advisory> advisories = parser.parse(jsonResponse);
183 if (cache != null) {
184 cache.put(key, advisories);
185 }
186 return advisories;
187 } catch (Exception ex) {
188 LOGGER.debug("Error connecting to Node Audit API. Error: {}",
189 ex.getMessage());
190 throw new SearchException("Could not connect to Node Audit API: " + ex.getMessage(), ex);
191 }
192 case 503:
193 LOGGER.debug("Node Audit API returned `{} {}` - retrying request.",
194 conn.getResponseCode(), conn.getResponseMessage());
195 if (count < 5) {
196 final int next = count + 1;
197 try {
198 Thread.sleep(1500L * next);
199 } catch (InterruptedException ex) {
200 Thread.currentThread().interrupt();
201 throw new UnexpectedAnalysisException(ex);
202 }
203 return submitPackage(packageJson, key, next);
204 }
205 throw new SearchException("Could not perform Node Audit analysis - service returned a 503.");
206 case 400:
207 LOGGER.debug("Invalid payload submitted to Node Audit API. Received response code: {} {}",
208 conn.getResponseCode(), conn.getResponseMessage());
209 throw new SearchException("Could not perform Node Audit analysis. Invalid payload submitted to Node Audit API.");
210 default:
211 LOGGER.debug("Could not connect to Node Audit API. Received response code: {} {}",
212 conn.getResponseCode(), conn.getResponseMessage());
213 throw new IOException("Could not connect to Node Audit API");
214 }
215 } catch (IOException ex) {
216 if (ex instanceof javax.net.ssl.SSLHandshakeException
217 && ex.getMessage().contains("unable to find valid certification path to requested target")) {
218 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
219 + " Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", nodeAuditUrl);
220 throw new URLConnectionFailureException(msg, ex);
221 }
222 throw ex;
223 }
224 }
225
226
227
228
229
230
231 private String generateRandomSession() {
232 final int length = 16;
233 final SecureRandom r = new SecureRandom();
234 final StringBuilder sb = new StringBuilder();
235 while (sb.length() < length) {
236 sb.append(Integer.toHexString(r.nextInt()));
237 }
238 return sb.toString().substring(0, length);
239 }
240 }