diff --git a/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml b/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml index 0d2c942fd7..01c31c5d7a 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml +++ b/application/org.openjdk.jmc.flightrecorder.ui/defaultPages.xml @@ -727,6 +727,30 @@ + + + + + + + + + + + + + diff --git a/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png b/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png new file mode 100644 index 0000000000..907f05ea14 Binary files /dev/null and b/application/org.openjdk.jmc.flightrecorder.ui/icons/pages/security16.png differ diff --git a/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml b/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml index 0cd7d48f2d..c4097961a8 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml +++ b/application/org.openjdk.jmc.flightrecorder.ui/org.openjdk.jmc.flightrecorder.ui_contexts.xml @@ -145,6 +145,11 @@ + + + + + diff --git a/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml b/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml index bd2dd2abf5..cd95d64bbb 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml +++ b/application/org.openjdk.jmc.flightrecorder.ui/plugin.xml @@ -318,6 +318,10 @@ class="org.openjdk.jmc.flightrecorder.ui.pages.TlabPage$TlabPageFactory" id="org.openjdk.jmc.flightrecorder.ui.tlab"> + + diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java index e57c125b6b..ea6dca51c9 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ImageConstants.java @@ -132,6 +132,7 @@ public class ImageConstants { public static final String PAGE_COMPILATIONS = "pages/compilations.png"; //$NON-NLS-1$ public static final String PAGE_CLASSLOADING = "pages/classloading.png"; //$NON-NLS-1$ public static final String PAGE_TLAB_ALLOCATIONS = "pages/allocations.png"; //$NON-NLS-1$ + public static final String PAGE_SECURITY = "pages/security16.png"; //$NON-NLS-1$ public static final String PAGE_ENVIRONMENT = "pages/system.png"; //$NON-NLS-1$ public static final String PAGE_PROCESSES = "pages/processes.png"; //$NON-NLS-1$ public static final String PAGE_ENVIRONMENT_VARIABLES = "pages/environment.png"; //$NON-NLS-1$ diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java index 223e581d7d..ef3d883bc2 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/ItemList.java @@ -41,6 +41,7 @@ import java.util.stream.Stream; import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; @@ -83,6 +84,21 @@ public void addColumn(IAttribute a) { a.getContentType() instanceof LinearKindOfQuantity, accessor); } + public void addColumn(IAttribute a, ColumnLabelProvider iconProvider) { + @SuppressWarnings("deprecation") + IMemberAccessor accessor = ItemToolkit.accessor(a); + addColumn(a.getIdentifier(), a.getName(), a.getDescription(), + a.getContentType() instanceof LinearKindOfQuantity, accessor, iconProvider); + } + + private void addColumn( + String columnId, String name, String description, boolean right, IMemberAccessor accessor, + ColumnLabelProvider iconProvider) { + columns.add(new ColumnBuilder(name, columnId, accessor, iconProvider).description(description) + .style(right ? SWT.RIGHT : SWT.NONE).build()); + + } + public void addColumn( String columnId, String name, String description, boolean right, IMemberAccessor accessor) { columns.add(new ColumnBuilder(name, columnId, accessor).description(description) diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java index eb4fd826f3..f17fc13242 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/messages/internal/Messages.java @@ -523,6 +523,11 @@ public class Messages extends NLS { public static String SAVE_AS_NO_SRC_ERROR_MSG; public static String SAVE_AS_TITLE; public static String SEARCH_TREE_TEXT; + public static String SecurityPage_PAGE_NAME; + public static String SecurityPage_TIMELINE_SELECTION; + public static String SecurityPage_TABLE_SELECTION; + public static String SecurityPage_SECTION_X509_CERTIFICATES; + public static String SecurityPage_SECTION_X509_ALGORITHMS; public static String SELECTION_STORE_NO_SELECTION; public static String SELECT_RANGE_WIZARD_DESCRIPTION; public static String SELECT_RANGE_WIZARD_TEXT; diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java new file mode 100644 index 0000000000..1f7b33a20a --- /dev/null +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/SecurityPage.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.flightrecorder.ui.pages; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.openjdk.jmc.common.IDescribable; +import org.openjdk.jmc.common.IState; +import org.openjdk.jmc.common.IWritableState; +import org.openjdk.jmc.common.item.IAggregator; +import org.openjdk.jmc.common.item.IItem; +import org.openjdk.jmc.common.item.IItemCollection; +import org.openjdk.jmc.common.item.IItemFilter; +import org.openjdk.jmc.common.item.IMemberAccessor; +import org.openjdk.jmc.common.item.ItemCollectionToolkit; +import org.openjdk.jmc.common.item.ItemFilters; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.common.unit.IRange; +import org.openjdk.jmc.flightrecorder.JfrAttributes; +import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators; +import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; +import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; +import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; +import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; +import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; +import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory; +import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage; +import org.openjdk.jmc.flightrecorder.ui.IPageContainer; +import org.openjdk.jmc.flightrecorder.ui.IPageDefinition; +import org.openjdk.jmc.flightrecorder.ui.IPageUI; +import org.openjdk.jmc.flightrecorder.ui.StreamModel; +import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; +import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit; +import org.openjdk.jmc.flightrecorder.ui.common.FilterComponent; +import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector; +import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; +import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; +import org.openjdk.jmc.flightrecorder.ui.common.ItemList; +import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder; +import org.openjdk.jmc.flightrecorder.ui.common.ItemRow; +import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; +import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; +import org.openjdk.jmc.ui.UIPlugin; +import org.openjdk.jmc.ui.charts.IXDataRenderer; +import org.openjdk.jmc.ui.charts.RendererToolkit; +import org.openjdk.jmc.ui.charts.XYChart; +import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; +import org.openjdk.jmc.ui.column.ColumnMenusFactory; +import org.openjdk.jmc.ui.column.TableSettings; +import org.openjdk.jmc.ui.handlers.MCContextMenuManager; +import org.openjdk.jmc.ui.misc.ChartCanvas; +import org.openjdk.jmc.ui.misc.CompositeToolkit; +import org.openjdk.jmc.ui.misc.PersistableSashForm; +import org.openjdk.jmc.ui.misc.SWTColorToolkit; + +public class SecurityPage extends AbstractDataPage { + + public static class SecurityPageFactory implements IDataPageFactory { + + @Override + public String getName(IState state) { + return Messages.SecurityPage_PAGE_NAME; + } + + @Override + public ImageDescriptor getImageDescriptor(IState state) { + return FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.PAGE_SECURITY); + } + + @Override + public String[] getTopics(IState state) { + return new String[] {JfrRuleTopics.SECURITY}; + } + + @Override + public IDisplayablePage createPage(IPageDefinition definition, StreamModel items, IPageContainer editor) { + return new SecurityPage(definition, items, editor); + } + + } + + private static ColumnLabelProvider LEGEND_LP = new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + return getText(element, IDescribable::getName); + } + + @Override + public String getToolTipText(Object element) { + return getText(element, IDescribable::getDescription); + }; + + private String getText(Object element, Function accessor) { + return accessor.apply(JdkAggregators.X509_CERTIFICATE_COUNT); + }; + + @Override + public Image getImage(Object element) { + return SWTColorToolkit + .getColorThumbnail(SWTColorToolkit.asRGB(DataPageToolkit.getFieldColor((String) element))); + + }; + }; + + private static ColumnLabelProvider ICON_LP = new ColumnLabelProvider() { + @Override + public String getText(Object element) { + return ""; // Return empty string for image-only column + } + + @SuppressWarnings("unchecked") + @Override + public Image getImage(Object element) { + if (element instanceof IItem) { + IItem iItem = (IItem) element; + IMemberAccessor iconAccessor = (IMemberAccessor) JdkAttributes.CRYPTO_ICON + .getAccessor(iItem.getType()); + + ImageDescriptor descriptor; + if (iconAccessor != null) { + String iconText = iconAccessor.getMember(iItem); + if (iconText.contains("ACTION")) + descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_ACTION); + else if (iconText.contains("ATTENTION")) + descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_ATTENTION); + else + descriptor = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_CRYPTO_OK); + + if (descriptor != null) { + return descriptor.createImage(); + } + } + } + return null; + } + }; + + private static final ItemListBuilder SECURITY_X509_ALGORITHM_LIST = new ItemListBuilder(); + + static { + + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CRYPTO_ICON, ICON_LP); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CRYPTO_REMARK); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SIGNATURE_ALGORITHM); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.KEY_TYPE); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.KEY_LENGTH); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SERIAL_NUMBER); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.SUBJECT); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.VALID_FROM); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.VALID_UNTIL); + SECURITY_X509_ALGORITHM_LIST.addColumn(JfrAttributes.EVENT_THREAD); + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ISSUER); + + } + + private static final String SASH = "sash"; //$NON-NLS-1$ + private static final String SECURITY_X509_ALGORITHM_TABLE = "securityX509AlgorithmTable"; //$NON-NLS-1$ + private static final String SECURITY_X509_ALGORITHM_FILTER = "securityX509AlgorithmFilter"; //$NON-NLS-1$ + private static final String CHART = "chart"; //$NON-NLS-1$ + private static final String SERIES = "series"; //$NON-NLS-1$ + private static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$ + private static final String X509_CERTIFICATE_COUNT = "x509CertificateCount"; //$NON-NLS-1$ + + private class SecurityPageUI implements IPageUI { + + private final SashForm sash; + private final IPageContainer pageContainer; + private final CheckboxTableViewer chartLegend; + private final ChartCanvas chartCanvas; + private final ItemList securityX509AlgorithmTable; + private final FilterComponent securityX509AlgorithmFilter; + private IItemCollection selectionItems; + private XYChart chart; + private IRange currentRange; + private FlavorSelector flavorSelector; + + SecurityPageUI(Composite parent, FormToolkit toolkit, IPageContainer pageContainer, IState state) { + this.pageContainer = pageContainer; + Form form = DataPageToolkit.createForm(parent, toolkit, getName(), getIcon()); + sash = new SashForm(form.getBody(), SWT.VERTICAL); + toolkit.adapt(sash); + + Boolean isCertificateIdQty = getDataSource().getItems().getAggregate(JdkAggregators.IS_CERTIFICATE_ID_QTY); + + if (isCertificateIdQty) + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ID_QTY); + else + SECURITY_X509_ALGORITHM_LIST.addColumn(JdkAttributes.CERTIFICATE_ID); + + //Algorithm table + Section s2 = CompositeToolkit.createSection(sash, toolkit, Messages.SecurityPage_SECTION_X509_ALGORITHMS); + securityX509AlgorithmTable = SECURITY_X509_ALGORITHM_LIST.buildWithoutBorder(s2, + TableSettings.forState(state.getChild(SECURITY_X509_ALGORITHM_TABLE))); + + securityX509AlgorithmTable.getManager().getViewer().addSelectionChangedListener(e -> pageContainer + .showSelection(ItemCollectionToolkit.build(securityX509AlgorithmTable.getSelection().get()))); + securityX509AlgorithmFilter = FilterComponent.createFilterComponent(securityX509AlgorithmTable, + securityX509AlgorithmFilterState, getDataSource().getItems().apply(JdkFilters.SECURITY), + pageContainer.getSelectionStore()::getSelections, this::onAlgorithmFilterChange); + MCContextMenuManager mm1 = MCContextMenuManager + .create(securityX509AlgorithmTable.getManager().getViewer().getControl()); + ColumnMenusFactory.addDefaultMenus(securityX509AlgorithmTable.getManager(), mm1); + SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), + securityX509AlgorithmTable, Messages.SecurityPage_TABLE_SELECTION, mm1); + mm1.add(securityX509AlgorithmFilter.getShowFilterAction()); + mm1.add(securityX509AlgorithmFilter.getShowSearchAction()); + securityX509AlgorithmFilter.loadState(state.getChild(SECURITY_X509_ALGORITHM_FILTER)); + s2.setClient(securityX509AlgorithmFilter.getComponent()); + + PersistableSashForm.loadState(sash, state.getChild(SASH)); + flavorSelector = FlavorSelector.itemsWithTimerange(form, JdkFilters.SECURITY, getDataSource().getItems(), + pageContainer, this::onInputSelected, this::onShow, flavorSelectorState); + addResultActions(form); + onAlgorithmFilterChange(securityX509AlgorithmFilterState); + securityX509AlgorithmTable.getManager().setSelectionState(securityX509AlgorithmSelectionState); + + //Create Chart + Composite chartContainer = toolkit.createComposite(sash); + chartContainer.setLayout(new GridLayout(2, false)); + chartCanvas = new ChartCanvas(chartContainer); + chart = new XYChart(pageContainer.getRecordingRange(), RendererToolkit.empty(), 120); + chart.setVisibleRange(timelineRange.getStart(), timelineRange.getEnd()); + chart.addVisibleRangeListener(r -> timelineRange = r); + chartCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + DataPageToolkit.setChart(chartCanvas, chart, pageContainer::showSelection); + SelectionStoreActionToolkit.addSelectionStoreRangeActions(pageContainer.getSelectionStore(), chart, + JfrAttributes.LIFETIME, Messages.SecurityPage_TIMELINE_SELECTION, chartCanvas.getContextMenu()); + DataPageToolkit.createChartTimestampTooltip(chartCanvas); + + chartLegend = CheckboxTableViewer.newCheckList(chartContainer, SWT.BORDER); + GridData gd = new GridData(SWT.FILL, SWT.FILL, false, true); + gd.widthHint = 120; + chartLegend.getTable().setLayoutData(gd); + chartLegend.setContentProvider(ArrayContentProvider.getInstance()); + chartLegend.setLabelProvider(LEGEND_LP); + chartLegend.addCheckStateListener(e -> buildChart()); + chartLegend.addSelectionChangedListener(e -> buildChart()); + ColumnViewerToolTipSupport.enableFor(chartLegend); + List chartSeries = new ArrayList<>(); + chartSeries.addAll(Arrays.asList(X509_CERTIFICATE_COUNT)); + chartLegend.setInput(chartSeries.toArray()); + IState chartState = state.getChild(CHART); + if (chartState != null) { + for (IState c : chartState.getChildren()) { + chartLegend.setChecked(c.getAttribute(ID_ATTRIBUTE), true); + } + } + + onShow(true); + } + + private Optional buildBarChart( + IItemCollection items, IAggregator aggregator, String id) { + if (chartLegend.getChecked(id)) { + return Optional.of(DataPageToolkit.buildTimestampHistogram(aggregator.getName(), + aggregator.getDescription(), items, aggregator, DataPageToolkit.getFieldColor(id))); + } + return Optional.empty(); + } + + private void buildChart() { + IItemCollection itemsInRange = getItems(); + List rows = new ArrayList<>(); + IItemCollection securityEvents = itemsInRange.apply(JdkFilters.SECURITY); + buildBarChart(securityEvents, JdkAggregators.X509_CERTIFICATE_COUNT, X509_CERTIFICATE_COUNT) + .ifPresent(rows::add); + + IXDataRenderer root = RendererToolkit.uniformRows(rows); + chartCanvas.replaceRenderer(root); + } + + private void onShow(Boolean show) { + IRange range = show ? currentRange : pageContainer.getRecordingRange(); + chart.setVisibleRange(range.getStart(), range.getEnd()); + if (chartLegend != null) + buildChart(); + } + + private void onInputSelected(IItemCollection items, IRange timeRange) { + this.currentRange = timeRange; + selectionItems = items; + securityX509AlgorithmTable.show(getItems().apply(JdkFilters.SECURITY)); + if (chartLegend != null) + buildChart(); + } + + private IItemCollection getItems() { + return selectionItems != null ? selectionItems.apply(JdkFilters.SECURITY) + : getDataSource().getItems().apply(JdkFilters.SECURITY); + } + + private void onAlgorithmFilterChange(IItemFilter filter) { + securityX509AlgorithmFilter.filterChangeHelper(filter, securityX509AlgorithmTable, + getDataSource().getItems().apply(JdkFilters.SECURITY)); + securityX509AlgorithmFilterState = filter; + if (chartLegend != null) + buildChart(); + } + + @Override + public void saveTo(IWritableState state) { + PersistableSashForm.saveState(sash, state.createChild(SASH)); + IWritableState chartState = state.createChild(CHART); + securityX509AlgorithmTable.getManager().getSettings() + .saveState(state.createChild(SECURITY_X509_ALGORITHM_TABLE)); + securityX509AlgorithmFilter.saveState(state.createChild(SECURITY_X509_ALGORITHM_FILTER)); + for (Object o : chartLegend.getCheckedElements()) { + chartState.createChild(SERIES).putString(ID_ATTRIBUTE, ((String) o)); + } + + saveToLocal(); + } + + private void saveToLocal() { + securityX509AlgorithmSelectionState = securityX509AlgorithmTable.getManager().getSelectionState(); + flavorSelectorState = flavorSelector.getFlavorSelectorState(); + } + } + + private IRange timelineRange; + private FlavorSelectorState flavorSelectorState; + private IItemFilter securityX509AlgorithmFilterState; + private SelectionState securityX509AlgorithmSelectionState; + + public SecurityPage(IPageDefinition defintion, StreamModel items, IPageContainer editor) { + super(defintion, items, editor); + timelineRange = editor.getRecordingRange(); + } + + @Override + public IItemFilter getDefaultSelectionFilter() { + return ItemFilters.type(JdkTypeIDs.X509_CERTIFICATE); + } + + @Override + public IPageUI display(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { + return new SecurityPageUI(parent, toolkit, editor, state); + } + +} diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/resources/org/openjdk/jmc/flightrecorder/ui/messages/internal/messages.properties b/application/org.openjdk.jmc.flightrecorder.ui/src/main/resources/org/openjdk/jmc/flightrecorder/ui/messages/internal/messages.properties index 17827f2b13..6413062dd5 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/resources/org/openjdk/jmc/flightrecorder/ui/messages/internal/messages.properties +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/resources/org/openjdk/jmc/flightrecorder/ui/messages/internal/messages.properties @@ -611,6 +611,12 @@ TlabPage_INSIDE_TLAB_SUM_PERCENTAGE_DESC=The estimated allocation size in TLABs TlabPage_OUTSIDE_TLAB_SUM_PERCENTAGE=Alloc Outside TLABs (%) TlabPage_OUTSIDE_TLAB_SUM_PERCENTAGE_DESC=The total size of allocations outside TLABs as a percentage of all allocations outside TLABs. +SecurityPage_PAGE_NAME=Security +SecurityPage_TIMELINE_SELECTION=Security X509 +SecurityPage_TABLE_SELECTION=Security X509 Selection +SecurityPage_SECTION_X509_CERTIFICATES=X509 Certificates +SecurityPage_SECTION_X509_ALGORITHMS=Signature Algorithms + JVMInformationPage_COLUMN_NEW_VALUE=New Value JVMInformationPage_COLUMN_OLD_VALUE=Old Value JVMInformationPage_COLUMN_VALUE=Value diff --git a/application/org.openjdk.jmc.ui/icons/crypto_action.svg b/application/org.openjdk.jmc.ui/icons/crypto_action.svg new file mode 100644 index 0000000000..955b71014a --- /dev/null +++ b/application/org.openjdk.jmc.ui/icons/crypto_action.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/org.openjdk.jmc.ui/icons/crypto_attention.svg b/application/org.openjdk.jmc.ui/icons/crypto_attention.svg new file mode 100644 index 0000000000..7426b4b9a2 --- /dev/null +++ b/application/org.openjdk.jmc.ui/icons/crypto_attention.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/org.openjdk.jmc.ui/icons/crypto_ok.svg b/application/org.openjdk.jmc.ui/icons/crypto_ok.svg new file mode 100644 index 0000000000..50e2ba8da0 --- /dev/null +++ b/application/org.openjdk.jmc.ui/icons/crypto_ok.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/UIPlugin.java b/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/UIPlugin.java index 17d162bfa8..583cba22bb 100644 --- a/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/UIPlugin.java +++ b/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/UIPlugin.java @@ -192,6 +192,12 @@ public class UIPlugin extends MCAbstractUIPlugin { public static final String ICON_FIND = "search-glass.png"; //$NON-NLS-1$ + public static final String ICON_CRYPTO_ACTION = "crypto_action.svg"; //$NON-NLS-1$ + + public static final String ICON_CRYPTO_ATTENTION = "crypto_attention.svg"; //$NON-NLS-1$ + + public static final String ICON_CRYPTO_OK = "crypto_ok.svg"; //$NON-NLS-1$ + public static enum ImageRegistryPrefixes { COLORED_SQUARE, TYPE_IMAGES, NONE // Use NONE prefix to avoid conflict with other prefixes } diff --git a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/item/Aggregators.java b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/item/Aggregators.java index 9237b79f65..ebe93b584c 100644 --- a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/item/Aggregators.java +++ b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/item/Aggregators.java @@ -1044,6 +1044,10 @@ public SetConsumer newItemConsumer(IType itemType) { } }; + public static IAggregator distinctAttribute(String typeId, IAttribute attribute) { + return filter(distinctAsString(attribute, "~ "), ItemFilters.type(typeId)); //$NON-NLS-1$ + } + public static IAggregator distinctAsString(String typeId, IAttribute attribute) { return filter(distinctAsString(attribute, ", "), ItemFilters.type(typeId)); //$NON-NLS-1$ } diff --git a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/messages/internal/Messages.java b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/messages/internal/Messages.java index e4d329a696..c6dc1d756e 100644 --- a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/messages/internal/Messages.java +++ b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/messages/internal/Messages.java @@ -85,6 +85,20 @@ public class Messages { public static final String TimestampKind_SINCE_1970_MSG = "TimestampKind_SINCE_1970_MSG"; //$NON-NLS-1$ public static final String TypeHandling_MESSAGE_SIZE = "TypeHandling_MESSAGE_SIZE"; //$NON-NLS-1$ public static final String UnitLookup_TIMESTAMP_OUT_OF_RANGE = "UnitLookup_TIMESTAMP_OUT_OF_RANGE"; //$NON-NLS-1$ + public static final String Crypto_ACTION = "Crypto_ACTION"; //$NON-NLS-1$ + public static final String Crypto_ATTENTION = "Crypto_ATTENTION"; //$NON-NLS-1$ + public static final String Crypto_OK = "Crypto_OK"; //$NON-NLS-1$ + public static final String Crypto_SHA1 = "Crypto_SHA1"; //$NON-NLS-1$ + public static final String Crypto_MD2 = "Crypto_MD2"; //$NON-NLS-1$ + public static final String Crypto_MD5 = "Crypto_MD5"; //$NON-NLS-1$ + public static final String Crypto_RSA_INSUFFICIENT_KEY_SIZE = "Crypto_RSA_INSUFFICIENT_KEY_SIZE"; //$NON-NLS-1$ + public static final String Crypto_RSA_KEY_SIZE_1024 = "Crypto_RSA_KEY_SIZE_1024"; //$NON-NLS-1$ + public static final String Crypto_RSA_KEY_SIZE_LESS_2048 = "Crypto_RSA_KEY_SIZE_LESS_2048"; //$NON-NLS-1$ + public static final String Crypto_DSA_INSUFFICIENT_KEY_SIZE = "Crypto_DSA_INSUFFICIENT_KEY_SIZE"; //$NON-NLS-1$ + public static final String Crypto_DSA_KEY_SIZE_LESS_2048 = "Crypto_DSA_KEY_SIZE_LESS_2048"; //$NON-NLS-1$ + public static final String Crypto_EC_INSUFFICIENT_KEY_SIZE = "Crypto_EC_INSUFFICIENT_KEY_SIZE"; //$NON-NLS-1$ + public static final String Crypto_Certificate_Expiring = "Crypto_Certificate_Expiring"; //$NON-NLS-1$ + public static final String Crypto_Certificate_Expired = "Crypto_Certificate_Expired"; //$NON-NLS-1$ private Messages() { } diff --git a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/security/CryptoUtil.java b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/security/CryptoUtil.java new file mode 100644 index 0000000000..3d3f052984 --- /dev/null +++ b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/security/CryptoUtil.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.common.security; + +import java.text.MessageFormat; +import org.openjdk.jmc.common.messages.internal.Messages; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.common.unit.UnitLookup; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class CryptoUtil { + + private static final String ACTION = Messages.getString(Messages.Crypto_ACTION); + private static final String ATTENTION = Messages.getString(Messages.Crypto_ATTENTION); + private static final String OK = Messages.getString(Messages.Crypto_OK); + + public static String getCryptoRemark( + String signatureAlgorithm, String keyType, Long keyLength, IQuantity expiryDate) { + + String remark = ""; + + if (signatureAlgorithm.contains("SHA1")) { + remark = ATTENTION.concat(Messages.getString(Messages.Crypto_SHA1)); + } else if (signatureAlgorithm.contains("MD2")) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_MD2)); + } else if (signatureAlgorithm.contains("MD5")) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_MD5)); + } + + if (keyType.contains("RSA")) { + if (keyLength < 1024) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_RSA_INSUFFICIENT_KEY_SIZE)).concat(remark); + } else if (keyLength == 1024) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_RSA_KEY_SIZE_1024)).concat(remark); + } else if (keyLength < 2048) { + remark = remark.concat(ATTENTION).concat(Messages.getString(Messages.Crypto_RSA_KEY_SIZE_LESS_2048)); + } + } + + if (keyType.contains("DSA")) { + if (keyLength < 1024) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_DSA_INSUFFICIENT_KEY_SIZE)).concat(remark); + } else if (keyLength < 2048) { + remark = remark.concat(ATTENTION).concat(Messages.getString(Messages.Crypto_DSA_KEY_SIZE_LESS_2048)); + } + } + + if (keyType.contains("EC")) { + if (keyLength < 224) { + remark = ACTION.concat(Messages.getString(Messages.Crypto_EC_INSUFFICIENT_KEY_SIZE)).concat(remark); + } + } + + if (expiryDate != null) { + + IQuantity duration = expiryDate.subtract(UnitLookup.EPOCH_MS.quantity(System.currentTimeMillis())); + long expiringInDays = TimeUnit.MILLISECONDS.toDays(duration.longValue()); + + if ((expiringInDays > 0) && (expiringInDays < 90)) { + remark = remark.concat(ATTENTION).concat( + MessageFormat.format(Messages.getString(Messages.Crypto_Certificate_Expiring), expiringInDays)); + } else if (expiringInDays < 0) { + remark = ACTION.concat(MessageFormat.format(Messages.getString(Messages.Crypto_Certificate_Expired), + (expiringInDays * -1))).concat(remark); + } + + } + + if (remark.equals("")) { + remark = OK; + } + + return remark; + } + + public static String getCryptoIcon( + String signatureAlgorithm, String keyType, Long keyLength, IQuantity expiryDate) { + + String icon = ""; + + if (signatureAlgorithm.contains("SHA1")) { + icon = "ATTENTION"; + } else if (signatureAlgorithm.contains("MD2")) { + icon = "ACTION"; + } else if (signatureAlgorithm.contains("MD5")) { + icon = "ACTION"; + } + + if (keyType.contains("RSA")) { + if (keyLength < 1024) { + icon = "ACTION".concat(icon); + } else if (keyLength == 1024) { + icon = "ACTION".concat(icon); + } else if (keyLength < 2048) { + icon = icon.concat("ATTENTION"); + } + } + + if (keyType.contains("DSA")) { + if (keyLength < 1024) { + icon = "ACTION".concat(icon); + } else if (keyLength < 2048) { + icon = icon.concat("ATTENTION"); + } + } + + if (keyType.contains("EC")) { + if (keyLength < 224) { + icon = "ACTION".concat(icon); + } + } + + if (expiryDate != null) { + + IQuantity duration = expiryDate.subtract(UnitLookup.EPOCH_MS.quantity(System.currentTimeMillis())); + long expiringInDays = TimeUnit.MILLISECONDS.toDays(duration.longValue()); + + if ((expiringInDays > 0) && (expiringInDays < 90)) { + icon = icon.concat("ATTENTION"); + } else if (expiringInDays < 0) { + icon = "ACTION".concat(icon); + } + + } + + if (icon.equals("")) { + icon = "OK"; + } + + return icon; + } + + public static String getCryptoRuleResult( + String signatureAlgorithm, String keyType, Long keyLength, IQuantity expiryDate, Number certificateId) { + + List> remarks = new ArrayList<>(); + String strCertificateId = ""; + if (!certificateId.equals(0)) { + strCertificateId = "Certificate Id : ".concat(certificateId.toString()).concat(" - "); + } + + if (signatureAlgorithm.contains("SHA1")) { + remarks.add(Map.entry(ATTENTION, strCertificateId.concat(Messages.getString(Messages.Crypto_SHA1)))); + } else if (signatureAlgorithm.contains("MD2")) { + remarks.add(Map.entry(ACTION, strCertificateId.concat(Messages.getString(Messages.Crypto_MD2)))); + } else if (signatureAlgorithm.contains("MD5")) { + remarks.add(Map.entry(ACTION, strCertificateId.concat(Messages.getString(Messages.Crypto_MD5)))); + } + + if (keyType.contains("RSA")) { + if (keyLength < 1024) { + remarks.add(Map.entry(ACTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_RSA_INSUFFICIENT_KEY_SIZE)))); + } else if (keyLength == 1024) { + remarks.add(Map.entry(ACTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_RSA_KEY_SIZE_1024)))); + } else if (keyLength < 2048) { + remarks.add(Map.entry(ATTENTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_RSA_KEY_SIZE_LESS_2048)))); + } + } + + if (keyType.contains("DSA")) { + if (keyLength < 1024) { + remarks.add(Map.entry(ACTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_DSA_INSUFFICIENT_KEY_SIZE)))); + } else if (keyLength < 2048) { + remarks.add(Map.entry(ATTENTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_DSA_KEY_SIZE_LESS_2048)))); + } + } + + if (keyType.contains("EC")) { + if (keyLength < 224) { + remarks.add(Map.entry(ACTION, + strCertificateId.concat(Messages.getString(Messages.Crypto_EC_INSUFFICIENT_KEY_SIZE)))); + } + } + + if (expiryDate != null) { + + IQuantity duration = expiryDate.subtract(UnitLookup.EPOCH_MS.quantity(System.currentTimeMillis())); + long expiringInDays = TimeUnit.MILLISECONDS.toDays(duration.longValue()); + + if ((expiringInDays > 0) && (expiringInDays < 90)) { + remarks.add(Map.entry(ATTENTION, strCertificateId.concat(MessageFormat + .format(Messages.getString(Messages.Crypto_Certificate_Expiring), expiringInDays)))); + } else if (expiringInDays < 0) { + remarks.add(Map.entry(ACTION, strCertificateId.concat(MessageFormat + .format(Messages.getString(Messages.Crypto_Certificate_Expired), (expiringInDays * -1))))); + } + + } + + if (remarks.isEmpty()) { + remarks.add(Map.entry(OK, "Everything is fine")); + } + + return convertRemarksToFormattedString(remarks); + } + + public static String convertRemarksToFormattedString(List> entries) { + Map> groupedMap = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + groupedMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()) + .add(entry.getKey().concat(entry.getValue())); + } + + StringBuilder result = new StringBuilder(); + + for (Map.Entry> entry : groupedMap.entrySet()) { + for (String value : entry.getValue()) { + if (value.contains("Everything is fine")) + continue; + result.append(" • ").append(value).append("\n"); + } + } + return result.toString().trim(); + } + +} diff --git a/core/org.openjdk.jmc.common/src/main/resources/org/openjdk/jmc/common/messages/internal/messages.properties b/core/org.openjdk.jmc.common/src/main/resources/org/openjdk/jmc/common/messages/internal/messages.properties index 90cc56a2cf..eda68c1e65 100644 --- a/core/org.openjdk.jmc.common/src/main/resources/org/openjdk/jmc/common/messages/internal/messages.properties +++ b/core/org.openjdk.jmc.common/src/main/resources/org/openjdk/jmc/common/messages/internal/messages.properties @@ -208,3 +208,18 @@ NameConverter_JVM_ARCH_32BIT=32-bit NameConverter_JVM_ARCH_64BIT=64-bit NameConverter_DEBUG=Debug NameConverter_UNKNOWN_LOCAL_JVM=Local + +Crypto_ACTION=Action Required. +Crypto_ATTENTION=Attention Needed. +Crypto_OK=Ok +Crypto_SHA1= SHA-1 signature. +Crypto_MD2= MD2 signature. +Crypto_MD5= MD5 signature. +Crypto_RSA_INSUFFICIENT_KEY_SIZE= RSA signature with insufficient key size. +Crypto_RSA_KEY_SIZE_1024= Removed root certificates with 1024-bit keys. +Crypto_RSA_KEY_SIZE_LESS_2048= Use RSA recommended key size. +Crypto_DSA_INSUFFICIENT_KEY_SIZE= DSA signature with insufficient key size. +Crypto_DSA_KEY_SIZE_LESS_2048= Use DSA recommended key size. +Crypto_EC_INSUFFICIENT_KEY_SIZE= EC signature with insufficient key size. +Crypto_Certificate_Expiring= The Certificate is expiring in {0} days. +Crypto_Certificate_Expired= The Certificate has expired before {0} days. diff --git a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/general/CryptoSecurityRule.java b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/general/CryptoSecurityRule.java new file mode 100644 index 0000000000..b09e2c69b5 --- /dev/null +++ b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/general/CryptoSecurityRule.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.flightrecorder.rules.jdk.general; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.openjdk.jmc.common.item.IItemCollection; +import org.openjdk.jmc.common.util.IPreferenceValueProvider; +import org.openjdk.jmc.common.util.TypedPreference; +import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; +import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; +import org.openjdk.jmc.flightrecorder.rules.IResult; +import org.openjdk.jmc.flightrecorder.rules.IResultValueProvider; +import org.openjdk.jmc.flightrecorder.rules.IRule; +import org.openjdk.jmc.flightrecorder.rules.ResultBuilder; +import org.openjdk.jmc.flightrecorder.rules.Severity; +import org.openjdk.jmc.flightrecorder.rules.TypedResult; +import org.openjdk.jmc.flightrecorder.rules.jdk.messages.internal.Messages; +import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; +import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit; +import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit.EventAvailability; +import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit.RequiredEventsBuilder; + +public class CryptoSecurityRule implements IRule { + + private static final String CRYPTO_SECURITY_RESULT_ID = "CryptoSecurityRule"; //$NON-NLS-1$ + + private static final Map REQUIRED_EVENTS = RequiredEventsBuilder.create() + .addEventType(JdkTypeIDs.X509_CERTIFICATE, EventAvailability.AVAILABLE).build(); + + private static final String ACTION = Messages.getString(Messages.Crypto_ACTION); + + private static final String ATTENTION = Messages.getString(Messages.Crypto_ATTENTION); + + private static final Collection> RESULT_ATTRIBUTES = Arrays + .> asList(TypedResult.SCORE); + + @Override + public String getId() { + return CRYPTO_SECURITY_RESULT_ID; + } + + @Override + public String getTopic() { + return JfrRuleTopics.SECURITY; + } + + @Override + public String getName() { + return Messages.getString(Messages.CryptoSecurity_RULE_NAME); + } + + @Override + public Map getRequiredEvents() { + return REQUIRED_EVENTS; + } + + private IResult getResult( + IItemCollection items, IPreferenceValueProvider valueProvider, IResultValueProvider resultProvider) { + + boolean actionNeeded = (RulesToolkit.findMatches(JdkTypeIDs.X509_CERTIFICATE, items, + JdkAttributes.CRYPTO_REMARK, Messages.getString(Messages.Crypto_ACTION), false) != null); //$NON-NLS-1$ + boolean attentionNeeded = (RulesToolkit.findMatches(JdkTypeIDs.X509_CERTIFICATE, items, + JdkAttributes.CRYPTO_REMARK, Messages.getString(Messages.Crypto_ATTENTION), false) != null); //$NON-NLS-1$ + + String ruleResult = RulesToolkit.findAttribute(JdkTypeIDs.X509_CERTIFICATE, items, + JdkAttributes.CRYPTO_RULE_RESULT); + Result processedResult = processResult(ruleResult); + + ruleResult = processedResult.cleanedAllActions + "\n" + processedResult.cleanedAllAttentions; + + if (actionNeeded) { + if (attentionNeeded) { + return ResultBuilder.createFor(CryptoSecurityRule.this, valueProvider).setSeverity(Severity.WARNING) + .setSummary(Messages.getString(Messages.Crypto_ACTION_ATTENTION_INFO) + + MessageFormat.format(Messages.getString(Messages.Crypto_REFERENCE_INFO), ruleResult)) + .build(); + } else { + return ResultBuilder.createFor(CryptoSecurityRule.this, valueProvider).setSeverity(Severity.WARNING) + .setSummary(Messages.getString(Messages.Crypto_ACTION_INFO) + + MessageFormat.format(Messages.getString(Messages.Crypto_REFERENCE_INFO), ruleResult)) + .build(); + } + } else if (attentionNeeded) { + return ResultBuilder.createFor(CryptoSecurityRule.this, valueProvider).setSeverity(Severity.WARNING) + .setSummary(Messages.getString(Messages.Crypto_ATTENTION_INFO) + + MessageFormat.format(Messages.getString(Messages.Crypto_REFERENCE_INFO), ruleResult)) + .build(); + } else { + return ResultBuilder.createFor(CryptoSecurityRule.this, valueProvider).setSeverity(Severity.OK) + .setSummary("OK").build(); + } + + } + + public static class Result { + public final String allActions; // joined by '~' + public final String allAttentions; // joined by '~' + public final int allActionCount; + public final int allAttentionCount; + public final String cleanedAllActions; // allActions with "Action Required." removed + public final String cleanedAllAttentions;// allAttentions with "Attention Needed." removed + + public Result(String allActions, String allAttentions, int allActionCount, int allAttentionCount, + String cleanedAllActions, String cleanedAllAttentions) { + this.allActions = allActions; + this.allAttentions = allAttentions; + this.allActionCount = allActionCount; + this.allAttentionCount = allAttentionCount; + this.cleanedAllActions = cleanedAllActions; + this.cleanedAllAttentions = cleanedAllAttentions; + } + } + + private Result processResult(String input) { + + if (input == null) + input = ""; + + final String ACTION_BULLET = "🔴 "; + final String ATTENTION_BULLET = "🟠 "; + + String[] tokens = input.split("~", -1); + + List actionFragments = new ArrayList<>(); + List attentionFragments = new ArrayList<>(); + List originalFragments = new ArrayList<>(); + + String bulletChars = "[\\u2022\\u2023\\u2027•\\*]"; + String patternString = "(?s)\\b(?:Action Required\\.|Attention Needed\\.).*?(?=(?:" + bulletChars + ")|$)"; + Pattern fragmentPattern = Pattern.compile(patternString); + + for (String token : tokens) { + if (token == null || token.trim().isEmpty()) + continue; + + Matcher m = fragmentPattern.matcher(token); + while (m.find()) { + String frag = m.group().trim(); + frag = frag.replaceAll("^[\\s\\u2022\\u2023\\u2027•\\*]+", "").trim(); + + if (frag.isEmpty()) + continue; + + boolean isAction = frag.contains(ACTION); + boolean isAttention = frag.contains(ATTENTION); + + if (isAction) { + actionFragments.add(ACTION_BULLET + frag); + } + if (isAttention) { + attentionFragments.add(ATTENTION_BULLET + frag); + } + + String prefix; + if (isAction && isAttention) + prefix = ACTION_BULLET + ATTENTION_BULLET; + else if (isAction) + prefix = ACTION_BULLET; + else if (isAttention) + prefix = ATTENTION_BULLET; + else + prefix = ""; + + String fragClean = frag.replace(ACTION, "").replace(ATTENTION, "").trim(); + + if (!fragClean.isEmpty()) { + originalFragments.add(prefix + fragClean); + } + } + } + + int allActionCount = actionFragments.size(); + int allAttentionCount = attentionFragments.size(); + + // Build titled sections + String allActions = "\nAction Required (" + allActionCount + ")\n\n" + + (actionFragments.isEmpty() ? "" : String.join("\n", actionFragments)) + "\n"; + + String allAttentions = "\nAttention Needed (" + allAttentionCount + ")\n\n" + + (attentionFragments.isEmpty() ? "" : String.join("\n", attentionFragments)) + "\n"; + + // Clean versions + String cleanedAllActions = "\n\nAction Required (" + allActionCount + ")\n\n" + actionFragments.stream() + .map(s -> s.replace(ACTION, "").trim()).filter(s -> !s.isEmpty()).collect(Collectors.joining("\n")) + + "\n"; + + String cleanedAllAttentions = "\nAttention Needed (" + allAttentionCount + ")\n\n" + attentionFragments.stream() + .map(s -> s.replace(ATTENTION, "").trim()).filter(s -> !s.isEmpty()).collect(Collectors.joining("\n")) + + "\n"; + + return new Result(allActions, allAttentions, allActionCount, allAttentionCount, cleanedAllActions, + cleanedAllAttentions); + } + + @Override + public RunnableFuture createEvaluation( + final IItemCollection items, final IPreferenceValueProvider preferenceValueProvider, + final IResultValueProvider dependencyResults) { + FutureTask evaluationTask = new FutureTask<>(new Callable() { + @Override + public IResult call() throws Exception { + return getResult(items, preferenceValueProvider, dependencyResults); + } + }); + return evaluationTask; + } + + @Override + public Collection> getConfigurationAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection> getResults() { + return RESULT_ATTRIBUTES; + } + +} diff --git a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/Messages.java b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/Messages.java index 79a61cb894..8d29848718 100644 --- a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/Messages.java +++ b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/java/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/Messages.java @@ -325,6 +325,13 @@ public class Messages { public static final String GcFreedRatioRule_RESULT_SHORT_DESCRIPTION = "GcFreedRatioRule_RESULT_SHORT_DESCRIPTION"; //$NON-NLS-1$ public static final String GarbageCollectionInfo_RULE_NAME = "GarbageCollectionInfo_RULE_NAME"; //$NON-NLS-1$ public static final String ZGCAllocationStall_RULE_NAME = "ZGCAllocationStall_RULE_NAME"; //$NON-NLS-1$ + public static final String CryptoSecurity_RULE_NAME = "CryptoSecurity_RULE_NAME"; //$NON-NLS-1$ + public static final String Crypto_ACTION = "Crypto_ACTION"; //$NON-NLS-1$ + public static final String Crypto_ATTENTION = "Crypto_ATTENTION"; //$NON-NLS-1$ + public static final String Crypto_ACTION_INFO = "Crypto_ACTION_INFO"; //$NON-NLS-1$ + public static final String Crypto_ATTENTION_INFO = "Crypto_ATTENTION_INFO"; //$NON-NLS-1$ + public static final String Crypto_ACTION_ATTENTION_INFO = "Crypto_ACTION_ATTENTION_INFO"; //$NON-NLS-1$ + public static final String Crypto_REFERENCE_INFO = "Crypto_REFERENCE_INFO"; //$NON-NLS-1$ public static final String ZGCAllocationStallRule_CONFIG_INFO_LIMIT = "ZGCAllocationStallRule_CONFIG_INFO_LIMIT"; //$NON-NLS-1$ public static final String ZGCAllocationStallRule_CONFIG_INFO_LIMIT_LONG = "ZGCAllocationStallRule_CONFIG_INFO_LIMIT_LONG"; //$NON-NLS-1$ public static final String ZGCAllocationStallRule_RATE = "ZGCAllocationStallRule_RATE"; //$NON-NLS-1$ diff --git a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/META-INF/services/org.openjdk.jmc.flightrecorder.rules.IRule b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/META-INF/services/org.openjdk.jmc.flightrecorder.rules.IRule index 68cc1beff5..43015626ad 100644 --- a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/META-INF/services/org.openjdk.jmc.flightrecorder.rules.IRule +++ b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/META-INF/services/org.openjdk.jmc.flightrecorder.rules.IRule @@ -49,6 +49,7 @@ org.openjdk.jmc.flightrecorder.rules.jdk.exceptions.ErrorRule org.openjdk.jmc.flightrecorder.rules.jdk.exceptions.ExceptionRule org.openjdk.jmc.flightrecorder.rules.jdk.exceptions.FatalErrorRule org.openjdk.jmc.flightrecorder.rules.jdk.general.ClassLeakingRule +org.openjdk.jmc.flightrecorder.rules.jdk.general.CryptoSecurityRule org.openjdk.jmc.flightrecorder.rules.jdk.general.DMSIncidentRule org.openjdk.jmc.flightrecorder.rules.jdk.general.DebugNonSafepointsRule org.openjdk.jmc.flightrecorder.rules.jdk.general.DiscouragedGcOptionsRule diff --git a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/messages.properties b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/messages.properties index 2463ae248c..c880a03266 100644 --- a/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/messages.properties +++ b/core/org.openjdk.jmc.flightrecorder.rules.jdk/src/main/resources/org/openjdk/jmc/flightrecorder/rules/jdk/messages/internal/messages.properties @@ -172,6 +172,13 @@ ContextSwitchRuleFactory_RULE_NAME=Context Switches ContextSwitchRuleFactory_TEXT_INFO=The program causes many context switches during the recording. ContextSwitchRuleFactory_TEXT_INFO_LONG=The program context switched a lot and many threads waited on the same monitor. Consider using fewer threads, or decreasing lock contention by other means. ContextSwitchRuleFactory_TEXT_OK=The program did not context switch excessively during the recording. +CryptoSecurity_RULE_NAME=Crypto Security Rule +Crypto_ACTION=Action Required. +Crypto_ATTENTION=Attention Needed. +Crypto_ACTION_INFO=Action Required in specific domains. +Crypto_ATTENTION_INFO=Attention Needed in specific domains. +Crypto_ACTION_ATTENTION_INFO=Observations indicate action and attention are needed in specific domains. +Crypto_REFERENCE_INFO={0} \n Please refer Security screen for more details. DMSIncidentRule_AGGR_INCIDENTS_COUNT=DMS Incidents DMSIncidentRule_AGGR_INCIDENTS_COUNT_DESC=The number of DMS incidents DMSIncidentRule_CONFIG_WARNING_LIMIT=DMS incident warning limit diff --git a/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/JfrRuleTopics.java b/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/JfrRuleTopics.java index a23b98422f..e3f4fbdd8f 100644 --- a/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/JfrRuleTopics.java +++ b/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/JfrRuleTopics.java @@ -64,5 +64,6 @@ public final class JfrRuleTopics { public static final String THREAD_DUMPS = "thread_dumps"; //$NON-NLS-1$ public static final String THREADS = "threads"; //$NON-NLS-1$ public static final String TLAB = "tlab"; //$NON-NLS-1$ + public static final String SECURITY = "security"; //$NON-NLS-1$ public static final String VM_OPERATIONS = "vm_operations"; //$NON-NLS-1$ } diff --git a/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/RulesToolkit.java b/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/RulesToolkit.java index 2b5ddc6823..6eddaa7af8 100644 --- a/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/RulesToolkit.java +++ b/core/org.openjdk.jmc.flightrecorder.rules/src/main/java/org/openjdk/jmc/flightrecorder/rules/util/RulesToolkit.java @@ -287,6 +287,12 @@ public static String findMatches( ItemFilters.and(ItemFilters.type(typeId), ItemFilters.matches(attribute, regexp)))); } + public static String findAttribute(String typeId, IItemCollection items, IAttribute attribute) { + + return items.getAggregate((IAggregator) Aggregators + .filter(Aggregators.distinctAttribute(typeId, attribute), ItemFilters.and(ItemFilters.type(typeId)))); + } + /** * Gets the value of a certain attribute for a given item * diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAggregators.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAggregators.java index d86573b6db..348f50a1b3 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAggregators.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAggregators.java @@ -524,6 +524,12 @@ public final class JdkAggregators { public static final IAggregator TOP_ADDRESS = Aggregators.min(JdkTypeIDs.NATIVE_LIBRARY, JdkAttributes.TOP_ADDRESS); + public static final IAggregator X509_CERTIFICATE_COUNT = Aggregators + .count(Messages.getString(Messages.AGGR_X509_CERTIFICATES_COUNT), JdkTypeIDs.X509_CERTIFICATE); + + public static final IAggregator IS_CERTIFICATE_ID_QTY = or(JdkTypeIDs.X509_CERTIFICATE, + JdkAttributes.IS_CERTIFICATE_ID_QTY); + /** * Aggregator for getting the first value, ie. the value from the event with the first occurring * start time. diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAttributes.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAttributes.java index 7f1aec2cd8..64c898e922 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAttributes.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkAttributes.java @@ -70,6 +70,7 @@ import org.openjdk.jmc.common.item.IAttribute; import org.openjdk.jmc.common.item.IMemberAccessor; import org.openjdk.jmc.common.item.IType; +import org.openjdk.jmc.common.security.CryptoUtil; import org.openjdk.jmc.common.unit.ContentType; import org.openjdk.jmc.common.unit.IQuantity; import org.openjdk.jmc.common.unit.UnitLookup; @@ -1437,4 +1438,181 @@ public String getMember(U i) { public static final IAttribute RSS_PEAK = new Attribute("peak", //$NON-NLS-1$ Messages.getString(Messages.ATTR_RSS_PEAK), Messages.getString(Messages.ATTR_RSS_PEAK_DESC), MEMORY) { }; + + //Security related attributes + public static final IAttribute SIGNATURE_ALGORITHM = new Attribute("algorithm", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_SIGNATURE_ALGORITHM), + Messages.getString(Messages.ATTR_SIGNATURE_ALGORITHM), PLAIN_TEXT) { + }; + + public static final IAttribute CERTIFICATE_ID = new Attribute("certificateId", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CERTIFICATE_ID), Messages.getString(Messages.ATTR_CERTIFICATE_ID), + RAW_NUMBER) { + }; + + public static final IAttribute CERTIFICATE_ID_QTY = new Attribute("certificateId", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CERTIFICATE_ID), Messages.getString(Messages.ATTR_CERTIFICATE_ID), + NUMBER) { + }; + + public static final IAttribute IS_CERTIFICATE_ID_QTY = Attribute.canonicalize(new Attribute( + "isCertificateIdQty", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CRYPTO_ICON), Messages.getString(Messages.ATTR_CRYPTO_ICON_DESC), FLAG) { + @Override + public IMemberAccessor customAccessor(IType type) { + final IMemberAccessor certificateIdAccessor = type.getAccessor(CERTIFICATE_ID.getKey()); + final IMemberAccessor certificateIdPre9Accessor = type + .getAccessor(CERTIFICATE_ID_QTY.getKey()); + return new IMemberAccessor() { + @Override + public Boolean getMember(U i) { + if (certificateIdAccessor != null) { + return false; + } else if (certificateIdPre9Accessor != null) { + return true; + } + + return false; + } + }; + } + }); + + public static final IAttribute CERTIFICATE_ISSUER = new Attribute("issuer", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CERTIFICATE_ISSUER), Messages.getString(Messages.ATTR_CERTIFICATE_ISSUER), + PLAIN_TEXT) { + }; + + public static final IAttribute KEY_LENGTH = new Attribute("keyLength", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_KEY_LENGTH), Messages.getString(Messages.ATTR_KEY_LENGTH), NUMBER) { + }; + + public static final IAttribute KEY_TYPE = new Attribute("keyType", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_KEY_TYPE), Messages.getString(Messages.ATTR_KEY_TYPE), PLAIN_TEXT) { + }; + + public static final IAttribute SERIAL_NUMBER = new Attribute("serialNumber", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_SERIAL_NUMBER), Messages.getString(Messages.ATTR_SERIAL_NUMBER), + PLAIN_TEXT) { + }; + + public static final IAttribute SUBJECT = new Attribute("subject", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_SUBJECT), Messages.getString(Messages.ATTR_SUBJECT), PLAIN_TEXT) { + }; + + public static final IAttribute VALID_FROM = new Attribute("validFrom", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_VALID_FROM), Messages.getString(Messages.ATTR_RSS_PEAK_DESC), TIMESTAMP) { + }; + + public static final IAttribute VALID_UNTIL = new Attribute("validUntil", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_VALID_UNTIL), Messages.getString(Messages.ATTR_RSS_PEAK_DESC), TIMESTAMP) { + }; + + public static final IAttribute CRYPTO_REMARK = Attribute.canonicalize(new Attribute("cryptoRemark", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CRYPTO_REMARK), Messages.getString(Messages.ATTR_CRYPTO_REMARK_DESC), + PLAIN_TEXT) { + @Override + public IMemberAccessor customAccessor(IType type) { + final IMemberAccessor signatureAlgorithmAccessor = type + .getAccessor(SIGNATURE_ALGORITHM.getKey()); + final IMemberAccessor keyTypeAccessor = type.getAccessor(KEY_TYPE.getKey()); + final IMemberAccessor keyLengthAccessor = type.getAccessor(KEY_LENGTH.getKey()); + final IMemberAccessor expiryDateAccessor = type.getAccessor(VALID_UNTIL.getKey()); + if ((signatureAlgorithmAccessor == null) || (keyTypeAccessor == null) || (keyLengthAccessor == null)) { + return null; + } + return new IMemberAccessor() { + @Override + public String getMember(U i) { + String signatureAlgorithm = signatureAlgorithmAccessor.getMember(i); + String keyType = keyTypeAccessor.getMember(i); + IQuantity keyLength = keyLengthAccessor.getMember(i); + IQuantity expiryDate = null; + if (expiryDateAccessor != null) { + expiryDate = expiryDateAccessor.getMember(i); + } + return signatureAlgorithm != null && keyType != null && keyLength != null + ? CryptoUtil.getCryptoRemark(signatureAlgorithm, keyType, keyLength.longValue(), expiryDate) + : null; + } + }; + } + }); + + public static final IAttribute CRYPTO_ICON = Attribute.canonicalize(new Attribute("cryptoIcon", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CRYPTO_ICON), Messages.getString(Messages.ATTR_CRYPTO_ICON_DESC), + PLAIN_TEXT) { + @Override + public IMemberAccessor customAccessor(IType type) { + final IMemberAccessor signatureAlgorithmAccessor = type + .getAccessor(SIGNATURE_ALGORITHM.getKey()); + final IMemberAccessor keyTypeAccessor = type.getAccessor(KEY_TYPE.getKey()); + final IMemberAccessor keyLengthAccessor = type.getAccessor(KEY_LENGTH.getKey()); + final IMemberAccessor expiryDateAccessor = type.getAccessor(VALID_UNTIL.getKey()); + if ((signatureAlgorithmAccessor == null) || (keyTypeAccessor == null) || (keyLengthAccessor == null)) { + return null; + } + return new IMemberAccessor() { + @Override + public String getMember(U i) { + String signatureAlgorithm = signatureAlgorithmAccessor.getMember(i); + String keyType = keyTypeAccessor.getMember(i); + IQuantity keyLength = keyLengthAccessor.getMember(i); + IQuantity expiryDate = null; + if (expiryDateAccessor != null) { + expiryDate = expiryDateAccessor.getMember(i); + } + return signatureAlgorithm != null && keyType != null && keyLength != null + ? CryptoUtil.getCryptoIcon(signatureAlgorithm, keyType, keyLength.longValue(), expiryDate) + : null; + } + }; + } + }); + + public static final IAttribute CRYPTO_RULE_RESULT = Attribute + .canonicalize(new Attribute("cryptoRuleResult", //$NON-NLS-1$ + Messages.getString(Messages.ATTR_CRYPTO_ICON), Messages.getString(Messages.ATTR_CRYPTO_ICON_DESC), + PLAIN_TEXT) { + @Override + public IMemberAccessor customAccessor(IType type) { + final IMemberAccessor signatureAlgorithmAccessor = type + .getAccessor(SIGNATURE_ALGORITHM.getKey()); + final IMemberAccessor keyTypeAccessor = type.getAccessor(KEY_TYPE.getKey()); + final IMemberAccessor keyLengthAccessor = type.getAccessor(KEY_LENGTH.getKey()); + final IMemberAccessor expiryDateAccessor = type.getAccessor(VALID_UNTIL.getKey()); + final IMemberAccessor certificateIdAccessor = type.getAccessor(CERTIFICATE_ID.getKey()); + final IMemberAccessor certificateIdPre9Accessor = type + .getAccessor(CERTIFICATE_ID_QTY.getKey()); + if ((signatureAlgorithmAccessor == null) || (keyTypeAccessor == null) + || (keyLengthAccessor == null)) { + return null; + } + return new IMemberAccessor() { + @Override + public String getMember(U i) { + String signatureAlgorithm = signatureAlgorithmAccessor.getMember(i); + String keyType = keyTypeAccessor.getMember(i); + IQuantity keyLength = keyLengthAccessor.getMember(i); + IQuantity expiryDate = null; + if (expiryDateAccessor != null) { + expiryDate = expiryDateAccessor.getMember(i); + } + Number certificateId = 0; + IQuantity qtyCertificate = null; + if (certificateIdAccessor != null) { + certificateId = certificateIdAccessor.getMember(i); + } else if (certificateIdPre9Accessor != null) { + qtyCertificate = certificateIdPre9Accessor.getMember(i); + certificateId = qtyCertificate.numberValue(); + } + return signatureAlgorithm != null && keyType != null && keyLength != null + ? CryptoUtil.getCryptoRuleResult(signatureAlgorithm, keyType, keyLength.longValue(), + expiryDate, certificateId) + : null; + } + }; + } + }); + } diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkFilters.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkFilters.java index f70ac6224b..3ca95a1725 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkFilters.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkFilters.java @@ -81,6 +81,7 @@ public final class JdkFilters { JdkTypeIDs.ALLOC_OUTSIDE_TLAB); public static final IItemFilter OBJ_ALLOC = ItemFilters.type(JdkTypeIDs.OBJ_ALLOC_SAMPLE); public static final IItemFilter ZGC_ALLOCATION_STALL = ItemFilters.type(JdkTypeIDs.ZGC_ALLOCATION_STALL); + public static final IItemFilter SECURITY = ItemFilters.type(JdkTypeIDs.X509_CERTIFICATE); public static final IItemFilter REFERENCE_STATISTICS = ItemFilters.type(JdkTypeIDs.GC_REFERENCE_STATISTICS); public static final IItemFilter GARBAGE_COLLECTION = ItemFilters.type(JdkTypeIDs.GARBAGE_COLLECTION); public static final IItemFilter OLD_GARBAGE_COLLECTION = ItemFilters diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkTypeIDs.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkTypeIDs.java index fdaee6f3cb..5609c72008 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkTypeIDs.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/JdkTypeIDs.java @@ -111,6 +111,8 @@ public final class JdkTypeIDs { public static final String ZGC_ALLOCATION_STALL = PREFIX + "ZAllocationStall"; + public static final String X509_CERTIFICATE = PREFIX + "X509Certificate"; + public static final String THROWABLES_STATISTICS = PREFIX + "ExceptionStatistics"; public static final String ERRORS_THROWN = PREFIX + "JavaErrorThrow"; /* diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/messages/internal/Messages.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/messages/internal/Messages.java index 404fccc682..c6b1901218 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/messages/internal/Messages.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/jdk/messages/internal/Messages.java @@ -207,6 +207,7 @@ public class Messages { public static final String AGGR_VM_OPERATION_COUNT_DESC = "AGGR_VM_OPERATION_COUNT_DESC"; //$NON-NLS-1$ public static final String AGGR_VM_OPERATION_DURATION = "AGGR_VM_OPERATION_DURATION"; //$NON-NLS-1$ public static final String AGGR_VM_OPERATION_DURATION_DESC = "AGGR_VM_OPERATION_DURATION_DESC"; //$NON-NLS-1$ + public static final String AGGR_X509_CERTIFICATES_COUNT = "AGGR_X509_CERTIFICATES_COUNT"; //$NON-NLS-1$ public static final String ATTR_ADAPTORS = "ATTR_ADAPTORS"; //$NON-NLS-1$ public static final String ATTR_AGENT_DYNAMIC = "ATTR_AGENT_DYNAMIC"; //$NON-NLS-1$ public static final String ATTR_AGENT_DYNAMIC_DESC = "ATTR_AGENT_DYNAMIC_DESC"; //$NON-NLS-1$; @@ -566,6 +567,19 @@ public class Messages { public static final String ATTR_THREADS_DAEMON_COUNT_DESC = "ATTR_THREADS_DAEMON_COUNT_DESC"; public static final String ATTR_THREADS_PEAK_COUNT = "ATTR_THREADS_PEAK_COUNT"; public static final String ATTR_THREADS_PEAK_COUNT_DESC = "ATTR_THREADS_PEAK_COUNT_DESC"; + public static final String ATTR_SIGNATURE_ALGORITHM = "ATTR_SIGNATURE_ALGORITHM"; //$NON-NLS-1$ + public static final String ATTR_CERTIFICATE_ID = "ATTR_CERTIFICATE_ID"; //$NON-NLS-1$ + public static final String ATTR_CERTIFICATE_ISSUER = "ATTR_CERTIFICATE_ISSUER"; //$NON-NLS-1$ + public static final String ATTR_KEY_LENGTH = "ATTR_KEY_LENGTH"; //$NON-NLS-1$ + public static final String ATTR_KEY_TYPE = "ATTR_KEY_TYPE"; //$NON-NLS-1$ + public static final String ATTR_SERIAL_NUMBER = "ATTR_SERIAL_NUMBER"; //$NON-NLS-1$ + public static final String ATTR_SUBJECT = "ATTR_SUBJECT"; //$NON-NLS-1$ + public static final String ATTR_VALID_FROM = "ATTR_VALID_FROM"; //$NON-NLS-1$ + public static final String ATTR_VALID_UNTIL = "ATTR_VALID_UNTIL"; //$NON-NLS-1$ + public static final String ATTR_CRYPTO_REMARK = "ATTR_CRYPTO_REMARK"; //$NON-NLS-1$ + public static final String ATTR_CRYPTO_REMARK_DESC = "ATTR_CRYPTO_REMARK_DESC"; //$NON-NLS-1$ + public static final String ATTR_CRYPTO_ICON = "ATTR_CRYPTO_ICON"; //$NON-NLS-1$ + public static final String ATTR_CRYPTO_ICON_DESC = "ATTR_CRYPTO_ICON_DESC"; //$NON-NLS-1$ private Messages() { } diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/parser/synthetic/OracleJdkTypeIDsPre11.java b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/parser/synthetic/OracleJdkTypeIDsPre11.java index 90cfe10258..56e72007ac 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/parser/synthetic/OracleJdkTypeIDsPre11.java +++ b/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/parser/synthetic/OracleJdkTypeIDsPre11.java @@ -118,6 +118,8 @@ public final class OracleJdkTypeIDsPre11 { final static String MONITOR_ENTER = JVM_EVENT_ID_ROOT + "java/monitor_enter"; final static String MONITOR_WAIT = JVM_EVENT_ID_ROOT + "java/monitor_wait"; + private final static String X509_CERTIFICATE = JDK_EVENT_ID_ROOT + "java/x509_certificate"; + private final static String METASPACE_OOM = JVM_EVENT_ID_ROOT + "vm/gc/metaspace/out_of_memory"; private final static String CODE_CACHE_FULL = JVM_EVENT_ID_ROOT + "vm/code_cache/full"; @@ -416,6 +418,8 @@ public static String translate(String typeId) { return JdkTypeIDs.RECORDINGS; case GC_G1MMU: return JdkTypeIDs.GC_G1MMU; + case X509_CERTIFICATE: + return JdkTypeIDs.X509_CERTIFICATE; default: return typeId; } diff --git a/core/org.openjdk.jmc.flightrecorder/src/main/resources/org/openjdk/jmc/flightrecorder/jdk/messages/internal/messages.properties b/core/org.openjdk.jmc.flightrecorder/src/main/resources/org/openjdk/jmc/flightrecorder/jdk/messages/internal/messages.properties index 376b7dfc9a..29748187f3 100644 --- a/core/org.openjdk.jmc.flightrecorder/src/main/resources/org/openjdk/jmc/flightrecorder/jdk/messages/internal/messages.properties +++ b/core/org.openjdk.jmc.flightrecorder/src/main/resources/org/openjdk/jmc/flightrecorder/jdk/messages/internal/messages.properties @@ -375,6 +375,19 @@ ATTR_THREADS_DAEMON_COUNT=Daemon Threads ATTR_THREADS_DAEMON_COUNT_DESC=The number of live daemon threads ATTR_THREADS_PEAK_COUNT=Peak Threads ATTR_THREADS_PEAK_COUNT_DESC=Peak live thread count since JVM start (or since the peak count was reset) +ATTR_SIGNATURE_ALGORITHM=Signature Algorithm +ATTR_CERTIFICATE_ID=Certificate Id +ATTR_CERTIFICATE_ISSUER=Certificate Issuer +ATTR_KEY_LENGTH=Key Length +ATTR_KEY_TYPE=Key Type +ATTR_SERIAL_NUMBER=Serial Number +ATTR_SUBJECT=Subject +ATTR_VALID_FROM=Valid From +ATTR_VALID_UNTIL=Valid Until +ATTR_CRYPTO_REMARK=Crypto Remark +ATTR_CRYPTO_REMARK_DESC=Crypto Remarks based on the signature algorithm, key type and key length +ATTR_CRYPTO_ICON=Status +ATTR_CRYPTO_ICON_DESC=Crypto Icon will be used to show the action / attention needed in specific domain AGGR_MAX_USED_MEMORY=Maximum used memory AGGR_MIN_TOTAL_MEMORY=Available physical memory AGGR_ADDRESSES_COUNT=Addresses @@ -538,3 +551,4 @@ AGGR_VM_OPERATION_DURATION=VM Operation Duration AGGR_VM_OPERATION_DURATION_DESC=The sum of the durations for the selected VM operation events. AGGR_COMPILATIONS_COUNT=Compilations AGGR_COMPILATIONS_COUNT_DESC=The number of compilations +AGGR_X509_CERTIFICATES_COUNT=X509 Certificates \ No newline at end of file diff --git a/core/tests/org.openjdk.jmc.flightrecorder.rules.jdk.test/src/main/resources/baseline/JfrRuleBaseline.xml b/core/tests/org.openjdk.jmc.flightrecorder.rules.jdk.test/src/main/resources/baseline/JfrRuleBaseline.xml index 09d4ff0a82..d8dcec3976 100644 --- a/core/tests/org.openjdk.jmc.flightrecorder.rules.jdk.test/src/main/resources/baseline/JfrRuleBaseline.xml +++ b/core/tests/org.openjdk.jmc.flightrecorder.rules.jdk.test/src/main/resources/baseline/JfrRuleBaseline.xml @@ -331,6 +331,10 @@ This recording is only 9.903 s long, consider creating a recording longer than ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -637,6 +641,10 @@ This recording is only 9.903 s long, consider creating a recording longer than ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -915,6 +923,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -1191,6 +1203,10 @@ Events of the following types have truncated stack traces: <ul><li>O ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -1487,6 +1503,10 @@ Events of the following types have truncated stack traces: <ul><li>O ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -1762,6 +1782,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -2035,6 +2059,10 @@ Events of the following types have truncated stack traces: <ul><li>O ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -2362,6 +2390,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -2662,6 +2694,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -2951,6 +2987,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -3240,6 +3280,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -3531,6 +3575,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -3800,6 +3848,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -4068,6 +4120,10 @@ Events of the following types have truncated stack traces: <ul><li>A ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -4392,6 +4448,10 @@ Some of these inspections were caused by the 'Object Count' JFR event. It trigge ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -4666,6 +4726,10 @@ Some of these inspections were caused by the 'Object Count' JFR event. It trigge ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -4940,6 +5004,10 @@ Some of these inspections were caused by the 'Object Count' JFR event. It trigge ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -5263,6 +5331,10 @@ Enabling the following event types would improve the accuracy of this rule: jdk. ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -5585,6 +5657,10 @@ Enabling the following event types would improve the accuracy of this rule: jdk. ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -5863,6 +5939,10 @@ Enabling the following event types would improve the accuracy of this rule: jdk. ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -6189,6 +6269,10 @@ To improve rule accuracy and/or get more details for further investigation, it i ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -6572,6 +6656,10 @@ String deduplication is only supported when using the G1 (JDK 8u20+) or Shenando ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable @@ -6852,6 +6940,10 @@ Events of the following types have truncated stack traces: <ul><li>D ZGCAllocationStallRule Not Applicable + + + CryptoSecurityRule + Not Applicable