blob: e02936aee69b455392a45512c064d5f06c7ff589 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2022 Eclipse contributors and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import org.bouncycastle.bcpg.*;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.internal.p2.ui.ProvUIMessages;
import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
/**
* Presents information about a key in a format similar to what key servers
* display.
*
* @since 1.2.4
*/
public class PGPPublicKeyViewDialog extends TitleAreaDialog {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //$NON-NLS-1$
final private PGPPublicKey originalKey;
final private PGPPublicKeyService keyService;
private StyledText styledText;
public PGPPublicKeyViewDialog(Shell parentShell, PGPPublicKey key, PGPPublicKeyService keyService) {
super(parentShell);
this.originalKey = key;
this.keyService = keyService;
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); //$NON-NLS-1$
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText(ProvUIMessages.PGPPublicKeyViewDialog_Title);
if (keyService != null) {
computeVerifiedCertifications(newShell);
}
}
@Override
protected void setShellStyle(int newShellStyle) {
super.setShellStyle(newShellStyle | SWT.RESIZE | SWT.DIALOG_TRIM);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
composite.setLayoutData(data);
styledText = new StyledText(composite, SWT.READ_ONLY | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
styledText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// Create a sightly smaller text (mono-space) font.
FontData[] fontData = JFaceResources.getTextFont().getFontData();
for (FontData fontDataElement : fontData) {
fontDataElement.setHeight(fontDataElement.getHeight() - 1);
}
Font font = new Font(styledText.getDisplay(), fontData);
styledText.setFont(font);
styledText.addDisposeListener(e -> font.dispose());
GC gc = new GC(styledText);
gc.setFont(font);
data.widthHint = convertWidthInCharsToPixels(gc.getFontMetrics(), 110);
gc.dispose();
update(originalKey, Set.of());
return composite;
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CLOSE_LABEL, true).setFocus();
}
@SuppressWarnings("nls")
protected void update(PGPPublicKey key, Set<PGPPublicKey> verifiedCertifications) {
StyledString content = new StyledString();
String fingerprint = PGPPublicKeyService.toHex(key.getFingerprint()).toUpperCase(Locale.ROOT);
PublicKeyPacket publicKeyPacket = key.getPublicKeyPacket();
publicKeyPacket.getAlgorithm();
content.append(" ");
content.append(publicKeyPacket instanceof PublicSubkeyPacket ? "sub" : "pub", StyledString.QUALIFIER_STYLER);
content.append(" ");
int algorithm = publicKeyPacket.getAlgorithm();
switch (algorithm) {
case PublicKeyAlgorithmTags.RSA_GENERAL:
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_SIGN: {
content.append("rsa");
break;
}
case PublicKeyAlgorithmTags.DSA: {
content.append("dsa");
break;
}
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: {
content.append("elgamal");
break;
}
default: {
content.append("[");
content.append(Integer.toString(algorithm));
content.append("]");
break;
}
}
int bitStrength = key.getBitStrength();
content.append(Integer.toString(bitStrength));
content.append("/");
content.append(fingerprint);
content.append(" ");
content.append(DATE_FORMAT.format(key.getCreationTime()));
content.append(" ");
content.append("\n");
List<String> users = new ArrayList<>();
key.getUserIDs().forEachRemaining(users::add);
if (!users.isEmpty()) {
for (String user : users) {
content.append(" ");
content.append("uid", StyledString.QUALIFIER_STYLER);
content.append(" ");
content.append(user, StyledString.COUNTER_STYLER);
content.append("\n");
}
}
Long subKeyOf = null;
for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext();) {
PGPSignature signature = signatures.next();
long keyID = signature.getKeyID();
if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
subKeyOf = keyID;
}
content.append(" ");
content.append("sig", StyledString.QUALIFIER_STYLER);
content.append(" ");
content.append(PGPPublicKeyService.toHex(keyID));
content.append(" ");
Date creationTime = signature.getCreationTime();
String formattedCreationTime = DATE_FORMAT.format(creationTime);
content.append(formattedCreationTime);
long signatureExpirationTime = signature.getHashedSubPackets().getSignatureExpirationTime();
content.append(" ");
content.append(signatureExpirationTime == 0 ? formattedCreationTime.replaceAll(".", "_")
: DATE_FORMAT.format(new Date(creationTime.getTime() + 1000 * signatureExpirationTime)));
content.append(" ");
Optional<PGPPublicKey> resolvedKey = verifiedCertifications.stream().filter(k -> k.getKeyID() == keyID)
.findFirst();
long keyExpirationTime = signature.getHashedSubPackets().getKeyExpirationTime();
content.append(keyExpirationTime == 0 || resolvedKey == null || !resolvedKey.isPresent()
? formattedCreationTime.replaceAll(".", "_")
: DATE_FORMAT.format(
new Date(resolvedKey.get().getCreationTime().getTime() + 1000 * keyExpirationTime)));
if (resolvedKey != null && resolvedKey.isPresent()) {
content.append(" ");
content.append(getLabel(resolvedKey.get()), StyledString.COUNTER_STYLER);
}
content.append("\n");
}
styledText.setText(content.getString());
styledText.setStyleRanges(content.getStyleRanges());
List<String> title = new ArrayList<>();
if (subKeyOf != null) {
long keyID = subKeyOf;
verifiedCertifications.stream().filter(k -> k.getKeyID() == keyID).findFirst()
.ifPresentOrElse(k -> title.add(getLabel(k)), () -> title.add(PGPPublicKeyService.toHex(keyID)));
}
title.add((subKeyOf == null ? "" : "sub ") + (users.isEmpty() ? fingerprint : users.get(0)));
setTitle(String.join("\n", title));
}
private String getLabel(PGPPublicKey key) {
Iterator<String> userIDs = key.getUserIDs();
if (userIDs.hasNext()) {
return userIDs.next();
}
return PGPPublicKeyService.toHex(key.getFingerprint()).toUpperCase(Locale.ROOT);
}
private void computeVerifiedCertifications(Shell shell) {
Display display = shell.getDisplay();
new Job(PGPPublicKeyViewDialog.class.getName()) {
{
setSystem(true);
setPriority(Job.SHORT);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized (keyService) {
PGPPublicKey enhancedKey = keyService.addKey(originalKey);
Set<PGPPublicKey> verifiedCertifications = keyService.getVerifiedCertifications(originalKey);
display.asyncExec(() -> {
if (!shell.isDisposed()) {
update(enhancedKey, verifiedCertifications);
}
});
}
return Status.OK_STATUS;
}
}.schedule();
}
}