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 009183e..8da71f1 100644 --- a/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java +++ b/src/main/java/com/blackberry/jwteditor/view/config/ConfigView.java @@ -45,7 +45,7 @@ public ConfigView(BurpConfig burpConfig, UserInterface userInterface, boolean is private void createUIComponents() { proxyConfigView = new ProxyConfigView(userInterface, burpConfig.proxyConfig()); - intruderConfigView = new IntruderConfigView(userInterface, keysModel, burpConfig.intruderConfig()); + 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.java b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java index 83f08f7..e5e48df 100644 --- a/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java +++ b/src/main/java/com/blackberry/jwteditor/view/config/IntruderConfigView.java @@ -20,24 +20,16 @@ import burp.api.montoya.ui.UserInterface; 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.blackberry.jwteditor.view.utils.DocumentAdapter; import com.nimbusds.jose.JWSAlgorithm; import javax.swing.*; -import java.util.List; -import java.util.Optional; +import static com.blackberry.jwteditor.view.config.IntruderConfigModel.*; import static java.awt.Font.BOLD; class IntruderConfigView { - private final IntruderConfig intruderConfig; - private final KeysModel keysModel; - private JPanel mainPanel; private JTextField intruderParameterName; private JComboBox comboBoxPayloadPosition; @@ -47,93 +39,66 @@ class IntruderConfigView { private JCheckBox resignIntruderJWS; private JComboBox comboBoxIntruderSigningAlg; - IntruderConfigView(UserInterface userInterface, KeysModel keysModel, IntruderConfig intruderConfig) { - this.keysModel = keysModel; - this.intruderConfig = intruderConfig; - - intruderParameterName.setText(intruderConfig.fuzzParameter()); + IntruderConfigView(UserInterface userInterface, IntruderConfigModel model) { + intruderParameterName.setText(model.fuzzParameter()); intruderParameterName.getDocument().addDocumentListener( - new DocumentAdapter(e -> intruderConfig.setFuzzParameter(intruderParameterName.getText())) + new DocumentAdapter(e -> model.setFuzzParameter(intruderParameterName.getText())) ); - comboBoxPayloadPosition.setModel(new DefaultComboBoxModel<>(FuzzLocation.values())); - comboBoxPayloadPosition.setSelectedItem(intruderConfig.fuzzLocation()); - comboBoxPayloadPosition.addActionListener(e -> intruderConfig.setFuzzLocation((FuzzLocation) comboBoxPayloadPosition.getSelectedItem())); + comboBoxPayloadPosition.setModel(new DefaultComboBoxModel<>(model.fuzzLocations())); + comboBoxPayloadPosition.setSelectedItem(model.fuzzLocation()); + comboBoxPayloadPosition.addActionListener(e -> model.setFuzzLocation((FuzzLocation) comboBoxPayloadPosition.getSelectedItem())); - updateSigningKeyList(); - comboBoxIntruderSigningKeyId.addActionListener(e -> { - String newSigningKeyId = (String) comboBoxIntruderSigningKeyId.getSelectedItem(); + comboBoxIntruderSigningKeyId.setModel(new DefaultComboBoxModel<>(model.signingKeyIds())); + comboBoxIntruderSigningKeyId.setSelectedItem(model.signingKeyId()); + comboBoxIntruderSigningKeyId.setEnabled(model.hasSigningKeys()); + comboBoxIntruderSigningKeyId.addActionListener(e -> model.setSigningKeyId((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)); + 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); - } - 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())); - } - } - } + 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; - private void updateSigningAlgorithmList() { - Key key = keysModel.getSigningKeys().stream() - .filter(k -> k.getID().equals(intruderConfig.signingKeyId())) - .findFirst() - .orElseThrow(); + case SELECTED_ALGORITHM_UPDATED: + comboBoxIntruderSigningAlg.setSelectedItem(evt.getNewValue()); + break; - JWSAlgorithm[] signingAlgorithms = key.getSigningAlgorithms(); - comboBoxIntruderSigningAlg.setModel(new DefaultComboBoxModel(signingAlgorithms)); + case RESIGN_UPDATED: + resignIntruderJWS.setSelected((Boolean) evt.getNewValue()); + break; + } + }); + } - if (signingAlgorithms.length > 0) { - JWSAlgorithm algorithm = signingAlgorithms[0]; - comboBoxIntruderSigningAlg.setSelectedItem(algorithm); - intruderConfig.setSigningAlgorithm(algorithm); - } + private void updateControls(boolean hasSigningKeys) { + resignIntruderJWS.setEnabled(hasSigningKeys); + comboBoxIntruderSigningKeyId.setEnabled(hasSigningKeys); + comboBoxIntruderSigningAlg.setEnabled(hasSigningKeys); } } 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