diff --git a/src/main/java/com/blackberry/jwteditor/model/keys/KeysModel.java b/src/main/java/com/blackberry/jwteditor/model/keys/KeysModel.java index e71531b..7480002 100644 --- a/src/main/java/com/blackberry/jwteditor/model/keys/KeysModel.java +++ b/src/main/java/com/blackberry/jwteditor/model/keys/KeysModel.java @@ -131,8 +131,11 @@ public void addKey(Key key) { oldKey = keys.put(key.getID(), key); } - for (KeysModelListener modelListener: this.modelListeners) { - modelListener.notifyKeyDeleted(oldKey); + for (KeysModelListener modelListener : modelListeners) { + if (oldKey != null) { + modelListener.notifyKeyDeleted(oldKey); + } + modelListener.notifyKeyInserted(key); } } @@ -165,7 +168,7 @@ public void deleteKey(String keyId) { } if (rowIndex >= 0) { - for (KeysModelListener modelListener: this.modelListeners) { + for (KeysModelListener modelListener : this.modelListeners) { modelListener.notifyKeyDeleted(rowIndex); } } diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.form b/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.form index 13c70cc..a36bee6 100644 --- a/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.form +++ b/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.form @@ -1,320 +1,51 @@
- - + + - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - + - - - - - diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java b/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java index fc01d61..8da71f1 100644 --- a/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java +++ b/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java @@ -20,185 +20,32 @@ import burp.api.montoya.ui.UserInterface; import burp.config.BurpConfig; -import burp.intruder.FuzzLocation; -import burp.intruder.IntruderConfig; -import burp.proxy.HighlightColor; -import burp.proxy.ProxyConfig; -import burp.scanner.ScannerConfig; -import com.blackberry.jwteditor.model.keys.Key; import com.blackberry.jwteditor.model.keys.KeysModel; -import com.blackberry.jwteditor.model.keys.KeysModelListener.SimpleKeysModelListener; -import com.blackberry.jwteditor.view.utils.DocumentAdapter; -import com.nimbusds.jose.JWSAlgorithm; import javax.swing.*; -import java.awt.*; -import java.util.List; -import java.util.Optional; - -import static java.awt.Font.BOLD; public class ConfigView { - private final IntruderConfig intruderConfig; + private final BurpConfig burpConfig; + private final UserInterface userInterface; + private final boolean isProVersion; private final KeysModel keysModel; private JPanel mainPanel; - private JCheckBox checkBoxHighlightJWT; - private JLabel labelHighlightColor; - private JComboBox comboBoxHighlightColor; - private JLabel labelHighlightJWT; - private JTextField intruderParameterName; - private JComboBox comboBoxPayloadPosition; - private JComboBox comboBoxIntruderSigningKeyId; - private JCheckBox checkBoxHeaderInsertionPoint; - private JTextField scannerParameterName; - private JPanel proxyPanel; - private JLabel proxyLabel; - private JLabel intruderLabel; - private JLabel scannerLabel; - private JPanel intruderPanel; - private JLabel spacerLabel; - private JCheckBox resignIntruderJWS; - private JComboBox comboBoxIntruderSigningAlg; + private ProxyConfigView proxyConfigView; + private ScannerConfigView scannerConfigView; + private IntruderConfigView intruderConfigView; public ConfigView(BurpConfig burpConfig, UserInterface userInterface, boolean isProVersion, KeysModel keysModel) { + this.burpConfig = burpConfig; + this.userInterface = userInterface; + this.isProVersion = isProVersion; this.keysModel = keysModel; - this.intruderConfig = burpConfig.intruderConfig(); - - ProxyConfig proxyConfig = burpConfig.proxyConfig(); - - checkBoxHighlightJWT.setSelected(proxyConfig.highlightJWT()); - checkBoxHighlightJWT.addActionListener(e -> { - comboBoxHighlightColor.setEnabled(checkBoxHighlightJWT.isSelected()); - proxyConfig.setHighlightJWT(checkBoxHighlightJWT.isSelected()); - }); - - comboBoxHighlightColor.setModel(new DefaultComboBoxModel<>(HighlightColor.values())); - comboBoxHighlightColor.setSelectedItem(proxyConfig.highlightColor()); - comboBoxHighlightColor.setEnabled(proxyConfig.highlightJWT()); - comboBoxHighlightColor.addActionListener(e -> proxyConfig.setHighlightColor((HighlightColor) comboBoxHighlightColor.getSelectedItem())); - - intruderParameterName.setText(intruderConfig.fuzzParameter()); - intruderParameterName.getDocument().addDocumentListener( - new DocumentAdapter(e -> intruderConfig.setFuzzParameter(intruderParameterName.getText())) - ); - - comboBoxPayloadPosition.setModel(new DefaultComboBoxModel<>(FuzzLocation.values())); - comboBoxPayloadPosition.setSelectedItem(intruderConfig.fuzzLocation()); - comboBoxPayloadPosition.addActionListener(e -> intruderConfig.setFuzzLocation((FuzzLocation) comboBoxPayloadPosition.getSelectedItem())); - - updateSigningKeyList(); - comboBoxIntruderSigningKeyId.addActionListener(e -> { - String newSigningKeyId = (String) comboBoxIntruderSigningKeyId.getSelectedItem(); - - if (!intruderConfig.signingKeyId().equals(newSigningKeyId)) { - intruderConfig.setSigningKeyId(newSigningKeyId); - updateSigningAlgorithmList(); - } - }); - comboBoxIntruderSigningAlg.addActionListener(e -> intruderConfig.setSigningAlgorithm((JWSAlgorithm) comboBoxIntruderSigningAlg.getSelectedItem())); - resignIntruderJWS.addActionListener(e -> intruderConfig.setResign(resignIntruderJWS.isSelected())); - keysModel.addKeyModelListener(new SimpleKeysModelListener(this::updateSigningKeyList)); - - ScannerConfig scannerConfig = burpConfig.scannerConfig(); - - checkBoxHeaderInsertionPoint.setEnabled(isProVersion); - checkBoxHeaderInsertionPoint.setSelected(scannerConfig.enableHeaderJWSInsertionPointLocation()); - checkBoxHeaderInsertionPoint.addActionListener(e -> { - scannerConfig.setEnableHeaderJWSInsertionPointLocation(checkBoxHeaderInsertionPoint.isSelected()); - scannerParameterName.setEnabled(checkBoxHeaderInsertionPoint.isSelected()); - }); - - scannerParameterName.setEnabled(scannerConfig.enableHeaderJWSInsertionPointLocation()); - scannerParameterName.setText(scannerConfig.insertionPointLocationParameterName()); - scannerParameterName.getDocument().addDocumentListener( - new DocumentAdapter(e -> scannerConfig.setInsertionPointLocationParameterName(scannerParameterName.getText())) - ); - - proxyLabel.setFont(proxyLabel.getFont().deriveFont(BOLD)); - intruderLabel.setFont(intruderLabel.getFont().deriveFont(BOLD)); - scannerLabel.setFont(scannerLabel.getFont().deriveFont(BOLD)); - userInterface.applyThemeToComponent(mainPanel); - - comboBoxHighlightColor.setRenderer(new HighlightComboRenderer()); - } - - private void updateSigningKeyList() { - List signingKeys = keysModel.getSigningKeys(); - String[] signingKeyIds = signingKeys.stream().map(Key::getID).toArray(String[]::new); - String modelSelectedSigningId = intruderConfig.signingKeyId(); - - String viewSelectedKeyId = (String) comboBoxIntruderSigningKeyId.getSelectedItem(); - comboBoxIntruderSigningKeyId.setModel(new DefaultComboBoxModel<>(signingKeyIds)); - - if (signingKeys.isEmpty()) { - resignIntruderJWS.setSelected(false); - resignIntruderJWS.setEnabled(false); - comboBoxIntruderSigningKeyId.setEnabled(false); - comboBoxIntruderSigningAlg.setEnabled(false); - intruderConfig.setResign(false); - intruderConfig.setSigningKeyId(null); - } else { - resignIntruderJWS.setEnabled(true); - comboBoxIntruderSigningKeyId.setEnabled(true); - comboBoxIntruderSigningAlg.setEnabled(true); - - Optional selectedKey = signingKeys.stream() - .filter(k -> k.getID().equals(modelSelectedSigningId)) - .findFirst(); - - - if (selectedKey.isPresent()) { - Key key = selectedKey.get(); - - resignIntruderJWS.setSelected(intruderConfig.resign()); - comboBoxIntruderSigningKeyId.setSelectedItem(key.getID()); - - if (!modelSelectedSigningId.equals(viewSelectedKeyId)) { - comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel(key.getSigningAlgorithms())); - comboBoxIntruderSigningAlg.setSelectedIndex(0); - } - } else { - resignIntruderJWS.setSelected(false); - comboBoxIntruderSigningKeyId.setSelectedIndex(0); - - Key key = signingKeys.get(0); - comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel(key.getSigningAlgorithms())); - } - } } - private void updateSigningAlgorithmList() { - Key key = keysModel.getSigningKeys().stream() - .filter(k -> k.getID().equals(intruderConfig.signingKeyId())) - .findFirst() - .orElseThrow(); - - JWSAlgorithm[] signingAlgorithms = key.getSigningAlgorithms(); - comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel(signingAlgorithms)); - - if (signingAlgorithms.length > 0) { - JWSAlgorithm algorithm = signingAlgorithms[0]; - comboBoxIntruderSigningAlg.setSelectedItem(algorithm); - intruderConfig.setSigningAlgorithm(algorithm); - } - } - - /** - * Custom list cell renderer to color rows of combo box drop down list. - */ - private static class HighlightComboRenderer implements ListCellRenderer { - private final ListCellRenderer renderer = new DefaultListCellRenderer(); - - @Override - public Component getListCellRendererComponent(JList list, HighlightColor value, int index, boolean isSelected, boolean cellHasFocus) { - JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - - Color background = isSelected ? list.getSelectionBackground() : value.color; - label.setBackground(background); - - return label; - } + private void createUIComponents() { + proxyConfigView = new ProxyConfigView(userInterface, burpConfig.proxyConfig()); + intruderConfigView = new IntruderConfigView(userInterface, new IntruderConfigModel(keysModel, burpConfig.intruderConfig())); + scannerConfigView = new ScannerConfigView(userInterface, burpConfig.scannerConfig(), isProVersion); } } diff --git a/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigModel.java b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigModel.java new file mode 100644 index 0000000..a3dd2e1 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigModel.java @@ -0,0 +1,168 @@ +package com.blackberry.jwteditor.view.config; + +import burp.intruder.FuzzLocation; +import burp.intruder.IntruderConfig; +import com.blackberry.jwteditor.model.keys.Key; +import com.blackberry.jwteditor.model.keys.KeysModel; +import com.blackberry.jwteditor.model.keys.KeysModelListener.SimpleKeysModelListener; +import com.nimbusds.jose.JWSAlgorithm; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.stream; + +class IntruderConfigModel { + static final String SIGNING_KEYS_UPDATED = "signingKeysUpdated"; + static final String SELECTED_KEY_UPDATED = "selectedKeyUpdated"; + static final String SIGNING_ALGORITHMS_UPDATED = "signingAlgorithmsUpdated"; + static final String SELECTED_ALGORITHM_UPDATED = "selectedAlgorithmUpdated"; + static final String RESIGN_UPDATED = "resignUpdated"; + + private static final JWSAlgorithm[] NO_ALGORITHMS = new JWSAlgorithm[0]; + + private final PropertyChangeSupport propertyChangeSupport; + private final KeysModel keysModel; + private final IntruderConfig intruderConfig; + private final List listeners; + + private String[] signingKeyIds; + private JWSAlgorithm[] selectedKeySigningAlgorithms; + + IntruderConfigModel(KeysModel keysModel, IntruderConfig intruderConfig) { + this.keysModel = keysModel; + this.intruderConfig = intruderConfig; + this.signingKeyIds = signingKeyIds(); + this.selectedKeySigningAlgorithms = signingAlgorithms(); + this.listeners = new ArrayList<>(); + this.propertyChangeSupport = new PropertyChangeSupport(this); + + keysModel.addKeyModelListener(new SimpleKeysModelListener(this::updateSigningKeyList)); + } + + String fuzzParameter() { + return intruderConfig.fuzzParameter(); + } + + void setFuzzParameter(String fuzzParameter) { + intruderConfig.setFuzzParameter(fuzzParameter); + } + + FuzzLocation fuzzLocation() { + return intruderConfig.fuzzLocation(); + } + + void setFuzzLocation(FuzzLocation fuzzLocation) { + intruderConfig.setFuzzLocation(fuzzLocation); + } + + FuzzLocation[] fuzzLocations() { + return FuzzLocation.values(); + } + + boolean hasSigningKeys() { + return !keysModel.getSigningKeys().isEmpty(); + } + + String[] signingKeyIds() { + return keysModel.getSigningKeys().stream().map(Key::getID).toArray(String[]::new); + } + + String signingKeyId() { + return intruderConfig.signingKeyId(); + } + + public void setSigningKeyId(String signingKeyId) { + String oldSigningKeyId = intruderConfig.signingKeyId(); + JWSAlgorithm[] oldAlgorithms = selectedKeySigningAlgorithms; + intruderConfig.setSigningKeyId(signingKeyId); + + boolean selectedKeyUnchanged = (oldSigningKeyId == null && signingKeyId==null) || (oldSigningKeyId != null && oldSigningKeyId.equals(signingKeyId)); + + if (!selectedKeyUnchanged) { + selectedKeySigningAlgorithms = signingAlgorithms(); + propertyChangeSupport.firePropertyChange(SIGNING_ALGORITHMS_UPDATED, oldAlgorithms, selectedKeySigningAlgorithms); + } + } + + JWSAlgorithm[] signingAlgorithms() { + if (intruderConfig.signingKeyId() == null) { + return NO_ALGORITHMS; + } + + return keysModel.getSigningKeys().stream() + .filter(k -> k.getID().equals(intruderConfig.signingKeyId())) + .findFirst() + .orElseThrow() + .getSigningAlgorithms(); + } + + JWSAlgorithm signingAlgorithm() { + return intruderConfig.signingAlgorithm(); + } + + void setSigningAlgorithm(JWSAlgorithm signingAlgorithm) { + intruderConfig.setSigningAlgorithm(signingAlgorithm); + } + + boolean resign() { + return intruderConfig.resign() && hasSigningKeys(); + } + + void setResign(boolean resign) { + intruderConfig.setResign(resign); + } + + void addPropertyChangeListener(PropertyChangeListener listener) { + listeners.add(listener); + propertyChangeSupport.addPropertyChangeListener(listener); + } + + void clearListeners() { + listeners.forEach(propertyChangeSupport::removePropertyChangeListener); + } + + private void updateSigningKeyList() { + String[] oldSigningKeyIds = signingKeyIds; + JWSAlgorithm[] oldSigningAlgorithms = selectedKeySigningAlgorithms; + JWSAlgorithm oldSigningAlgorithm = intruderConfig.signingAlgorithm(); + signingKeyIds = signingKeyIds(); + propertyChangeSupport.firePropertyChange(SIGNING_KEYS_UPDATED, oldSigningKeyIds, signingKeyIds); + + String selectedKeyId = intruderConfig.signingKeyId(); + boolean modelIsEmpty = signingKeyIds.length == 0; + boolean modelWasEmpty = oldSigningKeyIds.length == 0 && signingKeyIds.length > 0; + boolean selectedKeyDeleted = selectedKeyId != null && (signingKeyIds.length == 0 || stream(signingKeyIds).noneMatch(selectedKeyId::equals)); + boolean wasResigning = intruderConfig.resign(); + + if (modelIsEmpty) { + selectedKeySigningAlgorithms = NO_ALGORITHMS; + propertyChangeSupport.firePropertyChange(SIGNING_ALGORITHMS_UPDATED, oldSigningAlgorithms, NO_ALGORITHMS); + + String oldSigningKeyId = intruderConfig.signingKeyId(); + intruderConfig.setSigningKeyId(null); + propertyChangeSupport.firePropertyChange(SELECTED_KEY_UPDATED, oldSigningKeyId, null); + + intruderConfig.setSigningAlgorithm(null); + propertyChangeSupport.firePropertyChange(SELECTED_ALGORITHM_UPDATED, oldSigningAlgorithm, null); + + intruderConfig.setResign(false); + propertyChangeSupport.firePropertyChange(RESIGN_UPDATED, wasResigning, false); + } + else if (modelWasEmpty || selectedKeyDeleted) { + String firstKeyId = signingKeyIds[0]; + intruderConfig.setSigningKeyId(firstKeyId); + selectedKeySigningAlgorithms = signingAlgorithms(); + JWSAlgorithm firstSigningAlgorithm = selectedKeySigningAlgorithms[0]; + intruderConfig.setSigningAlgorithm(firstSigningAlgorithm); + intruderConfig.setResign(false); + + propertyChangeSupport.firePropertyChange(SELECTED_KEY_UPDATED, null, firstKeyId); + propertyChangeSupport.firePropertyChange(SIGNING_ALGORITHMS_UPDATED, oldSigningAlgorithms, selectedKeySigningAlgorithms); + propertyChangeSupport.firePropertyChange(SELECTED_ALGORITHM_UPDATED, oldSigningAlgorithm, firstSigningAlgorithm); + propertyChangeSupport.firePropertyChange(RESIGN_UPDATED, wasResigning, false); + } + } +} diff --git a/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.form b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.form new file mode 100644 index 0000000..265fb65 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.form @@ -0,0 +1,143 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java new file mode 100644 index 0000000..e5e48df --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java @@ -0,0 +1,104 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.view.config; + +import burp.api.montoya.ui.UserInterface; +import burp.intruder.FuzzLocation; +import com.blackberry.jwteditor.view.utils.DocumentAdapter; +import com.nimbusds.jose.JWSAlgorithm; + +import javax.swing.*; + +import static com.blackberry.jwteditor.view.config.IntruderConfigModel.*; +import static java.awt.Font.BOLD; + + +class IntruderConfigView { + private JPanel mainPanel; + private JTextField intruderParameterName; + private JComboBox comboBoxPayloadPosition; + private JComboBox comboBoxIntruderSigningKeyId; + private JLabel intruderLabel; + private JLabel spacerLabel; + private JCheckBox resignIntruderJWS; + private JComboBox comboBoxIntruderSigningAlg; + + IntruderConfigView(UserInterface userInterface, IntruderConfigModel model) { + intruderParameterName.setText(model.fuzzParameter()); + intruderParameterName.getDocument().addDocumentListener( + new DocumentAdapter(e -> model.setFuzzParameter(intruderParameterName.getText())) + ); + + comboBoxPayloadPosition.setModel(new DefaultComboBoxModel<>(model.fuzzLocations())); + comboBoxPayloadPosition.setSelectedItem(model.fuzzLocation()); + comboBoxPayloadPosition.addActionListener(e -> model.setFuzzLocation((FuzzLocation) comboBoxPayloadPosition.getSelectedItem())); + + comboBoxIntruderSigningKeyId.setModel(new DefaultComboBoxModel<>(model.signingKeyIds())); + comboBoxIntruderSigningKeyId.setSelectedItem(model.signingKeyId()); + comboBoxIntruderSigningKeyId.setEnabled(model.hasSigningKeys()); + comboBoxIntruderSigningKeyId.addActionListener(e -> model.setSigningKeyId((String) comboBoxIntruderSigningKeyId.getSelectedItem())); + + comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel<>(model.signingAlgorithms())); + comboBoxIntruderSigningAlg.setSelectedItem(model.signingAlgorithm()); + comboBoxIntruderSigningAlg.setEnabled(model.hasSigningKeys()); + comboBoxIntruderSigningAlg.addActionListener(e -> model.setSigningAlgorithm((JWSAlgorithm) comboBoxIntruderSigningAlg.getSelectedItem())); + + resignIntruderJWS.setEnabled(model.hasSigningKeys()); + resignIntruderJWS.setSelected(model.resign()); + resignIntruderJWS.addActionListener(e -> model.setResign(resignIntruderJWS.isSelected())); + + intruderLabel.setFont(intruderLabel.getFont().deriveFont(BOLD)); + userInterface.applyThemeToComponent(mainPanel); + + updateControls(model.hasSigningKeys()); + + model.addPropertyChangeListener(evt -> { + switch (evt.getPropertyName()) { + case SIGNING_KEYS_UPDATED: + comboBoxIntruderSigningKeyId.setModel(new DefaultComboBoxModel<>((String[]) evt.getNewValue())); + comboBoxIntruderSigningKeyId.setSelectedItem(model.signingKeyId()); + updateControls(model.hasSigningKeys()); + resignIntruderJWS.setSelected(model.resign()); + break; + + case SIGNING_ALGORITHMS_UPDATED: + comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel((JWSAlgorithm[]) evt.getNewValue())); + break; + + case SELECTED_KEY_UPDATED: + comboBoxIntruderSigningKeyId.setSelectedItem(evt.getNewValue()); + break; + + case SELECTED_ALGORITHM_UPDATED: + comboBoxIntruderSigningAlg.setSelectedItem(evt.getNewValue()); + break; + + case RESIGN_UPDATED: + resignIntruderJWS.setSelected((Boolean) evt.getNewValue()); + break; + } + }); + } + + private void updateControls(boolean hasSigningKeys) { + resignIntruderJWS.setEnabled(hasSigningKeys); + comboBoxIntruderSigningKeyId.setEnabled(hasSigningKeys); + comboBoxIntruderSigningAlg.setEnabled(hasSigningKeys); + } +} diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.form b/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.form new file mode 100644 index 0000000..84b5ca7 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.form @@ -0,0 +1,66 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.java b/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.java new file mode 100644 index 0000000..e8a11c6 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/ProxyConfigView.java @@ -0,0 +1,69 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.view.config; + +import burp.api.montoya.ui.UserInterface; +import burp.proxy.HighlightColor; +import burp.proxy.ProxyConfig; + +import javax.swing.*; +import java.awt.*; + +import static java.awt.Font.BOLD; + + +class ProxyConfigView { + private JPanel mainPanel; + private JCheckBox checkBoxHighlightJWT; + private JComboBox comboBoxHighlightColor; + private JLabel proxyLabel; + + ProxyConfigView(UserInterface userInterface, ProxyConfig proxyConfig) { + checkBoxHighlightJWT.setSelected(proxyConfig.highlightJWT()); + + checkBoxHighlightJWT.addActionListener(e -> { + comboBoxHighlightColor.setEnabled(checkBoxHighlightJWT.isSelected()); + proxyConfig.setHighlightJWT(checkBoxHighlightJWT.isSelected()); + }); + + comboBoxHighlightColor.setModel(new DefaultComboBoxModel<>(HighlightColor.values())); + comboBoxHighlightColor.setSelectedItem(proxyConfig.highlightColor()); + comboBoxHighlightColor.setEnabled(proxyConfig.highlightJWT()); + comboBoxHighlightColor.addActionListener(e -> proxyConfig.setHighlightColor((HighlightColor) comboBoxHighlightColor.getSelectedItem())); + + proxyLabel.setFont(proxyLabel.getFont().deriveFont(BOLD)); + userInterface.applyThemeToComponent(mainPanel); + + comboBoxHighlightColor.setRenderer(new HighlightComboRenderer()); + } + + private static class HighlightComboRenderer implements ListCellRenderer { + private final ListCellRenderer renderer = new DefaultListCellRenderer(); + + @Override + public Component getListCellRendererComponent(JList list, HighlightColor value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + Color background = isSelected ? list.getSelectionBackground() : value.color; + label.setBackground(background); + + return label; + } + } +} diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.form b/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.form new file mode 100644 index 0000000..a8ef9aa --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.form @@ -0,0 +1,96 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.java b/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.java new file mode 100644 index 0000000..9e11d8d --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/view/config/ScannerConfigView.java @@ -0,0 +1,54 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.view.config; + +import burp.api.montoya.ui.UserInterface; +import burp.scanner.ScannerConfig; +import com.blackberry.jwteditor.view.utils.DocumentAdapter; + +import javax.swing.*; + +import static java.awt.Font.BOLD; + + +class ScannerConfigView { + private JPanel mainPanel; + private JCheckBox checkBoxHeaderInsertionPoint; + private JTextField scannerParameterName; + private JLabel scannerLabel; + + ScannerConfigView(UserInterface userInterface, ScannerConfig scannerConfig, boolean isProVersion) { + + checkBoxHeaderInsertionPoint.setEnabled(isProVersion); + checkBoxHeaderInsertionPoint.setSelected(scannerConfig.enableHeaderJWSInsertionPointLocation()); + checkBoxHeaderInsertionPoint.addActionListener(e -> { + scannerConfig.setEnableHeaderJWSInsertionPointLocation(checkBoxHeaderInsertionPoint.isSelected()); + scannerParameterName.setEnabled(checkBoxHeaderInsertionPoint.isSelected()); + }); + + scannerParameterName.setEnabled(scannerConfig.enableHeaderJWSInsertionPointLocation()); + scannerParameterName.setText(scannerConfig.insertionPointLocationParameterName()); + scannerParameterName.getDocument().addDocumentListener( + new DocumentAdapter(e -> scannerConfig.setInsertionPointLocationParameterName(scannerParameterName.getText())) + ); + + scannerLabel.setFont(scannerLabel.getFont().deriveFont(BOLD)); + userInterface.applyThemeToComponent(mainPanel); + } +} diff --git a/src/test/java/com/blackberry/jwteditor/KeyLoader.java b/src/test/java/com/blackberry/jwteditor/KeyLoader.java new file mode 100644 index 0000000..320d7a1 --- /dev/null +++ b/src/test/java/com/blackberry/jwteditor/KeyLoader.java @@ -0,0 +1,56 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor; + +import com.blackberry.jwteditor.exceptions.PemException; +import com.blackberry.jwteditor.exceptions.UnsupportedKeyException; +import com.blackberry.jwteditor.model.keys.JWKKeyFactory; +import com.blackberry.jwteditor.model.keys.Key; +import com.blackberry.jwteditor.utils.PEMUtils; +import com.nimbusds.jose.jwk.JWK; + +public class KeyLoader { + + public static Key loadECKey(String pem, String keyId) { + try { + JWK jwk = PEMUtils.pemToECKey(pem, keyId); + return JWKKeyFactory.from(jwk); + } catch (PemException | UnsupportedKeyException e) { + throw new IllegalStateException(e); + } + } + + public static Key loadRSAKey(String pem, String keyId) { + try { + JWK jwk = PEMUtils.pemToRSAKey(pem, keyId); + return JWKKeyFactory.from(jwk); + } catch (PemException | UnsupportedKeyException e) { + throw new IllegalStateException(e); + } + } + + public static Key loadOKPKey(String pem, String keyId) { + try { + JWK jwk = PEMUtils.pemToOctetKeyPair(pem, keyId); + return JWKKeyFactory.from(jwk); + } catch (PemException | UnsupportedKeyException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/test/java/com/blackberry/jwteditor/KeysModelBuilder.java b/src/test/java/com/blackberry/jwteditor/KeysModelBuilder.java index 647ddfb..054e9d5 100644 --- a/src/test/java/com/blackberry/jwteditor/KeysModelBuilder.java +++ b/src/test/java/com/blackberry/jwteditor/KeysModelBuilder.java @@ -18,50 +18,29 @@ package com.blackberry.jwteditor; -import com.blackberry.jwteditor.exceptions.PemException; -import com.blackberry.jwteditor.exceptions.UnsupportedKeyException; -import com.blackberry.jwteditor.model.keys.JWKKeyFactory; import com.blackberry.jwteditor.model.keys.Key; import com.blackberry.jwteditor.model.keys.KeysModel; -import com.blackberry.jwteditor.utils.PEMUtils; -import com.nimbusds.jose.jwk.JWK; import java.util.concurrent.atomic.AtomicInteger; +import static com.blackberry.jwteditor.KeyLoader.*; + class KeysModelBuilder { private final AtomicInteger keyId = new AtomicInteger(); private final KeysModel model = new KeysModel(); KeysModelBuilder withECKey(String pem) { - try { - JWK jwk = PEMUtils.pemToECKey(pem, Integer.toString(keyId.incrementAndGet())); - model.addKey(JWKKeyFactory.from(jwk)); - } catch (PemException | UnsupportedKeyException e) { - throw new IllegalStateException(e); - } - + model.addKey(loadECKey(pem, nextKeyId())); return this; } KeysModelBuilder withRSAKey(String pem) { - try { - JWK jwk = PEMUtils.pemToRSAKey(pem, Integer.toString(keyId.incrementAndGet())); - model.addKey(JWKKeyFactory.from(jwk)); - } catch (PemException | UnsupportedKeyException e) { - throw new IllegalStateException(e); - } - + model.addKey(loadRSAKey(pem, nextKeyId())); return this; } KeysModelBuilder withOKPKey(String pem) { - try { - JWK jwk = PEMUtils.pemToOctetKeyPair(pem, Integer.toString(keyId.incrementAndGet())); - model.addKey(JWKKeyFactory.from(jwk)); - } catch (PemException | UnsupportedKeyException e) { - throw new IllegalStateException(e); - } - + model.addKey(loadOKPKey(pem, nextKeyId())); return this; } @@ -74,6 +53,10 @@ KeysModel build() { return model; } + private String nextKeyId() { + return Integer.toString(keyId.incrementAndGet()); + } + static KeysModelBuilder keysModel() { return new KeysModelBuilder(); } diff --git a/src/test/java/com/blackberry/jwteditor/KeysModelTest.java b/src/test/java/com/blackberry/jwteditor/KeysModelTest.java index 01d00ed..a5d11e4 100644 --- a/src/test/java/com/blackberry/jwteditor/KeysModelTest.java +++ b/src/test/java/com/blackberry/jwteditor/KeysModelTest.java @@ -18,12 +18,16 @@ package com.blackberry.jwteditor; +import com.blackberry.jwteditor.model.keys.Key; import com.blackberry.jwteditor.model.keys.KeysModel; +import com.blackberry.jwteditor.model.keys.KeysModelListener; import com.blackberry.jwteditor.model.keys.KeysModelListener.InertKeysModelListener; import com.blackberry.jwteditor.model.keys.PasswordKey; import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import static com.blackberry.jwteditor.KeysModelBuilder.keysModel; import static data.PemData.*; @@ -122,4 +126,52 @@ public void notifyKeyDeleted(int rowIndex) { assertThat(noOfListenerInvocations.get()).isEqualTo(4); } + + @Test + void addKeyWithNewId_listenerOnlyNotifiedOfKeyInserted() { + AtomicBoolean keyDeleted = new AtomicBoolean(); + AtomicReference insertedKey = new AtomicReference<>(); + Key key = new PasswordKey("testKeyId", "secret", 8, 1337); + KeysModel model = new KeysModel(); + + model.addKeyModelListener(new KeysModelListener() { + @Override + public void notifyKeyInserted(Key key) { + insertedKey.compareAndSet(null, key); + } + + @Override + public void notifyKeyDeleted(int rowIndex) { + keyDeleted.set(true); + } + + @Override + public void notifyKeyDeleted(Key key) { + keyDeleted.set(true); + } + }); + + model.addKey(key); + + assertThat(insertedKey.get()).isSameAs(key); + assertThat(keyDeleted).isFalse(); + } + + @Test + void addKeyWithExistingId_listenerNotifiedOfKeyDeletion() { + AtomicReference deletedKey = new AtomicReference<>(); + Key oldKey = new PasswordKey("testKeyId", "secret", 8, 1337); + KeysModel model = keysModel().withKey(oldKey).build(); + + model.addKeyModelListener(new InertKeysModelListener() { + @Override + public void notifyKeyDeleted(Key key) { + deletedKey.set(key); + } + }); + + model.addKey(new PasswordKey("testKeyId", "sci", 8, 1337)); + + assertThat(deletedKey.get()).isSameAs(oldKey); + } } \ No newline at end of file diff --git a/src/test/java/com/blackberry/jwteditor/view/config/IntruderConfigModelTest.java b/src/test/java/com/blackberry/jwteditor/view/config/IntruderConfigModelTest.java new file mode 100644 index 0000000..a78954d --- /dev/null +++ b/src/test/java/com/blackberry/jwteditor/view/config/IntruderConfigModelTest.java @@ -0,0 +1,480 @@ +package com.blackberry.jwteditor.view.config; + +import burp.intruder.FuzzLocation; +import burp.intruder.IntruderConfig; +import com.blackberry.jwteditor.model.keys.KeysModel; +import com.nimbusds.jose.JWSAlgorithm; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static burp.intruder.FuzzLocation.HEADER; +import static com.blackberry.jwteditor.KeyLoader.*; +import static com.blackberry.jwteditor.view.config.IntruderConfigModel.*; +import static com.nimbusds.jose.JWSAlgorithm.*; +import static data.PemData.*; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class IntruderConfigModelTest { + private static final String KEY_ID = "id"; + + private final KeysModel keysModel = new KeysModel(); + private final IntruderConfig wrappedConfig = new IntruderConfig(); + private final IntruderConfigModel model = new IntruderConfigModel(keysModel, wrappedConfig); + + @AfterEach + void cleanUp() { + model.clearListeners(); + stream(model.signingKeyIds()).forEach(keysModel::deleteKey); + } + + @Test + void whenSetFuzzParameter_thenValueSetOnModelAndWrappedConfig() { + String parameter = "kid"; + model.setFuzzParameter(parameter); + + assertThat(model.fuzzParameter()).isEqualTo(parameter); + assertThat(wrappedConfig.fuzzParameter()).isEqualTo(parameter); + } + + @Test + void whenGetFuzzParameter_thenValueRetrievedFromWrappedConfig() { + String parameter = "kid"; + wrappedConfig.setFuzzParameter(parameter); + + assertThat(model.fuzzParameter()).isEqualTo(parameter); + } + + @Test + void whenSetFuzzLocation_thenValueSetOnModelAndWrappedConfig() { + FuzzLocation location = HEADER; + model.setFuzzLocation(location); + + assertThat(model.fuzzLocation()).isEqualTo(location); + assertThat(wrappedConfig.fuzzLocation()).isEqualTo(location); + } + + @Test + void whenGetFuzzLocation_thenValueRetrievedFromWrappedConfig() { + FuzzLocation location = HEADER; + wrappedConfig.setFuzzLocation(location); + + assertThat(model.fuzzLocation()).isEqualTo(location); + } + + @Test + void whenGetFuzzLocations_thenAllFuzzLocationValuesReturned() { + assertThat(model.fuzzLocations()).containsExactly(FuzzLocation.values()); + } + + @Test + void whenEmptyKeysModel_thenHasSigningKeyIsFalse() { + assertThat(model.hasSigningKeys()).isFalse(); + } + + @Test + void whenEmptyKeysModel_thenNoSigningKeyIds() { + assertThat(model.signingKeyIds()).isEmpty(); + } + + @Test + void whenEmptyKeysModel_thenSelectedSigningKeyIdIsNull() { + assertThat(model.signingKeyId()).isNull(); + } + + @Test + void whenEmptyKeysModel_thenNoSigningAlgorithms() { + assertThat(model.signingAlgorithms()).isEmpty(); + } + + @Test + void whenEmptyKeysModel_thenSelectedSigningAlgorithmsIsNull() { + assertThat(model.signingAlgorithm()).isNull(); + } + + @Test + void whenEmptyKeysModel_thenResignIsFalse() { + wrappedConfig.setResign(true); + + assertThat(model.resign()).isFalse(); + } + + @Test + void whenKeysModelNotEmptyButHasNoSigningKeys_thenHasSigningKeysIsFalse() { + keysModel.addKey(loadOKPKey(X25519Public, "kid")); + + assertThat(model.hasSigningKeys()).isFalse(); + } + + @Test + void whenKeysModelHasSigningKeys_thenHasSigningKeysIsTrue() { + keysModel.addKey(loadRSAKey(RSA1024Private, "kid")); + + assertThat(model.hasSigningKeys()).isTrue(); + } + + @Test + void whenKeysModelNotEmptyButHasNoSigningKeys_thenSigningKeyIdsEmpty() { + keysModel.addKey(loadOKPKey(X25519Public, "kid")); + + assertThat(model.signingKeyIds()).isEmpty(); + } + + @Test + void whenKeysModelHasSigningKeys_thenSigningKeyIdsNotEmpty() { + keysModel.addKey(loadRSAKey(RSA1024Private, "kid1")); + keysModel.addKey(loadECKey(PRIME256v1PrivateSEC1, "kid2")); + + assertThat(model.signingKeyIds()).containsExactly("kid1", "kid2"); + } + + @Test + void whenKeysModelHasSigningAndNonSigningKeys_thenSigningKeyIdsContainsOnlySigningKeyIds() { + keysModel.addKey(loadRSAKey(RSA1024Private, "kid1")); + keysModel.addKey(loadOKPKey(X25519Public, "kid2")); + keysModel.addKey(loadECKey(PRIME256v1PrivateSEC1, "kid3")); + + assertThat(model.signingKeyIds()).containsExactly("kid1", "kid3"); + } + + @Test + void givenKeysModelHasSigningKey_whenSetSigningKeyId_thenSigningKeyAndSigningAlgorithmsUpdated() { + String keyId = "kid1"; + keysModel.addKey(loadRSAKey(RSA1024Private, keyId)); + + model.setSigningKeyId(keyId); + + assertThat(model.signingKeyId()).isEqualTo(keyId); + assertThat(wrappedConfig.signingKeyId()).isEqualTo(keyId); + assertThat(model.signingAlgorithms()).containsExactly(RS256, RS384, RS512, PS256, PS384); + } + + @Test + void givenKeysModelHasSigningKey_whenSetInvalidSigningKeyId_thenExceptionThrown() { + keysModel.addKey(loadRSAKey(RSA1024Private, "kid1")); + + assertThrows(NoSuchElementException.class, () -> model.setSigningKeyId("invalid")); + } + + @Test + void givenKeysModelHasSigningKey_whenSetSigningKeyId_thenSigningAlgorithmsUpdatedEventPublished() { + List publishedAlgorithms = new ArrayList<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_ALGORITHMS_UPDATED) && evt.getNewValue() instanceof JWSAlgorithm[] algorithms) { + publishedAlgorithms.addAll(asList(algorithms)); + } + }); + + String keyId = "kid1"; + keysModel.addKey(loadRSAKey(RSA1024Private, keyId)); + + model.setSigningKeyId(keyId); + + assertThat(publishedAlgorithms).containsExactly(RS256, RS384, RS512, PS256, PS384); + } + + @Test + void givenKeysModelHasSigningKey_whenSetAlgorithm_thenAlgorithmStored() { + String keyId = "kid1"; + keysModel.addKey(loadRSAKey(RSA1024Private, keyId)); + + model.setSigningAlgorithm(PS256); + + assertThat(model.signingAlgorithm()).isEqualTo(PS256); + assertThat(wrappedConfig.signingAlgorithm()).isEqualTo(PS256); + } + + @Test + void givenNoSelectedKeyIdOrAlgorithm_whenSetResign_thenResignFalse() { + model.setResign(true); + + assertThat(model.resign()).isFalse(); + } + + @Test + void givenSelectedAlgorithmButNoKeyId_whenSetResign_thenResignFalse() { + model.setSigningAlgorithm(EdDSA); + + model.setResign(true); + + assertThat(model.resign()).isFalse(); + } + + @Test + void givenSelectedKeyIdAndAlgorithm_whenSetResign_thenResignFalse() { + String keyId = "kid"; + keysModel.addKey(loadRSAKey(RSA1024Private, keyId)); + model.setSigningKeyId(keyId); + model.setSigningAlgorithm(RS256); + + model.setResign(true); + + assertThat(model.resign()).isTrue(); + } + + @Test + void whenKeyAddedToModel_thenSigningKeysUpdatedEventFired() { + List oldIds = new ArrayList<>(); + List newIds = new ArrayList<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_KEYS_UPDATED)) { + oldIds.addAll(asList((String[]) evt.getOldValue())); + newIds.addAll(asList((String[]) evt.getNewValue())); + } + }); + + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(oldIds).isEmpty(); + assertThat(newIds).containsExactly(KEY_ID); + } + + @Test + void whenKeyDeletedFromModel_thenSigningKeysUpdatedEventFired() { + List oldIds = new ArrayList<>(); + List newIds = new ArrayList<>(); + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_KEYS_UPDATED)) { + oldIds.addAll(asList((String[]) evt.getOldValue())); + newIds.addAll(asList((String[]) evt.getNewValue())); + } + }); + + keysModel.deleteKey(KEY_ID); + + assertThat(oldIds).containsExactly(KEY_ID); + assertThat(newIds).isEmpty(); + } + + @Test + void givenSelectedKey_whenSelectedKeyDeletedFromModel_thenSelectedKeyUpdatedFired() { + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + model.setSigningKeyId(KEY_ID); + AtomicReference event = new AtomicReference<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SELECTED_KEY_UPDATED)) { + event.set(evt); + } + }); + + keysModel.deleteKey(KEY_ID); + + assertThat(event.get().getOldValue()).isEqualTo(KEY_ID); + assertThat(event.get().getNewValue()).isNull(); + } + + @Test + void givenSelectedKey_whenSelectedKeyDeletedFromModel_thenSigningAlgorithmsEventUpdatedFired() { + List oldAlgorithms = new ArrayList<>(); + List newAlgorithms = new ArrayList<>(); + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + model.setSigningKeyId(KEY_ID); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_ALGORITHMS_UPDATED)) { + oldAlgorithms.addAll(asList((JWSAlgorithm[]) evt.getOldValue())); + newAlgorithms.addAll(asList((JWSAlgorithm[]) evt.getNewValue())); + } + }); + + keysModel.deleteKey(KEY_ID); + + assertThat(oldAlgorithms).containsExactly(RS256, RS384, RS512, PS256, PS384); + assertThat(newAlgorithms).isEmpty(); + } + + @Test + void givenSelectedAlgorithm_whenSelectedKeyDeletedFromModel_thenSelectedAlgorithmUpdatedFired() { + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + model.setSigningKeyId(KEY_ID); + model.setSigningAlgorithm(RS256); + AtomicReference event = new AtomicReference<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SELECTED_ALGORITHM_UPDATED)) { + event.set(evt); + } + }); + + keysModel.deleteKey(KEY_ID); + + assertThat(event.get().getOldValue()).isEqualTo(RS256); + assertThat(event.get().getNewValue()).isNull(); + } + + @Test + void givenSelectedAlgorithm_whenResigningAndSelectedKeyDeleted_thenResignDisabled() { + AtomicBoolean resign = new AtomicBoolean(true); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(RESIGN_UPDATED) && evt.getNewValue() instanceof Boolean selected) { + resign.set(selected); + } + }); + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + model.setResign(true); + + keysModel.deleteKey(KEY_ID); + + assertThat(model.resign()).isFalse(); + assertThat(resign).isFalse(); + } + + @Test + void givenEmptyModel_whenKeyLoaded_thenKeyAndFirstAlgorithmSelected() { + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(model.signingKeyId()).isEqualTo(KEY_ID); + assertThat(wrappedConfig.signingKeyId()).isEqualTo(KEY_ID); + assertThat(model.signingAlgorithm()).isEqualTo(RS256); + assertThat(wrappedConfig.signingAlgorithm()).isEqualTo(RS256); + assertThat(model.signingAlgorithms()).containsExactly(RS256, RS384, RS512, PS256, PS384); + } + + @Test + void givenEmptyModel_whenKeyLoaded_thenSigningKeysUpdatedEventFired() { + List oldIds = new ArrayList<>(); + List newIds = new ArrayList<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_KEYS_UPDATED)) { + oldIds.addAll(asList((String[]) evt.getOldValue())); + newIds.addAll(asList((String[]) evt.getNewValue())); + } + }); + + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(oldIds).isEmpty(); + assertThat(newIds).containsExactly(KEY_ID); + } + + @Test + void givenEmptyModel_whenKeyLoaded_thenSelectedKeyUpdatedEventFired() { + AtomicReference oldKeyId = new AtomicReference<>(); + AtomicReference newKeyId = new AtomicReference<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SELECTED_KEY_UPDATED)) { + oldKeyId.set((String) evt.getOldValue()); + newKeyId.set((String) evt.getNewValue()); + } + }); + + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(oldKeyId.get()).isNull(); + assertThat(newKeyId.get()).isEqualTo(KEY_ID); + } + + @Test + void givenEmptyModel_whenKeyLoaded_thenSigningAlgorithmsUpdatedEventFired() { + List oldAlgorithms = new ArrayList<>(); + List newAlgorithms = new ArrayList<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SIGNING_ALGORITHMS_UPDATED)) { + oldAlgorithms.addAll(asList((JWSAlgorithm[]) evt.getOldValue())); + newAlgorithms.addAll(asList((JWSAlgorithm[]) evt.getNewValue())); + } + }); + + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(oldAlgorithms).isEmpty(); + assertThat(newAlgorithms).containsExactly(RS256, RS384, RS512, PS256, PS384); + } + + @Test + void givenEmptyModel_whenKeyLoaded_thenSelectedAlgorithmUpdatedEventFired() { + AtomicReference oldSigningAlgorithm = new AtomicReference<>(); + AtomicReference newSigningAlgorithm = new AtomicReference<>(); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(SELECTED_ALGORITHM_UPDATED)) { + oldSigningAlgorithm.set((JWSAlgorithm) evt.getOldValue()); + newSigningAlgorithm.set((JWSAlgorithm) evt.getNewValue()); + } + }); + + keysModel.addKey(loadRSAKey(RSA1024Private, KEY_ID)); + + assertThat(oldSigningAlgorithm.get()).isNull(); + assertThat(newSigningAlgorithm.get()).isEqualTo(RS256); + } + + @Test + void givenTwoKeysInModel_whenSelectedKeyDeleted_thenRemainingKeyAndFirstAlgorithmSelected() { + String kid = "kid2"; + keysModel.addKey(loadRSAKey(RSA1024Private, kid)); + keysModel.addKey(loadOKPKey(ED25519Private, KEY_ID)); + + keysModel.deleteKey(kid); + + assertThat(model.signingKeyId()).isEqualTo(KEY_ID); + assertThat(wrappedConfig.signingKeyId()).isEqualTo(KEY_ID); + assertThat(model.signingAlgorithm()).isEqualTo(EdDSA); + assertThat(wrappedConfig.signingAlgorithm()).isEqualTo(EdDSA); + assertThat(model.signingAlgorithms()).containsExactly(EdDSA); + } + + @Test + void givenTwoKeysInModel_whenResigningAndSelectedKeyDeleted_thenResignDisabled() { + AtomicBoolean resign = new AtomicBoolean(true); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(RESIGN_UPDATED) && evt.getNewValue() instanceof Boolean selected) { + resign.set(selected); + } + }); + + String kid = "kid2"; + keysModel.addKey(loadRSAKey(RSA1024Private, kid)); + keysModel.addKey(loadOKPKey(ED25519Private, KEY_ID)); + model.setResign(true); + + keysModel.deleteKey(kid); + + assertThat(model.resign()).isFalse(); + assertThat(resign).isFalse(); + } + + @Test + void givenMultipleKeysInModel_whenSelectedKeyDeleted_thenFirstKeyAndAlgorithmSelected() { + String kid = "kid2"; + keysModel.addKey(loadRSAKey(RSA1024Private, kid)); + keysModel.addKey(loadOKPKey(ED25519Private, KEY_ID)); + keysModel.addKey(loadOKPKey(X448Private, "kid3")); + + keysModel.deleteKey(kid); + + assertThat(model.signingKeyId()).isEqualTo(KEY_ID); + assertThat(wrappedConfig.signingKeyId()).isEqualTo(KEY_ID); + assertThat(model.signingAlgorithm()).isEqualTo(EdDSA); + assertThat(wrappedConfig.signingAlgorithm()).isEqualTo(EdDSA); + assertThat(model.signingAlgorithms()).containsExactly(EdDSA); + } + + @Test + void givenMultipleKeysInModel_whenResigningAndSelectedKeyDeleted_thenResignDisabled() { + AtomicBoolean resign = new AtomicBoolean(true); + model.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals(RESIGN_UPDATED) && evt.getNewValue() instanceof Boolean selected) { + resign.set(selected); + } + }); + + String kid = "kid2"; + keysModel.addKey(loadRSAKey(RSA1024Private, kid)); + keysModel.addKey(loadOKPKey(ED25519Private, KEY_ID)); + keysModel.addKey(loadOKPKey(X448Private, "kid3")); + model.setResign(true); + + keysModel.deleteKey(kid); + + assertThat(model.resign()).isFalse(); + assertThat(resign).isFalse(); + } +} \ No newline at end of file