blob: 68f8a455555582367ad857f928a8fb3f18a4bb1b [file] [log] [blame]
/*
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.gpg.bc.internal.keys;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.util.Arrays;
import org.eclipse.jgit.gpg.bc.internal.BCText;
/**
* A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption.
* It accepts an AAD in the factory's constructor, so the factory can be used to
* create a {@link PBESecretKeyDecryptor} only for a particular input.
* <p>
* For JGit's needs, this is sufficient, but for a general upstream
* implementation that limitation might not be acceptable.
* </p>
*/
class OCBPBEProtectionRemoverFactory
implements PBEProtectionRemoverFactory {
private final PGPDigestCalculatorProvider calculatorProvider;
private final char[] passphrase;
private final byte[] aad;
/**
* Creates a new factory instance with the given parameters.
* <p>
* Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s
* created by the factory can be used to decrypt only a particular input
* matching this AAD.
* </p>
*
* @param passphrase to use for secret key derivation
* @param calculatorProvider for computing digests
* @param aad for the OCB decryption
*/
OCBPBEProtectionRemoverFactory(char[] passphrase,
PGPDigestCalculatorProvider calculatorProvider, byte[] aad) {
this.calculatorProvider = calculatorProvider;
this.passphrase = passphrase;
this.aad = aad;
}
@Override
public PBESecretKeyDecryptor createDecryptor(String protection)
throws PGPException {
return new PBESecretKeyDecryptor(passphrase, calculatorProvider) {
@Override
public byte[] recoverKeyData(int encAlgorithm, byte[] key,
byte[] iv, byte[] encrypted, int encryptedOffset,
int encryptedLength) throws PGPException {
String algorithmName = PGPUtil
.getSymmetricCipherName(encAlgorithm);
byte[] decrypted = null;
try {
Cipher c = Cipher
.getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$
SecretKey secretKey = new SecretKeySpec(key, algorithmName);
c.init(Cipher.DECRYPT_MODE, secretKey,
new IvParameterSpec(iv));
c.updateAAD(aad);
decrypted = new byte[c.getOutputSize(encryptedLength)];
int decryptedLength = c.update(encrypted, encryptedOffset,
encryptedLength, decrypted);
// doFinal() for OCB will check the MAC and throw an
// exception if it doesn't match
decryptedLength += c.doFinal(decrypted, decryptedLength);
if (decryptedLength != decrypted.length) {
throw new PGPException(MessageFormat.format(
BCText.get().cryptWrongDecryptedLength,
Integer.valueOf(decryptedLength),
Integer.valueOf(decrypted.length)));
}
byte[] result = decrypted;
decrypted = null; // Don't clear in finally
return result;
} catch (NoClassDefFoundError e) {
String msg = MessageFormat.format(
BCText.get().gpgNoSuchAlgorithm,
algorithmName + "/OCB"); //$NON-NLS-1$
throw new PGPException(msg,
new NoSuchAlgorithmException(msg, e));
} catch (PGPException e) {
throw e;
} catch (Exception e) {
throw new PGPException(
MessageFormat.format(BCText.get().cryptCipherError,
e.getLocalizedMessage()),
e);
} finally {
if (decrypted != null) {
// Prevent halfway decrypted data leaking.
Arrays.fill(decrypted, (byte) 0);
}
}
}
};
}
}