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