/*
* Kontalk Java client
* Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.crypto;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.encoders.Hex;
import org.kontalk.misc.KonException;
/**
* Personal PGP key(s).
*/
public final class PersonalKey {
private static final Logger LOGGER = Logger.getLogger(PersonalKey.class.getName());
/** (Server) Authentication key. */
private final PGPPublicKey mAuthKey;
/** (Server) Login key. */
private final PrivateKey mLoginKey;
/** Signing key. */
private final PGPKeyPair mSignKey;
/** En-/decryption key. */
private final PGPKeyPair mEncryptKey;
/** X.509 bridge certificate. */
private final X509Certificate mBridgeCert;
/** Primary user ID. */
private final String mUID;
private PersonalKey(PGPKeyPair authKP,
PGPKeyPair signKP,
PGPKeyPair encryptKP,
X509Certificate bridgeCert,
String uid) throws PGPException {
mAuthKey = authKP.getPublicKey();
mLoginKey = PGPUtils.convertPrivateKey(authKP.getPrivateKey());
mSignKey = signKP;
mEncryptKey = encryptKP;
mBridgeCert = bridgeCert;
mUID = uid;
}
PGPPrivateKey getPrivateEncryptionKey() {
return mEncryptKey.getPrivateKey();
}
PGPPrivateKey getPrivateSigningKey() {
return mSignKey.getPrivateKey();
}
int getSigningAlgorithm() {
return mSignKey.getPublicKey().getAlgorithm();
}
public X509Certificate getBridgeCertificate() {
return mBridgeCert;
}
public PrivateKey getServerLoginKey() {
return mLoginKey;
}
/** Returns the first user ID in the key. */
public String getUserId() {
return mUID;
}
public String getFingerprint() {
return Hex.toHexString(mAuthKey.getFingerprint());
}
/** Creates a {@link PersonalKey} from private keyring data.
* X.509 bridge certificate is created from key data.
*/
public static PersonalKey load(byte[] privateKeyData,
char[] passphrase)
throws KonException, IOException, PGPException, CertificateException, NoSuchProviderException {
return load(privateKeyData, passphrase, null);
}
/** Creates a {@link PersonalKey} from private keyring data. */
@SuppressWarnings("unchecked")
public static PersonalKey load(byte[] privateKeyData,
char[] passphrase,
byte[] bridgeCertData)
throws KonException, IOException, PGPException, CertificateException, NoSuchProviderException {
PGPSecretKeyRing secRing = new PGPSecretKeyRing(privateKeyData, PGPUtils.FP_CALC);
PGPSecretKey authKey = null;
PGPSecretKey signKey = null;
PGPSecretKey encrKey = null;
// assign from key ring
Iterator<PGPSecretKey> skeys = secRing.getSecretKeys();
while (skeys.hasNext()) {
PGPSecretKey key = skeys.next();
if (key.isMasterKey()) {
// master key: authentication / legacy: signing
authKey = key;
} else if (PGPUtils.isSigningKey(key.getPublicKey())) {
// sub keys: encryption and signing / legacy: only encryption
signKey = key;
} else if (key.getPublicKey().isEncryptionKey()) {
encrKey = key;
}
}
// legacy: auth key is actually signing key
if (signKey == null && authKey != null && authKey.isSigningKey()) {
LOGGER.info("legacy key");
signKey = authKey;
}
if (authKey == null || signKey == null || encrKey == null) {
LOGGER.warning("something could not be found, "
+"sign="+signKey+ ", auth="+authKey+", encr="+encrKey);
throw new KonException(KonException.Error.LOAD_KEY,
new PGPException("could not find all keys in key data"));
}
// decrypt private keys
PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(PGPUtils.PROVIDER)
.build(passphrase);
PGPKeyPair authKeyPair = PGPUtils.decrypt(authKey, decryptor);
PGPKeyPair signKeyPair = PGPUtils.decrypt(signKey, decryptor);
PGPKeyPair encryptKeyPair = PGPUtils.decrypt(encrKey, decryptor);
// user ID
Iterator<?> uidIt = authKey.getUserIDs();
if (!uidIt.hasNext())
throw new KonException(KonException.Error.LOAD_KEY,
new PGPException("no UID in key"));
String uid = (String) uidIt.next();
// X.509 bridge certificate
X509Certificate bridgeCert;
if (bridgeCertData != null) {
bridgeCert = PGPUtils.loadX509Cert(bridgeCertData);
} else {
// public key ring
ByteArrayOutputStream out = new ByteArrayOutputStream();
authKeyPair.getPublicKey().encode(out);
signKeyPair.getPublicKey().encode(out);
encryptKeyPair.getPublicKey().encode(out);
byte[] publicKeyRingData = out.toByteArray();
PGPPublicKeyRing pubKeyRing = new BcPGPPublicKeyRing(publicKeyRingData);
// re-create cert
bridgeCert = createX509Certificate(authKeyPair, pubKeyRing);
}
return new PersonalKey(authKeyPair, signKeyPair, encryptKeyPair, bridgeCert, uid);
}
private static X509Certificate createX509Certificate(PGPKeyPair keyPair,
PGPPublicKeyRing keyRing)
throws KonException {
try {
return X509Bridge.createCertificate(keyPair, keyRing.getEncoded());
} catch (InvalidKeyException | IllegalStateException | NoSuchAlgorithmException |
SignatureException | CertificateException | NoSuchProviderException |
PGPException | IOException | OperatorCreationException ex) {
LOGGER.log(Level.WARNING, "can't create X.509 certificate");
throw new KonException(KonException.Error.LOAD_KEY, ex);
}
}
}