001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl.resolution; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.IOException; 026import java.io.UncheckedIOException; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.Set; 032 033import org.eclipse.aether.RepositorySystemSession; 034import org.eclipse.aether.artifact.Artifact; 035import org.eclipse.aether.repository.ArtifactRepository; 036import org.eclipse.aether.resolution.ArtifactResult; 037import org.eclipse.aether.spi.checksums.TrustedChecksumsSource; 038import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; 039import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector; 040import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper; 041import org.eclipse.aether.transfer.ChecksumFailureException; 042import org.eclipse.aether.util.ConfigUtils; 043import org.eclipse.aether.util.artifact.ArtifactIdUtils; 044 045import static java.util.Objects.requireNonNull; 046 047/** 048 * Artifact resolver processor that verifies the checksums of all resolved artifacts against trusted checksums. Is also 049 * able to "record" (calculate and write them) to trusted checksum sources, that do support this operation. 050 * <p> 051 * It uses a list of {@link ChecksumAlgorithmFactory}ies to work with, by default SHA-1. 052 * <p> 053 * Configuration keys: 054 * <ul> 055 * <li>{@code aether.artifactResolver.postProcessor.trustedChecksums.checksumAlgorithms} - Comma separated 056 * list of {@link ChecksumAlgorithmFactory} names to use (default "SHA-1").</li> 057 * <li>{@code aether.artifactResolver.postProcessor.trustedChecksums.failIfMissing} - To fail if artifact 058 * being validated is missing a trusted checksum (default {@code false}).</li> 059 * <li>{@code aether.artifactResolver.postProcessor.trustedChecksums.snapshots} - Should snapshot artifacts be 060 * handled (validated or recorded). Snapshots are by "best practice" in-house produced, hence should be trusted 061 * (default {@code false}).</li> 062 * <li>{@code aether.artifactResolver.postProcessor.trustedChecksums.record} - If this value set to {@code true}, 063 * this component with not validate but "record" encountered artifact checksums instead 064 * (default {@code false}).</li> 065 * </ul> 066 * <p> 067 * This component uses {@link TrustedChecksumsSource} as source of checksums for validation and also to "record" the 068 * calculated checksums. To have this component usable, there must exist at least one enabled checksum source. In case 069 * of multiple checksum sources enabled, ALL of them are used as source for validation or recording. This 070 * implies that if two enabled checksum sources "disagree" about an artifact checksum, the validation failure is 071 * inevitable. 072 * 073 * @since 1.9.0 074 */ 075@Singleton 076@Named(TrustedChecksumsArtifactResolverPostProcessor.NAME) 077public final class TrustedChecksumsArtifactResolverPostProcessor extends ArtifactResolverPostProcessorSupport { 078 public static final String NAME = "trustedChecksums"; 079 080 private static final String CONFIG_PROPS_PREFIX = 081 ArtifactResolverPostProcessorSupport.CONFIG_PROPS_PREFIX + NAME + "."; 082 083 /** 084 * Is post processor enabled. 085 * 086 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 087 * @configurationType {@link java.lang.Boolean} 088 * @configurationDefaultValue false 089 */ 090 public static final String CONFIG_PROP_ENABLED = ArtifactResolverPostProcessorSupport.CONFIG_PROPS_PREFIX + NAME; 091 092 /** 093 * The checksum algorithms to apply during post-processing as comma separated list. 094 * 095 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 096 * @configurationType {@link java.lang.String} 097 * @configurationDefaultValue {@link #DEFAULT_CHECKSUM_ALGORITHMS} 098 */ 099 public static final String CONFIG_PROP_CHECKSUM_ALGORITHMS = CONFIG_PROPS_PREFIX + "checksumAlgorithms"; 100 101 public static final String DEFAULT_CHECKSUM_ALGORITHMS = "SHA-1"; 102 103 /** 104 * Should post processor fail resolution if checksum is missing? 105 * 106 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 107 * @configurationType {@link java.lang.Boolean} 108 * @configurationDefaultValue false 109 */ 110 public static final String CONFIG_PROP_FAIL_IF_MISSING = CONFIG_PROPS_PREFIX + "failIfMissing"; 111 112 /** 113 * Should post processor process snapshots as well? 114 * 115 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 116 * @configurationType {@link java.lang.Boolean} 117 * @configurationDefaultValue false 118 */ 119 public static final String CONFIG_PROP_SNAPSHOTS = CONFIG_PROPS_PREFIX + "snapshots"; 120 121 /** 122 * Should post processor go into "record" mode (and collect checksums instead of validate them)? 123 * 124 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 125 * @configurationType {@link java.lang.Boolean} 126 * @configurationDefaultValue false 127 */ 128 public static final String CONFIG_PROP_RECORD = CONFIG_PROPS_PREFIX + "record"; 129 130 private static final String CHECKSUM_ALGORITHMS_CACHE_KEY = 131 TrustedChecksumsArtifactResolverPostProcessor.class.getName() + ".checksumAlgorithms"; 132 133 private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector; 134 135 private final Map<String, TrustedChecksumsSource> trustedChecksumsSources; 136 137 @Inject 138 public TrustedChecksumsArtifactResolverPostProcessor( 139 ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector, 140 Map<String, TrustedChecksumsSource> trustedChecksumsSources) { 141 this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector); 142 this.trustedChecksumsSources = requireNonNull(trustedChecksumsSources); 143 } 144 145 @Override 146 protected boolean isEnabled(RepositorySystemSession session) { 147 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED); 148 } 149 150 @SuppressWarnings("unchecked") 151 @Override 152 protected void doPostProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) { 153 final List<ChecksumAlgorithmFactory> checksumAlgorithms = (List<ChecksumAlgorithmFactory>) session.getData() 154 .computeIfAbsent( 155 CHECKSUM_ALGORITHMS_CACHE_KEY, 156 () -> checksumAlgorithmFactorySelector.selectList( 157 ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString( 158 session, DEFAULT_CHECKSUM_ALGORITHMS, CONFIG_PROP_CHECKSUM_ALGORITHMS)))); 159 160 final boolean failIfMissing = ConfigUtils.getBoolean(session, false, CONFIG_PROP_FAIL_IF_MISSING); 161 final boolean record = ConfigUtils.getBoolean(session, false, CONFIG_PROP_RECORD); 162 final boolean snapshots = ConfigUtils.getBoolean(session, false, CONFIG_PROP_SNAPSHOTS); 163 164 for (ArtifactResult artifactResult : artifactResults) { 165 if (artifactResult.getArtifact().isSnapshot() && !snapshots) { 166 continue; 167 } 168 if (artifactResult.isResolved()) { 169 if (record) { 170 recordArtifactChecksums(session, artifactResult, checksumAlgorithms); 171 } else if (!validateArtifactChecksums(session, artifactResult, checksumAlgorithms, failIfMissing)) { 172 artifactResult.setArtifact(artifactResult.getArtifact().setFile(null)); // make it unresolved 173 } 174 } 175 } 176 } 177 178 /** 179 * Calculates and records checksums into trusted sources that support writing. 180 */ 181 private void recordArtifactChecksums( 182 RepositorySystemSession session, 183 ArtifactResult artifactResult, 184 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) { 185 Artifact artifact = artifactResult.getArtifact(); 186 ArtifactRepository artifactRepository = artifactResult.getRepository(); 187 try { 188 final Map<String, String> calculatedChecksums = 189 ChecksumAlgorithmHelper.calculate(artifact.getFile(), checksumAlgorithmFactories); 190 191 for (TrustedChecksumsSource trustedChecksumsSource : trustedChecksumsSources.values()) { 192 TrustedChecksumsSource.Writer writer = 193 trustedChecksumsSource.getTrustedArtifactChecksumsWriter(session); 194 if (writer != null) { 195 try { 196 writer.addTrustedArtifactChecksums( 197 artifact, artifactRepository, checksumAlgorithmFactories, calculatedChecksums); 198 } catch (IOException e) { 199 throw new UncheckedIOException( 200 "Could not write required checksums for " + artifact.getFile(), e); 201 } 202 } 203 } 204 } catch (IOException e) { 205 throw new UncheckedIOException("Could not calculate required checksums for " + artifact.getFile(), e); 206 } 207 } 208 209 /** 210 * Validates trusted checksums against {@link ArtifactResult}, returns {@code true} denoting "valid" checksums or 211 * {@code false} denoting "invalid" checksums. 212 */ 213 private boolean validateArtifactChecksums( 214 RepositorySystemSession session, 215 ArtifactResult artifactResult, 216 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories, 217 boolean failIfMissing) { 218 Artifact artifact = artifactResult.getArtifact(); 219 ArtifactRepository artifactRepository = artifactResult.getRepository(); 220 boolean valid = true; 221 boolean validated = false; 222 try { 223 // full set: calculate all algorithms we were asked for 224 final Map<String, String> calculatedChecksums = 225 ChecksumAlgorithmHelper.calculate(artifact.getFile(), checksumAlgorithmFactories); 226 227 for (Map.Entry<String, TrustedChecksumsSource> entry : trustedChecksumsSources.entrySet()) { 228 final String trustedSourceName = entry.getKey(); 229 final TrustedChecksumsSource trustedChecksumsSource = entry.getValue(); 230 231 // upper bound set: ask source for checksums, ideally same as calculatedChecksums but may be less 232 Map<String, String> trustedChecksums = trustedChecksumsSource.getTrustedArtifactChecksums( 233 session, artifact, artifactRepository, checksumAlgorithmFactories); 234 235 if (trustedChecksums == null) { 236 continue; // not enabled 237 } 238 validated = true; 239 240 if (!calculatedChecksums.equals(trustedChecksums)) { 241 Set<String> missingTrustedAlg = new HashSet<>(calculatedChecksums.keySet()); 242 missingTrustedAlg.removeAll(trustedChecksums.keySet()); 243 244 if (!missingTrustedAlg.isEmpty() && failIfMissing) { 245 artifactResult.addException(new ChecksumFailureException("Missing from " + trustedSourceName 246 + " trusted checksum(s) " + missingTrustedAlg + " for artifact " 247 + ArtifactIdUtils.toId(artifact))); 248 valid = false; 249 } 250 251 // compare values but only present ones, failIfMissing handled above 252 // we still want to report all: algX - missing, algY - mismatch, etc 253 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { 254 String calculatedChecksum = calculatedChecksums.get(checksumAlgorithmFactory.getName()); 255 String trustedChecksum = trustedChecksums.get(checksumAlgorithmFactory.getName()); 256 if (trustedChecksum != null && !Objects.equals(calculatedChecksum, trustedChecksum)) { 257 artifactResult.addException(new ChecksumFailureException("Artifact " 258 + ArtifactIdUtils.toId(artifact) + " trusted checksum mismatch: " 259 + trustedSourceName + "=" + trustedChecksum + "; calculated=" 260 + calculatedChecksum)); 261 valid = false; 262 } 263 } 264 } 265 } 266 267 if (!validated && failIfMissing) { 268 artifactResult.addException(new ChecksumFailureException( 269 "There are no enabled trusted checksums" + " source(s) to validate against.")); 270 valid = false; 271 } 272 } catch (IOException e) { 273 throw new UncheckedIOException(e); 274 } 275 return valid; 276 } 277}