Refactor error handling: use Exceptions instead of Result wrapper type
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
8e06cb12d7
commit
44cda6e809
|
|
@ -3,7 +3,7 @@ package spiffe.bundle.jwtbundle;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
@ -29,9 +29,9 @@ public class JwtBundle implements JwtBundleSource {
|
||||||
/**
|
/**
|
||||||
* Creates a new bundle from JWT public keys.
|
* Creates a new bundle from JWT public keys.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain to associate to the JwtBundle
|
* @param trustDomain a {@link TrustDomain} to associate to the JwtBundle
|
||||||
* @param jwtKeys a Map of Public Keys
|
* @param jwtKeys a Map of public Keys
|
||||||
* @return a new JwtBundle.
|
* @return a new {@link JwtBundle}.
|
||||||
*/
|
*/
|
||||||
public static JwtBundle fromJWTKeys(@NonNull TrustDomain trustDomain, Map<String, PublicKey> jwtKeys) {
|
public static JwtBundle fromJWTKeys(@NonNull TrustDomain trustDomain, Map<String, PublicKey> jwtKeys) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
|
|
@ -40,11 +40,11 @@ public class JwtBundle implements JwtBundleSource {
|
||||||
/**
|
/**
|
||||||
* Loads a bundle from a file on disk.
|
* Loads a bundle from a file on disk.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain to associate to the JwtBundle.
|
* @param trustDomain a {@link TrustDomain} to associate to the JWT bundle.
|
||||||
* @param bundlePath a path to a file containing the JwtBundle.
|
* @param bundlePath a path to a file containing the JWT bundle.
|
||||||
* @return a <code>Result.ok(jwtBundle)</code>, or a <code>Result.error(errorMessage)</code>
|
* @return a instance of a {@link JwtBundle}
|
||||||
*/
|
*/
|
||||||
public static Result<JwtBundle, String > load(
|
public static JwtBundle load(
|
||||||
@NonNull final TrustDomain trustDomain,
|
@NonNull final TrustDomain trustDomain,
|
||||||
@NonNull final Path bundlePath) {
|
@NonNull final Path bundlePath) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
|
|
@ -53,29 +53,30 @@ public class JwtBundle implements JwtBundleSource {
|
||||||
/**
|
/**
|
||||||
* Parses a bundle from a byte array.
|
* Parses a bundle from a byte array.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain
|
* @param trustDomain a {@link TrustDomain}
|
||||||
* @param bundleBytes an array of bytes representing the bundle.
|
* @param bundleBytes an array of bytes representing the JWT bundle.
|
||||||
* @return
|
* @return an instance of a {@link JwtBundle}
|
||||||
*/
|
*/
|
||||||
public static Result<JwtBundle, String> parse(
|
public static JwtBundle parse(
|
||||||
@NonNull final TrustDomain trustDomain,
|
@NonNull final TrustDomain trustDomain,
|
||||||
@NonNull final byte[] bundleBytes) {
|
@NonNull final byte[] bundleBytes) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the JwtBundle for a TrustDomain.
|
* Returns the JWT bundle for a trust domain.
|
||||||
*
|
*
|
||||||
* @param trustDomain an instance of a TrustDomain
|
* @param trustDomain a {@link TrustDomain}
|
||||||
* @return a {@link spiffe.result.Ok} containing the JwtBundle for the TrustDomain, or
|
* @return a {@link JwtBundle} for the trust domain
|
||||||
* an {@link spiffe.result.Error} if there is no bundle for the TrustDomain
|
*
|
||||||
|
* @throws BundleNotFoundException if there is no bundle for the given trust domain
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<JwtBundle, String> getJwtBundleForTrustDomain(TrustDomain trustDomain) {
|
public JwtBundle getJwtBundleForTrustDomain(TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
if (this.trustDomain.equals(trustDomain)) {
|
if (this.trustDomain.equals(trustDomain)) {
|
||||||
return Result.ok(this);
|
return this;
|
||||||
}
|
}
|
||||||
return Result.error("No JWT bundle for trust domain %s", trustDomain);
|
throw new BundleNotFoundException(String.format("No JWT bundle found for trust domain %s", trustDomain));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,7 +85,7 @@ public class JwtBundle implements JwtBundleSource {
|
||||||
* it returns an Optional.empty().
|
* it returns an Optional.empty().
|
||||||
*
|
*
|
||||||
* @param keyId the Key ID
|
* @param keyId the Key ID
|
||||||
* @return an {@link Optional} containing a PublicKey.
|
* @return an {@link Optional} containing a {@link PublicKey}.
|
||||||
*/
|
*/
|
||||||
public Optional<PublicKey> findJwtKey(String keyId) {
|
public Optional<PublicKey> findJwtKey(String keyId) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ package spiffe.bundle.jwtbundle;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>JwtBundleSet</code> represents a set of X509Bundles keyed by TrustDomain.
|
* A <code>JwtBundleSet</code> represents a set of JWT bundles keyed by trust domain.
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
public class JwtBundleSet implements JwtBundleSource {
|
public class JwtBundleSet implements JwtBundleSource {
|
||||||
|
|
@ -22,29 +21,33 @@ public class JwtBundleSet implements JwtBundleSource {
|
||||||
this.bundles = bundles;
|
this.bundles = bundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JWT bundle set from the list of JWT bundles.
|
||||||
|
*
|
||||||
|
* @param bundles List of {@link JwtBundle}
|
||||||
|
* @return a {@link JwtBundleSet}
|
||||||
|
*/
|
||||||
public static JwtBundleSet of(@NonNull final List<JwtBundle> bundles) {
|
public static JwtBundleSet of(@NonNull final List<JwtBundle> bundles) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JwtBundleSet of(@NonNull final TrustDomain trustDomain,
|
/**
|
||||||
@NonNull final JwtBundle jwtBundle) {
|
* Gets the JWT bundle associated to a trust domain.
|
||||||
throw new NotImplementedException("Not implemented");
|
*
|
||||||
}
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
|
* @return a {@link JwtBundle} associated to the given trust domain
|
||||||
public List<JwtBundle> getJwtBundles() {
|
* @throws BundleNotFoundException if no bundle could be found for the given trust domain
|
||||||
return new ArrayList<>(bundles.values());
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<JwtBundle, String> getJwtBundleForTrustDomain(final TrustDomain trustDomain) {
|
public JwtBundle getJwtBundleForTrustDomain(final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
if (bundles.containsKey(trustDomain)) {
|
if (bundles.containsKey(trustDomain)) {
|
||||||
return Result.ok(bundles.get(trustDomain));
|
return bundles.get(trustDomain);
|
||||||
}
|
}
|
||||||
return Result.error("No JWT bundle for trust domain %s", trustDomain);
|
throw new BundleNotFoundException(String.format("No JWT bundle for trust domain %s", trustDomain));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add bundle to set, if the trustDomain already exists
|
* Add JWT bundle to this set, if the trust domain already exists
|
||||||
* replace the bundle.
|
* replace the bundle.
|
||||||
*
|
*
|
||||||
* @param jwtBundle an instance of a JwtBundle.
|
* @param jwtBundle an instance of a JwtBundle.
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
package spiffe.bundle.jwtbundle;
|
package spiffe.bundle.jwtbundle;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>JwtBundleSource</code> represents a source of JWT-Bundles.
|
* A <code>JwtBundleSource</code> represents a source of JWT bundles.
|
||||||
*/
|
*/
|
||||||
public interface JwtBundleSource {
|
public interface JwtBundleSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the JWT bundle for a trustDomain.
|
* Returns the JWT bundle for a trust domain.
|
||||||
*
|
*
|
||||||
* @param trustDomain an instance of a TrustDomain
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
* @return a {@link spiffe.result.Ok} containing a {@link JwtBundle}, or a {@link spiffe.result.Error} if
|
* @return the {@link JwtBundle} for the given trust domain
|
||||||
* no bundle is found for the given trust domain.
|
*
|
||||||
|
* @throws BundleNotFoundException if no bundle is found for the given trust domain.
|
||||||
*/
|
*/
|
||||||
Result<JwtBundle, String> getJwtBundleForTrustDomain(@NonNull final TrustDomain trustDomain);
|
JwtBundle getJwtBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ package spiffe.bundle.x509bundle;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -29,54 +30,53 @@ public class X509Bundle implements X509BundleSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load loads a Bundle from a file on disk.
|
* Loads a X509 bundle from a file on disk.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain to associate to the bundle
|
* @param trustDomain a {@link TrustDomain} to associate to the bundle
|
||||||
* @param bundlePath a Path to the file that has the X509 Authorities
|
* @param bundlePath a path to the file that has the X509 authorities
|
||||||
* @return an instance of X509Bundle with the X509 Authorities
|
* @return an instance of {@link X509Bundle} with the X509 authorities
|
||||||
* associated to the TrustDomain.
|
* associated to the trust domain.
|
||||||
|
*
|
||||||
|
* @throws IOException in case of failure accessing the given bundle path
|
||||||
|
* @throws CertificateException if the bundle cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<X509Bundle, String> load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) {
|
public static X509Bundle load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) throws IOException, CertificateException {
|
||||||
try {
|
|
||||||
val bundleBytes = Files.readAllBytes(bundlePath);
|
val bundleBytes = Files.readAllBytes(bundlePath);
|
||||||
|
|
||||||
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
||||||
if (x509Certificates.isError()) {
|
val x509CertificateSet = new HashSet<>(x509Certificates);
|
||||||
return Result.error(x509Certificates.getError());
|
return new X509Bundle(trustDomain, x509CertificateSet);
|
||||||
}
|
|
||||||
|
|
||||||
val x509CertificateSet = new HashSet<>(x509Certificates.getValue());
|
|
||||||
val x509Bundle = new X509Bundle(trustDomain, x509CertificateSet);
|
|
||||||
return Result.ok(x509Bundle);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return Result.error("Error loading X509Bundle from path %s: %s", bundlePath, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a bundle from a byte array.
|
* Parses a X095 bundle from an array of bytes.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain to associate to the bundle
|
* @param trustDomain a {@link TrustDomain} to associate to the X509 bundle
|
||||||
* @param bundleBytes an array of bytes that represents the X509 Authorities
|
* @param bundleBytes an array of bytes that represents the X509 authorities
|
||||||
* @return an instance of X509Bundle with the X509 Authorities
|
*
|
||||||
* associated to the TrustDomain.
|
* @return an instance of {@link X509Bundle} with the X509 authorities
|
||||||
|
* associated to the given trust domain
|
||||||
|
*
|
||||||
|
* @throws CertificateException if the bundle cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<X509Bundle, String> parse(@NonNull final TrustDomain trustDomain, @NonNull final byte[] bundleBytes) {
|
public static X509Bundle parse(@NonNull final TrustDomain trustDomain, @NonNull final byte[] bundleBytes) throws CertificateException {
|
||||||
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
val x509Certificates = CertificateUtils.generateCertificates(bundleBytes);
|
||||||
if (x509Certificates.isError()) {
|
val x509CertificateSet = new HashSet<>(x509Certificates);
|
||||||
return Result.error(x509Certificates.getError());
|
return new X509Bundle(trustDomain, x509CertificateSet);
|
||||||
}
|
|
||||||
|
|
||||||
val x509CertificateSet = new HashSet<>(x509Certificates.getValue());
|
|
||||||
val x509Bundle = new X509Bundle(trustDomain, x509CertificateSet);
|
|
||||||
return Result.ok(x509Bundle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the X509 bundle associated to the trust domain.
|
||||||
|
*
|
||||||
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
|
* @return the {@link X509Bundle} associated to the given trust domain
|
||||||
|
*
|
||||||
|
* @throws BundleNotFoundException if no X509 bundle can be found for the given trust domain
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<X509Bundle, String> getX509BundleForTrustDomain(TrustDomain trustDomain) {
|
public X509Bundle getX509BundleForTrustDomain(TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
if (this.trustDomain.equals(trustDomain)) {
|
if (this.trustDomain.equals(trustDomain)) {
|
||||||
return Result.ok(this);
|
return this;
|
||||||
}
|
}
|
||||||
return Result.error("No X509 bundle for trust domain %s", trustDomain);
|
throw new BundleNotFoundException(String.format("No X509 bundle found for trust domain %s", trustDomain));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ package spiffe.bundle.x509bundle;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509BundleSet</code> represents a set of X509 Bundles keyed by TrustDomain.
|
* A <code>X509BundleSet</code> represents a set of X509 bundles keyed by trust domain.
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
public class X509BundleSet implements X509BundleSource {
|
public class X509BundleSet implements X509BundleSource {
|
||||||
|
|
@ -22,10 +21,10 @@ public class X509BundleSet implements X509BundleSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new <code>X509BundleSet</code> initialized with the bundles
|
* Creates a new X509 bundle set from a list of X509 bundles.
|
||||||
*
|
*
|
||||||
* @param bundles list of bundles to put in the X509BundleSet
|
* @param bundles a list of {@link X509Bundle}
|
||||||
* @return a X509BundleSet initialized with the list of bundles
|
* @return a {@link X509BundleSet} initialized with the list of bundles
|
||||||
*/
|
*/
|
||||||
public static X509BundleSet of(@NonNull final List<X509Bundle> bundles) {
|
public static X509BundleSet of(@NonNull final List<X509Bundle> bundles) {
|
||||||
ConcurrentHashMap<TrustDomain, X509Bundle> bundleMap = new ConcurrentHashMap<>();
|
ConcurrentHashMap<TrustDomain, X509Bundle> bundleMap = new ConcurrentHashMap<>();
|
||||||
|
|
@ -36,46 +35,27 @@ public class X509BundleSet implements X509BundleSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new <code>X509BundleSet</code> initialized with the x509Bundle.
|
* Adds a bundle to this Set, if the trust domain already exists,
|
||||||
*/
|
* replaces the bundle.
|
||||||
public static X509BundleSet of(@NonNull final TrustDomain trustDomain, @NonNull final X509Bundle x509Bundle) {
|
|
||||||
ConcurrentHashMap<TrustDomain, X509Bundle> bundleMap = new ConcurrentHashMap<>();
|
|
||||||
bundleMap.put(trustDomain, x509Bundle);
|
|
||||||
return new X509BundleSet(bundleMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a bundle to set, if the trustDomain already exists
|
|
||||||
* replace the bundle.
|
|
||||||
*
|
*
|
||||||
* @param x509Bundle a X509Bundle.
|
* @param x509Bundle a {@link X509Bundle}
|
||||||
*/
|
*/
|
||||||
public void add(@NonNull X509Bundle x509Bundle){
|
public void add(@NonNull X509Bundle x509Bundle){
|
||||||
bundles.put(x509Bundle.getTrustDomain(), x509Bundle);
|
bundles.put(x509Bundle.getTrustDomain(), x509Bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the bundles contained in the X509BundleSet.
|
* Returns the X509 bundle associated to the trust domain.
|
||||||
*
|
*
|
||||||
* @return a list with all the bundles for all the trustDomains
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
*/
|
* @return the {@link X509Bundle} associated to the given trust domain
|
||||||
public List<X509Bundle> getX509Bundles() {
|
* @throws BundleNotFoundException if no bundle could be found for the given trust domain
|
||||||
return new ArrayList<>(bundles.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link spiffe.result.Ok} containing the X509Bundle for a trust domain,
|
|
||||||
* if the current set doesn't have bundle for the trust domain,
|
|
||||||
* it returns an {@link spiffe.result.Error}.
|
|
||||||
*
|
|
||||||
* @param trustDomain an instance of a TrustDomain
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<X509Bundle, String> getX509BundleForTrustDomain(final TrustDomain trustDomain) {
|
public X509Bundle getX509BundleForTrustDomain(final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
if (bundles.containsKey(trustDomain)) {
|
if (bundles.containsKey(trustDomain)) {
|
||||||
return Result.ok(bundles.get(trustDomain));
|
return bundles.get(trustDomain);
|
||||||
}
|
}
|
||||||
return Result.error("No X509 bundle for trust domain %s", trustDomain);
|
throw new BundleNotFoundException(String.format("No X509 bundle for trust domain %s", trustDomain));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@ package spiffe.bundle.x509bundle;
|
||||||
|
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509BundleSource</code> represents a source of X509-Bundles keyed by TrustDomain.
|
* A <code>X509BundleSource</code> represents a source of X509 bundles keyed by trust domain.
|
||||||
*/
|
*/
|
||||||
public interface X509BundleSource {
|
public interface X509BundleSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the bundle associated to a trustDomain.
|
* Returns the X509 bundle associated to the given trust domain.
|
||||||
*
|
*
|
||||||
* @param trustDomain an instance of a TrustDomain
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
* @return a {@link spiffe.result.Ok} containing a {@link X509Bundle}, or a {@link spiffe.result.Error} if
|
* @return the {@link X509Bundle} for the given trust domain
|
||||||
* no bundle is found for the given trust domain.
|
* @throws BundleNotFoundException if no bundle is found for the given trust domain
|
||||||
*/
|
*/
|
||||||
Result<X509Bundle, String> getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain);
|
X509Bundle getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package spiffe.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checked exception thrown to indicate that a bundle could not be
|
||||||
|
* found in the bundle source.
|
||||||
|
*/
|
||||||
|
public class BundleNotFoundException extends Exception {
|
||||||
|
public BundleNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BundleNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package spiffe.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checked exception thrown to indicate that the socket endpoint address
|
||||||
|
* could not be parsed or is not valid.
|
||||||
|
*/
|
||||||
|
public class SocketEndpointAddressException extends Exception {
|
||||||
|
public SocketEndpointAddressException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketEndpointAddressException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketEndpointAddressException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package spiffe.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unchecked exception thrown when a there was an error retrieving
|
||||||
|
* or processing a X509Context.
|
||||||
|
*/
|
||||||
|
public class X509ContextException extends RuntimeException {
|
||||||
|
public X509ContextException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509ContextException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509ContextException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package spiffe.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unchecked thrown when there is an error creating or initializing a X509 source
|
||||||
|
*/
|
||||||
|
public class X509SourceException extends RuntimeException {
|
||||||
|
public X509SourceException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509SourceException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509SourceException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package spiffe.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checked exception thrown when there is an error parsing
|
||||||
|
* the components of an X509 SVID.
|
||||||
|
*/
|
||||||
|
public class X509SvidException extends Exception {
|
||||||
|
|
||||||
|
public X509SvidException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509SvidException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509SvidException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
package spiffe.internal;
|
package spiffe.internal;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import lombok.var;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
|
|
@ -14,7 +13,6 @@ import java.security.PrivateKey;
|
||||||
import java.security.cert.*;
|
import java.security.cert.*;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -33,139 +31,108 @@ public class CertificateUtils {
|
||||||
private static final String X509_CERTIFICATE_TYPE = "X.509";
|
private static final String X509_CERTIFICATE_TYPE = "X.509";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a List of X509Certificates from a byte array.
|
* Generate a list of X509 certificates from a byte array.
|
||||||
*
|
*
|
||||||
* @param input as byte array representing a list of X509Certificates, as a DER or PEM
|
* @param input as byte array representing a list of X509 certificates, as a DER or PEM
|
||||||
* @return a List of X509Certificate
|
* @return a List of {@link X509Certificate}
|
||||||
*/
|
*/
|
||||||
public static Result<List<X509Certificate>, String> generateCertificates(byte[] input) {
|
public static List<X509Certificate> generateCertificates(byte[] input) throws CertificateException {
|
||||||
val certificateFactory = getCertificateFactory();
|
val certificateFactory = getCertificateFactory();
|
||||||
if (certificateFactory.isError()) {
|
|
||||||
return Result.error("Error parsing certificates: could not create certificate factory %s", certificateFactory.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val certificates = certificateFactory
|
val certificates = certificateFactory
|
||||||
.getValue()
|
|
||||||
.generateCertificates(new ByteArrayInputStream(input));
|
.generateCertificates(new ByteArrayInputStream(input));
|
||||||
|
|
||||||
val x509CertificateList = certificates.stream()
|
return certificates.stream()
|
||||||
.map(X509Certificate.class::cast)
|
.map(X509Certificate.class::cast)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return Result.ok(x509CertificateList);
|
|
||||||
} catch (CertificateException e) {
|
|
||||||
return Result.error("Error parsing certificates: %s", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a PrivateKey from an array of bytes.
|
* Generates a private key from an array of bytes.
|
||||||
*
|
*
|
||||||
* @param privateKeyBytes is a PEM or DER PKCS#8 Private Key.
|
* @param privateKeyBytes is a PEM or DER PKCS#8 private key.
|
||||||
* @return a Result {@link spiffe.result.Ok} containing a {@link PrivateKey} or an {@link spiffe.result.Error}.
|
* @return a instance of {@link PrivateKey}
|
||||||
|
* @throws InvalidKeySpecException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
*/
|
*/
|
||||||
public static Result<PrivateKey, String> generatePrivateKey(byte[] privateKeyBytes) {
|
public static PrivateKey generatePrivateKey(byte[] privateKeyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||||
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||||
Result<PrivateKey, Throwable> privateKeyResult = generatePrivateKeyWithSpec(kspec);
|
PrivateKey privateKey = null;
|
||||||
|
|
||||||
if (privateKeyResult.isOk()) {
|
|
||||||
return Result.ok(privateKeyResult.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateKey is in PEM format, not supported, need to convert to DER and try again
|
|
||||||
if (privateKeyResult.getError() instanceof InvalidKeySpecException) {
|
|
||||||
byte[] keyDer = toDerFormat(privateKeyBytes);
|
|
||||||
kspec= new PKCS8EncodedKeySpec(keyDer);
|
|
||||||
privateKeyResult = generatePrivateKeyWithSpec(kspec);
|
|
||||||
}
|
|
||||||
return Result.ok(privateKeyResult.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Result<PrivateKey, Throwable> generatePrivateKeyWithSpec(PKCS8EncodedKeySpec kspec) {
|
|
||||||
try {
|
try {
|
||||||
val keyFactory = KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM);
|
privateKey = generatePrivateKeyWithSpec(kspec);
|
||||||
val privateKey = keyFactory.generatePrivate(kspec);
|
} catch (InvalidKeySpecException e) {
|
||||||
return Result.ok(privateKey);
|
byte[] keyDer = toDerFormat(privateKeyBytes);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
kspec = new PKCS8EncodedKeySpec(keyDer);
|
||||||
return Result.error(e);
|
privateKey = generatePrivateKeyWithSpec(kspec);
|
||||||
}
|
}
|
||||||
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a certificate chain against a set of trusted certificates.
|
* Validate a certificate chain with a set of trusted certificates.
|
||||||
*
|
*
|
||||||
* @param chain the certificate chain
|
* @param chain the certificate chain
|
||||||
* @param trustedCerts to validate the certificate chain
|
* @param trustedCerts to validate the certificate chain
|
||||||
* @return a Result {@link spiffe.result.Ok} if the chain can be chained to any of the trustedCerts, or
|
* @throws CertificateException
|
||||||
* an {@link spiffe.result.Error}.
|
* @throws InvalidAlgorithmParameterException
|
||||||
*
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws CertPathValidatorException
|
||||||
*/
|
*/
|
||||||
public static Result<Boolean, String> validate(List<X509Certificate> chain, List<X509Certificate> trustedCerts) {
|
public static void validate(List<X509Certificate> chain, List<X509Certificate> trustedCerts) throws CertificateException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, CertPathValidatorException {
|
||||||
val certificateFactory = getCertificateFactory();
|
val certificateFactory = getCertificateFactory();
|
||||||
if (certificateFactory.isError()) {
|
val pkixParameters = toPkixParameters(trustedCerts);
|
||||||
return Result.error(certificateFactory.getError());
|
val certPath = certificateFactory.generateCertPath(chain);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
PKIXParameters pkixParameters = toPkixParameters(trustedCerts);
|
|
||||||
val certPath = certificateFactory.getValue().generateCertPath(chain);
|
|
||||||
getCertPathValidator().validate(certPath, pkixParameters);
|
getCertPathValidator().validate(certPath, pkixParameters);
|
||||||
} catch (CertificateException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertPathValidatorException e) {
|
|
||||||
return Result.error("Error validating certificate chain: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the SpiffeID from a SVID - X509Certificate.
|
* Extracts the SPIFE ID from a X509 certificate.
|
||||||
* <p>
|
* <p>
|
||||||
* It iterates over the list of SubjectAlternativesNames, read each entry, takes the value from the index
|
* It iterates over the list of SubjectAlternativesNames, read each entry, takes the value from the index
|
||||||
* defined in SAN_VALUE_INDEX and filters the entries that starts with the SPIFFE_PREFIX and returns the first.
|
* defined in SAN_VALUE_INDEX and filters the entries that starts with the SPIFFE_PREFIX and returns the first.
|
||||||
*
|
*
|
||||||
* @param certificate a X509Certificate
|
* @param certificate a {@link X509Certificate}
|
||||||
* @return spiffe.result.Result with the SpiffeId
|
* @return an instance of a {@link SpiffeId}
|
||||||
* @throws RuntimeException when the certificate subjectAlternatives names cannot be read
|
* @throws CertificateException if the certificate contains multiple SPIFFE IDs, or does not contain any, or
|
||||||
* @throws IllegalArgumentException when the certificate contains multiple SpiffeId.
|
* the SAN extension cannot be decoded
|
||||||
*/
|
*/
|
||||||
public static Result<SpiffeId, String> getSpiffeId(X509Certificate certificate) {
|
public static SpiffeId getSpiffeId(X509Certificate certificate) throws CertificateException {
|
||||||
val spiffeIds = getSpiffeIds(certificate);
|
val spiffeIds = getSpiffeIds(certificate);
|
||||||
|
|
||||||
if (spiffeIds.size() > 1) {
|
if (spiffeIds.size() > 1) {
|
||||||
return Result.error("Certificate contains multiple SPIFFE IDs.");
|
throw new CertificateException("Certificate contains multiple SPIFFE IDs");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spiffeIds.size() < 1) {
|
if (spiffeIds.size() < 1) {
|
||||||
return Result.error("No SPIFFE ID found in the certificate.");
|
throw new CertificateException("No SPIFFE ID found in the certificate");
|
||||||
}
|
}
|
||||||
|
|
||||||
val spiffeId = SpiffeId.parse(spiffeIds.get(0));
|
return SpiffeId.parse(spiffeIds.get(0));
|
||||||
if (spiffeId.isError()) {
|
|
||||||
return Result.error(spiffeId.getError());
|
|
||||||
}
|
|
||||||
return spiffeId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extracts the trustDomain of a chain of certificates
|
/**
|
||||||
public static Result<TrustDomain, String> getTrustDomain(List<X509Certificate> chain) {
|
* Extracts the trust domain of a chain of certificates.
|
||||||
|
*
|
||||||
|
* @param chain a list of {@link X509Certificate}
|
||||||
|
* @return a {@link TrustDomain}
|
||||||
|
*
|
||||||
|
* @throws CertificateException
|
||||||
|
*/
|
||||||
|
public static TrustDomain getTrustDomain(List<X509Certificate> chain) throws CertificateException {
|
||||||
val spiffeId = getSpiffeId(chain.get(0));
|
val spiffeId = getSpiffeId(chain.get(0));
|
||||||
if (spiffeId.isError()) {
|
return spiffeId.getTrustDomain();
|
||||||
return Result.error(spiffeId.getError());
|
|
||||||
}
|
|
||||||
return Result.ok(spiffeId.getValue().getTrustDomain());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> getSpiffeIds(X509Certificate certificate) {
|
private static List<String> getSpiffeIds(X509Certificate certificate) throws CertificateParsingException {
|
||||||
try {
|
|
||||||
return certificate.getSubjectAlternativeNames()
|
return certificate.getSubjectAlternativeNames()
|
||||||
.stream()
|
.stream()
|
||||||
.map(san -> (String) san.get(SAN_VALUE_INDEX))
|
.map(san -> (String) san.get(SAN_VALUE_INDEX))
|
||||||
.filter(uri -> startsWith(uri, SPIFFE_PREFIX))
|
.filter(uri -> startsWith(uri, SPIFFE_PREFIX))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} catch (CertificateParsingException e) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PrivateKey generatePrivateKeyWithSpec(PKCS8EncodedKeySpec kspec) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
return KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM).generatePrivate(kspec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of PKIXParameters used as input for the PKIX CertPathValidator
|
// Create an instance of PKIXParameters used as input for the PKIX CertPathValidator
|
||||||
|
|
@ -187,17 +154,13 @@ public class CertificateUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the X509 Certificate Factory
|
// Get the X509 Certificate Factory
|
||||||
private static Result<CertificateFactory, String> getCertificateFactory() {
|
private static CertificateFactory getCertificateFactory() throws CertificateException {
|
||||||
try {
|
return CertificateFactory.getInstance(X509_CERTIFICATE_TYPE);
|
||||||
return Result.ok(CertificateFactory.getInstance(X509_CERTIFICATE_TYPE));
|
|
||||||
} catch (CertificateException e) {
|
|
||||||
return Result.error("Error creating certificate factory: %s", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a private key in PEM format, encode it as DER
|
// Given a private key in PEM format, encode it as DER
|
||||||
private static byte[] toDerFormat(byte[] privateKeyPem) {
|
private static byte[] toDerFormat(byte[] privateKeyPem) {
|
||||||
String privateKey = new String(privateKeyPem);
|
var privateKey = new String(privateKeyPem);
|
||||||
privateKey = privateKey.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?)", "");
|
privateKey = privateKey.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?)", "");
|
||||||
privateKey = privateKey.replaceAll("\n", "");
|
privateKey = privateKey.replaceAll("\n", "");
|
||||||
val decoder = Base64.getDecoder();
|
val decoder = Base64.getDecoder();
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package spiffe.result;
|
|
||||||
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link spiffe.result.Error} represents a Result that conveys an error of type E.
|
|
||||||
*
|
|
||||||
* @param <V> the type of the value conveyed by the Result
|
|
||||||
* @param <E> the type of the error wrapped in the Error
|
|
||||||
*/
|
|
||||||
@Value
|
|
||||||
public class Error<V, E> implements Result<V, E> {
|
|
||||||
|
|
||||||
E error;
|
|
||||||
|
|
||||||
Error(final E error) {
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws NoSuchElementException, Error does not contain any value.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public V getValue() {
|
|
||||||
throw new NoSuchElementException("No value present in Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOk() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isError() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package spiffe.result;
|
|
||||||
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link spiffe.result.Ok} represents a Result that conveys a value of type T.
|
|
||||||
*
|
|
||||||
* @param <V> the type the value wrapped in the Ok result
|
|
||||||
* @param <E> the type of the error
|
|
||||||
*/
|
|
||||||
@Value
|
|
||||||
public class Ok<V, E> implements Result<V, E> {
|
|
||||||
|
|
||||||
V value;
|
|
||||||
|
|
||||||
public Ok(final V value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws NoSuchElementException, Ok does not contain any Error.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public E getError() {
|
|
||||||
throw new NoSuchElementException("No error present in an Ok result");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOk() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isError() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package spiffe.result;
|
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A <code>Result</code> represents the result of an operation, that can be {@link spiffe.result.Ok} or {@link spiffe.result.Error}.
|
|
||||||
*
|
|
||||||
* @param <V> type of the value conveyed by the Result
|
|
||||||
* @param <E> type of the error conveyed by the Result.
|
|
||||||
*
|
|
||||||
* @see Ok
|
|
||||||
* @see Error
|
|
||||||
*/
|
|
||||||
public interface Result<V, E> {
|
|
||||||
|
|
||||||
V getValue();
|
|
||||||
|
|
||||||
E getError();
|
|
||||||
|
|
||||||
boolean isOk();
|
|
||||||
|
|
||||||
boolean isError();
|
|
||||||
|
|
||||||
static <V, E> Ok<V, E> ok(final V value) {
|
|
||||||
return new Ok<>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static <V, E> Error<V, E> error(final E error) {
|
|
||||||
return new Error<>(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
static <V> Result<V, String> error(String format, Object ...args) {
|
|
||||||
return Result.error(String.format(format, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the Function if the actual Result is an Ok.
|
|
||||||
*
|
|
||||||
* @param fn Function to apply, receives a superclass of U and returns a Result of T
|
|
||||||
* @param u Parameter of the Function
|
|
||||||
* @param <U> Type of the parameter of the Function
|
|
||||||
* @return A Result of type V.
|
|
||||||
*/
|
|
||||||
default <U> Result<V, E> thenApply(Function<? super U, Result<V, E>> fn, U u) {
|
|
||||||
if (this.isOk()) {
|
|
||||||
return fn.apply(u);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the BiFunction if the actual Result is an Ok.
|
|
||||||
*
|
|
||||||
* @param fn Function to apply, receives a superclass of U and returns a Result of T
|
|
||||||
* @param u First parameter of the BiFunction
|
|
||||||
* @param s Second parameter of the BiFunction
|
|
||||||
* @param <U> Type of the parameter of the BiFunction
|
|
||||||
* @return A Result of type V.
|
|
||||||
*/
|
|
||||||
default <U, S> Result<V, E> thenApply(BiFunction<? super U, ? super S, Result<V, E>> fn, U u, S s) {
|
|
||||||
if (this.isOk()) {
|
|
||||||
return fn.apply(u, s);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package spiffe.spiffeid;
|
package spiffe.spiffeid;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -29,70 +29,51 @@ public class SpiffeId {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an instance of a SpiffeId, containing the TrustDomain and
|
* Returns an instance representing a SPIFFE ID, containing the trust domain and
|
||||||
* a path generated joining the segments (e.g. /path1/path2).
|
* a path generated joining the segments (e.g. /path1/path2).
|
||||||
*
|
*
|
||||||
* @param trustDomain an instance of a TrustDomain
|
* @param trustDomain an instance of a {@link TrustDomain}
|
||||||
* @param segments a list of string path segments
|
* @param segments a list of string path segments
|
||||||
*
|
*
|
||||||
* @return a {@code Resul}, either an {@link spiffe.result.Ok} wrapping a {@link SpiffeId}
|
* @return a {@link SpiffeId}
|
||||||
* or an {@link spiffe.result.Error} wrapping the error message.
|
|
||||||
*/
|
*/
|
||||||
public static Result<SpiffeId, String> of(final TrustDomain trustDomain, final String... segments) {
|
public static SpiffeId of(@NonNull final TrustDomain trustDomain, final String... segments) {
|
||||||
if (trustDomain == null) {
|
|
||||||
return Result.error("Trust Domain cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
val path = Arrays.stream(segments)
|
val path = Arrays.stream(segments)
|
||||||
.filter(StringUtils::isNotBlank)
|
.filter(StringUtils::isNotBlank)
|
||||||
.map(SpiffeId::normalize)
|
.map(SpiffeId::normalize)
|
||||||
.map(s -> "/" + s)
|
.map(s -> "/" + s)
|
||||||
.collect(Collectors.joining());
|
.collect(Collectors.joining());
|
||||||
|
return new SpiffeId(trustDomain, path);
|
||||||
return Result.ok(new SpiffeId(trustDomain, path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a SpiffeId from a string (e.g. spiffe://example.org/test).
|
* Parses a SPIFFE ID from a string (e.g. spiffe://example.org/test).
|
||||||
*
|
*
|
||||||
* @param spiffeIdAsString a String representing a spiffeId
|
* @param spiffeIdAsString a String representing a SPIFFE ID
|
||||||
* @return A {@link Result}, either an {@link spiffe.result.Ok} wrapping a {@link SpiffeId}
|
* @return A {@link SpiffeId}
|
||||||
* or an {@link spiffe.result.Error} wrapping the error message.
|
* @throws IllegalArgumentException if the given string cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<SpiffeId, String> parse(final String spiffeIdAsString) {
|
public static SpiffeId parse(@NonNull final String spiffeIdAsString) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(spiffeIdAsString)) {
|
if (StringUtils.isBlank(spiffeIdAsString)) {
|
||||||
return Result.error("SPIFFE ID cannot be empty");
|
throw new IllegalArgumentException("SPIFFE ID cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
val uri = URI.create(spiffeIdAsString);
|
val uri = URI.create(spiffeIdAsString);
|
||||||
|
|
||||||
if (!SPIFFE_SCHEMA.equals(uri.getScheme())) {
|
if (!SPIFFE_SCHEMA.equals(uri.getScheme())) {
|
||||||
return Result.error("Invalid SPIFFE schema");
|
throw new IllegalArgumentException("Invalid SPIFFE schema");
|
||||||
}
|
|
||||||
|
|
||||||
val trustDomainResult = TrustDomain.of(uri.getHost());
|
|
||||||
if (trustDomainResult.isError()) {
|
|
||||||
return Result.error(trustDomainResult.getError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val trustDomain = TrustDomain.of(uri.getHost());
|
||||||
val path = uri.getPath();
|
val path = uri.getPath();
|
||||||
|
return new SpiffeId(trustDomain, path);
|
||||||
return Result.ok(new SpiffeId(trustDomainResult.getValue(), path));
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return Result.error("Could not parse SPIFFE ID %s: %s", spiffeIdAsString, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the trustDomain of the current object equals the
|
* Returns true if the trust domain of this SPIFFE ID is the same as the given trust domain.
|
||||||
* trustDomain passed as parameter.
|
|
||||||
*
|
*
|
||||||
* @param trustDomain instance of a TrustDomain
|
* @param trustDomain instance of a {@link TrustDomain}
|
||||||
* @return true if the trustDomain given as a parameter is the same as the trustDomain
|
* @return <code>true</code> if the given trust domain equals the trust domain of this object, <code>false</code> otherwise
|
||||||
* of the current SpiffeId object.
|
|
||||||
*/
|
*/
|
||||||
public boolean memberOf(final TrustDomain trustDomain) {
|
public boolean memberOf(final TrustDomain trustDomain) {
|
||||||
return this.trustDomain.equals(trustDomain);
|
return this.trustDomain.equals(trustDomain);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package spiffe.spiffeid;
|
package spiffe.spiffeid;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
|
||||||
import spiffe.result.Result;
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
|
@ -20,18 +21,17 @@ public class SpiffeIdUtils {
|
||||||
private static final char DEFAULT_CHAR_SEPARATOR = ',';
|
private static final char DEFAULT_CHAR_SEPARATOR = ',';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the Accepted SPIFFE IDs from a System Property and parse them to SpiffeId instances.
|
* Reads the Accepted SPIFFE IDs from a system property and parses them to {@link SpiffeId} instances.
|
||||||
*
|
*
|
||||||
* @param systemProperty name of the System Property that should contain a list of
|
* @param systemProperty name of the system property that contains a list of SPIFFE IDs separated by a commas.
|
||||||
* SPIFFE IDs separated by a comma.
|
* @return a list of {@link SpiffeId} parsed from the values read from the security property
|
||||||
*
|
*
|
||||||
* @return a {@link Result}
|
* @throws IllegalArgumentException if the given system property is empty or if any of the SPIFFE IDs
|
||||||
* {@link spiffe.result.Ok} containing a List of SpiffeId instances. If no value is found, returns an empty list.
|
* cannot be parsed
|
||||||
* {@link spiffe.result.Error} in case the param systemProperty is blank.
|
|
||||||
*/
|
*/
|
||||||
public static Result<List<SpiffeId>, String> getSpiffeIdsFromSystemProperty(final String systemProperty) {
|
public static List<SpiffeId> getSpiffeIdsFromSystemProperty(final String systemProperty) {
|
||||||
if (isBlank(systemProperty)) {
|
if (isBlank(systemProperty)) {
|
||||||
return Result.error("System property cannot be empty.");
|
throw new IllegalArgumentException("Argument systemProperty cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
val spiffeIds = System.getProperty(systemProperty);
|
val spiffeIds = System.getProperty(systemProperty);
|
||||||
|
|
@ -39,54 +39,60 @@ public class SpiffeIdUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the Accepted SPIFFE IDs from a Security Property (defined in java.security file) and parse
|
* Reads the accepted SPIFFE IDs from a security Property (defined in java.security file) and parses
|
||||||
* them to SpiffeId instances.
|
* them to {@link SpiffeId} instances.
|
||||||
* <p>
|
|
||||||
* @param securityProperty name of the Security Property that should contain a list of
|
|
||||||
* SPIFFE IDs separated by a comma.
|
|
||||||
*
|
*
|
||||||
* @return a Result:
|
* @param securityProperty name of the security property that contains a list of SPIFFE IDs separated by commas.
|
||||||
* {@link spiffe.result.Ok} containing a List of SpiffeId instances. If no value is found, returns an empty list.
|
* @return a List of {@link SpiffeId} parsed from the values read from the given security property
|
||||||
* {@link spiffe.result.Error} in case the param systemProperty is blank.
|
*
|
||||||
|
* @throws IllegalArgumentException if the security property is empty or if any of the SPIFFE IDs
|
||||||
|
* cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<List<SpiffeId>, String> getSpiffeIdsFromSecurityProperty(final String securityProperty) {
|
public static List<SpiffeId> getSpiffeIdsFromSecurityProperty(final String securityProperty) {
|
||||||
if (isBlank(securityProperty)) {
|
if (isBlank(securityProperty)) {
|
||||||
return Result.error("Security property cannot be empty");
|
throw new IllegalArgumentException("Argument securityProperty cannot be empty");
|
||||||
}
|
}
|
||||||
val spiffeIds = Security.getProperty(securityProperty);
|
val spiffeIds = Security.getProperty(securityProperty);
|
||||||
return toListOfSpiffeIds(spiffeIds, DEFAULT_CHAR_SEPARATOR);
|
return toListOfSpiffeIds(spiffeIds, DEFAULT_CHAR_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a file containing a list of SPIFFE IDs and parse them to SpiffeId instances.
|
* Reads a file containing a list of SPIFFE IDs and parses them to {@link SpiffeId} instances.
|
||||||
|
* <p>
|
||||||
|
* The file should have one SPIFFE ID per line.
|
||||||
*
|
*
|
||||||
* @param spiffeIdFile
|
* @param spiffeIdsFile the path to the file containing a list of SPIFFE IDs
|
||||||
* @param separator
|
* @return a List of {@link SpiffeId} parsed from the file provided
|
||||||
* @return
|
*
|
||||||
|
* @throws IOException if the given spiffeIdsFile cannot be read
|
||||||
|
* @throws IllegalArgumentException if any of the SPIFFE IDs in the file cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<List<SpiffeId>, String> getSpiffeIdListFromFile(final Path spiffeIdFile, final char separator) {
|
public static List<SpiffeId> getSpiffeIdListFromFile(final Path spiffeIdsFile) throws IOException {
|
||||||
throw new NotImplementedException("Not implemented");
|
Stream<String> lines = Files.lines(spiffeIdsFile);
|
||||||
|
return lines
|
||||||
|
.map(SpiffeId::parse)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string representing a list of SPIFFE IDs and return a Result containing a List of
|
* Parses a string representing a list of SPIFFE IDs and returns a list of
|
||||||
* instances of SpiffeId.
|
* instances of {@link SpiffeId}.
|
||||||
*
|
*
|
||||||
* @param spiffeIds a list of SPIFFE IDs represented in a string
|
* @param spiffeIds a list of SPIFFE IDs represented in a string
|
||||||
* @param separator used to separate the SPIFFE IDs in the string.
|
* @param separator used to separate the SPIFFE IDs in the string.
|
||||||
* @return a Result containing a List of SpiffeId instances or an Error.
|
*
|
||||||
|
* @return a list of {@link SpiffeId} instances.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException is the string provided is blank
|
||||||
*/
|
*/
|
||||||
public static Result<List<SpiffeId>, String> toListOfSpiffeIds(final String spiffeIds, final char separator) {
|
public static List<SpiffeId> toListOfSpiffeIds(final String spiffeIds, final char separator) {
|
||||||
if (isBlank(spiffeIds)) {
|
if (isBlank(spiffeIds)) {
|
||||||
return Result.error("SPIFFE IDs is empty");
|
throw new IllegalArgumentException("Argument spiffeIds cannot be emtpy");
|
||||||
}
|
}
|
||||||
|
|
||||||
val array = spiffeIds.split(String.valueOf(separator));
|
val array = spiffeIds.split(String.valueOf(separator));
|
||||||
val spiffeIdList = Arrays.stream(array)
|
return Arrays.stream(array)
|
||||||
.map(SpiffeId::parse)
|
.map(SpiffeId::parse)
|
||||||
.map(Result::getValue)
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return Result.ok(spiffeIdList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package spiffe.spiffeid;
|
package spiffe.spiffeid;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
@ -24,27 +24,30 @@ public class TrustDomain {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of a TrustDomain.
|
* Creates a trust domain.
|
||||||
*
|
*
|
||||||
* @param trustDomain a TrustDomain represented as a String, must not be blank.
|
* @param trustDomain a trust domain represented as a string, must not be blank.
|
||||||
* @return an Ok result containing the parsed TrustDomain, or an Error if the trustDomain cannot be parsed
|
* @return an instance of a {@link TrustDomain}
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the given string is blank or cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<TrustDomain, String> of(String trustDomain) {
|
public static TrustDomain of(@NonNull String trustDomain) {
|
||||||
if (StringUtils.isBlank(trustDomain)) {
|
if (StringUtils.isBlank(trustDomain)) {
|
||||||
return Result.error("Trust Domain cannot be empty.");
|
throw new IllegalArgumentException("Trust Domain cannot be empty");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val uri = new URI(normalize(trustDomain));
|
val uri = new URI(normalize(trustDomain));
|
||||||
val result = new TrustDomain(getHost(uri));
|
val host = getHost(uri);
|
||||||
return Result.ok(result);
|
return new TrustDomain(host);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
return Result.error(format("Unable to parse: %s.", trustDomain));
|
throw new IllegalArgumentException(format("Unable to parse: %s", trustDomain), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the trustDomain as String
|
* Returns the trust domain as a string.
|
||||||
* @return a String with the Trust Domain
|
*
|
||||||
|
* @return a String with the trust domain
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import spiffe.bundle.jwtbundle.JwtBundleSource;
|
import spiffe.bundle.jwtbundle.JwtBundleSource;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
@ -47,20 +46,22 @@ public class JwtSvid {
|
||||||
* Parses and validates a JWT-SVID token and returns the
|
* Parses and validates a JWT-SVID token and returns the
|
||||||
* JWT-SVID. The JWT-SVID signature is verified using the JWT bundle source.
|
* JWT-SVID. The JWT-SVID signature is verified using the JWT bundle source.
|
||||||
*
|
*
|
||||||
* @param token a token as a String
|
* @param token a token as a string
|
||||||
* @param jwtBundleSource an implementation of a JwtBundleSource
|
* @param jwtBundleSource an implementation of a {@link JwtBundleSource}
|
||||||
* @param audience the audience as a String
|
* @param audience the audience as a String
|
||||||
* @return a JwtSvid or Error
|
* @return an instance of a {@link JwtSvid}
|
||||||
|
*
|
||||||
|
* @throws //TODO: declare thrown exceptions
|
||||||
*/
|
*/
|
||||||
public Result<JwtSvid, String> parseAndValidate(@NonNull final String token, @NonNull final JwtBundleSource jwtBundleSource, String... audience) {
|
public JwtSvid parseAndValidate(@NonNull final String token, @NonNull final JwtBundleSource jwtBundleSource, String... audience) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the JWT-SVID marshaled to a string. The returned value is
|
* Returns the JWT-SVID marshaled to a string. The returned value is
|
||||||
* the same token value originally passed to ParseAndValidate.
|
* the same token value originally passed to parseAndValidate.
|
||||||
*
|
*
|
||||||
* @return
|
* @return the token
|
||||||
*/
|
*/
|
||||||
public String marshall() {
|
public String marshall() {
|
||||||
return token;
|
return token;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package spiffe.svid.jwtsvid;
|
package spiffe.svid.jwtsvid;
|
||||||
|
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -11,10 +10,12 @@ public interface JwtSvidSource {
|
||||||
/**
|
/**
|
||||||
* Fetches a JWT-SVID from the source with the given parameters
|
* Fetches a JWT-SVID from the source with the given parameters
|
||||||
*
|
*
|
||||||
* @param subject a SpiffeId
|
* @param subject a {@link SpiffeId}
|
||||||
* @param audience the audience
|
* @param audience the audience
|
||||||
* @param extraAudiences an array of Strings
|
* @param extraAudiences an array of Strings
|
||||||
* @return a JwtSvid
|
* @return a {@link JwtSvid}
|
||||||
|
*
|
||||||
|
* @throws //TODO: declare thrown exceptions
|
||||||
*/
|
*/
|
||||||
Result<JwtSvid, String> FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences);
|
JwtSvid FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,35 @@ package spiffe.svid.x509svid;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509Svid</code> represents a SPIFFE X509-SVID.
|
* A <code>X509Svid</code> represents a SPIFFE X509 SVID.
|
||||||
* <p>
|
* <p>
|
||||||
* Contains a SPIFFE ID, a PrivateKey and a chain of X509Certificate.
|
* Contains a SPIFFE ID, a private key and a chain of X509 certificates.
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
public class X509Svid {
|
public class X509Svid {
|
||||||
|
|
||||||
SpiffeId spiffeId;
|
SpiffeId spiffeId;
|
||||||
|
|
||||||
// The X.509 certificates of the X509-SVID. The leaf certificate is
|
/**
|
||||||
// the X509-SVID certificate. Any remaining certificates (if any)
|
* The X.509 certificates of the X509-SVID. The leaf certificate is
|
||||||
// chain the X509-SVID certificate back to a X509 root
|
* the X509-SVID certificate. Any remaining certificates (if any) chain
|
||||||
// for the trust domain.
|
* the X509-SVID certificate back to a X509 root for the trust domain.
|
||||||
|
*/
|
||||||
List<X509Certificate> chain;
|
List<X509Certificate> chain;
|
||||||
|
|
||||||
PrivateKey privateKey;
|
PrivateKey privateKey;
|
||||||
|
|
@ -42,63 +46,58 @@ public class X509Svid {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the X509-SVID from PEM encoded files on disk.
|
* Loads the X509 SVID from PEM encoded files on disk.
|
||||||
*
|
*
|
||||||
* @param certsFile path to x509 certificate chain file
|
* @param certsFilePath path to X509 certificate chain file
|
||||||
* @param privateKeyFile path to PrivateKey file
|
* @param privateKeyFilePath path to private key file
|
||||||
* @return an instance of X509Svid
|
* @return an instance of {@link X509Svid}
|
||||||
|
*
|
||||||
|
* @throws X509SvidException if there is an error parsing the given certsFilePath or the privateKeyFilePath
|
||||||
*/
|
*/
|
||||||
public static Result<X509Svid, String> load(@NonNull Path certsFile, @NonNull Path privateKeyFile) {
|
public static X509Svid load(@NonNull Path certsFilePath, @NonNull Path privateKeyFilePath) throws X509SvidException {
|
||||||
|
byte[] certsBytes;
|
||||||
|
byte[] privateKeyBytes;
|
||||||
try {
|
try {
|
||||||
val certsBytes = Files.readAllBytes(certsFile);
|
certsBytes = Files.readAllBytes(certsFilePath);
|
||||||
byte[] privateKeyBytes = Files.readAllBytes(privateKeyFile);
|
privateKeyBytes = Files.readAllBytes(privateKeyFilePath);
|
||||||
return createX509Svid(certsBytes, privateKeyBytes);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return Result.error("Error loading X509-SVID from certsFile %s and privateKeyFile %s: %s", certsFile, privateKeyFile, e.getMessage());
|
throw new X509SvidException(String.format("Could not load X509Svid from certsFilePath %s and privateKeyFilePath %s", certsFilePath, privateKeyFilePath), e);
|
||||||
}
|
}
|
||||||
|
return createX509Svid(certsBytes, privateKeyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the X509-SVID from PEM or DER blocks containing certificate chain and key
|
* Parses the X509 SVID from PEM or DER blocks containing certificate chain and key
|
||||||
* bytes. The key must be a PEM or DER block with PKCS#8.
|
* bytes. The key must be a PEM or DER block with PKCS#8.
|
||||||
*
|
*
|
||||||
* @param certsBytes chain of certificates as a byte array
|
* @param certsBytes chain of certificates as a byte array
|
||||||
* @param privateKeyBytes private key byte array
|
* @param privateKeyBytes private key as byte array
|
||||||
* @return a Result(Success) object containing the X509-SVID, or a Error containing the Exception cause
|
* @return a {@link X509Svid} parsed from the given certBytes and privateKeyBytes
|
||||||
|
*
|
||||||
|
* @throws X509SvidException if the given certsBytes or privateKeyBytes cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static Result<X509Svid, String> parse(@NonNull byte[] certsBytes, @NonNull byte[] privateKeyBytes) {
|
public static X509Svid parse(@NonNull byte[] certsBytes, @NonNull byte[] privateKeyBytes) throws X509SvidException {
|
||||||
return createX509Svid(certsBytes, privateKeyBytes);
|
return createX509Svid(certsBytes, privateKeyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the chain of certificates as an array. */
|
/**
|
||||||
|
* Return the chain of certificates as an array.
|
||||||
|
*/
|
||||||
public X509Certificate[] getChainArray() {
|
public X509Certificate[] getChainArray() {
|
||||||
return chain.toArray(new X509Certificate[0]);
|
return chain.toArray(new X509Certificate[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result<X509Svid, String> createX509Svid(byte[] certsBytes, byte[] privateKeyBytes) {
|
private static X509Svid createX509Svid(byte[] certsBytes, byte[] privateKeyBytes) throws X509SvidException {
|
||||||
val x509Certificates = CertificateUtils.generateCertificates(certsBytes);
|
List<X509Certificate> x509Certificates = null;
|
||||||
if (x509Certificates.isError()) {
|
try {
|
||||||
return Result.error(x509Certificates.getError());
|
x509Certificates = CertificateUtils.generateCertificates(certsBytes);
|
||||||
}
|
|
||||||
|
|
||||||
val privateKey = CertificateUtils.generatePrivateKey(privateKeyBytes);
|
val privateKey = CertificateUtils.generatePrivateKey(privateKeyBytes);
|
||||||
if (privateKey.isError()) {
|
val spiffeId = CertificateUtils.getSpiffeId(x509Certificates.get(0));
|
||||||
return Result.error(privateKey.getError());
|
return new X509Svid(spiffeId, x509Certificates, privateKey);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new X509SvidException("X509 SVID could not be parsed from cert bytes", e);
|
||||||
|
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||||
|
throw new X509SvidException("X509 SVID Private Key could not be parsed from privateKeyBytes", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
val spiffeId =
|
|
||||||
CertificateUtils
|
|
||||||
.getSpiffeId(x509Certificates.getValue().get(0));
|
|
||||||
if (spiffeId.isError()) {
|
|
||||||
return Result.error("Error creating X509-SVID: %s", spiffeId.getError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val x509Svid = new X509Svid(
|
|
||||||
spiffeId.getValue(),
|
|
||||||
x509Certificates.getValue(),
|
|
||||||
privateKey.getValue());
|
|
||||||
|
|
||||||
return Result.ok(x509Svid);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package spiffe.svid.x509svid;
|
package spiffe.svid.x509svid;
|
||||||
|
|
||||||
import spiffe.result.Result;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509SvidSource</code> represents a source of X509-SVIDs.
|
* A <code>X509SvidSource</code> represents a source of X509 SVIDs.
|
||||||
*/
|
*/
|
||||||
public interface X509SvidSource {
|
public interface X509SvidSource {
|
||||||
Result<X509Svid, String> getX509Svid();
|
|
||||||
|
/**
|
||||||
|
* Returns the X509 SVID in the source.
|
||||||
|
*
|
||||||
|
* @return an instance of a {@link X509Svid}
|
||||||
|
*/
|
||||||
|
X509Svid getX509Svid();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@ package spiffe.svid.x509svid;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -14,60 +18,48 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509SvidValidator</code> provides methods to validate
|
* A <code>X509SvidValidator</code> provides methods to validate
|
||||||
* a chain of X509 Certificates using an X509BundleSource.
|
* a chain of X509 certificates using an X509 bundle source.
|
||||||
*/
|
*/
|
||||||
public class X509SvidValidator {
|
public class X509SvidValidator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that a chain of certificate can be chained to one trustedCert in the x509BundleSource.
|
* Verifies that a chain of certificates can be chained to one authority in the given X509 bundle source.
|
||||||
*
|
*
|
||||||
* @param chain a chain of X509 Certificates to be validated
|
* @param chain a list representing the chain of X509 certificates to be validated
|
||||||
* @param x509BundleSource a {@link X509BundleSource }to provide the trusted bundle certs
|
* @param x509BundleSource a {@link X509BundleSource } to provide the authorities
|
||||||
*
|
*
|
||||||
* @return a Result object conveying the result of the verification. If the chain can be verified with
|
* @throws CertificateException is the chain cannot be verified with an authority from the X509 bundle source
|
||||||
* a trusted bundle, it returns an Ok(true), otherwise returns an Error with a String message.
|
* @throws NullPointerException if the given chain or 509BundleSource are null
|
||||||
*/
|
*/
|
||||||
public static Result<Boolean, String> verifyChain(
|
public static void verifyChain(
|
||||||
@NonNull List<X509Certificate> chain,
|
@NonNull List<X509Certificate> chain,
|
||||||
@NonNull X509BundleSource x509BundleSource) {
|
@NonNull X509BundleSource x509BundleSource) throws CertificateException {
|
||||||
|
try {
|
||||||
val trustDomain = CertificateUtils.getTrustDomain(chain);
|
val trustDomain = CertificateUtils.getTrustDomain(chain);
|
||||||
if (trustDomain.isError()) {
|
val x509Bundle = x509BundleSource.getX509BundleForTrustDomain(trustDomain);
|
||||||
return Result.error(trustDomain.getError());
|
CertificateUtils.validate(chain, new ArrayList<>(x509Bundle.getX509Authorities()));
|
||||||
|
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertPathValidatorException | BundleNotFoundException e) {
|
||||||
|
throw new CertificateException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
val x509Bundle = x509BundleSource.getX509BundleForTrustDomain(trustDomain.getValue());
|
|
||||||
|
|
||||||
if (x509Bundle.isError()) {
|
|
||||||
return Result.error("No X509 Bundle found for the Trust Domain %s", trustDomain.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return CertificateUtils.validate(chain, new ArrayList<>(x509Bundle.getValue().getX509Authorities()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that the Certificate provided has a SPIFFE ID that is in the list of acceptedSpiffeIds supplied.
|
* Checks that the X509 SVID provided has a SPIFFE ID that is in the list of accepted SPIFFE IDs supplied.
|
||||||
*
|
*
|
||||||
* @param spiffeId a SPIFFE ID to be verified
|
* @param x509Certificate a {@link X509Svid} with a SPIFFE ID to be verified
|
||||||
* @param acceptedSpiffedIdsSupplier a Supplier of a List os SPIFFE IDs that are accepted
|
* @param acceptedSpiffedIdsSupplier a {@link Supplier} of a list os SPIFFE IDs that are accepted
|
||||||
*
|
*
|
||||||
* @return an {@link spiffe.result.Ok} with true if the SPIFFE ID is in the list,
|
* @throws CertificateException is the SPIFFE ID in x509Certificate is not in the list supplied by acceptedSpiffedIdsSupplier,
|
||||||
* an {@link spiffe.result.Error} containing en error message if the SPIFFE ID is not in the list
|
* or if the SPIFFE ID cannot be parsed from the x509Certificate
|
||||||
* or if there's en error getting the list.
|
* @throws NullPointerException if the given x509Certificate or acceptedSpiffedIdsSupplier are null
|
||||||
*/
|
*/
|
||||||
public static Result<Boolean, String> verifySpiffeId(SpiffeId spiffeId, Supplier<Result<List<SpiffeId>, String>> acceptedSpiffedIdsSupplier) {
|
public static void verifySpiffeId(@NonNull X509Certificate x509Certificate,
|
||||||
if (acceptedSpiffedIdsSupplier.get().isError()) {
|
@NonNull Supplier<List<SpiffeId>> acceptedSpiffedIdsSupplier)
|
||||||
return Result.error("Error getting list of accepted SPIFFE IDs");
|
throws CertificateException {
|
||||||
}
|
|
||||||
|
|
||||||
val spiffeIdList = acceptedSpiffedIdsSupplier.get();
|
val spiffeIdList = acceptedSpiffedIdsSupplier.get();
|
||||||
if (spiffeIdList.isError()) {
|
val spiffeId = CertificateUtils.getSpiffeId(x509Certificate);
|
||||||
return Result.error(spiffeIdList.getError());
|
if (!spiffeIdList.contains(spiffeId)) {
|
||||||
|
throw new CertificateException(String.format("SPIFFE ID %s in x509Certificate is not accepted", spiffeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spiffeIdList.getValue().contains(spiffeId)) {
|
|
||||||
return Result.ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.error("SPIFFE ID '%s' is not accepted", spiffeId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
package spiffe.workloadapi;
|
package spiffe.workloadapi;
|
||||||
|
|
||||||
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to get the default Workload api address and parse string addresses.
|
* Utility class to get the default Workload API address and parse string addresses.
|
||||||
*/
|
*/
|
||||||
public class Address {
|
public class Address {
|
||||||
|
|
||||||
|
|
@ -18,88 +21,128 @@ public class Address {
|
||||||
*/
|
*/
|
||||||
public static final String SOCKET_ENV_VARIABLE = "SPIFFE_ENDPOINT_SOCKET";
|
public static final String SOCKET_ENV_VARIABLE = "SPIFFE_ENDPOINT_SOCKET";
|
||||||
|
|
||||||
|
private static final List<String> VALID_SCHEMES = Arrays.asList("unix", "tcp");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default Workload API address hold by the system environment variable
|
||||||
|
* defined by SOCKET_ENV_VARIABLE
|
||||||
|
*/
|
||||||
public static String getDefaultAddress() {
|
public static String getDefaultAddress() {
|
||||||
return System.getenv(Address.SOCKET_ENV_VARIABLE);
|
return System.getenv(Address.SOCKET_ENV_VARIABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<URI, String> parseAddress(String addr) {
|
/**
|
||||||
|
* Parses and validates a Workload API socket address.
|
||||||
|
* <p>
|
||||||
|
* The given address should either have a tcp, or a unix scheme.
|
||||||
|
* <p>
|
||||||
|
* The given address should contain a path.
|
||||||
|
* <p>
|
||||||
|
* The given address cannot be opaque, cannot have fragments, query values or user info.
|
||||||
|
* <p>
|
||||||
|
* If the given address is tcp, it should contain an IP and a port.
|
||||||
|
*
|
||||||
|
* @param address the Workload API socket address as a string
|
||||||
|
* @return an instance of a {@link URI}
|
||||||
|
*
|
||||||
|
* @throws SocketEndpointAddressException if the address could not be parsed or if it is not valid
|
||||||
|
*/
|
||||||
|
public static URI parseAddress(String address) throws SocketEndpointAddressException {
|
||||||
URI parsedAddress;
|
URI parsedAddress;
|
||||||
try {
|
try {
|
||||||
parsedAddress = new URI(addr);
|
parsedAddress = new URI(address);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
return Result.error("Workload endpoint socket is not a valid URI: %s", e.getMessage());
|
throw new SocketEndpointAddressException(String.format("Workload endpoint socket is not a valid URI: %s", address), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedAddress.getScheme() == null) {
|
val scheme = parsedAddress.getScheme();
|
||||||
return Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme");
|
if (!isValid(scheme)) {
|
||||||
|
throw new SocketEndpointAddressException(String.format("Workload endpoint socket URI must have a tcp:// or unix:// scheme: %s", address));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (parsedAddress.getScheme()) {
|
String error = null;
|
||||||
|
switch (scheme) {
|
||||||
case "unix": {
|
case "unix": {
|
||||||
if (parsedAddress.isOpaque() && parsedAddress.isAbsolute()) {
|
if (parsedAddress.isOpaque() && parsedAddress.isAbsolute()) {
|
||||||
return Result.error("Workload endpoint unix socket URI must not be opaque");
|
error = "Workload endpoint unix socket URI must not be opaque: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getUserInfo())) {
|
if (StringUtils.isNotBlank(parsedAddress.getUserInfo())) {
|
||||||
return Result.error("Workload endpoint unix socket URI must not include user info");
|
error = "Workload endpoint unix socket URI must not include user info: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(parsedAddress.getHost()) && StringUtils.isBlank(parsedAddress.getPath())) {
|
if (StringUtils.isBlank(parsedAddress.getHost()) && StringUtils.isBlank(parsedAddress.getPath())) {
|
||||||
return Result.error("Workload endpoint unix socket URI must include a path");
|
error = "Workload endpoint unix socket URI must include a path: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getRawQuery())) {
|
if (StringUtils.isNotBlank(parsedAddress.getRawQuery())) {
|
||||||
return Result.error("Workload endpoint unix socket URI must not include query values");
|
error = "Workload endpoint unix socket URI must not include query values: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getFragment())) {
|
if (StringUtils.isNotBlank(parsedAddress.getFragment())) {
|
||||||
return Result.error("Workload endpoint unix socket URI must not include a fragment");
|
error = "Workload endpoint unix socket URI must not include a fragment: %s";
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
return Result.ok(parsedAddress);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "tcp": {
|
case "tcp": {
|
||||||
if (parsedAddress.isOpaque() && parsedAddress.isAbsolute()) {
|
if (parsedAddress.isOpaque() && parsedAddress.isAbsolute()) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must not be opaque");
|
error = "Workload endpoint tcp socket URI must not be opaque: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getUserInfo())) {
|
if (StringUtils.isNotBlank(parsedAddress.getUserInfo())) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must not include user info");
|
error = "Workload endpoint tcp socket URI must not include user info: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(parsedAddress.getHost())) {
|
if (StringUtils.isBlank(parsedAddress.getHost())) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must include a host");
|
error = "Workload endpoint tcp socket URI must include a host: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getPath())) {
|
if (StringUtils.isNotBlank(parsedAddress.getPath())) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must not include a path");
|
error = "Workload endpoint tcp socket URI must not include a path: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getRawQuery())) {
|
if (StringUtils.isNotBlank(parsedAddress.getRawQuery())) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must not include query values");
|
error = "Workload endpoint tcp socket URI must not include query values: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(parsedAddress.getFragment())) {
|
if (StringUtils.isNotBlank(parsedAddress.getFragment())) {
|
||||||
return Result.error("Workload endpoint tcp socket URI must not include a fragment");
|
error = "Workload endpoint tcp socket URI must not include a fragment: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ip = parseIp(parsedAddress.getHost());
|
String ip = parseIp(parsedAddress.getHost());
|
||||||
if (ip == null) {
|
if (StringUtils.isBlank(ip)) {
|
||||||
return Result.error("Workload endpoint tcp socket URI host component must be an IP:port");
|
error = "Workload endpoint tcp socket URI host component must be an IP:port: %s";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int port = parsedAddress.getPort();
|
int port = parsedAddress.getPort();
|
||||||
if (port == -1) {
|
if (port == -1) {
|
||||||
return Result.error("Workload endpoint tcp socket URI host component must include a port");
|
error = "Workload endpoint tcp socket URI host component must include a port: %s";
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
return Result.ok(parsedAddress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme");
|
if (StringUtils.isNotBlank(error)) {
|
||||||
|
throw new SocketEndpointAddressException(String.format(error, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValid(String scheme) {
|
||||||
|
return (StringUtils.isNotBlank(scheme) && VALID_SCHEMES.contains(scheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parseIp(String host) {
|
private static String parseIp(String host) {
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,35 @@ package spiffe.workloadapi;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import spiffe.bundle.jwtbundle.JwtBundle;
|
import spiffe.bundle.jwtbundle.JwtBundle;
|
||||||
import spiffe.bundle.jwtbundle.JwtBundleSource;
|
import spiffe.bundle.jwtbundle.JwtBundleSource;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
import spiffe.svid.jwtsvid.JwtSvid;
|
import spiffe.svid.jwtsvid.JwtSvid;
|
||||||
import spiffe.svid.jwtsvid.JwtSvidSource;
|
import spiffe.svid.jwtsvid.JwtSvidSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>JwtSource</code> represents a source of SPIFFE JWT-SVID and JWT bundles
|
* A <code>JwtSource</code> represents a source of SPIFFE JWT SVID and JWT bundles
|
||||||
* maintained via the Workload API.
|
* maintained via the Workload API.
|
||||||
*/
|
*/
|
||||||
public class JwtSource implements JwtSvidSource, JwtBundleSource {
|
public class JwtSource implements JwtSvidSource, JwtBundleSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new JWTSource. It blocks until the initial update
|
* Creates a new JWT source. It blocks until the initial update
|
||||||
* has been received from the Workload API.
|
* has been received from the Workload API.
|
||||||
*
|
*
|
||||||
* @param spiffeSocketPath a Path to the Workload API endpoint
|
* @param spiffeSocketPath a path to the Workload API endpoint
|
||||||
* @return a Result containing an instance of a JwtSource, or an Error with an
|
* @return an instance of a {@link JwtSource}
|
||||||
* Exception.
|
|
||||||
*/
|
*/
|
||||||
public static Result<JwtSource, String> newSource(String spiffeSocketPath) {
|
public static JwtSource newSource(String spiffeSocketPath) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<JwtBundle, String> getJwtBundleForTrustDomain(TrustDomain trustDomain) {
|
public JwtBundle getJwtBundleForTrustDomain(TrustDomain trustDomain) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<JwtSvid, String> FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences) {
|
public JwtSvid FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package spiffe.workloadapi;
|
package spiffe.workloadapi;
|
||||||
|
|
||||||
import spiffe.result.Error;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a <code>Watcher</code> handles updates of type T.
|
* a <code>Watcher</code> handles updates of type T.
|
||||||
*
|
*
|
||||||
|
|
@ -11,5 +9,5 @@ public interface Watcher<T> {
|
||||||
|
|
||||||
void OnUpdate(final T update);
|
void OnUpdate(final T update);
|
||||||
|
|
||||||
void OnError(final Error<T, String> t);
|
void OnError(final Throwable e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import lombok.extern.java.Log;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import spiffe.bundle.jwtbundle.JwtBundleSet;
|
import spiffe.bundle.jwtbundle.JwtBundleSet;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
|
import spiffe.exception.X509ContextException;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.svid.jwtsvid.JwtSvid;
|
import spiffe.svid.jwtsvid.JwtSvid;
|
||||||
import spiffe.workloadapi.internal.GrpcConversionUtils;
|
import spiffe.workloadapi.internal.GrpcConversionUtils;
|
||||||
|
|
@ -23,7 +24,7 @@ import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIBlocki
|
||||||
import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIStub;
|
import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIStub;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.net.URI;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
@ -34,10 +35,7 @@ import static spiffe.workloadapi.internal.Workload.X509SVIDResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>WorkloadApiClient</code> represents a client to interact with the Workload API.
|
* A <code>WorkloadApiClient</code> represents a client to interact with the Workload API.
|
||||||
* Supports one-shot calls and watch updates for X509 and JWT SVIDS and Bundles.
|
* Supports one-shot calls and watch updates for X509 and JWT SVIDS and bundles.
|
||||||
* <p>
|
|
||||||
* Multiple WorkloadApiClients can be created for the same SPIFFE Socket Path,
|
|
||||||
* they will share a common ManagedChannel.
|
|
||||||
*/
|
*/
|
||||||
@Log
|
@Log
|
||||||
public class WorkloadApiClient implements Closeable {
|
public class WorkloadApiClient implements Closeable {
|
||||||
|
|
@ -55,26 +53,25 @@ public class WorkloadApiClient implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new WorkloadAPI client using the Default Address from the Environment Variable
|
* Creates a new Workload API client using the default socket endpoint address.
|
||||||
|
* @see Address#getDefaultAddress()
|
||||||
*
|
*
|
||||||
* @return a Result containing a WorklaodAPI client
|
* @return a {@link WorkloadApiClient}
|
||||||
*/
|
*/
|
||||||
public static Result<WorkloadApiClient, String> newClient() {
|
public static WorkloadApiClient newClient() throws SocketEndpointAddressException {
|
||||||
val options = ClientOptions.builder().build();
|
val options = ClientOptions.builder().build();
|
||||||
return newClient(options);
|
return newClient(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Workload API Client.
|
* Creates a new Workload API client.
|
||||||
* <p>
|
* <p>
|
||||||
* If the SPIFFE Socket Path is not provided, it uses the Default Address from
|
* If the SPIFFE socket endpoint address is not provided in the options, it uses the default address.
|
||||||
* the Environment variable for creating the client.
|
|
||||||
*
|
*
|
||||||
* @param options {@link ClientOptions}
|
* @param options {@link ClientOptions}
|
||||||
* @return a Result containing a WorklaodAPI client
|
* @return a {@link WorkloadApiClient}
|
||||||
*/
|
*/
|
||||||
public static Result<WorkloadApiClient, String> newClient(@NonNull ClientOptions options) {
|
public static WorkloadApiClient newClient(@NonNull ClientOptions options) throws SocketEndpointAddressException {
|
||||||
|
|
||||||
String spiffeSocketPath;
|
String spiffeSocketPath;
|
||||||
if (StringUtils.isNotBlank(options.spiffeSocketPath)) {
|
if (StringUtils.isNotBlank(options.spiffeSocketPath)) {
|
||||||
spiffeSocketPath = options.spiffeSocketPath;
|
spiffeSocketPath = options.spiffeSocketPath;
|
||||||
|
|
@ -82,13 +79,8 @@ public class WorkloadApiClient implements Closeable {
|
||||||
spiffeSocketPath = Address.getDefaultAddress();
|
spiffeSocketPath = Address.getDefaultAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
val parseResult = Address.parseAddress(spiffeSocketPath);
|
val socketEndpointAddress = Address.parseAddress(spiffeSocketPath);
|
||||||
if (parseResult.isError()) {
|
val managedChannel = GrpcManagedChannelFactory.newChannel(socketEndpointAddress);
|
||||||
return Result.error(parseResult.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
URI parsedAddress = parseResult.getValue();
|
|
||||||
val managedChannel = GrpcManagedChannelFactory.newChannel(parsedAddress);
|
|
||||||
|
|
||||||
val workloadAPIAsyncStub = SpiffeWorkloadAPIGrpc
|
val workloadAPIAsyncStub = SpiffeWorkloadAPIGrpc
|
||||||
.newStub(managedChannel)
|
.newStub(managedChannel)
|
||||||
|
|
@ -98,64 +90,54 @@ public class WorkloadApiClient implements Closeable {
|
||||||
.newBlockingStub(managedChannel)
|
.newBlockingStub(managedChannel)
|
||||||
.withInterceptors(new SecurityHeaderInterceptor());
|
.withInterceptors(new SecurityHeaderInterceptor());
|
||||||
|
|
||||||
val workloadApiClient = new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel);
|
return new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel);
|
||||||
return Result.ok(workloadApiClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One-shot fetch call to get an X509 Context (SPIFFE X509-SVID and Bundles).
|
* One-shot blocking fetch call to get an X509 context.
|
||||||
|
*
|
||||||
|
* @throws X509ContextException if there is an error fetching or processing the X509 context
|
||||||
*/
|
*/
|
||||||
public Result<X509Context, String> fetchX509Context() {
|
public X509Context fetchX509Context() {
|
||||||
Context.CancellableContext cancellableContext;
|
Context.CancellableContext cancellableContext;
|
||||||
cancellableContext = Context.current().withCancellation();
|
cancellableContext = Context.current().withCancellation();
|
||||||
Result<X509Context, String> result;
|
X509Context result;
|
||||||
try {
|
try {
|
||||||
result = cancellableContext.call(this::processX509Context);
|
result = cancellableContext.call(this::processX509Context);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error("Error fetching X509Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
|
throw new X509ContextException("Error fetching X509Context", e);
|
||||||
}
|
}
|
||||||
// close connection
|
// close connection
|
||||||
cancellableContext.close();
|
cancellableContext.close();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<X509Context, String> processX509Context() {
|
|
||||||
try {
|
|
||||||
Iterator<X509SVIDResponse> x509SVIDResponse = workloadApiBlockingStub.fetchX509SVID(newX509SvidRequest());
|
|
||||||
if (x509SVIDResponse.hasNext()) {
|
|
||||||
return GrpcConversionUtils.toX509Context(x509SVIDResponse.next());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Result.error("Error processing X509Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
|
|
||||||
}
|
|
||||||
return Result.error("Could not get X509Context");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watches for updates to the X509 Context.
|
* Watches for X509 context updates.
|
||||||
*
|
*
|
||||||
* @param watcher receives the update X509 context.
|
* @param watcher an instance that implements a {@link Watcher}.
|
||||||
*/
|
*/
|
||||||
public void watchX509Context(Watcher<X509Context> watcher) {
|
public void watchX509Context(Watcher<X509Context> watcher) {
|
||||||
StreamObserver<X509SVIDResponse> streamObserver = new StreamObserver<X509SVIDResponse>() {
|
StreamObserver<X509SVIDResponse> streamObserver = new StreamObserver<X509SVIDResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onNext(X509SVIDResponse value) {
|
public void onNext(X509SVIDResponse value) {
|
||||||
Result<X509Context, String> x509Context = GrpcConversionUtils.toX509Context(value);
|
X509Context x509Context = null;
|
||||||
if (x509Context.isError()) {
|
try {
|
||||||
watcher.OnError(Result.error(x509Context.getError()));
|
x509Context = GrpcConversionUtils.toX509Context(value);
|
||||||
|
} catch (CertificateException | X509SvidException e) {
|
||||||
|
watcher.OnError(new X509ContextException("Error processing X509 Context update", e));
|
||||||
}
|
}
|
||||||
watcher.OnUpdate(x509Context.getValue());
|
watcher.OnUpdate(x509Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable t) {
|
public void onError(Throwable t) {
|
||||||
String error = String.format("Error getting X509Context update: %s %n %s", t.getMessage(), ExceptionUtils.getStackTrace(t));
|
watcher.OnError(new X509ContextException("Error getting X509Context", t));
|
||||||
watcher.OnError(Result.error(error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCompleted() {
|
public void onCompleted() {
|
||||||
watcher.OnError(Result.error("Unexpected completed stream."));
|
watcher.OnError(new X509ContextException("Unexpected completed stream"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Context.CancellableContext cancellableContext;
|
Context.CancellableContext cancellableContext;
|
||||||
|
|
@ -170,20 +152,22 @@ public class WorkloadApiClient implements Closeable {
|
||||||
* @param subject a SPIFFE ID
|
* @param subject a SPIFFE ID
|
||||||
* @param audience the audience of the JWT-SVID
|
* @param audience the audience of the JWT-SVID
|
||||||
* @param extraAudience the extra audience for the JWT_SVID
|
* @param extraAudience the extra audience for the JWT_SVID
|
||||||
* @return an {@link spiffe.result.Ok} containing the JWT SVID, or an {@link spiffe.result.Error}
|
*
|
||||||
* if the JwtSvid could not be fetched.
|
* @return an instance of a {@link JwtSvid}
|
||||||
|
*
|
||||||
|
* @throws //TODO: declare thrown exceptions
|
||||||
*/
|
*/
|
||||||
public Result<JwtSvid, String> fetchJwtSvid(SpiffeId subject, String audience, String... extraAudience) {
|
public JwtSvid fetchJwtSvid(SpiffeId subject, String audience, String... extraAudience) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the JWT bundles for JWT-SVID validation, keyed
|
* Fetches the JWT bundles for JWT-SVID validation, keyed by trust domain.
|
||||||
* by a SPIFFE ID of the trust domain to which they belong.
|
|
||||||
*
|
*
|
||||||
* @return an {@link spiffe.result.Ok} containing the JwtBundleSet.
|
* @return an instance of a {@link JwtBundleSet}
|
||||||
|
* @throws //TODO: declare thrown exceptions
|
||||||
*/
|
*/
|
||||||
public Result<JwtBundleSet, String> fetchJwtBundles() {
|
public JwtBundleSet fetchJwtBundles() {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,14 +177,16 @@ public class WorkloadApiClient implements Closeable {
|
||||||
*
|
*
|
||||||
* @param token JWT token
|
* @param token JWT token
|
||||||
* @param audience audience of the JWT
|
* @param audience audience of the JWT
|
||||||
* @return the JwtSvid if the token and audience could be validated.
|
* @return the {@link JwtSvid} if the token and audience could be validated.
|
||||||
|
*
|
||||||
|
* @throws //TODO: declare thrown exceptions
|
||||||
*/
|
*/
|
||||||
public Result<JwtSvid, String> validateJwtSvid(String token, String audience) {
|
public JwtSvid validateJwtSvid(String token, String audience) {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watches for updates to the JWT Bundles.
|
* Watches for JWT bundles updates.
|
||||||
*
|
*
|
||||||
* @param jwtBundlesWatcher receives the update for JwtBundles.
|
* @param jwtBundlesWatcher receives the update for JwtBundles.
|
||||||
*/
|
*/
|
||||||
|
|
@ -208,10 +194,9 @@ public class WorkloadApiClient implements Closeable {
|
||||||
throw new NotImplementedException("Not implemented");
|
throw new NotImplementedException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
private X509SVIDRequest newX509SvidRequest() {
|
/**
|
||||||
return X509SVIDRequest.newBuilder().build();
|
* Closes this Workload API closing the underlying channel and cancelling the contexts.
|
||||||
}
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
log.info("Closing WorkloadAPI client");
|
log.info("Closing WorkloadAPI client");
|
||||||
|
|
@ -224,6 +209,22 @@ public class WorkloadApiClient implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private X509SVIDRequest newX509SvidRequest() {
|
||||||
|
return X509SVIDRequest.newBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Context processX509Context() {
|
||||||
|
try {
|
||||||
|
Iterator<X509SVIDResponse> x509SVIDResponse = workloadApiBlockingStub.fetchX509SVID(newX509SvidRequest());
|
||||||
|
if (x509SVIDResponse.hasNext()) {
|
||||||
|
return GrpcConversionUtils.toX509Context(x509SVIDResponse.next());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new X509ContextException("Error processing X509Context", e);
|
||||||
|
}
|
||||||
|
throw new X509ContextException("Error processing X509Context: x509SVIDResponse is empty");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for creating a new {@link WorkloadApiClient}.
|
* Options for creating a new {@link WorkloadApiClient}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A <code>X509Context</code> represents the X509 materials that are fetched from the Workload API.
|
* A <code>X509Context</code> represents the X509 materials that are fetched from the Workload API.
|
||||||
* <p>
|
* <p>
|
||||||
* Contains a List of {@link X509Svid} and a {@link X509BundleSet}.
|
* Contains a list of {@link X509Svid} and a {@link X509BundleSet}.
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
public class X509Context {
|
public class X509Context {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.java.Log;
|
import lombok.extern.java.Log;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import spiffe.bundle.x509bundle.X509Bundle;
|
import spiffe.bundle.x509bundle.X509Bundle;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSet;
|
import spiffe.bundle.x509bundle.X509BundleSet;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
import spiffe.result.Error;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
|
import spiffe.exception.X509SourceException;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
import spiffe.svid.x509svid.X509SvidSource;
|
import spiffe.svid.x509svid.X509SvidSource;
|
||||||
|
|
@ -20,13 +22,15 @@ import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509Source</code> represents a source of X509-SVID and X509 Bundles maintained via the
|
* A <code>X509Source</code> represents a source of X509 SVIDs and X509 bundles maintained via the
|
||||||
* Workload API.
|
* Workload API.
|
||||||
* <p>
|
* <p>
|
||||||
* It handles a {@link X509Svid} and a {@link X509BundleSet} that are updated automatically
|
* It handles a {@link X509Svid} and a {@link X509BundleSet} that are updated automatically
|
||||||
* whenever there is an update from the Workload API.
|
* whenever there is an update from the Workload API.
|
||||||
* <p>
|
* <p>
|
||||||
* It implements the Closeable interface. The {@link #close()} method closes the source,
|
* Implements {@link X509SvidSource} and {@link X509BundleSource}.
|
||||||
|
* <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
|
* dropping the connection to the Workload API. Other source methods will return an error
|
||||||
* after close has been called.
|
* after close has been called.
|
||||||
*/
|
*/
|
||||||
|
|
@ -41,94 +45,81 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable {
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new X509Source. It blocks until the initial update
|
* Creates a new X509 source. It blocks until the initial update
|
||||||
* has been received from the Workload API.
|
* has been received from the Workload API.
|
||||||
* <p>
|
* <p>
|
||||||
* It uses the Default Address from the Environment variable to get the Workload API endpoint address.
|
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
|
||||||
* <p>
|
* <p>
|
||||||
* It uses the default X509-SVID.
|
* It uses the default X509 SVID.
|
||||||
*
|
*
|
||||||
* @return an initialized an {@link spiffe.result.Ok} with X509Source, or an {@link Error} in
|
* @return an instance of {@link X509Source}, with the svid and bundles initialized
|
||||||
* case the X509Source could not be initialized.
|
*
|
||||||
|
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||||
|
* @throws X509SourceException if the source could not be initialized
|
||||||
*/
|
*/
|
||||||
public static Result<X509Source, String> newSource() {
|
public static X509Source newSource() throws SocketEndpointAddressException {
|
||||||
X509SourceOptions x509SourceOptions = X509SourceOptions.builder().build();
|
X509SourceOptions x509SourceOptions = X509SourceOptions.builder().build();
|
||||||
return newSource(x509SourceOptions);
|
return newSource(x509SourceOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new X509Source. It blocks until the initial update
|
* Creates a new X509 source. It blocks until the initial update
|
||||||
* has been received from the Workload API.
|
* has been received from the Workload API.
|
||||||
* <p>
|
* <p>
|
||||||
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
||||||
* a new client is created.
|
* a new client is created.
|
||||||
*
|
*
|
||||||
* @param options {@link X509SourceOptions}
|
* @param options {@link X509SourceOptions}
|
||||||
* @return an initialized an {@link spiffe.result.Ok} with X509Source, or an {@link Error} in
|
* @return an instance of {@link X509Source}, with the svid and bundles initialized
|
||||||
* case the X509Source could not be initialized.
|
*
|
||||||
|
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||||
|
* @throws X509SourceException if the source could not be initialized
|
||||||
*/
|
*/
|
||||||
public static Result<X509Source, String> newSource(@NonNull X509SourceOptions options) {
|
public static X509Source newSource(@NonNull X509SourceOptions options) throws SocketEndpointAddressException {
|
||||||
|
|
||||||
if (options.workloadApiClient == null) {
|
if (options.workloadApiClient == null) {
|
||||||
Result<WorkloadApiClient, String> workloadApiClient = createClient(options);
|
options.workloadApiClient = createClient(options);
|
||||||
if (workloadApiClient.isError()) {
|
|
||||||
return Result.error(workloadApiClient.getError());
|
|
||||||
}
|
|
||||||
options.workloadApiClient = workloadApiClient.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val x509Source = new X509Source();
|
val x509Source = new X509Source();
|
||||||
x509Source.picker = options.picker;
|
x509Source.picker = options.picker;
|
||||||
x509Source.workloadApiClient = options.workloadApiClient;
|
x509Source.workloadApiClient = options.workloadApiClient;
|
||||||
|
|
||||||
Result<Boolean, String> init = x509Source.init();
|
try {
|
||||||
if (init.isError()) {
|
x509Source.init();
|
||||||
|
} catch (Exception e) {
|
||||||
x509Source.close();
|
x509Source.close();
|
||||||
return Result.error("Error creating X509 Source: %s", init.getError());
|
throw new X509SourceException("Error creating X509 source", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(x509Source);
|
return x509Source;
|
||||||
}
|
|
||||||
|
|
||||||
private static Result<WorkloadApiClient, String> createClient(@NonNull X509Source.@NonNull X509SourceOptions options) {
|
|
||||||
Result<WorkloadApiClient, String> workloadApiClient;
|
|
||||||
val clientOptions= WorkloadApiClient.ClientOptions
|
|
||||||
.builder()
|
|
||||||
.spiffeSocketPath(options.spiffeSocketPath)
|
|
||||||
.build();
|
|
||||||
workloadApiClient = WorkloadApiClient.newClient(clientOptions);
|
|
||||||
return workloadApiClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private X509Source() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the X509-SVID handled by this source, returns an Error in case
|
* Returns the X509 SVID handled by this source.
|
||||||
* the source is already closed.
|
|
||||||
*
|
*
|
||||||
* @return an {@link spiffe.result.Ok} containing the {@link X509Svid}
|
* @return a {@link X509Svid}
|
||||||
|
* @throws IllegalStateException if the source is closed
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<X509Svid, String> getX509Svid() {
|
public X509Svid getX509Svid() {
|
||||||
val checkClosed = checkClosed();
|
if (isClosed()) {
|
||||||
if (checkClosed.isError()) {
|
throw new IllegalStateException("X509 SVID source is closed");
|
||||||
return Result.error(checkClosed.getError());
|
|
||||||
}
|
}
|
||||||
return Result.ok(svid);
|
return svid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the X509-Bundle for a given trust domain, returns an Error in case
|
* Returns the X509 bundle for a given trust domain.
|
||||||
* there is no bundle for the trust domain, or the source is already closed.
|
|
||||||
*
|
*
|
||||||
* @return an {@link spiffe.result.Ok} containing the {@link X509Bundle}.
|
* @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
|
@Override
|
||||||
public Result<X509Bundle, String> getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) {
|
public X509Bundle getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
val checkClosed = checkClosed();
|
if (isClosed()) {
|
||||||
if (checkClosed.isError()) {
|
throw new IllegalStateException("X509 bundle source is closed");
|
||||||
return Result.error(checkClosed.getError());
|
|
||||||
}
|
}
|
||||||
return bundles.getX509BundleForTrustDomain(trustDomain);
|
return bundles.getX509BundleForTrustDomain(trustDomain);
|
||||||
}
|
}
|
||||||
|
|
@ -150,14 +141,18 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Boolean, String> init() {
|
private static WorkloadApiClient createClient(@NonNull X509Source.@NonNull X509SourceOptions options) throws SocketEndpointAddressException {
|
||||||
Result<X509Context, String> x509Context = workloadApiClient.fetchX509Context();
|
val clientOptions= WorkloadApiClient.ClientOptions
|
||||||
if (x509Context.isError()) {
|
.builder()
|
||||||
return Result.error(x509Context.getError());
|
.spiffeSocketPath(options.spiffeSocketPath)
|
||||||
|
.build();
|
||||||
|
return WorkloadApiClient.newClient(clientOptions);
|
||||||
}
|
}
|
||||||
setX509Context(x509Context.getValue());
|
|
||||||
|
private void init() {
|
||||||
|
X509Context x509Context = workloadApiClient.fetchX509Context();
|
||||||
|
setX509Context(x509Context);
|
||||||
setX509ContextWatcher();
|
setX509ContextWatcher();
|
||||||
return Result.ok(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setX509ContextWatcher() {
|
private void setX509ContextWatcher() {
|
||||||
|
|
@ -169,8 +164,8 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void OnError(Error<X509Context, String> error) {
|
public void OnError(Throwable error) {
|
||||||
log.log(Level.SEVERE, String.format("Error in X509Context watcher: %s", error.getError()));
|
log.log(Level.SEVERE, String.format("Error in X509Context watcher: %s %n %s", error.getMessage(), ExceptionUtils.getStackTrace(error)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -188,12 +183,9 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Boolean, String> checkClosed() {
|
private boolean isClosed() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (closed) {
|
return closed;
|
||||||
return Result.error("source is closed");
|
|
||||||
}
|
|
||||||
return Result.ok(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,8 +194,21 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable {
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public static class X509SourceOptions {
|
public static class X509SourceOptions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address to the Workload API, if it is not set, the default address will be used.
|
||||||
|
*/
|
||||||
String spiffeSocketPath;
|
String spiffeSocketPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to choose the X509 SVID from the list returned by the Workload API
|
||||||
|
* If it is not set, the default svid is picked.
|
||||||
|
*/
|
||||||
Function<List<X509Svid>, X509Svid> picker;
|
Function<List<X509Svid>, X509Svid> picker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom instance of a {@link WorkloadApiClient}, if it is not set, a new instance will be created
|
||||||
|
*/
|
||||||
WorkloadApiClient workloadApiClient;
|
WorkloadApiClient workloadApiClient;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,50 @@
|
||||||
package spiffe.workloadapi.internal;
|
package spiffe.workloadapi.internal;
|
||||||
|
|
||||||
|
import lombok.val;
|
||||||
import spiffe.bundle.x509bundle.X509Bundle;
|
import spiffe.bundle.x509bundle.X509Bundle;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSet;
|
import spiffe.bundle.x509bundle.X509BundleSet;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
import spiffe.workloadapi.X509Context;
|
import spiffe.workloadapi.X509Context;
|
||||||
|
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for converting GRPC objects to JAVA-SPIFFE domain objects.
|
||||||
|
*/
|
||||||
public class GrpcConversionUtils {
|
public class GrpcConversionUtils {
|
||||||
|
|
||||||
public static Result<X509Context, String> toX509Context(Workload.X509SVIDResponse x509SVIDResponse) {
|
public static X509Context toX509Context(Workload.X509SVIDResponse x509SVIDResponse) throws CertificateException, X509SvidException {
|
||||||
Result<List<X509Svid>, String> x509SvidListResult = getListOfX509Svid(x509SVIDResponse);
|
List<X509Svid> x509SvidList = getListOfX509Svid(x509SVIDResponse);
|
||||||
if (x509SvidListResult.isError()) {
|
List<X509Bundle> x509BundleList = getListOfX509Bundles(x509SVIDResponse);
|
||||||
return Result.error(x509SvidListResult.getError());
|
X509BundleSet bundleSet = X509BundleSet.of(x509BundleList);
|
||||||
|
return new X509Context(x509SvidList, bundleSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<List<X509Bundle>, String> x509BundleListResult = getListOfX509Bundles(x509SVIDResponse);
|
private static List<X509Bundle> getListOfX509Bundles(Workload.X509SVIDResponse x509SVIDResponse) throws CertificateException {
|
||||||
if (x509BundleListResult.isError()) {
|
|
||||||
return Result.error(x509BundleListResult.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
X509BundleSet bundleSet = X509BundleSet.of(x509BundleListResult.getValue());
|
|
||||||
X509Context result = new X509Context(x509SvidListResult.getValue(), bundleSet);
|
|
||||||
return Result.ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Result<List<X509Bundle>, String> getListOfX509Bundles(Workload.X509SVIDResponse x509SVIDResponse) {
|
|
||||||
List<X509Bundle> x509BundleList = new ArrayList<>();
|
List<X509Bundle> x509BundleList = new ArrayList<>();
|
||||||
for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) {
|
for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) {
|
||||||
Result<SpiffeId, String> spiffeId = SpiffeId.parse(x509SVID.getSpiffeId());
|
SpiffeId spiffeId = SpiffeId.parse(x509SVID.getSpiffeId());
|
||||||
if (spiffeId.isError()) {
|
|
||||||
return Result.error(spiffeId.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<X509Bundle, String> bundle = X509Bundle.parse(
|
X509Bundle bundle = X509Bundle.parse(
|
||||||
spiffeId.getValue().getTrustDomain(),
|
spiffeId.getTrustDomain(),
|
||||||
x509SVID.getBundle().toByteArray());
|
x509SVID.getBundle().toByteArray());
|
||||||
if (bundle.isError()) {
|
x509BundleList.add(bundle);
|
||||||
return Result.error(bundle.getError());
|
|
||||||
}
|
}
|
||||||
x509BundleList.add(bundle.getValue());
|
return x509BundleList;
|
||||||
}
|
|
||||||
return Result.ok(x509BundleList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result<List<X509Svid>, String> getListOfX509Svid(Workload.X509SVIDResponse x509SVIDResponse) {
|
private static List<X509Svid> getListOfX509Svid(Workload.X509SVIDResponse x509SVIDResponse) throws X509SvidException {
|
||||||
List<X509Svid> x509SvidList = new ArrayList<>();
|
List<X509Svid> x509SvidList = new ArrayList<>();
|
||||||
for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) {
|
for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) {
|
||||||
Result<X509Svid, String> svid = X509Svid.parse(
|
val svid = X509Svid.parse(
|
||||||
x509SVID.getX509Svid().toByteArray(),
|
x509SVID.getX509Svid().toByteArray(),
|
||||||
x509SVID.getX509SvidKey().toByteArray());
|
x509SVID.getX509SvidKey().toByteArray());
|
||||||
if (svid.isError()){
|
x509SvidList.add(svid);
|
||||||
return Result.error(svid.getError());
|
|
||||||
}
|
}
|
||||||
x509SvidList.add(svid.getValue());
|
return x509SvidList;
|
||||||
}
|
|
||||||
return Result.ok(x509SvidList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public class GrpcManagedChannelFactory {
|
||||||
* Return a ManagedChannel to the Spiffe Socket Endpoint provided.
|
* Return a ManagedChannel to the Spiffe Socket Endpoint provided.
|
||||||
*
|
*
|
||||||
* @param address URI representing the Workload API endpoint.
|
* @param address URI representing the Workload API endpoint.
|
||||||
* @return a instance of a ManagedChannel.
|
* @return a instance of a {@link ManagedChannel}
|
||||||
*/
|
*/
|
||||||
public static ManagedChannel newChannel(@NonNull URI address) {
|
public static ManagedChannel newChannel(@NonNull URI address) {
|
||||||
if ("unix".equals(address.getScheme())) {
|
if ("unix".equals(address.getScheme())) {
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,48 @@
|
||||||
package spiffe.bundle.x509bundle;
|
package spiffe.bundle.x509bundle;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class X509BundleTest {
|
public class X509BundleTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() throws IOException {
|
void parse_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() throws IOException {
|
||||||
byte[] bundlePem = Files.readAllBytes(Paths.get("../testdata/bundle.pem"));
|
byte[] bundlePem = Files.readAllBytes(Paths.get("../testdata/bundle.pem"));
|
||||||
TrustDomain trustDomain = TrustDomain.of("example.org").getValue();
|
TrustDomain trustDomain = TrustDomain.of("example.org");
|
||||||
|
|
||||||
Result<X509Bundle, String> x509Bundle = X509Bundle.parse(trustDomain, bundlePem);
|
X509Bundle x509Bundle = null;
|
||||||
|
try {
|
||||||
|
x509Bundle = X509Bundle.parse(trustDomain, bundlePem);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
fail("Not expected exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
assertAll(
|
assertEquals(1, x509Bundle.getX509Authorities().size());
|
||||||
() -> assertEquals(1, x509Bundle.getValue().getX509Authorities().size()),
|
assertEquals("example.org", x509Bundle.getTrustDomain().toString());
|
||||||
() -> assertEquals("example.org", x509Bundle.getValue().getTrustDomain().toString())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void load_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() {
|
void load_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() {
|
||||||
Path bundlePath = Paths.get("../testdata/bundle.pem");
|
Path bundlePath = Paths.get("../testdata/bundle.pem");
|
||||||
TrustDomain trustDomain = TrustDomain.of("example.org").getValue();
|
TrustDomain trustDomain = TrustDomain.of("example.org");
|
||||||
|
|
||||||
Result<X509Bundle, String> x509Bundle = X509Bundle.load(trustDomain, bundlePath);
|
X509Bundle x509Bundle = null;
|
||||||
|
try {
|
||||||
|
x509Bundle = X509Bundle.load(trustDomain, bundlePath);
|
||||||
|
} catch (IOException | CertificateException e) {
|
||||||
|
fail("Not expected exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
assertAll(
|
assertEquals(1, x509Bundle.getX509Authorities().size());
|
||||||
() -> assertEquals(1, x509Bundle.getValue().getX509Authorities().size()),
|
assertEquals("example.org", x509Bundle.getTrustDomain().toString());
|
||||||
() -> assertEquals("example.org", x509Bundle.getValue().getTrustDomain().toString())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,20 @@ package spiffe.internal;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class CertificateUtilsTest {
|
public class CertificateUtilsTest {
|
||||||
|
|
||||||
|
|
@ -16,23 +23,31 @@ public class CertificateUtilsTest {
|
||||||
void generateCertificates_ofPEMByteArray_returnsListWithOneX509Certificate() throws IOException {
|
void generateCertificates_ofPEMByteArray_returnsListWithOneX509Certificate() throws IOException {
|
||||||
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
||||||
|
|
||||||
val x509CertificateList = CertificateUtils.generateCertificates(certBytes);
|
List<X509Certificate> x509CertificateList = null;
|
||||||
|
SpiffeId spiffeId = null;
|
||||||
|
try {
|
||||||
|
x509CertificateList = CertificateUtils.generateCertificates(certBytes);
|
||||||
|
spiffeId = CertificateUtils.getSpiffeId(x509CertificateList.get(0));
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
fail("Not expected exception. Should have generated the certificates", e);
|
||||||
|
}
|
||||||
|
|
||||||
val spiffeId = CertificateUtils.getSpiffeId(x509CertificateList.getValue().get(0));
|
assertEquals("spiffe://example.org/test", spiffeId.toString());
|
||||||
assertEquals("spiffe://example.org/test", spiffeId.getValue().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validate_certificateThatIsExpired_ReturnsError() throws IOException {
|
void validate_certificateThatIsExpired_throwsCertificateException() throws IOException, CertificateException {
|
||||||
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert_other.pem"));
|
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert_other.pem"));
|
||||||
val bundleBytes = Files.readAllBytes(Paths.get("../testdata/bundle_other.pem"));
|
val bundleBytes = Files.readAllBytes(Paths.get("../testdata/bundle_other.pem"));
|
||||||
|
|
||||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
val chain = CertificateUtils.generateCertificates(certBytes);
|
||||||
val trustedCert = CertificateUtils.generateCertificates(bundleBytes);
|
val trustedCert = CertificateUtils.generateCertificates(bundleBytes);
|
||||||
|
|
||||||
val result = CertificateUtils.validate(chain.getValue(), trustedCert.getValue());
|
try {
|
||||||
|
CertificateUtils.validate(chain, trustedCert);
|
||||||
assertTrue(result.isError());
|
fail("Expected exception");
|
||||||
assertTrue(result.getError().contains("Error validating certificate chain: validity check failed"));
|
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertPathValidatorException e) {
|
||||||
|
assertEquals("validity check failed", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package spiffe.spiffeid;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import spiffe.result.Error;
|
|
||||||
import spiffe.result.Ok;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
|
@ -11,56 +9,56 @@ public class SpiffeIdTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_TrustDomainAndPathSegments_ReturnsSpiffeIdWithTrustDomainAndPathWithSegments() {
|
void of_TrustDomainAndPathSegments_ReturnsSpiffeIdWithTrustDomainAndPathWithSegments() {
|
||||||
val trustDomain = TrustDomain.of("trust-domain.org").getValue();
|
val trustDomain = TrustDomain.of("trust-domain.org");
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.of(trustDomain, "path1", "path2");
|
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
|
||||||
|
|
||||||
assertAll("spiffeId",
|
assertAll("spiffeId",
|
||||||
() -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()),
|
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
|
||||||
() -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath())
|
() -> assertEquals("/path1/path2", spiffeId.getPath())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_BlankPaths_ReturnsSpiffeIdWithTrustDomainAndPathWithSegments() {
|
void of_TrustDomainAndNoPaths_ReturnsSpiffeIdWithTrustDomain() {
|
||||||
val trustDomain = TrustDomain.of("trust-domain.org").getValue();
|
val trustDomain = TrustDomain.of("trust-domain.org");
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.of(trustDomain, "", "");
|
val spiffeId = SpiffeId.of(trustDomain);
|
||||||
|
|
||||||
assertAll("spiffeId",
|
assertAll("spiffeId",
|
||||||
() -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()),
|
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
|
||||||
() -> assertEquals("", spiffeIdResult.getValue().getPath())
|
() -> assertEquals("", spiffeId.getPath())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_TrustDomainAndPathsWithCaps_ReturnsSpiffeIdNormalized() {
|
void of_TrustDomainAndPathsWithCaps_ReturnsSpiffeIdNormalized() {
|
||||||
val trustDomain = TrustDomain.of("TRuST-DoMAIN.Org").getValue();
|
val trustDomain = TrustDomain.of("TRuST-DoMAIN.Org");
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.of(trustDomain, "PATH1", "paTH2");
|
val spiffeId = SpiffeId.of(trustDomain, "PATH1", "paTH2");
|
||||||
|
|
||||||
assertAll("normalized spiffeId",
|
assertAll("normalized spiffeId",
|
||||||
() -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()),
|
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
|
||||||
() -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath())
|
() -> assertEquals("/path1/path2", spiffeId.getPath())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_TrustDomainAndPathWithLeadingAndTrailingBlanks_ReturnsSpiffeIdNormalized() {
|
void of_TrustDomainAndPathWithLeadingAndTrailingBlanks_ReturnsSpiffeIdNormalized() {
|
||||||
val trustDomain = TrustDomain.of(" trust-domain.org ").getValue();
|
val trustDomain = TrustDomain.of(" trust-domain.org ");
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.of(trustDomain, " path1 ", " path2 ");
|
val spiffeId = SpiffeId.of(trustDomain, " path1 ", " path2 ");
|
||||||
|
|
||||||
assertAll("normalized spiffeId",
|
assertAll("normalized spiffeId",
|
||||||
() -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()),
|
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
|
||||||
() -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath())
|
() -> assertEquals("/path1/path2", spiffeId.getPath())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toString_SpiffeId_ReturnsTheSpiffeIdInAStringFormatIncludingTheSchema() {
|
void toString_SpiffeId_ReturnsTheSpiffeIdInAStringFormatIncludingTheSchema() {
|
||||||
val trustDomain = TrustDomain.of("trust-domain.org").getValue();
|
val trustDomain = TrustDomain.of("trust-domain.org");
|
||||||
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2", "path3").getValue();
|
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2", "path3");
|
||||||
|
|
||||||
val spiffeIdToString = spiffeId.toString();
|
val spiffeIdToString = spiffeId.toString();
|
||||||
|
|
||||||
|
|
@ -69,20 +67,20 @@ public class SpiffeIdTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void memberOf_aTrustDomainAndASpiffeIdWithSameTrustDomain_ReturnTrue() {
|
void memberOf_aTrustDomainAndASpiffeIdWithSameTrustDomain_ReturnTrue() {
|
||||||
val trustDomain = TrustDomain.of("trust-domain.org").getValue();
|
val trustDomain = TrustDomain.of("trust-domain.org");
|
||||||
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2").getValue();
|
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
|
||||||
|
|
||||||
val isMemberOf = spiffeId.memberOf(TrustDomain.of("trust-domain.org").getValue());
|
val isMemberOf = spiffeId.memberOf(TrustDomain.of("trust-domain.org"));
|
||||||
|
|
||||||
assertTrue(isMemberOf);
|
assertTrue(isMemberOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void memberOf_aTrustDomainAndASpiffeIdWithDifferentTrustDomain_ReturnFalse() {
|
void memberOf_aTrustDomainAndASpiffeIdWithDifferentTrustDomain_ReturnFalse() {
|
||||||
val trustDomain = TrustDomain.of("trust-domain.org").getValue();
|
val trustDomain = TrustDomain.of("trust-domain.org");
|
||||||
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2").getValue();
|
val spiffeId = SpiffeId.of(trustDomain, "path1", "path2");
|
||||||
|
|
||||||
val isMemberOf = spiffeId.memberOf(TrustDomain.of("other-domain.org").getValue());
|
val isMemberOf = spiffeId.memberOf(TrustDomain.of("other-domain.org"));
|
||||||
|
|
||||||
assertFalse(isMemberOf);
|
assertFalse(isMemberOf);
|
||||||
}
|
}
|
||||||
|
|
@ -91,69 +89,67 @@ public class SpiffeIdTest {
|
||||||
void parse_aString_ReturnsASpiffeIdThatHasTrustDomainAndPathSegments() {
|
void parse_aString_ReturnsASpiffeIdThatHasTrustDomainAndPathSegments() {
|
||||||
val spiffeIdAsString = "spiffe://trust-domain.org/path1/path2";
|
val spiffeIdAsString = "spiffe://trust-domain.org/path1/path2";
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.parse(spiffeIdAsString);
|
val spiffeId = SpiffeId.parse(spiffeIdAsString);
|
||||||
|
|
||||||
assertAll("SpiffeId",
|
assertAll("SpiffeId",
|
||||||
() -> assertEquals(Ok.class, spiffeIdResult.getClass()),
|
() -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()),
|
||||||
() -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()),
|
() -> assertEquals("/path1/path2", spiffeId.getPath())
|
||||||
() -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_aStringContainingInvalidSchema_ReturnsError() {
|
void parse_aStringContainingInvalidSchema_throwsIllegalArgumentException() {
|
||||||
val invalidadSpiffeId = "siffe://trust-domain.org/path1/path2";
|
val invalidadSpiffeId = "siffe://trust-domain.org/path1/path2";
|
||||||
|
|
||||||
val spiffeIdResult = SpiffeId.parse(invalidadSpiffeId);
|
try {
|
||||||
|
SpiffeId.parse(invalidadSpiffeId);
|
||||||
assertAll("Error",
|
fail("Should have thrown IllegalArgumentException");
|
||||||
() -> assertEquals(Error.class, spiffeIdResult.getClass()),
|
} catch (IllegalArgumentException e) {
|
||||||
() -> assertEquals("Invalid SPIFFE schema", spiffeIdResult.getError())
|
assertEquals("Invalid SPIFFE schema", e.getMessage());
|
||||||
);
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_aBlankString_ReturnsAError() {
|
void parse_aBlankString_throwsIllegalArgumentException() {
|
||||||
val spiffeIdAsString = "";
|
try {
|
||||||
|
SpiffeId.parse("");
|
||||||
val spiffeIdResult = SpiffeId.parse(spiffeIdAsString);
|
fail("Should have thrown IllegalArgumentException");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
assertAll("Error",
|
assertEquals("SPIFFE ID cannot be empty", e.getMessage());
|
||||||
() -> assertEquals(Error.class, spiffeIdResult.getClass()),
|
}
|
||||||
() -> assertEquals("SPIFFE ID cannot be empty", spiffeIdResult.getError())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_nullTrustDomain_returnsAError() {
|
void of_nullTrustDomain_throwsIllegalArgumentException() {
|
||||||
val spiffeIdResult = SpiffeId.of(null, "path");
|
try {
|
||||||
|
SpiffeId.of(null, "path");
|
||||||
assertEquals(Error.class, spiffeIdResult.getClass());
|
fail("Should have thrown IllegalArgumentException");
|
||||||
assertEquals("Trust Domain cannot be null", spiffeIdResult.getError());
|
} catch (NullPointerException e) {
|
||||||
|
assertEquals("trustDomain is marked non-null but is null", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void equals_twoSpiffeIdsWithSameTrustDomainAndPath_returnsTrue() {
|
void equals_twoSpiffeIdsWithSameTrustDomainAndPath_returnsTrue() {
|
||||||
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue();
|
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org"), "path1");
|
||||||
val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue();
|
val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org"), "path1");
|
||||||
|
|
||||||
assertEquals(spiffeId1, spiffeId2);
|
assertEquals(spiffeId1, spiffeId2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void equals_twoSpiffeIdsWithSameTrustDomainAndDifferentPath_returnsFalse() {
|
void equals_twoSpiffeIdsWithSameTrustDomainAndDifferentPath_returnsFalse() {
|
||||||
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue();
|
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org"), "path1");
|
||||||
val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "other").getValue();
|
val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org"), "other");
|
||||||
|
|
||||||
assertNotEquals(spiffeId1, spiffeId2);
|
assertNotEquals(spiffeId1, spiffeId2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void equals_twoSpiffeIdsWithDifferentTrustDomainAndSamePath_returnsFalse() {
|
void equals_twoSpiffeIdsWithDifferentTrustDomainAndSamePath_returnsFalse() {
|
||||||
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue();
|
val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org"), "path1");
|
||||||
val spiffeId2 = SpiffeId.of(TrustDomain.of("other.org").getValue(), "path1").getValue();
|
val spiffeId2 = SpiffeId.of(TrustDomain.of("other.org"), "path1");
|
||||||
|
|
||||||
assertNotEquals(spiffeId1, spiffeId2);
|
assertNotEquals(spiffeId1, spiffeId2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,86 +2,85 @@ package spiffe.spiffeid;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import spiffe.result.Error;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
|
||||||
|
|
||||||
public class TrustDomainTest {
|
public class TrustDomainTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_givenAString_returnTrustDomain() {
|
void of_givenAString_returnTrustDomain() {
|
||||||
val trustDomainResult = TrustDomain.of("domain.test");
|
val trustDomain = TrustDomain.of("domain.test");
|
||||||
assertEquals("domain.test", trustDomainResult.getValue().toString());
|
assertEquals("domain.test", trustDomain.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_givenASpiffeIdString_returnTrustDomainWithHostPart() {
|
void of_givenASpiffeIdString_returnTrustDomainWithHostPart() {
|
||||||
val trustDomainResult = TrustDomain.of("spiffe://domain.test");
|
val trustDomain = TrustDomain.of("spiffe://domain.test");
|
||||||
assertEquals("domain.test", trustDomainResult.getValue().toString());
|
assertEquals("domain.test", trustDomain.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_givenASpiffeIdStringWithPath_returnTrustDomainWithHostPart() {
|
void of_givenASpiffeIdStringWithPath_returnTrustDomainWithHostPart() {
|
||||||
val trustDomainResult = TrustDomain.of("spiffe://domain.test/workload");
|
val trustDomain = TrustDomain.of("spiffe://domain.test/workload");
|
||||||
assertEquals("domain.test", trustDomainResult.getValue().toString());
|
assertEquals("domain.test", trustDomain.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_givenAStringWithCaps_returnNormalizedTrustDomain() {
|
void of_givenAStringWithCaps_returnNormalizedTrustDomain() {
|
||||||
val trustDomainResult = TrustDomain.of("DoMAin.TesT");
|
val trustDomain = TrustDomain.of("DoMAin.TesT");
|
||||||
|
|
||||||
assertEquals("domain.test", trustDomainResult.getValue().toString());
|
assertEquals("domain.test", trustDomain.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_givenAStringWithTrailingAndLeadingBlanks_returnNormalizedTrustDomain() {
|
void of_givenAStringWithTrailingAndLeadingBlanks_returnNormalizedTrustDomain() {
|
||||||
val trustDomainResult = TrustDomain.of(" domain.test ");
|
val trustDomain = TrustDomain.of(" domain.test ");
|
||||||
|
|
||||||
assertEquals("domain.test", trustDomainResult.getValue().toString());
|
assertEquals("domain.test", trustDomain.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_nullString_ThrowsIllegalArgumentException() {
|
void of_nullString_ThrowsIllegalArgumentException() {
|
||||||
val trustDomainResult = TrustDomain.of(null);
|
try {
|
||||||
|
TrustDomain.of(null);
|
||||||
assertAll(
|
} catch (NullPointerException e) {
|
||||||
() -> assertEquals(Error.class, trustDomainResult.getClass()),
|
assertEquals("trustDomain is marked non-null but is null", e.getMessage());
|
||||||
() -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError())
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_emptyString_ThrowsIllegalArgumentException() {
|
void of_emptyString_ThrowsIllegalArgumentException() {
|
||||||
val trustDomainResult = TrustDomain.of("");
|
try {
|
||||||
assertAll(
|
TrustDomain.of("");
|
||||||
() -> assertEquals(Error.class, trustDomainResult.getClass()),
|
} catch (IllegalArgumentException e) {
|
||||||
() -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError())
|
assertEquals("Trust Domain cannot be empty", e.getMessage());
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void of_blankString_ThrowsIllegalArgumentException() {
|
void of_blankString_ThrowsIllegalArgumentException() {
|
||||||
val trustDomainResult = TrustDomain.of(" ");
|
try {
|
||||||
assertAll(
|
TrustDomain.of(" ");
|
||||||
() -> assertEquals(Error.class, trustDomainResult.getClass()),
|
} catch (IllegalArgumentException e) {
|
||||||
() -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError())
|
assertEquals("Trust Domain cannot be empty", e.getMessage());
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void equals_twoTrustDomainObjectsWithTheSameString_returnsTrue() {
|
void equals_twoTrustDomainObjectsWithTheSameString_returnsTrue() {
|
||||||
val trustDomainResult1 = TrustDomain.of("example.org");
|
val trustDomain1 = TrustDomain.of("example.org");
|
||||||
val trustDomainResult2 = TrustDomain.of("example.org");
|
val trustDomain2 = TrustDomain.of("example.org");
|
||||||
|
|
||||||
assertEquals(trustDomainResult1.getValue(), trustDomainResult2.getValue());
|
assertEquals(trustDomain1, trustDomain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void equals_twoTrustDomainObjectsWithDifferentStrings_returnsFalse() {
|
void equals_twoTrustDomainObjectsWithDifferentStrings_returnsFalse() {
|
||||||
val trustDomainResult1 = TrustDomain.of("example.org");
|
val trustDomain1 = TrustDomain.of("example.org");
|
||||||
val trustDomainResult2 = TrustDomain.of("other.org");
|
val trustDomain2 = TrustDomain.of("other.org");
|
||||||
|
|
||||||
assertNotEquals(trustDomainResult1.getValue(), trustDomainResult2.getValue());
|
assertNotEquals(trustDomain1, trustDomain2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package spiffe.svid.x509svid;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
@ -12,45 +13,50 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||||
public class X509SvidTest {
|
public class X509SvidTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_GivenCertAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException {
|
void parse_GivenCertAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws X509SvidException, IOException {
|
||||||
val certPem = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
val certPem = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
||||||
val keyPem = Files.readAllBytes(Paths.get("../testdata/pkcs8key.pem"));
|
val keyPem = Files.readAllBytes(Paths.get("../testdata/pkcs8key.pem"));
|
||||||
|
|
||||||
val result = X509Svid.parse(certPem, keyPem);
|
val x509Svid = X509Svid.parse(certPem, keyPem);
|
||||||
|
|
||||||
assertAll("X509-SVID",
|
assertAll("X509-SVID",
|
||||||
() -> assertTrue(result.isOk()),
|
() -> assertEquals("spiffe://example.org/test", x509Svid.getSpiffeId().toString()),
|
||||||
() -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()),
|
() -> assertEquals(1, x509Svid.getChain().size()),
|
||||||
() -> assertEquals(1, result.getValue().getChain().size()),
|
() -> assertNotNull(x509Svid.getPrivateKey())
|
||||||
() -> assertNotNull(result.getValue().getPrivateKey())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_GivenChainOfCertsAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException {
|
void parse_GivenChainOfCertsAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException, X509SvidException {
|
||||||
val certPem = Files.readAllBytes(Paths.get("../testdata/x509chain.pem"));
|
val certPem = Files.readAllBytes(Paths.get("../testdata/x509chain.pem"));
|
||||||
val keyPem = Files.readAllBytes(Paths.get("../testdata/pkcs8key.pem"));
|
val keyPem = Files.readAllBytes(Paths.get("../testdata/pkcs8key.pem"));
|
||||||
|
|
||||||
val result = X509Svid.parse(certPem, keyPem);
|
val result = X509Svid.parse(certPem, keyPem);
|
||||||
|
|
||||||
assertAll("X509-SVID",
|
assertAll("X509-SVID",
|
||||||
() -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()),
|
() -> assertEquals("spiffe://example.org/test", result.getSpiffeId().toString()),
|
||||||
() -> assertEquals(4, result.getValue().getChain().size()),
|
() -> assertEquals(4, result.getChain().size()),
|
||||||
() -> assertNotNull(result.getValue().getPrivateKey())
|
() -> assertNotNull(result.getPrivateKey())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void load_GivenCertAndPrivateKeyPaths_ReturnsX509Svid() {
|
void load_GivenCertAndPrivateKeyPaths_ReturnsX509Svid() throws X509SvidException {
|
||||||
val certsFile = Paths.get("../testdata/x509cert.pem");
|
val certsFile = Paths.get("../testdata/x509cert.pem");
|
||||||
val privateKeyFile = Paths.get("../testdata/pkcs8key.pem");
|
val privateKeyFile = Paths.get("../testdata/pkcs8key.pem");
|
||||||
|
|
||||||
val result = X509Svid.load(certsFile, privateKeyFile);
|
X509Svid result;
|
||||||
|
try {
|
||||||
|
result = X509Svid.load(certsFile, privateKeyFile);
|
||||||
|
} catch (X509SvidException e) {
|
||||||
|
fail("Not expected exception", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
assertAll("X509-SVID",
|
assertAll("X509-SVID",
|
||||||
() -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()),
|
() -> assertEquals("spiffe://example.org/test", result.getSpiffeId().toString()),
|
||||||
() -> assertEquals(1, result.getValue().getChain().size()),
|
() -> assertEquals(1, result.getChain().size()),
|
||||||
() -> assertNotNull(result.getValue().getPrivateKey())
|
() -> assertNotNull(result.getPrivateKey())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@ import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import spiffe.bundle.x509bundle.X509Bundle;
|
import spiffe.bundle.x509bundle.X509Bundle;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -32,50 +33,55 @@ public class X509SvidValidatorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifyChain_certificateExpired_returnsError() throws IOException {
|
void verifyChain_certificateExpired_throwsCertificateException() throws IOException, CertificateException, BundleNotFoundException {
|
||||||
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
||||||
val chain = CertificateUtils.generateCertificates(certBytes).getValue();
|
val chain = CertificateUtils.generateCertificates(certBytes);
|
||||||
Result<X509Bundle, String> x509Bundle=
|
X509Bundle x509Bundle=
|
||||||
X509Bundle.load(
|
X509Bundle.load(
|
||||||
TrustDomain.of("example.org").getValue(),
|
TrustDomain.of("example.org"),
|
||||||
Paths.get("../testdata/bundle.pem")
|
Paths.get("../testdata/bundle.pem")
|
||||||
);
|
);
|
||||||
|
|
||||||
when(bundleSourceMock
|
when(bundleSourceMock
|
||||||
.getX509BundleForTrustDomain(
|
.getX509BundleForTrustDomain(
|
||||||
TrustDomain.of("example.org").getValue()))
|
TrustDomain.of("example.org")))
|
||||||
.thenReturn(x509Bundle);
|
.thenReturn(x509Bundle);
|
||||||
|
|
||||||
val result = X509SvidValidator.verifyChain(chain, bundleSourceMock);
|
try {
|
||||||
|
X509SvidValidator.verifyChain(chain, bundleSourceMock);
|
||||||
assertTrue(result.isError());
|
fail("Verify chain should have thrown validation exception");
|
||||||
assertTrue(result.getError().contains("CertificateExpiredException: NotAfter"));
|
} catch (CertificateException e) {
|
||||||
|
assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkSpiffeId_givenASpiffeIdInTheListOfAcceptedIds_returnsValid() {
|
void checkSpiffeId_givenASpiffeIdInTheListOfAcceptedIds_doesntThrowException() throws IOException, CertificateException {
|
||||||
val spiffeId1 = SpiffeId.parse("spiffe://example.org/test").getValue();
|
val spiffeId1 = SpiffeId.parse("spiffe://example.org/test");
|
||||||
val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2").getValue();
|
val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2");
|
||||||
|
|
||||||
Result<List<SpiffeId>, String> spiffeIdList = Result.ok(Arrays.asList(spiffeId1, spiffeId2));
|
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
||||||
|
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
|
||||||
|
|
||||||
val result = X509SvidValidator
|
val spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
||||||
.verifySpiffeId(SpiffeId.parse("spiffe://example.org/test").getValue(), () -> spiffeIdList);
|
|
||||||
|
|
||||||
assertTrue(result.isOk());
|
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkSpiffeId_givenASpiffeIdNotInTheListOfAcceptedIds_returnsValid() {
|
void checkSpiffeId_givenASpiffeIdNotInTheListOfAcceptedIds_throwsCertificateException() throws IOException, CertificateException {
|
||||||
val spiffeId1 = SpiffeId.parse("spiffe://example.org/other1").getValue();
|
val spiffeId1 = SpiffeId.parse("spiffe://example.org/other1");
|
||||||
val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2").getValue();
|
val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2");
|
||||||
Result<List<SpiffeId>, String> spiffeIdList = Result.ok(Arrays.asList(spiffeId1, spiffeId2));
|
List<SpiffeId> spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
||||||
|
|
||||||
val result = X509SvidValidator.verifySpiffeId(SpiffeId.parse("spiffe://example.org/test").getValue(), () -> spiffeIdList);
|
val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem"));
|
||||||
|
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
|
||||||
|
|
||||||
assertAll(
|
try {
|
||||||
() -> assertTrue(result.isError()),
|
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
|
||||||
() -> assertEquals("SPIFFE ID 'spiffe://example.org/test' is not accepted",result.getError())
|
fail("Should have thrown CertificateException");
|
||||||
);
|
} catch (CertificateException e) {
|
||||||
|
assertEquals("SPIFFE ID spiffe://example.org/test in x509Certificate is not accepted", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package spiffe.workloadapi;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
@ -14,33 +14,38 @@ public class AddressTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("provideTestAddress")
|
@MethodSource("provideTestAddress")
|
||||||
void parseAddressInvalid(String input, Result expected) {
|
void parseAddressInvalid(String input, Object expected) {
|
||||||
Result<URI, String> result = Address.parseAddress(input);
|
URI result = null;
|
||||||
|
try {
|
||||||
|
result = Address.parseAddress(input);
|
||||||
assertEquals(expected, result);
|
assertEquals(expected, result);
|
||||||
|
} catch (SocketEndpointAddressException e) {
|
||||||
|
assertEquals(expected, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stream<Arguments> provideTestAddress() {
|
static Stream<Arguments> provideTestAddress() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of("unix://foo", Result.ok(URI.create("unix://foo"))),
|
Arguments.of("unix://foo", URI.create("unix://foo")),
|
||||||
Arguments.of("\\t", Result.error("Workload endpoint socket is not a valid URI: Illegal character in path at index 0: \\t")),
|
Arguments.of("\\t", "Workload endpoint socket is not a valid URI: \\t"),
|
||||||
Arguments.of("blah", Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme")),
|
Arguments.of("blah", "Workload endpoint socket URI must have a tcp:// or unix:// scheme: blah"),
|
||||||
Arguments.of("unix:opaque", Result.error("Workload endpoint unix socket URI must not be opaque")),
|
Arguments.of("unix:opaque", "Workload endpoint unix socket URI must not be opaque: unix:opaque"),
|
||||||
Arguments.of("unix://", Result.error("Workload endpoint socket is not a valid URI: Expected authority at index 7: unix://")),
|
Arguments.of("unix://", "Workload endpoint socket is not a valid URI: unix://"),
|
||||||
Arguments.of("unix://foo?whatever", Result.error("Workload endpoint unix socket URI must not include query values")),
|
Arguments.of("unix://foo?whatever", "Workload endpoint unix socket URI must not include query values: unix://foo?whatever"),
|
||||||
Arguments.of("unix://foo#whatever", Result.error("Workload endpoint unix socket URI must not include a fragment")),
|
Arguments.of("unix://foo#whatever", "Workload endpoint unix socket URI must not include a fragment: unix://foo#whatever"),
|
||||||
Arguments.of("unix://john:doe@foo/path", Result.error("Workload endpoint unix socket URI must not include user info")),
|
Arguments.of("unix://john:doe@foo/path", "Workload endpoint unix socket URI must not include user info: unix://john:doe@foo/path"),
|
||||||
|
|
||||||
Arguments.of("tcp://1.2.3.4:5", Result.ok(URI.create("tcp://1.2.3.4:5"))),
|
Arguments.of("tcp://1.2.3.4:5", URI.create("tcp://1.2.3.4:5")),
|
||||||
Arguments.of("tcp:opaque", Result.error("Workload endpoint tcp socket URI must not be opaque")),
|
Arguments.of("tcp:opaque", "Workload endpoint tcp socket URI must not be opaque: tcp:opaque"),
|
||||||
Arguments.of("tcp://", Result.error("Workload endpoint socket is not a valid URI: Expected authority at index 6: tcp://")),
|
Arguments.of("tcp://", "Workload endpoint socket is not a valid URI: tcp://"),
|
||||||
Arguments.of("tcp://1.2.3.4:5?whatever", Result.error("Workload endpoint tcp socket URI must not include query values")),
|
Arguments.of("tcp://1.2.3.4:5?whatever", "Workload endpoint tcp socket URI must not include query values: tcp://1.2.3.4:5?whatever"),
|
||||||
Arguments.of("tcp://1.2.3.4:5#whatever", Result.error("Workload endpoint tcp socket URI must not include a fragment")),
|
Arguments.of("tcp://1.2.3.4:5#whatever", "Workload endpoint tcp socket URI must not include a fragment: tcp://1.2.3.4:5#whatever"),
|
||||||
Arguments.of("tcp://john:doe@1.2.3.4:5/path", Result.error("Workload endpoint tcp socket URI must not include user info")),
|
Arguments.of("tcp://john:doe@1.2.3.4:5/path", "Workload endpoint tcp socket URI must not include user info: tcp://john:doe@1.2.3.4:5/path"),
|
||||||
Arguments.of("tcp://1.2.3.4:5/path", Result.error("Workload endpoint tcp socket URI must not include a path")),
|
Arguments.of("tcp://1.2.3.4:5/path", "Workload endpoint tcp socket URI must not include a path: tcp://1.2.3.4:5/path"),
|
||||||
Arguments.of("tcp://foo", Result.error("Workload endpoint tcp socket URI host component must be an IP:port")),
|
Arguments.of("tcp://foo", "Workload endpoint tcp socket URI host component must be an IP:port: tcp://foo"),
|
||||||
Arguments.of("tcp://1.2.3.4", Result.error("Workload endpoint tcp socket URI host component must include a port")),
|
Arguments.of("tcp://1.2.3.4", "Workload endpoint tcp socket URI host component must include a port: tcp://1.2.3.4"),
|
||||||
|
|
||||||
Arguments.of("blah://foo", Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme"))
|
Arguments.of("blah://foo", "Workload endpoint socket URI must have a tcp:// or unix:// scheme: blah://foo")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,17 +3,17 @@ package spiffe.helper;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
class BundleEntry {
|
class BundleEntry {
|
||||||
String alias;
|
String alias;
|
||||||
Certificate certificate;
|
X509Certificate certificate;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
BundleEntry(
|
BundleEntry(
|
||||||
final String alias,
|
final String alias,
|
||||||
final Certificate certificate) {
|
final X509Certificate certificate) {
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
this.certificate = certificate;
|
this.certificate = certificate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package spiffe.helper;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import spiffe.result.Result;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
@ -17,7 +16,7 @@ import java.security.cert.CertificateException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Java KeyStore, provides some functions
|
* Represents a Java KeyStore, provides some functions
|
||||||
* to store a PrivateKey, Certificate chain and Bundles.
|
* to store a private key, a X509 certificate chain, and X509 bundles.
|
||||||
* Package private, to be used by the KeyStoreHelper.
|
* Package private, to be used by the KeyStoreHelper.
|
||||||
*/
|
*/
|
||||||
class KeyStore {
|
class KeyStore {
|
||||||
|
|
@ -33,25 +32,20 @@ class KeyStore {
|
||||||
KeyStore(
|
KeyStore(
|
||||||
@NonNull final Path keyStoreFilePath,
|
@NonNull final Path keyStoreFilePath,
|
||||||
@NonNull final KeyStoreType keyStoreType,
|
@NonNull final KeyStoreType keyStoreType,
|
||||||
@NonNull final char[] keyStorePassword) {
|
@NonNull final char[] keyStorePassword) throws KeyStoreException {
|
||||||
this.keyStoreFilePath = keyStoreFilePath;
|
this.keyStoreFilePath = keyStoreFilePath;
|
||||||
this.keyStoreType = keyStoreType;
|
this.keyStoreType = keyStoreType;
|
||||||
this.keyStorePassword = keyStorePassword;
|
this.keyStorePassword = keyStorePassword;
|
||||||
setupKeyStore();
|
setupKeyStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupKeyStore() {
|
private void setupKeyStore() throws KeyStoreException {
|
||||||
this.keyStoreFile = new File(keyStoreFilePath.toUri());
|
this.keyStoreFile = new File(keyStoreFilePath.toUri());
|
||||||
|
this.keyStore = loadKeyStore(keyStoreFile);
|
||||||
val keyStore = loadKeyStore(keyStoreFile);
|
|
||||||
if (keyStore.isError()) {
|
|
||||||
throw new RuntimeException(keyStore.getError());
|
|
||||||
}
|
|
||||||
this.keyStore = keyStore.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<java.security.KeyStore, Throwable> loadKeyStore(final File keyStoreFile) {
|
private java.security.KeyStore loadKeyStore(final File keyStoreFile) throws KeyStoreException {
|
||||||
try {
|
try {
|
||||||
val keyStore = java.security.KeyStore.getInstance(keyStoreType.value());
|
val keyStore = java.security.KeyStore.getInstance(keyStoreType.value());
|
||||||
|
|
||||||
|
|
@ -62,21 +56,19 @@ class KeyStore {
|
||||||
//create new keyStore
|
//create new keyStore
|
||||||
keyStore.load(null, keyStorePassword);
|
keyStore.load(null, keyStorePassword);
|
||||||
}
|
}
|
||||||
return Result.ok(keyStore);
|
return keyStore;
|
||||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
|
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
|
||||||
return Result.error(e);
|
throw new KeyStoreException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a PrivateKey and Certificate chain in a Java KeyStore
|
* Store a private key and X509 certificate chain in a Java KeyStore
|
||||||
*
|
*
|
||||||
* @param privateKeyEntry contains the alias, privateKey, chain, privateKey password
|
* @param privateKeyEntry contains the alias, privateKey, chain, privateKey password
|
||||||
* @return Result of Boolean indicating if it was successful or an Error wrapping an Exception
|
|
||||||
*/
|
*/
|
||||||
Result<Boolean, Throwable> storePrivateKey(final PrivateKeyEntry privateKeyEntry) {
|
void storePrivateKey(final PrivateKeyEntry privateKeyEntry) throws KeyStoreException {
|
||||||
try {
|
|
||||||
// Store PrivateKey Entry in KeyStore
|
// Store PrivateKey Entry in KeyStore
|
||||||
keyStore.setKeyEntry(
|
keyStore.setKeyEntry(
|
||||||
privateKeyEntry.getAlias(),
|
privateKeyEntry.getAlias(),
|
||||||
|
|
@ -85,35 +77,27 @@ class KeyStore {
|
||||||
privateKeyEntry.getCertificateChain()
|
privateKeyEntry.getCertificateChain()
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.flush();
|
this.flush();
|
||||||
} catch (KeyStoreException e) {
|
|
||||||
return Result.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a Bundle Entry in the KeyStore
|
* Store a Bundle Entry in the KeyStore
|
||||||
*/
|
*/
|
||||||
Result<Boolean, Throwable> storeBundleEntry(BundleEntry bundleEntry) {
|
void storeBundleEntry(BundleEntry bundleEntry) throws KeyStoreException {
|
||||||
try {
|
|
||||||
// Store Bundle Entry in KeyStore
|
// Store Bundle Entry in KeyStore
|
||||||
this.keyStore.setCertificateEntry(
|
this.keyStore.setCertificateEntry(
|
||||||
bundleEntry.getAlias(),
|
bundleEntry.getAlias(),
|
||||||
bundleEntry.getCertificate()
|
bundleEntry.getCertificate()
|
||||||
);
|
);
|
||||||
return this.flush();
|
this.flush();
|
||||||
} catch (KeyStoreException e) {
|
|
||||||
return Result.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush KeyStore to disk, to the configured (@see keyStoreFilePath)
|
// Flush KeyStore to disk, to the configured (@see keyStoreFilePath)
|
||||||
private Result<Boolean, Throwable> flush() {
|
private void flush() throws KeyStoreException {
|
||||||
try {
|
try {
|
||||||
keyStore.store(new FileOutputStream(keyStoreFile), keyStorePassword);
|
keyStore.store(new FileOutputStream(keyStoreFile), keyStorePassword);
|
||||||
return Result.ok(true);
|
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
|
||||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
|
throw new KeyStoreException(e);
|
||||||
return Result.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,24 @@ package spiffe.helper;
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.java.Log;
|
import lombok.extern.java.Log;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import spiffe.result.Error;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.workloadapi.Watcher;
|
import spiffe.workloadapi.Watcher;
|
||||||
import spiffe.workloadapi.WorkloadApiClient;
|
import spiffe.workloadapi.WorkloadApiClient;
|
||||||
import spiffe.workloadapi.WorkloadApiClient.ClientOptions;
|
import spiffe.workloadapi.WorkloadApiClient.ClientOptions;
|
||||||
import spiffe.workloadapi.X509Context;
|
import spiffe.workloadapi.X509Context;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>KeyStoreHelper</code> represents a helper for storing X095-SVIDs and Bundles,
|
* A <code>KeyStoreHelper</code> represents a helper for storing X509 SVIDs and bundles,
|
||||||
* that are automatically rotated via the Worklaod API, in a Java KeyStore in a file in disk.
|
* that are automatically rotated via the Workload API, in a Java KeyStore in a file in disk.
|
||||||
*/
|
*/
|
||||||
@Log
|
@Log
|
||||||
public class KeyStoreHelper {
|
public class KeyStoreHelper {
|
||||||
|
|
@ -33,8 +32,8 @@ public class KeyStoreHelper {
|
||||||
private final String spiffeSocketPath;
|
private final String spiffeSocketPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of a KeyStoreHelper for fetching X509-SVIDs and Bundles
|
* Create an instance of a KeyStoreHelper for fetching X509 SVIDs and bundles
|
||||||
* from a Workload API and store them in a Java binary KeyStore in disk.
|
* from a Workload API and store them in a binary Java KeyStore in disk.
|
||||||
* <p>
|
* <p>
|
||||||
* It blocks until the initial update has been received from the Workload API.
|
* It blocks until the initial update has been received from the Workload API.
|
||||||
*
|
*
|
||||||
|
|
@ -48,8 +47,10 @@ public class KeyStoreHelper {
|
||||||
* @param privateKeyPassword the password to protect the key
|
* @param privateKeyPassword the password to protect the key
|
||||||
* @param privateKeyAlias the alias name
|
* @param privateKeyAlias the alias name
|
||||||
* @param spiffeSocketPath optional spiffeSocketPath, if absent uses SPIFFE_ENDPOINT_SOCKET env variable
|
* @param spiffeSocketPath optional spiffeSocketPath, if absent uses SPIFFE_ENDPOINT_SOCKET env variable
|
||||||
* @throws RuntimeException if this first update cannot be fetched.
|
*
|
||||||
* @throws RuntimeException if the KeyStore cannot be setup.
|
* @throws SocketEndpointAddressException is the socket endpoint address is not valid
|
||||||
|
* @throws KeyStoreException is the entry cannot be stored in the KeyStore
|
||||||
|
* @throws RuntimeException if there is an error fetching the certificates from the Workload API
|
||||||
*/
|
*/
|
||||||
@Builder
|
@Builder
|
||||||
public KeyStoreHelper(
|
public KeyStoreHelper(
|
||||||
|
|
@ -58,7 +59,8 @@ public class KeyStoreHelper {
|
||||||
@NonNull final char[] keyStorePassword,
|
@NonNull final char[] keyStorePassword,
|
||||||
@NonNull final char[] privateKeyPassword,
|
@NonNull final char[] privateKeyPassword,
|
||||||
@NonNull final String privateKeyAlias,
|
@NonNull final String privateKeyAlias,
|
||||||
@NonNull String spiffeSocketPath) {
|
@NonNull String spiffeSocketPath)
|
||||||
|
throws SocketEndpointAddressException, KeyStoreException {
|
||||||
|
|
||||||
|
|
||||||
this.privateKeyPassword = privateKeyPassword.clone();
|
this.privateKeyPassword = privateKeyPassword.clone();
|
||||||
|
|
@ -66,7 +68,7 @@ public class KeyStoreHelper {
|
||||||
this.spiffeSocketPath = spiffeSocketPath;
|
this.spiffeSocketPath = spiffeSocketPath;
|
||||||
|
|
||||||
this.keyStore =
|
this.keyStore =
|
||||||
spiffe.helper.KeyStore
|
KeyStore
|
||||||
.builder()
|
.builder()
|
||||||
.keyStoreFilePath(keyStoreFilePath)
|
.keyStoreFilePath(keyStoreFilePath)
|
||||||
.keyStoreType(keyStoreType)
|
.keyStoreType(keyStoreType)
|
||||||
|
|
@ -76,9 +78,8 @@ public class KeyStoreHelper {
|
||||||
setupX509ContextFetcher();
|
setupX509ContextFetcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
private void setupX509ContextFetcher() throws SocketEndpointAddressException {
|
||||||
private void setupX509ContextFetcher() {
|
WorkloadApiClient workloadApiClient;
|
||||||
Result<WorkloadApiClient, String> workloadApiClient;
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(spiffeSocketPath)) {
|
if (StringUtils.isNotBlank(spiffeSocketPath)) {
|
||||||
ClientOptions clientOptions = ClientOptions.builder().spiffeSocketPath(spiffeSocketPath).build();
|
ClientOptions clientOptions = ClientOptions.builder().spiffeSocketPath(spiffeSocketPath).build();
|
||||||
|
|
@ -88,8 +89,8 @@ public class KeyStoreHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
setX509ContextWatcher(workloadApiClient.getValue(), countDownLatch);
|
setX509ContextWatcher(workloadApiClient, countDownLatch);
|
||||||
countDownLatch.await();
|
await(countDownLatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setX509ContextWatcher(WorkloadApiClient workloadApiClient, CountDownLatch countDownLatch) {
|
private void setX509ContextWatcher(WorkloadApiClient workloadApiClient, CountDownLatch countDownLatch) {
|
||||||
|
|
@ -97,18 +98,22 @@ public class KeyStoreHelper {
|
||||||
@Override
|
@Override
|
||||||
public void OnUpdate(X509Context update) {
|
public void OnUpdate(X509Context update) {
|
||||||
log.log(Level.INFO, "Received X509Context update");
|
log.log(Level.INFO, "Received X509Context update");
|
||||||
|
try {
|
||||||
storeX509ContextUpdate(update);
|
storeX509ContextUpdate(update);
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
this.OnError(e);
|
||||||
|
}
|
||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void OnError(Error<X509Context, String> error) {
|
public void OnError(Throwable t) {
|
||||||
throw new RuntimeException(error.getError());
|
throw new RuntimeException(t);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeX509ContextUpdate(final X509Context update) {
|
private void storeX509ContextUpdate(final X509Context update) throws KeyStoreException {
|
||||||
val privateKeyEntry = PrivateKeyEntry.builder()
|
val privateKeyEntry = PrivateKeyEntry.builder()
|
||||||
.alias(privateKeyAlias)
|
.alias(privateKeyAlias)
|
||||||
.password(privateKeyPassword)
|
.password(privateKeyPassword)
|
||||||
|
|
@ -116,14 +121,19 @@ public class KeyStoreHelper {
|
||||||
.certificateChain(update.getDefaultSvid().getChainArray())
|
.certificateChain(update.getDefaultSvid().getChainArray())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
val storeKeyResult = keyStore.storePrivateKey(privateKeyEntry);
|
keyStore.storePrivateKey(privateKeyEntry);
|
||||||
if (storeKeyResult.isError()) {
|
|
||||||
throw new RuntimeException(storeKeyResult.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.log(Level.INFO, "Stored X509Context update");
|
log.log(Level.INFO, "Stored X509Context update");
|
||||||
|
|
||||||
// TODO: Store all the Bundles
|
// TODO: Store all the Bundles
|
||||||
throw new NotImplementedException("Bundle Storing is not implemented");
|
throw new NotImplementedException("Bundle Storing is not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void await(CountDownLatch countDownLatch) {
|
||||||
|
try {
|
||||||
|
countDownLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,21 @@ import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
class PrivateKeyEntry {
|
class PrivateKeyEntry {
|
||||||
String alias;
|
String alias;
|
||||||
Key privateKey;
|
Key privateKey;
|
||||||
char[] password;
|
char[] password;
|
||||||
Certificate[] certificateChain;
|
X509Certificate[] certificateChain;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
PrivateKeyEntry(
|
PrivateKeyEntry(
|
||||||
final String alias,
|
final String alias,
|
||||||
final Key privateKey,
|
final Key privateKey,
|
||||||
final char[] password,
|
final char[] password,
|
||||||
final Certificate[] certificateChain) {
|
final X509Certificate[] certificateChain) {
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import lombok.val;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
|
|
||||||
|
|
@ -20,7 +21,8 @@ import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
public class KeyStoreTest {
|
public class KeyStoreTest {
|
||||||
|
|
||||||
|
|
@ -31,12 +33,12 @@ public class KeyStoreTest {
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setup() throws X509SvidException {
|
||||||
x509Svid = X509Svid
|
x509Svid = X509Svid
|
||||||
.load(
|
.load(
|
||||||
Paths.get("../testdata/x509cert.pem"),
|
Paths.get("../testdata/x509cert.pem"),
|
||||||
Paths.get("../testdata/pkcs8key.pem")
|
Paths.get("../testdata/pkcs8key.pem")
|
||||||
).getValue();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -60,9 +62,8 @@ public class KeyStoreTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
val result = keyStore.storePrivateKey(privateKeyEntry);
|
keyStore.storePrivateKey(privateKeyEntry);
|
||||||
|
|
||||||
assertTrue(result.isOk());
|
|
||||||
checkEntryWasStored(keyStoreFilePath, keyStorePassword, privateKeyPassword, keyStoreType, DEFAULT_ALIAS);
|
checkEntryWasStored(keyStoreFilePath, keyStorePassword, privateKeyPassword, keyStoreType, DEFAULT_ALIAS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +82,7 @@ public class KeyStoreTest {
|
||||||
val privateKey = (PrivateKey) keyStore.getKey(alias, privateKeyPassword);
|
val privateKey = (PrivateKey) keyStore.getKey(alias, privateKeyPassword);
|
||||||
|
|
||||||
assertEquals(1, chain.length);
|
assertEquals(1, chain.length);
|
||||||
assertEquals("spiffe://example.org/test", spiffeId.getValue().toString());
|
assertEquals("spiffe://example.org/test", spiffeId.toString());
|
||||||
assertNotNull(privateKey);
|
assertNotNull(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,9 @@ will trust for TLS connections.
|
||||||
```
|
```
|
||||||
val sslContextOptions = SslContextOptions
|
val sslContextOptions = SslContextOptions
|
||||||
.builder()
|
.builder()
|
||||||
.x509Source(x509Source.newSource().getValue())
|
.x509Source(x509Source.newSource()())
|
||||||
.build();
|
.build();
|
||||||
Result<SSLContext, String> sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
||||||
if (sslContext.isError()) {
|
|
||||||
// handle sslContext.getError();
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [HttpsServer example](src/main/java/spiffe/provider/examples/HttpsServer.java).
|
See [HttpsServer example](src/main/java/spiffe/provider/examples/HttpsServer.java).
|
||||||
|
|
@ -33,21 +29,13 @@ Supplier of accepted SPIFFE IDs list can be provided as part of the `SslContextO
|
||||||
.spiffeSocketPath(spiffeSocket)
|
.spiffeSocketPath(spiffeSocket)
|
||||||
.build();
|
.build();
|
||||||
val x509Source = X509Source.newSource(sourceOptions);
|
val x509Source = X509Source.newSource(sourceOptions);
|
||||||
if (x509Source.isError()) {
|
|
||||||
// handle x509source.getError()
|
|
||||||
}
|
|
||||||
|
|
||||||
SslContextOptions sslContextOptions = SslContextOptions
|
SslContextOptions sslContextOptions = SslContextOptions
|
||||||
.builder()
|
.builder()
|
||||||
.acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier)
|
.acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier)
|
||||||
.x509Source(x509Source.getValue())
|
.x509Source(x509Source())
|
||||||
.build();
|
.build();
|
||||||
Result<SSLContext, String> sslContext = SpiffeSslContextFactory
|
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
||||||
.getSslContext(sslContextOptions);
|
|
||||||
|
|
||||||
if (sslContext.isError()) {
|
|
||||||
// handle sslContext.getError()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [HttpsClient example](src/main/java/spiffe/provider/examples/HttpsClient.java) that defines a Supplier for providing
|
See [HttpsClient example](src/main/java/spiffe/provider/examples/HttpsClient.java) that defines a Supplier for providing
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package spiffe.provider;
|
package spiffe.provider;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
import spiffe.svid.x509svid.X509SvidSource;
|
import spiffe.svid.x509svid.X509SvidSource;
|
||||||
|
|
||||||
|
|
@ -17,9 +16,9 @@ import java.util.Objects;
|
||||||
import static spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS;
|
import static spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>SpiffeKeyManager</code> represents a X509 KeyManager for the SPIFFE Provider.
|
* A <code>SpiffeKeyManager</code> represents a X509 key manager for the SPIFFE provider.
|
||||||
* <p>
|
* <p>
|
||||||
* Provides the chain of X509 Certificates and the Private Key.
|
* Provides the chain of X509 certificates and the private key.
|
||||||
*/
|
*/
|
||||||
public final class SpiffeKeyManager extends X509ExtendedKeyManager {
|
public final class SpiffeKeyManager extends X509ExtendedKeyManager {
|
||||||
|
|
||||||
|
|
@ -32,27 +31,24 @@ public final class SpiffeKeyManager extends X509ExtendedKeyManager {
|
||||||
/**
|
/**
|
||||||
* Returns the certificate chain associated with the given alias.
|
* Returns the certificate chain associated with the given alias.
|
||||||
*
|
*
|
||||||
* @return the X.509 SVID Certificates
|
* @return the certificate chain as an array of {@link X509Certificate}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public X509Certificate[] getCertificateChain(String alias) {
|
public X509Certificate[] getCertificateChain(String alias) {
|
||||||
if (!Objects.equals(alias, DEFAULT_ALIAS)) {
|
if (!Objects.equals(alias, DEFAULT_ALIAS)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Result<X509Svid, String> x509Svid = x509SvidSource.getX509Svid();
|
X509Svid x509Svid = x509SvidSource.getX509Svid();
|
||||||
if (x509Svid.isError()) {
|
return x509Svid.getChainArray();
|
||||||
throw new IllegalStateException(x509Svid.getError());
|
|
||||||
}
|
|
||||||
return x509Svid.getValue().getChainArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the key associated with the given alias.
|
* Returns the private key handled by this key manager.
|
||||||
*
|
*
|
||||||
* @param alias a key entry, as this KeyManager only handles one identity, i.e. one SVID,
|
* @param alias a key entry, as this KeyManager only handles one identity, i.e. one SVID,
|
||||||
* it will return the PrivateKey if the alias asked for is 'Spiffe'.
|
* it will return the PrivateKey if the given alias is 'Spiffe'.
|
||||||
*
|
*
|
||||||
* @return the Private Key
|
* @return the {@link PrivateKey} handled by this key manager
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PrivateKey getPrivateKey(String alias) {
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
|
|
@ -61,12 +57,8 @@ public final class SpiffeKeyManager extends X509ExtendedKeyManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<X509Svid, String> x509Svid = x509SvidSource.getX509Svid();
|
X509Svid x509Svid = x509SvidSource.getX509Svid();
|
||||||
if (x509Svid.isError()) {
|
return x509Svid.getPrivateKey();
|
||||||
throw new IllegalStateException(x509Svid.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509Svid.getValue().getPrivateKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,11 +96,8 @@ public final class SpiffeKeyManager extends X509ExtendedKeyManager {
|
||||||
// the ALIAS handled by the current KeyManager, if it's not supported returns null
|
// the ALIAS handled by the current KeyManager, if it's not supported returns null
|
||||||
private String getAlias(String... keyTypes) {
|
private String getAlias(String... keyTypes) {
|
||||||
val x509Svid = x509SvidSource.getX509Svid();
|
val x509Svid = x509SvidSource.getX509Svid();
|
||||||
if (x509Svid.isError()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
val privateKeyAlgorithm = x509Svid.getValue().getPrivateKey().getAlgorithm();
|
val privateKeyAlgorithm = x509Svid.getPrivateKey().getAlgorithm();
|
||||||
if (Arrays.asList(keyTypes).contains(privateKeyAlgorithm)) {
|
if (Arrays.asList(keyTypes).contains(privateKeyAlgorithm)) {
|
||||||
return DEFAULT_ALIAS;
|
return DEFAULT_ALIAS;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ public final class SpiffeKeyManagerFactory extends KeyManagerFactorySpi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method creates a KeyManager and initializes with a x509SvidSource passed as parameter.
|
* This method creates a KeyManager and initializes with the given X509 SVID source.
|
||||||
*
|
*
|
||||||
* @param x509SvidSource implementation of a {@link spiffe.bundle.x509bundle.X509BundleSource}
|
* @param x509SvidSource an instance of a {@link X509SvidSource}
|
||||||
* @return a {@link KeyManager}
|
* @return an array with an instance of a {@link KeyManager}
|
||||||
*/
|
*/
|
||||||
public KeyManager[] engineGetKeyManagers(X509SvidSource x509SvidSource) {
|
public KeyManager[] engineGetKeyManagers(X509SvidSource x509SvidSource) {
|
||||||
val spiffeKeyManager = new SpiffeKeyManager(x509SvidSource);
|
val spiffeKeyManager = new SpiffeKeyManager(x509SvidSource);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.workloadapi.X509Source;
|
import spiffe.workloadapi.X509Source;
|
||||||
|
|
||||||
|
|
@ -32,11 +30,12 @@ public final class SpiffeSslContextFactory {
|
||||||
* If the option acceptedSpiffeIdsSupplier is not provided, the list of accepted SPIFFE IDs
|
* If the option acceptedSpiffeIdsSupplier is not provided, the list of accepted SPIFFE IDs
|
||||||
* is read from the Security Property ssl.spiffe.accept.
|
* is read from the Security Property ssl.spiffe.accept.
|
||||||
* If the sslProcotol is not provided, the default TLSv1.2 is used.
|
* If the sslProcotol is not provided, the default TLSv1.2 is used.
|
||||||
*
|
* @return a {@link SSLContext}
|
||||||
* @return a Result containing a SSLContext
|
* @throws IllegalArgumentException if the X509Source is not provided in the options
|
||||||
|
* @throws NoSuchAlgorithmException at initializing the SSL context
|
||||||
|
* @throws KeyManagementException at initializing the SSL context
|
||||||
*/
|
*/
|
||||||
public static Result<SSLContext, String> getSslContext(@NonNull SslContextOptions options) {
|
public static SSLContext getSslContext(@NonNull SslContextOptions options) throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
try {
|
|
||||||
SSLContext sslContext;
|
SSLContext sslContext;
|
||||||
if (StringUtils.isNotBlank(options.sslProtocol)) {
|
if (StringUtils.isNotBlank(options.sslProtocol)) {
|
||||||
sslContext = SSLContext.getInstance(options.sslProtocol);
|
sslContext = SSLContext.getInstance(options.sslProtocol);
|
||||||
|
|
@ -45,7 +44,7 @@ public final class SpiffeSslContextFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.x509Source == null) {
|
if (options.x509Source == null) {
|
||||||
return Result.error("x509Source option cannot be null, a X509 Source must be provided");
|
throw new IllegalArgumentException("x509Source option cannot be null, a X509 Source must be provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
sslContext.init(
|
sslContext.init(
|
||||||
|
|
@ -53,10 +52,7 @@ public final class SpiffeSslContextFactory {
|
||||||
new SpiffeTrustManagerFactory().engineGetTrustManagers(options.x509Source, options.acceptedSpiffeIdsSupplier),
|
new SpiffeTrustManagerFactory().engineGetTrustManagers(options.x509Source, options.acceptedSpiffeIdsSupplier),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
return Result.ok(sslContext);
|
return sslContext;
|
||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
|
||||||
return Result.error("Error creating SSL Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,13 +62,13 @@ public final class SpiffeSslContextFactory {
|
||||||
public static class SslContextOptions {
|
public static class SslContextOptions {
|
||||||
String sslProtocol;
|
String sslProtocol;
|
||||||
X509Source x509Source;
|
X509Source x509Source;
|
||||||
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier;
|
Supplier<List<SpiffeId>> acceptedSpiffeIdsSupplier;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public SslContextOptions(
|
public SslContextOptions(
|
||||||
String sslProtocol,
|
String sslProtocol,
|
||||||
X509Source x509Source,
|
X509Source x509Source,
|
||||||
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
|
Supplier<List<SpiffeId>> acceptedSpiffeIdsSupplier) {
|
||||||
this.x509Source = x509Source;
|
this.x509Source = x509Source;
|
||||||
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
||||||
this.sslProtocol = sslProtocol;
|
this.sslProtocol = sslProtocol;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import javax.net.ssl.SSLSocketFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>SpiffeSslSocketFactory</code> is an implementation of SSLSocketFactory
|
* A <code>SpiffeSslSocketFactory</code> is an implementation of SSLSocketFactory
|
||||||
|
|
@ -17,12 +19,9 @@ public class SpiffeSslSocketFactory extends SSLSocketFactory {
|
||||||
|
|
||||||
private final SSLSocketFactory delegate;
|
private final SSLSocketFactory delegate;
|
||||||
|
|
||||||
public SpiffeSslSocketFactory(SslContextOptions contextOptions) {
|
public SpiffeSslSocketFactory(SslContextOptions contextOptions) throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
val sslContext = SpiffeSslContextFactory.getSslContext(contextOptions);
|
val sslContext = SpiffeSslContextFactory.getSslContext(contextOptions);
|
||||||
if (sslContext.isError()) {
|
delegate = sslContext.getSocketFactory();
|
||||||
throw new RuntimeException(sslContext.getError());
|
|
||||||
}
|
|
||||||
delegate = sslContext.getValue().getSocketFactory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -42,7 +41,7 @@ public class SpiffeSslSocketFactory extends SSLSocketFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createSocket(String s, int i) throws IOException {
|
public Socket createSocket(String s, int i) throws IOException {
|
||||||
return delegate.createSocket(s, i );
|
return delegate.createSocket(s, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package spiffe.provider;
|
package spiffe.provider;
|
||||||
|
|
||||||
import lombok.val;
|
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
import spiffe.internal.CertificateUtils;
|
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.svid.x509svid.X509SvidValidator;
|
import spiffe.svid.x509svid.X509SvidValidator;
|
||||||
|
|
||||||
|
|
@ -25,7 +22,7 @@ import java.util.function.Supplier;
|
||||||
public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
|
|
||||||
private final X509BundleSource x509BundleSource;
|
private final X509BundleSource x509BundleSource;
|
||||||
private final Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier;
|
private final Supplier<List<SpiffeId>> acceptedSpiffeIdsSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a SpiffeTrustManager with a X509BundleSource used to provide the trusted
|
* Creates a SpiffeTrustManager with a X509BundleSource used to provide the trusted
|
||||||
|
|
@ -35,7 +32,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
* @param acceptedSpiffeIdsSupplier a Supplier of a list of accepted SPIFFE IDs.
|
* @param acceptedSpiffeIdsSupplier a Supplier of a list of accepted SPIFFE IDs.
|
||||||
*/
|
*/
|
||||||
public SpiffeTrustManager(X509BundleSource X509BundleSource,
|
public SpiffeTrustManager(X509BundleSource X509BundleSource,
|
||||||
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
|
Supplier<List<SpiffeId>> acceptedSpiffeIdsSupplier) {
|
||||||
this.x509BundleSource = X509BundleSource;
|
this.x509BundleSource = X509BundleSource;
|
||||||
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
||||||
}
|
}
|
||||||
|
|
@ -54,10 +51,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
val result = validatePeerChain(chain);
|
validatePeerChain(chain);
|
||||||
if (result.isError()) {
|
|
||||||
throw new CertificateException(result.getError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,10 +68,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
val result = validatePeerChain(chain);
|
validatePeerChain(chain);
|
||||||
if (result.isError()) {
|
|
||||||
throw new CertificateException(result.getError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -85,7 +76,9 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
return new X509Certificate[0];
|
return new X509Certificate[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@link #checkClientTrusted(X509Certificate[], String)} */
|
/**
|
||||||
|
* {@link #checkClientTrusted(X509Certificate[], String)}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
|
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
|
||||||
checkClientTrusted(chain, authType);
|
checkClientTrusted(chain, authType);
|
||||||
|
|
@ -96,33 +89,25 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager {
|
||||||
checkServerTrusted(chain, authType);
|
checkServerTrusted(chain, authType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@link #checkClientTrusted(X509Certificate[], String)} */
|
/**
|
||||||
|
* {@link #checkClientTrusted(X509Certificate[], String)}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
|
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
|
||||||
checkClientTrusted(chain, authType);
|
checkClientTrusted(chain, authType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@link #checkServerTrusted(X509Certificate[], String)} */
|
/**
|
||||||
|
* {@link #checkServerTrusted(X509Certificate[], String)}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
|
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
|
||||||
checkServerTrusted(chain, authType);
|
checkServerTrusted(chain, authType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the spiffeId using the checkSpiffeId function and the chain using the bundleSource and a Validator
|
// Check the spiffeId using the checkSpiffeId function and the chain using the bundleSource and a Validator
|
||||||
private Result<Boolean, String> validatePeerChain(X509Certificate[] chain) {
|
private void validatePeerChain(X509Certificate[] chain) throws CertificateException {
|
||||||
val spiffeId = CertificateUtils.getSpiffeId(chain[0]);
|
X509SvidValidator.verifySpiffeId(chain[0], acceptedSpiffeIdsSupplier);
|
||||||
if (spiffeId.isError()) {
|
X509SvidValidator.verifyChain(Arrays.asList(chain), x509BundleSource);
|
||||||
return Result.error(spiffeId.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return X509SvidValidator
|
|
||||||
.verifySpiffeId(
|
|
||||||
spiffeId.getValue(),
|
|
||||||
acceptedSpiffeIdsSupplier)
|
|
||||||
.thenApply(
|
|
||||||
X509SvidValidator::verifyChain,
|
|
||||||
Arrays.asList(chain),
|
|
||||||
x509BundleSource
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package spiffe.provider;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.SpiffeIdUtils;
|
import spiffe.spiffeid.SpiffeIdUtils;
|
||||||
|
|
||||||
|
|
@ -77,9 +76,9 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi {
|
||||||
*/
|
*/
|
||||||
public TrustManager[] engineGetTrustManagers(
|
public TrustManager[] engineGetTrustManagers(
|
||||||
X509BundleSource x509BundleSource,
|
X509BundleSource x509BundleSource,
|
||||||
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
|
Supplier<List<SpiffeId>> acceptedSpiffeIdsSupplier) {
|
||||||
|
|
||||||
Supplier<Result<List<SpiffeId>, String>> spiffeIdsSupplier;
|
Supplier<List<SpiffeId>> spiffeIdsSupplier;
|
||||||
if (acceptedSpiffeIdsSupplier != null) {
|
if (acceptedSpiffeIdsSupplier != null) {
|
||||||
spiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
spiffeIdsSupplier = acceptedSpiffeIdsSupplier;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -104,7 +103,7 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<List<SpiffeId>, String> getAcceptedSpiffeIds() {
|
private List<SpiffeId> getAcceptedSpiffeIds() {
|
||||||
return SpiffeIdUtils.getSpiffeIdsFromSecurityProperty(SSL_SPIFFE_ACCEPT_PROPERTY);
|
return SpiffeIdUtils.getSpiffeIdsFromSecurityProperty(SSL_SPIFFE_ACCEPT_PROPERTY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
package spiffe.provider;
|
package spiffe.provider;
|
||||||
|
|
||||||
import lombok.val;
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
|
import spiffe.exception.X509SourceException;
|
||||||
import spiffe.workloadapi.X509Source;
|
import spiffe.workloadapi.X509Source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code>X509SourceManager</code> is a Singleton that handles an instance of a X509Source.
|
* A <code>X509SourceManager</code> is a Singleton that handles an instance of a X509Source.
|
||||||
* Uses the environment variable 'SPIFFE_ENDPOINT_SOCKET' to create a X509Source backed by the
|
* <p>
|
||||||
|
* The default SPIFFE socket enpoint address is used to create a X509Source backed by the
|
||||||
* Workload API.
|
* Workload API.
|
||||||
* If the environment variable is not defined, it will throw an <code>IllegalStateException</code>.
|
* 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>.
|
* If the X509Source cannot be initialized, it will throw a <code>RuntimeException</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* @implNote The reason to have this Singleton is because we need to have
|
* @implNote This Singleton needed to be able to handle a single {@link X509Source} instance
|
||||||
* a single X509Source instance to be used by the {@link SpiffeKeyManagerFactory}
|
* to be used by the {@link SpiffeKeyManagerFactory} and {@link SpiffeTrustManagerFactory} to inject it
|
||||||
* and {@link SpiffeTrustManagerFactory} to inject it in the {@link SpiffeKeyManager} and {@link SpiffeTrustManager}
|
* in the {@link SpiffeKeyManager} and {@link SpiffeTrustManager} instances.
|
||||||
* instances.
|
|
||||||
*/
|
*/
|
||||||
public enum X509SourceManager {
|
public enum X509SourceManager {
|
||||||
|
|
||||||
|
|
@ -22,15 +23,11 @@ public enum X509SourceManager {
|
||||||
private final X509Source x509Source;
|
private final X509Source x509Source;
|
||||||
|
|
||||||
X509SourceManager() {
|
X509SourceManager() {
|
||||||
val x509SourceResult =
|
try {
|
||||||
X509Source.newSource();
|
x509Source = X509Source.newSource();
|
||||||
if (x509SourceResult.isError()) {
|
} catch (SocketEndpointAddressException e) {
|
||||||
// panic in case of error creating the X509Source
|
throw new X509SourceException("Could not create X509 Source. Socket endpoint address is not valid", e);
|
||||||
throw new RuntimeException(x509SourceResult.getError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the singleton instance
|
|
||||||
x509Source = x509SourceResult.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public X509Source getX509Source() {
|
public X509Source getX509Source() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package spiffe.provider.examples;
|
package spiffe.provider.examples;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
import spiffe.provider.SpiffeSslContextFactory;
|
import spiffe.provider.SpiffeSslContextFactory;
|
||||||
import spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
import spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.workloadapi.X509Source;
|
import spiffe.workloadapi.X509Source;
|
||||||
import spiffe.workloadapi.X509Source.X509SourceOptions;
|
import spiffe.workloadapi.X509Source.X509SourceOptions;
|
||||||
|
|
@ -15,6 +15,8 @@ import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -32,62 +34,55 @@ import java.util.stream.Stream;
|
||||||
public class HttpsClient {
|
public class HttpsClient {
|
||||||
|
|
||||||
String spiffeSocket;
|
String spiffeSocket;
|
||||||
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsListSupplier;
|
Supplier<List<SpiffeId>> acceptedSpiffeIdsListSupplier;
|
||||||
int serverPort;
|
int serverPort;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) {
|
||||||
String spiffeSocket = "unix:/tmp/agent.sock";
|
String spiffeSocket = "unix:/tmp/agent.sock";
|
||||||
HttpsClient httpsClient =
|
HttpsClient httpsClient = new HttpsClient(4000, spiffeSocket, HttpsClient::listOfSpiffeIds);
|
||||||
new HttpsClient(4000, spiffeSocket, HttpsClient::listOfSpiffeIds);
|
try {
|
||||||
httpsClient.run();
|
httpsClient.run();
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException | IOException | SocketEndpointAddressException e) {
|
||||||
|
throw new RuntimeException("Error starting Https Client", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpsClient(int serverPort, String spiffeSocket, Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsListSupplier) {
|
HttpsClient(int serverPort, String spiffeSocket, Supplier<List<SpiffeId>> acceptedSpiffeIdsListSupplier) {
|
||||||
this.serverPort = serverPort;
|
this.serverPort = serverPort;
|
||||||
this.spiffeSocket = spiffeSocket;
|
this.spiffeSocket = spiffeSocket;
|
||||||
this.acceptedSpiffeIdsListSupplier = acceptedSpiffeIdsListSupplier;
|
this.acceptedSpiffeIdsListSupplier = acceptedSpiffeIdsListSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() throws IOException {
|
void run() throws IOException, SocketEndpointAddressException, KeyManagementException, NoSuchAlgorithmException {
|
||||||
|
|
||||||
val sourceOptions = X509SourceOptions
|
val sourceOptions = X509SourceOptions
|
||||||
.builder()
|
.builder()
|
||||||
.spiffeSocketPath(spiffeSocket)
|
.spiffeSocketPath(spiffeSocket)
|
||||||
.build();
|
.build();
|
||||||
val x509Source = X509Source.newSource(sourceOptions);
|
val x509Source = X509Source.newSource(sourceOptions);
|
||||||
if (x509Source.isError()) {
|
|
||||||
throw new RuntimeException(x509Source.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
SslContextOptions sslContextOptions = SslContextOptions
|
SslContextOptions sslContextOptions = SslContextOptions
|
||||||
.builder()
|
.builder()
|
||||||
.acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier)
|
.acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier)
|
||||||
.x509Source(x509Source.getValue())
|
.x509Source(x509Source)
|
||||||
.build();
|
.build();
|
||||||
Result<SSLContext, String> sslContext = SpiffeSslContextFactory
|
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
||||||
.getSslContext(sslContextOptions);
|
|
||||||
|
|
||||||
if (sslContext.isError()) {
|
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||||
throw new RuntimeException(sslContext.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
SSLSocketFactory sslSocketFactory = sslContext.getValue().getSocketFactory();
|
|
||||||
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", serverPort);
|
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", serverPort);
|
||||||
|
|
||||||
new WorkloadThread(sslSocket, x509Source.getValue()).start();
|
new WorkloadThread(sslSocket, x509Source).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<List<SpiffeId>, String> listOfSpiffeIds() {
|
static List<SpiffeId> listOfSpiffeIds() {
|
||||||
try {
|
try {
|
||||||
Path path = Paths.get("java-spiffe-provider/src/main/java/spiffe/provider/examples/spiffeIds.txt");
|
Path path = Paths.get("java-spiffe-provider/src/main/java/spiffe/provider/examples/spiffeIds.txt");
|
||||||
Stream<String> lines = Files.lines(path);
|
Stream<String> lines = Files.lines(path);
|
||||||
List<SpiffeId> list = lines
|
return lines
|
||||||
.map(SpiffeId::parse)
|
.map(SpiffeId::parse)
|
||||||
.map(Result::getValue)
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.ok(list);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error("Error getting list of accepted SPIFFE IDs: %s", e.getMessage());
|
throw new RuntimeException("Error getting list of spiffeIds", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package spiffe.provider.examples;
|
package spiffe.provider.examples;
|
||||||
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import spiffe.exception.SocketEndpointAddressException;
|
||||||
import spiffe.provider.SpiffeSslContextFactory;
|
import spiffe.provider.SpiffeSslContextFactory;
|
||||||
import spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
import spiffe.provider.SpiffeSslContextFactory.SslContextOptions;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.workloadapi.X509Source;
|
import spiffe.workloadapi.X509Source;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
@ -11,10 +11,12 @@ import javax.net.ssl.SSLServerSocket;
|
||||||
import javax.net.ssl.SSLServerSocketFactory;
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example of a simple HTTPS Server backed by the Workload API to get the X509 Certificates
|
* Example of a simple HTTPS Server backed by the Workload API to get the X509 certificates
|
||||||
* and trusted cert bundles.
|
* and trusted bundles.
|
||||||
* <p>
|
* <p>
|
||||||
* The purpose of this class is to show the use of the {@link SpiffeSslContextFactory} to create
|
* The purpose of this class is to show the use of the {@link SpiffeSslContextFactory} to create
|
||||||
* a {@link SSLContext} that uses X509-SVID provided by a Workload API. The SSLContext uses the
|
* a {@link SSLContext} that uses X509-SVID provided by a Workload API. The SSLContext uses the
|
||||||
|
|
@ -28,38 +30,41 @@ public class HttpsServer {
|
||||||
|
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) {
|
||||||
HttpsServer httpsServer = new HttpsServer(4000);
|
HttpsServer httpsServer = new HttpsServer(4000);
|
||||||
|
try {
|
||||||
httpsServer.run();
|
httpsServer.run();
|
||||||
|
} catch (IOException | KeyManagementException | NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("Error starting HttpsServer");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpsServer(int port ) {
|
HttpsServer(int port ) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() throws IOException {
|
void run() throws IOException, KeyManagementException, NoSuchAlgorithmException {
|
||||||
val x509Source = X509Source.newSource();
|
X509Source x509Source = null;
|
||||||
if (x509Source.isError()) {
|
try {
|
||||||
throw new RuntimeException(x509Source.getError());
|
x509Source = X509Source.newSource();
|
||||||
|
} catch (SocketEndpointAddressException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
val sslContextOptions = SslContextOptions
|
val sslContextOptions = SslContextOptions
|
||||||
.builder()
|
.builder()
|
||||||
.x509Source(x509Source.getValue())
|
.x509Source(x509Source)
|
||||||
.build();
|
.build();
|
||||||
Result<SSLContext, String> sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
|
||||||
if (sslContext.isError()) {
|
|
||||||
throw new RuntimeException(sslContext.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
SSLServerSocketFactory sslServerSocketFactory = sslContext.getValue().getServerSocketFactory();
|
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
|
||||||
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
|
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);
|
||||||
|
|
||||||
// Server will validate Client chain and SPIFFE ID
|
// Server will validate Client chain and SPIFFE ID
|
||||||
sslServerSocket.setNeedClientAuth(true);
|
sslServerSocket.setNeedClientAuth(true);
|
||||||
|
|
||||||
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
|
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
|
||||||
new WorkloadThread(sslSocket, x509Source.getValue()).start();
|
new WorkloadThread(sslSocket, x509Source).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@ class WorkloadThread extends Thread {
|
||||||
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
|
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
|
||||||
|
|
||||||
SpiffeId peerSpiffeId = CertificateUtils
|
SpiffeId peerSpiffeId = CertificateUtils
|
||||||
.getSpiffeId((X509Certificate) sslSession.getPeerCertificates()[0]).getValue();
|
.getSpiffeId((X509Certificate) sslSession.getPeerCertificates()[0]);
|
||||||
|
|
||||||
SpiffeId mySpiffeId = CertificateUtils
|
SpiffeId mySpiffeId = CertificateUtils
|
||||||
.getSpiffeId((X509Certificate) sslSession.getLocalCertificates()[0]).getValue();
|
.getSpiffeId((X509Certificate) sslSession.getLocalCertificates()[0]);
|
||||||
|
|
||||||
// Send message to peer
|
// Send message to peer
|
||||||
printWriter.printf("Hello %s, I'm %s", peerSpiffeId, mySpiffeId);
|
printWriter.printf("Hello %s, I'm %s", peerSpiffeId, mySpiffeId);
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.internal.CertificateUtils;
|
import spiffe.internal.CertificateUtils;
|
||||||
import spiffe.result.Result;
|
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
import spiffe.svid.x509svid.X509SvidSource;
|
import spiffe.svid.x509svid.X509SvidSource;
|
||||||
|
|
||||||
import javax.net.ssl.X509KeyManager;
|
import javax.net.ssl.X509KeyManager;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
@ -26,32 +27,31 @@ public class SpiffeKeyManagerTest {
|
||||||
X509Svid x509Svid;
|
X509Svid x509Svid;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setup() throws X509SvidException {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
keyManager = (X509KeyManager) new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource)[0];
|
keyManager = (X509KeyManager) new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource)[0];
|
||||||
x509Svid = X509Svid
|
x509Svid = X509Svid
|
||||||
.load(
|
.load(
|
||||||
Paths.get("../testdata/x509cert.pem"),
|
Paths.get("../testdata/x509cert.pem"),
|
||||||
Paths.get("../testdata/pkcs8key.pem"))
|
Paths.get("../testdata/pkcs8key.pem"));
|
||||||
.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCertificateChain_returnsAnArrayOfX509Certificates() {
|
void getCertificateChain_returnsAnArrayOfX509Certificates() throws CertificateException {
|
||||||
when(x509SvidSource.getX509Svid()).thenReturn(Result.ok(x509Svid));
|
when(x509SvidSource.getX509Svid()).thenReturn(x509Svid);
|
||||||
|
|
||||||
val certificateChain = keyManager.getCertificateChain(DEFAULT_ALIAS);
|
val certificateChain = keyManager.getCertificateChain(DEFAULT_ALIAS);
|
||||||
val spiffeId = CertificateUtils.getSpiffeId(certificateChain[0]);
|
val spiffeId = CertificateUtils.getSpiffeId(certificateChain[0]);
|
||||||
|
|
||||||
assertAll(
|
assertAll(
|
||||||
() -> assertEquals(1, certificateChain.length),
|
() -> assertEquals(1, certificateChain.length),
|
||||||
() -> assertEquals("spiffe://example.org/test", spiffeId.getValue().toString())
|
() -> assertEquals("spiffe://example.org/test", spiffeId.toString())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getPrivateKey_aliasIsSpiffe_returnAPrivateKey() {
|
void getPrivateKey_aliasIsSpiffe_returnAPrivateKey() {
|
||||||
when(x509SvidSource.getX509Svid()).thenReturn(Result.ok(x509Svid));
|
when(x509SvidSource.getX509Svid()).thenReturn(x509Svid);
|
||||||
|
|
||||||
val privateKey = keyManager.getPrivateKey(DEFAULT_ALIAS);
|
val privateKey = keyManager.getPrivateKey(DEFAULT_ALIAS);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import spiffe.bundle.x509bundle.X509Bundle;
|
import spiffe.bundle.x509bundle.X509Bundle;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
import spiffe.bundle.x509bundle.X509BundleSource;
|
||||||
import spiffe.result.Result;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
|
import spiffe.exception.X509SvidException;
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
import spiffe.svid.x509svid.X509Svid;
|
import spiffe.svid.x509svid.X509Svid;
|
||||||
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
@ -35,22 +37,19 @@ public class SpiffeTrustManagerTest {
|
||||||
X509TrustManager trustManager;
|
X509TrustManager trustManager;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void setupClass() {
|
static void setupClass() throws IOException, CertificateException, X509SvidException {
|
||||||
x509Svid = X509Svid
|
x509Svid = X509Svid
|
||||||
.load(
|
.load(
|
||||||
Paths.get("../testdata/x509cert.pem"),
|
Paths.get("../testdata/x509cert.pem"),
|
||||||
Paths.get("../testdata/pkcs8key.pem"))
|
Paths.get("../testdata/pkcs8key.pem"));
|
||||||
.getValue();
|
|
||||||
otherX509Svid = X509Svid
|
otherX509Svid = X509Svid
|
||||||
.load(
|
.load(
|
||||||
Paths.get("../testdata/x509cert_other.pem"),
|
Paths.get("../testdata/x509cert_other.pem"),
|
||||||
Paths.get("../testdata/key_other.pem"))
|
Paths.get("../testdata/key_other.pem"));
|
||||||
.getValue();
|
|
||||||
x509Bundle = X509Bundle
|
x509Bundle = X509Bundle
|
||||||
.load(
|
.load(
|
||||||
TrustDomain.of("example.org").getValue(),
|
TrustDomain.of("example.org"),
|
||||||
Paths.get("../testdata/bundle.pem"))
|
Paths.get("../testdata/bundle.pem"));
|
||||||
.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|
@ -60,63 +59,61 @@ public class SpiffeTrustManagerTest {
|
||||||
new SpiffeTrustManagerFactory()
|
new SpiffeTrustManagerFactory()
|
||||||
.engineGetTrustManagers(
|
.engineGetTrustManagers(
|
||||||
bundleSource,
|
bundleSource,
|
||||||
() -> Result.ok(acceptedSpiffeIds))[0];
|
() -> acceptedSpiffeIds)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkClientTrusted_passAExpiredCertificate_throwsException() {
|
void checkClientTrusted_passAExpiredCertificate_throwsException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://example.org/test").getValue()
|
SpiffeId.parse("spiffe://example.org/test")
|
||||||
);
|
);
|
||||||
|
|
||||||
val chain = x509Svid.getChainArray();
|
val chain = x509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org").getValue())).thenReturn(Result.ok(x509Bundle));
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkClientTrusted(chain, "");
|
trustManager.checkClientTrusted(chain, "");
|
||||||
fail("CertificateException was expected");
|
fail("CertificateException was expected");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertTrue(e.getMessage().contains("CertificateExpiredException: NotAfter"));
|
assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkClientTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() {
|
void checkClientTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://example.org/other").getValue()
|
SpiffeId.parse("spiffe://example.org/other")
|
||||||
);
|
);
|
||||||
|
|
||||||
X509Certificate[] chain = x509Svid.getChainArray();
|
X509Certificate[] chain = x509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org")))
|
||||||
.getX509BundleForTrustDomain(
|
.thenReturn(x509Bundle);
|
||||||
TrustDomain.of("example.org").getValue()))
|
|
||||||
.thenReturn(Result.ok(x509Bundle));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkClientTrusted(chain, "");
|
trustManager.checkClientTrusted(chain, "");
|
||||||
fail("CertificateException was expected");
|
fail("CertificateException was expected");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertEquals("SPIFFE ID 'spiffe://example.org/test' is not accepted", e.getMessage());
|
assertEquals("SPIFFE ID spiffe://example.org/test in x509Certificate is not accepted", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkClientTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() {
|
void checkClientTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://other.org/test").getValue()
|
SpiffeId.parse("spiffe://other.org/test")
|
||||||
);
|
);
|
||||||
|
|
||||||
val chain = otherX509Svid.getChainArray();
|
val chain = otherX509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("other.org").getValue())).thenReturn(Result.ok(x509Bundle));
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("other.org"))).thenReturn(x509Bundle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkClientTrusted(chain, "");
|
trustManager.checkClientTrusted(chain, "");
|
||||||
|
|
@ -127,56 +124,56 @@ public class SpiffeTrustManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkServerTrusted_passAnExpiredCertificate_ThrowsException() {
|
void checkServerTrusted_passAnExpiredCertificate_ThrowsException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://example.org/test").getValue()
|
SpiffeId.parse("spiffe://example.org/test")
|
||||||
);
|
);
|
||||||
|
|
||||||
val chain = x509Svid.getChainArray();
|
val chain = x509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org").getValue())).thenReturn(Result.ok(x509Bundle));
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkServerTrusted(chain, "");
|
trustManager.checkServerTrusted(chain, "");
|
||||||
fail("CertificateException was expected");
|
fail("CertificateException was expected");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertTrue(e.getMessage().contains("CertificateExpiredException: NotAfter"));
|
assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkServerTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() {
|
void checkServerTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://example.org/other").getValue()
|
SpiffeId.parse("spiffe://example.org/other")
|
||||||
);
|
);
|
||||||
|
|
||||||
val chain = x509Svid.getChainArray();
|
val chain = x509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org").getValue())).thenReturn(Result.ok(x509Bundle));
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("example.org"))).thenReturn(x509Bundle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkServerTrusted(chain, "");
|
trustManager.checkServerTrusted(chain, "");
|
||||||
fail("CertificateException was expected");
|
fail("CertificateException was expected");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertEquals("SPIFFE ID 'spiffe://example.org/test' is not accepted", e.getMessage());
|
assertEquals("SPIFFE ID spiffe://example.org/test in x509Certificate is not accepted", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkServerTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() {
|
void checkServerTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException {
|
||||||
acceptedSpiffeIds =
|
acceptedSpiffeIds =
|
||||||
Collections
|
Collections
|
||||||
.singletonList(
|
.singletonList(
|
||||||
SpiffeId.parse("spiffe://other.org/test").getValue()
|
SpiffeId.parse("spiffe://other.org/test")
|
||||||
);
|
);
|
||||||
|
|
||||||
val chain = otherX509Svid.getChainArray();
|
val chain = otherX509Svid.getChainArray();
|
||||||
|
|
||||||
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("other.org").getValue())).thenReturn(Result.ok(x509Bundle));
|
when(bundleSource.getX509BundleForTrustDomain(TrustDomain.of("other.org"))).thenReturn(x509Bundle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trustManager.checkServerTrusted(chain, "");
|
trustManager.checkServerTrusted(chain, "");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue