You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2022/01/31 11:26:00 UTC

[maven-resolver] branch master updated: [MRESOLVER-234] Provided checksums (#141)

This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new e61c081  [MRESOLVER-234] Provided checksums (#141)
e61c081 is described below

commit e61c081d446acaa9547bb7aff57d28bc8498524d
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Mon Jan 31 12:25:56 2022 +0100

    [MRESOLVER-234] Provided checksums (#141)
    
    This feature allows Resolver to get "provided" checksums ahead
    of remote transport (resolution), hence, it may operate with
    "good known" checksums for example (or use any other source).
---
 .../connector/basic/BasicRepositoryConnector.java  |  28 +++-
 .../basic/BasicRepositoryConnectorFactory.java     |  33 +++-
 .../aether/connector/basic/ChecksumValidator.java  |  49 ++++--
 .../connector/basic/ChecksumValidatorTest.java     |  45 +++---
 .../basic/TestChecksumAlgorithmSelector.java       |   2 +-
 .../eclipse/aether/impl/guice/AetherModule.java    |  16 ++
 .../internal/impl/AbstractChecksumPolicy.java      |   8 +-
 .../internal/impl/FileProvidedChecksumsSource.java | 169 +++++++++++++++++++++
 .../impl/Maven2RepositoryLayoutFactory.java        |   5 +-
 .../DefaultChecksumAlgorithmFactorySelector.java   |  15 +-
 .../internal/impl/DefaultArtifactResolverTest.java |   2 -
 .../internal/impl/FailChecksumPolicyTest.java      |  21 ++-
 .../impl/FileProvidedChecksumsSourceTest.java      | 107 +++++++++++++
 .../impl/Maven2RepositoryLayoutFactoryTest.java    |   7 +-
 .../internal/impl/WarnChecksumPolicyTest.java      |  12 +-
 .../checksum/ChecksumAlgorithmFactorySelector.java |  11 +-
 .../spi/connector/checksum/ChecksumPolicy.java     |  62 ++++++--
 .../checksum/ProvidedChecksumsSource.java          |  47 ++++++
 .../spi/connector/layout/RepositoryLayout.java     |   6 +-
 .../aether/spi/connector/transport/GetTask.java    |  10 +-
 20 files changed, 560 insertions(+), 95 deletions(-)

diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
index a338c6d..c761941 100644
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
+++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java
@@ -50,6 +50,7 @@ import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider;
+import org.eclipse.aether.spi.connector.checksum.ProvidedChecksumsSource;
 import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
 import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider;
 import org.eclipse.aether.spi.connector.transport.GetTask;
@@ -88,6 +89,8 @@ final class BasicRepositoryConnector
 
     private static final Logger LOGGER = LoggerFactory.getLogger( BasicRepositoryConnector.class );
 
+    private final Map<String, ProvidedChecksumsSource> providedChecksumsSources;
+
     private final FileProcessor fileProcessor;
 
     private final RemoteRepository repository;
@@ -117,7 +120,8 @@ final class BasicRepositoryConnector
                               TransporterProvider transporterProvider,
                               RepositoryLayoutProvider layoutProvider,
                               ChecksumPolicyProvider checksumPolicyProvider,
-                              FileProcessor fileProcessor )
+                              FileProcessor fileProcessor,
+                              Map<String, ProvidedChecksumsSource> providedChecksumsSources )
             throws NoRepositoryConnectorException
     {
         try
@@ -141,6 +145,7 @@ final class BasicRepositoryConnector
         this.session = session;
         this.repository = repository;
         this.fileProcessor = fileProcessor;
+        this.providedChecksumsSources = providedChecksumsSources;
 
         maxThreads = ConfigUtils.getInteger( session, 5, CONFIG_PROP_THREADS, "maven.artifact.threads" );
         smartChecksums = ConfigUtils.getBoolean( session, true, CONFIG_PROP_SMART_CHECKSUMS );
@@ -239,12 +244,25 @@ final class BasicRepositoryConnector
             }
 
             Runnable task = new GetTaskRunner( location, transfer.getFile(), checksumPolicy,
-                    checksumLocations, listener );
+                    checksumLocations, null, listener );
             executor.execute( errorForwarder.wrap( task ) );
         }
 
         for ( ArtifactDownload transfer : safe( artifactDownloads ) )
         {
+            Map<String, String> providedChecksums = Collections.emptyMap();
+            for ( ProvidedChecksumsSource providedChecksumsSource : providedChecksumsSources.values() )
+            {
+                Map<String, String> provided = providedChecksumsSource.getProvidedArtifactChecksums(
+                    session, transfer, layout.getChecksumAlgorithmFactories() );
+
+                if ( provided != null )
+                {
+                    providedChecksums = provided;
+                    break;
+                }
+            }
+
             URI location = layout.getLocation( transfer.getArtifact(), false );
 
             TransferResource resource = newTransferResource( location, transfer.getFile(), transfer.getTrace() );
@@ -265,7 +283,8 @@ final class BasicRepositoryConnector
                     checksumLocations = layout.getChecksumLocations( transfer.getArtifact(), false, location );
                 }
 
-                task = new GetTaskRunner( location, transfer.getFile(), checksumPolicy, checksumLocations, listener );
+                task = new GetTaskRunner( location, transfer.getFile(), checksumPolicy,
+                    checksumLocations, providedChecksums, listener );
             }
             executor.execute( errorForwarder.wrap( task ) );
         }
@@ -416,12 +435,13 @@ final class BasicRepositoryConnector
 
         GetTaskRunner( URI path, File file, ChecksumPolicy checksumPolicy,
                        List<RepositoryLayout.ChecksumLocation> checksumLocations,
+                       Map<String, String> providedChecksums,
                        TransferTransportListener<?> listener )
         {
             super( path, listener );
             this.file = requireNonNull( file, "destination file cannot be null" );
             checksumValidator = new ChecksumValidator( file, fileProcessor, this,
-                    checksumPolicy, safe( checksumLocations ) );
+                    checksumPolicy, providedChecksums, safe( checksumLocations ) );
         }
 
         @Override
diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory.java
index aa747a9..280df6d 100644
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory.java
+++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory.java
@@ -19,6 +19,9 @@ package org.eclipse.aether.connector.basic;
  * under the License.
  */
 
+import java.util.Collections;
+import java.util.Map;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -29,6 +32,7 @@ import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.spi.connector.RepositoryConnector;
 import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider;
+import org.eclipse.aether.spi.connector.checksum.ProvidedChecksumsSource;
 import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider;
 import org.eclipse.aether.spi.connector.transport.TransporterProvider;
 import org.eclipse.aether.spi.io.FileProcessor;
@@ -53,6 +57,8 @@ public final class BasicRepositoryConnectorFactory
 
     private FileProcessor fileProcessor;
 
+    private Map<String, ProvidedChecksumsSource> providedChecksumsSources;
+
     private float priority;
 
     /**
@@ -66,13 +72,17 @@ public final class BasicRepositoryConnectorFactory
     }
 
     @Inject
-    BasicRepositoryConnectorFactory( TransporterProvider transporterProvider, RepositoryLayoutProvider layoutProvider,
-                                     ChecksumPolicyProvider checksumPolicyProvider, FileProcessor fileProcessor )
+    BasicRepositoryConnectorFactory( TransporterProvider transporterProvider,
+                                     RepositoryLayoutProvider layoutProvider,
+                                     ChecksumPolicyProvider checksumPolicyProvider,
+                                     FileProcessor fileProcessor,
+                                     Map<String, ProvidedChecksumsSource> providedChecksumsSources )
     {
         setTransporterProvider( transporterProvider );
         setRepositoryLayoutProvider( layoutProvider );
         setChecksumPolicyProvider( checksumPolicyProvider );
         setFileProcessor( fileProcessor );
+        setProvidedChecksumSources( providedChecksumsSources );
     }
 
     public void initService( ServiceLocator locator )
@@ -81,6 +91,7 @@ public final class BasicRepositoryConnectorFactory
         setRepositoryLayoutProvider( locator.getService( RepositoryLayoutProvider.class ) );
         setChecksumPolicyProvider( locator.getService( ChecksumPolicyProvider.class ) );
         setFileProcessor( locator.getService( FileProcessor.class ) );
+        setProvidedChecksumSources( Collections.emptyMap() );
     }
 
     /**
@@ -132,6 +143,22 @@ public final class BasicRepositoryConnectorFactory
         return this;
     }
 
+    /**
+     * Sets the provided checksum sources to use for this component.
+     *
+     * @param providedChecksumsSources The provided checksum sources to use, must not be {@code null}.
+     * @return This component for chaining, never {@code null}.
+     * @since 1.8.0
+     */
+    public BasicRepositoryConnectorFactory setProvidedChecksumSources(
+        Map<String, ProvidedChecksumsSource> providedChecksumsSources )
+    {
+        this.providedChecksumsSources = requireNonNull(
+            providedChecksumsSources, "provided checksum sources cannot be null"
+        );
+        return this;
+    }
+
     public float getPriority()
     {
         return priority;
@@ -156,7 +183,7 @@ public final class BasicRepositoryConnectorFactory
         requireNonNull( "repository", "repository cannot be null" );
 
         return new BasicRepositoryConnector( session, repository, transporterProvider, layoutProvider,
-                                             checksumPolicyProvider, fileProcessor );
+                                             checksumPolicyProvider, fileProcessor, providedChecksumsSources );
     }
 
 }
diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
index 2e4e3b7..cd2ddf7 100644
--- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
+++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/ChecksumValidator.java
@@ -30,6 +30,7 @@ import java.util.UUID;
 
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
+import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
 import org.eclipse.aether.spi.connector.layout.RepositoryLayout.ChecksumLocation;
 import org.eclipse.aether.spi.io.FileProcessor;
 import org.eclipse.aether.transfer.ChecksumFailureException;
@@ -45,6 +46,10 @@ final class ChecksumValidator
     interface ChecksumFetcher
     {
 
+        /**
+         * Fetches the checksums from remote location into provided local file. The checksums fetched in this way
+         * are of kind {@link ChecksumKind#REMOTE_EXTERNAL}.
+         */
         boolean fetchChecksum( URI remote, File local )
             throws Exception;
 
@@ -62,6 +67,8 @@ final class ChecksumValidator
 
     private final ChecksumPolicy checksumPolicy;
 
+    private final Map<String, String> providedChecksums;
+
     private final Collection<ChecksumLocation> checksumLocations;
 
     private final Map<File, Object> checksumFiles;
@@ -70,6 +77,7 @@ final class ChecksumValidator
                        FileProcessor fileProcessor,
                        ChecksumFetcher checksumFetcher,
                        ChecksumPolicy checksumPolicy,
+                       Map<String, String> providedChecksums,
                        Collection<ChecksumLocation> checksumLocations )
     {
         this.dataFile = dataFile;
@@ -77,6 +85,7 @@ final class ChecksumValidator
         this.fileProcessor = fileProcessor;
         this.checksumFetcher = checksumFetcher;
         this.checksumPolicy = checksumPolicy;
+        this.providedChecksums = providedChecksums;
         this.checksumLocations = checksumLocations;
         this.checksumFiles = new HashMap<>();
     }
@@ -90,14 +99,20 @@ final class ChecksumValidator
         return null;
     }
 
