Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package de.usd.cstchef.operations.encryption;

import java.security.Key;
import java.util.LinkedHashSet;

import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;

import java.security.spec.MGF1ParameterSpec;

/**
* Central place that knows how to turn a user selected RSA padding (and, for
* OAEP, the two message digests) into an initialised {@link Cipher}.
*
* Keeping this logic in one small class means the UI operations
* ({@code RsaEncryption} / {@code RsaDecryption}) only have to read their combo
* boxes and hand the values over. Supporting a new padding or digest in the
* future is a one line change to the constants below.
*/
public class RsaCipherBuilder {

/** Selectable padding modes shown in the operations' "Padding" combo box. */
public static final String PADDING_PKCS1 = "PKCS1Padding";
public static final String PADDING_OAEP = "OAEPPadding";

public static final String[] PADDINGS = new String[] {
PADDING_PKCS1,
PADDING_OAEP
};

/**
* Message digests offered for the OAEP hash and the MGF1 hash. These are
* the digests SunJCE accepts inside an {@link OAEPParameterSpec}. Add new
* entries here to make them available in both RSA operations at once.
*/
public static final String[] DIGESTS = new String[] {
"SHA-1",
"SHA-224",
"SHA-256",
"SHA-384",
"SHA-512"
};

public static final String DEFAULT_DIGEST = "SHA-256";

/**
* Whether a padding name selects OAEP. Matches both the simple PEM choice
* ({@link #PADDING_OAEP}) and the provider's named variants used in KeyStore
* mode (e.g. {@code OAEPWITHSHA-256ANDMGF1PADDING}).
*/
public static boolean isOaep(String padding) {
return padding != null && padding.toUpperCase().contains("OAEP");
}

/**
* Build the padding list for KeyStore mode. We keep whatever the security
* provider advertises (so previously saved recipes still match) but make
* sure an OAEP option is always present. Some JVMs / Burp's bundled JRE
* advertise a reduced RSA padding list, which otherwise leaves the user
* with no way to pick OAEP even though we support it.
*/
public static String[] keyStorePaddings(String[] providerPaddings) {
LinkedHashSet<String> paddings = new LinkedHashSet<>();
if (providerPaddings != null) {
for (String padding : providerPaddings) {
if (padding != null && !padding.isEmpty()) {
paddings.add(padding);
}
}
}

// Fall back to PKCS1 if the provider advertised nothing usable.
if (paddings.isEmpty()) {
paddings.add(PADDING_PKCS1);
}

boolean hasOaep = false;
for (String padding : paddings) {
if (isOaep(padding)) {
hasOaep = true;
break;
}
}
if (!hasOaep) {
paddings.add(PADDING_OAEP);
}

return paddings.toArray(new String[0]);
}

/**
* Build and initialise an RSA {@link Cipher}.
*
* @param mode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
* @param key the public (encrypt) or private (decrypt) key
* @param padding one of {@link #PADDINGS}
* @param oaepHash OAEP digest, one of {@link #DIGESTS} (ignored for PKCS1)
* @param mgf1Hash MGF1 digest, one of {@link #DIGESTS} (ignored for PKCS1)
*/
public static Cipher build(int mode, Key key, String padding, String oaepHash, String mgf1Hash) throws Exception {
if (PADDING_OAEP.equals(padding)) {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec spec = new OAEPParameterSpec(
oaepHash,
"MGF1",
new MGF1ParameterSpec(mgf1Hash),
PSource.PSpecified.DEFAULT);
cipher.init(mode, key, spec);
return cipher;
}

// Default / backwards compatible behaviour: RSA with PKCS#1 v1.5 padding.
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(mode, key);
return cipher;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.usd.cstchef.operations.encryption;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
Expand Down Expand Up @@ -28,6 +29,10 @@ public class RsaDecryption extends KeystoreOperation {

private VariableTextArea privateKeyTextArea;

private JComboBox<String> pemPadding;
private JComboBox<String> pemOaepHash;
private JComboBox<String> pemMgf1Hash;

private JComboBox<String> typeComboBox;

private static String[] inOutModes = new String[] { "Raw", "Hex", "Base64" };
Expand All @@ -38,6 +43,8 @@ public class RsaDecryption extends KeystoreOperation {
protected JComboBox<String> inputMode;
protected JComboBox<String> outputMode;
protected JComboBox<String> paddings;
protected JComboBox<String> ksOaepHash;
protected JComboBox<String> ksMgf1Hash;

private String lastSelection = "PEM";

Expand Down Expand Up @@ -74,17 +81,29 @@ protected ByteArray perform(ByteArray input) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
Cipher cipher = RsaCipherBuilder.build(Cipher.DECRYPT_MODE, privateKey,
(String) pemPadding.getSelectedItem(),
(String) pemOaepHash.getSelectedItem(),
(String) pemMgf1Hash.getSelectedItem());
return factory.createByteArray(cipher.doFinal(input.getBytes()));
}
else if(typeComboBox.getSelectedItem().equals("KeyStore")) {
if( ! this.keyAvailable.isSelected() )
throw new IllegalArgumentException("No private key available.");

String padding = (String)paddings.getSelectedItem();
Cipher cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding));
cipher.init(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey());
Cipher cipher;
if(RsaCipherBuilder.isOaep(padding)) {
// Named OAEP transformations (e.g. OAEPWITHSHA-256ANDMGF1PADDING) leave MGF1 at
// SHA-1 in SunJCE, so build an explicit OAEPParameterSpec with both chosen digests.
cipher = RsaCipherBuilder.build(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey(),
RsaCipherBuilder.PADDING_OAEP,
(String) ksOaepHash.getSelectedItem(),
(String) ksMgf1Hash.getSelectedItem());
} else {
cipher = Cipher.getInstance(String.format("%s/%s/%s", algorithm, cipherMode, padding));
cipher.init(Cipher.DECRYPT_MODE, this.selectedEntry.getPrivateKey());
}

String selectedInputMode = (String)inputMode.getSelectedItem();
String selectedOutputMode = (String)outputMode.getSelectedItem();
Expand Down Expand Up @@ -155,14 +174,35 @@ public void createMyUI() {
CipherUtils utils = CipherUtils.getInstance();
CipherInfo info = utils.getCipherInfo(this.algorithm);

this.paddings = new JComboBox<>(info.getPaddings());
this.paddings = new JComboBox<>(RsaCipherBuilder.keyStorePaddings(info.getPaddings()));
this.addUIElement("Padding", this.paddings);

this.ksOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS);
this.ksOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST);
this.addUIElement("OAEP Hash", this.ksOaepHash);

this.ksMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS);
this.ksMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST);
this.addUIElement("MGF1 Hash", this.ksMgf1Hash);

