diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundle.java b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundle.java index a34dd4e..f390c97 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundle.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundle.java @@ -3,7 +3,7 @@ package spiffe.bundle.jwtbundle; import lombok.NonNull; import lombok.Value; import org.apache.commons.lang3.NotImplementedException; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; import spiffe.spiffeid.TrustDomain; import java.nio.file.Path; @@ -29,9 +29,9 @@ public class JwtBundle implements JwtBundleSource { /** * Creates a new bundle from JWT public keys. * - * @param trustDomain a TrustDomain to associate to the JwtBundle - * @param jwtKeys a Map of Public Keys - * @return a new JwtBundle. + * @param trustDomain a {@link TrustDomain} to associate to the JwtBundle + * @param jwtKeys a Map of public Keys + * @return a new {@link JwtBundle}. */ public static JwtBundle fromJWTKeys(@NonNull TrustDomain trustDomain, Map jwtKeys) { throw new NotImplementedException("Not implemented"); @@ -40,11 +40,11 @@ public class JwtBundle implements JwtBundleSource { /** * Loads a bundle from a file on disk. * - * @param trustDomain a TrustDomain to associate to the JwtBundle. - * @param bundlePath a path to a file containing the JwtBundle. - * @return a Result.ok(jwtBundle), or a Result.error(errorMessage) + * @param trustDomain a {@link TrustDomain} to associate to the JWT bundle. + * @param bundlePath a path to a file containing the JWT bundle. + * @return a instance of a {@link JwtBundle} */ - public static Result load( + public static JwtBundle load( @NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) { throw new NotImplementedException("Not implemented"); @@ -53,29 +53,30 @@ public class JwtBundle implements JwtBundleSource { /** * Parses a bundle from a byte array. * - * @param trustDomain a TrustDomain - * @param bundleBytes an array of bytes representing the bundle. - * @return + * @param trustDomain a {@link TrustDomain} + * @param bundleBytes an array of bytes representing the JWT bundle. + * @return an instance of a {@link JwtBundle} */ - public static Result parse( + public static JwtBundle parse( @NonNull final TrustDomain trustDomain, @NonNull final byte[] bundleBytes) { 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 - * @return a {@link spiffe.result.Ok} containing the JwtBundle for the TrustDomain, or - * an {@link spiffe.result.Error} if there is no bundle for the TrustDomain + * @param trustDomain a {@link TrustDomain} + * @return a {@link JwtBundle} for the trust domain + * + * @throws BundleNotFoundException if there is no bundle for the given trust domain */ @Override - public Result getJwtBundleForTrustDomain(TrustDomain trustDomain) { + public JwtBundle getJwtBundleForTrustDomain(TrustDomain trustDomain) throws BundleNotFoundException { 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,9 +85,9 @@ public class JwtBundle implements JwtBundleSource { * it returns an Optional.empty(). * * @param keyId the Key ID - * @return an {@link Optional} containing a PublicKey. + * @return an {@link Optional} containing a {@link PublicKey}. */ - public Optional findJwtKey(String keyId) { + public Optional findJwtKey(String keyId) { throw new NotImplementedException("Not implemented"); } diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSet.java b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSet.java index ab38538..2fd830a 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSet.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSet.java @@ -3,15 +3,14 @@ package spiffe.bundle.jwtbundle; import lombok.NonNull; import lombok.Value; import org.apache.commons.lang3.NotImplementedException; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; import spiffe.spiffeid.TrustDomain; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** - * A JwtBundleSet represents a set of X509Bundles keyed by TrustDomain. + * A JwtBundleSet represents a set of JWT bundles keyed by trust domain. */ @Value public class JwtBundleSet implements JwtBundleSource { @@ -22,29 +21,33 @@ public class JwtBundleSet implements JwtBundleSource { 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 bundles) { throw new NotImplementedException("Not implemented"); } - public static JwtBundleSet of(@NonNull final TrustDomain trustDomain, - @NonNull final JwtBundle jwtBundle) { - throw new NotImplementedException("Not implemented"); - } - - public List getJwtBundles() { - return new ArrayList<>(bundles.values()); - } - + /** + * Gets the JWT bundle associated to a trust domain. + * + * @param trustDomain an instance of a {@link TrustDomain} + * @return a {@link JwtBundle} associated to the given trust domain + * @throws BundleNotFoundException if no bundle could be found for the given trust domain + */ @Override - public Result getJwtBundleForTrustDomain(final TrustDomain trustDomain) { + public JwtBundle getJwtBundleForTrustDomain(final TrustDomain trustDomain) throws BundleNotFoundException { 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. * * @param jwtBundle an instance of a JwtBundle. diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSource.java b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSource.java index 2cc5b9b..2110062 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSource.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/jwtbundle/JwtBundleSource.java @@ -1,20 +1,21 @@ package spiffe.bundle.jwtbundle; import lombok.NonNull; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; import spiffe.spiffeid.TrustDomain; /** - * A JwtBundleSource represents a source of JWT-Bundles. + * A JwtBundleSource represents a source of JWT bundles. */ 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 - * @return a {@link spiffe.result.Ok} containing a {@link JwtBundle}, or a {@link spiffe.result.Error} if - * no bundle is found for the given trust domain. + * @param trustDomain an instance of a {@link TrustDomain} + * @return the {@link JwtBundle} for the given trust domain + * + * @throws BundleNotFoundException if no bundle is found for the given trust domain. */ - Result getJwtBundleForTrustDomain(@NonNull final TrustDomain trustDomain); + JwtBundle getJwtBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException; } diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509Bundle.java b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509Bundle.java index 084aa63..c1b6dde 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509Bundle.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509Bundle.java @@ -3,13 +3,14 @@ package spiffe.bundle.x509bundle; import lombok.NonNull; import lombok.Value; import lombok.val; +import spiffe.exception.BundleNotFoundException; import spiffe.internal.CertificateUtils; -import spiffe.result.Result; import spiffe.spiffeid.TrustDomain; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashSet; 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 bundlePath a Path to the file that has the X509 Authorities - * @return an instance of X509Bundle with the X509 Authorities - * associated to the TrustDomain. + * @param trustDomain a {@link TrustDomain} to associate to the bundle + * @param bundlePath a path to the file that has the X509 authorities + * @return an instance of {@link X509Bundle} with the X509 authorities + * associated to the trust domain. + * + * @throws IOException in case of failure accessing the given bundle path + * @throws CertificateException if the bundle cannot be parsed */ - public static Result load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) { - try { - val bundleBytes = Files.readAllBytes(bundlePath); - - val x509Certificates = CertificateUtils.generateCertificates(bundleBytes); - if (x509Certificates.isError()) { - return Result.error(x509Certificates.getError()); - } - - 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()); - } + public static X509Bundle load(@NonNull final TrustDomain trustDomain, @NonNull final Path bundlePath) throws IOException, CertificateException { + val bundleBytes = Files.readAllBytes(bundlePath); + val x509Certificates = CertificateUtils.generateCertificates(bundleBytes); + val x509CertificateSet = new HashSet<>(x509Certificates); + return new X509Bundle(trustDomain, x509CertificateSet); } /** - * 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 bundleBytes an array of bytes that represents the X509 Authorities - * @return an instance of X509Bundle with the X509 Authorities - * associated to the TrustDomain. + * @param trustDomain a {@link TrustDomain} to associate to the X509 bundle + * @param bundleBytes an array of bytes that represents the X509 authorities + * + * @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 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); - if (x509Certificates.isError()) { - return Result.error(x509Certificates.getError()); - } - - val x509CertificateSet = new HashSet<>(x509Certificates.getValue()); - val x509Bundle = new X509Bundle(trustDomain, x509CertificateSet); - return Result.ok(x509Bundle); + val x509CertificateSet = new HashSet<>(x509Certificates); + return new X509Bundle(trustDomain, x509CertificateSet); } + /** + * 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 - public Result getX509BundleForTrustDomain(TrustDomain trustDomain) { + public X509Bundle getX509BundleForTrustDomain(TrustDomain trustDomain) throws BundleNotFoundException { 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)); } } diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSet.java b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSet.java index 48443fe..c365be8 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSet.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSet.java @@ -2,15 +2,14 @@ package spiffe.bundle.x509bundle; import lombok.NonNull; import lombok.Value; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; import spiffe.spiffeid.TrustDomain; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** - * A X509BundleSet represents a set of X509 Bundles keyed by TrustDomain. + * A X509BundleSet represents a set of X509 bundles keyed by trust domain. */ @Value public class X509BundleSet implements X509BundleSource { @@ -22,10 +21,10 @@ public class X509BundleSet implements X509BundleSource { } /** - * Creates a new X509BundleSet 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 - * @return a X509BundleSet initialized with the list of bundles + * @param bundles a list of {@link X509Bundle} + * @return a {@link X509BundleSet} initialized with the list of bundles */ public static X509BundleSet of(@NonNull final List bundles) { ConcurrentHashMap bundleMap = new ConcurrentHashMap<>(); @@ -36,46 +35,27 @@ public class X509BundleSet implements X509BundleSource { } /** - * Creates a new X509BundleSet initialized with the x509Bundle. - */ - public static X509BundleSet of(@NonNull final TrustDomain trustDomain, @NonNull final X509Bundle x509Bundle) { - ConcurrentHashMap bundleMap = new ConcurrentHashMap<>(); - bundleMap.put(trustDomain, x509Bundle); - return new X509BundleSet(bundleMap); - } - - /** - * Adds a bundle to set, if the trustDomain already exists - * replace the bundle. + * Adds a bundle to this Set, if the trust domain already exists, + * replaces the bundle. * - * @param x509Bundle a X509Bundle. + * @param x509Bundle a {@link X509Bundle} */ public void add(@NonNull X509Bundle 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 - */ - public List getX509Bundles() { - 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 + * @param trustDomain an instance of a {@link TrustDomain} + * @return the {@link X509Bundle} associated to the given trust domain + * @throws BundleNotFoundException if no bundle could be found for the given trust domain */ @Override - public Result getX509BundleForTrustDomain(final TrustDomain trustDomain) { + public X509Bundle getX509BundleForTrustDomain(final TrustDomain trustDomain) throws BundleNotFoundException { 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)); } } diff --git a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSource.java b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSource.java index 575b79f..5b8edd1 100644 --- a/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSource.java +++ b/java-spiffe-core/src/main/java/spiffe/bundle/x509bundle/X509BundleSource.java @@ -2,21 +2,20 @@ package spiffe.bundle.x509bundle; import lombok.NonNull; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; import spiffe.spiffeid.TrustDomain; /** - * A X509BundleSource represents a source of X509-Bundles keyed by TrustDomain. + * A X509BundleSource represents a source of X509 bundles keyed by trust domain. */ 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 - * @return a {@link spiffe.result.Ok} containing a {@link X509Bundle}, or a {@link spiffe.result.Error} if - * no bundle is found for the given trust domain. + * @param trustDomain an instance of a {@link TrustDomain} + * @return the {@link X509Bundle} for the given trust domain + * @throws BundleNotFoundException if no bundle is found for the given trust domain */ - Result getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain); - + X509Bundle getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException; } diff --git a/java-spiffe-core/src/main/java/spiffe/exception/BundleNotFoundException.java b/java-spiffe-core/src/main/java/spiffe/exception/BundleNotFoundException.java new file mode 100644 index 0000000..4403b6a --- /dev/null +++ b/java-spiffe-core/src/main/java/spiffe/exception/BundleNotFoundException.java @@ -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); + } +} diff --git a/java-spiffe-core/src/main/java/spiffe/exception/SocketEndpointAddressException.java b/java-spiffe-core/src/main/java/spiffe/exception/SocketEndpointAddressException.java new file mode 100644 index 0000000..b828062 --- /dev/null +++ b/java-spiffe-core/src/main/java/spiffe/exception/SocketEndpointAddressException.java @@ -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); + } +} diff --git a/java-spiffe-core/src/main/java/spiffe/exception/X509ContextException.java b/java-spiffe-core/src/main/java/spiffe/exception/X509ContextException.java new file mode 100644 index 0000000..c01d232 --- /dev/null +++ b/java-spiffe-core/src/main/java/spiffe/exception/X509ContextException.java @@ -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); + } +} diff --git a/java-spiffe-core/src/main/java/spiffe/exception/X509SourceException.java b/java-spiffe-core/src/main/java/spiffe/exception/X509SourceException.java new file mode 100644 index 0000000..da9dd32 --- /dev/null +++ b/java-spiffe-core/src/main/java/spiffe/exception/X509SourceException.java @@ -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); + } +} diff --git a/java-spiffe-core/src/main/java/spiffe/exception/X509SvidException.java b/java-spiffe-core/src/main/java/spiffe/exception/X509SvidException.java new file mode 100644 index 0000000..4afefcd --- /dev/null +++ b/java-spiffe-core/src/main/java/spiffe/exception/X509SvidException.java @@ -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); + } +} diff --git a/java-spiffe-core/src/main/java/spiffe/internal/CertificateUtils.java b/java-spiffe-core/src/main/java/spiffe/internal/CertificateUtils.java index 5444644..cef83cd 100644 --- a/java-spiffe-core/src/main/java/spiffe/internal/CertificateUtils.java +++ b/java-spiffe-core/src/main/java/spiffe/internal/CertificateUtils.java @@ -1,8 +1,7 @@ package spiffe.internal; import lombok.val; -import org.apache.commons.lang3.exception.ExceptionUtils; -import spiffe.result.Result; +import lombok.var; import spiffe.spiffeid.SpiffeId; import spiffe.spiffeid.TrustDomain; @@ -14,7 +13,6 @@ import java.security.PrivateKey; import java.security.cert.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.stream.Collectors; @@ -33,139 +31,108 @@ public class CertificateUtils { 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 - * @return a List of X509Certificate + * @param input as byte array representing a list of X509 certificates, as a DER or PEM + * @return a List of {@link X509Certificate} */ - public static Result, String> generateCertificates(byte[] input) { + public static List generateCertificates(byte[] input) throws CertificateException { val certificateFactory = getCertificateFactory(); - if (certificateFactory.isError()) { - return Result.error("Error parsing certificates: could not create certificate factory %s", certificateFactory.getError()); - } - try { - val certificates = certificateFactory - .getValue() - .generateCertificates(new ByteArrayInputStream(input)); + val certificates = certificateFactory + .generateCertificates(new ByteArrayInputStream(input)); - val x509CertificateList = certificates.stream() - .map(X509Certificate.class::cast) - .collect(Collectors.toList()); - - return Result.ok(x509CertificateList); - } catch (CertificateException e) { - return Result.error("Error parsing certificates: %s", e.getMessage()); - } + return certificates.stream() + .map(X509Certificate.class::cast) + .collect(Collectors.toList()); } /** - * 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. - * @return a Result {@link spiffe.result.Ok} containing a {@link PrivateKey} or an {@link spiffe.result.Error}. + * @param privateKeyBytes is a PEM or DER PKCS#8 private key. + * @return a instance of {@link PrivateKey} + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException */ - public static Result generatePrivateKey(byte[] privateKeyBytes) { + public static PrivateKey generatePrivateKey(byte[] privateKeyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyBytes); - Result privateKeyResult = generatePrivateKeyWithSpec(kspec); - - 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) { + PrivateKey privateKey = null; + try { + privateKey = generatePrivateKeyWithSpec(kspec); + } catch (InvalidKeySpecException e) { byte[] keyDer = toDerFormat(privateKeyBytes); - kspec= new PKCS8EncodedKeySpec(keyDer); - privateKeyResult = generatePrivateKeyWithSpec(kspec); - } - return Result.ok(privateKeyResult.getValue()); - } - - private static Result generatePrivateKeyWithSpec(PKCS8EncodedKeySpec kspec) { - try { - val keyFactory = KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM); - val privateKey = keyFactory.generatePrivate(kspec); - return Result.ok(privateKey); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - return Result.error(e); + kspec = new PKCS8EncodedKeySpec(keyDer); + 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 - * @return a Result {@link spiffe.result.Ok} if the chain can be chained to any of the trustedCerts, or - * an {@link spiffe.result.Error}. - * + * @throws CertificateException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + * @throws CertPathValidatorException */ - public static Result validate(List chain, List trustedCerts) { + public static void validate(List chain, List trustedCerts) throws CertificateException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, CertPathValidatorException { val certificateFactory = getCertificateFactory(); - if (certificateFactory.isError()) { - return Result.error(certificateFactory.getError()); - } - - try { - PKIXParameters pkixParameters = toPkixParameters(trustedCerts); - val certPath = certificateFactory.getValue().generateCertPath(chain); - 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); + val pkixParameters = toPkixParameters(trustedCerts); + val certPath = certificateFactory.generateCertPath(chain); + getCertPathValidator().validate(certPath, pkixParameters); } /** - * Extracts the SpiffeID from a SVID - X509Certificate. + * Extracts the SPIFE ID from a X509 certificate. *

* 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. * - * @param certificate a X509Certificate - * @return spiffe.result.Result with the SpiffeId - * @throws RuntimeException when the certificate subjectAlternatives names cannot be read - * @throws IllegalArgumentException when the certificate contains multiple SpiffeId. + * @param certificate a {@link X509Certificate} + * @return an instance of a {@link SpiffeId} + * @throws CertificateException if the certificate contains multiple SPIFFE IDs, or does not contain any, or + * the SAN extension cannot be decoded */ - public static Result getSpiffeId(X509Certificate certificate) { + public static SpiffeId getSpiffeId(X509Certificate certificate) throws CertificateException { val spiffeIds = getSpiffeIds(certificate); if (spiffeIds.size() > 1) { - return Result.error("Certificate contains multiple SPIFFE IDs."); + throw new CertificateException("Certificate contains multiple SPIFFE IDs"); } 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)); - if (spiffeId.isError()) { - return Result.error(spiffeId.getError()); - } - return spiffeId; + return SpiffeId.parse(spiffeIds.get(0)); } - // Extracts the trustDomain of a chain of certificates - public static Result getTrustDomain(List 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 chain) throws CertificateException { val spiffeId = getSpiffeId(chain.get(0)); - if (spiffeId.isError()) { - return Result.error(spiffeId.getError()); - } - return Result.ok(spiffeId.getValue().getTrustDomain()); + return spiffeId.getTrustDomain(); } - private static List getSpiffeIds(X509Certificate certificate) { - try { - return certificate.getSubjectAlternativeNames() - .stream() - .map(san -> (String) san.get(SAN_VALUE_INDEX)) - .filter(uri -> startsWith(uri, SPIFFE_PREFIX)) - .collect(Collectors.toList()); - } catch (CertificateParsingException e) { - return new ArrayList<>(); - } + private static List getSpiffeIds(X509Certificate certificate) throws CertificateParsingException { + return certificate.getSubjectAlternativeNames() + .stream() + .map(san -> (String) san.get(SAN_VALUE_INDEX)) + .filter(uri -> startsWith(uri, SPIFFE_PREFIX)) + .collect(Collectors.toList()); + } + + 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 @@ -187,17 +154,13 @@ public class CertificateUtils { } // Get the X509 Certificate Factory - private static Result getCertificateFactory() { - try { - return Result.ok(CertificateFactory.getInstance(X509_CERTIFICATE_TYPE)); - } catch (CertificateException e) { - return Result.error("Error creating certificate factory: %s", e.getMessage()); - } + private static CertificateFactory getCertificateFactory() throws CertificateException { + return CertificateFactory.getInstance(X509_CERTIFICATE_TYPE); } // Given a private key in PEM format, encode it as DER 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("\n", ""); val decoder = Base64.getDecoder(); diff --git a/java-spiffe-core/src/main/java/spiffe/result/Error.java b/java-spiffe-core/src/main/java/spiffe/result/Error.java deleted file mode 100644 index 954e438..0000000 --- a/java-spiffe-core/src/main/java/spiffe/result/Error.java +++ /dev/null @@ -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 the type of the value conveyed by the Result - * @param the type of the error wrapped in the Error - */ -@Value -public class Error implements Result { - - 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; - } -} diff --git a/java-spiffe-core/src/main/java/spiffe/result/Ok.java b/java-spiffe-core/src/main/java/spiffe/result/Ok.java deleted file mode 100644 index 75b111d..0000000 --- a/java-spiffe-core/src/main/java/spiffe/result/Ok.java +++ /dev/null @@ -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 the type the value wrapped in the Ok result - * @param the type of the error - */ -@Value -public class Ok implements Result { - - 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; - } -} diff --git a/java-spiffe-core/src/main/java/spiffe/result/Result.java b/java-spiffe-core/src/main/java/spiffe/result/Result.java deleted file mode 100644 index 0e1009f..0000000 --- a/java-spiffe-core/src/main/java/spiffe/result/Result.java +++ /dev/null @@ -1,67 +0,0 @@ -package spiffe.result; - -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * A Result represents the result of an operation, that can be {@link spiffe.result.Ok} or {@link spiffe.result.Error}. - * - * @param type of the value conveyed by the Result - * @param type of the error conveyed by the Result. - * - * @see Ok - * @see Error - */ -public interface Result { - - V getValue(); - - E getError(); - - boolean isOk(); - - boolean isError(); - - static Ok ok(final V value) { - return new Ok<>(value); - } - - static Error error(final E error) { - return new Error<>(error); - } - - static Result 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 Type of the parameter of the Function - * @return A Result of type V. - */ - default Result thenApply(Function> 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 Type of the parameter of the BiFunction - * @return A Result of type V. - */ - default Result thenApply(BiFunction> fn, U u, S s) { - if (this.isOk()) { - return fn.apply(u, s); - } - return this; - } -} diff --git a/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeId.java b/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeId.java index f1c9f5f..acb6353 100644 --- a/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeId.java +++ b/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeId.java @@ -1,9 +1,9 @@ package spiffe.spiffeid; +import lombok.NonNull; import lombok.Value; import lombok.val; import org.apache.commons.lang3.StringUtils; -import spiffe.result.Result; import java.net.URI; 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). * - * @param trustDomain an instance of a TrustDomain - * @param segments a list of string path segments + * @param trustDomain an instance of a {@link TrustDomain} + * @param segments a list of string path segments * - * @return a {@code Resul}, either an {@link spiffe.result.Ok} wrapping a {@link SpiffeId} - * or an {@link spiffe.result.Error} wrapping the error message. + * @return a {@link SpiffeId} */ - public static Result of(final TrustDomain trustDomain, final String... segments) { - if (trustDomain == null) { - return Result.error("Trust Domain cannot be null"); - } - + public static SpiffeId of(@NonNull final TrustDomain trustDomain, final String... segments) { val path = Arrays.stream(segments) .filter(StringUtils::isNotBlank) .map(SpiffeId::normalize) .map(s -> "/" + s) .collect(Collectors.joining()); - - return Result.ok(new SpiffeId(trustDomain, path)); + return 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 - * @return A {@link Result}, either an {@link spiffe.result.Ok} wrapping a {@link SpiffeId} - * or an {@link spiffe.result.Error} wrapping the error message. + * @param spiffeIdAsString a String representing a SPIFFE ID + * @return A {@link SpiffeId} + * @throws IllegalArgumentException if the given string cannot be parsed */ - public static Result parse(final String spiffeIdAsString) { - + public static SpiffeId parse(@NonNull final String 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())) { - return Result.error("Invalid SPIFFE schema"); - } - - val trustDomainResult = TrustDomain.of(uri.getHost()); - if (trustDomainResult.isError()) { - return Result.error(trustDomainResult.getError()); - } - - val path = uri.getPath(); - - return Result.ok(new SpiffeId(trustDomainResult.getValue(), path)); - - } catch (IllegalArgumentException e) { - return Result.error("Could not parse SPIFFE ID %s: %s", spiffeIdAsString, e.getMessage()); + if (!SPIFFE_SCHEMA.equals(uri.getScheme())) { + throw new IllegalArgumentException("Invalid SPIFFE schema"); } + + val trustDomain = TrustDomain.of(uri.getHost()); + val path = uri.getPath(); + return new SpiffeId(trustDomain, path); } /** - * Returns true if the trustDomain of the current object equals the - * trustDomain passed as parameter. + * Returns true if the trust domain of this SPIFFE ID is the same as the given trust domain. * - * @param trustDomain instance of a TrustDomain - * @return true if the trustDomain given as a parameter is the same as the trustDomain - * of the current SpiffeId object. + * @param trustDomain instance of a {@link TrustDomain} + * @return true if the given trust domain equals the trust domain of this object, false otherwise */ public boolean memberOf(final TrustDomain trustDomain) { return this.trustDomain.equals(trustDomain); diff --git a/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeIdUtils.java b/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeIdUtils.java index 01e16ff..15f894b 100644 --- a/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeIdUtils.java +++ b/java-spiffe-core/src/main/java/spiffe/spiffeid/SpiffeIdUtils.java @@ -1,14 +1,15 @@ package spiffe.spiffeid; 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.security.Security; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -20,18 +21,17 @@ public class SpiffeIdUtils { 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 - * SPIFFE IDs separated by a comma. + * @param systemProperty name of the system property that contains a list of SPIFFE IDs separated by a commas. + * @return a list of {@link SpiffeId} parsed from the values read from the security property * - * @return a {@link Result} - * {@link spiffe.result.Ok} containing a List of SpiffeId instances. If no value is found, returns an empty list. - * {@link spiffe.result.Error} in case the param systemProperty is blank. + * @throws IllegalArgumentException if the given system property is empty or if any of the SPIFFE IDs + * cannot be parsed */ - public static Result, String> getSpiffeIdsFromSystemProperty(final String systemProperty) { + public static List getSpiffeIdsFromSystemProperty(final String 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); @@ -39,54 +39,60 @@ public class SpiffeIdUtils { } /** - * Read the Accepted SPIFFE IDs from a Security Property (defined in java.security file) and parse - * them to SpiffeId instances. - *

- * @param securityProperty name of the Security Property that should contain a list of - * SPIFFE IDs separated by a comma. + * Reads the accepted SPIFFE IDs from a security Property (defined in java.security file) and parses + * them to {@link SpiffeId} instances. * - * @return a Result: - * {@link spiffe.result.Ok} containing a List of SpiffeId instances. If no value is found, returns an empty list. - * {@link spiffe.result.Error} in case the param systemProperty is blank. + * @param securityProperty name of the security property that contains a list of SPIFFE IDs separated by commas. + * @return a List of {@link SpiffeId} parsed from the values read from the given security property + * + * @throws IllegalArgumentException if the security property is empty or if any of the SPIFFE IDs + * cannot be parsed */ - public static Result, String> getSpiffeIdsFromSecurityProperty(final String securityProperty) { + public static List getSpiffeIdsFromSecurityProperty(final String 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); 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. + *

+ * The file should have one SPIFFE ID per line. * - * @param spiffeIdFile - * @param separator - * @return + * @param spiffeIdsFile the path to the file containing a list of SPIFFE IDs + * @return a List of {@link SpiffeId} parsed from the file provided + * + * @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, String> getSpiffeIdListFromFile(final Path spiffeIdFile, final char separator) { - throw new NotImplementedException("Not implemented"); + public static List getSpiffeIdListFromFile(final Path spiffeIdsFile) throws IOException { + Stream 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 - * instances of SpiffeId. + * Parses a string representing a list of SPIFFE IDs and returns a list of + * instances of {@link SpiffeId}. * * @param spiffeIds a list of SPIFFE IDs represented in a 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, String> toListOfSpiffeIds(final String spiffeIds, final char separator) { + public static List toListOfSpiffeIds(final String spiffeIds, final char separator) { 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 spiffeIdList = Arrays.stream(array) + return Arrays.stream(array) .map(SpiffeId::parse) - .map(Result::getValue) .collect(Collectors.toList()); - - return Result.ok(spiffeIdList); } } diff --git a/java-spiffe-core/src/main/java/spiffe/spiffeid/TrustDomain.java b/java-spiffe-core/src/main/java/spiffe/spiffeid/TrustDomain.java index 4ebfad9..ee4a0cf 100644 --- a/java-spiffe-core/src/main/java/spiffe/spiffeid/TrustDomain.java +++ b/java-spiffe-core/src/main/java/spiffe/spiffeid/TrustDomain.java @@ -1,10 +1,10 @@ package spiffe.spiffeid; +import lombok.NonNull; import lombok.Value; import lombok.val; import org.apache.commons.lang3.StringUtils; -import spiffe.result.Result; import java.net.URI; 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. - * @return an Ok result containing the parsed TrustDomain, or an Error if the trustDomain cannot be parsed + * @param trustDomain a trust domain represented as a string, must not be blank. + * @return an instance of a {@link TrustDomain} + * + * @throws IllegalArgumentException if the given string is blank or cannot be parsed */ - public static Result of(String trustDomain) { + public static TrustDomain of(@NonNull String trustDomain) { if (StringUtils.isBlank(trustDomain)) { - return Result.error("Trust Domain cannot be empty."); + throw new IllegalArgumentException("Trust Domain cannot be empty"); } try { val uri = new URI(normalize(trustDomain)); - val result = new TrustDomain(getHost(uri)); - return Result.ok(result); + val host = getHost(uri); + return new TrustDomain(host); } 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 - * @return a String with the Trust Domain + * Returns the trust domain as a string. + * + * @return a String with the trust domain */ @Override public String toString() { diff --git a/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvid.java b/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvid.java index 99925f7..e117af7 100644 --- a/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvid.java +++ b/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvid.java @@ -4,7 +4,6 @@ import lombok.NonNull; import lombok.Value; import org.apache.commons.lang3.NotImplementedException; import spiffe.bundle.jwtbundle.JwtBundleSource; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import java.time.LocalDateTime; @@ -47,20 +46,22 @@ public class JwtSvid { * Parses and validates a JWT-SVID token and returns the * JWT-SVID. The JWT-SVID signature is verified using the JWT bundle source. * - * @param token a token as a String - * @param jwtBundleSource an implementation of a JwtBundleSource + * @param token a token as a string + * @param jwtBundleSource an implementation of a {@link JwtBundleSource} * @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 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"); } /** * 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() { return token; diff --git a/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvidSource.java b/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvidSource.java index 660f93f..74aab83 100644 --- a/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvidSource.java +++ b/java-spiffe-core/src/main/java/spiffe/svid/jwtsvid/JwtSvidSource.java @@ -1,6 +1,5 @@ package spiffe.svid.jwtsvid; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; /** @@ -11,10 +10,12 @@ public interface JwtSvidSource { /** * 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 extraAudiences an array of Strings - * @return a JwtSvid + * @return a {@link JwtSvid} + * + * @throws //TODO: declare thrown exceptions */ - Result FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences); + JwtSvid FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences); } diff --git a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509Svid.java b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509Svid.java index e3a29ca..011752f 100644 --- a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509Svid.java +++ b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509Svid.java @@ -3,31 +3,35 @@ package spiffe.svid.x509svid; import lombok.NonNull; import lombok.Value; import lombok.val; +import spiffe.exception.X509SvidException; import spiffe.internal.CertificateUtils; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; import java.util.List; /** - * A X509Svid represents a SPIFFE X509-SVID. + * A X509Svid represents a SPIFFE X509 SVID. *

- * 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 public class X509Svid { SpiffeId spiffeId; - // The X.509 certificates of the X509-SVID. The leaf certificate is - // the X509-SVID certificate. Any remaining certificates (if any) - // chain the X509-SVID certificate back to a X509 root - // for the trust domain. + /** + * The X.509 certificates of the X509-SVID. The leaf certificate is + * the X509-SVID certificate. Any remaining certificates (if any) chain + * the X509-SVID certificate back to a X509 root for the trust domain. + */ List chain; 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 privateKeyFile path to PrivateKey file - * @return an instance of X509Svid + * @param certsFilePath path to X509 certificate chain file + * @param privateKeyFilePath path to private key file + * @return an instance of {@link X509Svid} + * + * @throws X509SvidException if there is an error parsing the given certsFilePath or the privateKeyFilePath */ - public static Result load(@NonNull Path certsFile, @NonNull Path privateKeyFile) { + public static X509Svid load(@NonNull Path certsFilePath, @NonNull Path privateKeyFilePath) throws X509SvidException { + byte[] certsBytes; + byte[] privateKeyBytes; try { - val certsBytes = Files.readAllBytes(certsFile); - byte[] privateKeyBytes = Files.readAllBytes(privateKeyFile); - return createX509Svid(certsBytes, privateKeyBytes); + certsBytes = Files.readAllBytes(certsFilePath); + privateKeyBytes = Files.readAllBytes(privateKeyFilePath); } 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); } - } - - /** - * 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. - * - * @param certsBytes chain of certificates as a byte array - * @param privateKeyBytes private key byte array - * @return a Result(Success) object containing the X509-SVID, or a Error containing the Exception cause - */ - public static Result parse(@NonNull byte[] certsBytes, @NonNull byte[] privateKeyBytes) { return createX509Svid(certsBytes, privateKeyBytes); } - /** Return the chain of certificates as an array. */ + /** + * 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. + * + * @param certsBytes chain of certificates as a byte array + * @param privateKeyBytes private key as byte array + * @return a {@link X509Svid} parsed from the given certBytes and privateKeyBytes + * + * @throws X509SvidException if the given certsBytes or privateKeyBytes cannot be parsed + */ + public static X509Svid parse(@NonNull byte[] certsBytes, @NonNull byte[] privateKeyBytes) throws X509SvidException { + return createX509Svid(certsBytes, privateKeyBytes); + } + + /** + * Return the chain of certificates as an array. + */ public X509Certificate[] getChainArray() { return chain.toArray(new X509Certificate[0]); } - private static Result createX509Svid(byte[] certsBytes, byte[] privateKeyBytes) { - val x509Certificates = CertificateUtils.generateCertificates(certsBytes); - if (x509Certificates.isError()) { - return Result.error(x509Certificates.getError()); + private static X509Svid createX509Svid(byte[] certsBytes, byte[] privateKeyBytes) throws X509SvidException { + List x509Certificates = null; + try { + x509Certificates = CertificateUtils.generateCertificates(certsBytes); + val privateKey = CertificateUtils.generatePrivateKey(privateKeyBytes); + val spiffeId = CertificateUtils.getSpiffeId(x509Certificates.get(0)); + 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 privateKey = CertificateUtils.generatePrivateKey(privateKeyBytes); - if (privateKey.isError()) { - return Result.error(privateKey.getError()); - } - - 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); } - } diff --git a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidSource.java b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidSource.java index 4ad4ebd..0b53428 100644 --- a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidSource.java +++ b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidSource.java @@ -1,10 +1,14 @@ package spiffe.svid.x509svid; -import spiffe.result.Result; - /** - * A X509SvidSource represents a source of X509-SVIDs. + * A X509SvidSource represents a source of X509 SVIDs. */ public interface X509SvidSource { - Result getX509Svid(); + + /** + * Returns the X509 SVID in the source. + * + * @return an instance of a {@link X509Svid} + */ + X509Svid getX509Svid(); } diff --git a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidValidator.java b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidValidator.java index 7bd7e40..7ff9ee3 100644 --- a/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidValidator.java +++ b/java-spiffe-core/src/main/java/spiffe/svid/x509svid/X509SvidValidator.java @@ -3,10 +3,14 @@ package spiffe.svid.x509svid; import lombok.NonNull; import lombok.val; import spiffe.bundle.x509bundle.X509BundleSource; +import spiffe.exception.BundleNotFoundException; import spiffe.internal.CertificateUtils; -import spiffe.result.Result; 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.util.ArrayList; import java.util.List; @@ -14,60 +18,48 @@ import java.util.function.Supplier; /** * A X509SvidValidator 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 { /** - * 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 x509BundleSource a {@link X509BundleSource }to provide the trusted bundle certs + * @param chain a list representing the chain of X509 certificates to be validated + * @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 - * a trusted bundle, it returns an Ok(true), otherwise returns an Error with a String message. + * @throws CertificateException is the chain cannot be verified with an authority from the X509 bundle source + * @throws NullPointerException if the given chain or 509BundleSource are null */ - public static Result verifyChain( + public static void verifyChain( @NonNull List chain, - @NonNull X509BundleSource x509BundleSource) { - val trustDomain = CertificateUtils.getTrustDomain(chain); - if (trustDomain.isError()) { - return Result.error(trustDomain.getError()); + @NonNull X509BundleSource x509BundleSource) throws CertificateException { + try { + val trustDomain = CertificateUtils.getTrustDomain(chain); + val x509Bundle = x509BundleSource.getX509BundleForTrustDomain(trustDomain); + 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 acceptedSpiffedIdsSupplier a Supplier of a List os SPIFFE IDs that are accepted + * @param x509Certificate a {@link X509Svid} with a SPIFFE ID to be verified + * @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, - * an {@link spiffe.result.Error} containing en error message if the SPIFFE ID is not in the list - * or if there's en error getting the list. + * @throws CertificateException is the SPIFFE ID in x509Certificate is not in the list supplied by acceptedSpiffedIdsSupplier, + * or if the SPIFFE ID cannot be parsed from the x509Certificate + * @throws NullPointerException if the given x509Certificate or acceptedSpiffedIdsSupplier are null */ - public static Result verifySpiffeId(SpiffeId spiffeId, Supplier, String>> acceptedSpiffedIdsSupplier) { - if (acceptedSpiffedIdsSupplier.get().isError()) { - return Result.error("Error getting list of accepted SPIFFE IDs"); - } - + public static void verifySpiffeId(@NonNull X509Certificate x509Certificate, + @NonNull Supplier> acceptedSpiffedIdsSupplier) + throws CertificateException { val spiffeIdList = acceptedSpiffedIdsSupplier.get(); - if (spiffeIdList.isError()) { - return Result.error(spiffeIdList.getError()); + val spiffeId = CertificateUtils.getSpiffeId(x509Certificate); + 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); } } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java index 09d6aec..f510293 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java @@ -1,15 +1,18 @@ package spiffe.workloadapi; +import lombok.val; import org.apache.commons.lang3.StringUtils; -import spiffe.result.Result; +import spiffe.exception.SocketEndpointAddressException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; 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 { @@ -18,88 +21,128 @@ public class Address { */ public static final String SOCKET_ENV_VARIABLE = "SPIFFE_ENDPOINT_SOCKET"; + private static final List 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() { return System.getenv(Address.SOCKET_ENV_VARIABLE); } - public static Result parseAddress(String addr) { + /** + * Parses and validates a Workload API socket address. + *

+ * The given address should either have a tcp, or a unix scheme. + *

+ * The given address should contain a path. + *

+ * The given address cannot be opaque, cannot have fragments, query values or user info. + *

+ * 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; try { - parsedAddress = new URI(addr); + parsedAddress = new URI(address); } 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) { - return Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme"); + val scheme = parsedAddress.getScheme(); + 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": { 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())) { - 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())) { - 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())) { - 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())) { - 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"; } - - return Result.ok(parsedAddress); - + break; } case "tcp": { 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())) { - 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())) { - 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())) { - 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())) { - 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())) { - 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()); - if (ip == null) { - return Result.error("Workload endpoint tcp socket URI host component must be an IP:port"); + if (StringUtils.isBlank(ip)) { + error = "Workload endpoint tcp socket URI host component must be an IP:port: %s"; + break; } int port = parsedAddress.getPort(); 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"; } - - return Result.ok(parsedAddress); + break; } } - 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) { diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/JwtSource.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/JwtSource.java index cd187f0..c36d375 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/JwtSource.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/JwtSource.java @@ -3,37 +3,35 @@ package spiffe.workloadapi; import org.apache.commons.lang3.NotImplementedException; import spiffe.bundle.jwtbundle.JwtBundle; import spiffe.bundle.jwtbundle.JwtBundleSource; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.spiffeid.TrustDomain; import spiffe.svid.jwtsvid.JwtSvid; import spiffe.svid.jwtsvid.JwtSvidSource; /** - * A JwtSource represents a source of SPIFFE JWT-SVID and JWT bundles + * A JwtSource represents a source of SPIFFE JWT SVID and JWT bundles * maintained via the Workload API. */ 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. * - * @param spiffeSocketPath a Path to the Workload API endpoint - * @return a Result containing an instance of a JwtSource, or an Error with an - * Exception. + * @param spiffeSocketPath a path to the Workload API endpoint + * @return an instance of a {@link JwtSource} */ - public static Result newSource(String spiffeSocketPath) { + public static JwtSource newSource(String spiffeSocketPath) { throw new NotImplementedException("Not implemented"); } @Override - public Result getJwtBundleForTrustDomain(TrustDomain trustDomain) { + public JwtBundle getJwtBundleForTrustDomain(TrustDomain trustDomain) { throw new NotImplementedException("Not implemented"); } @Override - public Result FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences) { + public JwtSvid FetchJwtSvid(SpiffeId subject, String audience, String... extraAudiences) { throw new NotImplementedException("Not implemented"); } } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/Watcher.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/Watcher.java index b45f855..37f5060 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/Watcher.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/Watcher.java @@ -1,7 +1,5 @@ package spiffe.workloadapi; -import spiffe.result.Error; - /** * a Watcher handles updates of type T. * @@ -11,5 +9,5 @@ public interface Watcher { void OnUpdate(final T update); - void OnError(final Error t); + void OnError(final Throwable e); } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java index 0cdb503..1914fe8 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java @@ -10,9 +10,10 @@ import lombok.extern.java.Log; import lombok.val; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; 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.svid.jwtsvid.JwtSvid; import spiffe.workloadapi.internal.GrpcConversionUtils; @@ -23,7 +24,7 @@ import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIBlocki import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIStub; import java.io.Closeable; -import java.net.URI; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -34,10 +35,7 @@ import static spiffe.workloadapi.internal.Workload.X509SVIDResponse; /** * A WorkloadApiClient represents a client to interact with the Workload API. - * Supports one-shot calls and watch updates for X509 and JWT SVIDS and Bundles. - *

- * Multiple WorkloadApiClients can be created for the same SPIFFE Socket Path, - * they will share a common ManagedChannel. + * Supports one-shot calls and watch updates for X509 and JWT SVIDS and bundles. */ @Log 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 newClient() { + public static WorkloadApiClient newClient() throws SocketEndpointAddressException { val options = ClientOptions.builder().build(); return newClient(options); } /** - * Creates a new Workload API Client. + * Creates a new Workload API client. *

- * If the SPIFFE Socket Path is not provided, it uses the Default Address from - * the Environment variable for creating the client. + * If the SPIFFE socket endpoint address is not provided in the options, it uses the default address. * * @param options {@link ClientOptions} - * @return a Result containing a WorklaodAPI client + * @return a {@link WorkloadApiClient} */ - public static Result newClient(@NonNull ClientOptions options) { - + public static WorkloadApiClient newClient(@NonNull ClientOptions options) throws SocketEndpointAddressException { String spiffeSocketPath; if (StringUtils.isNotBlank(options.spiffeSocketPath)) { spiffeSocketPath = options.spiffeSocketPath; @@ -82,13 +79,8 @@ public class WorkloadApiClient implements Closeable { spiffeSocketPath = Address.getDefaultAddress(); } - val parseResult = Address.parseAddress(spiffeSocketPath); - if (parseResult.isError()) { - return Result.error(parseResult.getError()); - } - - URI parsedAddress = parseResult.getValue(); - val managedChannel = GrpcManagedChannelFactory.newChannel(parsedAddress); + val socketEndpointAddress = Address.parseAddress(spiffeSocketPath); + val managedChannel = GrpcManagedChannelFactory.newChannel(socketEndpointAddress); val workloadAPIAsyncStub = SpiffeWorkloadAPIGrpc .newStub(managedChannel) @@ -98,64 +90,54 @@ public class WorkloadApiClient implements Closeable { .newBlockingStub(managedChannel) .withInterceptors(new SecurityHeaderInterceptor()); - val workloadApiClient = new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel); - return Result.ok(workloadApiClient); + return new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel); } /** - * 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 fetchX509Context() { + public X509Context fetchX509Context() { Context.CancellableContext cancellableContext; cancellableContext = Context.current().withCancellation(); - Result result; + X509Context result; try { result = cancellableContext.call(this::processX509Context); } 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 cancellableContext.close(); return result; } - private Result processX509Context() { - try { - Iterator 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 watcher) { StreamObserver streamObserver = new StreamObserver() { @Override public void onNext(X509SVIDResponse value) { - Result x509Context = GrpcConversionUtils.toX509Context(value); - if (x509Context.isError()) { - watcher.OnError(Result.error(x509Context.getError())); + X509Context x509Context = null; + try { + 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 public void onError(Throwable t) { - String error = String.format("Error getting X509Context update: %s %n %s", t.getMessage(), ExceptionUtils.getStackTrace(t)); - watcher.OnError(Result.error(error)); + watcher.OnError(new X509ContextException("Error getting X509Context", t)); } @Override public void onCompleted() { - watcher.OnError(Result.error("Unexpected completed stream.")); + watcher.OnError(new X509ContextException("Unexpected completed stream")); } }; Context.CancellableContext cancellableContext; @@ -170,20 +152,22 @@ public class WorkloadApiClient implements Closeable { * @param subject a SPIFFE ID * @param audience the audience of 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 fetchJwtSvid(SpiffeId subject, String audience, String... extraAudience) { + public JwtSvid fetchJwtSvid(SpiffeId subject, String audience, String... extraAudience) { throw new NotImplementedException("Not implemented"); } /** - * Fetches the JWT bundles for JWT-SVID validation, keyed - * by a SPIFFE ID of the trust domain to which they belong. + * Fetches the JWT bundles for JWT-SVID validation, keyed by trust domain. * - * @return an {@link spiffe.result.Ok} containing the JwtBundleSet. + * @return an instance of a {@link JwtBundleSet} + * @throws //TODO: declare thrown exceptions */ - public Result fetchJwtBundles() { + public JwtBundleSet fetchJwtBundles() { throw new NotImplementedException("Not implemented"); } @@ -193,14 +177,16 @@ public class WorkloadApiClient implements Closeable { * * @param token JWT token * @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 validateJwtSvid(String token, String audience) { + public JwtSvid validateJwtSvid(String token, String audience) { throw new NotImplementedException("Not implemented"); } /** - * Watches for updates to the JWT Bundles. + * Watches for JWT bundles updates. * * @param jwtBundlesWatcher receives the update for JwtBundles. */ @@ -208,10 +194,9 @@ public class WorkloadApiClient implements Closeable { 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 public void close() { 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 = 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}. */ diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Context.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Context.java index 0567b1f..cde7ced 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Context.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Context.java @@ -10,7 +10,7 @@ import java.util.List; /** * A X509Context represents the X509 materials that are fetched from the Workload API. *

- * Contains a List of {@link X509Svid} and a {@link X509BundleSet}. + * Contains a list of {@link X509Svid} and a {@link X509BundleSet}. */ @Value public class X509Context { diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java index 41823d0..f751e81 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NonNull; import lombok.extern.java.Log; import lombok.val; +import org.apache.commons.lang3.exception.ExceptionUtils; import spiffe.bundle.x509bundle.X509Bundle; import spiffe.bundle.x509bundle.X509BundleSet; import spiffe.bundle.x509bundle.X509BundleSource; -import spiffe.result.Error; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; +import spiffe.exception.SocketEndpointAddressException; +import spiffe.exception.X509SourceException; import spiffe.spiffeid.TrustDomain; import spiffe.svid.x509svid.X509Svid; import spiffe.svid.x509svid.X509SvidSource; @@ -20,13 +22,15 @@ import java.util.function.Function; import java.util.logging.Level; /** - * A X509Source represents a source of X509-SVID and X509 Bundles maintained via the + * A X509Source represents a source of X509 SVIDs and X509 bundles maintained via the * Workload API. *

* It handles a {@link X509Svid} and a {@link X509BundleSet} that are updated automatically * whenever there is an update from the Workload API. *

- * It implements the Closeable interface. The {@link #close()} method closes the source, + * Implements {@link X509SvidSource} and {@link X509BundleSource}. + *

+ * Implements the {@link Closeable} interface. The {@link #close()} method closes the source, * dropping the connection to the Workload API. Other source methods will return an error * after close has been called. */ @@ -41,94 +45,81 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { 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. *

- * 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. *

- * 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 - * case the X509Source could not be initialized. + * @return an instance of {@link X509Source}, with the svid and bundles initialized + * + * @throws SocketEndpointAddressException if the address to the Workload API is not valid + * @throws X509SourceException if the source could not be initialized */ - public static Result newSource() { + public static X509Source newSource() throws SocketEndpointAddressException { X509SourceOptions x509SourceOptions = X509SourceOptions.builder().build(); 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. *

* The {@link WorkloadApiClient} can be provided in the options, if it is not, * a new client is created. * * @param options {@link X509SourceOptions} - * @return an initialized an {@link spiffe.result.Ok} with X509Source, or an {@link Error} in - * case the X509Source could not be initialized. + * @return an instance of {@link X509Source}, with the svid and bundles initialized + * + * @throws SocketEndpointAddressException if the address to the Workload API is not valid + * @throws X509SourceException if the source could not be initialized */ - public static Result newSource(@NonNull X509SourceOptions options) { - + public static X509Source newSource(@NonNull X509SourceOptions options) throws SocketEndpointAddressException { if (options.workloadApiClient == null) { - Result workloadApiClient = createClient(options); - if (workloadApiClient.isError()) { - return Result.error(workloadApiClient.getError()); - } - options.workloadApiClient = workloadApiClient.getValue(); + options.workloadApiClient = createClient(options); } val x509Source = new X509Source(); x509Source.picker = options.picker; x509Source.workloadApiClient = options.workloadApiClient; - Result init = x509Source.init(); - if (init.isError()) { + try { + x509Source.init(); + } catch (Exception e) { x509Source.close(); - return Result.error("Error creating X509 Source: %s", init.getError()); + throw new X509SourceException("Error creating X509 source", e); } - return Result.ok(x509Source); - } - - private static Result createClient(@NonNull X509Source.@NonNull X509SourceOptions options) { - Result workloadApiClient; - val clientOptions= WorkloadApiClient.ClientOptions - .builder() - .spiffeSocketPath(options.spiffeSocketPath) - .build(); - workloadApiClient = WorkloadApiClient.newClient(clientOptions); - return workloadApiClient; - } - - private X509Source() { + return x509Source; } /** - * Returns the X509-SVID handled by this source, returns an Error in case - * the source is already closed. + * Returns the X509 SVID handled by this source. * - * @return an {@link spiffe.result.Ok} containing the {@link X509Svid} + * @return a {@link X509Svid} + * @throws IllegalStateException if the source is closed */ @Override - public Result getX509Svid() { - val checkClosed = checkClosed(); - if (checkClosed.isError()) { - return Result.error(checkClosed.getError()); + public X509Svid getX509Svid() { + if (isClosed()) { + throw new IllegalStateException("X509 SVID source is closed"); } - return Result.ok(svid); + return svid; } /** - * Returns the X509-Bundle for a given trust domain, returns an Error in case - * there is no bundle for the trust domain, or the source is already closed. + * Returns the X509 bundle for a given trust domain. * - * @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 - public Result getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) { - val checkClosed = checkClosed(); - if (checkClosed.isError()) { - return Result.error(checkClosed.getError()); + public X509Bundle getX509BundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException { + if (isClosed()) { + throw new IllegalStateException("X509 bundle source is closed"); } return bundles.getX509BundleForTrustDomain(trustDomain); } @@ -150,14 +141,18 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { } - private Result init() { - Result x509Context = workloadApiClient.fetchX509Context(); - if (x509Context.isError()) { - return Result.error(x509Context.getError()); - } - setX509Context(x509Context.getValue()); + private static WorkloadApiClient createClient(@NonNull X509Source.@NonNull X509SourceOptions options) throws SocketEndpointAddressException { + val clientOptions= WorkloadApiClient.ClientOptions + .builder() + .spiffeSocketPath(options.spiffeSocketPath) + .build(); + return WorkloadApiClient.newClient(clientOptions); + } + + private void init() { + X509Context x509Context = workloadApiClient.fetchX509Context(); + setX509Context(x509Context); setX509ContextWatcher(); - return Result.ok(true); } private void setX509ContextWatcher() { @@ -169,8 +164,8 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { } @Override - public void OnError(Error error) { - log.log(Level.SEVERE, String.format("Error in X509Context watcher: %s", error.getError())); + public void OnError(Throwable error) { + 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 checkClosed() { + private boolean isClosed() { synchronized (this) { - if (closed) { - return Result.error("source is closed"); - } - return Result.ok(true); + return closed; } } @@ -202,8 +194,21 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { */ @Data public static class X509SourceOptions { + + /** + * Address to the Workload API, if it is not set, the default address will be used. + */ 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, X509Svid> picker; + + /** + * A custom instance of a {@link WorkloadApiClient}, if it is not set, a new instance will be created + */ WorkloadApiClient workloadApiClient; @Builder diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcConversionUtils.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcConversionUtils.java index 04a6fa9..b01d19e 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcConversionUtils.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcConversionUtils.java @@ -1,63 +1,50 @@ package spiffe.workloadapi.internal; +import lombok.val; import spiffe.bundle.x509bundle.X509Bundle; import spiffe.bundle.x509bundle.X509BundleSet; -import spiffe.result.Result; +import spiffe.exception.X509SvidException; import spiffe.spiffeid.SpiffeId; import spiffe.svid.x509svid.X509Svid; import spiffe.workloadapi.X509Context; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; +/** + * Utility methods for converting GRPC objects to JAVA-SPIFFE domain objects. + */ public class GrpcConversionUtils { - public static Result toX509Context(Workload.X509SVIDResponse x509SVIDResponse) { - Result, String> x509SvidListResult = getListOfX509Svid(x509SVIDResponse); - if (x509SvidListResult.isError()) { - return Result.error(x509SvidListResult.getError()); - } - - Result, String> x509BundleListResult = getListOfX509Bundles(x509SVIDResponse); - 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); + public static X509Context toX509Context(Workload.X509SVIDResponse x509SVIDResponse) throws CertificateException, X509SvidException { + List x509SvidList = getListOfX509Svid(x509SVIDResponse); + List x509BundleList = getListOfX509Bundles(x509SVIDResponse); + X509BundleSet bundleSet = X509BundleSet.of(x509BundleList); + return new X509Context(x509SvidList, bundleSet); } - private static Result, String> getListOfX509Bundles(Workload.X509SVIDResponse x509SVIDResponse) { + private static List getListOfX509Bundles(Workload.X509SVIDResponse x509SVIDResponse) throws CertificateException { List x509BundleList = new ArrayList<>(); for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) { - Result spiffeId = SpiffeId.parse(x509SVID.getSpiffeId()); - if (spiffeId.isError()) { - return Result.error(spiffeId.getError()); - } + SpiffeId spiffeId = SpiffeId.parse(x509SVID.getSpiffeId()); - Result bundle = X509Bundle.parse( - spiffeId.getValue().getTrustDomain(), + X509Bundle bundle = X509Bundle.parse( + spiffeId.getTrustDomain(), x509SVID.getBundle().toByteArray()); - if (bundle.isError()) { - return Result.error(bundle.getError()); - } - x509BundleList.add(bundle.getValue()); + x509BundleList.add(bundle); } - return Result.ok(x509BundleList); + return x509BundleList; } - private static Result, String> getListOfX509Svid(Workload.X509SVIDResponse x509SVIDResponse) { + private static List getListOfX509Svid(Workload.X509SVIDResponse x509SVIDResponse) throws X509SvidException { List x509SvidList = new ArrayList<>(); for (Workload.X509SVID x509SVID : x509SVIDResponse.getSvidsList()) { - Result svid = X509Svid.parse( + val svid = X509Svid.parse( x509SVID.getX509Svid().toByteArray(), x509SVID.getX509SvidKey().toByteArray()); - if (svid.isError()){ - return Result.error(svid.getError()); - } - x509SvidList.add(svid.getValue()); + x509SvidList.add(svid); } - return Result.ok(x509SvidList); + return x509SvidList; } } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java index 3045991..f5ae12c 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java @@ -23,7 +23,7 @@ public class GrpcManagedChannelFactory { * Return a ManagedChannel to the Spiffe Socket Endpoint provided. * * @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) { if ("unix".equals(address.getScheme())) { diff --git a/java-spiffe-core/src/test/java/spiffe/bundle/x509bundle/X509BundleTest.java b/java-spiffe-core/src/test/java/spiffe/bundle/x509bundle/X509BundleTest.java index 78c8094..5bca5f4 100644 --- a/java-spiffe-core/src/test/java/spiffe/bundle/x509bundle/X509BundleTest.java +++ b/java-spiffe-core/src/test/java/spiffe/bundle/x509bundle/X509BundleTest.java @@ -1,42 +1,48 @@ package spiffe.bundle.x509bundle; import org.junit.jupiter.api.Test; -import spiffe.result.Result; import spiffe.spiffeid.TrustDomain; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; 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.fail; public class X509BundleTest { @Test void parse_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() throws IOException { byte[] bundlePem = Files.readAllBytes(Paths.get("../testdata/bundle.pem")); - TrustDomain trustDomain = TrustDomain.of("example.org").getValue(); + TrustDomain trustDomain = TrustDomain.of("example.org"); - Result 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.getValue().getX509Authorities().size()), - () -> assertEquals("example.org", x509Bundle.getValue().getTrustDomain().toString()) - ); + assertEquals(1, x509Bundle.getX509Authorities().size()); + assertEquals("example.org", x509Bundle.getTrustDomain().toString()); } @Test void load_bundleByteArrayInPEMFormatAndTrustDomain_returnX509Bundle() { Path bundlePath = Paths.get("../testdata/bundle.pem"); - TrustDomain trustDomain = TrustDomain.of("example.org").getValue(); + TrustDomain trustDomain = TrustDomain.of("example.org"); - Result 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.getValue().getX509Authorities().size()), - () -> assertEquals("example.org", x509Bundle.getValue().getTrustDomain().toString()) - ); + assertEquals(1, x509Bundle.getX509Authorities().size()); + assertEquals("example.org", x509Bundle.getTrustDomain().toString()); } } diff --git a/java-spiffe-core/src/test/java/spiffe/internal/CertificateUtilsTest.java b/java-spiffe-core/src/test/java/spiffe/internal/CertificateUtilsTest.java index 649e39e..c059a6f 100644 --- a/java-spiffe-core/src/test/java/spiffe/internal/CertificateUtilsTest.java +++ b/java-spiffe-core/src/test/java/spiffe/internal/CertificateUtilsTest.java @@ -2,13 +2,20 @@ package spiffe.internal; import lombok.val; import org.junit.jupiter.api.Test; +import spiffe.spiffeid.SpiffeId; import java.io.IOException; import java.nio.file.Files; 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.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class CertificateUtilsTest { @@ -16,23 +23,31 @@ public class CertificateUtilsTest { void generateCertificates_ofPEMByteArray_returnsListWithOneX509Certificate() throws IOException { val certBytes = Files.readAllBytes(Paths.get("../testdata/x509cert.pem")); - val x509CertificateList = CertificateUtils.generateCertificates(certBytes); + List 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.getValue().toString()); + assertEquals("spiffe://example.org/test", spiffeId.toString()); } @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 bundleBytes = Files.readAllBytes(Paths.get("../testdata/bundle_other.pem")); val chain = CertificateUtils.generateCertificates(certBytes); val trustedCert = CertificateUtils.generateCertificates(bundleBytes); - val result = CertificateUtils.validate(chain.getValue(), trustedCert.getValue()); - - assertTrue(result.isError()); - assertTrue(result.getError().contains("Error validating certificate chain: validity check failed")); + try { + CertificateUtils.validate(chain, trustedCert); + fail("Expected exception"); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertPathValidatorException e) { + assertEquals("validity check failed", e.getMessage()); + } } } diff --git a/java-spiffe-core/src/test/java/spiffe/spiffeid/SpiffeIdTest.java b/java-spiffe-core/src/test/java/spiffe/spiffeid/SpiffeIdTest.java index 1c3a65f..5d5a0c6 100644 --- a/java-spiffe-core/src/test/java/spiffe/spiffeid/SpiffeIdTest.java +++ b/java-spiffe-core/src/test/java/spiffe/spiffeid/SpiffeIdTest.java @@ -2,8 +2,6 @@ package spiffe.spiffeid; import lombok.val; import org.junit.jupiter.api.Test; -import spiffe.result.Error; -import spiffe.result.Ok; import static org.junit.jupiter.api.Assertions.*; @@ -11,56 +9,56 @@ public class SpiffeIdTest { @Test 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", - () -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()), - () -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath()) + () -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()), + () -> assertEquals("/path1/path2", spiffeId.getPath()) ); } @Test - void of_BlankPaths_ReturnsSpiffeIdWithTrustDomainAndPathWithSegments() { - val trustDomain = TrustDomain.of("trust-domain.org").getValue(); + void of_TrustDomainAndNoPaths_ReturnsSpiffeIdWithTrustDomain() { + val trustDomain = TrustDomain.of("trust-domain.org"); - val spiffeIdResult = SpiffeId.of(trustDomain, "", ""); + val spiffeId = SpiffeId.of(trustDomain); assertAll("spiffeId", - () -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()), - () -> assertEquals("", spiffeIdResult.getValue().getPath()) + () -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()), + () -> assertEquals("", spiffeId.getPath()) ); } @Test 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", - () -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()), - () -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath()) + () -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()), + () -> assertEquals("/path1/path2", spiffeId.getPath()) ); } @Test 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", - () -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()), - () -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath()) + () -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()), + () -> assertEquals("/path1/path2", spiffeId.getPath()) ); } @Test void toString_SpiffeId_ReturnsTheSpiffeIdInAStringFormatIncludingTheSchema() { - val trustDomain = TrustDomain.of("trust-domain.org").getValue(); - val spiffeId = SpiffeId.of(trustDomain, "path1", "path2", "path3").getValue(); + val trustDomain = TrustDomain.of("trust-domain.org"); + val spiffeId = SpiffeId.of(trustDomain, "path1", "path2", "path3"); val spiffeIdToString = spiffeId.toString(); @@ -69,20 +67,20 @@ public class SpiffeIdTest { @Test void memberOf_aTrustDomainAndASpiffeIdWithSameTrustDomain_ReturnTrue() { - val trustDomain = TrustDomain.of("trust-domain.org").getValue(); - val spiffeId = SpiffeId.of(trustDomain, "path1", "path2").getValue(); + val trustDomain = TrustDomain.of("trust-domain.org"); + 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); } @Test void memberOf_aTrustDomainAndASpiffeIdWithDifferentTrustDomain_ReturnFalse() { - val trustDomain = TrustDomain.of("trust-domain.org").getValue(); - val spiffeId = SpiffeId.of(trustDomain, "path1", "path2").getValue(); + val trustDomain = TrustDomain.of("trust-domain.org"); + 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); } @@ -91,69 +89,67 @@ public class SpiffeIdTest { void parse_aString_ReturnsASpiffeIdThatHasTrustDomainAndPathSegments() { val spiffeIdAsString = "spiffe://trust-domain.org/path1/path2"; - val spiffeIdResult = SpiffeId.parse(spiffeIdAsString); + val spiffeId = SpiffeId.parse(spiffeIdAsString); assertAll("SpiffeId", - () -> assertEquals(Ok.class, spiffeIdResult.getClass()), - () -> assertEquals("trust-domain.org", spiffeIdResult.getValue().getTrustDomain().toString()), - () -> assertEquals("/path1/path2", spiffeIdResult.getValue().getPath()) + () -> assertEquals("trust-domain.org", spiffeId.getTrustDomain().toString()), + () -> assertEquals("/path1/path2", spiffeId.getPath()) ); } @Test - void parse_aStringContainingInvalidSchema_ReturnsError() { + void parse_aStringContainingInvalidSchema_throwsIllegalArgumentException() { val invalidadSpiffeId = "siffe://trust-domain.org/path1/path2"; - val spiffeIdResult = SpiffeId.parse(invalidadSpiffeId); - - assertAll("Error", - () -> assertEquals(Error.class, spiffeIdResult.getClass()), - () -> assertEquals("Invalid SPIFFE schema", spiffeIdResult.getError()) - ); - + try { + SpiffeId.parse(invalidadSpiffeId); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid SPIFFE schema", e.getMessage()); + } } @Test - void parse_aBlankString_ReturnsAError() { - val spiffeIdAsString = ""; - - val spiffeIdResult = SpiffeId.parse(spiffeIdAsString); - - assertAll("Error", - () -> assertEquals(Error.class, spiffeIdResult.getClass()), - () -> assertEquals("SPIFFE ID cannot be empty", spiffeIdResult.getError()) - ); + void parse_aBlankString_throwsIllegalArgumentException() { + try { + SpiffeId.parse(""); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("SPIFFE ID cannot be empty", e.getMessage()); + } } @Test - void of_nullTrustDomain_returnsAError() { - val spiffeIdResult = SpiffeId.of(null, "path"); - - assertEquals(Error.class, spiffeIdResult.getClass()); - assertEquals("Trust Domain cannot be null", spiffeIdResult.getError()); + void of_nullTrustDomain_throwsIllegalArgumentException() { + try { + SpiffeId.of(null, "path"); + fail("Should have thrown IllegalArgumentException"); + } catch (NullPointerException e) { + assertEquals("trustDomain is marked non-null but is null", e.getMessage()); + } } @Test void equals_twoSpiffeIdsWithSameTrustDomainAndPath_returnsTrue() { - val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue(); - val spiffeId2 = 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"), "path1"); assertEquals(spiffeId1, spiffeId2); } @Test void equals_twoSpiffeIdsWithSameTrustDomainAndDifferentPath_returnsFalse() { - val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue(); - val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "other").getValue(); + val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org"), "path1"); + val spiffeId2 = SpiffeId.of(TrustDomain.of("example.org"), "other"); assertNotEquals(spiffeId1, spiffeId2); } @Test void equals_twoSpiffeIdsWithDifferentTrustDomainAndSamePath_returnsFalse() { - val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org").getValue(), "path1").getValue(); - val spiffeId2 = SpiffeId.of(TrustDomain.of("other.org").getValue(), "path1").getValue(); + val spiffeId1 = SpiffeId.of(TrustDomain.of("example.org"), "path1"); + val spiffeId2 = SpiffeId.of(TrustDomain.of("other.org"), "path1"); assertNotEquals(spiffeId1, spiffeId2); } diff --git a/java-spiffe-core/src/test/java/spiffe/spiffeid/TrustDomainTest.java b/java-spiffe-core/src/test/java/spiffe/spiffeid/TrustDomainTest.java index fe493bf..f18a421 100644 --- a/java-spiffe-core/src/test/java/spiffe/spiffeid/TrustDomainTest.java +++ b/java-spiffe-core/src/test/java/spiffe/spiffeid/TrustDomainTest.java @@ -2,86 +2,85 @@ package spiffe.spiffeid; import lombok.val; 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 { @Test void of_givenAString_returnTrustDomain() { - val trustDomainResult = TrustDomain.of("domain.test"); - assertEquals("domain.test", trustDomainResult.getValue().toString()); + val trustDomain = TrustDomain.of("domain.test"); + assertEquals("domain.test", trustDomain.toString()); } @Test void of_givenASpiffeIdString_returnTrustDomainWithHostPart() { - val trustDomainResult = TrustDomain.of("spiffe://domain.test"); - assertEquals("domain.test", trustDomainResult.getValue().toString()); + val trustDomain = TrustDomain.of("spiffe://domain.test"); + assertEquals("domain.test", trustDomain.toString()); } @Test void of_givenASpiffeIdStringWithPath_returnTrustDomainWithHostPart() { - val trustDomainResult = TrustDomain.of("spiffe://domain.test/workload"); - assertEquals("domain.test", trustDomainResult.getValue().toString()); + val trustDomain = TrustDomain.of("spiffe://domain.test/workload"); + assertEquals("domain.test", trustDomain.toString()); } @Test 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 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 void of_nullString_ThrowsIllegalArgumentException() { - val trustDomainResult = TrustDomain.of(null); - - assertAll( - () -> assertEquals(Error.class, trustDomainResult.getClass()), - () -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError()) - ); + try { + TrustDomain.of(null); + } catch (NullPointerException e) { + assertEquals("trustDomain is marked non-null but is null", e.getMessage()); + } } @Test void of_emptyString_ThrowsIllegalArgumentException() { - val trustDomainResult = TrustDomain.of(""); - assertAll( - () -> assertEquals(Error.class, trustDomainResult.getClass()), - () -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError()) - ); + try { + TrustDomain.of(""); + } catch (IllegalArgumentException e) { + assertEquals("Trust Domain cannot be empty", e.getMessage()); + } } @Test void of_blankString_ThrowsIllegalArgumentException() { - val trustDomainResult = TrustDomain.of(" "); - assertAll( - () -> assertEquals(Error.class, trustDomainResult.getClass()), - () -> assertEquals("Trust Domain cannot be empty.", trustDomainResult.getError()) - ); + try { + TrustDomain.of(" "); + } catch (IllegalArgumentException e) { + assertEquals("Trust Domain cannot be empty", e.getMessage()); + } } @Test void equals_twoTrustDomainObjectsWithTheSameString_returnsTrue() { - val trustDomainResult1 = TrustDomain.of("example.org"); - val trustDomainResult2 = TrustDomain.of("example.org"); + val trustDomain1 = TrustDomain.of("example.org"); + val trustDomain2 = TrustDomain.of("example.org"); - assertEquals(trustDomainResult1.getValue(), trustDomainResult2.getValue()); + assertEquals(trustDomain1, trustDomain2); } @Test void equals_twoTrustDomainObjectsWithDifferentStrings_returnsFalse() { - val trustDomainResult1 = TrustDomain.of("example.org"); - val trustDomainResult2 = TrustDomain.of("other.org"); + val trustDomain1 = TrustDomain.of("example.org"); + val trustDomain2 = TrustDomain.of("other.org"); - assertNotEquals(trustDomainResult1.getValue(), trustDomainResult2.getValue()); + assertNotEquals(trustDomain1, trustDomain2); } } diff --git a/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidTest.java b/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidTest.java index c4a4e8f..93433cb 100644 --- a/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidTest.java +++ b/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidTest.java @@ -2,6 +2,7 @@ package spiffe.svid.x509svid; import lombok.val; import org.junit.jupiter.api.Test; +import spiffe.exception.X509SvidException; import java.io.IOException; import java.nio.file.Files; @@ -12,45 +13,50 @@ import static org.junit.jupiter.api.Assertions.*; public class X509SvidTest { @Test - void parse_GivenCertAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException { + void parse_GivenCertAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws X509SvidException, IOException { val certPem = Files.readAllBytes(Paths.get("../testdata/x509cert.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", - () -> assertTrue(result.isOk()), - () -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()), - () -> assertEquals(1, result.getValue().getChain().size()), - () -> assertNotNull(result.getValue().getPrivateKey()) + () -> assertEquals("spiffe://example.org/test", x509Svid.getSpiffeId().toString()), + () -> assertEquals(1, x509Svid.getChain().size()), + () -> assertNotNull(x509Svid.getPrivateKey()) ); } @Test - void parse_GivenChainOfCertsAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException { + void parse_GivenChainOfCertsAndPrivateKeyPEMsInByteArrays_ReturnsX509Svid() throws IOException, X509SvidException { val certPem = Files.readAllBytes(Paths.get("../testdata/x509chain.pem")); val keyPem = Files.readAllBytes(Paths.get("../testdata/pkcs8key.pem")); val result = X509Svid.parse(certPem, keyPem); assertAll("X509-SVID", - () -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()), - () -> assertEquals(4, result.getValue().getChain().size()), - () -> assertNotNull(result.getValue().getPrivateKey()) + () -> assertEquals("spiffe://example.org/test", result.getSpiffeId().toString()), + () -> assertEquals(4, result.getChain().size()), + () -> assertNotNull(result.getPrivateKey()) ); } @Test - void load_GivenCertAndPrivateKeyPaths_ReturnsX509Svid() { + void load_GivenCertAndPrivateKeyPaths_ReturnsX509Svid() throws X509SvidException { val certsFile = Paths.get("../testdata/x509cert.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", - () -> assertEquals("spiffe://example.org/test", result.getValue().getSpiffeId().toString()), - () -> assertEquals(1, result.getValue().getChain().size()), - () -> assertNotNull(result.getValue().getPrivateKey()) + () -> assertEquals("spiffe://example.org/test", result.getSpiffeId().toString()), + () -> assertEquals(1, result.getChain().size()), + () -> assertNotNull(result.getPrivateKey()) ); } } diff --git a/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidValidatorTest.java b/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidValidatorTest.java index 307073f..643b966 100644 --- a/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidValidatorTest.java +++ b/java-spiffe-core/src/test/java/spiffe/svid/x509svid/X509SvidValidatorTest.java @@ -7,14 +7,15 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import spiffe.bundle.x509bundle.X509Bundle; import spiffe.bundle.x509bundle.X509BundleSource; +import spiffe.exception.BundleNotFoundException; import spiffe.internal.CertificateUtils; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.spiffeid.TrustDomain; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; @@ -32,50 +33,55 @@ public class X509SvidValidatorTest { } @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 chain = CertificateUtils.generateCertificates(certBytes).getValue(); - Result x509Bundle= + val chain = CertificateUtils.generateCertificates(certBytes); + X509Bundle x509Bundle= X509Bundle.load( - TrustDomain.of("example.org").getValue(), + TrustDomain.of("example.org"), Paths.get("../testdata/bundle.pem") ); when(bundleSourceMock .getX509BundleForTrustDomain( - TrustDomain.of("example.org").getValue())) + TrustDomain.of("example.org"))) .thenReturn(x509Bundle); - val result = X509SvidValidator.verifyChain(chain, bundleSourceMock); - - assertTrue(result.isError()); - assertTrue(result.getError().contains("CertificateExpiredException: NotAfter")); + try { + X509SvidValidator.verifyChain(chain, bundleSourceMock); + fail("Verify chain should have thrown validation exception"); + } catch (CertificateException e) { + assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage()); + } } @Test - void checkSpiffeId_givenASpiffeIdInTheListOfAcceptedIds_returnsValid() { - val spiffeId1 = SpiffeId.parse("spiffe://example.org/test").getValue(); - val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2").getValue(); + void checkSpiffeId_givenASpiffeIdInTheListOfAcceptedIds_doesntThrowException() throws IOException, CertificateException { + val spiffeId1 = SpiffeId.parse("spiffe://example.org/test"); + val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2"); - Result, 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 - .verifySpiffeId(SpiffeId.parse("spiffe://example.org/test").getValue(), () -> spiffeIdList); + val spiffeIdList = Arrays.asList(spiffeId1, spiffeId2); - assertTrue(result.isOk()); + X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList); } @Test - void checkSpiffeId_givenASpiffeIdNotInTheListOfAcceptedIds_returnsValid() { - val spiffeId1 = SpiffeId.parse("spiffe://example.org/other1").getValue(); - val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2").getValue(); - Result, String> spiffeIdList = Result.ok(Arrays.asList(spiffeId1, spiffeId2)); + void checkSpiffeId_givenASpiffeIdNotInTheListOfAcceptedIds_throwsCertificateException() throws IOException, CertificateException { + val spiffeId1 = SpiffeId.parse("spiffe://example.org/other1"); + val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2"); + List 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( - () -> assertTrue(result.isError()), - () -> assertEquals("SPIFFE ID 'spiffe://example.org/test' is not accepted",result.getError()) - ); + try { + X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList); + fail("Should have thrown CertificateException"); + } catch (CertificateException e) { + assertEquals("SPIFFE ID spiffe://example.org/test in x509Certificate is not accepted", e.getMessage()); + } } } diff --git a/java-spiffe-core/src/test/java/spiffe/workloadapi/AddressTest.java b/java-spiffe-core/src/test/java/spiffe/workloadapi/AddressTest.java index 8c30cf5..3ca6ab8 100644 --- a/java-spiffe-core/src/test/java/spiffe/workloadapi/AddressTest.java +++ b/java-spiffe-core/src/test/java/spiffe/workloadapi/AddressTest.java @@ -3,7 +3,7 @@ package spiffe.workloadapi; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import spiffe.result.Result; +import spiffe.exception.SocketEndpointAddressException; import java.net.URI; import java.util.stream.Stream; @@ -14,33 +14,38 @@ public class AddressTest { @ParameterizedTest @MethodSource("provideTestAddress") - void parseAddressInvalid(String input, Result expected) { - Result result = Address.parseAddress(input); - assertEquals(expected, result); + void parseAddressInvalid(String input, Object expected) { + URI result = null; + try { + result = Address.parseAddress(input); + assertEquals(expected, result); + } catch (SocketEndpointAddressException e) { + assertEquals(expected, e.getMessage()); + } } static Stream provideTestAddress() { return Stream.of( - Arguments.of("unix://foo", Result.ok(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("blah", Result.error("Workload endpoint socket URI must have a tcp:// or unix:// scheme")), - Arguments.of("unix:opaque", Result.error("Workload endpoint unix socket URI must not be opaque")), - Arguments.of("unix://", Result.error("Workload endpoint socket is not a valid URI: Expected authority at index 7: unix://")), - Arguments.of("unix://foo?whatever", Result.error("Workload endpoint unix socket URI must not include query values")), - Arguments.of("unix://foo#whatever", Result.error("Workload endpoint unix socket URI must not include a fragment")), - Arguments.of("unix://john:doe@foo/path", Result.error("Workload endpoint unix socket URI must not include user info")), + Arguments.of("unix://foo", URI.create("unix://foo")), + Arguments.of("\\t", "Workload endpoint socket is not a valid URI: \\t"), + Arguments.of("blah", "Workload endpoint socket URI must have a tcp:// or unix:// scheme: blah"), + Arguments.of("unix:opaque", "Workload endpoint unix socket URI must not be opaque: unix:opaque"), + Arguments.of("unix://", "Workload endpoint socket is not a valid URI: unix://"), + Arguments.of("unix://foo?whatever", "Workload endpoint unix socket URI must not include query values: unix://foo?whatever"), + 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", "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:opaque", Result.error("Workload endpoint tcp socket URI must not be opaque")), - Arguments.of("tcp://", Result.error("Workload endpoint socket is not a valid URI: Expected authority at index 6: 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", Result.error("Workload endpoint tcp socket URI must not include a fragment")), - 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://1.2.3.4:5/path", Result.error("Workload endpoint tcp socket URI must not include a path")), - Arguments.of("tcp://foo", Result.error("Workload endpoint tcp socket URI host component must be an IP:port")), - 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:5", URI.create("tcp://1.2.3.4:5")), + Arguments.of("tcp:opaque", "Workload endpoint tcp socket URI must not be opaque: tcp:opaque"), + Arguments.of("tcp://", "Workload endpoint socket is not a valid URI: tcp://"), + 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", "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", "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", "Workload endpoint tcp socket URI must not include a path: tcp://1.2.3.4:5/path"), + 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", "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") ); } } \ No newline at end of file diff --git a/java-spiffe-helper/src/main/java/spiffe/helper/BundleEntry.java b/java-spiffe-helper/src/main/java/spiffe/helper/BundleEntry.java index ea5d6ed..7f635fb 100644 --- a/java-spiffe-helper/src/main/java/spiffe/helper/BundleEntry.java +++ b/java-spiffe-helper/src/main/java/spiffe/helper/BundleEntry.java @@ -3,17 +3,17 @@ package spiffe.helper; import lombok.Builder; import lombok.Value; -import java.security.cert.Certificate; +import java.security.cert.X509Certificate; @Value class BundleEntry { String alias; - Certificate certificate; + X509Certificate certificate; @Builder BundleEntry( final String alias, - final Certificate certificate) { + final X509Certificate certificate) { this.alias = alias; this.certificate = certificate; } diff --git a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStore.java b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStore.java index 4b15471..4ef1af8 100644 --- a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStore.java +++ b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStore.java @@ -3,7 +3,6 @@ package spiffe.helper; import lombok.Builder; import lombok.NonNull; import lombok.val; -import spiffe.result.Result; import java.io.File; import java.io.FileInputStream; @@ -17,7 +16,7 @@ import java.security.cert.CertificateException; /** * 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. */ class KeyStore { @@ -33,25 +32,20 @@ class KeyStore { KeyStore( @NonNull final Path keyStoreFilePath, @NonNull final KeyStoreType keyStoreType, - @NonNull final char[] keyStorePassword) { + @NonNull final char[] keyStorePassword) throws KeyStoreException { this.keyStoreFilePath = keyStoreFilePath; this.keyStoreType = keyStoreType; this.keyStorePassword = keyStorePassword; setupKeyStore(); } - private void setupKeyStore() { + private void setupKeyStore() throws KeyStoreException { this.keyStoreFile = new File(keyStoreFilePath.toUri()); - - val keyStore = loadKeyStore(keyStoreFile); - if (keyStore.isError()) { - throw new RuntimeException(keyStore.getError()); - } - this.keyStore = keyStore.getValue(); + this.keyStore = loadKeyStore(keyStoreFile); } - private Result loadKeyStore(final File keyStoreFile) { + private java.security.KeyStore loadKeyStore(final File keyStoreFile) throws KeyStoreException { try { val keyStore = java.security.KeyStore.getInstance(keyStoreType.value()); @@ -62,58 +56,48 @@ class KeyStore { //create new keyStore keyStore.load(null, keyStorePassword); } - return Result.ok(keyStore); - } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { - return Result.error(e); + return keyStore; + } catch (IOException | NoSuchAlgorithmException | CertificateException 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 - * @return Result of Boolean indicating if it was successful or an Error wrapping an Exception */ - Result storePrivateKey(final PrivateKeyEntry privateKeyEntry) { - try { - // Store PrivateKey Entry in KeyStore - keyStore.setKeyEntry( - privateKeyEntry.getAlias(), - privateKeyEntry.getPrivateKey(), - privateKeyEntry.getPassword(), - privateKeyEntry.getCertificateChain() - ); + void storePrivateKey(final PrivateKeyEntry privateKeyEntry) throws KeyStoreException { + // Store PrivateKey Entry in KeyStore + keyStore.setKeyEntry( + privateKeyEntry.getAlias(), + privateKeyEntry.getPrivateKey(), + privateKeyEntry.getPassword(), + privateKeyEntry.getCertificateChain() + ); - return this.flush(); - } catch (KeyStoreException e) { - return Result.error(e); - } + this.flush(); } /** * Store a Bundle Entry in the KeyStore */ - Result storeBundleEntry(BundleEntry bundleEntry) { - try { - // Store Bundle Entry in KeyStore - this.keyStore.setCertificateEntry( - bundleEntry.getAlias(), - bundleEntry.getCertificate() - ); - return this.flush(); - } catch (KeyStoreException e) { - return Result.error(e); - } + void storeBundleEntry(BundleEntry bundleEntry) throws KeyStoreException { + // Store Bundle Entry in KeyStore + this.keyStore.setCertificateEntry( + bundleEntry.getAlias(), + bundleEntry.getCertificate() + ); + this.flush(); } // Flush KeyStore to disk, to the configured (@see keyStoreFilePath) - private Result flush() { + private void flush() throws KeyStoreException { try { keyStore.store(new FileOutputStream(keyStoreFile), keyStorePassword); - return Result.ok(true); - } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { - return Result.error(e); + } catch (IOException | NoSuchAlgorithmException | CertificateException e) { + throw new KeyStoreException(e); } } } diff --git a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java index 56b6ac4..2038bd3 100644 --- a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java +++ b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java @@ -2,25 +2,24 @@ package spiffe.helper; import lombok.Builder; import lombok.NonNull; -import lombok.SneakyThrows; import lombok.extern.java.Log; import lombok.val; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; -import spiffe.result.Error; -import spiffe.result.Result; +import spiffe.exception.SocketEndpointAddressException; import spiffe.workloadapi.Watcher; import spiffe.workloadapi.WorkloadApiClient; import spiffe.workloadapi.WorkloadApiClient.ClientOptions; import spiffe.workloadapi.X509Context; import java.nio.file.Path; +import java.security.KeyStoreException; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; /** - * A KeyStoreHelper represents a helper for storing X095-SVIDs and Bundles, - * that are automatically rotated via the Worklaod API, in a Java KeyStore in a file in disk. + * A KeyStoreHelper represents a helper for storing X509 SVIDs and bundles, + * that are automatically rotated via the Workload API, in a Java KeyStore in a file in disk. */ @Log public class KeyStoreHelper { @@ -33,23 +32,25 @@ public class KeyStoreHelper { private final String spiffeSocketPath; /** - * 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. + * Create an instance of a KeyStoreHelper for fetching X509 SVIDs and bundles + * from a Workload API and store them in a binary Java KeyStore in disk. *

* It blocks until the initial update has been received from the Workload API. * * @param keyStoreFilePath path to File storing the KeyStore. * @param keyStoreType the type of keystore. Only JKS and PKCS12 are supported. If it's not provided, PKCS12 is used - * See the KeyStore section in the - * Java Cryptography Architecture Standard Algorithm Name Documentation - * for information about standard keystore types. + * See the KeyStore section in the + * Java Cryptography Architecture Standard Algorithm Name Documentation + * for information about standard keystore types. * @param keyStorePassword the password to generate the keystore integrity check * @param privateKeyPassword the password to protect the key * @param privateKeyAlias the alias name * @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 public KeyStoreHelper( @@ -58,7 +59,8 @@ public class KeyStoreHelper { @NonNull final char[] keyStorePassword, @NonNull final char[] privateKeyPassword, @NonNull final String privateKeyAlias, - @NonNull String spiffeSocketPath) { + @NonNull String spiffeSocketPath) + throws SocketEndpointAddressException, KeyStoreException { this.privateKeyPassword = privateKeyPassword.clone(); @@ -66,7 +68,7 @@ public class KeyStoreHelper { this.spiffeSocketPath = spiffeSocketPath; this.keyStore = - spiffe.helper.KeyStore + KeyStore .builder() .keyStoreFilePath(keyStoreFilePath) .keyStoreType(keyStoreType) @@ -76,9 +78,8 @@ public class KeyStoreHelper { setupX509ContextFetcher(); } - @SneakyThrows - private void setupX509ContextFetcher() { - Result workloadApiClient; + private void setupX509ContextFetcher() throws SocketEndpointAddressException { + WorkloadApiClient workloadApiClient; if (StringUtils.isNotBlank(spiffeSocketPath)) { ClientOptions clientOptions = ClientOptions.builder().spiffeSocketPath(spiffeSocketPath).build(); @@ -88,8 +89,8 @@ public class KeyStoreHelper { } CountDownLatch countDownLatch = new CountDownLatch(1); - setX509ContextWatcher(workloadApiClient.getValue(), countDownLatch); - countDownLatch.await(); + setX509ContextWatcher(workloadApiClient, countDownLatch); + await(countDownLatch); } private void setX509ContextWatcher(WorkloadApiClient workloadApiClient, CountDownLatch countDownLatch) { @@ -97,18 +98,22 @@ public class KeyStoreHelper { @Override public void OnUpdate(X509Context update) { log.log(Level.INFO, "Received X509Context update"); - storeX509ContextUpdate(update); + try { + storeX509ContextUpdate(update); + } catch (KeyStoreException e) { + this.OnError(e); + } countDownLatch.countDown(); } @Override - public void OnError(Error error) { - throw new RuntimeException(error.getError()); + public void OnError(Throwable t) { + throw new RuntimeException(t); } }); } - private void storeX509ContextUpdate(final X509Context update) { + private void storeX509ContextUpdate(final X509Context update) throws KeyStoreException { val privateKeyEntry = PrivateKeyEntry.builder() .alias(privateKeyAlias) .password(privateKeyPassword) @@ -116,14 +121,19 @@ public class KeyStoreHelper { .certificateChain(update.getDefaultSvid().getChainArray()) .build(); - val storeKeyResult = keyStore.storePrivateKey(privateKeyEntry); - if (storeKeyResult.isError()) { - throw new RuntimeException(storeKeyResult.getError()); - } + keyStore.storePrivateKey(privateKeyEntry); log.log(Level.INFO, "Stored X509Context update"); // TODO: Store all the Bundles throw new NotImplementedException("Bundle Storing is not implemented"); } + + private void await(CountDownLatch countDownLatch) { + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } } diff --git a/java-spiffe-helper/src/main/java/spiffe/helper/PrivateKeyEntry.java b/java-spiffe-helper/src/main/java/spiffe/helper/PrivateKeyEntry.java index bffbcc1..04c2e6a 100644 --- a/java-spiffe-helper/src/main/java/spiffe/helper/PrivateKeyEntry.java +++ b/java-spiffe-helper/src/main/java/spiffe/helper/PrivateKeyEntry.java @@ -4,21 +4,21 @@ import lombok.Builder; import lombok.Value; import java.security.Key; -import java.security.cert.Certificate; +import java.security.cert.X509Certificate; @Value class PrivateKeyEntry { String alias; Key privateKey; char[] password; - Certificate[] certificateChain; + X509Certificate[] certificateChain; @Builder PrivateKeyEntry( final String alias, final Key privateKey, final char[] password, - final Certificate[] certificateChain) { + final X509Certificate[] certificateChain) { this.alias = alias; this.privateKey = privateKey; this.password = password; diff --git a/java-spiffe-helper/src/test/java/spiffe/helper/KeyStoreTest.java b/java-spiffe-helper/src/test/java/spiffe/helper/KeyStoreTest.java index 50fd4d2..1fd6533 100644 --- a/java-spiffe-helper/src/test/java/spiffe/helper/KeyStoreTest.java +++ b/java-spiffe-helper/src/test/java/spiffe/helper/KeyStoreTest.java @@ -4,6 +4,7 @@ import lombok.val; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import spiffe.exception.X509SvidException; import spiffe.internal.CertificateUtils; import spiffe.svid.x509svid.X509Svid; @@ -20,7 +21,8 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; 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 { @@ -31,12 +33,12 @@ public class KeyStoreTest { @BeforeEach - void setup() { + void setup() throws X509SvidException { x509Svid = X509Svid .load( Paths.get("../testdata/x509cert.pem"), Paths.get("../testdata/pkcs8key.pem") - ).getValue(); + ); } @Test @@ -60,9 +62,8 @@ public class KeyStoreTest { .build(); - val result = keyStore.storePrivateKey(privateKeyEntry); + keyStore.storePrivateKey(privateKeyEntry); - assertTrue(result.isOk()); checkEntryWasStored(keyStoreFilePath, keyStorePassword, privateKeyPassword, keyStoreType, DEFAULT_ALIAS); } @@ -81,7 +82,7 @@ public class KeyStoreTest { val privateKey = (PrivateKey) keyStore.getKey(alias, privateKeyPassword); assertEquals(1, chain.length); - assertEquals("spiffe://example.org/test", spiffeId.getValue().toString()); + assertEquals("spiffe://example.org/test", spiffeId.toString()); assertNotNull(privateKey); } diff --git a/java-spiffe-provider/README.md b/java-spiffe-provider/README.md index 4ee20e3..3900878 100644 --- a/java-spiffe-provider/README.md +++ b/java-spiffe-provider/README.md @@ -13,13 +13,9 @@ will trust for TLS connections. ``` val sslContextOptions = SslContextOptions .builder() - .x509Source(x509Source.newSource().getValue()) + .x509Source(x509Source.newSource()()) .build(); - Result sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); - if (sslContext.isError()) { - // handle sslContext.getError(); - } - + SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); ``` 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) .build(); val x509Source = X509Source.newSource(sourceOptions); - if (x509Source.isError()) { - // handle x509source.getError() - } SslContextOptions sslContextOptions = SslContextOptions .builder() .acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier) - .x509Source(x509Source.getValue()) + .x509Source(x509Source()) .build(); - Result sslContext = SpiffeSslContextFactory - .getSslContext(sslContextOptions); - - if (sslContext.isError()) { - // handle sslContext.getError() - } + SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); ``` See [HttpsClient example](src/main/java/spiffe/provider/examples/HttpsClient.java) that defines a Supplier for providing diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManager.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManager.java index aa6ef69..627457a 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManager.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManager.java @@ -1,7 +1,6 @@ package spiffe.provider; import lombok.val; -import spiffe.result.Result; import spiffe.svid.x509svid.X509Svid; import spiffe.svid.x509svid.X509SvidSource; @@ -17,9 +16,9 @@ import java.util.Objects; import static spiffe.provider.SpiffeProviderConstants.DEFAULT_ALIAS; /** - * A SpiffeKeyManager represents a X509 KeyManager for the SPIFFE Provider. + * A SpiffeKeyManager represents a X509 key manager for the SPIFFE provider. *

- * 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 { @@ -32,27 +31,24 @@ public final class SpiffeKeyManager extends X509ExtendedKeyManager { /** * 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 public X509Certificate[] getCertificateChain(String alias) { if (!Objects.equals(alias, DEFAULT_ALIAS)) { return null; } - Result x509Svid = x509SvidSource.getX509Svid(); - if (x509Svid.isError()) { - throw new IllegalStateException(x509Svid.getError()); - } - return x509Svid.getValue().getChainArray(); + X509Svid x509Svid = x509SvidSource.getX509Svid(); + return x509Svid.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, - * 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 public PrivateKey getPrivateKey(String alias) { @@ -61,12 +57,8 @@ public final class SpiffeKeyManager extends X509ExtendedKeyManager { return null; } - Result x509Svid = x509SvidSource.getX509Svid(); - if (x509Svid.isError()) { - throw new IllegalStateException(x509Svid.getError()); - } - - return x509Svid.getValue().getPrivateKey(); + X509Svid x509Svid = x509SvidSource.getX509Svid(); + return x509Svid.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 private String getAlias(String... keyTypes) { 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)) { return DEFAULT_ALIAS; } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManagerFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManagerFactory.java index f12dd2c..3a35f05 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManagerFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeKeyManagerFactory.java @@ -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} - * @return a {@link KeyManager} + * @param x509SvidSource an instance of a {@link X509SvidSource} + * @return an array with an instance of a {@link KeyManager} */ public KeyManager[] engineGetKeyManagers(X509SvidSource x509SvidSource) { val spiffeKeyManager = new SpiffeKeyManager(x509SvidSource); diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java index 06131ba..7ec42c1 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java @@ -4,8 +4,6 @@ import lombok.Builder; import lombok.Data; import lombok.NonNull; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.workloadapi.X509Source; @@ -32,31 +30,29 @@ public final class SpiffeSslContextFactory { * If the option acceptedSpiffeIdsSupplier is not provided, the list of accepted SPIFFE IDs * is read from the Security Property ssl.spiffe.accept. * If the sslProcotol is not provided, the default TLSv1.2 is used. - * - * @return a Result containing a SSLContext + * @return a {@link 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 getSslContext(@NonNull SslContextOptions options) { - try { - SSLContext sslContext; - if (StringUtils.isNotBlank(options.sslProtocol)) { - sslContext = SSLContext.getInstance(options.sslProtocol); - } else { - sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); - } - - if (options.x509Source == null) { - return Result.error("x509Source option cannot be null, a X509 Source must be provided"); - } - - sslContext.init( - new SpiffeKeyManagerFactory().engineGetKeyManagers(options.x509Source), - new SpiffeTrustManagerFactory().engineGetTrustManagers(options.x509Source, options.acceptedSpiffeIdsSupplier), - null); - - return Result.ok(sslContext); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - return Result.error("Error creating SSL Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); + public static SSLContext getSslContext(@NonNull SslContextOptions options) throws NoSuchAlgorithmException, KeyManagementException { + SSLContext sslContext; + if (StringUtils.isNotBlank(options.sslProtocol)) { + sslContext = SSLContext.getInstance(options.sslProtocol); + } else { + sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); } + + if (options.x509Source == null) { + throw new IllegalArgumentException("x509Source option cannot be null, a X509 Source must be provided"); + } + + sslContext.init( + new SpiffeKeyManagerFactory().engineGetKeyManagers(options.x509Source), + new SpiffeTrustManagerFactory().engineGetTrustManagers(options.x509Source, options.acceptedSpiffeIdsSupplier), + null); + + return sslContext; } /** @@ -66,13 +62,13 @@ public final class SpiffeSslContextFactory { public static class SslContextOptions { String sslProtocol; X509Source x509Source; - Supplier, String>> acceptedSpiffeIdsSupplier; + Supplier> acceptedSpiffeIdsSupplier; @Builder public SslContextOptions( String sslProtocol, X509Source x509Source, - Supplier, String>> acceptedSpiffeIdsSupplier) { + Supplier> acceptedSpiffeIdsSupplier) { this.x509Source = x509Source; this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier; this.sslProtocol = sslProtocol; diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java index 75c1d45..c9c6898 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java @@ -7,6 +7,8 @@ import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; /** * A SpiffeSslSocketFactory is an implementation of SSLSocketFactory @@ -17,12 +19,9 @@ public class SpiffeSslSocketFactory extends SSLSocketFactory { private final SSLSocketFactory delegate; - public SpiffeSslSocketFactory(SslContextOptions contextOptions) { + public SpiffeSslSocketFactory(SslContextOptions contextOptions) throws KeyManagementException, NoSuchAlgorithmException { val sslContext = SpiffeSslContextFactory.getSslContext(contextOptions); - if (sslContext.isError()) { - throw new RuntimeException(sslContext.getError()); - } - delegate = sslContext.getValue().getSocketFactory(); + delegate = sslContext.getSocketFactory(); } @Override @@ -42,7 +41,7 @@ public class SpiffeSslSocketFactory extends SSLSocketFactory { @Override public Socket createSocket(String s, int i) throws IOException { - return delegate.createSocket(s, i ); + return delegate.createSocket(s, i); } @Override diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManager.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManager.java index dd4eff4..deff3e7 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManager.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManager.java @@ -1,9 +1,6 @@ package spiffe.provider; -import lombok.val; import spiffe.bundle.x509bundle.X509BundleSource; -import spiffe.internal.CertificateUtils; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.svid.x509svid.X509SvidValidator; @@ -25,17 +22,17 @@ import java.util.function.Supplier; public final class SpiffeTrustManager extends X509ExtendedTrustManager { private final X509BundleSource x509BundleSource; - private final Supplier, String>> acceptedSpiffeIdsSupplier; + private final Supplier> acceptedSpiffeIdsSupplier; /** * Creates a SpiffeTrustManager with a X509BundleSource used to provide the trusted * bundles, and a Supplier of a List of accepted SpiffeIds to be used during peer SVID validation. * - * @param X509BundleSource an implementation of a {@link X509BundleSource} + * @param X509BundleSource an implementation of a {@link X509BundleSource} * @param acceptedSpiffeIdsSupplier a Supplier of a list of accepted SPIFFE IDs. */ public SpiffeTrustManager(X509BundleSource X509BundleSource, - Supplier, String>> acceptedSpiffeIdsSupplier) { + Supplier> acceptedSpiffeIdsSupplier) { this.x509BundleSource = X509BundleSource; this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier; } @@ -54,10 +51,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager { */ @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - val result = validatePeerChain(chain); - if (result.isError()) { - throw new CertificateException(result.getError()); - } + validatePeerChain(chain); } /** @@ -74,10 +68,7 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager { */ @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - val result = validatePeerChain(chain); - if (result.isError()) { - throw new CertificateException(result.getError()); - } + validatePeerChain(chain); } @Override @@ -85,7 +76,9 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager { return new X509Certificate[0]; } - /** {@link #checkClientTrusted(X509Certificate[], String)} */ + /** + * {@link #checkClientTrusted(X509Certificate[], String)} + */ @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { checkClientTrusted(chain, authType); @@ -96,33 +89,25 @@ public final class SpiffeTrustManager extends X509ExtendedTrustManager { checkServerTrusted(chain, authType); } - /** {@link #checkClientTrusted(X509Certificate[], String)} */ + /** + * {@link #checkClientTrusted(X509Certificate[], String)} + */ @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { checkClientTrusted(chain, authType); } - /** {@link #checkServerTrusted(X509Certificate[], String)} */ + /** + * {@link #checkServerTrusted(X509Certificate[], String)} + */ @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { checkServerTrusted(chain, authType); } // Check the spiffeId using the checkSpiffeId function and the chain using the bundleSource and a Validator - private Result validatePeerChain(X509Certificate[] chain) { - val spiffeId = CertificateUtils.getSpiffeId(chain[0]); - if (spiffeId.isError()) { - return Result.error(spiffeId.getError()); - } - - return X509SvidValidator - .verifySpiffeId( - spiffeId.getValue(), - acceptedSpiffeIdsSupplier) - .thenApply( - X509SvidValidator::verifyChain, - Arrays.asList(chain), - x509BundleSource - ); + private void validatePeerChain(X509Certificate[] chain) throws CertificateException { + X509SvidValidator.verifySpiffeId(chain[0], acceptedSpiffeIdsSupplier); + X509SvidValidator.verifyChain(Arrays.asList(chain), x509BundleSource); } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java index ce905ab..e864cbc 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java @@ -2,7 +2,6 @@ package spiffe.provider; import lombok.val; import spiffe.bundle.x509bundle.X509BundleSource; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.spiffeid.SpiffeIdUtils; @@ -77,9 +76,9 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi { */ public TrustManager[] engineGetTrustManagers( X509BundleSource x509BundleSource, - Supplier, String>> acceptedSpiffeIdsSupplier) { + Supplier> acceptedSpiffeIdsSupplier) { - Supplier, String>> spiffeIdsSupplier; + Supplier> spiffeIdsSupplier; if (acceptedSpiffeIdsSupplier != null) { spiffeIdsSupplier = acceptedSpiffeIdsSupplier; } else { @@ -104,7 +103,7 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi { } - private Result, String> getAcceptedSpiffeIds() { + private List getAcceptedSpiffeIds() { return SpiffeIdUtils.getSpiffeIdsFromSecurityProperty(SSL_SPIFFE_ACCEPT_PROPERTY); } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/X509SourceManager.java b/java-spiffe-provider/src/main/java/spiffe/provider/X509SourceManager.java index 649be88..1c9ede6 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/X509SourceManager.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/X509SourceManager.java @@ -1,19 +1,20 @@ package spiffe.provider; -import lombok.val; +import spiffe.exception.SocketEndpointAddressException; +import spiffe.exception.X509SourceException; import spiffe.workloadapi.X509Source; /** * A X509SourceManager is a Singleton that handles an instance of a X509Source. - * Uses the environment variable 'SPIFFE_ENDPOINT_SOCKET' to create a X509Source backed by the + *

+ * The default SPIFFE socket enpoint address is used to create a X509Source backed by the * Workload API. * If the environment variable is not defined, it will throw an IllegalStateException. * If the X509Source cannot be initialized, it will throw a RuntimeException. *

- * @implNote The reason to have this Singleton is because we need to have - * a single X509Source instance to be used by the {@link SpiffeKeyManagerFactory} - * and {@link SpiffeTrustManagerFactory} to inject it in the {@link SpiffeKeyManager} and {@link SpiffeTrustManager} - * instances. + * @implNote This Singleton needed to be able to handle a single {@link X509Source} instance + * to be used by the {@link SpiffeKeyManagerFactory} and {@link SpiffeTrustManagerFactory} to inject it + * in the {@link SpiffeKeyManager} and {@link SpiffeTrustManager} instances. */ public enum X509SourceManager { @@ -22,15 +23,11 @@ public enum X509SourceManager { private final X509Source x509Source; X509SourceManager() { - val x509SourceResult = - X509Source.newSource(); - if (x509SourceResult.isError()) { - // panic in case of error creating the X509Source - throw new RuntimeException(x509SourceResult.getError()); + try { + x509Source = X509Source.newSource(); + } catch (SocketEndpointAddressException e) { + throw new X509SourceException("Could not create X509 Source. Socket endpoint address is not valid", e); } - - // set the singleton instance - x509Source = x509SourceResult.getValue(); } public X509Source getX509Source() { diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java index 9d51a1a..b7fb222 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java @@ -1,9 +1,9 @@ package spiffe.provider.examples; import lombok.val; +import spiffe.exception.SocketEndpointAddressException; import spiffe.provider.SpiffeSslContextFactory; import spiffe.provider.SpiffeSslContextFactory.SslContextOptions; -import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; import spiffe.workloadapi.X509Source; import spiffe.workloadapi.X509Source.X509SourceOptions; @@ -15,6 +15,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -32,62 +34,55 @@ import java.util.stream.Stream; public class HttpsClient { String spiffeSocket; - Supplier, String>> acceptedSpiffeIdsListSupplier; + Supplier> acceptedSpiffeIdsListSupplier; int serverPort; - public static void main(String[] args) throws IOException { + public static void main(String[] args) { String spiffeSocket = "unix:/tmp/agent.sock"; - HttpsClient httpsClient = - new HttpsClient(4000, spiffeSocket, HttpsClient::listOfSpiffeIds); - httpsClient.run(); + HttpsClient httpsClient = new HttpsClient(4000, spiffeSocket, HttpsClient::listOfSpiffeIds); + try { + httpsClient.run(); + } catch (KeyManagementException | NoSuchAlgorithmException | IOException | SocketEndpointAddressException e) { + throw new RuntimeException("Error starting Https Client", e); + } } - HttpsClient(int serverPort, String spiffeSocket, Supplier, String>> acceptedSpiffeIdsListSupplier) { + HttpsClient(int serverPort, String spiffeSocket, Supplier> acceptedSpiffeIdsListSupplier) { this.serverPort = serverPort; this.spiffeSocket = spiffeSocket; this.acceptedSpiffeIdsListSupplier = acceptedSpiffeIdsListSupplier; } - void run() throws IOException { + void run() throws IOException, SocketEndpointAddressException, KeyManagementException, NoSuchAlgorithmException { val sourceOptions = X509SourceOptions .builder() .spiffeSocketPath(spiffeSocket) .build(); val x509Source = X509Source.newSource(sourceOptions); - if (x509Source.isError()) { - throw new RuntimeException(x509Source.getError()); - } SslContextOptions sslContextOptions = SslContextOptions .builder() .acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier) - .x509Source(x509Source.getValue()) + .x509Source(x509Source) .build(); - Result sslContext = SpiffeSslContextFactory - .getSslContext(sslContextOptions); + SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); - if (sslContext.isError()) { - throw new RuntimeException(sslContext.getError()); - } - - SSLSocketFactory sslSocketFactory = sslContext.getValue().getSocketFactory(); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", serverPort); - new WorkloadThread(sslSocket, x509Source.getValue()).start(); + new WorkloadThread(sslSocket, x509Source).start(); } - static Result, String> listOfSpiffeIds() { + static List listOfSpiffeIds() { try { Path path = Paths.get("java-spiffe-provider/src/main/java/spiffe/provider/examples/spiffeIds.txt"); Stream lines = Files.lines(path); - List list = lines + return lines .map(SpiffeId::parse) - .map(Result::getValue) .collect(Collectors.toList()); - return Result.ok(list); } 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); } } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java index 208fbf7..9cf7595 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java @@ -1,9 +1,9 @@ package spiffe.provider.examples; import lombok.val; +import spiffe.exception.SocketEndpointAddressException; import spiffe.provider.SpiffeSslContextFactory; import spiffe.provider.SpiffeSslContextFactory.SslContextOptions; -import spiffe.result.Result; import spiffe.workloadapi.X509Source; import javax.net.ssl.SSLContext; @@ -11,10 +11,12 @@ import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; 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 - * and trusted cert bundles. + * Example of a simple HTTPS Server backed by the Workload API to get the X509 certificates + * and trusted bundles. *

* 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 @@ -28,38 +30,41 @@ public class HttpsServer { int port; - public static void main(String[] args) throws IOException { + public static void main(String[] args) { HttpsServer httpsServer = new HttpsServer(4000); - httpsServer.run(); + try { + httpsServer.run(); + } catch (IOException | KeyManagementException | NoSuchAlgorithmException e) { + throw new RuntimeException("Error starting HttpsServer"); + } } HttpsServer(int port ) { this.port = port; } - void run() throws IOException { - val x509Source = X509Source.newSource(); - if (x509Source.isError()) { - throw new RuntimeException(x509Source.getError()); + void run() throws IOException, KeyManagementException, NoSuchAlgorithmException { + X509Source x509Source = null; + try { + x509Source = X509Source.newSource(); + } catch (SocketEndpointAddressException e) { + throw new RuntimeException(e); } val sslContextOptions = SslContextOptions .builder() - .x509Source(x509Source.getValue()) + .x509Source(x509Source) .build(); - Result sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); - if (sslContext.isError()) { - throw new RuntimeException(sslContext.getError()); - } + SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); - SSLServerSocketFactory sslServerSocketFactory = sslContext.getValue().getServerSocketFactory(); + SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); // Server will validate Client chain and SPIFFE ID sslServerSocket.setNeedClientAuth(true); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); - new WorkloadThread(sslSocket, x509Source.getValue()).start(); + new WorkloadThread(sslSocket, x509Source).start(); } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java index d856749..a524804 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java @@ -37,10 +37,10 @@ class WorkloadThread extends Thread { PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream)); SpiffeId peerSpiffeId = CertificateUtils - .getSpiffeId((X509Certificate) sslSession.getPeerCertificates()[0]).getValue(); + .getSpiffeId((X509Certificate) sslSession.getPeerCertificates()[0]); SpiffeId mySpiffeId = CertificateUtils - .getSpiffeId((X509Certificate) sslSession.getLocalCertificates()[0]).getValue(); + .getSpiffeId((X509Certificate) sslSession.getLocalCertificates()[0]); // Send message to peer printWriter.printf("Hello %s, I'm %s", peerSpiffeId, mySpiffeId); diff --git a/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeKeyManagerTest.java b/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeKeyManagerTest.java index 08b3024..b344038 100644 --- a/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeKeyManagerTest.java +++ b/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeKeyManagerTest.java @@ -5,13 +5,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import spiffe.exception.X509SvidException; import spiffe.internal.CertificateUtils; -import spiffe.result.Result; import spiffe.svid.x509svid.X509Svid; import spiffe.svid.x509svid.X509SvidSource; import javax.net.ssl.X509KeyManager; import java.nio.file.Paths; +import java.security.cert.CertificateException; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -26,32 +27,31 @@ public class SpiffeKeyManagerTest { X509Svid x509Svid; @BeforeEach - void setup() { + void setup() throws X509SvidException { MockitoAnnotations.initMocks(this); keyManager = (X509KeyManager) new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource)[0]; x509Svid = X509Svid .load( Paths.get("../testdata/x509cert.pem"), - Paths.get("../testdata/pkcs8key.pem")) - .getValue(); + Paths.get("../testdata/pkcs8key.pem")); } @Test - void getCertificateChain_returnsAnArrayOfX509Certificates() { - when(x509SvidSource.getX509Svid()).thenReturn(Result.ok(x509Svid)); + void getCertificateChain_returnsAnArrayOfX509Certificates() throws CertificateException { + when(x509SvidSource.getX509Svid()).thenReturn(x509Svid); val certificateChain = keyManager.getCertificateChain(DEFAULT_ALIAS); val spiffeId = CertificateUtils.getSpiffeId(certificateChain[0]); assertAll( () -> assertEquals(1, certificateChain.length), - () -> assertEquals("spiffe://example.org/test", spiffeId.getValue().toString()) + () -> assertEquals("spiffe://example.org/test", spiffeId.toString()) ); } @Test void getPrivateKey_aliasIsSpiffe_returnAPrivateKey() { - when(x509SvidSource.getX509Svid()).thenReturn(Result.ok(x509Svid)); + when(x509SvidSource.getX509Svid()).thenReturn(x509Svid); val privateKey = keyManager.getPrivateKey(DEFAULT_ALIAS); diff --git a/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeTrustManagerTest.java b/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeTrustManagerTest.java index 43189da..d798ba5 100644 --- a/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeTrustManagerTest.java +++ b/java-spiffe-provider/src/test/java/spiffe/provider/SpiffeTrustManagerTest.java @@ -8,12 +8,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import spiffe.bundle.x509bundle.X509Bundle; import spiffe.bundle.x509bundle.X509BundleSource; -import spiffe.result.Result; +import spiffe.exception.BundleNotFoundException; +import spiffe.exception.X509SvidException; import spiffe.spiffeid.SpiffeId; import spiffe.spiffeid.TrustDomain; import spiffe.svid.x509svid.X509Svid; import javax.net.ssl.X509TrustManager; +import java.io.IOException; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -35,22 +37,19 @@ public class SpiffeTrustManagerTest { X509TrustManager trustManager; @BeforeAll - static void setupClass() { + static void setupClass() throws IOException, CertificateException, X509SvidException { x509Svid = X509Svid .load( Paths.get("../testdata/x509cert.pem"), - Paths.get("../testdata/pkcs8key.pem")) - .getValue(); + Paths.get("../testdata/pkcs8key.pem")); otherX509Svid = X509Svid .load( Paths.get("../testdata/x509cert_other.pem"), - Paths.get("../testdata/key_other.pem")) - .getValue(); + Paths.get("../testdata/key_other.pem")); x509Bundle = X509Bundle .load( - TrustDomain.of("example.org").getValue(), - Paths.get("../testdata/bundle.pem")) - .getValue(); + TrustDomain.of("example.org"), + Paths.get("../testdata/bundle.pem")); } @BeforeEach @@ -60,63 +59,61 @@ public class SpiffeTrustManagerTest { new SpiffeTrustManagerFactory() .engineGetTrustManagers( bundleSource, - () -> Result.ok(acceptedSpiffeIds))[0]; + () -> acceptedSpiffeIds)[0]; } @Test - void checkClientTrusted_passAExpiredCertificate_throwsException() { + void checkClientTrusted_passAExpiredCertificate_throwsException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://example.org/test").getValue() + SpiffeId.parse("spiffe://example.org/test") ); 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 { trustManager.checkClientTrusted(chain, ""); fail("CertificateException was expected"); } catch (CertificateException e) { - assertTrue(e.getMessage().contains("CertificateExpiredException: NotAfter")); + assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage()); } } @Test - void checkClientTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() { + void checkClientTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://example.org/other").getValue() + SpiffeId.parse("spiffe://example.org/other") ); X509Certificate[] 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 { trustManager.checkClientTrusted(chain, ""); fail("CertificateException was expected"); } 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 - void checkClientTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() { + void checkClientTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://other.org/test").getValue() + SpiffeId.parse("spiffe://other.org/test") ); 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 { trustManager.checkClientTrusted(chain, ""); @@ -127,56 +124,56 @@ public class SpiffeTrustManagerTest { } @Test - void checkServerTrusted_passAnExpiredCertificate_ThrowsException() { + void checkServerTrusted_passAnExpiredCertificate_ThrowsException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://example.org/test").getValue() + SpiffeId.parse("spiffe://example.org/test") ); 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 { trustManager.checkServerTrusted(chain, ""); fail("CertificateException was expected"); } catch (CertificateException e) { - assertTrue(e.getMessage().contains("CertificateExpiredException: NotAfter")); + assertEquals("java.security.cert.CertPathValidatorException: validity check failed", e.getMessage()); } } @Test - void checkServerTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() { + void checkServerTrusted_passCertificateWithNonAcceptedSpiffeId_ThrowCertificateException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://example.org/other").getValue() + SpiffeId.parse("spiffe://example.org/other") ); 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 { trustManager.checkServerTrusted(chain, ""); fail("CertificateException was expected"); } 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 - void checkServerTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() { + void checkServerTrusted_passCertificateThatDoesntChainToBundle_ThrowCertificateException() throws BundleNotFoundException { acceptedSpiffeIds = Collections .singletonList( - SpiffeId.parse("spiffe://other.org/test").getValue() + SpiffeId.parse("spiffe://other.org/test") ); 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 { trustManager.checkServerTrusted(chain, "");