-    public void validate( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
+    public void validate( Map<String, ?> actualChecksums, Map<String, ?> includedChecksums )
         throws ChecksumFailureException
     {
         if ( checksumPolicy == null )
         {
             return;
         }
-        if ( inlinedChecksums != null && validateInlinedChecksums( actualChecksums, inlinedChecksums ) )
+        if ( providedChecksums != null
+               && validateChecksums( actualChecksums, ChecksumKind.PROVIDED, providedChecksums ) )
+        {
+            return;
+        }
+        if ( includedChecksums != null
+               && validateChecksums( actualChecksums, ChecksumKind.REMOTE_INCLUDED, includedChecksums ) )
         {
             return;
         }
@@ -108,10 +123,10 @@ final class ChecksumValidator
         checksumPolicy.onNoMoreChecksums();
     }
 
-    private boolean validateInlinedChecksums( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
+    private boolean validateChecksums( Map<String, ?> actualChecksums, ChecksumKind kind, Map<String, ?> checksums )
         throws ChecksumFailureException
     {
-        for ( Map.Entry<String, ?> entry : inlinedChecksums.entrySet() )
+        for ( Map.Entry<String, ?> entry : checksums.entrySet() )
         {
             String algo = entry.getKey();
             Object calculated = actualChecksums.get( algo );
@@ -135,10 +150,10 @@ final class ChecksumValidator
 
             if ( !isEqualChecksum( expected, actual ) )
             {
-                checksumPolicy.onChecksumMismatch( factory.getName(), ChecksumPolicy.KIND_UNOFFICIAL,
+                checksumPolicy.onChecksumMismatch( factory.getName(), kind,
                                                    new ChecksumFailureException( expected, actual ) );
             }
-            else if ( checksumPolicy.onChecksumMatch( factory.getName(), ChecksumPolicy.KIND_UNOFFICIAL ) )
+            else if ( checksumPolicy.onChecksumMatch( factory.getName(), kind ) )
             {
                 return true;
             }
@@ -156,7 +171,9 @@ final class ChecksumValidator
             if ( calculated instanceof Exception )
             {
                 checksumPolicy.onChecksumError(
-                        factory.getName(), 0, new ChecksumFailureException( (Exception) calculated ) );
+                        factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
+                        new ChecksumFailureException( (Exception) calculated )
+                );
                 continue;
             }
             try
@@ -165,14 +182,18 @@ final class ChecksumValidator
                 File tmp = createTempFile( checksumFile );
                 try
                 {
-                    if ( !checksumFetcher.fetchChecksum( checksumLocation.getLocation(), tmp ) )
+                    if ( !checksumFetcher.fetchChecksum(
+                        checksumLocation.getLocation(), tmp
+                    ) )
                     {
                         continue;
                     }
                 }
                 catch ( Exception e )
                 {
-                    checksumPolicy.onChecksumError( factory.getName(), 0, new ChecksumFailureException( e ) );
+                    checksumPolicy.onChecksumError(
+                        factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
+                    );
                     continue;
                 }
 
@@ -183,16 +204,20 @@ final class ChecksumValidator
                 if ( !isEqualChecksum( expected, actual ) )
                 {
                     checksumPolicy.onChecksumMismatch(
-                            factory.getName(), 0, new ChecksumFailureException( expected, actual ) );
+                        factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
+                            new ChecksumFailureException( expected, actual )
+                    );
                 }
-                else if ( checksumPolicy.onChecksumMatch( factory.getName(), 0 ) )
+                else if ( checksumPolicy.onChecksumMatch( factory.getName(), ChecksumKind.REMOTE_EXTERNAL ) )
                 {
                     return true;
                 }
             }
             catch ( IOException e )
             {
-                checksumPolicy.onChecksumError( factory.getName(), 0, new ChecksumFailureException( e ) );
+                checksumPolicy.onChecksumError(
+                    factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
+                );
             }
         }
         return false;
diff --git a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
index aac3c1b..5e4f121 100644
--- a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
+++ b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/ChecksumValidatorTest.java
@@ -57,9 +57,9 @@ public class ChecksumValidatorTest
         private Object conclusion;
 
         @Override
-        public boolean onChecksumMatch( String algorithm, int kind )
+        public boolean onChecksumMatch( String algorithm, ChecksumKind kind )
         {
-            callbacks.add( String.format( "match(%s, %04x)", algorithm, kind ) );
+            callbacks.add( String.format( "match(%s, %s)", algorithm, kind ) );
             if ( inspectAll )
             {
                 if ( conclusion == null )
@@ -72,10 +72,10 @@ public class ChecksumValidatorTest
         }
 
         @Override
-        public void onChecksumMismatch( String algorithm, int kind, ChecksumFailureException exception )
+        public void onChecksumMismatch( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
             throws ChecksumFailureException
         {
-            callbacks.add( String.format( "mismatch(%s, %04x)", algorithm, kind ) );
+            callbacks.add( String.format( "mismatch(%s, %s)", algorithm, kind ) );
             if ( inspectAll )
             {
                 conclusion = exception;
@@ -85,9 +85,9 @@ public class ChecksumValidatorTest
         }
 
         @Override
-        public void onChecksumError( String algorithm, int kind, ChecksumFailureException exception )
+        public void onChecksumError( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
         {
-            callbacks.add( String.format( "error(%s, %04x, %s)", algorithm, kind, exception.getCause().getMessage() ) );
+            callbacks.add( String.format( "error(%s, %s, %s)", algorithm, kind, exception.getCause().getMessage() ) );
         }
 
         @Override
@@ -201,7 +201,12 @@ public class ChecksumValidatorTest
 
     private ChecksumValidator newValidator( String... factories )
     {
-        return new ChecksumValidator( dataFile, new TestFileProcessor(), fetcher, policy, newChecksums( factories ) );
+        return newValidator( null, factories );
+    }
+
+    private ChecksumValidator newValidator( Map<String, String> providedChecksums, String... factories )
+    {
+        return new ChecksumValidator( dataFile, new TestFileProcessor(), fetcher, policy, providedChecksums, newChecksums( factories ) );
     }
 
     private Map<String, ?> checksums( String... algoDigestPairs )
@@ -251,7 +256,7 @@ public class ChecksumValidatorTest
         fetcher.mock( SHA1, "foo" );
         validator.validate( checksums( SHA1, "foo" ), null );
         fetcher.assertFetchedFiles( SHA1 );
-        policy.assertCallbacks( "match(SHA-1, 0000)" );
+        policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)" );
     }
 
     @Test
@@ -271,7 +276,7 @@ public class ChecksumValidatorTest
             assertTrue( e.isRetryWorthy() );
         }
         fetcher.assertFetchedFiles( SHA1 );
-        policy.assertCallbacks( "mismatch(SHA-1, 0000)" );
+        policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)" );
     }
 
     @Test
@@ -284,7 +289,7 @@ public class ChecksumValidatorTest
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( SHA1, "foo", MD5, "bar" ), null );
         fetcher.assertFetchedFiles( SHA1, MD5 );
-        policy.assertCallbacks( "match(SHA-1, 0000)", "match(MD5, 0000)", "noMore()" );
+        policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
     }
 
     @Test
@@ -306,21 +311,23 @@ public class ChecksumValidatorTest
             assertTrue( e.isRetryWorthy() );
         }
         fetcher.assertFetchedFiles( SHA1, MD5 );
-        policy.assertCallbacks( "mismatch(SHA-1, 0000)", "match(MD5, 0000)", "noMore()" );
+        policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
     }
 
     @Test
-    public void testValidate_InlinedBeforeExternal()
+    public void testValidate_IncludedBeforeExternal()
         throws Exception
     {
         policy.inspectAll = true;
-        ChecksumValidator validator = newValidator( SHA1, MD5 );
+        HashMap<String, String> provided = new HashMap<>();
+        provided.put( SHA1, "foo" );
+        ChecksumValidator validator = newValidator( provided, SHA1, MD5 );
         fetcher.mock( SHA1, "foo" );
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo", MD5, "bar" ) );
         fetcher.assertFetchedFiles( SHA1, MD5 );
-        policy.assertCallbacks( "match(SHA-1, 0001)", "match(MD5, 0001)", "match(SHA-1, 0000)", "match(MD5, 0000)",
-                                "noMore()" );
+        policy.assertCallbacks( "match(SHA-1, PROVIDED)", "match(SHA-1, REMOTE_INCLUDED)", "match(MD5, REMOTE_INCLUDED)",
+            "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
     }
 
     @Test
@@ -331,7 +338,7 @@ public class ChecksumValidatorTest
         ChecksumValidator validator = newValidator( SHA1 );
         fetcher.mock( SHA1, "FOO" );
         validator.validate( checksums( SHA1, "foo" ), checksums( SHA1, "foo" ) );
-        policy.assertCallbacks( "match(SHA-1, 0001)", "match(SHA-1, 0000)", "noMore()" );
+        policy.assertCallbacks( "match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()" );
     }
 
     @Test
@@ -342,7 +349,7 @@ public class ChecksumValidatorTest
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( MD5, "bar" ), null );
         fetcher.assertFetchedFiles( SHA1, MD5 );
-        policy.assertCallbacks( "match(MD5, 0000)" );
+        policy.assertCallbacks( "match(MD5, REMOTE_EXTERNAL)" );
     }
 
     @Test
@@ -354,7 +361,7 @@ public class ChecksumValidatorTest
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( MD5, "bar" ), null );
         fetcher.assertFetchedFiles( SHA1, MD5 );
-        policy.assertCallbacks( "error(SHA-1, 0000, inaccessible)", "match(MD5, 0000)" );
+        policy.assertCallbacks( "error(SHA-1, REMOTE_EXTERNAL, inaccessible)", "match(MD5, REMOTE_EXTERNAL)" );
     }
 
     @Test
@@ -366,7 +373,7 @@ public class ChecksumValidatorTest
         fetcher.mock( MD5, "bar" );
         validator.validate( checksums( SHA1, null, MD5, "bar" ), null );
         fetcher.assertFetchedFiles( MD5 );
-        policy.assertCallbacks( "error(SHA-1, 0000, error)", "match(MD5, 0000)" );
+        policy.assertCallbacks( "error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)" );
     }
 
     @Test
diff --git a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/TestChecksumAlgorithmSelector.java b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/TestChecksumAlgorithmSelector.java
index 8ebddaa..6a88470 100644
--- a/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/TestChecksumAlgorithmSelector.java
+++ b/maven-resolver-connector-basic/src/test/java/org/eclipse/aether/connector/basic/TestChecksumAlgorithmSelector.java
@@ -51,7 +51,7 @@ public class TestChecksumAlgorithmSelector
     public static final String TEST_CHECKSUM_VALUE = "01020304";
 
     @Override
-    public Set<String> getChecksumAlgorithmNames()
+    public Set<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories()
     {
         return Collections.emptySet(); // irrelevant
     }
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
index c324fa9..9629dc9 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
@@ -41,6 +41,7 @@ import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.impl.RepositoryConnectorProvider;
 import org.eclipse.aether.impl.RepositoryEventDispatcher;
 import org.eclipse.aether.internal.impl.DefaultTrackingFileManager;
+import org.eclipse.aether.internal.impl.FileProvidedChecksumsSource;
 import org.eclipse.aether.internal.impl.TrackingFileManager;
 import org.eclipse.aether.internal.impl.checksum.Md5ChecksumAlgorithmFactory;
 import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
@@ -83,6 +84,7 @@ import org.eclipse.aether.internal.impl.Maven2RepositoryLayoutFactory;
 import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
 import org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory;
 import org.eclipse.aether.named.providers.NoopNamedLockFactory;
+import org.eclipse.aether.spi.connector.checksum.ProvidedChecksumsSource;
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider;
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
@@ -168,6 +170,9 @@ public class AetherModule
                 .to( EnhancedLocalRepositoryManagerFactory.class ).in( Singleton.class );
         bind( TrackingFileManager.class ).to( DefaultTrackingFileManager.class ).in( Singleton.class );
 
+        bind( ProvidedChecksumsSource.class ).annotatedWith( Names.named( FileProvidedChecksumsSource.NAME ) ) //
+            .to( FileProvidedChecksumsSource.class ).in( Singleton.class );
+
         bind( ChecksumAlgorithmFactory.class ).annotatedWith( Names.named( Md5ChecksumAlgorithmFactory.NAME ) )
                 .to( Md5ChecksumAlgorithmFactory.class );
         bind( ChecksumAlgorithmFactory.class ).annotatedWith( Names.named( Sha1ChecksumAlgorithmFactory.NAME ) )
@@ -209,6 +214,17 @@ public class AetherModule
 
     @Provides
     @Singleton
+    Map<String, ProvidedChecksumsSource> provideChecksumSources(
+        @Named( FileProvidedChecksumsSource.NAME ) ProvidedChecksumsSource fileProvidedChecksumSource
+    )
+    {
+        Map<String, ProvidedChecksumsSource> providedChecksumsSource = new HashMap<>();
+        providedChecksumsSource.put( FileProvidedChecksumsSource.NAME, fileProvidedChecksumSource );
+        return providedChecksumsSource;
+    }
+
+    @Provides
+    @Singleton
     Map<String, ChecksumAlgorithmFactory> provideChecksumTypes(
             @Named( Sha512ChecksumAlgorithmFactory.NAME ) ChecksumAlgorithmFactory sha512,
             @Named( Sha256ChecksumAlgorithmFactory.NAME ) ChecksumAlgorithmFactory sha256,
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/AbstractChecksumPolicy.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/AbstractChecksumPolicy.java
index d5e1ecb..edb00f7 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/AbstractChecksumPolicy.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/AbstractChecksumPolicy.java
@@ -41,26 +41,26 @@ abstract class AbstractChecksumPolicy
     }
 
     @Override
-    public boolean onChecksumMatch( String algorithm, int kind )
+    public boolean onChecksumMatch( String algorithm, ChecksumKind kind )
     {
         requireNonNull( algorithm, "algorithm cannot be null" );
         return true;
     }
 
     @Override
-    public void onChecksumMismatch( String algorithm, int kind, ChecksumFailureException exception )
+    public void onChecksumMismatch( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
             throws ChecksumFailureException
     {
         requireNonNull( algorithm, "algorithm cannot be null" );
         requireNonNull( exception, "exception cannot be null" );
-        if ( ( kind & KIND_UNOFFICIAL ) == 0 )
+        if ( !kind.isIgnoreOnMismatch() )
         {
             throw exception;
         }
     }
 
     @Override
-    public void onChecksumError( String algorithm, int kind, ChecksumFailureException exception )
+    public void onChecksumError( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
             throws ChecksumFailureException
     {
         requireNonNull( algorithm, "algorithm cannot be null" );
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSource.java
new file mode 100644
index 0000000..2289fa5
--- /dev/null
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSource.java
@@ -0,0 +1,169 @@
+package org.eclipse.aether.internal.impl;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.spi.connector.ArtifactDownload;
+import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
+import org.eclipse.aether.spi.connector.checksum.ProvidedChecksumsSource;
+import org.eclipse.aether.spi.io.FileProcessor;
+import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.artifact.ArtifactIdUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Local filesystem backed {@link ProvidedChecksumsSource} implementation that use specified directory as base
+ * directory, where it expects artifacts checksums on standard Maven2 "local" layout. This implementation uses Artifact
+ * (and Metadata) coordinates solely to form path from baseDir (for Metadata file name is
+ * {@code maven-metadata-local.xml.sha1} in case of SHA-1 checksum).
+ *
+ * @since 1.8.0
+ */
+@Singleton
+@Named( FileProvidedChecksumsSource.NAME )
+public final class FileProvidedChecksumsSource
+    implements ProvidedChecksumsSource
+{
+    public static final String NAME = "file";
+
+    static final String CONFIG_PROP_BASE_DIR = "aether.artifactResolver.providedChecksumsSource.file.baseDir";
+
+    static final String LOCAL_REPO_PREFIX = ".checksums";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger( FileProvidedChecksumsSource.class );
+
+    private final FileProcessor fileProcessor;
+
+    private final SimpleLocalRepositoryManager simpleLocalRepositoryManager;
+
+    @Inject
+    public FileProvidedChecksumsSource( FileProcessor fileProcessor )
+    {
+        this.fileProcessor = requireNonNull( fileProcessor );
+        // we really needs just "local layout" from it (relative paths), so baseDir here is irrelevant
+        this.simpleLocalRepositoryManager = new SimpleLocalRepositoryManager( new File( "" ) );
+    }
+
+    @Override
+    public Map<String, String> getProvidedArtifactChecksums( RepositorySystemSession session,
+                                                             ArtifactDownload transfer,
+                                                             List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
+    {
+        Path baseDir = getBaseDir( session );
+        if ( baseDir == null )
+        {
+            return null;
+        }
+        ArrayList<ChecksumFilePath> checksumFilePaths = new ArrayList<>( checksumAlgorithmFactories.size() );
+        for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories )
+        {
+            checksumFilePaths.add( new ChecksumFilePath(
+                    simpleLocalRepositoryManager.getPathForArtifact( transfer.getArtifact(), false ) + '.'
+                    + checksumAlgorithmFactory.getFileExtension(), checksumAlgorithmFactory ) );
+        }
+        return getProvidedChecksums( baseDir, checksumFilePaths, ArtifactIdUtils.toId( transfer.getArtifact() ) );
+    }
+
+    /**
+     * May return {@code null}.
+     */
+    private Map<String, String> getProvidedChecksums( Path baseDir,
+                                                      List<ChecksumFilePath> checksumFilePaths,
+                                                      String subjectId )
+    {
+        HashMap<String, String> checksums = new HashMap<>();
+        for ( ChecksumFilePath checksumFilePath : checksumFilePaths )
+        {
+            Path checksumPath =  baseDir.resolve( checksumFilePath.path );
+            if ( Files.isReadable( checksumPath ) )
+            {
+                try
+                {
+                    String checksum = fileProcessor.readChecksum( checksumPath.toFile() );
+                    if ( checksum != null )
+                    {
+                        LOGGER.debug( "Resolved provided checksum '{}:{}' for '{}'",
+                                checksumFilePath.checksumAlgorithmFactory.getName(), checksum, subjectId );
+
+                        checksums.put( checksumFilePath.checksumAlgorithmFactory.getName(), checksum );
+                    }
+                }
+                catch ( IOException e )
+                {
+                    LOGGER.warn( "Could not read provided checksum for '{}' at path '{}'",
+                            subjectId, checksumPath, e );
+                }
+            }
+        }
+        return checksums.isEmpty() ? null : checksums;
+    }
+
+    /**
+     * Returns the base {@link URI} of directory where checksums are laid out, may return {@code null}.
+     */
+    private Path getBaseDir( RepositorySystemSession session )
+    {
+        final String baseDirPath = ConfigUtils.getString( session, null, CONFIG_PROP_BASE_DIR );
+        final Path baseDir;
+        if ( baseDirPath != null )
+        {
+            baseDir = Paths.get( baseDirPath );
+        }
+        else
+        {
+            baseDir = session.getLocalRepository().getBasedir().toPath().resolve( LOCAL_REPO_PREFIX );
+        }
+        if ( !Files.isDirectory( baseDir ) )
+        {
+            return null;
+        }
+        return baseDir;
+    }
+
+    private static final class ChecksumFilePath
+    {
+        private final String path;
+
+        private final ChecksumAlgorithmFactory checksumAlgorithmFactory;
+
+        private ChecksumFilePath( String path, ChecksumAlgorithmFactory checksumAlgorithmFactory )
+        {
+            this.path = path;
+            this.checksumAlgorithmFactory = checksumAlgorithmFactory;
+        }
+    }
+}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
index 3715196..d0b3de9 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
@@ -26,7 +26,6 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -148,9 +147,9 @@ public final class Maven2RepositoryLayoutFactory
         }
 
         @Override
-        public List<String> getChecksumAlgorithmNames()
+        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories()
         {
-            return checksumAlgorithms.stream().map( ChecksumAlgorithmFactory::getName ).collect( Collectors.toList() );
+            return checksumAlgorithms;
         }
 
         @Override
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
index 3e6d657..d463100 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
@@ -23,15 +23,16 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
 
 /**
  * Default implementation.
@@ -73,15 +74,19 @@ public class DefaultChecksumAlgorithmFactorySelector
         {
             throw new IllegalArgumentException(
                     String.format( "Unsupported checksum algorithm %s, supported ones are %s",
-                            algorithmName, getChecksumAlgorithmNames() )
+                            algorithmName,
+                            getChecksumAlgorithmFactories().stream()
+                                                           .map( ChecksumAlgorithmFactory::getName )
+                                                           .collect( toList() )
+                    )
             );
         }
         return factory;
     }
 
     @Override
-    public Set<String> getChecksumAlgorithmNames()
+    public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories()
     {
-        return new HashSet<>( factories.keySet() );
+        return new ArrayList<>( factories.values() );
     }
 }
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
index 5723e7f..65ad488 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
@@ -38,8 +38,6 @@ import org.eclipse.aether.artifact.ArtifactProperties;
 import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.impl.UpdateCheckManager;
 import org.eclipse.aether.impl.VersionResolver;
-import org.eclipse.aether.internal.impl.DefaultArtifactResolver;
-import org.eclipse.aether.internal.impl.DefaultUpdateCheckManager;
 import org.eclipse.aether.internal.test.util.TestFileProcessor;
 import org.eclipse.aether.internal.test.util.TestFileUtils;
 import org.eclipse.aether.internal.test.util.TestLocalRepositoryManager;
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FailChecksumPolicyTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FailChecksumPolicyTest.java
index 9eceb40..512aa18 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FailChecksumPolicyTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FailChecksumPolicyTest.java
@@ -21,7 +21,7 @@ package org.eclipse.aether.internal.impl;
 
 import static org.junit.Assert.*;
 
-import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
+import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
 import org.eclipse.aether.transfer.ChecksumFailureException;
 import org.eclipse.aether.transfer.TransferResource;
 import org.junit.Before;
@@ -50,8 +50,9 @@ public class FailChecksumPolicyTest
     @Test
     public void testOnChecksumMatch()
     {
-        assertTrue( policy.onChecksumMatch( "SHA-1", 0 ) );
-        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumPolicy.KIND_UNOFFICIAL ) );
+        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumKind.REMOTE_EXTERNAL ) );
+        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumKind.REMOTE_INCLUDED ) );
+        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumKind.PROVIDED ) );
     }
 
     @Test
@@ -60,21 +61,29 @@ public class FailChecksumPolicyTest
     {
         try
         {
-            policy.onChecksumMismatch( "SHA-1", 0, exception );
+            policy.onChecksumMismatch( "SHA-1", ChecksumKind.REMOTE_EXTERNAL, exception );
             fail( "No exception" );
         }
         catch ( ChecksumFailureException e )
         {
             assertSame( exception, e );
         }
-        policy.onChecksumMismatch( "SHA-1", ChecksumPolicy.KIND_UNOFFICIAL, exception );
+        policy.onChecksumMismatch( "SHA-1", ChecksumKind.REMOTE_INCLUDED, exception );
+        try
+        {
+            policy.onChecksumMismatch("SHA-1", ChecksumKind.PROVIDED, exception);
+        }
+        catch ( ChecksumFailureException e)
+        {
+            assertSame( exception, e );
+        }
     }
 
     @Test
     public void testOnChecksumError()
         throws Exception
     {
-        policy.onChecksumError( "SHA-1", 0, exception );
+        policy.onChecksumError( "SHA-1", ChecksumKind.REMOTE_EXTERNAL, exception );
     }
 
     @Test
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSourceTest.java
new file mode 100644
index 0000000..e062a32
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/FileProvidedChecksumsSourceTest.java
@@ -0,0 +1,107 @@
+package org.eclipse.aether.internal.impl;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
+import org.eclipse.aether.internal.test.util.TestFileProcessor;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.RepositoryPolicy;
+import org.eclipse.aether.spi.connector.ArtifactDownload;
+import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
+import org.eclipse.aether.transfer.NoRepositoryLayoutException;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class FileProvidedChecksumsSourceTest
+{
+  private DefaultRepositorySystemSession session;
+
+  private RepositoryLayout repositoryLayout;
+
+  private FileProvidedChecksumsSource subject;
+
+  @Before
+  public void setup() throws NoRepositoryLayoutException, IOException
+  {
+    RemoteRepository repository = new RemoteRepository.Builder("test", "default", "https://irrelevant.com").build();
+    session = TestUtils.newSession();
+    repositoryLayout = new Maven2RepositoryLayoutFactory().newInstance(session, repository);
+    subject = new FileProvidedChecksumsSource(new TestFileProcessor() );
+
+    // populate local repository
+    Path baseDir = session.getLocalRepository().getBasedir().toPath().resolve( FileProvidedChecksumsSource.LOCAL_REPO_PREFIX);
+
+    // artifact: test:test:2.0 => "foobar"
+    {
+      Path test = baseDir.resolve("test/test/2.0/test-2.0.jar.sha1");
+      Files.createDirectories(test.getParent());
+      Files.write(test, "foobar".getBytes(StandardCharsets.UTF_8));
+    }
+  }
+
+  @Test
+  public void noProvidedArtifactChecksum()
+  {
+    ArtifactDownload transfer = new ArtifactDownload(
+        new DefaultArtifact("test:test:1.0"),
+        "irrelevant",
+        new File("irrelevant"),
+        RepositoryPolicy.CHECKSUM_POLICY_FAIL
+    );
+    Map<String, String> providedChecksums = subject.getProvidedArtifactChecksums(
+        session,
+        transfer,
+        repositoryLayout.getChecksumAlgorithmFactories()
+    );
+    assertNull(providedChecksums);
+  }
+
+  @Test
+  public void haveProvidedArtifactChecksum()
+  {
+    ArtifactDownload transfer = new ArtifactDownload(
+        new DefaultArtifact("test:test:2.0"),
+        "irrelevant",
+        new File("irrelevant"),
+        RepositoryPolicy.CHECKSUM_POLICY_FAIL
+    );
+    Map<String, String> providedChecksums = subject.getProvidedArtifactChecksums(
+        session,
+        transfer,
+        repositoryLayout.getChecksumAlgorithmFactories()
+    );
+    assertNotNull(providedChecksums);
+    assertEquals(providedChecksums.get(Sha1ChecksumAlgorithmFactory.NAME), "foobar");
+  }
+}
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactoryTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactoryTest.java
index 311607c..108bf90 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactoryTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactoryTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.*;
 import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.artifact.DefaultArtifact;
@@ -121,7 +122,11 @@ public class Maven2RepositoryLayoutFactoryTest
     @Test
     public void testChecksumAlgorithmNames()
     {
-        assertEquals( Arrays.asList( "SHA-1", "MD5" ), layout.getChecksumAlgorithmNames() );
+        assertEquals( Arrays.asList( "SHA-1", "MD5" ),
+                layout.getChecksumAlgorithmFactories().stream()
+                      .map( ChecksumAlgorithmFactory::getName )
+                      .collect( Collectors.toList() )
+        );
     }
 
     @Test
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/WarnChecksumPolicyTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/WarnChecksumPolicyTest.java
index 0f7b522..dd376a1 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/WarnChecksumPolicyTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/WarnChecksumPolicyTest.java
@@ -21,7 +21,7 @@ package org.eclipse.aether.internal.impl;
 
 import static org.junit.Assert.*;
 
-import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
+import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
 import org.eclipse.aether.transfer.ChecksumFailureException;
 import org.eclipse.aether.transfer.TransferResource;
 import org.junit.Before;
@@ -50,8 +50,8 @@ public class WarnChecksumPolicyTest
     @Test
     public void testOnChecksumMatch()
     {
-        assertTrue( policy.onChecksumMatch( "SHA-1", 0 ) );
-        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumPolicy.KIND_UNOFFICIAL ) );
+        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumKind.REMOTE_EXTERNAL ) );
+        assertTrue( policy.onChecksumMatch( "SHA-1", ChecksumKind.REMOTE_INCLUDED ) );
     }
 
     @Test
@@ -60,21 +60,21 @@ public class WarnChecksumPolicyTest
     {
         try
         {
-            policy.onChecksumMismatch( "SHA-1", 0, exception );
+            policy.onChecksumMismatch( "SHA-1", ChecksumKind.REMOTE_EXTERNAL, exception );
             fail( "No exception" );
         }
         catch ( ChecksumFailureException e )
         {
             assertSame( exception, e );
         }
-        policy.onChecksumMismatch( "SHA-1", ChecksumPolicy.KIND_UNOFFICIAL, exception );
+        policy.onChecksumMismatch( "SHA-1", ChecksumKind.REMOTE_INCLUDED, exception );
     }
 
     @Test
     public void testOnChecksumError()
         throws Exception
     {
-        policy.onChecksumError( "SHA-1", 0, exception );
+        policy.onChecksumError( "SHA-1", ChecksumKind.REMOTE_EXTERNAL, exception );
     }
 
     @Test
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
index eece32e..5bc0aa2 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
@@ -19,7 +19,7 @@ package org.eclipse.aether.spi.connector.checksum;
  * under the License.
  */
 
-import java.util.Set;
+import java.util.Collection;
 
 /**
  * Component performing selection of {@link ChecksumAlgorithmFactory} based on known factory names.
@@ -36,9 +36,10 @@ public interface ChecksumAlgorithmFactorySelector
     ChecksumAlgorithmFactory select( String algorithmName );
 
     /**
-     * Returns a set of supported algorithm names. This set represents ALL the algorithms supported by Resolver, and is
-     * NOT in any relation to given repository layout used checksums, returned by method {@link
-     * org.eclipse.aether.spi.connector.layout.RepositoryLayout#getChecksumAlgorithmNames()} (is super set of it).
+     * Returns a collection of supported algorithm names. This set represents ALL the algorithms supported by Resolver,
+     * and is NOT in any relation to given repository layout used checksums, returned by method {@link
+     * org.eclipse.aether.spi.connector.layout.RepositoryLayout#getChecksumAlgorithmFactories()} (in fact, is super set
+     * of it).
      */
-    Set<String> getChecksumAlgorithmNames();
+    Collection<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories();
 }
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumPolicy.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumPolicy.java
index f7d7a37..2b6e04a 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumPolicy.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumPolicy.java
@@ -64,23 +64,57 @@ import org.eclipse.aether.transfer.ChecksumFailureException;
  */
 public interface ChecksumPolicy
 {
-
     /**
-     * Bit flag indicating a checksum which is not part of the official repository layout/structure.
+     * Enum denoting origin of checksum.
+     *
+     * @since 1.8.0
      */
-    int KIND_UNOFFICIAL = 0x01;
+    enum ChecksumKind
+    {
+        /**
+         * Remote external kind of checksum are retrieved from remote doing extra transport round-trip (usually by
+         * getting "file.jar.sha1" for corresponding "file.jar" file). This kind of checksum is part of layout, and
+         * was from beginning the "official" (and one and only) checksum used by resolver.
+         */
+        REMOTE_EXTERNAL( false ),
+
+        /**
+         * Included checksums may be received from remote repository during the retrieval of the main file, for example
+         * from response headers in case of HTTP transport. They may be set with
+         * {@link org.eclipse.aether.spi.connector.transport.GetTask#setChecksum(String, String)}. Included checksums
+         * on mismatch are ignored, so {@link #REMOTE_EXTERNAL} will be trialed on mismatch.
+         */
+        REMOTE_INCLUDED( true ),
+
+        /**
+         * Provided checksums may be provided by {@link ProvidedChecksumsSource} components, ahead of artifact
+         * retrieval.
+         */
+        PROVIDED( false );
+
+        private final boolean ignoreOnMismatch;
+
+        ChecksumKind( boolean ignoreOnMismatch )
+        {
+            this.ignoreOnMismatch = ignoreOnMismatch;
+        }
+
+        public boolean isIgnoreOnMismatch()
+        {
+            return ignoreOnMismatch;
+        }
+    }
 
     /**
      * Signals a match between the locally computed checksum value and the checksum value declared by the remote
      * repository.
      *
      * @param algorithm The name of the checksum algorithm being used, must not be {@code null}.
-     * @param kind      A bit field providing further details about the checksum. See the {@code KIND_*} constants in
-     *                  this interface for possible bit flags.
+     * @param kind      A field providing further details about the checksum.
      * @return {@code true} to accept the download as valid and stop further validation, {@code false} to continue
      * validation with the next checksum.
      */
-    boolean onChecksumMatch( String algorithm, int kind );
+    boolean onChecksumMatch( String algorithm, ChecksumKind kind );
 
     /**
      * Signals a mismatch between the locally computed checksum value and the checksum value declared by the remote
@@ -88,14 +122,12 @@ public interface ChecksumPolicy
      * their internal state and defer a conclusion until all available checksums have been processed.
      *
      * @param algorithm The name of the checksum algorithm being used, must not be {@code null}.
-     * @param kind      A bit field providing further details about the checksum. See the {@code KIND_*} constants in
-     *                  this
-     *                  interface for possible bit flags.
+     * @param kind      A field providing further details about the checksum.
      * @param exception The exception describing the checksum mismatch, must not be {@code null}.
      * @throws ChecksumFailureException If the checksum validation is to be failed. If the method returns normally,
      *                                  validation continues with the next checksum.
      */
-    void onChecksumMismatch( String algorithm, int kind, ChecksumFailureException exception )
+    void onChecksumMismatch( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
             throws ChecksumFailureException;
 
     /**
@@ -103,14 +135,12 @@ public interface ChecksumPolicy
      * repository.
      *
      * @param algorithm The name of the checksum algorithm being used, must not be {@code null}.
-     * @param kind      A bit field providing further details about the checksum. See the {@code KIND_*} constants in
-     *                  this
-     *                  interface for possible bit flags.
+     * @param kind      A field providing further details about the checksum.
      * @param exception The exception describing the checksum error, must not be {@code null}.
      * @throws ChecksumFailureException If the checksum validation is to be failed. If the method returns normally,
      *                                  validation continues with the next checksum.
      */
-    void onChecksumError( String algorithm, int kind, ChecksumFailureException exception )
+    void onChecksumError( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
             throws ChecksumFailureException;
 
     /**
@@ -134,8 +164,8 @@ public interface ChecksumPolicy
      * issue or insist on rejecting the downloaded file as unusable.
      *
      * @param exception The exception that was thrown from a prior call to
-     *                  {@link #onChecksumMismatch(String, int, ChecksumFailureException)},
-     *                  {@link #onChecksumError(String, int, ChecksumFailureException)} or {@link
+     *                  {@link #onChecksumMismatch(String, ChecksumKind, ChecksumFailureException)},
+     *                  {@link #onChecksumError(String, ChecksumKind, ChecksumFailureException)} or {@link
      *                  #onNoMoreChecksums()}.
      * @return {@code true} to accept the download nevertheless and let artifact resolution succeed, {@code false} to
      * reject the transferred file as unusable.
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
new file mode 100644
index 0000000..b1d70b0
--- /dev/null
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
@@ -0,0 +1,47 @@
+package org.eclipse.aether.spi.connector.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.spi.connector.ArtifactDownload;
+
+/**
+ * Component able to provide (expected) checksums beforehand the download happens. Checksum provided by this component
+ * are of kind {@link org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind#PROVIDED}.
+ *
+ * @since 1.8.0
+ */
+public interface ProvidedChecksumsSource
+{
+    /**
+     * May return the provided checksums (for given artifact transfer) from trusted source other than remote
+     * repository, or {@code null}.
+     *
+     * @param transfer The transfer that is about to be executed.
+     * @param checksumAlgorithmFactories The checksum algorithms that are expected.
+     * @return Map of expected checksums, or {@code null}.
+     */
+    Map<String, String> getProvidedArtifactChecksums( RepositorySystemSession session,
+                                                      ArtifactDownload transfer,
+                                                      List<ChecksumAlgorithmFactory> checksumAlgorithmFactories );
+}
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/layout/RepositoryLayout.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/layout/RepositoryLayout.java
index 8bc88a7..4a02b06 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/layout/RepositoryLayout.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/layout/RepositoryLayout.java
@@ -126,12 +126,12 @@ public interface RepositoryLayout
     }
 
     /**
-     * Returns the list of checksum names this instance of layout uses, never {@code null}. The checksum order
-     * represents the order how checksums are validated (hence retrieved).
+     * Returns immutable list of {@link ChecksumAlgorithmFactory} this instance of layout uses, never {@code null}.
+     * The order represents the order how checksums are validated (hence retrieved).
      *
      * @since 1.8.0
      */
-    List<String> getChecksumAlgorithmNames();
+    List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories();
 
     /**
      * Gets the location within a remote repository where the specified artifact resides. The URI is relative to the
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/GetTask.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/GetTask.java
index 3d30694..e6eb9a4 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/GetTask.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/GetTask.java
@@ -209,9 +209,10 @@ public final class GetTask
 
     /**
      * Gets the checksums which the remote repository advertises for the resource. The map is keyed by algorithm name
-     * (cf. {@link java.security.MessageDigest#getInstance(String)}) and the values are hexadecimal representations of
-     * the corresponding value. <em>Note:</em> This is optional data that a transporter may return if the underlying
-     * transport protocol provides metadata (e.g. HTTP headers) along with the actual resource data.
+     * and the values are hexadecimal representations of the corresponding value. <em>Note:</em> This is optional
+     * data that a transporter may return if the underlying transport protocol provides metadata (e.g. HTTP headers)
+     * along with the actual resource data. Checksums returned by this method have kind of
+     * {@link org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind#REMOTE_INCLUDED}.
      * 
      * @return The (read-only) checksums advertised for the downloaded resource, possibly empty but never {@code null}.
      */
@@ -225,8 +226,7 @@ public final class GetTask
      * use this method to record checksum information which is readily available while performing the actual download,
      * they should not perform additional transfers to gather this data.
      * 
-     * @param algorithm The name of the checksum algorithm (e.g. {@code "SHA-1"}, cf.
-     *            {@link java.security.MessageDigest#getInstance(String)} ), may be {@code null}.
+     * @param algorithm The name of the checksum algorithm (e.g. {@code "SHA-1"}, may be {@code null}.
      * @param value The hexadecimal representation of the checksum, may be {@code null}.
      * @return This task for chaining, never {@code null}.
      */