/* | |
* Copyright 2012 gitblit.com. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.gitblit.authority; | |
import java.awt.BorderLayout; | |
import java.awt.Container; | |
import java.awt.Desktop; | |
import java.awt.Dimension; | |
import java.awt.EventQueue; | |
import java.awt.FlowLayout; | |
import java.awt.GridLayout; | |
import java.awt.Insets; | |
import java.awt.Point; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.KeyAdapter; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.WindowAdapter; | |
import java.awt.event.WindowEvent; | |
import java.io.BufferedInputStream; | |
import java.io.BufferedWriter; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileWriter; | |
import java.io.FilenameFilter; | |
import java.io.IOException; | |
import java.net.URI; | |
import java.security.PrivateKey; | |
import java.security.cert.CertificateFactory; | |
import java.security.cert.X509Certificate; | |
import java.text.MessageFormat; | |
import java.util.ArrayList; | |
import java.util.Calendar; | |
import java.util.Collections; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.mail.Message; | |
import javax.swing.ImageIcon; | |
import javax.swing.InputVerifier; | |
import javax.swing.JButton; | |
import javax.swing.JComponent; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JOptionPane; | |
import javax.swing.JPanel; | |
import javax.swing.JPasswordField; | |
import javax.swing.JScrollPane; | |
import javax.swing.JSplitPane; | |
import javax.swing.JTable; | |
import javax.swing.JTextArea; | |
import javax.swing.JTextField; | |
import javax.swing.JToolBar; | |
import javax.swing.RowFilter; | |
import javax.swing.SwingConstants; | |
import javax.swing.UIManager; | |
import javax.swing.event.ListSelectionEvent; | |
import javax.swing.event.ListSelectionListener; | |
import javax.swing.table.TableRowSorter; | |
import org.eclipse.jgit.errors.ConfigInvalidException; | |
import org.eclipse.jgit.lib.StoredConfig; | |
import org.eclipse.jgit.storage.file.FileBasedConfig; | |
import org.eclipse.jgit.util.FS; | |
import org.slf4j.LoggerFactory; | |
import com.gitblit.ConfigUserService; | |
import com.gitblit.Constants; | |
import com.gitblit.FileSettings; | |
import com.gitblit.IStoredSettings; | |
import com.gitblit.IUserService; | |
import com.gitblit.Keys; | |
import com.gitblit.client.HeaderPanel; | |
import com.gitblit.client.Translation; | |
import com.gitblit.models.Mailing; | |
import com.gitblit.models.UserModel; | |
import com.gitblit.service.MailService; | |
import com.gitblit.utils.ArrayUtils; | |
import com.gitblit.utils.FileUtils; | |
import com.gitblit.utils.StringUtils; | |
import com.gitblit.utils.TimeUtils; | |
import com.gitblit.utils.X509Utils; | |
import com.gitblit.utils.X509Utils.RevocationReason; | |
import com.gitblit.utils.X509Utils.X509Log; | |
import com.gitblit.utils.X509Utils.X509Metadata; | |
/** | |
* Simple GUI tool for administering Gitblit client certificates. | |
* | |
* @author James Moger | |
* | |
*/ | |
public class GitblitAuthority extends JFrame implements X509Log { | |
private static final long serialVersionUID = 1L; | |
private final UserCertificateTableModel tableModel; | |
private UserCertificatePanel userCertificatePanel; | |
private File folder; | |
private IStoredSettings gitblitSettings; | |
private IUserService userService; | |
private String caKeystorePassword; | |
private JTable table; | |
private int defaultDuration; | |
private TableRowSorter<UserCertificateTableModel> defaultSorter; | |
private MailService mail; | |
private JButton certificateDefaultsButton; | |
private JButton newSSLCertificate; | |
public static void main(String... args) { | |
// filter out the baseFolder parameter | |
String folder = "data"; | |
for (int i = 0; i< args.length; i++) { | |
String arg = args[i]; | |
if (arg.equals("--baseFolder")) { | |
if (i + 1 == args.length) { | |
System.out.println("Invalid --baseFolder parameter!"); | |
System.exit(-1); | |
} else if (!".".equals(args[i + 1])) { | |
folder = args[i+1]; | |
} | |
break; | |
} | |
} | |
final String baseFolder = folder; | |
EventQueue.invokeLater(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); | |
} catch (Exception e) { | |
} | |
GitblitAuthority authority = new GitblitAuthority(); | |
authority.initialize(baseFolder); | |
authority.setLocationRelativeTo(null); | |
authority.setVisible(true); | |
} | |
}); | |
} | |
public GitblitAuthority() { | |
super(); | |
tableModel = new UserCertificateTableModel(); | |
defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel); | |
} | |
public void initialize(String baseFolder) { | |
setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); | |
setTitle("Gitblit Certificate Authority v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")"); | |
setContentPane(getUI()); | |
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
addWindowListener(new WindowAdapter() { | |
@Override | |
public void windowClosing(WindowEvent event) { | |
saveSizeAndPosition(); | |
} | |
@Override | |
public void windowOpened(WindowEvent event) { | |
} | |
}); | |
File folder = new File(baseFolder).getAbsoluteFile(); | |
load(folder); | |
setSizeAndPosition(); | |
} | |
private void setSizeAndPosition() { | |
String sz = null; | |
String pos = null; | |
try { | |
StoredConfig config = getConfig(); | |
sz = config.getString("ui", null, "size"); | |
pos = config.getString("ui", null, "position"); | |
defaultDuration = config.getInt("new", "duration", 365); | |
} catch (Throwable t) { | |
t.printStackTrace(); | |
} | |
// try to restore saved window size | |
if (StringUtils.isEmpty(sz)) { | |
setSize(900, 600); | |
} else { | |
String[] chunks = sz.split("x"); | |
int width = Integer.parseInt(chunks[0]); | |
int height = Integer.parseInt(chunks[1]); | |
setSize(width, height); | |
} | |
// try to restore saved window position | |
if (StringUtils.isEmpty(pos)) { | |
setLocationRelativeTo(null); | |
} else { | |
String[] chunks = pos.split(","); | |
int x = Integer.parseInt(chunks[0]); | |
int y = Integer.parseInt(chunks[1]); | |
setLocation(x, y); | |
} | |
} | |
private void saveSizeAndPosition() { | |
try { | |
// save window size and position | |
StoredConfig config = getConfig(); | |
Dimension sz = GitblitAuthority.this.getSize(); | |
config.setString("ui", null, "size", | |
MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height)); | |
Point pos = GitblitAuthority.this.getLocationOnScreen(); | |
config.setString("ui", null, "position", | |
MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y)); | |
config.save(); | |
} catch (Throwable t) { | |
Utils.showException(GitblitAuthority.this, t); | |
} | |
} | |
private StoredConfig getConfig() throws IOException, ConfigInvalidException { | |
File configFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(configFile, FS.detect()); | |
config.load(); | |
return config; | |
} | |
private IUserService loadUsers(File folder) { | |
File file = new File(folder, "gitblit.properties"); | |
if (!file.exists()) { | |
return null; | |
} | |
gitblitSettings = new FileSettings(file.getAbsolutePath()); | |
mail = new MailService(gitblitSettings); | |
String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); | |
String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase(); | |
IUserService service = null; | |
if (!ext.equals("conf") && !ext.equals("properties") && ext.contains("userservice")) { | |
String realm = ext.substring(0, ext.indexOf("userservice")); | |
us = gitblitSettings.getString(MessageFormat.format("realm.{0}.backingUserService", realm), "${baseFolder}/users.conf"); | |
} | |
if (us.endsWith(".conf")) { | |
service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us)); | |
} else { | |
throw new RuntimeException("Unsupported user service: " + us); | |
} | |
service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us)); | |
return service; | |
} | |
private void load(File folder) { | |
this.folder = folder; | |
this.userService = loadUsers(folder); | |
System.out.println(Constants.baseFolder$ + " set to " + folder); | |
if (userService == null) { | |
JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder)); | |
} else { | |
// build empty certificate model for all users | |
Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>(); | |
for (String user : userService.getAllUsernames()) { | |
UserModel model = userService.getUserModel(user); | |
UserCertificateModel ucm = new UserCertificateModel(model); | |
map.put(user, ucm); | |
} | |
File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); | |
if (certificatesConfigFile.exists()) { | |
try { | |
config.load(); | |
// replace user certificate model with actual data | |
List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list; | |
for (UserCertificateModel ucm : list) { | |
ucm.user = userService.getUserModel(ucm.user.username); | |
map.put(ucm.user.username, ucm); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} catch (ConfigInvalidException e) { | |
e.printStackTrace(); | |
} | |
} | |
tableModel.list = new ArrayList<UserCertificateModel>(map.values()); | |
Collections.sort(tableModel.list); | |
tableModel.fireTableDataChanged(); | |
Utils.packColumns(table, Utils.MARGIN); | |
File caKeystore = new File(folder, X509Utils.CA_KEY_STORE); | |
if (!caKeystore.exists()) { | |
if (!X509Utils.unlimitedStrength) { | |
// prompt to confirm user understands JCE Standard Strength encryption | |
int res = JOptionPane.showConfirmDialog(GitblitAuthority.this, Translation.get("gb.jceWarning"), | |
Translation.get("gb.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); | |
if (res != JOptionPane.YES_OPTION) { | |
if (Desktop.isDesktopSupported()) { | |
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { | |
try { | |
Desktop.getDesktop().browse(URI.create("http://www.oracle.com/technetwork/java/javase/downloads/index.html")); | |
} catch (IOException e) { | |
} | |
} | |
} | |
System.exit(1); | |
} | |
} | |
// show certificate defaults dialog | |
certificateDefaultsButton.doClick(); | |
// create "localhost" ssl certificate | |
prepareX509Infrastructure(); | |
} | |
} | |
} | |
private boolean prepareX509Infrastructure() { | |
if (caKeystorePassword == null) { | |
JPasswordField pass = new JPasswordField(10); | |
pass.setText(caKeystorePassword); | |
pass.addAncestorListener(new RequestFocusListener()); | |
JPanel panel = new JPanel(new BorderLayout()); | |
panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH); | |
panel.add(pass, BorderLayout.CENTER); | |
int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION); | |
if (result == JOptionPane.OK_OPTION) { | |
caKeystorePassword = new String(pass.getPassword()); | |
} else { | |
return false; | |
} | |
} | |
X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword); | |
setMetadataDefaults(metadata); | |
metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR); | |
X509Utils.prepareX509Infrastructure(metadata, folder, this); | |
return true; | |
} | |
private List<X509Certificate> findCerts(File folder, String username) { | |
List<X509Certificate> list = new ArrayList<X509Certificate>(); | |
File userFolder = new File(folder, X509Utils.CERTS + File.separator + username); | |
if (!userFolder.exists()) { | |
return list; | |
} | |
File [] certs = userFolder.listFiles(new FilenameFilter() { | |
@Override | |
public boolean accept(File dir, String name) { | |
return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt"); | |
} | |
}); | |
try { | |
CertificateFactory factory = CertificateFactory.getInstance("X.509"); | |
for (File cert : certs) { | |
BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert)); | |
X509Certificate x509 = (X509Certificate) factory.generateCertificate(is); | |
is.close(); | |
list.add(x509); | |
} | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
return list; | |
} | |
private Container getUI() { | |
userCertificatePanel = new UserCertificatePanel(this) { | |
private static final long serialVersionUID = 1L; | |
@Override | |
public Insets getInsets() { | |
return Utils.INSETS; | |
} | |
@Override | |
public boolean isAllowEmail() { | |
return mail.isReady(); | |
} | |
@Override | |
public Date getDefaultExpiration() { | |
Calendar c = Calendar.getInstance(); | |
c.add(Calendar.DATE, defaultDuration); | |
c.set(Calendar.HOUR_OF_DAY, 0); | |
c.set(Calendar.MINUTE, 0); | |
c.set(Calendar.SECOND, 0); | |
c.set(Calendar.MILLISECOND, 0); | |
return c.getTime(); | |
} | |
@Override | |
public boolean saveUser(String username, UserCertificateModel ucm) { | |
return userService.updateUserModel(username, ucm.user); | |
} | |
@Override | |
public boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) { | |
if (!prepareX509Infrastructure()) { | |
return false; | |
} | |
Date notAfter = metadata.notAfter; | |
setMetadataDefaults(metadata); | |
metadata.notAfter = notAfter; | |
// set user's specified OID values | |
UserModel user = ucm.user; | |
if (!StringUtils.isEmpty(user.organizationalUnit)) { | |
metadata.oids.put("OU", user.organizationalUnit); | |
} | |
if (!StringUtils.isEmpty(user.organization)) { | |
metadata.oids.put("O", user.organization); | |
} | |
if (!StringUtils.isEmpty(user.locality)) { | |
metadata.oids.put("L", user.locality); | |
} | |
if (!StringUtils.isEmpty(user.stateProvince)) { | |
metadata.oids.put("ST", user.stateProvince); | |
} | |
if (!StringUtils.isEmpty(user.countryCode)) { | |
metadata.oids.put("C", user.countryCode); | |
} | |
File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); | |
File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this); | |
// save latest expiration date | |
if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) { | |
ucm.expires = metadata.notAfter; | |
} | |
updateAuthorityConfig(ucm); | |
// refresh user | |
ucm.certs = null; | |
int selectedIndex = table.getSelectedRow(); | |
tableModel.fireTableDataChanged(); | |
table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex); | |
if (sendEmail) { | |
sendEmail(user, metadata, zip); | |
} | |
return true; | |
} | |
@Override | |
public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) { | |
if (!prepareX509Infrastructure()) { | |
return false; | |
} | |
File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST); | |
File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); | |
if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) { | |
File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); | |
if (certificatesConfigFile.exists()) { | |
try { | |
config.load(); | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
} | |
// add serial to revoked list | |
ucm.revoke(cert.getSerialNumber(), reason); | |
ucm.update(config); | |
try { | |
config.save(); | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
// refresh user | |
ucm.certs = null; | |
int modelIndex = table.convertRowIndexToModel(table.getSelectedRow()); | |
tableModel.fireTableDataChanged(); | |
table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex); | |
return true; | |
} | |
return false; | |
} | |
}; | |
table = Utils.newTable(tableModel, Utils.DATE_FORMAT); | |
table.setRowSorter(defaultSorter); | |
table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer()); | |
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { | |
@Override | |
public void valueChanged(ListSelectionEvent e) { | |
if (e.getValueIsAdjusting()) { | |
return; | |
} | |
int row = table.getSelectedRow(); | |
if (row < 0) { | |
return; | |
} | |
int modelIndex = table.convertRowIndexToModel(row); | |
UserCertificateModel ucm = tableModel.get(modelIndex); | |
if (ucm.certs == null) { | |
ucm.certs = findCerts(folder, ucm.user.username); | |
} | |
userCertificatePanel.setUserCertificateModel(ucm); | |
} | |
}); | |
JPanel usersPanel = new JPanel(new BorderLayout()) { | |
private static final long serialVersionUID = 1L; | |
@Override | |
public Insets getInsets() { | |
return Utils.INSETS; | |
} | |
}; | |
usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH); | |
usersPanel.add(new JScrollPane(table), BorderLayout.CENTER); | |
usersPanel.setMinimumSize(new Dimension(400, 10)); | |
certificateDefaultsButton = new JButton(new ImageIcon(getClass().getResource("/settings_16x16.png"))); | |
certificateDefaultsButton.setFocusable(false); | |
certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults")); | |
certificateDefaultsButton.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
X509Metadata metadata = new X509Metadata("whocares", "whocares"); | |
File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); | |
NewCertificateConfig certificateConfig = null; | |
if (certificatesConfigFile.exists()) { | |
try { | |
config.load(); | |
} catch (Exception x) { | |
Utils.showException(GitblitAuthority.this, x); | |
} | |
certificateConfig = NewCertificateConfig.KEY.parse(config); | |
certificateConfig.update(metadata); | |
} | |
InputVerifier verifier = new InputVerifier() { | |
@Override | |
public boolean verify(JComponent comp) { | |
boolean returnValue; | |
JTextField textField = (JTextField) comp; | |
try { | |
Integer.parseInt(textField.getText()); | |
returnValue = true; | |
} catch (NumberFormatException e) { | |
returnValue = false; | |
} | |
return returnValue; | |
} | |
}; | |
JTextField siteNameTF = new JTextField(20); | |
siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit")); | |
JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"), | |
siteNameTF, Translation.get("gb.siteNameDescription")); | |
JTextField validityTF = new JTextField(4); | |
validityTF.setInputVerifier(verifier); | |
validityTF.setVerifyInputWhenFocusTarget(true); | |
validityTF.setText("" + certificateConfig.duration); | |
JPanel validityPanel = Utils.newFieldPanel(Translation.get("gb.validity"), | |
validityTF, Translation.get("gb.duration.days").replace("{0}", "").trim()); | |
JPanel p1 = new JPanel(new GridLayout(0, 1, 5, 2)); | |
p1.add(siteNamePanel); | |
p1.add(validityPanel); | |
DefaultOidsPanel oids = new DefaultOidsPanel(metadata); | |
JPanel panel = new JPanel(new BorderLayout()); | |
panel.add(p1, BorderLayout.NORTH); | |
panel.add(oids, BorderLayout.CENTER); | |
int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, | |
panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION, | |
JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png"))); | |
if (result == JOptionPane.OK_OPTION) { | |
try { | |
oids.update(metadata); | |
certificateConfig.duration = Integer.parseInt(validityTF.getText()); | |
certificateConfig.store(config, metadata); | |
config.save(); | |
Map<String, String> updates = new HashMap<String, String>(); | |
updates.put(Keys.web.siteName, siteNameTF.getText()); | |
gitblitSettings.saveSettings(updates); | |
} catch (Exception e1) { | |
Utils.showException(GitblitAuthority.this, e1); | |
} | |
} | |
} | |
}); | |
newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png"))); | |
newSSLCertificate.setFocusable(false); | |
newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate")); | |
newSSLCertificate.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR); | |
NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration); | |
dialog.setModal(true); | |
dialog.setVisible(true); | |
if (dialog.isCanceled()) { | |
return; | |
} | |
final Date expires = dialog.getExpiration(); | |
final String hostname = dialog.getHostname(); | |
final boolean serveCertificate = dialog.isServeCertificate(); | |
AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) { | |
@Override | |
protected Boolean doRequest() throws IOException { | |
if (!prepareX509Infrastructure()) { | |
return false; | |
} | |
// read CA private key and certificate | |
File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); | |
PrivateKey caPrivateKey = X509Utils.getPrivateKey(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword); | |
X509Certificate caCert = X509Utils.getCertificate(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword); | |
// generate new SSL certificate | |
X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword); | |
setMetadataDefaults(metadata); | |
metadata.notAfter = expires; | |
File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE); | |
X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this); | |
boolean hasCert = cert != null; | |
if (hasCert && serveCertificate) { | |
// update Gitblit https connector alias | |
Map<String, String> updates = new HashMap<String, String>(); | |
updates.put(Keys.server.certificateAlias, metadata.commonName); | |
gitblitSettings.saveSettings(updates); | |
} | |
return hasCert; | |
} | |
@Override | |
protected void onSuccess() { | |
if (serveCertificate) { | |
JOptionPane.showMessageDialog(GitblitAuthority.this, | |
MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname), | |
Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); | |
} else { | |
JOptionPane.showMessageDialog(GitblitAuthority.this, | |
MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname), | |
Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); | |
} | |
} | |
}; | |
worker.execute(); | |
} | |
}); | |
JButton emailBundle = new JButton(new ImageIcon(getClass().getResource("/mail_16x16.png"))); | |
emailBundle.setFocusable(false); | |
emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle")); | |
emailBundle.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
int row = table.getSelectedRow(); | |
if (row < 0) { | |
return; | |
} | |
int modelIndex = table.convertRowIndexToModel(row); | |
final UserCertificateModel ucm = tableModel.get(modelIndex); | |
if (ArrayUtils.isEmpty(ucm.certs)) { | |
JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName())); | |
} | |
final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip"); | |
if (!zip.exists()) { | |
return; | |
} | |
AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) { | |
@Override | |
protected Boolean doRequest() throws IOException { | |
X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares"); | |
metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME); | |
if (StringUtils.isEmpty(metadata.serverHostname)) { | |
metadata.serverHostname = Constants.NAME; | |
} | |
metadata.userDisplayname = ucm.user.getDisplayName(); | |
return sendEmail(ucm.user, metadata, zip); | |
} | |
@Override | |
protected void onSuccess() { | |
JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"), | |
ucm.user.getDisplayName())); | |
} | |
}; | |
worker.execute(); | |
} | |
}); | |
JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png"))); | |
logButton.setFocusable(false); | |
logButton.setToolTipText(Translation.get("gb.log")); | |
logButton.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt"); | |
if (log.exists()) { | |
String content = FileUtils.readContent(log, "\n"); | |
JTextArea textarea = new JTextArea(content); | |
JScrollPane scrollPane = new JScrollPane(textarea); | |
scrollPane.setPreferredSize(new Dimension(700, 400)); | |
JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE); | |
} | |
} | |
}); | |
final JTextField filterTextfield = new JTextField(15); | |
filterTextfield.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
filterUsers(filterTextfield.getText()); | |
} | |
}); | |
filterTextfield.addKeyListener(new KeyAdapter() { | |
@Override | |
public void keyReleased(KeyEvent e) { | |
filterUsers(filterTextfield.getText()); | |
} | |
}); | |
JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL); | |
buttonControls.setFloatable(false); | |
buttonControls.add(certificateDefaultsButton); | |
buttonControls.add(newSSLCertificate); | |
buttonControls.add(emailBundle); | |
buttonControls.add(logButton); | |
JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN)); | |
userControls.add(new JLabel(Translation.get("gb.filter"))); | |
userControls.add(filterTextfield); | |
JPanel topPanel = new JPanel(new BorderLayout(0, 0)); | |
topPanel.add(buttonControls, BorderLayout.WEST); | |
topPanel.add(userControls, BorderLayout.EAST); | |
JPanel leftPanel = new JPanel(new BorderLayout()); | |
leftPanel.add(topPanel, BorderLayout.NORTH); | |
leftPanel.add(usersPanel, BorderLayout.CENTER); | |
userCertificatePanel.setMinimumSize(new Dimension(375, 10)); | |
JLabel statusLabel = new JLabel(); | |
statusLabel.setHorizontalAlignment(SwingConstants.RIGHT); | |
if (X509Utils.unlimitedStrength) { | |
statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy"); | |
} else { | |
statusLabel.setText("JCE Standard Encryption Policy"); | |
} | |
JPanel root = new JPanel(new BorderLayout()) { | |
private static final long serialVersionUID = 1L; | |
@Override | |
public Insets getInsets() { | |
return Utils.INSETS; | |
} | |
}; | |
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel); | |
splitPane.setDividerLocation(1d); | |
root.add(splitPane, BorderLayout.CENTER); | |
root.add(statusLabel, BorderLayout.SOUTH); | |
return root; | |
} | |
private void filterUsers(final String fragment) { | |
table.clearSelection(); | |
userCertificatePanel.setUserCertificateModel(null); | |
if (StringUtils.isEmpty(fragment)) { | |
table.setRowSorter(defaultSorter); | |
return; | |
} | |
RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() { | |
@Override | |
public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) { | |
for (int i = entry.getValueCount() - 1; i >= 0; i--) { | |
if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
}; | |
TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>( | |
tableModel); | |
sorter.setRowFilter(containsFilter); | |
table.setRowSorter(sorter); | |
} | |
@Override | |
public void log(String message) { | |
BufferedWriter writer = null; | |
try { | |
writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true)); | |
writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message)); | |
writer.newLine(); | |
writer.flush(); | |
} catch (Exception e) { | |
LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e); | |
} finally { | |
if (writer != null) { | |
try { | |
writer.close(); | |
} catch (IOException e) { | |
} | |
} | |
} | |
} | |
private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) { | |
// send email | |
try { | |
if (mail.isReady()) { | |
Mailing mailing = Mailing.newPlain(); | |
mailing.subject = "Your Gitblit client certificate for " + metadata.serverHostname; | |
mailing.setRecipients(user.emailAddress); | |
String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata); | |
if (StringUtils.isEmpty(body)) { | |
body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName()); | |
} | |
mailing.content = body; | |
mailing.addAttachment(zip); | |
Message message = mail.createMessage(mailing); | |
mail.sendNow(message); | |
return true; | |
} else { | |
JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE); | |
} | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
return false; | |
} | |
private void setMetadataDefaults(X509Metadata metadata) { | |
metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME); | |
if (StringUtils.isEmpty(metadata.serverHostname)) { | |
metadata.serverHostname = Constants.NAME; | |
} | |
// set default values from config file | |
File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); | |
if (certificatesConfigFile.exists()) { | |
try { | |
config.load(); | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config); | |
certificateConfig.update(metadata); | |
} | |
} | |
private void updateAuthorityConfig(UserCertificateModel ucm) { | |
File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); | |
FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); | |
if (certificatesConfigFile.exists()) { | |
try { | |
config.load(); | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
} | |
ucm.update(config); | |
try { | |
config.save(); | |
} catch (Exception e) { | |
Utils.showException(GitblitAuthority.this, e); | |
} | |
} | |
} |