Securitybuilder
Fluent builders with typesafe API for the JCA
Install / Use
/learn @tersesystems/SecuritybuilderREADME
Security Builders
This library implements a set of "fluent" API builders for the java.security classes, and provides more typesafe, intuitive API to access trust stores, key stores and keys. The primary purpose of this library is to make small tasks easy, and provide better integration with the JSSE stack.
Also check out pgpainless which does roughly the same thing with PGP API.
Installation
Maven
In your pom.xml:
<dependency>
<groupId>com.tersesystems.securitybuilder</groupId>
<artifactId>securitybuilder</artifactId>
<version>1.0.1</version><!-- see badge for latest version -->
</dependency>
sbt
libraryDependencies += "com.tersesystems.securitybuilder" % "securitybuilder" % "1.0.1"
Usage
The primary use of this package is to set up test X.509 certificates, private keys and trust stores. The Java Cryptography Architecture lays out how to create and initialize certificates, keystores, and so on, but typically does so in frustrating ways.
The assumption is that you'll be working with Java 1.8 but with decent algorithms, so there are a number of preset defaults. The builders are thread-safe and only build when you pull the trigger, but assume immutable input, so don't pass in arrays or lists that you are still fiddling with.
All the classes are in com.tersesystems.securitybuilder package.
import com.tersesystems.securitybuilder.*;
In general, if you're just using the JCA, there are some based off Latacora's Cryptographic Right Answers:
- Use RSA with 2048 bit key length and SHA-2 for public and private keys.
- Use AES-GCM for encryption but SEE WARNING BELOW, and never reuse the IV. There is no provable difference between AES-128 and AES-256, so don't worry about it and use AES-256.
- If you're going over the network, you generally want full on TLS, so use JSSE with debugjsse as the provider to see what's going on under the hood. Be aware that SSLEngine/SSLSocket does not know that you're using HTTPS, so you need to define hostname verification yourself by setting
sslParameters.setEndpointIdentificationAlgorithm("HTTPS"). - Use an HMAC with at least SHA256, and a secret key that has at least 96 bits of entropy --
EntropySource.salt()uses 256 bits. - Use a MessageDigest with at least SHA256.
- Use PBKDF2 with a SHA-2 HMAC if you have to, but if you can use jBCrypt or scrypt go with that.
- There's no real need to use your own SecureRandom, and you don't need to use
useInstanceStrong, the entropy pool is the same and you may get blocking. UseEntropySource.
WARNING
Please be aware that some of the algorithms in the JCA are way, way out of date.
If you need a cryptography API, DON'T USE THE JCA! Even with these builders, building your own crypto using a low level library is like juggling chainsaws in the dark. In particular, low level libraries don't do key management and key rotation very well.
Use Google Tink instead, which has support for storing keysets, symmetric key encryption, digital signatures, envelope encryption and key rotation.
Google Tink doesn't do everything: in that case, I recommend looking for a fallback professional high-level library rather than rolling your own. This will typically involve an binding on top of a C library like lazysodium on top of libsodium, or a specialized crypto framework like Noise-Java implementing the Noise Protocol Framework.
JSSE (Java TLS Classes)
X509CertificateCreator
Creates an X509Certificate or a chain of X509Certificate.
Very useful for building up certificates if you use chain().
public class X509CertificateCreatorTest {
@Test
public void testFunctionalStyle() throws Exception {
FinalStage<RSAKeyPair> keyPairCreator = KeyPairCreator.creator().withRSA().withKeySize(2048);
RSAKeyPair rootKeyPair = keyPairCreator.create();
RSAKeyPair intermediateKeyPair = keyPairCreator.create();
RSAKeyPair eePair = keyPairCreator.create();
IssuerStage<RSAPrivateKey> creator =
X509CertificateCreator.creator().withSHA256withRSA().withDuration(Duration.ofDays(365));
String issuer = "CN=letsencrypt.derp,O=Root CA";
X509Certificate[] chain =
creator
.withRootCA(issuer, rootKeyPair, 2)
.chain(
rootKeyPair.getPrivate(),
rootCreator ->
rootCreator
.withPublicKey(intermediateKeyPair.getPublic())
.withSubject("OU=intermediate CA")
.withCertificateAuthorityExtensions(0)
.chain(
intermediateKeyPair.getPrivate(),
intCreator ->
intCreator
.withPublicKey(eePair.getPublic())
.withSubject("CN=tersesystems.com")
.withEndEntityExtensions()
.chain()))
.create();
PrivateKeyStore privateKeyStore =
PrivateKeyStore.create("tersesystems.com", eePair.getPrivate(), chain);
TrustStore trustStore = TrustStore.create(singletonList(chain[2]), cert -> "letsencrypt.derp");
try {
final PKIXCertPathValidatorResult result = CertificateChainValidator.validator()
.withAnchor(new TrustAnchor(issuer, rootKeyPair.getPublic(), null))
.withCertificates(chain)
.validate();
final PublicKey subjectPublicKey = result.getPublicKey();
assertThat(subjectPublicKey).isEqualTo(eePair.getPublic());
} catch (final CertPathValidatorException cpve) {
fail("Cannot test exception", cpve);
}
SSLContext sslContext =
SSLContextBuilder.builder()
.withTLS()
.withKeyManager(
KeyManagerBuilder.builder()
.withSunX509()
.withPrivateKeyStore(privateKeyStore)
.build())
.withTrustManager(
TrustManagerBuilder.builder()
.withDefaultAlgorithm()
.withTrustStore(trustStore)
.build())
.build();
assertThat(sslContext).isNotNull();
}
}
Admittedly this doesn't look very simple, but you should see the code it replaces.
CertificateBuilder
Builds a java.security.Certificate from a source.
If you use withX509(), it will give you an X509Certificate.
public class CertificateBuilderTest {
@Test
public void testX509Certificate() {
final InputStream inputStream = getClass().getResourceAsStream("/playframework.pem");
try {
final X509Certificate x509Certificate =
CertificateBuilder.builder()
.withX509()
.withInputStream(inputStream)
.build();
assertThat(x509Certificate.getSigAlgName()).isEqualTo("SHA256withECDSA");
} catch (final CertificateException e) {
fail(e.getMessage(), e);
}
}
}
KeyManagerBuilder
Builds a KeyManager from input. If you use withNewSunX509(), then you get a X509ExtendedKeyManager that can differentiate between RSA / DSA keys, pick out unexpired keys, and use password specific entries out of the store (if you use the keystore builder defined below). See Key Managers and Key Stores for the gory details.
Recommend using with debugjsse provider.
public class KeyManagerBuilderTest {
@Test
public void testKeyManagerWithKeyStore() {
try {
final KeyStore keyStore = KeyStoreBuilder.empty();
final X509ExtendedKeyManager keyManager =
KeyManagerBuilder.builder()
.withNewSunX509()
.withKeyStore(keyStore, "".toCharArray())
.build();
assertThat(keyManager.getPrivateKey("derp")).isNull();
} catch (final GeneralSecurityException e) {
fail(e.getMessage(), e);
}
}
}
TrustManagerBuilder
Builds a [TrustManager](https://docs.oracle.com/javase/8/
