2 * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Distribution License v. 1.0 which is available at
6 * https://www.eclipse.org/org/documents/edl-v10.php.
8 * SPDX-License-Identifier: BSD-3-Clause
10 package org.eclipse.jgit.gpg.bc.internal.keys;
12 import java.security.NoSuchAlgorithmException;
13 import java.text.MessageFormat;
15 import javax.crypto.Cipher;
16 import javax.crypto.SecretKey;
17 import javax.crypto.spec.IvParameterSpec;
18 import javax.crypto.spec.SecretKeySpec;
20 import org.bouncycastle.openpgp.PGPException;
21 import org.bouncycastle.openpgp.PGPUtil;
22 import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
23 import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
24 import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
25 import org.bouncycastle.util.Arrays;
26 import org.eclipse.jgit.gpg.bc.internal.BCText;
29 * A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption.
30 * It accepts an AAD in the factory's constructor, so the factory can be used to
31 * create a {@link PBESecretKeyDecryptor} only for a particular input.
33 * For JGit's needs, this is sufficient, but for a general upstream
34 * implementation that limitation might not be acceptable.
37 class OCBPBEProtectionRemoverFactory
38 implements PBEProtectionRemoverFactory {
40 private final PGPDigestCalculatorProvider calculatorProvider;
42 private final char[] passphrase;
44 private final byte[] aad;
47 * Creates a new factory instance with the given parameters.
49 * Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s
50 * created by the factory can be used to decrypt only a particular input
54 * @param passphrase to use for secret key derivation
55 * @param calculatorProvider for computing digests
56 * @param aad for the OCB decryption
58 OCBPBEProtectionRemoverFactory(char[] passphrase,
59 PGPDigestCalculatorProvider calculatorProvider, byte[] aad) {
60 this.calculatorProvider = calculatorProvider;
61 this.passphrase = passphrase;
66 public PBESecretKeyDecryptor createDecryptor(String protection)
68 return new PBESecretKeyDecryptor(passphrase, calculatorProvider) {
71 public byte[] recoverKeyData(int encAlgorithm, byte[] key,
72 byte[] iv, byte[] encrypted, int encryptedOffset,
73 int encryptedLength) throws PGPException {
74 String algorithmName = PGPUtil
75 .getSymmetricCipherName(encAlgorithm);
76 byte[] decrypted = null;
78 // errorprone: "Dynamically constructed transformation
79 // strings are also flagged, as they may conceal an instance
81 @SuppressWarnings("InsecureCryptoUsage")
83 .getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$
84 SecretKey secretKey = new SecretKeySpec(key, algorithmName);
85 c.init(Cipher.DECRYPT_MODE, secretKey,
86 new IvParameterSpec(iv));
88 decrypted = new byte[c.getOutputSize(encryptedLength)];
89 int decryptedLength = c.update(encrypted, encryptedOffset,
90 encryptedLength, decrypted);
91 // doFinal() for OCB will check the MAC and throw an
92 // exception if it doesn't match
93 decryptedLength += c.doFinal(decrypted, decryptedLength);
94 if (decryptedLength != decrypted.length) {
95 throw new PGPException(MessageFormat.format(
96 BCText.get().cryptWrongDecryptedLength,
97 Integer.valueOf(decryptedLength),
98 Integer.valueOf(decrypted.length)));
100 byte[] result = decrypted;
101 decrypted = null; // Don't clear in finally
103 } catch (NoClassDefFoundError e) {
104 String msg = MessageFormat.format(
105 BCText.get().gpgNoSuchAlgorithm,
106 algorithmName + "/OCB"); //$NON-NLS-1$
107 throw new PGPException(msg,
108 new NoSuchAlgorithmException(msg, e));
109 } catch (PGPException e) {
111 } catch (Exception e) {
112 throw new PGPException(
113 MessageFormat.format(BCText.get().cryptCipherError,
114 e.getLocalizedMessage()),
117 if (decrypted != null) {
118 // Prevent halfway decrypted data leaking.
119 Arrays.fill(decrypted, (byte) 0);