James Moger
2012-11-27 44f6238fd5fe7675e7de43f4a42d1f7dabcfee4e
commit | author | age
3983a6 1 /*
JM 2  * Copyright 2012 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.authority;
17
18 import java.awt.BorderLayout;
19 import java.awt.Container;
20 import java.awt.Dimension;
21 import java.awt.EventQueue;
22 import java.awt.FlowLayout;
23 import java.awt.Insets;
24 import java.awt.Point;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.KeyAdapter;
28 import java.awt.event.KeyEvent;
29 import java.awt.event.WindowAdapter;
30 import java.awt.event.WindowEvent;
31 import java.io.BufferedInputStream;
c8b26c 32 import java.io.BufferedWriter;
3983a6 33 import java.io.File;
JM 34 import java.io.FileInputStream;
c8b26c 35 import java.io.FileWriter;
3983a6 36 import java.io.FilenameFilter;
JM 37 import java.io.IOException;
c8b26c 38 import java.security.PrivateKey;
3983a6 39 import java.security.cert.CertificateFactory;
JM 40 import java.security.cert.X509Certificate;
41 import java.text.MessageFormat;
42 import java.util.ArrayList;
43 import java.util.Calendar;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49
50 import javax.activation.DataHandler;
51 import javax.activation.FileDataSource;
52 import javax.mail.Message;
53 import javax.mail.Multipart;
54 import javax.mail.internet.MimeBodyPart;
55 import javax.mail.internet.MimeMultipart;
56 import javax.swing.ImageIcon;
c8b26c 57 import javax.swing.InputVerifier;
JM 58 import javax.swing.JButton;
59 import javax.swing.JComponent;
3983a6 60 import javax.swing.JFrame;
JM 61 import javax.swing.JLabel;
62 import javax.swing.JOptionPane;
63 import javax.swing.JPanel;
db9718 64 import javax.swing.JPasswordField;
3983a6 65 import javax.swing.JScrollPane;
JM 66 import javax.swing.JSplitPane;
67 import javax.swing.JTable;
68 import javax.swing.JTextField;
69 import javax.swing.RowFilter;
c8b26c 70 import javax.swing.SwingConstants;
3983a6 71 import javax.swing.UIManager;
JM 72 import javax.swing.event.ListSelectionEvent;
73 import javax.swing.event.ListSelectionListener;
74 import javax.swing.table.TableRowSorter;
75
76 import org.eclipse.jgit.errors.ConfigInvalidException;
77 import org.eclipse.jgit.lib.StoredConfig;
78 import org.eclipse.jgit.storage.file.FileBasedConfig;
79 import org.eclipse.jgit.util.FS;
c8b26c 80 import org.slf4j.LoggerFactory;
3983a6 81
JM 82 import com.gitblit.ConfigUserService;
83 import com.gitblit.Constants;
84 import com.gitblit.FileSettings;
85 import com.gitblit.IStoredSettings;
86 import com.gitblit.IUserService;
87 import com.gitblit.Keys;
88 import com.gitblit.MailExecutor;
89 import com.gitblit.client.HeaderPanel;
90 import com.gitblit.client.Translation;
91 import com.gitblit.models.UserModel;
e571c4 92 import com.gitblit.utils.ArrayUtils;
3983a6 93 import com.gitblit.utils.StringUtils;
c8b26c 94 import com.gitblit.utils.TimeUtils;
3983a6 95 import com.gitblit.utils.X509Utils;
JM 96 import com.gitblit.utils.X509Utils.RevocationReason;
c8b26c 97 import com.gitblit.utils.X509Utils.X509Log;
3983a6 98 import com.gitblit.utils.X509Utils.X509Metadata;
JM 99
100 /**
101  * Simple GUI tool for administering Gitblit client certificates.
102  * 
103  * @author James Moger
104  *
105  */
c8b26c 106 public class GitblitAuthority extends JFrame implements X509Log {
3983a6 107
JM 108     private static final long serialVersionUID = 1L;
109     
110     private final UserCertificateTableModel tableModel;
111
112     private UserCertificatePanel userCertificatePanel;
113     
114     private File folder;
115     
116     private IStoredSettings gitblitSettings;
117     
118     private IUserService userService;
119     
db9718 120     private String caKeystorePassword;
3983a6 121
JM 122     private JTable table;
123     
124     private int defaultDuration;
125     
126     private TableRowSorter<UserCertificateTableModel> defaultSorter;
c8b26c 127     
JM 128     private MailExecutor mail;
129
130     private JButton certificateDefaultsButton;
3983a6 131
44f623 132     private JButton newSSLCertificate;
JM 133
3983a6 134     public static void main(String... args) {
JM 135         EventQueue.invokeLater(new Runnable() {
136             public void run() {
137                 try {
138                     UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
139                 } catch (Exception e) {
140                 }
141                 GitblitAuthority authority = new GitblitAuthority();
142                 authority.initialize();
143                 authority.setLocationRelativeTo(null);
144                 authority.setVisible(true);
145             }
146         });
147     }
148
149     public GitblitAuthority() {
150         super();
151         tableModel = new UserCertificateTableModel();
152         defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);
153     }
154     
155     public void initialize() {
156         setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
4ad1eb 157         setTitle("Gitblit Certificate Authority v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");
3983a6 158         setContentPane(getUI());
JM 159         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
160         addWindowListener(new WindowAdapter() {
161             @Override
162             public void windowClosing(WindowEvent event) {
163                 saveSizeAndPosition();
164             }
165
166             @Override
167             public void windowOpened(WindowEvent event) {
168             }
169         });        
170
171         setSizeAndPosition();
172         
173         File folder = new File(System.getProperty("user.dir"));
174         load(folder);
175     }
176     
177     private void setSizeAndPosition() {
178         String sz = null;
179         String pos = null;
180         try {
181             StoredConfig config = getConfig();
182             sz = config.getString("ui", null, "size");
183             pos = config.getString("ui", null, "position");
184             defaultDuration = config.getInt("new",  "duration", 365);
185         } catch (Throwable t) {
186             t.printStackTrace();
187         }
188
189         // try to restore saved window size
190         if (StringUtils.isEmpty(sz)) {
c8b26c 191             setSize(900, 600);
3983a6 192         } else {
JM 193             String[] chunks = sz.split("x");
194             int width = Integer.parseInt(chunks[0]);
195             int height = Integer.parseInt(chunks[1]);
196             setSize(width, height);
197         }
198
199         // try to restore saved window position
200         if (StringUtils.isEmpty(pos)) {
201             setLocationRelativeTo(null);
202         } else {
203             String[] chunks = pos.split(",");
204             int x = Integer.parseInt(chunks[0]);
205             int y = Integer.parseInt(chunks[1]);
206             setLocation(x, y);
207         }
208     }
209
210     private void saveSizeAndPosition() {
211         try {
212             // save window size and position
213             StoredConfig config = getConfig();
214             Dimension sz = GitblitAuthority.this.getSize();
215             config.setString("ui", null, "size",
216                     MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
217             Point pos = GitblitAuthority.this.getLocationOnScreen();
218             config.setString("ui", null, "position",
219                     MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
220             config.save();
221         } catch (Throwable t) {
222             Utils.showException(GitblitAuthority.this, t);
223         }
224     }
225     
226     private StoredConfig getConfig() throws IOException, ConfigInvalidException {
227         File configFile  = new File(System.getProperty("user.dir"), X509Utils.CA_CONFIG);
228         FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
229         config.load();
230         return config;
231     }
232     
233     private IUserService loadUsers(File folder) {
234         File file = new File(folder, "gitblit.properties");
235         if (!file.exists()) {
236             return null;
237         }
238         gitblitSettings = new FileSettings(file.getAbsolutePath());
c8b26c 239         mail = new MailExecutor(gitblitSettings);
3983a6 240         String us = gitblitSettings.getString(Keys.realm.userService, "users.conf");
JM 241         String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
242         IUserService service = null;
243         if (!ext.equals("conf") && !ext.equals("properties")) {
244             if (us.equals("com.gitblit.LdapUserService")) {
245                 us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "users.conf");        
246             } else if (us.equals("com.gitblit.LdapUserService")) {
247                 us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "users.conf");
248             }
249         }
250
251         if (us.endsWith(".conf")) {
252             service = new ConfigUserService(new File(us));
253         } else {
254             throw new RuntimeException("Unsupported user service: " + us);
255         }
256         
257         service = new ConfigUserService(new File(us));
258         return service;
259     }
260     
261     private void load(File folder) {
262         this.folder = folder;
263         this.userService = loadUsers(folder);
c8b26c 264         if (userService == null) {
JM 265             JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder));
266         } else {
3983a6 267             // build empty certificate model for all users
JM 268             Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();
269             for (String user : userService.getAllUsernames()) {
270                 UserModel model = userService.getUserModel(user);
271                 UserCertificateModel ucm = new UserCertificateModel(model);                
272                 map.put(user, ucm);
273             }
274             File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
275             FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
276             if (certificatesConfigFile.exists()) {
277                 try {
278                     config.load();
279                     // replace user certificate model with actual data
280                     List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list;                    
281                     for (UserCertificateModel ucm : list) {                        
282                         ucm.user = userService.getUserModel(ucm.user.username);
283                         map.put(ucm.user.username, ucm);
284                     }
285                 } catch (IOException e) {
286                     e.printStackTrace();
287                 } catch (ConfigInvalidException e) {
288                     e.printStackTrace();
289                 }
290             }
291             
292             tableModel.list = new ArrayList<UserCertificateModel>(map.values());
293             Collections.sort(tableModel.list);
294             tableModel.fireTableDataChanged();
ca1205 295             Utils.packColumns(table, Utils.MARGIN);
c8b26c 296             
JM 297             File caKeystore = new File(folder, X509Utils.CA_KEY_STORE);
298             if (!caKeystore.exists()) {
299                 // show certificate defaults dialog 
300                 certificateDefaultsButton.doClick();
44f623 301                 
JM 302                 // create "localhost" ssl certificate
303                 prepareX509Infrastructure();
c8b26c 304             }
3983a6 305         }
c8b26c 306     }
JM 307     
db9718 308     private boolean prepareX509Infrastructure() {
JM 309         if (caKeystorePassword == null) {
310             JPasswordField pass = new JPasswordField(10){
311                 private static final long serialVersionUID = 1L;
312
313                 public void addNotify()             
314                 {                 
315                     super.addNotify();
316                     requestFocusInWindow();             
317                 }         
318             }; 
319             pass.setText(caKeystorePassword);
320             JPanel panel = new JPanel(new BorderLayout());
321             panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH);
322             panel.add(pass, BorderLayout.CENTER);
323             int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION);
324             if (result == JOptionPane.OK_OPTION) {
325                 caKeystorePassword = new String(pass.getPassword());
326             } else {
327                 return false;
328             }
329         }
330
c8b26c 331         X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword);
JM 332         X509Utils.prepareX509Infrastructure(metadata, folder, this);
db9718 333         return true;
3983a6 334     }
JM 335     
336     private List<X509Certificate> findCerts(File folder, String username) {
337         List<X509Certificate> list = new ArrayList<X509Certificate>();
338         File userFolder = new File(folder, X509Utils.CERTS + File.separator + username);
339         if (!userFolder.exists()) {
340             return list;
341         }
342         File [] certs = userFolder.listFiles(new FilenameFilter() {
343             @Override
344             public boolean accept(File dir, String name) {
345                 return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt");
346             }
347         });
348         try {
349             CertificateFactory factory = CertificateFactory.getInstance("X.509");
350             for (File cert : certs) {                
351                 BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));
352                 X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);
353                 is.close();
354                 list.add(x509);
355             }
356         } catch (Exception e) {
357             Utils.showException(GitblitAuthority.this, e);
358         }
359         return list;
360     }
361     
362     private Container getUI() {        
363         userCertificatePanel = new UserCertificatePanel(this) {
364             
365             private static final long serialVersionUID = 1L;
366             @Override
367             public Insets getInsets() {
368                 return Utils.INSETS;
369             }
c8b26c 370             
JM 371             @Override
372             public boolean isAllowEmail() {
373                 return mail.isReady();
374             }
3983a6 375
JM 376             @Override
377             public Date getDefaultExpiration() {
378                 Calendar c = Calendar.getInstance();
379                 c.add(Calendar.DATE, defaultDuration);
380                 c.set(Calendar.HOUR_OF_DAY, 0);
381                 c.set(Calendar.MINUTE, 0);
382                 c.set(Calendar.SECOND, 0);
383                 c.set(Calendar.MILLISECOND, 0);
384                 return c.getTime();
385             }
386             
387             @Override
db9718 388             public boolean saveUser(String username, UserCertificateModel ucm) {
JM 389                 return userService.updateUserModel(username, ucm.user);
3983a6 390             }
JM 391             
392             @Override
db9718 393             public boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) {
JM 394                 if (!prepareX509Infrastructure()) {
395                     return false;
396                 }
397
3983a6 398                 Date notAfter = metadata.notAfter;
e571c4 399                 metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
JM 400                 if (StringUtils.isEmpty(metadata.serverHostname)) {
401                     metadata.serverHostname = Constants.NAME;
402                 }
3983a6 403                 UserModel user = ucm.user;                
JM 404                 
405                 // set default values from config file
406                 File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
407                 FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
408                 if (certificatesConfigFile.exists()) {
409                     try {
410                         config.load();
411                     } catch (Exception e) {
412                         Utils.showException(GitblitAuthority.this, e);
413                     }
414                     NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
415                     certificateConfig.update(metadata);
416                 }
417                 
418                 // restore expiration date
419                 metadata.notAfter = notAfter;
420                 
421                 // set user's specified OID values
422                 if (!StringUtils.isEmpty(user.organizationalUnit)) {
423                     metadata.oids.put("OU", user.organizationalUnit);
424                 }
425                 if (!StringUtils.isEmpty(user.organization)) {
426                     metadata.oids.put("O", user.organization);
427                 }
428                 if (!StringUtils.isEmpty(user.locality)) {
429                     metadata.oids.put("L", user.locality);
430                 }
431                 if (!StringUtils.isEmpty(user.stateProvince)) {
432                     metadata.oids.put("ST", user.stateProvince);
433                 }
434                 if (!StringUtils.isEmpty(user.countryCode)) {
435                     metadata.oids.put("C", user.countryCode);
436                 }
437
438                 File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
c8b26c 439                 File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);
JM 440
3983a6 441                 // save latest expiration date
db9718 442                 if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) {
3983a6 443                     ucm.expires = metadata.notAfter;
JM 444                 }
445                 ucm.update(config);
446                 try {
447                     config.save();
448                 } catch (Exception e) {
449                     Utils.showException(GitblitAuthority.this, e);
450                 }
451                 
452                 // refresh user
453                 ucm.certs = null;
454                 int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
455                 tableModel.fireTableDataChanged();
456                 table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
457                 
458                 if (sendEmail) {
e571c4 459                     sendEmail(user, metadata, zip);
3983a6 460                 }
db9718 461                 return true;
3983a6 462             }
JM 463             
464             @Override
db9718 465             public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) {
JM 466                 if (!prepareX509Infrastructure()) {
467                     return false;
468                 }
469
3983a6 470                 File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
JM 471                 File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
c8b26c 472                 if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) {
3983a6 473                     File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
JM 474                     FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
475                     if (certificatesConfigFile.exists()) {
476                         try {
477                             config.load();
478                         } catch (Exception e) {
479                             Utils.showException(GitblitAuthority.this, e);
480                         }
481                     }
482                     // add serial to revoked list
483                     ucm.revoke(cert.getSerialNumber(), reason);
484                     ucm.update(config);
485                     try {
486                         config.save();
487                     } catch (Exception e) {
488                         Utils.showException(GitblitAuthority.this, e);
489                     }
490                     
491                     // refresh user
492                     ucm.certs = null;
493                     int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
494                     tableModel.fireTableDataChanged();
495                     table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
c8b26c 496                     
db9718 497                     return true;
3983a6 498                 }
db9718 499                 
JM 500                 return false;
3983a6 501             }
JM 502         };
503         
504         table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
505         table.setRowSorter(defaultSorter);
506         table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer());
507         table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
508
509             @Override
510             public void valueChanged(ListSelectionEvent e) {
511                 if (e.getValueIsAdjusting()) {
512                     return;
513                 }
514                 int row = table.getSelectedRow();
515                 if (row < 0) {
516                     return;
517                 }
518                 int modelIndex = table.convertRowIndexToModel(row);
519                 UserCertificateModel ucm = tableModel.get(modelIndex);
520                 if (ucm.certs == null) {
521                     ucm.certs = findCerts(folder, ucm.user.username);
522                 }
523                 userCertificatePanel.setUserCertificateModel(ucm);
524             }
525         });
526         
527         JPanel usersPanel = new JPanel(new BorderLayout()) {
528             
529             private static final long serialVersionUID = 1L;
530
531             @Override
532             public Insets getInsets() {
533                 return Utils.INSETS;
534             }
535         };
536         usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH);
537         usersPanel.add(new JScrollPane(table), BorderLayout.CENTER);
538         usersPanel.setMinimumSize(new Dimension(400, 10));
539         
c8b26c 540         certificateDefaultsButton = new JButton(new ImageIcon(getClass().getResource("/settings_16x16.png")));
JM 541         certificateDefaultsButton.setFocusable(false);
e571c4 542         certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults"));        
c8b26c 543         certificateDefaultsButton.addActionListener(new ActionListener() {
JM 544             @Override
545             public void actionPerformed(ActionEvent e) {
546                 X509Metadata metadata = new X509Metadata("whocares", "whocares");
547                 File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
548                 FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
549                 NewCertificateConfig certificateConfig = null;
550                 if (certificatesConfigFile.exists()) {
551                     try {
552                         config.load();
553                     } catch (Exception x) {
554                         Utils.showException(GitblitAuthority.this, x);
555                     }
556                     certificateConfig = NewCertificateConfig.KEY.parse(config);
557                     certificateConfig.update(metadata);
558                 }
559                 InputVerifier verifier = new InputVerifier() {
560                     public boolean verify(JComponent comp) {
561                         boolean returnValue;
562                         JTextField textField = (JTextField) comp;
563                         try {
564                             Integer.parseInt(textField.getText());
565                             returnValue = true;
566                         } catch (NumberFormatException e) {
567                             returnValue = false;
568                         }
569                         return returnValue;
570                     }
571                 };
572
573                 JTextField durationTF = new JTextField(4);
574                 durationTF.setInputVerifier(verifier);
575                 durationTF.setVerifyInputWhenFocusTarget(true);
576                 durationTF.setText("" + certificateConfig.duration);
577                 JPanel durationPanel = Utils.newFieldPanel(Translation.get("gb.duration"), durationTF, Translation.get("gb.duration.days").replace("{0}",  "").trim());
578                 DefaultOidsPanel oids = new DefaultOidsPanel(metadata);
579
580                 JPanel panel = new JPanel(new BorderLayout());
581                 panel.add(durationPanel, BorderLayout.NORTH);
582                 panel.add(oids, BorderLayout.CENTER);
583
584                 int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, 
e571c4 585                         panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION,
c8b26c 586                         JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png")));
JM 587                 if (result == JOptionPane.OK_OPTION) {
588                     try {
589                         oids.update(metadata);
590                         certificateConfig.duration = Integer.parseInt(durationTF.getText());
591                         certificateConfig.store(config, metadata);
592                         config.save();
593                     } catch (Exception e1) {
594                         Utils.showException(GitblitAuthority.this, e1);
595                     }
596                 }
597             }
598         });
599         
44f623 600         newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png")));
e571c4 601         newSSLCertificate.setFocusable(false);
JM 602         newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));        
603         newSSLCertificate.addActionListener(new ActionListener() {
c8b26c 604             @Override
JM 605             public void actionPerformed(ActionEvent e) {
606                 Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
e571c4 607                 NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration);
c8b26c 608                 dialog.setModal(true);
JM 609                 dialog.setVisible(true);
610                 if (dialog.isCanceled()) {
611                     return;
612                 }
e571c4 613                 final Date expires = dialog.getExpiration();
JM 614                 final String hostname = dialog.getHostname();
615
616                 AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
617
618                     @Override
619                     protected Boolean doRequest() throws IOException {
db9718 620                         if (!prepareX509Infrastructure()) {
JM 621                             return false;
622                         }
e571c4 623                         
JM 624                         // read CA private key and certificate
625                         File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
626                         PrivateKey caPrivateKey = X509Utils.getPrivateKey(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
627                         X509Certificate caCert = X509Utils.getCertificate(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
628                         
629                         // generate new SSL certificate
630                         X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword);
631                         metadata.notAfter = expires;
632                         File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE);
633                         X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this);
634                         return cert != null;
635                     }
636
637                     @Override
638                     protected void onSuccess() {
639                         JOptionPane.showMessageDialog(GitblitAuthority.this, 
640                                 MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname),
641                                 Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
642                     }
643                 };
c8b26c 644                 
e571c4 645                 worker.execute();
JM 646             }
647         });
648         
649         JButton emailBundle = new JButton(new ImageIcon(getClass().getResource("/mail_16x16.png")));
650         emailBundle.setFocusable(false);
651         emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle"));        
652         emailBundle.addActionListener(new ActionListener() {
653             @Override
654             public void actionPerformed(ActionEvent e) {
655                 int row = table.getSelectedRow();
656                 if (row < 0) {
657                     return;
658                 }
659                 int modelIndex = table.convertRowIndexToModel(row);
660                 final UserCertificateModel ucm = tableModel.get(modelIndex);
661                 if (ArrayUtils.isEmpty(ucm.certs)) {
662                     JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName()));
663                 }
664                 final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip");
665                 if (!zip.exists()) {
666                     return;
667                 }
c8b26c 668                 
e571c4 669                 AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
JM 670                     @Override
671                     protected Boolean doRequest() throws IOException {
672                         X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares");
673                         metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
674                         if (StringUtils.isEmpty(metadata.serverHostname)) {
675                             metadata.serverHostname = Constants.NAME;
676                         }
677                         metadata.userDisplayname = ucm.user.getDisplayName();
44f623 678                         return sendEmail(ucm.user, metadata, zip);
e571c4 679                     }
JM 680
681                     @Override
682                     protected void onSuccess() {
683                         JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"),
684                                 ucm.user.getDisplayName()));
685                     }
686                     
687                 };
688                 worker.execute();                
c8b26c 689             }
JM 690         });
691         
692         final JTextField filterTextfield = new JTextField(15);
3983a6 693         filterTextfield.addActionListener(new ActionListener() {
JM 694             public void actionPerformed(ActionEvent e) {
695                 filterUsers(filterTextfield.getText());
696             }
697         });
698         filterTextfield.addKeyListener(new KeyAdapter() {
699             public void keyReleased(KeyEvent e) {
700                 filterUsers(filterTextfield.getText());
701             }
702         });
c8b26c 703         
JM 704         JPanel buttonControls = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, Utils.MARGIN));
705         buttonControls.add(certificateDefaultsButton);
e571c4 706         buttonControls.add(newSSLCertificate);
JM 707         buttonControls.add(emailBundle);
3983a6 708
c8b26c 709         JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN));
3983a6 710         userControls.add(new JLabel(Translation.get("gb.filter")));
JM 711         userControls.add(filterTextfield);
712         
c8b26c 713         JPanel topPanel = new JPanel(new BorderLayout(0, 0));
JM 714         topPanel.add(buttonControls, BorderLayout.WEST);
715         topPanel.add(userControls, BorderLayout.EAST);
716         
3983a6 717         JPanel leftPanel = new JPanel(new BorderLayout());
c8b26c 718         leftPanel.add(topPanel, BorderLayout.NORTH);
3983a6 719         leftPanel.add(usersPanel, BorderLayout.CENTER);
JM 720         
721         userCertificatePanel.setMinimumSize(new Dimension(375, 10));
c8b26c 722         
JM 723         JLabel statusLabel = new JLabel();
724         statusLabel.setHorizontalAlignment(SwingConstants.RIGHT);
725         if (X509Utils.unlimitedStrength) {
726             statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy");
727         } else {
728             statusLabel.setText("JCE Standard Encryption Policy");
729         }
3983a6 730         
JM 731         JPanel root = new JPanel(new BorderLayout()) {
732             private static final long serialVersionUID = 1L;
733             public Insets getInsets() {
734                 return Utils.INSETS;
735             }
736         };
737         JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel);
738         splitPane.setDividerLocation(1d);
c8b26c 739         root.add(splitPane, BorderLayout.CENTER);
JM 740         root.add(statusLabel, BorderLayout.SOUTH);
3983a6 741         return root;
JM 742     }
743     
744     private void filterUsers(final String fragment) {
745         if (StringUtils.isEmpty(fragment)) {
746             table.setRowSorter(defaultSorter);
747             return;
748         }
749         RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {
750             public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) {
751                 for (int i = entry.getValueCount() - 1; i >= 0; i--) {
752                     if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
753                         return true;
754                     }
755                 }
756                 return false;
757             }
758         };
759         TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>(
760                 tableModel);
761         sorter.setRowFilter(containsFilter);
762         table.setRowSorter(sorter);
763     }
c8b26c 764     
JM 765     @Override
766     public void log(String message) {
767         BufferedWriter writer = null;
768         try {
769             writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true));
770             writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
771             writer.newLine();
772             writer.flush();
773         } catch (Exception e) {
774             LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
775         } finally {
776             if (writer != null) {
777                 try {
778                     writer.close();
779                 } catch (IOException e) {
780                 }
781             }
782         }
783     }
e571c4 784     
44f623 785     private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) {
e571c4 786         // send email
JM 787         try {
788             if (mail.isReady()) {
789                 Message message = mail.createMessage(user.emailAddress);
790                 message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname);
791
792                 // body of email
793                 String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata);
794                 if (StringUtils.isEmpty(body)) {
795                     body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());
796                 }
797                 Multipart mp = new MimeMultipart();
798                 MimeBodyPart messagePart = new MimeBodyPart();
799                 messagePart.setText(body);
800                 mp.addBodyPart(messagePart);
801
802                 // attach zip
803                 MimeBodyPart filePart = new MimeBodyPart();
804                 FileDataSource fds = new FileDataSource(zip);
805                 filePart.setDataHandler(new DataHandler(fds));
806                 filePart.setFileName(fds.getName());
807                 mp.addBodyPart(filePart);
808
809                 message.setContent(mp);
810
811                 mail.sendNow(message);
44f623 812                 return true;
e571c4 813             } else {
JM 814                 JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
815             }
816         } catch (Exception e) {
817             Utils.showException(GitblitAuthority.this, e);
818         }
44f623 819         return false;
e571c4 820     }
3983a6 821 }