From 96001177ba08034491692a4ec3d9e367b8b36adb Mon Sep 17 00:00:00 2001 From: ftiercelin Date: Sun, 22 Dec 2024 15:30:10 +0000 Subject: [PATCH 1/3] add hostedSuppressionsAuthHeader option --- .../owasp/dependencycheck/taskdefs/Purge.java | 24 ++ ant/src/site/markdown/config-purge.md | 7 +- ant/src/site/markdown/config-update.md | 1 + ant/src/site/markdown/configuration.md | 1 + .../org/owasp/dependencycheck/CliParser.java | 10 +- .../completion-for-dependency-check.sh | 1 + cli/src/site/markdown/arguments.md | 1 + .../update/HostedSuppressionsDataSource.java | 6 +- .../maven/BaseDependencyCheckMojo.java | 7 + maven/src/site/markdown/configuration.md | 1 + .../utils/CredentialHelper.java | 192 +++++++++++++ .../dependencycheck/utils/Downloader.java | 153 +++++++--- .../owasp/dependencycheck/utils/Settings.java | 15 + .../utils/CredentialHelperTest.java | 261 ++++++++++++++++++ 14 files changed, 644 insertions(+), 36 deletions(-) create mode 100644 utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java create mode 100644 utils/src/test/java/org/owasp/dependencycheck/utils/CredentialHelperTest.java diff --git a/ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java b/ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java index 1599a6372b4..1fbdf9e8276 100644 --- a/ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java +++ b/ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java @@ -62,6 +62,11 @@ public class Purge extends Task { */ private String hostedSuppressionsUrl = null; + /** + * The authorization header to hosted suppressions file with base FP suppressions. + */ + private String hostedSuppressionsAuthHeader = null; + /** * Construct a new DependencyCheckTask. */ @@ -131,6 +136,24 @@ public void setHostedSuppressionsUrl(final String hostedSuppressionsUrl) { this.hostedSuppressionsUrl = hostedSuppressionsUrl; } + /** + * Get the value of hostedSuppressionsAuthHeader. + * + * @return the value of hostedSuppressionsAuthHeader + */ + public String getHostedSuppressionsAuthHeader() { + return hostedSuppressionsAuthHeader; + } + + /** + * Set the value of hostedSuppressionsAuthHeader. + * + * @param hostedSuppressionsUrl new value of hostedSuppressionsAuthHeader + */ + public void setHostedSuppressionsAuthHeader(final String hostedSuppressionsAuthHeader) { + this.hostedSuppressionsAuthHeader = hostedSuppressionsAuthHeader; + } + /** * Sets the * {@link Thread#getContextClassLoader() Thread Context Class Loader} to the @@ -214,6 +237,7 @@ protected void populateSettings() throws BuildException { log(msg, ex, Project.MSG_WARN); } settings.setStringIfNotEmpty(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, hostedSuppressionsUrl); + settings.setStringIfNotEmpty(Settings.KEYS.HOSTED_SUPPRESSIONS_AUTH_HEADER, hostedSuppressionsAuthHeader); if (dataDirectory != null) { settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory); } else { diff --git a/ant/src/site/markdown/config-purge.md b/ant/src/site/markdown/config-purge.md index 5006865fe81..2173d6d6947 100644 --- a/ant/src/site/markdown/config-purge.md +++ b/ant/src/site/markdown/config-purge.md @@ -23,6 +23,7 @@ Advanced Configuration ==================== The following properties can be configured in the plugin. However, they are less frequently changed. -Property | Description | Default Value -----------------------|--------------------------------------------------------------------------------------------------|------------------ -hostedSuppressionsUrl | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml +Property | Description | Default Value +-----------------------------|--------------------------------------------------------------------------------------------------|------------------ +hostedSuppressionsUrl | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml +hostedSuppressionsAuthHeader | The authorization header to a mirrored copy of the hosted suppressions file for internet-constrained environments | diff --git a/ant/src/site/markdown/config-update.md b/ant/src/site/markdown/config-update.md index 11b7481da69..72d9663b0b2 100644 --- a/ant/src/site/markdown/config-update.md +++ b/ant/src/site/markdown/config-update.md @@ -51,5 +51,6 @@ databaseUser | The username used when connecting to the database. databasePassword | The password used when connecting to the database. |   hostedSuppressionsEnabled | Whether the hosted suppression file will be used. | true hostedSuppressionsUrl | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml +hostedSuppressionsAuthHeader | The authorization header to a mirrored copy of the hosted suppressions file for internet-constrained environments | hostedSuppressionsValidForHours | Sets the number of hours to wait before checking for new updates of the hosted suppressions file | 2 hostedSuppressionsForceUpdate | Sets whether the hosted suppressions file should update regardless of the `autoupdate` and validForHours settings | false \ No newline at end of file diff --git a/ant/src/site/markdown/configuration.md b/ant/src/site/markdown/configuration.md index 6d1fe3a0b75..20c0a948215 100644 --- a/ant/src/site/markdown/configuration.md +++ b/ant/src/site/markdown/configuration.md @@ -164,5 +164,6 @@ databaseUser | The username used when connecting to the database. databasePassword | The password used when connecting to the database. |   hostedSuppressionsEnabled | Whether the hosted suppression file will be used. | true hostedSuppressionsUrl | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml +hostedSuppressionsAuthHeader | The authorization header to a mirrored copy of the hosted suppressions file for internet-constrained environments | hostedSuppressionsValidForHours | Sets the number of hours to wait before checking for new updates of the hosted suppressions file | 2 hostedSuppressionsForceUpdate | Sets whether the hosted suppressions file should update regardless of the `autoupdate` and validForHours settings | false \ No newline at end of file diff --git a/cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/cli/src/main/java/org/owasp/dependencycheck/CliParser.java index 4afa1c479ab..356398084f9 100644 --- a/cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -530,7 +530,10 @@ private void addAdvancedOptions(final Options options) { .addOption(newOptionWithArg(ARGUMENT.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS, "hours", "The number of hours to wait before checking for new updates of the the hosted suppressions file.")) .addOption(newOptionWithArg(ARGUMENT.HOSTED_SUPPRESSIONS_URL, "url", - "The URL for a mirrored hosted suppressions file")); + "The URL for a mirrored hosted suppressions file")) + .addOption(newOptionWithArg(ARGUMENT.HOSTED_SUPPRESSIONS_AUTH_HEADER, "authorization header", + "The authorization header for a mirrored hosted suppressions file")) + ; } @@ -1600,5 +1603,10 @@ public static class ARGUMENT { * suppressions file . */ public static final String HOSTED_SUPPRESSIONS_URL = "hostedSuppressionsUrl"; + /** + * The CLI argument to set the location of a mirrored hosted + * suppressions file authorization header. + */ + public static final String HOSTED_SUPPRESSIONS_AUTH_HEADER = "hostedSuppressionsAuthHeader"; } } diff --git a/cli/src/main/resources/completion-for-dependency-check.sh b/cli/src/main/resources/completion-for-dependency-check.sh index 09ffff27370..34a0140bbb4 100755 --- a/cli/src/main/resources/completion-for-dependency-check.sh +++ b/cli/src/main/resources/completion-for-dependency-check.sh @@ -78,6 +78,7 @@ _odc_completions() --hostedSuppressionsForceUpdate --hostedSuppressionsValidForHours --hostedSuppressionsUrl + --hostedSuppressionsAuthHeader --junitFailOnCVSS -l --log -n --noupdate diff --git a/cli/src/site/markdown/arguments.md b/cli/src/site/markdown/arguments.md index a60acf72a5f..2376ab59aa0 100644 --- a/cli/src/site/markdown/arguments.md +++ b/cli/src/site/markdown/arguments.md @@ -130,3 +130,4 @@ Advanced Options | | \-\-hostedSuppressionsForceUpdate | | Whether the hosted suppressions file will update regardless of the `noupdate` argument. | false | | | \-\-hostedSuppressionsValidForHours | \ | The number of hours to wait before checking for new updates of the hosted suppressions file | 2 | | | \-\-hostedSuppressionsUrl | \ | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml | +| | \-\-hostedSuppressionsAuthHeader | \ | The authorization header to a mirrored copy of the hosted suppressions file for internet-constrained environments | | diff --git a/core/src/main/java/org/owasp/dependencycheck/data/update/HostedSuppressionsDataSource.java b/core/src/main/java/org/owasp/dependencycheck/data/update/HostedSuppressionsDataSource.java index e50d12af9ad..71bfaf9417a 100644 --- a/core/src/main/java/org/owasp/dependencycheck/data/update/HostedSuppressionsDataSource.java +++ b/core/src/main/java/org/owasp/dependencycheck/data/update/HostedSuppressionsDataSource.java @@ -132,7 +132,11 @@ private void fetchHostedSuppressions(Settings settings, URL repoUrl, File repoFi if (LOGGER.isDebugEnabled()) { LOGGER.debug("Hosted Suppressions URL: {}", repoUrl.toExternalForm()); } - Downloader.getInstance().fetchFile(repoUrl, repoFile); + LOGGER.trace("Downloading Hosted Suppressions file from '{}'", repoUrl); + Downloader.getInstance().fetchFile(repoUrl, repoFile, + settings.useProxy(), + Settings.KEYS.HOSTED_SUPPRESSIONS_USER, Settings.KEYS.HOSTED_SUPPRESSIONS_PASSWORD, + Downloader.NO_PROPERTY_DEFINED, Settings.KEYS.HOSTED_SUPPRESSIONS_AUTH_HEADER); } catch (IOException | TooManyRequestsException | ResourceNotFoundException | WriteLockException ex) { throw new UpdateException("Failed to update the hosted suppressions file", ex); } diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 2e05a2c2fc8..195451e3dbd 100644 --- a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -1026,6 +1026,12 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma @SuppressWarnings("CanBeFinal") @Parameter(property = "hostedSuppressionsUrl") private String hostedSuppressionsUrl; + /** + * The hosted suppressions authorization header. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "hostedSuppressionsAuthHeader") + private String hostedSuppressionsAuthHeader; /** * Whether the hosted suppressions file will be updated regardless of the * `autoupdate` settings. @@ -2379,6 +2385,7 @@ protected void populateSettings() { } settings.setIntIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS, hostedSuppressionsValidForHours); settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, hostedSuppressionsUrl); + settings.setStringIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_AUTH_HEADER, hostedSuppressionsAuthHeader); settings.setBooleanIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_FORCEUPDATE, hostedSuppressionsForceUpdate); settings.setBooleanIfNotNull(Settings.KEYS.HOSTED_SUPPRESSIONS_ENABLED, hostedSuppressionsEnabled); } diff --git a/maven/src/site/markdown/configuration.md b/maven/src/site/markdown/configuration.md index 1b3caeaec02..c1a47e44ce0 100644 --- a/maven/src/site/markdown/configuration.md +++ b/maven/src/site/markdown/configuration.md @@ -173,6 +173,7 @@ databasePassword | The password used when connecting to the database. hostedSuppressionsEnabled | Whether the hosted suppressions file will be used. | true hostedSuppressionsForceUpdate | Whether the hosted suppressions file will update regardless of the `autoupdate` setting. | false hostedSuppressionsUrl | The URL to a mirrored copy of the hosted suppressions file for internet-constrained environments. | https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml +hostedSuppressionsAuthHeader | The authorization header to a mirrored copy of the hosted suppressions file for internet-constrained environments. | hostedSuppressionsValidForHours| Sets the number of hours to wait before checking for new updates from the NVD. | 2 retireJsUrlServerId | The id of a server defined in the settings.xml to retrieve the credentials (username and password) to connect to RetireJS instance. |   retireJsUser | If you don't want register user/password in settings.xml, you can specify user. |   diff --git a/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java b/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java new file mode 100644 index 00000000000..dc5335596b4 --- /dev/null +++ b/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java @@ -0,0 +1,192 @@ +package org.owasp.dependencycheck.utils; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.regex.Pattern; + +import org.apache.hc.client5.http.auth.BearerToken; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.StandardAuthScheme; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; + +public class CredentialHelper { + public static Credentials getCredentials(String messageScope, String theUser, char[] thePass, char[] theToken, char[] theAuth) throws InvalidSettingException { + Credentials _creds = null; + + // create a basic auth for user and password, if provided + if (theUser != null && !theUser.isBlank()) { + if(thePass == null || thePass.length == 0) + throw new InvalidSettingException("No password provided for user " + theUser + " for " + messageScope); + try { + _creds = new UsernamePasswordCredentials(theUser, thePass); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: invalid user or password"); + } + } + + // create a bearer token, if provided, takes precedence over user/password/token + if (theToken != null && theToken.length > 0) { + _creds = new BearerToken(new String(theToken)); + } + + // if an auth has been passed, takes precedence over user/password/token + if (theAuth != null && theAuth.length > 0) { + if(startsWith(theAuth,StandardAuthScheme.BASIC)) { + _creds = getBasicCredentialsFromAuthHeader(theAuth); + } else if(startsWith(theAuth, StandardAuthScheme.BEARER)) { + _creds = getBearerCredentialsFromAuthHeader(theAuth); + } else + throw new InvalidSettingException("Invalid authentication provided: unknown authentication scheme. " + + "Supported authentication schemes: " + + StandardAuthScheme.BASIC + " and " + StandardAuthScheme.BEARER); + } + return _creds; + } + + /** + * Create a bearer token credentials from the auth header + * @param authHeader + * @return credentials + * @throws InvalidSettingException + */ + protected static Credentials getBearerCredentialsFromAuthHeader(char[] authHeader) throws InvalidSettingException { + if (authHeader == null || authHeader.length == 0) + throw new InvalidSettingException("empty authentication header"); + try { + // get token + String token = new String(authHeader); + if(!token.startsWith(StandardAuthScheme.BEARER + " ")) + throw new InvalidSettingException("auth header should start with [" + StandardAuthScheme.BEARER + " ]"); + token = token.replaceAll("^" + Pattern.quote(StandardAuthScheme.BEARER),"").trim(); + if(token.isBlank()) + throw new InvalidSettingException("empty bearer token"); + return new BearerToken(token); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); + } + } + + /** + * Create a basic credentials from the basic auth header + * @param authHeader + * @return credentials + * @throws InvalidSettingException + */ + protected static Credentials getBasicCredentialsFromAuthHeader(char[] authHeader) throws InvalidSettingException { + if (authHeader == null || authHeader.length == 0) + throw new InvalidSettingException("empty authentication header"); + try { + // decode B64 to get user and password + String user = getBasicUser(authHeader, StandardAuthScheme.BASIC.length()+1); + return new UsernamePasswordCredentials( + user, + getBasicPassword(authHeader, StandardAuthScheme.BASIC.length()+1) + ); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); + } + } + + + + /** + * copy a char array into an array of bytes without using a String + * @param in + * @param start where to start in the array if chars + * @return byte array + * @throws InvalidSettingException + */ + protected static byte[] toBytes(char[] in, int start) throws InvalidSettingException { + if (in == null || in.length == 0) + throw new InvalidSettingException("Invalid authentication provided"); + if(start >= in.length) + throw new InvalidSettingException("Invalid authentication provided"); + char[] chars = new char[in.length - start]; + for(int i = start; i < in.length; i++) { + chars[i-start] = in[i]; + } + CharBuffer charBuffer = CharBuffer.wrap(chars); + ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), + byteBuffer.position(), byteBuffer.limit()); + Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data + return bytes; + } + + /** + * get the user out of a b64 string (Basic auth) + * @param in + * @param start + * @return + * @throws InvalidSettingException + */ + protected static String getBasicUser(char[] in, int start) throws InvalidSettingException { + if (in == null || in.length == 0) + throw new InvalidSettingException("Invalid authentication string"); + if(start >= in.length) + throw new InvalidSettingException("authentication string too short"); + byte[] src = toBytes(in, start); + if(src == null || src.length ==0) + return null; + byte[] decoded = Base64.getDecoder().decode(src); + String user = ""; + for(int i = 0; i < decoded.length; i++) { + if(decoded[i]!=':') user+=(char)decoded[i]; + else return user; + } + throw new InvalidSettingException("unable to find user"); + } + + /** + * get the password out of a B64 string (Basic auth) + * @param in + * @param start + * @return + * @throws InvalidSettingException + */ + protected static char[] getBasicPassword(char[] in, int start) throws InvalidSettingException { + if (in == null || in.length == 0) + throw new InvalidSettingException("Invalid authentication string"); + if(start >= in.length) + throw new InvalidSettingException("authentication string too short"); + byte[] src = toBytes(in, start); + if(src == null || src.length ==0) + return null; + byte[] decoded = Base64.getDecoder().decode(src); + start = 0; + for(int i = 0; i < decoded.length && start == 0; i++) { + if(decoded[i]==':') start = i + 1; + } + if(start == 0 || start >= decoded.length) + throw new InvalidSettingException("unable to find password"); + + char[] password = new char[decoded.length - start]; + for(int i = start; i < decoded.length; i++) { + password[i - start] = (char) (decoded[i] & 0xFF) ; + } + return password; + } + + /** + * Utility function to perform startsWith on a char array + * @param in + * @param start + * @return + */ + protected static boolean startsWith(char[] in, String start) { + if (in == null || in.length == 0) + return false; + if (start == null || start.isBlank()) + return false; + if(start.length() > in.length) + return false; + for(int i=0;i 0 && (theUser == null || theURL == null)) { throw new InvalidSettingException(desc + " URL and username are required when setting " + desc + " password"); } + if (theToken.length > 0 && theURL == null) { + throw new InvalidSettingException(desc + " URL is required when setting " + desc + " token"); + } + if (theAuth.length > 0 && theURL == null) { + throw new InvalidSettingException(desc + " URL is required when setting " + desc + " authorization header"); + } try { final URL parsedURL = new URL(theURL); - addCredentials(store, desc, parsedURL, theUser, thePass); + Credentials creds = CredentialHelper.getCredentials(parsedURL.toString(), theUser, thePass, theToken, theAuth); + addCredentials(store, desc, parsedURL, creds); } catch (MalformedURLException e) { throw new InvalidSettingException(desc + " URL must be a valid URL", e); } } - private static void addCredentials(CredentialsStore credentialsStore, String messageScope, URL parsedURL, String theUser, char[] thePass) - throws InvalidSettingException { - final String theProtocol = parsedURL.getProtocol(); - if ("file".equals(theProtocol)) { - LOGGER.warn("Credentials are not supported for file-protocol, double-check your configuration options for {}.", messageScope); - return; - } else if ("http".equals(theProtocol)) { - LOGGER.warn("Insecure configuration: Basic Credentials are configured to be used over a plain http connection for {}. " - + "Consider migrating to https to guard the credentials.", messageScope); - } else if (!"https".equals(theProtocol)) { + + protected static void addCredentials(CredentialsStore credentialsStore, String messageScope, + URL parsedURL, Credentials creds) + throws InvalidSettingException { + final String theProtocol = parsedURL.getProtocol(); + if ("file".equals(theProtocol)) { + LOGGER.warn("Credentials are not supported for file-protocol, double-check your configuration options for {}.", messageScope); + return; + } else if ("http".equals(theProtocol)) { + LOGGER.warn("Insecure configuration: Credentials are configured to be used over a plain http connection for {}. " + + "Consider migrating to https to guard the credentials.", messageScope); + } else if (!"https".equals(theProtocol)) { throw new InvalidSettingException("Unsupported protocol in the " + messageScope - + " URL; only file, http and https are supported"); + + " URL; only file, http and https are supported"); } + final String theHost = parsedURL.getHost(); final int thePort = parsedURL.getPort(); - final Credentials creds = new UsernamePasswordCredentials(theUser, thePass); + + // add credentials to store + if(creds == null) { + LOGGER.info("No credentials passed for {}", messageScope); + return; + } + LOGGER.info("Adding {} credentials for {}", creds.getClass().getSimpleName(), messageScope); final AuthScope scope = new AuthScope(theProtocol, theHost, thePort, null, null); credentialsStore.setCredentials(scope, creds); } @@ -328,6 +383,7 @@ public void fetchFile(URL url, File outputPath) */ public void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { + LOGGER.trace("Fetching {}",url); try { if ("file".equals(url.getProtocol())) { final Path p = Paths.get(url.toURI()); @@ -368,6 +424,31 @@ private static void wrapAndThrowHttpResponseException(String url, HttpResponseEx throw new DownloadFailedException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre); } } + + /** + * Retrieves a file from a given URL using an ad-hoc created CredentialsProvider if needed + * and saves it to the outputPath. + * + * @param url the URL of the file to download + * @param outputPath the path to the save the file to + * @param useProxy whether to use the configured proxy when downloading + * files + * @param userKey the settings key for the username to be used + * @param passwordKey the settings key for the password to be used + * @throws DownloadFailedException is thrown if there is an error downloading the file + * @throws URLConnectionFailureException is thrown when certificate-chain trust errors occur downloading the file + * @throws TooManyRequestsException thrown when a 429 is received + * @throws ResourceNotFoundException thrown when a 404 is received + * @implNote This method should only be used in cases where the target host cannot be determined beforehand from settings, so that ad-hoc + * Credentials needs to be constructed for the target URL when the user/password keys point to configured credentials. The method delegates to + * {@link #fetchFile(URL, File, boolean)} when credentials are not configured for the given keys or the resource points to a file. + */ + public void fetchFile(URL url, File outputPath, boolean useProxy, + String userKey, String passwordKey) throws DownloadFailedException, + TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { + fetchFile(url, outputPath, useProxy, userKey, passwordKey, + NO_PROPERTY_DEFINED, NO_PROPERTY_DEFINED); + } /** * Retrieves a file from a given URL using an ad-hoc created CredentialsProvider if needed @@ -379,6 +460,8 @@ private static void wrapAndThrowHttpResponseException(String url, HttpResponseEx * files * @param userKey the settings key for the username to be used * @param passwordKey the settings key for the password to be used + * @param tokenKey the settings key for the token to be used + * @param authKey the settings key for the authorization header to be used * @throws DownloadFailedException is thrown if there is an error downloading the file * @throws URLConnectionFailureException is thrown when certificate-chain trust errors occur downloading the file * @throws TooManyRequestsException thrown when a 429 is received @@ -387,16 +470,22 @@ private static void wrapAndThrowHttpResponseException(String url, HttpResponseEx * Credentials needs to be constructed for the target URL when the user/password keys point to configured credentials. The method delegates to * {@link #fetchFile(URL, File, boolean)} when credentials are not configured for the given keys or the resource points to a file. */ - public void fetchFile(URL url, File outputPath, boolean useProxy, String userKey, String passwordKey) throws DownloadFailedException, + public void fetchFile(URL url, File outputPath, boolean useProxy, + String userKey, String passwordKey, + String tokenKey, String authKey) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { - if ("file".equals(url.getProtocol()) - || userKey == null || settings.getString(userKey) == null - || passwordKey == null || settings.getString(passwordKey) == null - ) { + boolean hasCredentials = settings != null && ( + !settings.getString(passwordKey, "").isBlank() + || !settings.getString(tokenKey, "").isBlank() + || !settings.getString(authKey, "").isBlank()); + LOGGER.debug("credentials defined for {}: {}", url, hasCredentials); + + if ("file".equals(url.getProtocol()) || !hasCredentials) { // no credentials configured, so use the default fetchFile fetchFile(url, outputPath, useProxy); return; } + LOGGER.trace("Fetching {} userkey={}, passwordKey={}, tokenKey={}, authKey={}",url, userKey, passwordKey, tokenKey, authKey); final String theProtocol = url.getProtocol(); if (!("http".equals(theProtocol) || "https".equals(theProtocol))) { throw new DownloadFailedException("Unsupported protocol in the URL; only file, http and https are supported"); @@ -404,7 +493,9 @@ public void fetchFile(URL url, File outputPath, boolean useProxy, String userKey try { final HttpClientContext context = HttpClientContext.create(); final BasicCredentialsProvider localCredentials = new BasicCredentialsProvider(); - addCredentials(localCredentials, url.toString(), url, settings.getString(userKey), settings.getString(passwordKey).toCharArray()); + Credentials creds = CredentialHelper.getCredentials(url.toString(),settings.getString(userKey), settings.getString(passwordKey, "").toCharArray(), + settings.getString(tokenKey, "").toCharArray(), settings.getString(authKey, "").toCharArray()); + addCredentials(localCredentials, url.toString(), url, creds); context.setCredentialsProvider(localCredentials); try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) { final BasicClassicHttpRequest req = new BasicClassicHttpRequest(Method.GET, url.toURI()); diff --git a/utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index fa398a87411..245bc3f5811 100644 --- a/utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -327,6 +327,12 @@ public static final class KEYS { */ public static final String HOSTED_SUPPRESSIONS_PASSWORD = "hosted.suppressions.password"; + /** + * The properties key for the hosted suppressions authorization header value. + * For use when hosted suppressions are mirrored locally on a site requiring authentication + */ + public static final String HOSTED_SUPPRESSIONS_AUTH_HEADER = "hosted.suppressions.auth.header"; + /** * The properties key for defining whether the hosted suppressions file * will be updated regardless of the autoupdate settings. @@ -1505,6 +1511,15 @@ public String getConnectionString(String connectionStringKey, String dbFileNameK return connStr; } + /** + * @return whether the proxy should be used + */ + public boolean useProxy() { + String proxyServer = getString(Settings.KEYS.PROXY_SERVER, ""); + return proxyServer!=null && !proxyServer.isEmpty(); + } + + /** * Retrieves the primary data directory that is used for caching web * content. diff --git a/utils/src/test/java/org/owasp/dependencycheck/utils/CredentialHelperTest.java b/utils/src/test/java/org/owasp/dependencycheck/utils/CredentialHelperTest.java new file mode 100644 index 00000000000..52627fb1b62 --- /dev/null +++ b/utils/src/test/java/org/owasp/dependencycheck/utils/CredentialHelperTest.java @@ -0,0 +1,261 @@ +package org.owasp.dependencycheck.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.URL; +import java.util.Base64; +import java.util.UUID; + +import org.apache.hc.client5.http.auth.BearerToken; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.StandardAuthScheme; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.junit.Test; + +public class CredentialHelperTest { + + // make sure that getting a string from settings for a non defined property key doesn't throw an exception and returns the default value + @Test + public void test_settings_null() throws Exception { + Settings settings = new Settings(); + String expected = UUID.randomUUID().toString(); + assertNotNull(settings.getString(Downloader.NO_PROPERTY_DEFINED, expected)); + assertEquals(expected, settings.getString(Downloader.NO_PROPERTY_DEFINED, expected)); + } + + @Test + public void testBaseFunctions() throws Exception { + String user = UUID.randomUUID().toString(); + String password = UUID.randomUUID().toString(); + String b64 = StandardAuthScheme.BASIC + " " + Base64.getEncoder().encodeToString((user+":"+password).getBytes()); + int start = StandardAuthScheme.BASIC.length() + 1; + + // starts with + assertTrue(CredentialHelper.startsWith(b64.toCharArray(), StandardAuthScheme.BASIC)); + assertFalse(CredentialHelper.startsWith(b64.toCharArray(), StandardAuthScheme.BEARER)); + + // Basic auth + assertEquals(user, CredentialHelper.getBasicUser(b64.toCharArray(), start)); + assertEquals(password, new String(CredentialHelper.getBasicPassword(b64.toCharArray(), start))); + } + + + + /////////////////////////////////////////////////// + // test basic auth methods + /////////////////////////////////////////////////// + + @Test + public void testGetBasicCredentials() throws Exception { + String user = "U-" + UUID.randomUUID().toString(); + String pass = "P-" + UUID.randomUUID().toString(); + String auth = StandardAuthScheme.BASIC + " " + Base64.getEncoder().encodeToString(((user + ":" + pass).getBytes())); + + + Credentials credentials = CredentialHelper.getBasicCredentialsFromAuthHeader(auth.toCharArray()); + assertNotNull(credentials); + assertTrue(credentials instanceof UsernamePasswordCredentials); + assertEquals(user, ((UsernamePasswordCredentials) credentials).getUserName()); + assertEquals(pass, new String(((UsernamePasswordCredentials) credentials).getUserPassword())); + + checkBasicException(null); + checkBasicException("".toCharArray()); + checkBasicException(" ".toCharArray()); + checkBasicException("12323333333333 33".toCharArray()); + auth = StandardAuthScheme.BASIC + " "; + checkBasicException(auth.toCharArray()); + auth = StandardAuthScheme.BASIC + " !!!!!!!!!!!!!!!!!!!!!!!!"; + checkBasicException(auth.toCharArray()); + auth = StandardAuthScheme.BASIC + " " + Base64.getEncoder().encodeToString(((user + ":" + pass).getBytes()))+"1"; + checkBasicException(auth.toCharArray()); + } + + @Test + public void testCredsBasic() throws Exception { + String user = "U1-" + UUID.randomUUID().toString(); + String password = "P1-" + UUID.randomUUID().toString(); + + // no auth + URL url = new URL("http://127.0.0.1/index.cgi"); + checkBasicCreds(url, user, password, "", "", user, password); + url = new URL("https://127.0.0.1/index.cgi"); + checkBasicCreds(url, user, password, "", "", user, password); + + // user, password and auth + String user2 = "U2-" + UUID.randomUUID().toString(); + String password2 = "P2-" + UUID.randomUUID().toString(); + String b64 = StandardAuthScheme.BASIC + " " + + Base64.getEncoder().encodeToString((user2+":"+password2).getBytes()); + url = new URL("http://127.0.0.1/index.cgi"); + checkBasicCreds(url, user, password, "", b64, user2, password2); + url = new URL("https://127.0.0.1/index.cgi"); + checkBasicCreds(url, user, password, "", b64, user2, password2); + + // only auth + url = new URL("http://127.0.0.1/index.cgi"); + checkBasicCreds(url, null, null, "", b64, user2, password2); + url = new URL("https://127.0.0.1/index.cgi"); + checkBasicCreds(url, null, null, "", b64, user2, password2); + } + + @Test + public void testCredsBasicException() throws Exception { + // no password + URL url = new URL("https://127.0.0.1/index.cgi"); + String pfx = "U-"; + checkException(url, pfx+UUID.randomUUID().toString(), null, null, "no password", pfx); + checkException(url, null, null, UUID.randomUUID().toString(), + "supported", StandardAuthScheme.BASIC, StandardAuthScheme.BEARER); + } + + + /////////////////////////////////////////////////// + // test bearer auth methods + /////////////////////////////////////////////////// + + @Test + public void testGetBearerCredentials() throws Exception { + String token = "token-" + UUID.randomUUID(); + String auth = StandardAuthScheme.BEARER + " " + token; + + Credentials credentials = CredentialHelper.getBearerCredentialsFromAuthHeader(auth.toCharArray()); + assertNotNull(credentials); + assertTrue(credentials instanceof BearerToken); + assertEquals(token, ((BearerToken) credentials).getToken()); + + checkBearerException(null); + checkBearerException(new char[0]); + auth = UUID.randomUUID().toString(); + checkBearerException(auth.toCharArray()); + auth = StandardAuthScheme.BEARER + UUID.randomUUID().toString(); + checkBearerException(auth.toCharArray()); + auth = StandardAuthScheme.BEARER + " "; + checkBearerException(auth.toCharArray()); + } + + @Test + public void testCredsTokenAuth() throws Exception { + String user = "U1-" + UUID.randomUUID().toString(); + String password = "P1-" + UUID.randomUUID().toString(); + String token = "token-" + UUID.randomUUID(); + String auth = StandardAuthScheme.BEARER + " " + token; + + // with user / password + URL url = new URL("http://127.0.0.1/index.cgi"); + checkTokenCreds(url, user, password, "", auth, token); + url = new URL("https://127.0.0.1/index.cgi"); + checkTokenCreds(url, user, password, "", auth, token); + + // without user / password + url = new URL("http://127.0.0.1/index.cgi"); + checkTokenCreds(url, null, null, "", auth, token); + url = new URL("https://127.0.0.1/index.cgi"); + checkTokenCreds(url, null, null, "", auth, token); + } + + + @Test + public void testCredsToken() throws Exception { + String token = "token-" + UUID.randomUUID(); + URL url; + + // without user / password + url = new URL("http://127.0.0.1/index.cgi"); + checkTokenCreds(url, null, null, token, "", token); + url = new URL("https://127.0.0.1/index.cgi"); + checkTokenCreds(url, null, null, token, "", token); + } + + @Test + public void testCredsTokenException() throws Exception { + String auth = StandardAuthScheme.BEARER + " "; + URL url = new URL("https://127.0.0.1/index.cgi"); + checkException(url, null, null, null, auth, "empty bearer token"); + checkException(url, null, null, null, StandardAuthScheme.BEARER, "should start with"); + } + + + + + /////////////////////////////////////////////////// + // private test methods + /////////////////////////////////////////////////// + + private void checkException(URL url, String user, String password, String token, + String auth, String ... messages) throws Exception { + if(password == null) password = ""; + if(auth == null) auth = ""; + if(token == null) token = ""; + try { + CredentialHelper.getCredentials(url.toString(), + user, password.toCharArray(), + token.toCharArray(), auth.toCharArray()); + throw new Exception("should have thrown an InvalidSettingException"); + } catch(InvalidSettingException ok) { + assertNotNull(ok); + if(messages==null || messages.length == 0) + return; + assertNotNull(ok.getMessage()); + for(String message : messages) { + assertTrue(ok.getMessage().toLowerCase().contains(message.toLowerCase())); + } + } + } + + + + + private void checkTokenCreds(URL url, String user, String password, String token, String auth, + String expectedToken) throws Exception { + if(password == null) password = ""; + if(auth == null) auth = ""; + if(token == null) token = ""; + Credentials creds = CredentialHelper.getCredentials(url.toString(), + user, password.toCharArray(), + token.toCharArray(), auth.toCharArray()); + + assertTrue(creds instanceof BearerToken); + BearerToken bearer = (BearerToken) creds; + assertEquals(expectedToken, bearer.getToken()); + } + + + private void checkBasicCreds(URL url, String user, String password, + String token, String auth, + String expectedUser, String expectedPassword) throws Exception { + if(password == null) password = ""; + if(token == null) token = ""; + Credentials creds = CredentialHelper.getCredentials(url.toString(), + user, password.toCharArray(), + token.toCharArray(), auth.toCharArray()); + + assertTrue(creds instanceof UsernamePasswordCredentials); + UsernamePasswordCredentials basicCreds = (UsernamePasswordCredentials) creds; + assertEquals(expectedUser, basicCreds.getUserName()); + assertEquals(expectedPassword, new String(basicCreds.getUserPassword())); + } + + private void checkBasicException(char[] auth) throws Exception { + try { + CredentialHelper.getBasicCredentialsFromAuthHeader(auth); + throw new Exception("should have thrown an InvalidSettingException"); + } catch(InvalidSettingException ok) { + assertNotNull(ok); + assertNotNull(ok.getMessage()); + } + } + + private void checkBearerException(char[] auth) throws Exception { + try { + CredentialHelper.getBearerCredentialsFromAuthHeader(auth); + throw new Exception("should have thrown an InvalidSettingException"); + } catch(InvalidSettingException ok) { + assertNotNull(ok); + assertNotNull(ok.getMessage()); + } + } + +} From f1185c05c23f04b1d34c37a74f1bc4988541aa0d Mon Sep 17 00:00:00 2001 From: ftiercelin Date: Sun, 22 Dec 2024 15:41:17 +0000 Subject: [PATCH 2/3] lint --- .../utils/CredentialHelper.java | 218 +++++++++--------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java b/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java index dc5335596b4..9fc4b17c3be 100644 --- a/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java +++ b/utils/src/main/java/org/owasp/dependencycheck/utils/CredentialHelper.java @@ -13,40 +13,40 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; public class CredentialHelper { - public static Credentials getCredentials(String messageScope, String theUser, char[] thePass, char[] theToken, char[] theAuth) throws InvalidSettingException { + public static Credentials getCredentials(String messageScope, String theUser, char[] thePass, char[] theToken, char[] theAuth) throws InvalidSettingException { Credentials _creds = null; - + // create a basic auth for user and password, if provided if (theUser != null && !theUser.isBlank()) { - if(thePass == null || thePass.length == 0) - throw new InvalidSettingException("No password provided for user " + theUser + " for " + messageScope); - try { - _creds = new UsernamePasswordCredentials(theUser, thePass); - } catch (Exception e) { - throw new InvalidSettingException("Invalid authentication provided: invalid user or password"); - } + if(thePass == null || thePass.length == 0) + throw new InvalidSettingException("No password provided for user " + theUser + " for " + messageScope); + try { + _creds = new UsernamePasswordCredentials(theUser, thePass); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: invalid user or password"); + } } // create a bearer token, if provided, takes precedence over user/password/token - if (theToken != null && theToken.length > 0) { - _creds = new BearerToken(new String(theToken)); - } + if (theToken != null && theToken.length > 0) { + _creds = new BearerToken(new String(theToken)); + } // if an auth has been passed, takes precedence over user/password/token if (theAuth != null && theAuth.length > 0) { - if(startsWith(theAuth,StandardAuthScheme.BASIC)) { - _creds = getBasicCredentialsFromAuthHeader(theAuth); - } else if(startsWith(theAuth, StandardAuthScheme.BEARER)) { - _creds = getBearerCredentialsFromAuthHeader(theAuth); - } else - throw new InvalidSettingException("Invalid authentication provided: unknown authentication scheme. " - + "Supported authentication schemes: " - + StandardAuthScheme.BASIC + " and " + StandardAuthScheme.BEARER); + if(startsWith(theAuth,StandardAuthScheme.BASIC)) { + _creds = getBasicCredentialsFromAuthHeader(theAuth); + } else if(startsWith(theAuth, StandardAuthScheme.BEARER)) { + _creds = getBearerCredentialsFromAuthHeader(theAuth); + } else + throw new InvalidSettingException("Invalid authentication provided: unknown authentication scheme. " + + "Supported authentication schemes: " + + StandardAuthScheme.BASIC + " and " + StandardAuthScheme.BEARER); } - return _creds; - } + return _creds; + } - /** + /** * Create a bearer token credentials from the auth header * @param authHeader * @return credentials @@ -54,139 +54,139 @@ public static Credentials getCredentials(String messageScope, String theUser, ch */ protected static Credentials getBearerCredentialsFromAuthHeader(char[] authHeader) throws InvalidSettingException { if (authHeader == null || authHeader.length == 0) - throw new InvalidSettingException("empty authentication header"); - try { - // get token - String token = new String(authHeader); - if(!token.startsWith(StandardAuthScheme.BEARER + " ")) - throw new InvalidSettingException("auth header should start with [" + StandardAuthScheme.BEARER + " ]"); - token = token.replaceAll("^" + Pattern.quote(StandardAuthScheme.BEARER),"").trim(); - if(token.isBlank()) - throw new InvalidSettingException("empty bearer token"); - return new BearerToken(token); - } catch (Exception e) { - throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); - } - } + throw new InvalidSettingException("empty authentication header"); + try { + // get token + String token = new String(authHeader); + if(!token.startsWith(StandardAuthScheme.BEARER + " ")) + throw new InvalidSettingException("auth header should start with [" + StandardAuthScheme.BEARER + " ]"); + token = token.replaceAll("^" + Pattern.quote(StandardAuthScheme.BEARER),"").trim(); + if(token.isBlank()) + throw new InvalidSettingException("empty bearer token"); + return new BearerToken(token); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); + } + } - /** + /** * Create a basic credentials from the basic auth header * @param authHeader * @return credentials * @throws InvalidSettingException */ - protected static Credentials getBasicCredentialsFromAuthHeader(char[] authHeader) throws InvalidSettingException { + protected static Credentials getBasicCredentialsFromAuthHeader(char[] authHeader) throws InvalidSettingException { if (authHeader == null || authHeader.length == 0) - throw new InvalidSettingException("empty authentication header"); - try { - // decode B64 to get user and password - String user = getBasicUser(authHeader, StandardAuthScheme.BASIC.length()+1); - return new UsernamePasswordCredentials( - user, - getBasicPassword(authHeader, StandardAuthScheme.BASIC.length()+1) - ); - } catch (Exception e) { - throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); - } - } + throw new InvalidSettingException("empty authentication header"); + try { + // decode B64 to get user and password + String user = getBasicUser(authHeader, StandardAuthScheme.BASIC.length() + 1); + return new UsernamePasswordCredentials( + user, + getBasicPassword(authHeader, StandardAuthScheme.BASIC.length() + 1) + ); + } catch (Exception e) { + throw new InvalidSettingException("Invalid authentication provided: " + e.getMessage()); + } + } - /** - * copy a char array into an array of bytes without using a String - * @param in - * @param start where to start in the array if chars - * @return byte array - * @throws InvalidSettingException - */ - protected static byte[] toBytes(char[] in, int start) throws InvalidSettingException { + /** + * copy a char array into an array of bytes without using a String + * @param in + * @param start where to start in the array if chars + * @return byte array + * @throws InvalidSettingException + */ + protected static byte[] toBytes(char[] in, int start) throws InvalidSettingException { if (in == null || in.length == 0) - throw new InvalidSettingException("Invalid authentication provided"); + throw new InvalidSettingException("Invalid authentication provided"); if(start >= in.length) - throw new InvalidSettingException("Invalid authentication provided"); + throw new InvalidSettingException("Invalid authentication provided"); char[] chars = new char[in.length - start]; for(int i = start; i < in.length; i++) { - chars[i-start] = in[i]; + chars[i-start] = in[i]; } CharBuffer charBuffer = CharBuffer.wrap(chars); ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), - byteBuffer.position(), byteBuffer.limit()); + byteBuffer.position(), byteBuffer.limit()); Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data return bytes; - } - - /** - * get the user out of a b64 string (Basic auth) - * @param in - * @param start - * @return - * @throws InvalidSettingException - */ - protected static String getBasicUser(char[] in, int start) throws InvalidSettingException { + } + + /** + * get the user out of a b64 string (Basic auth) + * @param in + * @param start + * @return + * @throws InvalidSettingException + */ + protected static String getBasicUser(char[] in, int start) throws InvalidSettingException { if (in == null || in.length == 0) - throw new InvalidSettingException("Invalid authentication string"); + throw new InvalidSettingException("Invalid authentication string"); if(start >= in.length) - throw new InvalidSettingException("authentication string too short"); + throw new InvalidSettingException("authentication string too short"); byte[] src = toBytes(in, start); if(src == null || src.length ==0) - return null; + return null; byte[] decoded = Base64.getDecoder().decode(src); String user = ""; for(int i = 0; i < decoded.length; i++) { - if(decoded[i]!=':') user+=(char)decoded[i]; - else return user; + if(decoded[i]!=':') user += (char) decoded[i]; + else return user; } - throw new InvalidSettingException("unable to find user"); - } - - /** - * get the password out of a B64 string (Basic auth) - * @param in - * @param start - * @return - * @throws InvalidSettingException - */ - protected static char[] getBasicPassword(char[] in, int start) throws InvalidSettingException { + throw new InvalidSettingException("unable to find user"); + } + + /** + * get the password out of a B64 string (Basic auth) + * @param in + * @param start + * @return + * @throws InvalidSettingException + */ + protected static char[] getBasicPassword(char[] in, int start) throws InvalidSettingException { if (in == null || in.length == 0) - throw new InvalidSettingException("Invalid authentication string"); + throw new InvalidSettingException("Invalid authentication string"); if(start >= in.length) - throw new InvalidSettingException("authentication string too short"); + throw new InvalidSettingException("authentication string too short"); byte[] src = toBytes(in, start); if(src == null || src.length ==0) - return null; + return null; byte[] decoded = Base64.getDecoder().decode(src); start = 0; for(int i = 0; i < decoded.length && start == 0; i++) { - if(decoded[i]==':') start = i + 1; + if(decoded[i] == ':') start = i + 1; } if(start == 0 || start >= decoded.length) - throw new InvalidSettingException("unable to find password"); + throw new InvalidSettingException("unable to find password"); char[] password = new char[decoded.length - start]; for(int i = start; i < decoded.length; i++) { - password[i - start] = (char) (decoded[i] & 0xFF) ; + password[i - start] = (char) (decoded[i] & 0xFF) ; } return password; - } + } - /** - * Utility function to perform startsWith on a char array - * @param in - * @param start - * @return - */ - protected static boolean startsWith(char[] in, String start) { + /** + * Utility function to perform startsWith on a char array + * @param in + * @param start + * @return + */ + protected static boolean startsWith(char[] in, String start) { if (in == null || in.length == 0) - return false; + return false; if (start == null || start.isBlank()) - return false; + return false; if(start.length() > in.length) - return false; - for(int i=0;i Date: Sun, 22 Dec 2024 15:43:11 +0000 Subject: [PATCH 3/3] lint --- .../dependencycheck/utils/Downloader.java | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java b/utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java index b531846eae9..1f9b39e0bd1 100644 --- a/utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java +++ b/utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java @@ -75,10 +75,10 @@ * @author Jeremy Long, Hans Aikema */ public final class Downloader { - /** - * No defined key for this property - */ - public static final String NO_PROPERTY_DEFINED = UUID.randomUUID().toString(); + /** + * No defined key for this property + */ + public static final String NO_PROPERTY_DEFINED = UUID.randomUUID().toString(); /** * The builder to use for a HTTP Client that uses the configured proxy-settings @@ -199,7 +199,7 @@ private void tryAddRetireJSCredentials(Settings settings, CredentialsStore crede private void tryAddHostedSuppressionCredentials(Settings settings, CredentialsStore credentialsStore) throws InvalidSettingException { if (settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_PASSWORD) != null - || settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_AUTH_HEADER) != null) { + || settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_AUTH_HEADER) != null) { addConfiguredCredentials(settings, credentialsStore, Settings.KEYS.HOSTED_SUPPRESSIONS_USER, Settings.KEYS.HOSTED_SUPPRESSIONS_URL, @@ -274,13 +274,13 @@ private void tryAddNVDApiDatafeed(Settings settings, CredentialsStore credential * @throws InvalidSettingException When the password is empty or one of the other keys are not found in the settings. */ private void addConfiguredCredentials(Settings settings, CredentialsStore store, - String userKey, String urlKey, String passwordKey, - String desc) + String userKey, String urlKey, String passwordKey, + String desc) throws InvalidSettingException { - addConfiguredCredentials(settings, store, - userKey, urlKey, passwordKey, - NO_PROPERTY_DEFINED, NO_PROPERTY_DEFINED, - desc); + addConfiguredCredentials(settings, store, + userKey, urlKey, passwordKey, + NO_PROPERTY_DEFINED, NO_PROPERTY_DEFINED, + desc); } /** @@ -297,9 +297,9 @@ private void addConfiguredCredentials(Settings settings, CredentialsStore store, * @throws InvalidSettingException When the password is empty or one of the other keys are not found in the settings. */ private void addConfiguredCredentials(Settings settings, CredentialsStore store, - String userKey, String urlKey, String passwordKey, - String tokenKey, String authKey, - String desc) + String userKey, String urlKey, String passwordKey, + String tokenKey, String authKey, + String desc) throws InvalidSettingException { final String theUser = settings.getString(userKey); final String theURL = settings.getString(urlKey); @@ -327,29 +327,29 @@ private void addConfiguredCredentials(Settings settings, CredentialsStore store, protected static void addCredentials(CredentialsStore credentialsStore, String messageScope, - URL parsedURL, Credentials creds) - throws InvalidSettingException { - final String theProtocol = parsedURL.getProtocol(); - if ("file".equals(theProtocol)) { - LOGGER.warn("Credentials are not supported for file-protocol, double-check your configuration options for {}.", messageScope); - return; - } else if ("http".equals(theProtocol)) { - LOGGER.warn("Insecure configuration: Credentials are configured to be used over a plain http connection for {}. " + URL parsedURL, Credentials creds) + throws InvalidSettingException { + final String theProtocol = parsedURL.getProtocol(); + if ("file".equals(theProtocol)) { + LOGGER.warn("Credentials are not supported for file-protocol, double-check your configuration options for {}.", messageScope); + return; + } else if ("http".equals(theProtocol)) { + LOGGER.warn("Insecure configuration: Credentials are configured to be used over a plain http connection for {}. " + "Consider migrating to https to guard the credentials.", messageScope); - } else if (!"https".equals(theProtocol)) { + } else if (!"https".equals(theProtocol)) { throw new InvalidSettingException("Unsupported protocol in the " + messageScope + " URL; only file, http and https are supported"); } - + final String theHost = parsedURL.getHost(); final int thePort = parsedURL.getPort(); - + // add credentials to store if(creds == null) { - LOGGER.info("No credentials passed for {}", messageScope); - return; + LOGGER.info("No credentials passed for {}", messageScope); + return; } - LOGGER.info("Adding {} credentials for {}", creds.getClass().getSimpleName(), messageScope); + LOGGER.info("Adding {} credentials for {}", creds.getClass().getSimpleName(), messageScope); final AuthScope scope = new AuthScope(theProtocol, theHost, thePort, null, null); credentialsStore.setCredentials(scope, creds); } @@ -383,7 +383,7 @@ public void fetchFile(URL url, File outputPath) */ public void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { - LOGGER.trace("Fetching {}",url); + LOGGER.trace("Fetching {}",url); try { if ("file".equals(url.getProtocol())) { final Path p = Paths.get(url.toURI()); @@ -444,10 +444,10 @@ private static void wrapAndThrowHttpResponseException(String url, HttpResponseEx * {@link #fetchFile(URL, File, boolean)} when credentials are not configured for the given keys or the resource points to a file. */ public void fetchFile(URL url, File outputPath, boolean useProxy, - String userKey, String passwordKey) throws DownloadFailedException, + String userKey, String passwordKey) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { - fetchFile(url, outputPath, useProxy, userKey, passwordKey, - NO_PROPERTY_DEFINED, NO_PROPERTY_DEFINED); + fetchFile(url, outputPath, useProxy, userKey, passwordKey, + NO_PROPERTY_DEFINED, NO_PROPERTY_DEFINED); } /** @@ -471,13 +471,13 @@ public void fetchFile(URL url, File outputPath, boolean useProxy, * {@link #fetchFile(URL, File, boolean)} when credentials are not configured for the given keys or the resource points to a file. */ public void fetchFile(URL url, File outputPath, boolean useProxy, - String userKey, String passwordKey, - String tokenKey, String authKey) throws DownloadFailedException, + String userKey, String passwordKey, + String tokenKey, String authKey) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException { boolean hasCredentials = settings != null && ( - !settings.getString(passwordKey, "").isBlank() - || !settings.getString(tokenKey, "").isBlank() - || !settings.getString(authKey, "").isBlank()); + !settings.getString(passwordKey, "").isBlank() + || !settings.getString(tokenKey, "").isBlank() + || !settings.getString(authKey, "").isBlank()); LOGGER.debug("credentials defined for {}: {}", url, hasCredentials); if ("file".equals(url.getProtocol()) || !hasCredentials) { @@ -485,7 +485,7 @@ public void fetchFile(URL url, File outputPath, boolean useProxy, fetchFile(url, outputPath, useProxy); return; } - LOGGER.trace("Fetching {} userkey={}, passwordKey={}, tokenKey={}, authKey={}",url, userKey, passwordKey, tokenKey, authKey); + LOGGER.trace("Fetching {} userkey={}, passwordKey={}, tokenKey={}, authKey={}",url, userKey, passwordKey, tokenKey, authKey); final String theProtocol = url.getProtocol(); if (!("http".equals(theProtocol) || "https".equals(theProtocol))) { throw new DownloadFailedException("Unsupported protocol in the URL; only file, http and https are supported"); @@ -494,7 +494,7 @@ public void fetchFile(URL url, File outputPath, boolean useProxy, final HttpClientContext context = HttpClientContext.create(); final BasicCredentialsProvider localCredentials = new BasicCredentialsProvider(); Credentials creds = CredentialHelper.getCredentials(url.toString(),settings.getString(userKey), settings.getString(passwordKey, "").toCharArray(), - settings.getString(tokenKey, "").toCharArray(), settings.getString(authKey, "").toCharArray()); + settings.getString(tokenKey, "").toCharArray(), settings.getString(authKey, "").toCharArray()); addCredentials(localCredentials, url.toString(), url, creds); context.setCredentialsProvider(localCredentials); try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {