Skip to content
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

Added P-384 Utils #21

Merged
merged 12 commits into from
Aug 19, 2021
46 changes: 46 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<gson.version>2.8.7</gson.version>
<guava.version>30.1.1-jre</guava.version>
<siv-mode.version>1.4.3</siv-mode.version>
<bouncycastle.version>1.69</bouncycastle.version>
<slf4j.version>1.7.31</slf4j.version>

<!-- test dependencies -->
Expand Down Expand Up @@ -60,6 +61,14 @@
<version>${siv-mode.version}</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<!-- see maven-shade-plugin; we don't want this as a transitive dependency in other projects -->
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down Expand Up @@ -164,6 +173,43 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>false</createDependencyReducedPom>
<createSourcesJar>false</createSourcesJar>
<artifactSet>
<includes>
<include>org.bouncycastle:*</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.bouncycastle</pattern>
<shadedPattern>org.cryptomator.cryptolib.org.bouncycastle</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>org.bouncycastle:*</artifact>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/org/cryptomator/cryptolib/common/ECKeyPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.cryptomator.cryptolib.common;

import com.google.common.base.Preconditions;

import javax.security.auth.Destroyable;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.Objects;

public class ECKeyPair implements Destroyable {

private final KeyPair keyPair;
private boolean destroyed;

ECKeyPair(KeyPair keyPair) {
Preconditions.checkArgument(keyPair.getPrivate() instanceof ECPrivateKey);
Preconditions.checkArgument(keyPair.getPublic() instanceof ECPublicKey);
this.keyPair = keyPair;
}

public KeyPair keyPair() {
return keyPair;
}

public ECPrivateKey getPrivate() {
Preconditions.checkState(!destroyed);
assert keyPair.getPrivate() instanceof ECPrivateKey;
return (ECPrivateKey) keyPair.getPrivate();
}

public ECPublicKey getPublic() {
Preconditions.checkState(!destroyed);
assert keyPair.getPublic() instanceof ECPublicKey;
return (ECPublicKey) keyPair.getPublic();
}

@Override
public boolean isDestroyed() {
return destroyed;
}

@Override
public void destroy() {
Destroyables.destroySilently(keyPair.getPrivate());
destroyed = true;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ECKeyPair that = (ECKeyPair) o;
return MessageDigest.isEqual(this.getPublic().getEncoded(), that.getPublic().getEncoded());
}

@Override
public int hashCode() {
int result = Objects.hash(keyPair.getPublic().getAlgorithm());
result = 31 * result + Arrays.hashCode(keyPair.getPublic().getEncoded());
return result;
}
}
100 changes: 100 additions & 0 deletions src/main/java/org/cryptomator/cryptolib/common/P384KeyPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.cryptomator.cryptolib.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.spec.ECGenParameterSpec;

public class P384KeyPair extends ECKeyPair {

private static final String EC_ALG = "EC";
private static final String EC_CURVE_NAME = "secp384r1";
private static final String SIGNATURE_ALG = "SHA384withECDSA";

private P384KeyPair(KeyPair keyPair) {
super(keyPair);
}

public static P384KeyPair generate() {
KeyPair keyPair = getKeyPairGenerator().generateKeyPair();
return new P384KeyPair(keyPair);
}

/**
* Loads a key pair from the given file
*
* @param p12File A .p12 file
* @param passphrase The password to protect the key material
* @return
* @throws IOException In case of I/O errors
* @throws Pkcs12PasswordException If the supplied password is incorrect
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public static P384KeyPair load(Path p12File, char[] passphrase) throws IOException, Pkcs12PasswordException, Pkcs12Exception {
try (InputStream in = Files.newInputStream(p12File, StandardOpenOption.READ)) {
return load(in, passphrase);
}
}

/**
* Loads a key pair from the given input stream
*
* @param in An input stream providing PKCS#12 formatted data
* @param passphrase The password to protect the key material
* @return
* @throws IOException In case of I/O errors
* @throws Pkcs12PasswordException If the supplied password is incorrect
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public static P384KeyPair load(InputStream in, char[] passphrase) throws IOException, Pkcs12PasswordException, Pkcs12Exception {
KeyPair keyPair = Pkcs12Helper.importFrom(in, passphrase);
return new P384KeyPair(keyPair);
}

/**
* Stores this key pair in PKCS#12 format at the given path
*
* @param p12File The path of the .p12 file
* @param passphrase The password to protect the key material
* @throws IOException In case of I/O errors
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public void store(Path p12File, char[] passphrase) throws IOException, Pkcs12Exception {
Path tmpFile = p12File.resolveSibling(p12File.getFileName().toString() + ".tmp");
try (OutputStream out = Files.newOutputStream(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
store(out, passphrase);
}
Files.move(tmpFile, p12File, StandardCopyOption.REPLACE_EXISTING);
}

/**
* Stores this key in PKCS#12 format to the given output stream
*
* @param out The output stream to which the data will be written
* @param passphrase The password to protect the key material
* @throws IOException In case of I/O errors
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public void store(OutputStream out, char[] passphrase) throws IOException, Pkcs12Exception {
Pkcs12Helper.exportTo(keyPair(), out, passphrase, SIGNATURE_ALG);
}

private static KeyPairGenerator getKeyPairGenerator() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(EC_ALG);
keyGen.initialize(new ECGenParameterSpec(EC_CURVE_NAME));
return keyGen;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(EC_CURVE_NAME + " curve not supported");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cryptomator.cryptolib.common;

import org.cryptomator.cryptolib.api.CryptoException;

/**
* Loading from or exporting to PKCS12 format failed.
*/
public class Pkcs12Exception extends CryptoException {

protected Pkcs12Exception(String message, Throwable cause) {
super(message, cause);
}

}
94 changes: 94 additions & 0 deletions src/main/java/org/cryptomator/cryptolib/common/Pkcs12Helper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.cryptomator.cryptolib.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.Period;

class Pkcs12Helper {

private static final String X509_ISSUER = "CN=Cryptomator";
private static final String X509_SUBJECT = "CN=Self Signed Cert";
private static final int X509_VALID_DAYS = 3560;
private static final String KEYSTORE_ALIAS_KEY = "key";
private static final String KEYSTORE_ALIAS_CERT = "crt";

private Pkcs12Helper() {
}

/**
* Stores the given key pair in PKCS#12 format.
*
* @param keyPair The key pair to export
* @param out The output stream to which the result will be written
* @param pw The password to protect the key material
* @param signatureAlg A suited signature algorithm to sign a x509v3 cert holding the public key
* @throws IOException In case of I/O errors
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public static void exportTo(KeyPair keyPair, OutputStream out, char[] pw, String signatureAlg) throws IOException, Pkcs12Exception {
try {
KeyStore keyStore = getKeyStore();
keyStore.load(null, pw);
X509Certificate cert = X509CertBuilder.init(keyPair, signatureAlg) //
.withIssuer(X509_ISSUER) //
.withSubject(X509_SUBJECT) //
.withNotBefore(Instant.now()) //
.withNotAfter(Instant.now().plus(Period.ofDays(X509_VALID_DAYS)))
.build();
X509Certificate[] chain = new X509Certificate[]{cert};
keyStore.setKeyEntry(KEYSTORE_ALIAS_KEY, keyPair.getPrivate(), pw, chain);
keyStore.setCertificateEntry(KEYSTORE_ALIAS_CERT, cert);
keyStore.store(out, pw);
} catch (IllegalArgumentException | GeneralSecurityException e) {
throw new Pkcs12Exception("Failed to store PKCS12 file.", e);
}
}

/**
* Loads a key pair from PKCS#12 format.
*
* @param in Where to load the key pair from
* @param pw The password to protect the key material
* @throws IOException In case of I/O errors
* @throws Pkcs12PasswordException If the supplied password is incorrect
* @throws Pkcs12Exception If any cryptographic operation fails
*/
public static KeyPair importFrom(InputStream in, char[] pw) throws IOException, Pkcs12PasswordException, Pkcs12Exception {
try {
KeyStore keyStore = getKeyStore();
keyStore.load(in, pw);
PrivateKey sk = (PrivateKey) keyStore.getKey(KEYSTORE_ALIAS_KEY, pw);
PublicKey pk = keyStore.getCertificate(KEYSTORE_ALIAS_CERT).getPublicKey();
return new KeyPair(pk, sk);
} catch (UnrecoverableKeyException e) {
throw new Pkcs12PasswordException(e);
} catch (IOException e) {
if (e.getCause() instanceof UnrecoverableKeyException) {
throw new Pkcs12PasswordException(e);
} else {
throw e;
}
} catch (GeneralSecurityException e) {
throw new Pkcs12Exception("Failed to load PKCS12 file.", e);
}
}

private static KeyStore getKeyStore() {
try {
return KeyStore.getInstance("PKCS12");
} catch (KeyStoreException e) {
throw new IllegalStateException("Every implementation of the Java platform is required to support PKCS12.");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.cryptomator.cryptolib.common;

/**
* Loading from PKCS12 format failed due to wrong password.
*/
public class Pkcs12PasswordException extends Pkcs12Exception {

protected Pkcs12PasswordException(Throwable cause) {
super("Wrong password", cause);
}

}
Loading