Skip to content

Commit

Permalink
Move signature validation from ChannelSession to the VersionResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
spyrkob committed Sep 13, 2024
1 parent b028f62 commit 5f1f025
Show file tree
Hide file tree
Showing 15 changed files with 732 additions and 591 deletions.
175 changes: 8 additions & 167 deletions core/src/main/java/org/wildfly/channel/ChannelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@
*/
package org.wildfly.channel;

import static java.util.Collections.singleton;
import static java.util.Objects.requireNonNull;
import static org.wildfly.channel.version.VersionMatcher.COMPARATOR;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -37,9 +32,6 @@

import org.jboss.logging.Logger;
import org.wildfly.channel.spi.MavenVersionsResolver;
import org.wildfly.channel.spi.SignatureResult;
import org.wildfly.channel.spi.SignatureValidator;
import org.wildfly.channel.spi.ArtifactIdentifier;
import org.wildfly.channel.version.VersionMatcher;

/**
Expand All @@ -48,7 +40,6 @@
class ChannelImpl implements AutoCloseable {

private static final Logger LOG = Logger.getLogger(ChannelImpl.class);
protected static final String SIGNATURE_FILE_SUFFIX = ".asc";

private Channel channelDefinition;

Expand All @@ -62,7 +53,6 @@ class ChannelImpl implements AutoCloseable {
private boolean dependency = false;

public Optional<Blocklist> blocklist = Optional.empty();
private SignatureValidator signatureValidator;

public ChannelManifest getManifest() {
return channelManifest;
Expand All @@ -77,18 +67,16 @@ public ChannelImpl(Channel channelDefinition) {
*
* @param factory
* @param channels
* @param signatureValidator - the validator used to check the signatures of resolved artifacts
* @throws UnresolvedRequiredManifestException - if a required manifest cannot be resolved either via maven coordinates or in the list of channels
* @throws CyclicDependencyException - if the required manifests form a cyclic dependency
*/
void init(MavenVersionsResolver.Factory factory, List<ChannelImpl> channels, SignatureValidator signatureValidator) {
void init(MavenVersionsResolver.Factory factory, List<ChannelImpl> channels) {
if (resolver != null) {
//already initialized
return;
}
this.signatureValidator = signatureValidator;

resolver = factory.create(channelDefinition.getRepositories());
resolver = factory.create(channelDefinition);

if (channelDefinition.getManifestCoordinate() != null) {
channelManifest = resolveManifest(channelDefinition.getManifestCoordinate());
Expand All @@ -107,7 +95,8 @@ void init(MavenVersionsResolver.Factory factory, List<ChannelImpl> channels, Sig

if (channelDefinition.getBlocklistCoordinate() != null) {
BlocklistCoordinate blocklistCoordinate = channelDefinition.getBlocklistCoordinate();
this.blocklist = resolveChannelMetadata(List.of(blocklistCoordinate), true).stream()
final List<URL> urls = resolver.resolveChannelMetadata(List.of(blocklistCoordinate));
this.blocklist = urls.stream()
.map(Blocklist::from)
.findFirst();
}
Expand All @@ -117,7 +106,7 @@ private ChannelImpl findRequiredChannel(MavenVersionsResolver.Factory factory, L
ChannelImpl foundChannel = null;
for (ChannelImpl c: channels) {
if (c.getManifest() == null) {
c.init(factory, channels, signatureValidator);
c.init(factory, channels);
}
if (manifestRequirement.getId().equals(c.getManifest().getId())) {
foundChannel = c;
Expand Down Expand Up @@ -159,7 +148,7 @@ private ChannelImpl createNewChannelFromMaven(MavenVersionsResolver.Factory fact
final ChannelImpl requiredChannel = new ChannelImpl(requiredChannelDefinition);

try {
requiredChannel.init(factory, channels, signatureValidator);
requiredChannel.init(factory, channels);
} catch (UnresolvedMavenArtifactException e) {
throw new UnresolvedRequiredManifestException("Manifest with ID " + manifestRequirement.getId() + " is not available", manifestRequirement.getId(), e);
}
Expand Down Expand Up @@ -212,92 +201,12 @@ static class ResolveLatestVersionResult {
}

private ChannelManifest resolveManifest(ChannelManifestCoordinate manifestCoordinate) throws UnresolvedMavenArtifactException {
return resolveChannelMetadata(List.of(manifestCoordinate), false)
return resolver.resolveChannelMetadata(List.of(manifestCoordinate))
.stream()
.map(ChannelManifestMapper::from)
.findFirst().orElseThrow();
}

/**
* Resolve a list of channel metadata artifacts based on the coordinates.
* If the {@code ChannelMetadataCoordinate} contains non-null URL, that URL is returned.
* If the {@code ChannelMetadataCoordinate} contains non-null Maven coordinates, the Maven artifact will be resolved
* and a URL to it will be returned.
* If the Maven coordinates specify only groupId and artifactId, latest available version of matching Maven artifact
* will be resolved.
*
* The order of returned URLs is the same as order of coordinates.
*
* @param coords - list of ChannelMetadataCoordinate.
* @param optional - if artifact is optional, the method will return an empty collection if no versions are found
*
* @return a list of URLs to the metadata files
*
* @throws ArtifactTransferException if any artifacts can not be resolved.
*/
private List<URL> resolveChannelMetadata(List<? extends ChannelMetadataCoordinate> coords, boolean optional) throws ArtifactTransferException {
requireNonNull(coords);

List<URL> channels = new ArrayList<>();

for (ChannelMetadataCoordinate coord : coords) {
if (coord.getUrl() != null) {
LOG.infof("Resolving channel metadata at %s", coord.getUrl());
channels.add(coord.getUrl());
if (channelDefinition.isGpgCheck()) {
try {
final URL signatureUrl;
if (coord.getSignatureUrl() == null) {
signatureUrl = new URL(coord.getUrl().toExternalForm() + SIGNATURE_FILE_SUFFIX);
} else {
signatureUrl = coord.getSignatureUrl();
}
validateGpgSignature(coord.getUrl(), signatureUrl);
} catch (IOException e) {
throw new InvalidChannelMetadataException("Unable to download a detached signature file from: " + coord.getUrl().toExternalForm()+ SIGNATURE_FILE_SUFFIX,
List.of(e.getMessage()), e);
}
}
continue;
}

String version = coord.getVersion();
if (version == null) {
Set<String> versions = resolver.getAllVersions(coord.getGroupId(), coord.getArtifactId(), coord.getExtension(), coord.getClassifier());
Optional<String> latestVersion = VersionMatcher.getLatestVersion(versions);
if (latestVersion.isPresent()){
version = latestVersion.get();
} else if (optional) {
return Collections.emptyList();
} else {
throw new ArtifactTransferException(String.format("Unable to resolve the latest version of channel metadata %s:%s", coord.getGroupId(), coord.getArtifactId()),
singleton(new ArtifactCoordinate(coord.getGroupId(), coord.getArtifactId(), coord.getExtension(), coord.getClassifier(), "")),
attemptedRepositories());
}
}
LOG.infof("Resolving channel metadata from Maven artifact %s:%s:%s", coord.getGroupId(), coord.getArtifactId(), version);
File channelArtifact = resolver.resolveArtifact(coord.getGroupId(), coord.getArtifactId(), coord.getExtension(), coord.getClassifier(), version);
try {
channels.add(channelArtifact.toURI().toURL());
if (channelDefinition.isGpgCheck()) {
validateGpgSignature(coord.getGroupId(), coord.getArtifactId(), coord.getExtension(), coord.getClassifier(), version, channelArtifact);
}
} catch (MalformedURLException e) {
throw new ArtifactTransferException(String.format("Unable to resolve the latest version of channel metadata %s:%s", coord.getGroupId(), coord.getArtifactId()), e,
singleton(new ArtifactCoordinate(coord.getGroupId(), coord.getArtifactId(),
coord.getExtension(), coord.getClassifier(), coord.getVersion())),
attemptedRepositories());
}
}
return channels;
}

private Set<Repository> attemptedRepositories() {
return channelDefinition.getRepositories().stream()
.map(r -> new Repository(r.getId(), r.getUrl()))
.collect(Collectors.toSet());
}

Optional<ResolveLatestVersionResult> resolveLatestVersion(String groupId, String artifactId, String extension, String classifier, String baseVersion) {
requireNonNull(groupId);
requireNonNull(artifactId);
Expand Down Expand Up @@ -412,79 +321,11 @@ ResolveArtifactResult resolveArtifact(String groupId, String artifactId, String
}
}

final File artifact = resolver.resolveArtifact(groupId, artifactId, extension, classifier, version);
if (channelDefinition.isGpgCheck()) {
validateGpgSignature(groupId, artifactId, extension, classifier, version, artifact);
}
return new ResolveArtifactResult(artifact, this);
}

private void validateGpgSignature(String groupId, String artifactId, String extension, String classifier,
String version, File artifact) {
final ArtifactIdentifier mavenArtifact = new ArtifactIdentifier.MavenResource(groupId, artifactId, extension,
classifier, version);
try {
final File signature = resolver.resolveArtifact(groupId, artifactId, extension + SIGNATURE_FILE_SUFFIX,
classifier, version);
final SignatureResult signatureResult = signatureValidator.validateSignature(
mavenArtifact, new FileInputStream(artifact), new FileInputStream(signature),
channelDefinition.getGpgUrls());
if (signatureResult.getResult() != SignatureResult.Result.OK) {
throw new SignatureValidator.SignatureException("Failed to verify an artifact signature", signatureResult);
}
} catch (ArtifactTransferException | FileNotFoundException e) {
throw new SignatureValidator.SignatureException("Unable to find required signature for " + mavenArtifact,
e, SignatureResult.noSignature(mavenArtifact));
}
}

private void validateGpgSignature(URL artifactFile, URL signature) throws IOException {
final SignatureResult signatureResult = signatureValidator.validateSignature(
new ArtifactIdentifier.UrlResource(artifactFile),
artifactFile.openStream(), signature.openStream(),
channelDefinition.getGpgUrls()
);

if (signatureResult.getResult() != SignatureResult.Result.OK) {
throw new SignatureValidator.SignatureException("Failed to verify an artifact signature", signatureResult);
}
return new ResolveArtifactResult(resolver.resolveArtifact(groupId, artifactId, extension, classifier, version), this);
}

List<ResolveArtifactResult> resolveArtifacts(List<ArtifactCoordinate> coordinates) throws UnresolvedMavenArtifactException {
final List<File> resolvedArtifacts = resolver.resolveArtifacts(coordinates);

if (channelDefinition.isGpgCheck()) {
try {
final List<File> signatures = resolver.resolveArtifacts(coordinates.stream()
.map(c->new ArtifactCoordinate(c.getGroupId(), c.getArtifactId(), c.getExtension() + SIGNATURE_FILE_SUFFIX,
c.getClassifier(), c.getVersion()))
.collect(Collectors.toList()));
for (int i = 0; i < resolvedArtifacts.size(); i++) {
final File artifact = resolvedArtifacts.get(i);
final ArtifactCoordinate c = coordinates.get(i);
final ArtifactIdentifier.MavenResource mavenArtifact = new ArtifactIdentifier.MavenResource(c.getGroupId(), c.getArtifactId(),
c.getExtension(), c.getClassifier(), c.getVersion());
final File signature = signatures.get(i);
try {
final SignatureResult signatureResult = signatureValidator.validateSignature(mavenArtifact,
new FileInputStream(artifact), new FileInputStream(signature), channelDefinition.getGpgUrls());
if (signatureResult.getResult() != SignatureResult.Result.OK) {
throw new SignatureValidator.SignatureException("Failed to verify an artifact signature", signatureResult);
}
} catch (FileNotFoundException e) {
throw new SignatureValidator.SignatureException(String.format("Unable to find required signature for %s:%s:%s",
mavenArtifact.getGroupId(), mavenArtifact.getArtifactId(), mavenArtifact.getVersion()),
SignatureResult.noSignature(mavenArtifact));
}
}
} catch (ArtifactTransferException e) {
final ArtifactIdentifier.MavenResource artifact = new ArtifactIdentifier.MavenResource(e.getUnresolvedArtifacts().stream().findFirst().get());
throw new SignatureValidator.SignatureException(String.format("Unable to find required signature for %s:%s:%s",
artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()),
SignatureResult.noSignature(artifact));
}
}

return resolvedArtifacts.stream().map(f->new ResolveArtifactResult(f, this)).collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static ChannelManifestCoordinate create(@JsonProperty(value = "url") Stri
return new ChannelManifestCoordinate(gav.getGroupId(), gav.getArtifactId(), gav.getVersion());
}
} else {
return new ChannelManifestCoordinate(new URL(url));
return new ChannelManifestCoordinate(new URL(url), signatureUrl == null ? null : new URL(signatureUrl));
}
}

Expand Down
Loading

0 comments on commit 5f1f025

Please sign in to comment.