this.inputMode = new JComboBox<>(inOutModes);
this.addUIElement("Input", this.inputMode);

this.outputMode = new JComboBox<>(inOutModes);
this.addUIElement("Output", this.outputMode);

// The OAEP/MGF1 digests only apply to OAEP paddings, so only show them then.
this.paddings.addActionListener(e -> updateKeyStoreOaepVisibility());
updateKeyStoreOaepVisibility();
}

private void updateKeyStoreOaepVisibility() {
boolean oaep = RsaCipherBuilder.isOaep((String) this.paddings.getSelectedItem());
setRowVisible(this.ksOaepHash, oaep);
setRowVisible(this.ksMgf1Hash, oaep);
validate();
repaint();
updateStepPanel();
}

private void clearUI() {
Expand All @@ -182,6 +222,45 @@ private void clearUI() {
private void createUIForPEM() {
this.privateKeyTextArea = new VariableTextArea();
this.addUIElement("Private Key", this.privateKeyTextArea);

this.pemPadding = new JComboBox<>(RsaCipherBuilder.PADDINGS);
this.addUIElement("Padding", this.pemPadding);

this.pemOaepHash = new JComboBox<>(RsaCipherBuilder.DIGESTS);
this.pemOaepHash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST);
this.addUIElement("OAEP Hash", this.pemOaepHash);

this.pemMgf1Hash = new JComboBox<>(RsaCipherBuilder.DIGESTS);
this.pemMgf1Hash.setSelectedItem(RsaCipherBuilder.DEFAULT_DIGEST);
this.addUIElement("MGF1 Hash", this.pemMgf1Hash);

// The OAEP/MGF1 digests only apply to OAEP padding, so only show them then.
this.pemPadding.addActionListener(e -> updatePemOaepVisibility());
updatePemOaepVisibility();
}

// Show the OAEP/MGF1 hash rows only while OAEP padding is selected.
private void updatePemOaepVisibility() {
boolean oaep = RsaCipherBuilder.PADDING_OAEP.equals(this.pemPadding.getSelectedItem());
setRowVisible(this.pemOaepHash, oaep);
setRowVisible(this.pemMgf1Hash, oaep);
validate();
repaint();
updateStepPanel();
}

private void setRowVisible(Component comp, boolean visible) {
Component row = comp.getParent();
Component[] comps = getContentBoxComponents();
for (int i = 0; i < comps.length; i++) {
if (comps[i] == row) {
comps[i].setVisible(visible);
if (i + 1 < comps.length) {
comps[i + 1].setVisible(visible); // the spacing strut that follows the row
}
break;
}
}
}

public void updateStepPanel() {
Expand Down
Loading