/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.crypto;
import com.google.inject.Inject;
import io.bitsquare.storage.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.*;
import java.util.Date;
// TODO: use a password protection for key storage
public class KeyStorage {
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
public static final String DIR_KEY = "keyStorageDir";
public enum KeyEntry {
MSG_SIGNATURE("sig", Sig.KEY_ALGO),
MSG_ENCRYPTION("enc", Encryption.ASYM_KEY_ALGO);
private final String fileName;
private final String algorithm;
KeyEntry(String fileName, String algorithm) {
this.fileName = fileName;
this.algorithm = algorithm;
}
public String getFileName() {
return fileName;
}
public String getAlgorithm() {
return algorithm;
}
@NotNull
@Override
public String toString() {
return "Key{" +
"fileName='" + fileName + '\'' +
", algorithm='" + algorithm + '\'' +
'}';
}
}
private final File storageDir;
@Inject
public KeyStorage(@Named(DIR_KEY) File storageDir) {
this.storageDir = storageDir;
}
public boolean allKeyFilesExist() {
return fileExists(KeyEntry.MSG_SIGNATURE) && fileExists(KeyEntry.MSG_ENCRYPTION);
}
private boolean fileExists(KeyEntry keyEntry) {
return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists();
}
public KeyPair loadKeyPair(KeyEntry keyEntry) {
FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20);
// long now = System.currentTimeMillis();
try {
KeyFactory keyFactory = KeyFactory.getInstance(keyEntry.getAlgorithm(), "BC");
PublicKey publicKey;
PrivateKey privateKey;
File filePrivateKey = new File(storageDir + "/" + keyEntry.getFileName() + ".key");
try (FileInputStream fis = new FileInputStream(filePrivateKey.getPath())) {
byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
fis.read(encodedPrivateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException | IOException e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
}
if (privateKey instanceof RSAPrivateCrtKey) {
RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) privateKey;
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent());
publicKey = keyFactory.generatePublic(publicKeySpec);
} else if (privateKey instanceof DSAPrivateKey) {
DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey;
DSAParams dsaParams = dsaPrivateKey.getParams();
BigInteger p = dsaParams.getP();
BigInteger q = dsaParams.getQ();
BigInteger g = dsaParams.getG();
BigInteger y = g.modPow(dsaPrivateKey.getX(), p);
KeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g);
publicKey = keyFactory.generatePublic(publicKeySpec);
} else {
throw new RuntimeException("Unsupported key algo" + keyEntry.getAlgorithm());
}
log.debug("load completed in {} msec", System.currentTimeMillis() - new Date().getTime());
return new KeyPair(publicKey, privateKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchProviderException e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
}
}
public void saveKeyRing(KeyRing keyRing) {
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName());
savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName());
}
private void savePrivateKey(PrivateKey privateKey, String name) {
if (!storageDir.exists())
storageDir.mkdir();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
fos.write(pkcs8EncodedKeySpec.getEncoded());
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException("Could not save key " + name, e);
}
}
}