Skip to content

[feature] Facility for listing providers and algorithms #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 45 additions & 11 deletions src/main/java/ro/kuberam/libs/java/crypto/CryptoError.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,49 @@
*/
package ro.kuberam.libs.java.crypto;

import javax.annotation.Nullable;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;

public enum CryptoError {

NoSuchAlgorithmException("crypto:unknown-algorithm", "The specified algorithm is not supported."),
UNKNOWN_ALGORITHM("crypto:unknown-algorithm", "The specified algorithm is not supported.", NoSuchAlgorithmException.class),
UNKNOWN_PROVIDER("crypto:unknown-provider", "The specified provider is not available."),
SIGNATURE_TYPE("crypto:signature-type", "The specified signature type is not supported."),
UNREADABLE_KEYSTORE("crypto:unreadable-keystore", "I/O error while reading keystore, or the password is incorrect."),
DENIED_KEYSTORE("crypto:denied-keystore", "Permission denied to read keystore."),
KEYSTORE_URL("crypto:keystore-url", "The keystore URL is invalid."),
KeyStoreException("crypto:keystore-type", "The keystore type is not supported."),
KEYSTORE_TYPE("crypto:keystore-type", "The keystore type is not supported.", KeyStoreException.class),
ALIAS_KEY("crypto:alias-key", "Cannot find key for alias in given keystore."),
SIGNATURE_ELEMENT("crypto:signature-element", "Cannot find Signature element."),
NoSuchPaddingException("crypto:inexistent-padding", "No such padding."),
BadPaddingException("crypto:incorrect-padding", "Incorrect padding."),
INEXISTENT_PADDING("crypto:inexistent-padding", "No such padding.", NoSuchPaddingException.class),
INCORRECT_PADDING("crypto:incorrect-padding", "Incorrect padding.", BadPaddingException.class),
ENCRYPTION_TYPE("crypto:encryption-type", "The encryption type is not supported."),
InvalidKeySpecException("crypto:invalid-crypto-key", "The cryptographic key is invalid."),
InvalidKeyException("crypto:invalid-crypto-key", "The cryptographic key is invalid."),
IllegalBlockSizeException("crypto:block-size", "Illegal block size."),
INVALID_CRYPTO_KEY("crypto:invalid-crypto-key", "The cryptographic key is invalid.", InvalidKeySpecException.class, InvalidKeyException.class),
BLOCK_SIZE("crypto:block-size", "Illegal block size.", IllegalBlockSizeException.class),
DECRYPTION_TYPE("crypto:decryption-type", "The decryption type is not supported."),
NoSuchProviderException("crypto:no-provider", "The provider is not set."),
NO_PROVIDER("crypto:no-provider", "The provider is not set.", NoSuchProviderException.class),
INPUT_RESOURCES("crypto.input-resources", "The 'enveloped' and 'enveloping' signatures have to be applied to only one resource."),
INCORRECT_INITIALIZATION_VECTOR("crypto:incorrect-initialization-vector", "The initialization vector is not correct");

private String code;
private String message;
private final String code;
private final String message;
@Nullable final Class<? extends Throwable>[] describesExceptions;

CryptoError(final String code, final String message) {
this(code, message, null);
}

CryptoError(String code, String message) {
CryptoError(final String code, final String message, @Nullable final Class<? extends Throwable>... describesExceptions) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity:
The parameter is described as @Nullable Class<? extends Thowable>... describesExceptions
But the property is @Nullable final Class<? extends Throwable>[] describesExceptions;
What are the three dots for? Can they be used interchangeably?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the three dots for

The ellipsis indicates a varargs of the specified type.

Can they be used interchangeably?

No. Ellipsis are only valid on the last arg of a parameter list, and you have to be careful on typing. Consider... void fn(String... x), is that requesting an array of string or 1+n strings, how you call fn is important.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so very much like the ...rest argument in JavaScript. Interesting! Thanks for clarifying this.

this.code = code;
this.message = message;
this.describesExceptions = describesExceptions;
}

public String getCode() {
Expand All @@ -59,4 +75,22 @@ public String getMessage() {
public String getDescription() {
return this.code + ", " + this.message;
}

public static @Nullable CryptoError describeException(@Nullable final Throwable t) {
if (t == null) {
return null;
}

for (final CryptoError cryptoError : values()) {
if (cryptoError.describesExceptions != null) {
for (final Class<? extends Throwable> describesException : cryptoError.describesExceptions) {
if (describesException == t.getClass()) {
return cryptoError;
}
}
}
}

return null;
}
}
29 changes: 19 additions & 10 deletions src/main/java/ro/kuberam/libs/java/crypto/CryptoException.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,37 @@
*/
package ro.kuberam.libs.java.crypto;

import javax.annotation.Nullable;

public class CryptoException extends Exception {
/**
*
*/
private static final long serialVersionUID = -2606956271206243301L;
private CryptoError cryptoError;
@Nullable private final CryptoError cryptoError;

public CryptoException(CryptoError cryptoError) {
public CryptoException(final CryptoError cryptoError) {
super(cryptoError.getDescription());
this.cryptoError = cryptoError;
}

public CryptoException(CryptoError cryptoError, Throwable cause) {
public CryptoException(final CryptoError cryptoError, final Throwable cause) {
super(cryptoError.getDescription(), cause);
this.cryptoError = cryptoError;
}

public static CryptoException fromCause(final Throwable cause) {
final CryptoError cryptoError = CryptoError.valueOf(cause.getClass().getSimpleName());
return new CryptoException(cryptoError, cause);
}
public CryptoException(final Throwable cause) {
super(getDesc(cause), cause);
this.cryptoError = CryptoError.describeException(cause);
}

private static String getDesc(final Throwable cause) {
final CryptoError cryptoError = CryptoError.describeException(cause);
if (cryptoError != null) {
return cryptoError.getDescription();
} else {
return cause.getMessage();
}
}

@Nullable
public CryptoError getCryptoError() {
return cryptoError;
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/ro/kuberam/libs/java/crypto/Parameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String getCanonicalizationAlgorithm() {

public void setCanonicalizationAlgorithm(final String canonicalizationAlgorithm) throws CryptoException {
if (!contains(CANONICALIZATION_ALGORITHM_VALUES, canonicalizationAlgorithm)) {
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}

if (canonicalizationAlgorithm.equals("exclusive")) {
Expand All @@ -62,7 +62,7 @@ public String getDigestAlgorithm() {

public void setDigestAlgorithm(final String digestAlgorithm) throws CryptoException {
if (!contains(DIGEST_ALGORITHM_VALUES, digestAlgorithm)) {
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}

if (digestAlgorithm.equals("SHA256")) {
Expand All @@ -80,7 +80,7 @@ public String getSignatureAlgorithm() {

public void setSignatureAlgorithm(final String signatureAlgorithm) throws CryptoException {
if (!contains(SIGNATURE_ALGORITHM_VALUES, signatureAlgorithm)) {
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}

if (signatureAlgorithm.equals("DSA_SHA1")) {
Expand Down
111 changes: 97 additions & 14 deletions src/main/java/ro/kuberam/libs/java/crypto/digest/Hash.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Optional;
import java.security.*;
import java.util.*;
import javax.annotation.Nullable;

import org.slf4j.Logger;
Expand All @@ -47,16 +45,20 @@ public class Hash {
private static final Logger LOG = LoggerFactory.getLogger(Hash.class);

public static String hashString(final String data, final String algorithm) throws CryptoException {
return hashString(data, algorithm, null);
return hashString(data, algorithm, null, null);
}

public static String hashString(final String data, final String algorithm, final @Nullable String format)
public static String hashString(final String data, final String algorithm, @Nullable final String provider) throws CryptoException {
return hashString(data, algorithm, provider, null);
}

public static String hashString(final String data, final String algorithm, @Nullable final String provider, final @Nullable String format)
throws CryptoException {

// TODO: validate the format
final String actualFormat = Optional.ofNullable(format).filter(str -> !str.isEmpty()).orElse("base64");

final MessageDigest messageDigester = getMessageDigester(algorithm);
final MessageDigest messageDigester = getMessageDigester(algorithm, provider);
messageDigester.update(data.getBytes(StandardCharsets.UTF_8));

final byte[] resultBytes = messageDigester.digest();
Expand All @@ -70,17 +72,22 @@ public static String hashString(final String data, final String algorithm, final

public static String hashBinary(final InputStream data, final String algorithm)
throws CryptoException, IOException {
return hashBinary(data, algorithm, null);
return hashBinary(data, algorithm, null, null);
}

public static String hashBinary(final InputStream data, final String algorithm, @Nullable final String provider)
throws CryptoException, IOException {
return hashBinary(data, algorithm, provider, null);
}

public static String hashBinary(final InputStream data, final String algorithm, @Nullable final String format)
public static String hashBinary(final InputStream data, final String algorithm, @Nullable final String provider, @Nullable final String format)
throws CryptoException, IOException {

// TODO: validate the format
final String actualFormat = Optional.ofNullable(format).filter(str -> !str.isEmpty()).orElse("base64");

final byte[] resultBytes;
final MessageDigest messageDigester = getMessageDigester(algorithm);
final MessageDigest messageDigester = getMessageDigester(algorithm, provider);

final byte[] buf = new byte[Buffer.TRANSFER_SIZE];
int read = -1;
Expand All @@ -100,11 +107,87 @@ public static String hashBinary(final InputStream data, final String algorithm,
return result;
}

private static MessageDigest getMessageDigester(final String algorithm) throws CryptoException {
private static MessageDigest getMessageDigester(final String algorithm, @Nullable final String provider) throws CryptoException {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(CryptoError.NoSuchAlgorithmException, e);
if (provider != null) {
return MessageDigest.getInstance(algorithm, provider);
} else {
return MessageDigest.getInstance(algorithm);
}
} catch (final NoSuchAlgorithmException e) {
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM, e);
} catch (final NoSuchProviderException e) {
throw new CryptoException(CryptoError.UNKNOWN_PROVIDER, e);
}
}

/**
* Returns a list of security providers which
* offer hash services.
*
* @return the names of the security providers which
* offer hash services.
*/
public static List<String> listProviders() {
final List<String> hashProviders = new ArrayList<>();

final Provider[] providers = Security.getProviders();
for (final Provider provider : providers) {
final Set<Provider.Service> services = provider.getServices();
for (final Provider.Service service : services) {
if (service.getType().equals("MessageDigest")) {
hashProviders.add(provider.getName());
break;
}
}
}

return hashProviders;
}

/**
* Returns a Map of all hash services from each
* security provider.
*
* @return a map from key: `service provider name` to value: `algorithm name(s)`.
*/
public static Map<String, Set<String>> listAlgorithms() {
final Map<String, Set<String>> algorithms = new HashMap<>();

final Provider[] providers = Security.getProviders();
for (final Provider provider : providers) {
final Set<Provider.Service> services = provider.getServices();
for (final Provider.Service service : services) {
if (service.getType().equals("MessageDigest")) {

final Set<String> providerAlgs = algorithms.computeIfAbsent(provider.getName(), k -> new HashSet<>());
providerAlgs.add(service.getAlgorithm());
}
}
}

return algorithms;
}

/**
* Returns a Map of all hash services from a
* security provider.
*
* @param providerName the name of the security provider.
*
* @return the names of the algorithms provided by the security provider.
*/
public static Set<String> listAlgorithms(final String providerName) {
final Set<String> algorithms = new HashSet<>();

final Provider provider = Security.getProvider(providerName);
final Set<Provider.Service> services = provider.getServices();
for (final Provider.Service service : services) {
if (service.getType().equals("MessageDigest")) {
algorithms.add(service.getAlgorithm());
}
}

return algorithms;
}
}
4 changes: 2 additions & 2 deletions src/main/java/ro/kuberam/libs/java/crypto/digest/Hmac.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static byte[] hmac(final byte[] data, final byte[] secretKey, String algo
return mac.doFinal(data);

} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw CryptoException.fromCause(e);
throw new CryptoException(e);
}
}

Expand All @@ -125,7 +125,7 @@ public static byte[] hmac(final InputStream data, final byte[] secretKey, String
return mac.doFinal();

} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw CryptoException.fromCause(e);
throw new CryptoException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static String generate(Document inputDoc, String mechanismType, String ca
NodeList nodes = (NodeList) expr.evaluate(inputDoc, XPathConstants.NODESET);
if (nodes.getLength() < 1) {
// TODO this error message has to replaced
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}

// Node nodeToSign = nodes.item(0);
Expand Down Expand Up @@ -134,7 +134,7 @@ public static String generate(Document inputDoc, String mechanismType, String ca
try {
keyStore = KeyStore.getInstance(certificateDetails[0]);
} catch (KeyStoreException e) {
throw CryptoException.fromCause(e);
throw new CryptoException(e);
}
keyStore.load(keyStoreInputStream, certificateDetails[1].toCharArray());
String alias = certificateDetails[2];
Expand Down Expand Up @@ -219,14 +219,14 @@ public static String generate(Document inputDoc, String mechanismType, String ca
return serializer.writeToString(sigParent);
}
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new CryptoException(CryptoError.NoSuchAlgorithmException, e);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM, e);
} catch (CertificateException e) {
// TODO error code needs improving
throw new CryptoException(CryptoError.InvalidKeySpecException, e);
throw new CryptoException(CryptoError.INVALID_CRYPTO_KEY, e);
} catch (KeyStoreException e) {
throw new CryptoException(CryptoError.UNREADABLE_KEYSTORE, e);
} catch (UnrecoverableKeyException | KeyException e) {
throw new CryptoException(CryptoError.InvalidKeySpecException, e);
throw new CryptoException(CryptoError.INVALID_CRYPTO_KEY, e);
} catch (ParserConfigurationException | XPathExpressionException | MarshalException e) {
throw new IOException(e);
}
Expand Down Expand Up @@ -268,7 +268,7 @@ private static String getCanonicalizationAlgorithmUri(String canonicalizationAlg
return CanonicalizationMethod.INCLUSIVE;

default:
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}
}

Expand All @@ -285,7 +285,7 @@ private static String getDigestAlgorithmURI(String digestAlgorithm) throws Crypt
return DigestMethod.SHA1;

default:
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}
}

Expand All @@ -299,7 +299,7 @@ private static String getSignatureAlgorithmURI(String signatureAlgorithm) throws
return SignatureMethod.RSA_SHA1;

default:
throw new CryptoException(CryptoError.NoSuchAlgorithmException);
throw new CryptoException(CryptoError.UNKNOWN_ALGORITHM);
}
}
}
Loading