Refactoring to improve testability.
Add X509Source interface. Add tests to cover provider module. Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
e81a936a96
commit
e9df15e44b
|
|
@ -18,6 +18,8 @@ subprojects {
|
|||
jupiterVersion = '5.6.2'
|
||||
mockitoVersion = '3.3.3'
|
||||
lombokVersion = '1.18.12'
|
||||
nimbusVersion = '8.19'
|
||||
|
||||
if (gradle.ext.isMacOs) {
|
||||
osClassifier = "osx-x86_64"
|
||||
} else {
|
||||
|
|
@ -87,7 +89,7 @@ task jacocoTestReport(type: JacocoReport) {
|
|||
// Filter out autogenerated or internal code
|
||||
afterEvaluate {
|
||||
classDirectories.setFrom(files(classDirectories.files.collect {
|
||||
fileTree(dir: it, exclude: ['**/grpc/**', '**/exception/**', '**/provider/**', '**/internal/**'])
|
||||
fileTree(dir: it, exclude: ['**/grpc/**', '**/exception/**', '**/internal/**'])
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +101,9 @@ task jacocoTestReport(type: JacocoReport) {
|
|||
|
||||
coveralls {
|
||||
jacocoReportPath 'build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
|
||||
sourceDirs = ['java-spiffe-core/src/main/java', 'java-spiffe-helper/src/main/java']
|
||||
sourceDirs = ['java-spiffe-core/src/main/java',
|
||||
'java-spiffe-helper/src/main/java',
|
||||
'java-spiffe-provider/src/main/java']
|
||||
}
|
||||
|
||||
// always run the tests before generating the report
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ To create a new X.509 Source:
|
|||
```
|
||||
X509Source x509Source;
|
||||
try {
|
||||
x509Source = X509Source.newSource();
|
||||
x509Source = DefaultX509Source.newSource();
|
||||
} catch (SocketEndpointAddressException | X509SourceException e) {
|
||||
// handle exception
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ configure it is by providing an `X509SourceOptions` instance to the `newSource`
|
|||
.picker(list -> list.get(list.size()-1))
|
||||
.build();
|
||||
|
||||
X509Source x509Source = X509Source.newSource(x509SourceOptions);
|
||||
X509Source x509Source = DefaultX509Source.newSource(x509SourceOptions);
|
||||
```
|
||||
|
||||
It allows to configure another SVID picker. By default, the first SVID is used.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ buildscript {
|
|||
}
|
||||
|
||||
apply plugin: 'com.google.protobuf'
|
||||
apply plugin: 'java-test-fixtures'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
|
|
@ -48,9 +49,11 @@ dependencies {
|
|||
compileOnly group: 'org.apache.tomcat', name:'annotations-api', version: '6.0.53' // necessary for Java 9+
|
||||
|
||||
// library for processing JWT tokens and JOSE JWK bundles
|
||||
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '8.19'
|
||||
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: "${nimbusVersion}"
|
||||
testFixturesImplementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: "${nimbusVersion}"
|
||||
|
||||
// using bouncy castle for generating X.509 certs for testing purposes
|
||||
testImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.65'
|
||||
testFixturesImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.65'
|
||||
testFixturesImplementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.10'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ public final class GrpcManagedChannelFactory {
|
|||
private static final String TCP_SCHEME = "tcp";
|
||||
|
||||
private GrpcManagedChannelFactory() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ public final class GrpcManagedChannelFactory {
|
|||
private static final String TCP_SCHEME = "tcp";
|
||||
|
||||
private GrpcManagedChannelFactory() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package io.spiffe.bundle.x509bundle;
|
|||
|
||||
import io.spiffe.bundle.BundleSource;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.internal.CertificateUtils;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import lombok.NonNull;
|
||||
|
|
@ -10,12 +11,12 @@ import lombok.val;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
|
@ -58,20 +59,17 @@ public class X509Bundle implements BundleSource<X509Bundle> {
|
|||
* @return an instance of {@link X509Bundle} with the X.509 authorities
|
||||
* associated to the trust domain.
|
||||
*
|
||||
* @throws IOException in case of failure accessing the given bundle path
|
||||
* @throws CertificateException if the bundle cannot be parsed
|
||||
* @throws X509BundleException in case of failure accessing the given bundle path or the bundle cannot be parsed
|
||||
*/
|
||||
public static X509Bundle load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath)
|
||||
throws IOException, CertificateException {
|
||||
|
||||
public static X509Bundle load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) throws X509BundleException {
|
||||
final byte[] bundleBytes;
|
||||
try {
|
||||
bundleBytes = Files.readAllBytes(bundlePath);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new IOException("Unable to load X.509 bundle file", e);
|
||||
} catch (IOException e) {
|
||||
throw new X509BundleException("Unable to load X.509 bundle file", e);
|
||||
}
|
||||
|
||||
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
||||
val x509Certificates = generateX509Certificates(bundleBytes);
|
||||
val x509CertificateSet = new HashSet<>(x509Certificates);
|
||||
return new X509Bundle(trustDomain, x509CertificateSet);
|
||||
}
|
||||
|
|
@ -85,11 +83,10 @@ public class X509Bundle implements BundleSource<X509Bundle> {
|
|||
* @return an instance of {@link X509Bundle} with the X.509 authorities
|
||||
* associated to the given trust domain
|
||||
*
|
||||
* @throws CertificateException if the bundle cannot be parsed
|
||||
* @throws X509BundleException if the bundle cannot be parsed
|
||||
*/
|
||||
public static X509Bundle parse(@NonNull final TrustDomain trustDomain, @NonNull final byte[] bundleBytes)
|
||||
throws CertificateException {
|
||||
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
||||
public static X509Bundle parse(@NonNull final TrustDomain trustDomain, @NonNull final byte[] bundleBytes) throws X509BundleException {
|
||||
val x509Certificates = generateX509Certificates(bundleBytes);
|
||||
val x509CertificateSet = new HashSet<>(x509Certificates);
|
||||
return new X509Bundle(trustDomain, x509CertificateSet);
|
||||
}
|
||||
|
|
@ -146,4 +143,15 @@ public class X509Bundle implements BundleSource<X509Bundle> {
|
|||
public void removeX509Authority(@NonNull final X509Certificate x509Authority) {
|
||||
x509Authorities.remove(x509Authority);
|
||||
}
|
||||
|
||||
private static List<X509Certificate> generateX509Certificates(byte[] bundleBytes) throws X509BundleException {
|
||||
List<X509Certificate> x509Certificates;
|
||||
try {
|
||||
x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
||||
} catch (CertificateParsingException e) {
|
||||
throw new X509BundleException("Bundle certificates could not be parsed from bundle path", e);
|
||||
}
|
||||
return x509Certificates;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package io.spiffe.exception;
|
||||
|
||||
/**
|
||||
* Checked exception thrown when there is an error parsing
|
||||
* the components of an X.509 Bundle.
|
||||
*/
|
||||
public class X509BundleException extends Exception {
|
||||
public X509BundleException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public X509BundleException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ public enum AsymmetricKeyAlgorithm {
|
|||
} else if ("EC".equalsIgnoreCase(a)) {
|
||||
return EC;
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Algorithm not recognized: %s", a));
|
||||
throw new IllegalArgumentException(String.format("Algorithm not supported: %s", a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package io.spiffe.internal;
|
|||
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import lombok.NonNull;
|
||||
import lombok.val;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
|
@ -33,11 +32,11 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.spiffe.internal.AsymmetricKeyAlgorithm.EC;
|
||||
import static io.spiffe.internal.AsymmetricKeyAlgorithm.RSA;
|
||||
import static io.spiffe.internal.KeyUsage.CRL_SIGN;
|
||||
import static io.spiffe.internal.KeyUsage.DIGITAL_SIGNATURE;
|
||||
import static io.spiffe.internal.KeyUsage.KEY_CERT_SIGN;
|
||||
import static io.spiffe.internal.AsymmetricKeyAlgorithm.EC;
|
||||
import static io.spiffe.internal.AsymmetricKeyAlgorithm.RSA;
|
||||
import static org.apache.commons.lang3.StringUtils.startsWith;
|
||||
|
||||
/**
|
||||
|
|
@ -55,7 +54,6 @@ public class CertificateUtils {
|
|||
private static final String X509_CERTIFICATE_TYPE = "X.509";
|
||||
|
||||
private CertificateUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,7 +62,7 @@ public class CertificateUtils {
|
|||
* @param input as byte array representing a list of X.509 certificates, as a DER or PEM
|
||||
* @return a List of {@link X509Certificate}
|
||||
*/
|
||||
public static List<X509Certificate> generateCertificates(@NonNull final byte[] input) throws CertificateParsingException {
|
||||
public static List<X509Certificate> generateCertificates(final byte[] input) throws CertificateParsingException {
|
||||
if (input.length == 0) {
|
||||
throw new CertificateParsingException("No certificates found");
|
||||
}
|
||||
|
|
@ -105,6 +103,9 @@ public class CertificateUtils {
|
|||
* @throws CertPathValidatorException
|
||||
*/
|
||||
public static void validate(final List<X509Certificate> chain, final Collection<X509Certificate> trustedCerts) throws CertificateException, CertPathValidatorException {
|
||||
if (chain == null || chain.size() == 0) {
|
||||
throw new IllegalArgumentException("Chain of certificates is empty");
|
||||
}
|
||||
val certificateFactory = getCertificateFactory();
|
||||
PKIXParameters pkixParameters;
|
||||
try {
|
||||
|
|
@ -168,9 +169,6 @@ public class CertificateUtils {
|
|||
break;
|
||||
case EC:
|
||||
verifyKeys(privateKey, x509Certificate.getPublicKey(), SHA_512_WITH_ECDSA);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidKeyException(String.format("Private Key algorithm not supported: %s", algorithm));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,16 +231,13 @@ public class CertificateUtils {
|
|||
}
|
||||
|
||||
private static PrivateKey generatePrivateKeyWithSpec(final EncodedKeySpec keySpec, AsymmetricKeyAlgorithm algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
PrivateKey privateKey;
|
||||
PrivateKey privateKey = null;
|
||||
switch (algorithm) {
|
||||
case EC:
|
||||
privateKey = KeyFactory.getInstance(EC.value()).generatePrivate(keySpec);
|
||||
break;
|
||||
case RSA:
|
||||
privateKey = KeyFactory.getInstance(RSA.value()).generatePrivate(keySpec);
|
||||
break;
|
||||
default:
|
||||
throw new NoSuchAlgorithmException(String.format("Private Key algorithm is not supported: %s", algorithm));
|
||||
}
|
||||
return privateKey;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,270 @@
|
|||
package io.spiffe.workloadapi;
|
||||
|
||||
import io.spiffe.bundle.BundleSource;
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.bundle.x509bundle.X509BundleSet;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.WatcherException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.java.Log;
|
||||
import lombok.val;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static io.spiffe.workloadapi.internal.ThreadUtils.await;
|
||||
|
||||
/**
|
||||
* Represents a source of X.509 SVIDs and X.509 bundles maintained via the Workload API.
|
||||
* <p>
|
||||
* It handles a {@link X509Svid} and a {@link X509BundleSet} that are updated automatically
|
||||
* whenever there is an update from the Workload API.
|
||||
* <p>
|
||||
* Implements {@link X509SvidSource} and {@link BundleSource}.
|
||||
* <p>
|
||||
* Implements the {@link Closeable} interface. The {@link #close()} method closes the source,
|
||||
* dropping the connection to the Workload API. Other source methods will return an error
|
||||
* after close has been called.
|
||||
*/
|
||||
@Log
|
||||
public final class DefaultX509Source implements X509Source {
|
||||
|
||||
private static final String TIMEOUT_SYSTEM_PROPERTY = "spiffe.newX509Source.timeout";
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.parse(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, "PT0S"));
|
||||
|
||||
private X509Svid svid;
|
||||
private X509BundleSet bundles;
|
||||
|
||||
private final Function<List<X509Svid>, X509Svid> picker;
|
||||
private final WorkloadApiClient workloadApiClient;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
// private constructor
|
||||
private DefaultX509Source(final Function<List<X509Svid>, X509Svid> svidPicker, final WorkloadApiClient workloadApiClient) {
|
||||
this.picker = svidPicker;
|
||||
this.workloadApiClient = workloadApiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new X.509 source. It blocks until the initial update with the X.509 materials
|
||||
* has been received from the Workload API or until the timeout configured
|
||||
* through the system property `spiffe.newX509Source.timeout` expires.
|
||||
* If no timeout is configured, it blocks until it gets an X.509 update from the Workload API.
|
||||
* <p>
|
||||
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
|
||||
* <p>
|
||||
* It uses the default X.509 SVID (picks the first SVID that comes in the Workload API response).
|
||||
*
|
||||
* @return an instance of {@link DefaultX509Source}, with the SVID and bundles initialized
|
||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||
* @throws X509SourceException if the source could not be initialized
|
||||
*/
|
||||
public static DefaultX509Source newSource() throws SocketEndpointAddressException, X509SourceException {
|
||||
val x509SourceOptions = X509SourceOptions.builder().initTimeout(DEFAULT_TIMEOUT).build();
|
||||
return newSource(x509SourceOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new X.509 source. It blocks until the initial update with the X.509 materials
|
||||
* has been received from the Workload API, doing retries with a backoff exponential policy,
|
||||
* or until the timeout has expired.
|
||||
* <p>
|
||||
* If the timeout is not provided in the options, the default timeout is read from the
|
||||
* system property `spiffe.newX509Source.timeout`. If none is configured, this method will
|
||||
* block until the X.509 materials can be retrieved from the Workload API.
|
||||
* <p>
|
||||
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
||||
* a new client is created.
|
||||
* <p>
|
||||
* If no SVID Picker is provided in the options, it uses the default X.509 SVID (picks the first SVID that comes
|
||||
* in the Workload API response).
|
||||
*
|
||||
* @param options {@link X509SourceOptions}
|
||||
* @return an instance of {@link DefaultX509Source}, with the SVID and bundles initialized
|
||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||
* @throws X509SourceException if the source could not be initialized
|
||||
*/
|
||||
public static DefaultX509Source newSource(@NonNull final X509SourceOptions options)
|
||||
throws SocketEndpointAddressException, X509SourceException {
|
||||
if (options.workloadApiClient == null) {
|
||||
options.workloadApiClient = createClient(options);
|
||||
}
|
||||
|
||||
if (options.initTimeout == null) {
|
||||
options.initTimeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
val x509Source = new DefaultX509Source(options.svidPicker, options.workloadApiClient);
|
||||
|
||||
try {
|
||||
x509Source.init(options.initTimeout);
|
||||
} catch (Exception e) {
|
||||
x509Source.close();
|
||||
throw new X509SourceException("Error creating X.509 source", e);
|
||||
}
|
||||
|
||||
return x509Source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X.509 SVID handled by this source.
|
||||
*
|
||||
* @return a {@link X509Svid}
|
||||
* @throws IllegalStateException if the source is closed
|
||||
*/
|
||||
@Override
|
||||
public X509Svid getX509Svid() {
|
||||
if (isClosed()) {
|
||||
throw new IllegalStateException("X.509 SVID source is closed");
|
||||
}
|
||||
return svid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X.509 bundle for a given trust domain.
|
||||
*
|
||||
* @return an instance of a {@link X509Bundle}
|
||||
*
|
||||
* @throws BundleNotFoundException is there is no bundle for the trust domain provided
|
||||
* @throws IllegalStateException if the source is closed
|
||||
*/
|
||||
@Override
|
||||
public X509Bundle getBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||
if (isClosed()) {
|
||||
throw new IllegalStateException("X.509 bundle source is closed");
|
||||
}
|
||||
return bundles.getBundleForTrustDomain(trustDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this source, dropping the connection to the Workload API.
|
||||
* Other source methods will return an error after close has been called.
|
||||
* <p>
|
||||
* It is marked with {@link SneakyThrows} because it is not expected to throw
|
||||
* the checked exception defined on the {@link Closeable} interface.
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
synchronized (this) {
|
||||
if (!closed) {
|
||||
workloadApiClient.close();
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static WorkloadApiClient createClient(final X509SourceOptions options)
|
||||
throws SocketEndpointAddressException {
|
||||
val clientOptions = DefaultWorkloadApiClient.ClientOptions
|
||||
.builder()
|
||||
.spiffeSocketPath(options.spiffeSocketPath)
|
||||
.build();
|
||||
return DefaultWorkloadApiClient.newClient(clientOptions);
|
||||
}
|
||||
|
||||
private void init(final Duration timeout) throws TimeoutException {
|
||||
val done = new CountDownLatch(1);
|
||||
setX509ContextWatcher(done);
|
||||
|
||||
final boolean success;
|
||||
if (timeout.isZero()) {
|
||||
await(done);
|
||||
success = true;
|
||||
} else {
|
||||
success = await(done, timeout.getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
if (!success) {
|
||||
throw new TimeoutException("Timeout waiting for X.509 Context update");
|
||||
}
|
||||
}
|
||||
|
||||
private void setX509ContextWatcher(final CountDownLatch done) {
|
||||
workloadApiClient.watchX509Context(new Watcher<X509Context>() {
|
||||
@Override
|
||||
public void onUpdate(final X509Context update) {
|
||||
log.log(Level.INFO, "Received X509Context update");
|
||||
setX509Context(update);
|
||||
done.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final Throwable error) {
|
||||
log.log(Level.SEVERE, "Error in X509Context watcher", error);
|
||||
done.countDown();
|
||||
throw new WatcherException("Error in X509Context watcher", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setX509Context(final X509Context update) {
|
||||
final X509Svid svidUpdate;
|
||||
if (picker == null) {
|
||||
svidUpdate = update.getDefaultSvid();
|
||||
} else {
|
||||
svidUpdate = picker.apply(update.getX509Svids());
|
||||
}
|
||||
synchronized (this) {
|
||||
this.svid = svidUpdate;
|
||||
this.bundles = update.getX509BundleSet();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClosed() {
|
||||
synchronized (this) {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a new {@link DefaultX509Source}
|
||||
* <p>
|
||||
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
|
||||
* <p>
|
||||
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
|
||||
* from the System property `spiffe.newX509Source.timeout'. If this is also not defined, no default timeout is applied.
|
||||
* <p>
|
||||
* <code>svidPicker</code> Function to choose the X.509 SVID from the list returned by the Workload API.
|
||||
* If it is not set, the default SVID is picked.
|
||||
* <p>
|
||||
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set, a new client
|
||||
* will be created.
|
||||
*/
|
||||
@Data
|
||||
public static class X509SourceOptions {
|
||||
|
||||
private String spiffeSocketPath;
|
||||
private Duration initTimeout;
|
||||
private Function<List<X509Svid>, X509Svid> svidPicker;
|
||||
private WorkloadApiClient workloadApiClient;
|
||||
|
||||
@Builder
|
||||
public X509SourceOptions(final String spiffeSocketPath,
|
||||
final Duration initTimeout,
|
||||
final Function<List<X509Svid>, X509Svid> svidPicker,
|
||||
final WorkloadApiClient workloadApiClient) {
|
||||
this.spiffeSocketPath = spiffeSocketPath;
|
||||
this.initTimeout = initTimeout;
|
||||
this.svidPicker = svidPicker;
|
||||
this.workloadApiClient = workloadApiClient;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import io.spiffe.bundle.jwtbundle.JwtBundleSet;
|
|||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.bundle.x509bundle.X509BundleSet;
|
||||
import io.spiffe.exception.JwtBundleException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.exception.X509ContextException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
|
|
@ -14,7 +15,6 @@ import io.spiffe.svid.x509svid.X509Svid;
|
|||
import io.spiffe.workloadapi.grpc.Workload;
|
||||
import lombok.val;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
|
@ -27,7 +27,6 @@ import java.util.Set;
|
|||
final class GrpcConversionUtils {
|
||||
|
||||
private GrpcConversionUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
static X509Context toX509Context(final Iterator<Workload.X509SVIDResponse> x509SvidResponseIterator) throws X509ContextException {
|
||||
|
|
@ -75,7 +74,7 @@ final class GrpcConversionUtils {
|
|||
static X509Bundle parseX509Bundle(TrustDomain trustDomain, byte[] bundleBytes) throws X509ContextException {
|
||||
try {
|
||||
return X509Bundle.parse(trustDomain, bundleBytes);
|
||||
} catch (CertificateException e) {
|
||||
} catch (X509BundleException e) {
|
||||
throw new X509ContextException("X.509 Bundles could not be processed", e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,269 +2,12 @@ package io.spiffe.workloadapi;
|
|||
|
||||
import io.spiffe.bundle.BundleSource;
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.bundle.x509bundle.X509BundleSet;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.WatcherException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.java.Log;
|
||||
import lombok.val;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static io.spiffe.workloadapi.internal.ThreadUtils.await;
|
||||
|
||||
/**
|
||||
* Represents a source of X.509 SVIDs and X.509 bundles maintained via the Workload API.
|
||||
* <p>
|
||||
* It handles a {@link X509Svid} and a {@link X509BundleSet} that are updated automatically
|
||||
* whenever there is an update from the Workload API.
|
||||
* <p>
|
||||
* Implements {@link X509SvidSource} and {@link BundleSource}.
|
||||
* <p>
|
||||
* Implements the {@link Closeable} interface. The {@link #close()} method closes the source,
|
||||
* dropping the connection to the Workload API. Other source methods will return an error
|
||||
* after close has been called.
|
||||
* Source of X.509 SVIDs and Bundles.
|
||||
*/
|
||||
@Log
|
||||
public final class X509Source implements X509SvidSource, BundleSource<X509Bundle>, Closeable {
|
||||
|
||||
private static final String TIMEOUT_SYSTEM_PROPERTY = "spiffe.newX509Source.timeout";
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.parse(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, "PT0S"));
|
||||
|
||||
private X509Svid svid;
|
||||
private X509BundleSet bundles;
|
||||
|
||||
private final Function<List<X509Svid>, X509Svid> picker;
|
||||
private final WorkloadApiClient workloadApiClient;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
// private constructor
|
||||
private X509Source(final Function<List<X509Svid>, X509Svid> svidPicker, final WorkloadApiClient workloadApiClient) {
|
||||
this.picker = svidPicker;
|
||||
this.workloadApiClient = workloadApiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new X.509 source. It blocks until the initial update with the X.509 materials
|
||||
* has been received from the Workload API or until the timeout configured
|
||||
* through the system property `spiffe.newX509Source.timeout` expires.
|
||||
* If no timeout is configured, it blocks until it gets an X.509 update from the Workload API.
|
||||
* <p>
|
||||
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
|
||||
* <p>
|
||||
* It uses the default X.509 SVID (picks the first SVID that comes in the Workload API response).
|
||||
*
|
||||
* @return an instance of {@link X509Source}, with the SVID and bundles initialized
|
||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||
* @throws X509SourceException if the source could not be initialized
|
||||
*/
|
||||
public static X509Source newSource() throws SocketEndpointAddressException, X509SourceException {
|
||||
val x509SourceOptions = X509SourceOptions.builder().initTimeout(DEFAULT_TIMEOUT).build();
|
||||
return newSource(x509SourceOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new X.509 source. It blocks until the initial update with the X.509 materials
|
||||
* has been received from the Workload API, doing retries with a backoff exponential policy,
|
||||
* or until the timeout has expired.
|
||||
* <p>
|
||||
* If the timeout is not provided in the options, the default timeout is read from the
|
||||
* system property `spiffe.newX509Source.timeout`. If none is configured, this method will
|
||||
* block until the X.509 materials can be retrieved from the Workload API.
|
||||
* <p>
|
||||
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
||||
* a new client is created.
|
||||
* <p>
|
||||
* If no SVID Picker is provided in the options, it uses the default X.509 SVID (picks the first SVID that comes
|
||||
* in the Workload API response).
|
||||
*
|
||||
* @param options {@link X509SourceOptions}
|
||||
* @return an instance of {@link X509Source}, with the SVID and bundles initialized
|
||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||
* @throws X509SourceException if the source could not be initialized
|
||||
*/
|
||||
public static X509Source newSource(@NonNull final X509SourceOptions options)
|
||||
throws SocketEndpointAddressException, X509SourceException {
|
||||
if (options.workloadApiClient == null) {
|
||||
options.workloadApiClient = createClient(options);
|
||||
}
|
||||
|
||||
if (options.initTimeout == null) {
|
||||
options.initTimeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
val x509Source = new X509Source(options.svidPicker, options.workloadApiClient);
|
||||
|
||||
try {
|
||||
x509Source.init(options.initTimeout);
|
||||
} catch (Exception e) {
|
||||
x509Source.close();
|
||||
throw new X509SourceException("Error creating X.509 source", e);
|
||||
}
|
||||
|
||||
return x509Source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X.509 SVID handled by this source.
|
||||
*
|
||||
* @return a {@link X509Svid}
|
||||
* @throws IllegalStateException if the source is closed
|
||||
*/
|
||||
@Override
|
||||
public X509Svid getX509Svid() {
|
||||
if (isClosed()) {
|
||||
throw new IllegalStateException("X.509 SVID source is closed");
|
||||
}
|
||||
return svid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X.509 bundle for a given trust domain.
|
||||
*
|
||||
* @return an instance of a {@link X509Bundle}
|
||||
*
|
||||
* @throws BundleNotFoundException is there is no bundle for the trust domain provided
|
||||
* @throws IllegalStateException if the source is closed
|
||||
*/
|
||||
@Override
|
||||
public X509Bundle getBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||
if (isClosed()) {
|
||||
throw new IllegalStateException("X.509 bundle source is closed");
|
||||
}
|
||||
return bundles.getBundleForTrustDomain(trustDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this source, dropping the connection to the Workload API.
|
||||
* Other source methods will return an error after close has been called.
|
||||
* <p>
|
||||
* It is marked with {@link SneakyThrows} because it is not expected to throw
|
||||
* the checked exception defined on the {@link Closeable} interface.
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
synchronized (this) {
|
||||
if (!closed) {
|
||||
workloadApiClient.close();
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static WorkloadApiClient createClient(final X509SourceOptions options)
|
||||
throws SocketEndpointAddressException {
|
||||
val clientOptions = DefaultWorkloadApiClient.ClientOptions
|
||||
.builder()
|
||||
.spiffeSocketPath(options.spiffeSocketPath)
|
||||
.build();
|
||||
return DefaultWorkloadApiClient.newClient(clientOptions);
|
||||
}
|
||||
|
||||
private void init(final Duration timeout) throws TimeoutException {
|
||||
val done = new CountDownLatch(1);
|
||||
setX509ContextWatcher(done);
|
||||
|
||||
final boolean success;
|
||||
if (timeout.isZero()) {
|
||||
await(done);
|
||||
success = true;
|
||||
} else {
|
||||
success = await(done, timeout.getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
if (!success) {
|
||||
throw new TimeoutException("Timeout waiting for X.509 Context update");
|
||||
}
|
||||
}
|
||||
|
||||
private void setX509ContextWatcher(final CountDownLatch done) {
|
||||
workloadApiClient.watchX509Context(new Watcher<X509Context>() {
|
||||
@Override
|
||||
public void onUpdate(final X509Context update) {
|
||||
log.log(Level.INFO, "Received X509Context update");
|
||||
setX509Context(update);
|
||||
done.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final Throwable error) {
|
||||
log.log(Level.SEVERE, "Error in X509Context watcher", error);
|
||||
done.countDown();
|
||||
throw new WatcherException("Error in X509Context watcher", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setX509Context(final X509Context update) {
|
||||
final X509Svid svidUpdate;
|
||||
if (picker == null) {
|
||||
svidUpdate = update.getDefaultSvid();
|
||||
} else {
|
||||
svidUpdate = picker.apply(update.getX509Svids());
|
||||
}
|
||||
synchronized (this) {
|
||||
this.svid = svidUpdate;
|
||||
this.bundles = update.getX509BundleSet();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClosed() {
|
||||
synchronized (this) {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a new {@link X509Source}
|
||||
* <p>
|
||||
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
|
||||
* <p>
|
||||
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
|
||||
* from the System property `spiffe.newX509Source.timeout'. If this is also not defined, no default timeout is applied.
|
||||
* <p>
|
||||
* <code>svidPicker</code> Function to choose the X.509 SVID from the list returned by the Workload API.
|
||||
* If it is not set, the default SVID is picked.
|
||||
* <p>
|
||||
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set, a new client
|
||||
* will be created.
|
||||
*/
|
||||
@Data
|
||||
public static class X509SourceOptions {
|
||||
|
||||
private String spiffeSocketPath;
|
||||
private Duration initTimeout;
|
||||
private Function<List<X509Svid>, X509Svid> svidPicker;
|
||||
private WorkloadApiClient workloadApiClient;
|
||||
|
||||
@Builder
|
||||
public X509SourceOptions(final String spiffeSocketPath,
|
||||
final Duration initTimeout,
|
||||
final Function<List<X509Svid>, X509Svid> svidPicker,
|
||||
final WorkloadApiClient workloadApiClient) {
|
||||
this.spiffeSocketPath = spiffeSocketPath;
|
||||
this.initTimeout = initTimeout;
|
||||
this.svidPicker = svidPicker;
|
||||
this.workloadApiClient = workloadApiClient;
|
||||
}
|
||||
}
|
||||
|
||||
public interface X509Source extends X509SvidSource, BundleSource<X509Bundle>, Closeable {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import java.util.concurrent.TimeUnit;
|
|||
public final class ThreadUtils {
|
||||
|
||||
private ThreadUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
public static void await(CountDownLatch latch) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package io.spiffe.bundle.x509bundle;
|
||||
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.internal.DummyX509Certificate;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import lombok.Builder;
|
||||
|
|
@ -11,12 +12,10 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.platform.commons.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Stream;
|
||||
|
|
@ -116,7 +115,7 @@ public class X509BundleTest {
|
|||
try {
|
||||
X509Bundle x509Bundle = X509Bundle.load(TrustDomain.of("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
|
||||
assertEquals(2, x509Bundle.getX509Authorities().size());
|
||||
} catch (IOException | CertificateException | URISyntaxException e) {
|
||||
} catch (URISyntaxException | X509BundleException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -126,13 +125,13 @@ public class X509BundleTest {
|
|||
try {
|
||||
X509Bundle.load(TrustDomain.of("example.org"), Paths.get("testdata/x509bundle/non-existent.pem"));
|
||||
fail("should have thrown exception");
|
||||
} catch (IOException | CertificateException e) {
|
||||
} catch (X509BundleException e) {
|
||||
assertEquals("Unable to load X.509 bundle file", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_nullTrustDomain_throwsNullPointerException() throws IOException, CertificateException {
|
||||
void testLoad_nullTrustDomain_throwsNullPointerException() throws X509BundleException {
|
||||
try {
|
||||
X509Bundle.load(null,Paths.get("testdata/x509bundle/non-existent.pem"));
|
||||
fail("should have thrown exception");
|
||||
|
|
@ -142,7 +141,7 @@ public class X509BundleTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testLoad_nullBundlePath_throwsNullPointerException() throws IOException, CertificateException {
|
||||
void testLoad_nullBundlePath_throwsNullPointerException() throws X509BundleException {
|
||||
try {
|
||||
X509Bundle.load(TrustDomain.of("example.org"), null);
|
||||
fail("should have thrown exception");
|
||||
|
|
@ -152,7 +151,7 @@ public class X509BundleTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testParse_nullTrustDomain_throwsNullPointerException() throws IOException, CertificateException {
|
||||
void testParse_nullTrustDomain_throwsNullPointerException() throws X509BundleException {
|
||||
try {
|
||||
X509Bundle.parse(null, "bytes".getBytes());
|
||||
fail("should have thrown exception");
|
||||
|
|
@ -162,7 +161,7 @@ public class X509BundleTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testParse_nullBundlePath_throwsNullPointerException() throws IOException, CertificateException {
|
||||
void testParse_nullBundlePath_throwsNullPointerException() throws X509BundleException {
|
||||
try {
|
||||
X509Bundle.parse(TrustDomain.of("example.org"), null);
|
||||
fail("should have thrown exception");
|
||||
|
|
@ -215,7 +214,7 @@ public class X509BundleTest {
|
|||
// Load bundle2, which contains 2 certificates
|
||||
// The first certificate is the same than the one used in bundle1
|
||||
bundle2 = X509Bundle.load(TrustDomain.of("example.org"), Paths.get(toUri("testdata/x509bundle/certs.pem")));
|
||||
} catch (IOException | CertificateException | URISyntaxException e) {
|
||||
} catch (URISyntaxException | X509BundleException e) {
|
||||
fail(e);
|
||||
}
|
||||
|
||||
|
|
@ -295,7 +294,7 @@ public class X509BundleTest {
|
|||
.name("Parse empty bytes should fail")
|
||||
.path("testdata/x509bundle/empty.pem")
|
||||
.trustDomain(TrustDomain.of("example.org"))
|
||||
.expectedError("No certificates found")
|
||||
.expectedError("Bundle certificates could not be parsed from bundle path")
|
||||
.build()
|
||||
),
|
||||
Arguments.of(TestCase
|
||||
|
|
@ -303,7 +302,7 @@ public class X509BundleTest {
|
|||
.name("Parse non-PEM bytes should fail")
|
||||
.path("testdata/x509bundle/not-pem.pem")
|
||||
.trustDomain(TrustDomain.of("example.org"))
|
||||
.expectedError("Certificate could not be parsed from cert bytes")
|
||||
.expectedError("Bundle certificates could not be parsed from bundle path")
|
||||
.build()
|
||||
),
|
||||
Arguments.of(TestCase
|
||||
|
|
@ -311,7 +310,7 @@ public class X509BundleTest {
|
|||
.name("Parse should fail if no certificate block is is found")
|
||||
.path("testdata/x509bundle/key.pem")
|
||||
.trustDomain(TrustDomain.of("example.org"))
|
||||
.expectedError("Certificate could not be parsed from cert bytes")
|
||||
.expectedError("Bundle certificates could not be parsed from bundle path")
|
||||
.build()
|
||||
),
|
||||
Arguments.of(TestCase
|
||||
|
|
@ -319,7 +318,7 @@ public class X509BundleTest {
|
|||
.name("Parse a corrupted certificate should fail")
|
||||
.path("testdata/x509bundle/corrupted.pem")
|
||||
.trustDomain(TrustDomain.of("example.org"))
|
||||
.expectedError("Certificate could not be parsed from cert bytes")
|
||||
.expectedError("Bundle certificates could not be parsed from bundle path")
|
||||
.build()
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package io.spiffe.internal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AsymmetricKeyAlgorithmTest {
|
||||
|
||||
@Test
|
||||
void parseRSA() {
|
||||
AsymmetricKeyAlgorithm algorithm = AsymmetricKeyAlgorithm.parse("RSA");
|
||||
assertEquals(AsymmetricKeyAlgorithm.RSA, algorithm);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseEC() {
|
||||
AsymmetricKeyAlgorithm algorithm = AsymmetricKeyAlgorithm.parse("EC");
|
||||
assertEquals(AsymmetricKeyAlgorithm.EC, algorithm);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseUnknown() {
|
||||
try {
|
||||
AsymmetricKeyAlgorithm.parse("unknown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Algorithm not supported: unknown", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,9 +17,11 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static io.spiffe.internal.AsymmetricKeyAlgorithm.RSA;
|
||||
|
|
@ -69,7 +71,67 @@ public class CertificateUtilsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testGenerateiRsaPrivateKeyFromBytes() throws URISyntaxException, IOException {
|
||||
void validateCerts_nullTrustedCerts() throws URISyntaxException, IOException, CertificateParsingException {
|
||||
val certPath = Paths.get(toUri("testdata/internal/cert2.pem"));
|
||||
val certBytes = Files.readAllBytes(certPath);
|
||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
||||
|
||||
try {
|
||||
CertificateUtils.validate(chain, null);
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("No trusted Certs", e.getMessage());
|
||||
} catch (CertPathValidatorException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateCerts_emptyTrustedCerts() throws URISyntaxException, IOException, CertificateParsingException {
|
||||
val certPath = Paths.get(toUri("testdata/internal/cert2.pem"));
|
||||
val certBytes = Files.readAllBytes(certPath);
|
||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
||||
|
||||
try {
|
||||
CertificateUtils.validate(chain, Collections.emptyList());
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("No trusted Certs", e.getMessage());
|
||||
} catch (CertPathValidatorException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateCerts_nullChain() throws URISyntaxException, IOException, CertificateParsingException {
|
||||
val certPath = Paths.get(toUri("testdata/internal/cert2.pem"));
|
||||
val certBytes = Files.readAllBytes(certPath);
|
||||
val certificates = CertificateUtils.generateCertificates(certBytes);
|
||||
|
||||
try {
|
||||
CertificateUtils.validate(null, certificates);
|
||||
} catch (CertificateException | CertPathValidatorException e) {
|
||||
fail(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Chain of certificates is empty", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateCerts_emptyChain() throws URISyntaxException, IOException, CertificateParsingException {
|
||||
val certPath = Paths.get(toUri("testdata/internal/cert2.pem"));
|
||||
val certBytes = Files.readAllBytes(certPath);
|
||||
val certificates = CertificateUtils.generateCertificates(certBytes);
|
||||
|
||||
try {
|
||||
CertificateUtils.validate(Collections.emptyList(), certificates);
|
||||
} catch (CertificateException | CertPathValidatorException e) {
|
||||
fail(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Chain of certificates is empty", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateRsaPrivateKeyFromBytes() throws URISyntaxException, IOException {
|
||||
val keyPath = Paths.get(toUri("testdata/internal/privateKeyRsa.pem"));
|
||||
val keyBytes = Files.readAllBytes(keyPath);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import io.spiffe.bundle.x509bundle.X509Bundle;
|
|||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.utils.X509CertificateTestUtils.CertAndKeyPair;
|
||||
import io.spiffe.utils.CertAndKeyPair;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
|
|||
|
|
@ -74,15 +74,14 @@ public class AddressTest {
|
|||
|
||||
@Test
|
||||
void getDefaultAddress() throws Exception {
|
||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/agent.sock" );
|
||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test" );
|
||||
String defaultAddress = Address.getDefaultAddress();
|
||||
assertEquals("unix:/tmp/agent.sock", defaultAddress);
|
||||
assertEquals("unix:/tmp/test", defaultAddress);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultAddress_isBlankThrowsException() throws Exception {
|
||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
|
||||
String defaultAddress = null;
|
||||
try {
|
||||
Address.getDefaultAddress();
|
||||
fail();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
|
@ -21,18 +20,18 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class X509SourceTest {
|
||||
class DefaultX509SourceTest {
|
||||
|
||||
private X509Source x509Source;
|
||||
private DefaultX509Source x509Source;
|
||||
private WorkloadApiClientStub workloadApiClient;
|
||||
private WorkloadApiClientErrorStub workloadApiClientErrorStub;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, X509SourceException, SocketEndpointAddressException {
|
||||
void setUp() throws X509SourceException, SocketEndpointAddressException {
|
||||
workloadApiClient = new WorkloadApiClientStub();
|
||||
X509Source.X509SourceOptions options = X509Source.X509SourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
||||
DefaultX509Source.X509SourceOptions options = DefaultX509Source.X509SourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
||||
System.setProperty(JwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
|
||||
x509Source = X509Source.newSource(options);
|
||||
x509Source = DefaultX509Source.newSource(options);
|
||||
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
|
||||
}
|
||||
|
||||
|
|
@ -99,14 +98,14 @@ class X509SourceTest {
|
|||
|
||||
@Test
|
||||
void newSource_success() {
|
||||
val options = X509Source.X509SourceOptions
|
||||
val options = DefaultX509Source.X509SourceOptions
|
||||
.builder()
|
||||
.workloadApiClient(workloadApiClient)
|
||||
.svidPicker((list) -> list.get(0))
|
||||
.initTimeout(Duration.ofSeconds(0))
|
||||
.build();
|
||||
try {
|
||||
X509Source jwtSource = X509Source.newSource(options);
|
||||
DefaultX509Source jwtSource = DefaultX509Source.newSource(options);
|
||||
assertNotNull(jwtSource);
|
||||
} catch (SocketEndpointAddressException | X509SourceException e) {
|
||||
fail(e);
|
||||
|
|
@ -116,7 +115,7 @@ class X509SourceTest {
|
|||
@Test
|
||||
void newSource_nullParam() {
|
||||
try {
|
||||
X509Source.newSource(null);
|
||||
DefaultX509Source.newSource(null);
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("options is marked non-null but is null", e.getMessage());
|
||||
|
|
@ -127,12 +126,12 @@ class X509SourceTest {
|
|||
@Test
|
||||
void newSource_timeout() throws Exception {
|
||||
try {
|
||||
val options = X509Source.X509SourceOptions
|
||||
val options = DefaultX509Source.X509SourceOptions
|
||||
.builder()
|
||||
.initTimeout(Duration.ofSeconds(1))
|
||||
.spiffeSocketPath("unix:/tmp/test")
|
||||
.build();
|
||||
X509Source.newSource(options);
|
||||
DefaultX509Source.newSource(options);
|
||||
fail();
|
||||
} catch (X509SourceException e) {
|
||||
assertEquals("Error creating X.509 source", e.getMessage());
|
||||
|
|
@ -143,13 +142,13 @@ class X509SourceTest {
|
|||
|
||||
@Test
|
||||
void newSource_errorFetchingJwtBundles() {
|
||||
val options = X509Source.X509SourceOptions
|
||||
val options = DefaultX509Source.X509SourceOptions
|
||||
.builder()
|
||||
.workloadApiClient(workloadApiClientErrorStub)
|
||||
.spiffeSocketPath("unix:/tmp/test")
|
||||
.build();
|
||||
try {
|
||||
X509Source.newSource(options);
|
||||
DefaultX509Source.newSource(options);
|
||||
fail();
|
||||
} catch (X509SourceException e) {
|
||||
assertEquals("Error creating X.509 source", e.getMessage());
|
||||
|
|
@ -164,7 +163,7 @@ class X509SourceTest {
|
|||
try {
|
||||
// just in case the variable is defined in the environment
|
||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
|
||||
X509Source.newSource();
|
||||
DefaultX509Source.newSource();
|
||||
fail();
|
||||
} catch (X509SourceException | SocketEndpointAddressException e) {
|
||||
fail();
|
||||
|
|
@ -4,15 +4,12 @@ import io.spiffe.exception.JwtBundleException;
|
|||
import io.spiffe.exception.X509ContextException;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.workloadapi.grpc.Workload;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class GrpcConversionUtilsTest {
|
||||
|
||||
|
|
@ -44,16 +41,4 @@ class GrpcConversionUtilsTest {
|
|||
assertEquals("X.509 Bundles could not be processed", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrivateConstructor_InstanceCannotBeCreated() throws IllegalAccessException, InstantiationException {
|
||||
val constructor = GrpcConversionUtils.class.getDeclaredConstructors()[0];
|
||||
constructor.setAccessible(true);
|
||||
try {
|
||||
constructor.newInstance();
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertEquals("This is a utility class and cannot be instantiated", e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import io.spiffe.bundle.x509bundle.X509Bundle;
|
|||
import io.spiffe.bundle.x509bundle.X509BundleSet;
|
||||
import io.spiffe.exception.JwtBundleException;
|
||||
import io.spiffe.exception.JwtSvidException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
|
|
@ -22,7 +23,6 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
|
@ -127,7 +127,7 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
|
|||
Path pathBundle = Paths.get(toUri(x509Bundle));
|
||||
byte[] bundleBytes = Files.readAllBytes(pathBundle);
|
||||
return X509Bundle.parse(TrustDomain.of("example.org"), bundleBytes);
|
||||
} catch (IOException | CertificateException | URISyntaxException e) {
|
||||
} catch (IOException | URISyntaxException | X509BundleException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package io.spiffe.utils;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public class CertAndKeyPair {
|
||||
KeyPair keyPair;
|
||||
X509Certificate certificate;
|
||||
|
||||
public CertAndKeyPair(X509Certificate certificate, KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public X509Certificate getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,6 @@ import java.util.Set;
|
|||
public class TestUtils {
|
||||
|
||||
private TestUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
public static KeyPair generateECKeyPair(Curve curve) {
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package io.spiffe.utils;
|
||||
|
||||
import lombok.Value;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
|
|
@ -59,17 +58,6 @@ public class X509CertificateTestUtils {
|
|||
return new CertAndKeyPair(cert, certKeyPair);
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class CertAndKeyPair {
|
||||
KeyPair keyPair;
|
||||
X509Certificate certificate;
|
||||
|
||||
public CertAndKeyPair(X509Certificate certificate, KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
this.certificate = certificate;
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
return keyGen.generateKeyPair();
|
||||
|
|
@ -16,4 +16,6 @@ shadowJar {
|
|||
dependencies {
|
||||
api (project(':java-spiffe-core'))
|
||||
implementation group: 'commons-cli', name: 'commons-cli', version: '1.4'
|
||||
|
||||
testImplementation(testFixtures(project(":java-spiffe-core")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import java.net.URISyntaxException;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import static io.spiffe.helper.utils.TestUtils.toUri;
|
||||
import static io.spiffe.utils.TestUtils.toUri;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import java.net.URISyntaxException;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static io.spiffe.helper.utils.TestUtils.toUri;
|
||||
import static io.spiffe.utils.TestUtils.toUri;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package io.spiffe.helper.keystore;
|
|||
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.helper.exception.KeyStoreHelperException;
|
||||
import io.spiffe.helper.utils.TestUtils;
|
||||
import io.spiffe.internal.CertificateUtils;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.utils.TestUtils;
|
||||
import io.spiffe.workloadapi.Address;
|
||||
import io.spiffe.workloadapi.WorkloadApiClient;
|
||||
import lombok.SneakyThrows;
|
||||
|
|
@ -80,7 +80,7 @@ class KeyStoreHelperTest {
|
|||
try (val keystoreHelper = KeyStoreHelper.create(options)) {
|
||||
keystoreHelper.run(false);
|
||||
} catch (KeyStoreHelperException e) {
|
||||
fail();
|
||||
fail(e);
|
||||
}
|
||||
|
||||
checkPrivateKeyEntry(keyStoreFilePath, keyStorePass, keyPass, keyStoreType, alias);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package io.spiffe.helper.keystore;
|
||||
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.internal.CertificateUtils;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
|
|
@ -25,7 +26,7 @@ import java.security.UnrecoverableKeyException;
|
|||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static io.spiffe.helper.utils.TestUtils.toUri;
|
||||
import static io.spiffe.utils.TestUtils.toUri;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
|
@ -41,7 +42,7 @@ public class KeyStoreTest {
|
|||
private Path keyStoreFilePath;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws X509SvidException, URISyntaxException, IOException, CertificateException {
|
||||
void setup() throws X509SvidException, URISyntaxException, X509BundleException {
|
||||
x509Svid = X509Svid.load(
|
||||
Paths.get(toUri("testdata/svid.pem")),
|
||||
Paths.get(toUri("testdata/svid.key")));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import io.spiffe.bundle.x509bundle.X509Bundle;
|
|||
import io.spiffe.bundle.x509bundle.X509BundleSet;
|
||||
import io.spiffe.exception.JwtBundleException;
|
||||
import io.spiffe.exception.JwtSvidException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
|
|
@ -22,7 +23,6 @@ import java.net.URISyntaxException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Collections;
|
||||
|
||||
public class WorkloadApiClientStub implements WorkloadApiClient {
|
||||
|
|
@ -83,7 +83,7 @@ public class WorkloadApiClientStub implements WorkloadApiClient {
|
|||
Path pathBundle = Paths.get(toUri(x509Bundle));
|
||||
byte[] bundleBytes = Files.readAllBytes(pathBundle);
|
||||
return X509Bundle.parse(TrustDomain.of("example.org"), bundleBytes);
|
||||
} catch (IOException | CertificateException e) {
|
||||
} catch (IOException | X509BundleException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
package io.spiffe.helper.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Util methods for testing.
|
||||
*/
|
||||
public class TestUtils {
|
||||
|
||||
private TestUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
public static void setEnvironmentVariable(String variableName, String value) throws Exception {
|
||||
Class<?> processEnvironment = Class.forName("java.lang.ProcessEnvironment");
|
||||
|
||||
Field unmodifiableMapField = getField(processEnvironment, "theUnmodifiableEnvironment");
|
||||
Object unmodifiableMap = unmodifiableMapField.get(null);
|
||||
injectIntoUnmodifiableMap(variableName, value, unmodifiableMap);
|
||||
|
||||
Field mapField = getField(processEnvironment, "theEnvironment");
|
||||
Map<String, String> map = (Map<String, String>) mapField.get(null);
|
||||
map.put(variableName, value);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Class<?> clazz, String methodName, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Method method = clazz.getDeclaredMethod(methodName);
|
||||
method.setAccessible(true);
|
||||
return method.invoke(args);
|
||||
}
|
||||
|
||||
public static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
private static void injectIntoUnmodifiableMap(String key, String value, Object map) throws ReflectiveOperationException {
|
||||
Class unmodifiableMap = Class.forName("java.util.Collections$UnmodifiableMap");
|
||||
Field field = getField(unmodifiableMap, "m");
|
||||
Object obj = field.get(map);
|
||||
((Map<String, String>) obj).put(key, value);
|
||||
}
|
||||
|
||||
public static URI toUri(String path) throws URISyntaxException {
|
||||
return Thread.currentThread().getContextClassLoader().getResource(path).toURI();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,6 @@ shadowJar {
|
|||
|
||||
dependencies {
|
||||
api(project(":java-spiffe-core"))
|
||||
|
||||
testImplementation(testFixtures(project(":java-spiffe-core")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import java.security.Security;
|
|||
final class EnvironmentUtils {
|
||||
|
||||
private EnvironmentUtils() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,11 +40,11 @@ final class EnvironmentUtils {
|
|||
*/
|
||||
static String getProperty(final String variableName) {
|
||||
String value;
|
||||
value = Security.getProperty(variableName);
|
||||
value = System.getProperty(variableName);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
value = System.getProperty(variableName);
|
||||
value = Security.getProperty(variableName);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package io.spiffe.provider;
|
|||
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.provider.exception.SpiffeProviderException;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
import io.spiffe.workloadapi.DefaultX509Source;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import lombok.NonNull;
|
||||
import lombok.val;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactorySpi;
|
||||
|
|
@ -16,7 +18,7 @@ import java.security.KeyStore;
|
|||
* Implementation of a {@link KeyManagerFactorySpi} to create a {@link KeyManager} that is backed by the Workload API.
|
||||
* <p>
|
||||
* The Java Security API will call <code>engineGetKeyManagers()</code> to get an instance of a KeyManager.
|
||||
* This KeyManager instance is injected with an {@link X509Source} to obtain the latest X.509 SVIDs updates
|
||||
* This KeyManager instance is injected with an {@link DefaultX509Source} to obtain the latest X.509 SVIDs updates
|
||||
* from the Workload API.
|
||||
*
|
||||
* @see SpiffeSslContextFactory
|
||||
|
|
@ -27,14 +29,14 @@ import java.security.KeyStore;
|
|||
public final class SpiffeKeyManagerFactory extends KeyManagerFactorySpi {
|
||||
|
||||
/**
|
||||
* Default method for creating the KeyManager, uses an {@link X509Source} instance
|
||||
* Default method for creating the KeyManager, uses an {@link DefaultX509Source} instance
|
||||
* that is handled by the Singleton {@link X509SourceManager}
|
||||
*
|
||||
* @throws SpiffeProviderException in case there is an error setting up the X.509 source
|
||||
*/
|
||||
@Override
|
||||
protected KeyManager[] engineGetKeyManagers() {
|
||||
val x509Source = createX509Source();
|
||||
val x509Source = getX509Source();
|
||||
val spiffeKeyManager = new SpiffeKeyManager(x509Source);
|
||||
return new KeyManager[]{spiffeKeyManager};
|
||||
}
|
||||
|
|
@ -60,7 +62,7 @@ public final class SpiffeKeyManagerFactory extends KeyManagerFactorySpi {
|
|||
//no implementation needed
|
||||
}
|
||||
|
||||
private X509Source createX509Source() {
|
||||
private X509Source getX509Source() {
|
||||
try {
|
||||
return X509SourceManager.getX509Source();
|
||||
} catch (X509SourceException e) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.workloadapi.DefaultX509Source;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Builder;
|
||||
|
|
@ -30,14 +31,14 @@ public final class SpiffeSslContextFactory {
|
|||
|
||||
/**
|
||||
* Creates an {@link SSLContext} initialized with a {@link SpiffeKeyManager} and {@link SpiffeTrustManager}
|
||||
* that are backed by the Workload API via an {@link X509Source}.
|
||||
* that are backed by the Workload API via an {@link DefaultX509Source}.
|
||||
*
|
||||
* @param options {@link SslContextOptions}. The option {@link X509Source} must be not null.
|
||||
* @param options {@link SslContextOptions}. The option {@link DefaultX509Source} must be not null.
|
||||
* If the option <code>acceptedSpiffeIdsSupplier</code> is not provided, the Set of accepted SPIFFE IDs
|
||||
* is read from the Security or System Property <code>ssl.spiffe.accept</code>.
|
||||
* If the sslProtocol is not provided, the default TLSv1.2 is used.
|
||||
* @return an initialized {@link SSLContext}
|
||||
* @throws IllegalArgumentException if the {@link X509Source} is not provided in the options
|
||||
* @throws IllegalArgumentException if the {@link DefaultX509Source} is not provided in the options
|
||||
* @throws NoSuchAlgorithmException if there is a problem creating the SSL context
|
||||
* @throws KeyManagementException if there is a problem initializing the SSL context
|
||||
*/
|
||||
|
|
@ -85,7 +86,7 @@ public final class SpiffeSslContextFactory {
|
|||
* <p>
|
||||
* <code>sslProtocol</code> The SSL Protocol. Default: TLSv1.2
|
||||
* <p>
|
||||
* <code>x509Source</code> An {@link X509Source} that provides the X.509 materials.
|
||||
* <code>x509Source</code> An {@link DefaultX509Source} that provides the X.509 materials.
|
||||
* <p>
|
||||
* <code>acceptedSpiffeIdsSupplier</code> A supplier of a set of {@link SpiffeId} that will be accepted
|
||||
* for a secure socket connection.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import lombok.val;
|
||||
import io.spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
||||
import lombok.val;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
|
|
@ -31,6 +31,10 @@ public class SpiffeSslSocketFactory extends SSLSocketFactory {
|
|||
delegate = sslContext.getSocketFactory();
|
||||
}
|
||||
|
||||
SpiffeSslSocketFactory(final SSLSocketFactory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import io.spiffe.bundle.BundleSource;
|
|||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.provider.exception.SpiffeProviderException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.SpiffeIdUtils;
|
||||
import io.spiffe.workloadapi.DefaultX509Source;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import lombok.NonNull;
|
||||
import lombok.val;
|
||||
|
|
@ -22,10 +24,10 @@ import static io.spiffe.provider.SpiffeProviderConstants.SSL_SPIFFE_ACCEPT_PROPE
|
|||
|
||||
/**
|
||||
* Implementation of a {@link javax.net.ssl.TrustManagerFactory} to create a {@link SpiffeTrustManager} backed by a
|
||||
* {@link X509Source} that is maintained via the Workload API.
|
||||
* {@link DefaultX509Source} that is maintained via the Workload API.
|
||||
* <p>
|
||||
* The Java Security API will call <code>engineGetTrustManagers()</code> to get an instance of a {@link TrustManager}.
|
||||
* This TrustManager instance gets injected an {@link X509Source}, which implements {@link BundleSource} and
|
||||
* This TrustManager instance gets injected an {@link DefaultX509Source}, which implements {@link BundleSource} and
|
||||
* keeps bundles updated.
|
||||
* The TrustManager also gets a Supplier of a Set of accepted SPIFFE IDs used to validate the SPIFFE ID from the SVIDs
|
||||
* presented by a peer during the secure socket handshake.
|
||||
|
|
@ -43,7 +45,7 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi {
|
|||
() -> SpiffeIdUtils.toSetOfSpiffeIds(EnvironmentUtils.getProperty(SSL_SPIFFE_ACCEPT_PROPERTY));
|
||||
|
||||
/**
|
||||
* Creates a {@link TrustManager} initialized with the {@link X509Source} instance
|
||||
* Creates a {@link TrustManager} initialized with the {@link DefaultX509Source} instance
|
||||
* that is handled by the {@link X509SourceManager}, and with and a supplier of accepted SPIFFE IDs. that reads
|
||||
* the Set of {@link SpiffeId} from the System Property 'ssl.spiffe.accept'.
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package io.spiffe.provider;
|
|||
|
||||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.workloadapi.DefaultX509Source;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
|
||||
/**
|
||||
* Singleton that handles an instance of an {@link X509Source}.
|
||||
* Singleton that handles an instance of a {@link DefaultX509Source} that implements an {@link X509Source}.
|
||||
* <p>
|
||||
* The default SPIFFE socket endpoint address is used to create an X.509 Source backed by the
|
||||
* Workload API.
|
||||
|
|
@ -13,7 +14,7 @@ import io.spiffe.workloadapi.X509Source;
|
|||
* If the environment variable is not defined, it will throw an <code>IllegalStateException</code>.
|
||||
* If the X509Source cannot be initialized, it will throw a <code>RuntimeException</code>.
|
||||
* <p>
|
||||
* This Singleton needed to be able to handle a single {@link X509Source} instance
|
||||
* This Singleton needed to be able to handle a single {@link DefaultX509Source} instance
|
||||
* to be used by the {@link SpiffeKeyManagerFactory} and {@link SpiffeTrustManagerFactory} to inject it
|
||||
* in the {@link SpiffeKeyManager} and {@link SpiffeTrustManager} instances.
|
||||
*/
|
||||
|
|
@ -28,13 +29,13 @@ public final class X509SourceManager {
|
|||
* Returns the single instance handled by this singleton. If the instance has not been
|
||||
* created yet, it creates a new X509Source and initializes the singleton in a thread safe way.
|
||||
*
|
||||
* @return the single instance of {@link X509Source}
|
||||
* @return the single instance of {@link DefaultX509Source}
|
||||
* @throws X509SourceException if the X.509 source could not be initialized
|
||||
* @throws SocketEndpointAddressException is the socket endpoint address is not valid
|
||||
*/
|
||||
public static synchronized X509Source getX509Source() throws X509SourceException, SocketEndpointAddressException {
|
||||
if (x509Source == null) {
|
||||
x509Source = X509Source.newSource();
|
||||
x509Source = DefaultX509Source.newSource();
|
||||
}
|
||||
return x509Source;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package io.spiffe.provider;
|
||||
package io.spiffe.provider.exception;
|
||||
|
||||
/**
|
||||
* Unchecked exception thrown when there is an error setting up the source of SVIDs and bundles.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class EnvironmentUtilsTest {
|
||||
|
||||
@Test
|
||||
void getPropertyFromSystem() {
|
||||
System.setProperty("testVariable", "example");
|
||||
String value = EnvironmentUtils.getProperty("testVariable");
|
||||
assertEquals("example", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyFromSecurity() {
|
||||
Security.setProperty("testVariable", "example");
|
||||
String value = EnvironmentUtils.getProperty("testVariable");
|
||||
assertEquals("example", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSecurityPropertyWithDefaultValue() {
|
||||
Security.setProperty("testVariable", "example");
|
||||
String value = EnvironmentUtils.getProperty("otherVariable", "default");
|
||||
assertEquals("default", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSystemPropertyWithDefaultValue() {
|
||||
System.setProperty("testVariable", "example");
|
||||
String value = EnvironmentUtils.getProperty("testVariable", "default");
|
||||
assertEquals("example", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyReturnBlankForNotFoundVariable() {
|
||||
String value = EnvironmentUtils.getProperty("unknown");
|
||||
assertEquals("", value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static io.spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS;
|
||||
import static io.spiffe.utils.TestUtils.toUri;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class SpiffeKeyManagerFactoryTest {
|
||||
|
||||
@Test
|
||||
void engineGetKeyManagers_usingX509SourceManager() throws NoSuchFieldException, IllegalAccessException {
|
||||
// init singleton with an instance
|
||||
Field field = X509SourceManager.class.getDeclaredField("x509Source");
|
||||
field.setAccessible(true);
|
||||
X509Source source = new X509SourceStub();
|
||||
field.set(null, source);
|
||||
|
||||
KeyManager[] keyManagers = new SpiffeKeyManagerFactory().engineGetKeyManagers();
|
||||
SpiffeKeyManager keyManager = (SpiffeKeyManager) keyManagers[0];
|
||||
|
||||
X509Certificate[] chain = keyManager.getCertificateChain(DEFAULT_ALIAS);
|
||||
X509Certificate certificate = chain[0];
|
||||
|
||||
assertEquals(source.getX509Svid().getChain().get(0), certificate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetKeyManagers_passingAX509SvidSource() throws URISyntaxException, X509SvidException {
|
||||
Path cert = Paths.get(toUri("testdata/cert.pem"));
|
||||
Path key = Paths.get(toUri("testdata/key.pem"));
|
||||
X509Svid svid = X509Svid.load(cert, key);
|
||||
X509SvidSource x509SvidSource = () -> svid;
|
||||
|
||||
KeyManager[] keyManagers = new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource);
|
||||
SpiffeKeyManager keyManager = (SpiffeKeyManager) keyManagers[0];
|
||||
|
||||
assertEquals(svid.getChainArray()[0], keyManager.getCertificateChain(DEFAULT_ALIAS)[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetKeyManagers_nullParameter() {
|
||||
try {
|
||||
new SpiffeKeyManagerFactory().engineGetKeyManagers(null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("x509SvidSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineInit() {
|
||||
new SpiffeKeyManagerFactory().engineInit(null);
|
||||
new SpiffeKeyManagerFactory().engineInit(null, null);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.internal.CertificateUtils;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import io.spiffe.svid.x509svid.X509SvidSource;
|
||||
|
|
@ -10,16 +9,21 @@ import org.junit.jupiter.api.Test;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static io.spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS;
|
||||
import static io.spiffe.utils.X509CertificateTestUtils.createCertificate;
|
||||
import static io.spiffe.utils.X509CertificateTestUtils.createRootCA;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SpiffeKeyManagerTest {
|
||||
|
|
@ -27,24 +31,39 @@ public class SpiffeKeyManagerTest {
|
|||
@Mock
|
||||
X509SvidSource x509SvidSource;
|
||||
|
||||
X509KeyManager keyManager;
|
||||
SpiffeKeyManager spiffeKeyManager;
|
||||
X509Svid x509Svid;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws X509SvidException, URISyntaxException {
|
||||
void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
keyManager = (X509KeyManager) new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource)[0];
|
||||
x509Svid = X509Svid
|
||||
.load(
|
||||
|
||||
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test");
|
||||
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", rootCa, false);
|
||||
|
||||
X509Svid svid = X509Svid.parseRaw(leaf.getCertificate().getEncoded(), leaf.getKeyPair().getPrivate().getEncoded());
|
||||
|
||||
x509Svid = X509Svid.load(
|
||||
Paths.get(toUri("testdata/cert.pem")),
|
||||
Paths.get(toUri("testdata/key.pem")));
|
||||
when(x509SvidSource.getX509Svid()).thenReturn(x509Svid);
|
||||
|
||||
spiffeKeyManager = new SpiffeKeyManager(x509SvidSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCertificateChain_returnsAnArrayOfX509Certificates() throws CertificateException {
|
||||
when(x509SvidSource.getX509Svid()).thenReturn(x509Svid);
|
||||
void testCreateNewSpiffeKeyManager_nullSource() {
|
||||
try {
|
||||
new SpiffeKeyManager(null);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals("x509SvidSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
val certificateChain = keyManager.getCertificateChain(DEFAULT_ALIAS);
|
||||
@Test
|
||||
void getCertificateChain() throws CertificateException {
|
||||
val certificateChain = spiffeKeyManager.getCertificateChain(DEFAULT_ALIAS);
|
||||
val spiffeId = CertificateUtils.getSpiffeId(certificateChain[0]);
|
||||
|
||||
assertAll(
|
||||
|
|
@ -53,15 +72,66 @@ public class SpiffeKeyManagerTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCertificateChain_aliasNotSupported() {
|
||||
X509Certificate[] chain = spiffeKeyManager.getCertificateChain("other_alias");
|
||||
assertEquals(0, chain.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPrivateKey_aliasIsSpiffe_returnAPrivateKey() {
|
||||
when(x509SvidSource.getX509Svid()).thenReturn(x509Svid);
|
||||
|
||||
val privateKey = keyManager.getPrivateKey(DEFAULT_ALIAS);
|
||||
|
||||
val privateKey = spiffeKeyManager.getPrivateKey(DEFAULT_ALIAS);
|
||||
assertNotNull(privateKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPrivateKey_aliasNotSupported() {
|
||||
PrivateKey privateKey = spiffeKeyManager.getPrivateKey("other_alias");
|
||||
assertNull(privateKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientAliases() {
|
||||
String[] aliases = spiffeKeyManager.getClientAliases("EC", null);
|
||||
assertEquals(DEFAULT_ALIAS, aliases[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void chooseClientAlias() {
|
||||
String alias = spiffeKeyManager.chooseClientAlias(new String[]{"EC"}, null, null);
|
||||
assertEquals(DEFAULT_ALIAS, alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
void chooseEngineClientAlias() {
|
||||
String alias = spiffeKeyManager.chooseEngineClientAlias(new String[]{"EC"}, null, null);
|
||||
assertEquals(DEFAULT_ALIAS, alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getServerAliases() {
|
||||
String[] aliases = spiffeKeyManager.getServerAliases("EC", null);
|
||||
assertEquals(DEFAULT_ALIAS, aliases[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void chooseEngineServerAlias() {
|
||||
String alias = spiffeKeyManager.chooseEngineServerAlias("EC", null, null);
|
||||
assertEquals(DEFAULT_ALIAS, alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
void chooseServerAlias() {
|
||||
String alias = spiffeKeyManager.chooseServerAlias("EC", null, null);
|
||||
assertEquals(DEFAULT_ALIAS, alias);
|
||||
}
|
||||
|
||||
@Test
|
||||
void chooseServerAlias_keyTypeNotSupported() {
|
||||
String alias = spiffeKeyManager.chooseServerAlias("not-supported", null, null);
|
||||
assertNull(alias);
|
||||
}
|
||||
|
||||
private URI toUri(String path) throws URISyntaxException {
|
||||
return Thread.currentThread().getContextClassLoader().getResource(path).toURI();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import static io.spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SpiffeKeyStoreTest {
|
||||
|
||||
private static SpiffeKeyStore spiffeKeyStore;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
spiffeKeyStore = new SpiffeKeyStore();
|
||||
}
|
||||
@Test
|
||||
void engineGetKey() {
|
||||
assertNull(spiffeKeyStore.engineGetKey("alias", "pass".toCharArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetCertificateChain() {
|
||||
Certificate[] chain = spiffeKeyStore.engineGetCertificateChain("alias");
|
||||
assertEquals(0, chain.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetCertificate() {
|
||||
assertNull(spiffeKeyStore.engineGetCertificate("alias"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetCreationDate() {
|
||||
assertNotNull(spiffeKeyStore.engineGetCreationDate("alias"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineSetKeyEntry() {
|
||||
spiffeKeyStore.engineSetKeyEntry("alias", null, null);
|
||||
spiffeKeyStore.engineSetKeyEntry("alias", null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEngineSetKeyEntry() {
|
||||
spiffeKeyStore.engineSetKeyEntry("alias", null, null);
|
||||
spiffeKeyStore.engineSetKeyEntry("alias", null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineSetCertificateEntry() {
|
||||
spiffeKeyStore.engineSetCertificateEntry("alias", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineDeleteEntry() {
|
||||
spiffeKeyStore.engineDeleteEntry("alias");
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineAliases() {
|
||||
Enumeration<String> enumeration = spiffeKeyStore.engineAliases();
|
||||
assertEquals(DEFAULT_ALIAS, enumeration.nextElement());
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineContainsAlias() {
|
||||
assertTrue(spiffeKeyStore.engineContainsAlias(DEFAULT_ALIAS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineSize() {
|
||||
assertEquals(1, spiffeKeyStore.engineSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineIsKeyEntry() {
|
||||
assertTrue(spiffeKeyStore.engineIsKeyEntry(DEFAULT_ALIAS));
|
||||
assertFalse(spiffeKeyStore.engineIsKeyEntry("alias"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineIsCertificateEntry() {
|
||||
assertTrue(spiffeKeyStore.engineIsCertificateEntry(DEFAULT_ALIAS));
|
||||
assertFalse(spiffeKeyStore.engineIsCertificateEntry("alias"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetCertificateAlias() {
|
||||
assertEquals(DEFAULT_ALIAS, spiffeKeyStore.engineGetCertificateAlias(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineStore() {
|
||||
spiffeKeyStore.engineStore(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineLoad() {
|
||||
spiffeKeyStore.engineLoad(null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class SpiffeProviderTest {
|
||||
|
||||
@Test
|
||||
void install() throws NoSuchAlgorithmException {
|
||||
SpiffeProvider.install();
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SpiffeProviderConstants.ALGORITHM);
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(SpiffeProviderConstants.ALGORITHM);
|
||||
assertNotNull(keyManagerFactory);
|
||||
assertNotNull(trustManagerFactory);
|
||||
|
||||
// should do nothing
|
||||
SpiffeProvider.install();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class SpiffeSslContextFactoryTest {
|
||||
|
||||
X509Source x509Source;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
x509Source = new X509SourceStub();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_withX509Source() {
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions
|
||||
.builder().x509Source(x509Source).build();
|
||||
try {
|
||||
assertNotNull(SpiffeSslContextFactory.getSslContext(options));
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_withSupplierOfSpiffeIds() {
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions
|
||||
.builder().x509Source(x509Source).acceptedSpiffeIdsSupplier(Collections::emptySet).build();
|
||||
try {
|
||||
assertNotNull(SpiffeSslContextFactory.getSslContext(options));
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_withAcceptAny() {
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions
|
||||
.builder().x509Source(x509Source).acceptAnySpiffeId(true).build();
|
||||
try {
|
||||
assertNotNull(SpiffeSslContextFactory.getSslContext(options));
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_withOtherSslProtocol() {
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions
|
||||
.builder().x509Source(x509Source).sslProtocol("TLSv1.1").build();
|
||||
try {
|
||||
assertNotNull(SpiffeSslContextFactory.getSslContext(options));
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_nullOptions() throws KeyManagementException, NoSuchAlgorithmException {
|
||||
try {
|
||||
SpiffeSslContextFactory.getSslContext(null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("options is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSslContext_nullX509Source() throws KeyManagementException, NoSuchAlgorithmException {
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions.builder().build();
|
||||
try {
|
||||
SpiffeSslContextFactory.getSslContext(options);
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("x509Source option cannot be null, an X.509 Source must be provided", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class SpiffeSslSocketFactoryTest {
|
||||
|
||||
@Mock
|
||||
SSLSocketFactory sslSocketFactoryMock;
|
||||
|
||||
private SpiffeSslSocketFactory spiffeSslSocketFactory;
|
||||
private SSLSocketFactory socketFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
X509SourceStub x509Source = new X509SourceStub();
|
||||
SpiffeSslContextFactory.SslContextOptions options = SpiffeSslContextFactory.SslContextOptions.builder().x509Source(x509Source).build();
|
||||
spiffeSslSocketFactory = new SpiffeSslSocketFactory(options);
|
||||
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(options);
|
||||
socketFactory = sslContext.getSocketFactory();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultCipherSuites() {
|
||||
String[] defaultCipherSuites = spiffeSslSocketFactory.getDefaultCipherSuites();
|
||||
String[] expected = socketFactory.getDefaultCipherSuites();
|
||||
assertArrayEquals(expected, defaultCipherSuites);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSupportedCipherSuites() {
|
||||
String[] supportedCipherSuites = spiffeSslSocketFactory.getSupportedCipherSuites();
|
||||
String[] expected = socketFactory.getSupportedCipherSuites();
|
||||
assertArrayEquals(expected, supportedCipherSuites);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSocket() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket()).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket();
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSocket_HostParameter() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket("string", 1)).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket("string", 1);
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSocket_InetAddressParameter() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket(InetAddress.getLocalHost(), 1)).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket(InetAddress.getLocalHost(), 1);
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSocket_StringInetAddressParameter() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket("string", 1, InetAddress.getLocalHost(), 2)).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket("string", 1, InetAddress.getLocalHost(), 2);
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSocket_InetAddressPortInetAddressPortParameters() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket(InetAddress.getLocalHost(), 1, InetAddress.getLocalHost(), 2)).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket(InetAddress.getLocalHost(), 1, InetAddress.getLocalHost(), 2);
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSocket_parametersSocketStringPortAutoClose() throws IOException {
|
||||
SpiffeSslSocketFactory socketFactory = new SpiffeSslSocketFactory(sslSocketFactoryMock);
|
||||
|
||||
Socket expected = new Socket();
|
||||
Socket s = new Socket();
|
||||
when(sslSocketFactoryMock.createSocket(s, "string", 1, true)).thenReturn(expected);
|
||||
Socket socket = socketFactory.createSocket(s, "string", 1, true);
|
||||
|
||||
assertEquals(expected, socket);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.bundle.BundleSource;
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static io.spiffe.provider.SpiffeProviderConstants.SSL_SPIFFE_ACCEPT_PROPERTY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SpiffeTrustManagerFactoryTest {
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagers() throws Exception {
|
||||
System.setProperty(SSL_SPIFFE_ACCEPT_PROPERTY, "spiffe://example.org/test1 | spiffe://example.org/test2" );
|
||||
|
||||
// init singleton with an instance
|
||||
Field field = X509SourceManager.class.getDeclaredField("x509Source");
|
||||
field.setAccessible(true);
|
||||
X509Source source = new X509SourceStub();
|
||||
field.set(null, source);
|
||||
|
||||
TrustManager[] trustManagers = new SpiffeTrustManagerFactory().engineGetTrustManagers();
|
||||
SpiffeTrustManager trustManager = (SpiffeTrustManager) trustManagers[0];
|
||||
|
||||
BundleSource<X509Bundle> bundleSource = getX509BundleBundleSource(trustManager);
|
||||
Supplier<Set<SpiffeId>> supplier = getSetSupplier(trustManager);
|
||||
boolean acceptAny = isAcceptAny(trustManager);
|
||||
|
||||
TrustDomain trustDomain = TrustDomain.of("example.org");
|
||||
assertEquals(source.getBundleForTrustDomain(trustDomain), bundleSource.getBundleForTrustDomain(trustDomain));
|
||||
assertEquals(2, supplier.get().size());
|
||||
assertTrue(supplier.get().contains(SpiffeId.parse("spiffe://example.org/test1")));
|
||||
assertFalse(acceptAny);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testEngineGetTrustManagers_withCustomSource() throws NoSuchFieldException, IllegalAccessException, BundleNotFoundException {
|
||||
System.setProperty(SSL_SPIFFE_ACCEPT_PROPERTY, "spiffe://example.org/test1 | spiffe://example.org/test2" );
|
||||
|
||||
X509Source source = new X509SourceStub();
|
||||
TrustManager[] trustManagers = new SpiffeTrustManagerFactory().engineGetTrustManagers(source);
|
||||
SpiffeTrustManager trustManager = (SpiffeTrustManager) trustManagers[0];
|
||||
|
||||
BundleSource<X509Bundle> bundleSource = getX509BundleBundleSource(trustManager);
|
||||
Supplier<Set<SpiffeId>> supplier = getSetSupplier(trustManager);
|
||||
boolean acceptAny = isAcceptAny(trustManager);
|
||||
|
||||
TrustDomain trustDomain = TrustDomain.of("example.org");
|
||||
assertEquals(source.getBundleForTrustDomain(trustDomain), bundleSource.getBundleForTrustDomain(trustDomain));
|
||||
assertEquals(2, supplier.get().size());
|
||||
assertTrue(supplier.get().contains(SpiffeId.parse("spiffe://example.org/test1")));
|
||||
assertFalse(acceptAny);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagersAcceptAnySpiffeId() throws NoSuchFieldException, IllegalAccessException {
|
||||
X509Source source = new X509SourceStub();
|
||||
TrustManager[] trustManagers = new SpiffeTrustManagerFactory().engineGetTrustManagersAcceptAnySpiffeId(source);
|
||||
SpiffeTrustManager trustManager = (SpiffeTrustManager) trustManagers[0];
|
||||
boolean acceptAny = isAcceptAny(trustManager);
|
||||
assertTrue(acceptAny);
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagersAcceptAnySpiffeId_nullParameter() {
|
||||
try {
|
||||
new SpiffeTrustManagerFactory().engineGetTrustManagersAcceptAnySpiffeId(null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagers_nullParameter() {
|
||||
try {
|
||||
new SpiffeTrustManagerFactory().engineGetTrustManagers(null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagers_nullParameters() {
|
||||
try {
|
||||
new SpiffeTrustManagerFactory().engineGetTrustManagers(null, null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void engineGetTrustManagers_nullSupplier() {
|
||||
X509Source source = new X509SourceStub();
|
||||
try {
|
||||
new SpiffeTrustManagerFactory().engineGetTrustManagers(source, null);
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("acceptedSpiffeIdsSupplier is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private BundleSource<X509Bundle> getX509BundleBundleSource(SpiffeTrustManager trustManager) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field bundleField = SpiffeTrustManager.class.getDeclaredField("x509BundleSource");
|
||||
bundleField.setAccessible(true);
|
||||
return (BundleSource<X509Bundle>) bundleField.get(trustManager);
|
||||
}
|
||||
|
||||
private Supplier<Set<SpiffeId>> getSetSupplier(SpiffeTrustManager trustManager) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field supplierField = SpiffeTrustManager.class.getDeclaredField("acceptedSpiffeIdsSupplier");
|
||||
supplierField.setAccessible(true);
|
||||
return (Supplier<Set<SpiffeId>>) supplierField.get(trustManager);
|
||||
}
|
||||
|
||||
private boolean isAcceptAny(SpiffeTrustManager trustManager) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field acceptAnyField = SpiffeTrustManager.class.getDeclaredField("acceptAnySpiffeId");
|
||||
acceptAnyField.setAccessible(true);
|
||||
return (boolean) acceptAnyField.get(trustManager);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,8 @@ package io.spiffe.provider;
|
|||
import io.spiffe.bundle.BundleSource;
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -14,16 +12,19 @@ import org.junit.jupiter.api.Test;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.spiffe.utils.X509CertificateTestUtils.createCertificate;
|
||||
import static io.spiffe.utils.X509CertificateTestUtils.createRootCA;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
|
@ -33,62 +34,226 @@ public class SpiffeTrustManagerTest {
|
|||
@Mock
|
||||
BundleSource<X509Bundle> bundleSource;
|
||||
|
||||
static X509Bundle x509Bundle;
|
||||
static X509Svid x509Svid;
|
||||
static X509Svid otherX509Svid;
|
||||
static X509Certificate[] chain;
|
||||
static X509Bundle bundleKnown;
|
||||
static X509Bundle bundleUnknown;
|
||||
|
||||
Set<SpiffeId> acceptedSpiffeIds;
|
||||
X509TrustManager trustManager;
|
||||
|
||||
SpiffeTrustManager spiffeTrustManager;
|
||||
|
||||
@BeforeAll
|
||||
static void setupClass() throws IOException, CertificateException, X509SvidException, URISyntaxException {
|
||||
x509Svid = X509Svid
|
||||
.load(
|
||||
Paths.get(toUri("testdata/cert.pem")),
|
||||
Paths.get(toUri("testdata/key.pem")));
|
||||
otherX509Svid = X509Svid
|
||||
.load(
|
||||
Paths.get(toUri("testdata/cert2.pem")),
|
||||
Paths.get(toUri("testdata/key2.pem")));
|
||||
x509Bundle = X509Bundle
|
||||
.load(
|
||||
TrustDomain.of("example.org"),
|
||||
Paths.get(toUri("testdata/bundle.pem")));
|
||||
static void setupClass() throws Exception {
|
||||
val subject = "C = US, O = SPIRE";
|
||||
val issuerSubject = "C = US, O = SPIFFE";
|
||||
|
||||
val trustDomain = TrustDomain.of("spiffe://example.org");
|
||||
val spiffeIdRoot = trustDomain.newSpiffeId();
|
||||
val spiffeIdHost1 = SpiffeId.of(trustDomain, "host1");
|
||||
val spiffeIdHost2 = SpiffeId.of(trustDomain, "host2");
|
||||
val spiffeIdTest = SpiffeId.of(trustDomain, "test");
|
||||
|
||||
val rootCa = createRootCA(issuerSubject, spiffeIdRoot.toString() );
|
||||
val otherRootCa = createRootCA(issuerSubject, spiffeIdRoot.toString());
|
||||
|
||||
val intermediate1 = createCertificate(subject, issuerSubject, spiffeIdHost1.toString(), rootCa, true);
|
||||
val intermediate2 = createCertificate(subject, subject, spiffeIdHost2.toString(), intermediate1, true);
|
||||
val leaf = createCertificate(subject, subject, spiffeIdTest.toString(), intermediate2, false);
|
||||
|
||||
chain = new X509Certificate[]{leaf.getCertificate(), intermediate2.getCertificate(), intermediate1.getCertificate()};
|
||||
|
||||
bundleKnown = X509Bundle.parse(trustDomain, rootCa.getCertificate().getEncoded());
|
||||
bundleUnknown = X509Bundle.parse(trustDomain, otherRootCa.getCertificate().getEncoded());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setupMocks() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
trustManager = (X509TrustManager)
|
||||
new SpiffeTrustManagerFactory().engineGetTrustManagers(bundleSource, () -> acceptedSpiffeIds)[0];
|
||||
spiffeTrustManager = new SpiffeTrustManager(bundleSource, () -> acceptedSpiffeIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkClientTrusted_passAExpiredCertificate_throwsException() throws BundleNotFoundException {
|
||||
void testCreateSpiffeTrustManager_nullSource() {
|
||||
try {
|
||||
new SpiffeTrustManager(null, true);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSpiffeTrustManager_nullSupplier() {
|
||||
try {
|
||||
new SpiffeTrustManager(bundleSource, null);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals("acceptedSpiffeIdsSupplier is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSpiffeTrustManager_nullParameters() {
|
||||
try {
|
||||
new SpiffeTrustManager(null, null);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkClientTrustedMethods_Success() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
|
||||
val chain = x509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
try {
|
||||
trustManager.checkClientTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "", new Socket());
|
||||
spiffeTrustManager.checkClientTrusted(chain, "", getSslEngineStub());
|
||||
} catch (CertificateException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkClientTrustedMethods_ChainCannotVerify() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleUnknown);
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(chain, "", new Socket());
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(chain, "", getSslEngineStub());
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkClientTrustedMethods_ChainIsNull() throws CertificateException {
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(null, "");
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(null, "", new Socket());
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkClientTrusted(null, "", getSslEngineStub());
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkServerTrustedMethods_Success() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(chain, "");
|
||||
spiffeTrustManager.checkServerTrusted(chain, "", new Socket());
|
||||
spiffeTrustManager.checkServerTrusted(chain, "", getSslEngineStub());
|
||||
} catch (CertificateException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkServerTrustedMethods_ChainCannotVerify() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleUnknown);
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(chain, "");
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(chain, "", new Socket());
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(chain, "", getSslEngineStub());
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_checkServerTrustedMethods_ChainIsNull() throws CertificateException {
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(null, "");
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(null, "", new Socket());
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(null, "", getSslEngineStub());
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkClientTrusted_noBundleForTrustDomain_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
|
||||
val chain = x509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenThrow(new BundleNotFoundException("Bundle not found"));
|
||||
|
||||
try {
|
||||
trustManager.checkClientTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Bundle not found", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkServerTrusted_noBundleForTrustDomain_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenThrow(new BundleNotFoundException("Bundle not found"));
|
||||
|
||||
try {
|
||||
spiffeTrustManager.checkServerTrusted(chain, "");
|
||||
fail();
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Bundle not found", e.getMessage());
|
||||
}
|
||||
|
|
@ -97,14 +262,10 @@ public class SpiffeTrustManagerTest {
|
|||
@Test
|
||||
void checkClientTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/other"));
|
||||
|
||||
X509Certificate[] chain = x509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org")))
|
||||
.thenReturn(x509Bundle);
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
try {
|
||||
trustManager.checkClientTrusted(chain, "");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("SPIFFE ID spiffe://example.org/test in X.509 certificate is not accepted", e.getMessage());
|
||||
|
|
@ -112,47 +273,40 @@ public class SpiffeTrustManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void checkClientTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://other.org/test"));
|
||||
void checkClientTrusted_acceptyAnySpiffeId() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/other"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
val chain = otherX509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("other.org"))).thenReturn(x509Bundle);
|
||||
spiffeTrustManager = new SpiffeTrustManager(bundleSource, true);
|
||||
|
||||
try {
|
||||
trustManager.checkClientTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkServerTrusted_passAnExpiredCertificate_ThrowsException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
|
||||
void checkServerTrusted_acceptyAnySpiffeId() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/other"));
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
val chain = x509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||
spiffeTrustManager = new SpiffeTrustManager(bundleSource, true);
|
||||
|
||||
try {
|
||||
trustManager.checkServerTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
spiffeTrustManager.checkClientTrusted(chain, "");
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkServerTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://example.org/other"));
|
||||
|
||||
val chain = x509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(bundleKnown);
|
||||
|
||||
try {
|
||||
trustManager.checkServerTrusted(chain, "");
|
||||
spiffeTrustManager.checkServerTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("SPIFFE ID spiffe://example.org/test in X.509 certificate is not accepted", e.getMessage());
|
||||
|
|
@ -160,22 +314,132 @@ public class SpiffeTrustManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void checkServerTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException {
|
||||
acceptedSpiffeIds = Collections.singleton(SpiffeId.parse("spiffe://other.org/test"));
|
||||
|
||||
val chain = otherX509Svid.getChainArray();
|
||||
|
||||
when(bundleSource.getBundleForTrustDomain(TrustDomain.of("other.org"))).thenReturn(x509Bundle);
|
||||
|
||||
try {
|
||||
trustManager.checkServerTrusted(chain, "");
|
||||
fail("CertificateException was expected");
|
||||
} catch (CertificateException e) {
|
||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||
}
|
||||
void getAcceptedIssuers() {
|
||||
X509Certificate[] acceptedIssuers = spiffeTrustManager.getAcceptedIssuers();
|
||||
assertEquals(0, acceptedIssuers.length);
|
||||
}
|
||||
|
||||
private static URI toUri(String path) throws URISyntaxException {
|
||||
return Thread.currentThread().getContextClassLoader().getResource(path).toURI();
|
||||
private SSLEngine getSslEngineStub() {
|
||||
return new SSLEngine() {
|
||||
@Override
|
||||
public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) throws SSLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) throws SSLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getDelegatedTask() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInbound() throws SSLException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInboundDone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeOutbound() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutboundDone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledCipherSuites(String[] suites) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedProtocols() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledProtocols() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledProtocols(String[] protocols) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginHandshake() throws SSLException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseClientMode(boolean mode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseClientMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeedClientAuth(boolean need) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNeedClientAuth() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWantClientAuth(boolean want) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWantClientAuth() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableSessionCreation(boolean flag) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getEnableSessionCreation() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.utils.TestUtils;
|
||||
import io.spiffe.workloadapi.Address;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class X509SourceManagerTest {
|
||||
|
||||
@Test
|
||||
void getX509Source_returnTheX509SourceInstance() throws Exception {
|
||||
Field field = X509SourceManager.class.getDeclaredField("x509Source");
|
||||
field.setAccessible(true);
|
||||
X509Source source = new X509SourceStub();
|
||||
field.set(null, source);
|
||||
|
||||
X509Source x509Source = X509SourceManager.getX509Source();
|
||||
assertEquals(source, x509Source);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getX509Source_defaultAddressNotSet() throws Exception {
|
||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "" );
|
||||
try {
|
||||
X509SourceManager.getX509Source();
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("Endpoint Socket Address Environment Variable is not set: SPIFFE_ENDPOINT_SOCKET", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package io.spiffe.provider;
|
||||
|
||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||
import io.spiffe.exception.BundleNotFoundException;
|
||||
import io.spiffe.exception.X509BundleException;
|
||||
import io.spiffe.exception.X509SvidException;
|
||||
import io.spiffe.spiffeid.TrustDomain;
|
||||
import io.spiffe.svid.x509svid.X509Svid;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static io.spiffe.utils.TestUtils.toUri;
|
||||
|
||||
public class X509SourceStub implements X509Source {
|
||||
|
||||
private final X509Svid svid;
|
||||
private final X509Bundle bundle;
|
||||
|
||||
public X509SourceStub() {
|
||||
try {
|
||||
Path cert = Paths.get(toUri("testdata/cert.pem"));
|
||||
Path key = Paths.get(toUri("testdata/key.pem"));
|
||||
svid = X509Svid.load(cert, key);
|
||||
bundle = X509Bundle.load(
|
||||
TrustDomain.of("spiffe://example.org"),
|
||||
Paths.get(toUri("testdata/bundle.pem")));
|
||||
} catch (X509SvidException | URISyntaxException | X509BundleException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Bundle getBundleForTrustDomain(@NonNull TrustDomain trustDomain) throws BundleNotFoundException {
|
||||
if (TrustDomain.of("example.org").equals(trustDomain)) {
|
||||
return bundle;
|
||||
}
|
||||
throw new BundleNotFoundException("trustDomain not found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Svid getX509Svid() {
|
||||
return svid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import io.spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
|||
import io.spiffe.provider.SpiffeTrustManager;
|
||||
import io.spiffe.spiffeid.SpiffeId;
|
||||
import io.spiffe.spiffeid.SpiffeIdUtils;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import io.spiffe.workloadapi.DefaultX509Source;
|
||||
import lombok.val;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
|
@ -56,11 +56,11 @@ public class HttpsClient {
|
|||
|
||||
void run() throws IOException, SocketEndpointAddressException, KeyManagementException, NoSuchAlgorithmException, X509SourceException {
|
||||
|
||||
val sourceOptions = X509Source.X509SourceOptions
|
||||
val sourceOptions = DefaultX509Source.X509SourceOptions
|
||||
.builder()
|
||||
.spiffeSocketPath(spiffeSocket)
|
||||
.build();
|
||||
val x509Source = X509Source.newSource(sourceOptions);
|
||||
val x509Source = DefaultX509Source.newSource(sourceOptions);
|
||||
|
||||
SslContextOptions sslContextOptions = SslContextOptions
|
||||
.builder()
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ package io.spiffe.provider.examples.mtls;
|
|||
import io.spiffe.exception.SocketEndpointAddressException;
|
||||
import io.spiffe.exception.X509SourceException;
|
||||
import io.spiffe.provider.SpiffeKeyManager;
|
||||
import io.spiffe.provider.SpiffeProviderException;
|
||||
import io.spiffe.provider.SpiffeSslContextFactory;
|
||||
import io.spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
||||
import io.spiffe.provider.SpiffeTrustManager;
|
||||
import io.spiffe.provider.X509SourceManager;
|
||||
import io.spiffe.provider.exception.SpiffeProviderException;
|
||||
import io.spiffe.workloadapi.X509Source;
|
||||
import lombok.val;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue