diff --git a/.gitignore b/.gitignore index a1c2a23..6686142 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +/.project +/.classpath +/target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0b71657 --- /dev/null +++ b/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.5 + + + org.directtruststandards + timplus-tools + TIM+ tools + 1.0.0 + TIM+ tools such as a certificate generator. + 2020 + + + 1.8 + + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter + + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + 2.7 + + + com.fasterxml.jackson.core + jackson-annotations + + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + org.bouncycastle + bcpkix-jdk15on + 1.60 + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + -Xdoclint:none + UTF-8 + UTF-8 + true + true + true + 1.8 + public + + + + package + attach-javadocs + + jar + + + -Xdoclint:none + + + + + + + + + + sonatype-snapshot + Sonatype OSS Maven SNAPSHOT Repository + https://oss.sonatype.org/content/repositories/snapshots/ + false + + + sonatype-release + Sonatype OSS Maven Release Repositor + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + false + + + diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/CAPanel.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/CAPanel.java new file mode 100644 index 0000000..a0ed9d4 --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/CAPanel.java @@ -0,0 +1,1043 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; + +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JRadioButton; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.border.BevelBorder; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.SoftBevelBorder; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.bouncycastle.asn1.x509.CRLNumber; +import org.bouncycastle.asn1.x509.CRLReason; +import org.bouncycastle.asn1.x509.X509Extensions; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.x509.X509V2CRLGenerator; +import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; + +/** + * UI component for creating or loading self signed certificates. + * @author Greg Meyer + * + */ +///CLOVER:OFF +class CAPanel extends JPanel +{ + static final long serialVersionUID = -92734291206052662L; + + protected static final int WF_CONTEXT_LOAD_CERTS = 0; + protected static final int WF_CONTEXT_CREATE_CERTS = 1; + protected static final int WF_CONTEXT_CLEAR = 2; + protected static final int WF_CONTEXT_CERT_LOADED = 3; + protected static final int WF_CONTEXT_CERT_CREATED = 4; + + protected JRadioButton createCA; + protected JRadioButton loadCA; + protected JTextField certFile; + protected JTextField certPrivKeyFile; + + protected TextEntryField cnField; + protected TextEntryField countryField; + protected TextEntryField stateField; + protected TextEntryField locField; + protected TextEntryField orgField; + protected TextEntryField emailField; + + protected SpinEntryField expField; + protected DropDownEntry keyStr; + protected PasswordField passField; + + protected FileField certFileField; + protected FileField keyFileField; + + protected JButton loadCert; + protected JButton createCert; + protected JButton clear; + protected JButton genCert; + protected JButton signCSR; + protected JButton createCRL; + protected JCheckBox addToAltSubjects; + protected JCheckBox allowedToSign; + protected JCheckBox keyEnc; + protected JCheckBox digitalSig; + + protected JPanel fieldsPanel; + + protected CertCreateFields currentCert; + + public CAPanel() + { + super(); + + initUI(); + + addActions(); + + setWorkflowContext(WF_CONTEXT_CREATE_CERTS); + } + + protected void initUI() + { + setLayout(new BorderLayout()); + //setBorder(new SoftBevelBorder(BevelBorder.LOWERED)); + setBorder(new CompoundBorder( + new SoftBevelBorder(BevelBorder.LOWERED), new EmptyBorder(5,5,5,5)) ); + + + createCA = new JRadioButton("Create New CA"); + loadCA = new JRadioButton("Load CA"); + ButtonGroup group = new ButtonGroup(); + group.add(createCA); + group.add(loadCA); + createCA.setSelected(true); + + JPanel radioPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + + radioPanel.add(createCA); + radioPanel.add(loadCA); + + fieldsPanel = new JPanel(); + fieldsPanel.setLayout(new GridLayout(3, 3, 10, 10)); + + cnField = new TextEntryField("CN:"); + fieldsPanel.add(cnField); + + countryField= new TextEntryField("Country:"); + fieldsPanel.add(countryField); + + stateField= new TextEntryField("State:"); + fieldsPanel.add(stateField); + + locField= new TextEntryField("Location:"); + fieldsPanel.add(locField); + + orgField= new TextEntryField("Org:"); + fieldsPanel.add(orgField); + + emailField= new TextEntryField("Email:"); + fieldsPanel.add(emailField); + + expField = new SpinEntryField("Experiation (Days):", 365); + fieldsPanel.add(expField); + + keyStr = new DropDownEntry("Key Strength:", new Object[] {2048, 4096}); + fieldsPanel.add(keyStr); + + passField = new PasswordField("Password:"); + fieldsPanel.add(passField); + + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.add(radioPanel, BorderLayout.NORTH); + topPanel.add(fieldsPanel, BorderLayout.CENTER); + + add(topPanel, BorderLayout.NORTH); + + new FlowLayout(FlowLayout.LEFT); + + certFileField = new FileField("Certificate Authority File:", ""); + keyFileField = new FileField("Private Key File:", ""); + + + JPanel filePanel = new JPanel(new GridLayout(1, 2)); + filePanel.add(certFileField); + filePanel.add(keyFileField); + + loadCert = new JButton("Load"); + loadCert.setVisible(false); + createCert = new JButton("Create"); + clear = new JButton("Clear"); + clear.setVisible(false); + clear = new JButton("Clear"); + genCert = new JButton("Create Leaf Cert"); + genCert.setVisible(false); + signCSR = new JButton("Sign CSR"); + signCSR.setVisible(false); + createCRL = new JButton("Create Empty CRL"); + createCRL.setVisible(false); + + addToAltSubjects = new JCheckBox("Add Email To Alt Subject Names"); + addToAltSubjects.setVisible(false); + addToAltSubjects.setSelected(true); + allowedToSign = new JCheckBox("Allowed To Sign Certificates"); + allowedToSign.setVisible(false); + keyEnc = new JCheckBox("Key Encipherment Use"); + keyEnc.setVisible(false); + keyEnc.setSelected(true); + digitalSig = new JCheckBox("Digital Signature Use"); + digitalSig.setVisible(false); + keyEnc.setSelected(true); + + + JPanel addAltPanel = new JPanel(new GridLayout(2, 2)); + addAltPanel.setPreferredSize(new Dimension(450, addAltPanel.getPreferredSize().height)); + //addAltPanel.add(addToAltSubjects); + addAltPanel.add(allowedToSign); + addAltPanel.add(keyEnc); + addAltPanel.add(digitalSig); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.add(addAltPanel); + buttonPanel.add(loadCert); + buttonPanel.add(createCert); + buttonPanel.add(clear); + buttonPanel.add(genCert); + buttonPanel.add(signCSR); + buttonPanel.add(createCRL); + + JPanel combineAltAndButtonPanel = new JPanel(new BorderLayout()); + //combineAltAndButtonPanel.add(addAltPanel, BorderLayout.WEST); + combineAltAndButtonPanel.add(buttonPanel, BorderLayout.EAST); + + JPanel bottomPannel = new JPanel(new BorderLayout()); + bottomPannel.add(filePanel, BorderLayout.NORTH); + bottomPannel.add(combineAltAndButtonPanel, BorderLayout.SOUTH); + + this.add(bottomPannel, BorderLayout.SOUTH); + } + + private void addActions() + { + loadCert.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + loadCACert(); + } + }); + + createCert.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + createCACert(); + } + }); + + createCA.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + setWorkflowContext(WF_CONTEXT_CREATE_CERTS); + } + }); + + loadCA.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + setWorkflowContext(WF_CONTEXT_LOAD_CERTS); + } + }); + + clear.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + setWorkflowContext(WF_CONTEXT_CLEAR); + } + }); + + genCert.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + createLeaf(); + } + }); + + signCSR.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + signCSR(); + } + }); + + createCRL.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + createCRL(); + } + }); + + } + + + private void setWorkflowContext(int workflowContext) + { + switch (workflowContext) + { + case WF_CONTEXT_CREATE_CERTS: + case WF_CONTEXT_CLEAR: + { + // creating a new CA + cnField.setEnabled(true); + countryField.setEnabled(true); + stateField.setEnabled(true); + locField.setEnabled(true); + orgField.setEnabled(true); + emailField.setEnabled(true); + + expField.setEnabled(true); + keyStr.setEnabled(true); + passField.setEnabled(true); + + certFileField.setEnabled(true); + keyFileField.setEnabled(true); + + createCA.setEnabled(true); + loadCA.setEnabled(true); + + loadCert.setVisible(false); + createCert.setVisible(true); + clear.setVisible(false); + genCert.setVisible(false); + signCSR.setVisible(false); + createCRL.setVisible(false); + addToAltSubjects.setVisible(false); + + if(workflowContext == WF_CONTEXT_CLEAR) + { + cnField.setText(""); + countryField.setText(""); + stateField.setText(""); + locField.setText(""); + orgField.setText(""); + emailField.setText(""); + + expField.setValue(365); + keyStr.setValue("2048"); + passField.clear(); + certFileField.setFile(null); + keyFileField.setFile(null); + + createCA.setSelected(true); + + loadCert.setVisible(false); + createCert.setVisible(true); + clear.setVisible(false); + genCert.setVisible(false); + signCSR.setVisible(false); + createCRL.setVisible(false); + addToAltSubjects.setVisible(false); + + currentCert = null; + + } + break; + } + case WF_CONTEXT_LOAD_CERTS: + { + cnField.setEnabled(false); + countryField.setEnabled(false); + stateField.setEnabled(false); + locField.setEnabled(false); + orgField.setEnabled(false); + emailField.setEnabled(false); + + expField.setEnabled(false); + keyStr.setEnabled(false); + passField.setEnabled(true); + + loadCert.setVisible(true); + createCert.setVisible(false); + clear.setVisible(false); + genCert.setVisible(false); + signCSR.setVisible(false); + createCRL.setVisible(false); + addToAltSubjects.setVisible(false); + + createCA.setEnabled(true); + loadCA.setEnabled(true); + + certFileField.setEnabled(true); + keyFileField.setEnabled(true); + + break; + } + case WF_CONTEXT_CERT_CREATED: + case WF_CONTEXT_CERT_LOADED: + { + cnField.setEnabled(false); + countryField.setEnabled(false); + stateField.setEnabled(false); + locField.setEnabled(false); + orgField.setEnabled(false); + emailField.setEnabled(false); + + expField.setEnabled(false); + keyStr.setEnabled(false); + passField.setEnabled(false); + + createCA.setEnabled(false); + loadCA.setEnabled(false); + + loadCert.setVisible(false); + createCert.setVisible(false); + clear.setVisible(true); + genCert.setVisible(true); + signCSR.setVisible(true); + createCRL.setVisible(true); + + addToAltSubjects.setVisible(false); + + break; + } + + } + } + + private void createLeaf() + { + // create leaf certificates in the sub dialog + LeafCertGenDialog generator = new LeafCertGenDialog(null, currentCert); + generator.setVisible(true); + } + + @SuppressWarnings("deprecation") + private void signCSR() + { + JFileChooser fc = new JFileChooser(); + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + fc.setDialogTitle("Open Signing Request PEM File"); + + + int result = fc.showOpenDialog(this); + + if(result == JFileChooser.APPROVE_OPTION) + { + final File fl = fc.getSelectedFile(); + + PemReader reader = null; + try + { + reader = new PemReader( new InputStreamReader(FileUtils.openInputStream(fl))); + final PemObject certReq = reader.readPemObject(); + + final X509Certificate signedCert = CertGenerator.createCertFromCSR(certReq, currentCert); + + // validate the certificate + signedCert.verify(currentCert.getSignerCert().getPublicKey()); + + // write it to a file + final String addressName = cnField.getText(); + final File outFile = new File(addressName + ".der"); + FileUtils.writeByteArrayToFile(outFile, signedCert.getEncoded()); + + JOptionPane.showMessageDialog(this,"Signing successful.\r\nCertificate saved to " + outFile.getAbsolutePath(), + "CSR Sign", JOptionPane.PLAIN_MESSAGE ); + + } + catch (Exception e) + { + JOptionPane.showMessageDialog(this,"Error signging CSR: " + e.getMessage(), + "CSR Sign Error", JOptionPane.ERROR_MESSAGE ); + } + finally + { + IOUtils.closeQuietly(reader); + } + } + } + + + protected File createNewFileName(FileType type) + { + String fileName; + + int index; + String field = emailField.getText(); + if (!field.isEmpty()) + { + index = field.indexOf("@"); + if (index > -1) + fileName = field.substring(0, index); + else + fileName = field; + } + else + { + field = cnField.getText(); + index = field.indexOf("@"); + if (index > -1) + fileName = field.substring(0, index); + else + fileName = field; + } + + if (type == FileType.PRIVATE_KEY) + fileName += "Key"; + + if (type != FileType.CRL) + fileName += ".der"; + else + fileName += ".crl"; + + return new File(fileName); + + } + + protected void createCACert() + { + if (passField.getPassword().length == 0) + { + int selection = JOptionPane.showConfirmDialog(this, "The password field is empty. Do you wish to create a private key file without a password?", + "Empty Password", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (selection == JOptionPane.NO_OPTION) + return; + } + + // make sure there is at least a CN + if (cnField.getText().isEmpty()) + { + JOptionPane.showMessageDialog(this,"Common Name (CN) must have a value.", + "Invalid Cert File", JOptionPane.ERROR_MESSAGE ); + return; + } + + // see if the file attributes are set + if (certFileField.getFile().getPath().isEmpty()) + certFileField.setFile(createNewFileName(FileType.CERT)); + + if (keyFileField.getFile().getPath().isEmpty()) + keyFileField.setFile(createNewFileName(FileType.PRIVATE_KEY)); + + // check if the files already exist + if (certFileField.getFile().exists() || keyFileField.getFile().exists()) + { + int selection = JOptionPane.showConfirmDialog(this, "The certificate or key file already exists. This operation will overwrite the file. Continue?", + "Empty Password", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (selection == JOptionPane.NO_OPTION) + return; + } + + + + // get the fields + Map attributes = new HashMap(); + attributes.put("CN", cnField.getText()); + if (!countryField.getText().isEmpty()) + attributes.put("C", countryField.getText()); + if (!stateField.getText().isEmpty()) + attributes.put("ST", stateField.getText()); + if (!locField.getText().isEmpty()) + attributes.put("L", locField.getText()); + if (!orgField.getText().isEmpty()) + attributes.put("O", orgField.getText()); + if (!emailField.getText().isEmpty()) + attributes.put("EMAILADDRESS", emailField.getText()); + + int exp = Integer.parseInt(expField.getValue().toString()); + int keyStre = Integer.parseInt(keyStr.getValue().toString()); + + CertCreateFields createFields = new CertCreateFields(attributes, certFileField.getFile(), keyFileField.getFile(), + passField.getPassword(), exp, + keyStre, null, null); + + // this enough info to create the files... lets do it + CertCreateFields retCert; + try + { + retCert = CertGenerator.createCertificate(createFields); + } + catch (Exception e) + { + JOptionPane.showMessageDialog(this,"An error occured creating the certificate authority: " + e.getMessage(), + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + if (retCert == null) + { + JOptionPane.showMessageDialog(this,"An error occured creating the certificate the authority: unknown error", + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + JOptionPane.showMessageDialog(this,"CA certificate and private key created successfully.", + "SUCCESS", JOptionPane.PLAIN_MESSAGE); + + currentCert = retCert; + + setWorkflowContext(WF_CONTEXT_CERT_CREATED); + + } + + private void loadCACert() + { + File certFile = certFileField.getFile(); + File keyFile = keyFileField.getFile(); + + if (!certFile.exists()) + { + JOptionPane.showMessageDialog(this,"Certificate file does not exist or cannot be found.", + "Invalid Cert File", JOptionPane.ERROR_MESSAGE ); + return; + } + + if (!keyFile.exists()) + { + JOptionPane.showMessageDialog(this,"Private key file does not exist or cannot be found.", + "Invalid Key File", JOptionPane.ERROR_MESSAGE ); + + return; + } + + // load the certs from the file system + CertCreateFields retCert; + try + { + retCert = CertLoader.loadCertificate(certFile, keyFile, this.passField.getPassword()); + } + catch (Exception e) + { + JOptionPane.showMessageDialog(this,"An error occured loading the certificate authority: " + e.getMessage(), + "Certificate Load Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + if (retCert == null) + { + JOptionPane.showMessageDialog(this,"An error occured loading the certificate the authority: unknown error", + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + // make sure this cert has privs to act as a CA and sign other CERTs + if (retCert.getSignerCert().getBasicConstraints() < 0) + { + // may be a self signed cert + if (!retCert.getSignerCert().getSubjectX500Principal().equals(retCert.getSignerCert().getIssuerX500Principal())) + { + JOptionPane.showMessageDialog(this,"This certificate's policy does not allowed it to sign other certificates.", + "Policy Validation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + } + + // get the attributes + if (retCert.getAttributes().containsKey("EMAILADDRESS")) + this.emailField.setText(retCert.getAttributes().get("EMAILADDRESS").toString()); + else + this.emailField.setText(""); + + if (retCert.getAttributes().containsKey("CN")) + this.cnField.setText(retCert.getAttributes().get("CN").toString()); + else + this.cnField.setText(""); + + if (retCert.getAttributes().containsKey("C")) + this.countryField.setText(retCert.getAttributes().get("C").toString()); + else + this.countryField.setText(""); + + if (retCert.getAttributes().containsKey("ST")) + this.stateField.setText(retCert.getAttributes().get("ST").toString()); + else + this.stateField.setText(""); + + if (retCert.getAttributes().containsKey("L")) + this.locField.setText(retCert.getAttributes().get("L").toString()); + else + this.locField.setText(""); + + if (retCert.getAttributes().containsKey("O")) + this.orgField.setText(retCert.getAttributes().get("O").toString()); + else + this.orgField.setText(""); + + this.expField.setValue(retCert.getExpDays()); + + JOptionPane.showMessageDialog(this,"CA certificate and private key loaded successfully.", + "SUCCESS", JOptionPane.PLAIN_MESSAGE); + + currentCert = retCert; + + setWorkflowContext(WF_CONTEXT_CERT_LOADED); + } + + protected static class TextEntryField extends JPanel + { + static final long serialVersionUID = -7340775331901207365L; + + private JLabel label; + private JTextField text; + + public TextEntryField(String labelText) + { + super(); + + this.setLayout(new BorderLayout()); + + + label = new JLabel(labelText); + label.setPreferredSize(new Dimension(50, label.getPreferredSize().getSize().height)); + + text = new JTextField(); + text.setPreferredSize(new Dimension(150, label.getPreferredSize().getSize().height)); + + add(label, BorderLayout.NORTH); + + JPanel textPanel = new JPanel(new BorderLayout()); + textPanel.add(text, BorderLayout.NORTH); + add(textPanel); + } + + @Override + public void setEnabled(boolean b) + { + super.setEnabled(b); + text.setEnabled(b); + } + + public String getText() + { + return text.getText().trim(); + } + + public void setText(String value) + { + text.setText(value); + } + + public void setLabelText(String value) + { + label.setText(value); + } + } + + protected static class SpinEntryField extends JPanel + { + static final long serialVersionUID = 2260694248137330015L; + + + private JLabel label; + private JSpinner value; + + public SpinEntryField(String labelText, int intValue) + { + super(); + this.setLayout(new BorderLayout()); + + label = new JLabel(labelText); + label.setPreferredSize(new Dimension(50, label.getPreferredSize().getSize().height)); + + value = new JSpinner(new SpinnerNumberModel(intValue, + -10, //min + 100000, //max + 1)); + + + add(label, BorderLayout.NORTH); + + JPanel spinnerPanel = new JPanel(new BorderLayout()); + spinnerPanel.add(value, BorderLayout.NORTH); + add(spinnerPanel); + } + + @Override + public void setEnabled(boolean b) + { + super.setEnabled(b); + value.setEnabled(b); + } + + public Object getValue() + { + return value.getValue(); + } + + public void setValue(Object value) + { + this.value.setValue(value); + } + } + + protected static class DropDownEntry extends JPanel + { + static final long serialVersionUID = 3279442634853500454L; + + private JLabel label; + private JComboBox selections; + + public DropDownEntry(String labelText, Object[] items) + { + super(); + this.setLayout(new BorderLayout()); + + label = new JLabel(labelText); + label.setPreferredSize(new Dimension(50, label.getPreferredSize().getSize().height)); + + selections = new JComboBox(items); + + + add(label, BorderLayout.NORTH); + + JPanel dropPanel = new JPanel(new BorderLayout()); + dropPanel.add(selections, BorderLayout.NORTH); + + add(dropPanel); + } + + @Override + public void setEnabled(boolean b) + { + super.setEnabled(b); + selections.setEnabled(b); + } + + public Object getValue() + { + return selections.getSelectedItem(); + } + + public void setValue(Object value) + { + selections.setSelectedItem(value); + } + } + + protected static class PasswordField extends JPanel + { + static final long serialVersionUID = -7837326704224526655L; + + private JLabel label; + private JPasswordField pass; + + public PasswordField(String labelText) + { + super(); + this.setLayout(new BorderLayout()); + + label = new JLabel(labelText); + label.setPreferredSize(new Dimension(150, label.getPreferredSize().getSize().height)); + + pass = new JPasswordField(); + pass.setPreferredSize(new Dimension(150, label.getPreferredSize().getSize().height)); + + add(label, BorderLayout.NORTH); + + JPanel passPanel = new JPanel(new BorderLayout()); + passPanel.add(pass, BorderLayout.NORTH); + + add(passPanel); + } + + @Override + public void setEnabled(boolean b) + { + super.setEnabled(b); + pass.setEnabled(b); + } + + public char[] getPassword() + { + if (pass.getPassword() == null || pass.getPassword().length == 0) + return "".toCharArray(); + + return pass.getPassword(); + } + + public void clear() + { + pass.setText(""); + } + } + + protected static class FileField extends JPanel + { + static final long serialVersionUID = 8783281209944790372L; + + private JLabel label; + private JTextField text; + private JButton search; + + public FileField(String labelText, String file) + { + super(); + + this.setLayout(new BorderLayout()); + + + label = new JLabel(labelText); + label.setPreferredSize(new Dimension(50, label.getPreferredSize().getSize().height)); + + text = new JTextField(); + text.setPreferredSize(new Dimension(250, label.getPreferredSize().getSize().height)); + + add(label, BorderLayout.NORTH); + + JPanel filePanel = new JPanel(new BorderLayout()); + + search = new JButton("..."); + search.setPreferredSize(new Dimension(30, text.getPreferredSize().getSize().height)); + + + JPanel fileWithSearchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + fileWithSearchPanel.add(text); + fileWithSearchPanel.add(search); + + filePanel.add(fileWithSearchPanel, BorderLayout.NORTH); + add(filePanel); + + search.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + selectFile(); + } + }); + } + + private void selectFile() + { + JFileChooser fc = new JFileChooser(); + fc.setDragEnabled(false); + + + // set the current directory to be the images directory + if (!text.getText().trim().isEmpty()) + { + File startFile = new File(text.getText()); + if (startFile.exists()) + { + fc.setCurrentDirectory(startFile); + } + } + + + int result = fc.showOpenDialog(this); + + // if we selected an image, load the image + if(result == JFileChooser.APPROVE_OPTION) + { + text.setText(fc.getSelectedFile().getPath()); + } + } + + public File getFile() + { + return new File(text.getText().trim()); + } + + public void setFile(File fl) + { + if (fl != null) + text.setText(fl.getAbsolutePath()); + else + text.setText(""); + } + + @Override + public void setEnabled(boolean b) + { + super.setEnabled(b); + text.setEnabled(b); + search.setEnabled(b); + } + } + + protected void createCRL() + { + try + { + X509V2CRLGenerator crlGen = new X509V2CRLGenerator(); + + Date now = new Date(); + Calendar nextUpdate = Calendar.getInstance(); + nextUpdate.add(Calendar.DAY_OF_MONTH, 30); + crlGen.setIssuerDN(currentCert.getSignerCert().getSubjectX500Principal()); + + crlGen.setThisUpdate(now); + crlGen.setNextUpdate(nextUpdate.getTime()); + crlGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, + false, new AuthorityKeyIdentifierStructure(currentCert.getSignerCert())); + + crlGen.addExtension(X509Extensions.CRLNumber, + false, new CRLNumber(BigInteger.valueOf(1))); + + X509CRL crl = crlGen.generate((PrivateKey)currentCert.getSignerKey(), "BC"); + + final File crlFile = createNewFileName(FileType.CRL); + + FileUtils.writeByteArrayToFile(crlFile, crl.getEncoded()); + + + JOptionPane.showMessageDialog(this,"Empty CRL file created successfully:\r\n" + + crlFile.getName(), + "SUCCESS", JOptionPane.PLAIN_MESSAGE); + } + catch (Exception e) + { + JOptionPane.showMessageDialog(this,"An error occured creating the CRL: " + e.getMessage(), + "CRL Creation Error", JOptionPane.ERROR_MESSAGE); + } + + } + + protected enum FileType + { + CERT, + + PRIVATE_KEY, + + CRL + } +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/CertCreateFields.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertCreateFields.java new file mode 100644 index 0000000..b978fc5 --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertCreateFields.java @@ -0,0 +1,125 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.io.File; +import java.security.Key; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Map; + +/** + * Container for fields related to generating certificates. + * @author Greg Meyer + * + */ +///CLOVER:OFF +public class CertCreateFields +{ + private Map attributes; + private File newCertFile; + private File newKeyFile; + private char[] newPassword; + private int expDays; + private int keyStrength; + private X509Certificate signerCert; + private Key signerKey; + + public CertCreateFields(Map attributes, File newCertFile, File newKeyFile, + char[] newPassword, int expDays, int keyStrength, X509Certificate signerCert, Key signerKey) + { + this.attributes = attributes; + this.newCertFile = newCertFile; + this.newKeyFile = newKeyFile; + this.newPassword = newPassword; + this.expDays = expDays; + this.keyStrength = keyStrength; + this.signerCert = signerCert; + this.signerKey = signerKey; + } + + public Map getAttributes() { + return Collections.unmodifiableMap(attributes); + } + + public File getNewCertFile() { + return newCertFile; + } + + public File getNewKeyFile() { + return newKeyFile; + } + + public char[] getNewPassword() { + return newPassword; + } + + public int getExpDays() { + return expDays; + } + + public int getKeyStrength() { + return keyStrength; + } + + public X509Certificate getSignerCert() { + return signerCert; + } + + public Key getSignerKey() { + return signerKey; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public void setNewCertFile(File newCertFile) { + this.newCertFile = newCertFile; + } + + public void setNewKeyFile(File newKeyFile) { + this.newKeyFile = newKeyFile; + } + + public void setNewPassword(char[] newPassword) { + this.newPassword = newPassword; + } + + public void setExpDays(int expDays) { + this.expDays = expDays; + } + + public void setKeyStrength(int keyStrength) { + this.keyStrength = keyStrength; + } + + public void setSignerCert(X509Certificate signerCert) { + this.signerCert = signerCert; + } + + public void setSignerKey(Key signerKey) { + this.signerKey = signerKey; + } + +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/CertGenerator.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertGenerator.java new file mode 100644 index 0000000..2dd86bb --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertGenerator.java @@ -0,0 +1,415 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Enumeration; +import java.util.List; +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +import org.apache.commons.io.FileUtils; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.cms.Attribute; +import org.bouncycastle.asn1.pkcs.CertificationRequestInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.CRLDistPoint; +import org.bouncycastle.asn1.x509.DistributionPoint; +import org.bouncycastle.asn1.x509.DistributionPointName; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x509.X509Extension; +import org.bouncycastle.asn1.x509.X509Extensions; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.crypto.prng.VMPCRandomGenerator; +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.jce.X509Principal; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.x509.X509V3CertificateGenerator; +import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; + +/** + * Engine for generating self signed certificates and leaf node certificates. + * @author Greg Meyer + * + */ +///CLOVER:OFF +public class CertGenerator +{ + private static final String PBE_WITH_MD5_AND_DES_CBC_OID = "1.2.840.113549.1.5.3"; + + public static CertCreateFields createCertificate(CertCreateFields fields) throws Exception + { + // generate a key pair first using RSA and a key strength provided by the user + KeyPairGenerator kpg = (KeyPairGenerator) KeyPairGenerator.getInstance("RSA", "BC"); + + kpg.initialize(fields.getKeyStrength(), new SecureRandom()); + + KeyPair keyPair = kpg.generateKeyPair(); + + if (fields.getSignerCert() == null) + // this is request for a new CA + return createNewCA(fields, keyPair); + else + // new leaf certificate request + return createLeafCertificate(fields, keyPair); + } + + public static long generatePositiveRandom() + { + Random ranGen; + long retVal = -1; + byte[] seed = new byte[8]; + VMPCRandomGenerator seedGen = new VMPCRandomGenerator(); + seedGen.addSeedMaterial(new SecureRandom().nextLong()); + seedGen.nextBytes(seed); + ranGen = new SecureRandom(seed); + while (retVal < 1) + { + retVal = ranGen.nextLong(); + } + + return retVal; + } + + public static X509Certificate createCertFromCSR(PemObject certReq, CertCreateFields signerCert) throws Exception + { + /* + certReq.verify(); + + final CertificationRequestInfo reqInfo = certReq.getCertificationRequestInfo(); + + final X509V3CertificateGenerator v1CertGen = new X509V3CertificateGenerator(); + final Calendar start = Calendar.getInstance(); + final Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 3); + + v1CertGen.setSerialNumber(BigInteger.valueOf(generatePositiveRandom())); + v1CertGen.setIssuerDN(signerCert.getSignerCert().getSubjectX500Principal()); // issuer is the parent cert + v1CertGen.setNotBefore(start.getTime()); + v1CertGen.setNotAfter(end.getTime()); + v1CertGen.setSubjectDN(new X509Principal(reqInfo.getSubject().toString())); + v1CertGen.setPublicKey(certReq.getPublicKey()); + v1CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + + final ASN1Set attributesAsn1Set = reqInfo.getAttributes(); + + X509Extensions certificateRequestExtensions = null; + for (int i = 0; i < attributesAsn1Set.size(); ++i) + { + // There should be only only one attribute in the set. (that is, only + // the `Extension Request`, but loop through to find it properly) + final DEREncodable derEncodable = attributesAsn1Set.getObjectAt( i ); + + + if (derEncodable instanceof DERSequence) + { + final Attribute attribute = new Attribute( (DERSequence) attributesAsn1Set + .getObjectAt( i ) ); + + if (attribute.getAttrType().equals( PKCSObjectIdentifiers.pkcs_9_at_extensionRequest )) + { + // The `Extension Request` attribute is present. + final ASN1Set attributeValues = attribute.getAttrValues(); + + // The X509Extensions are contained as a value of the ASN.1 Set. + // Assume that it is the first value of the set. + if (attributeValues.size() >= 1) + { + certificateRequestExtensions = new X509Extensions( (ASN1Sequence) attributeValues + .getObjectAt( 0 ) ); + + // No need to search any more. + //break; + } + } + } + } + + @SuppressWarnings("unchecked") + Enumeration oids = certificateRequestExtensions.oids(); + while (oids.hasMoreElements()) + { + DERObjectIdentifier oid = oids.nextElement(); + X509Extension ex = certificateRequestExtensions.getExtension(oid); + + v1CertGen.addExtension(oid, ex.isCritical(), X509Extension.convertValueToObject(ex)); + } + + return v1CertGen.generate((PrivateKey)signerCert.getSignerKey(), CryptoExtensions.getJCEProviderName()); + */ + return null; + } + + private static CertCreateFields createNewCA(CertCreateFields fields, KeyPair keyPair) throws Exception + { + StringBuilder dnBuilder = new StringBuilder(); + + String altName = ""; + + // create the DN + if (fields.getAttributes().containsKey("EMAILADDRESS")) + { + dnBuilder.append("E=").append(fields.getAttributes().get("EMAILADDRESS")).append(", "); + altName = fields.getAttributes().get("EMAILADDRESS").toString(); + } + + if (fields.getAttributes().containsKey("CN")) + dnBuilder.append("CN=").append(fields.getAttributes().get("CN")).append(", "); + + if (fields.getAttributes().containsKey("C")) + dnBuilder.append("C=").append(fields.getAttributes().get("C")).append(", "); + + if (fields.getAttributes().containsKey("ST")) + dnBuilder.append("ST=").append(fields.getAttributes().get("ST")).append(", "); + + if (fields.getAttributes().containsKey("L")) + dnBuilder.append("L=").append(fields.getAttributes().get("L")).append(", "); + + if (fields.getAttributes().containsKey("O")) + dnBuilder.append("O=").append(fields.getAttributes().get("O")).append(", "); + + String DN = dnBuilder.toString().trim(); + if (DN.endsWith(",")) + DN = DN.substring(0, DN.length() - 1); + + X509V3CertificateGenerator v1CertGen = new X509V3CertificateGenerator(); + + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + end.add(Calendar.DAY_OF_MONTH, fields.getExpDays()); + + v1CertGen.setSerialNumber(BigInteger.valueOf(generatePositiveRandom())); + v1CertGen.setIssuerDN(new X509Principal(DN)); + v1CertGen.setNotBefore(start.getTime()); + v1CertGen.setNotAfter(end.getTime()); + v1CertGen.setSubjectDN(new X509Principal(DN)); // issuer and subject are the same for a CA + v1CertGen.setPublicKey(keyPair.getPublic()); + v1CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + v1CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true)); + + + X509Certificate newCACert = v1CertGen.generate(keyPair.getPrivate(), "BC"); + + // validate the certificate + newCACert.verify(keyPair.getPublic()); + + // write the certificate the file system + writeCertAndKey(newCACert, keyPair.getPrivate(), fields); + + return fields; + } + + private static CertCreateFields createLeafCertificate(CertCreateFields fields, KeyPair keyPair) throws Exception + { + StringBuilder dnBuilder = new StringBuilder(); + + + /* + * Create the DN + */ + if (fields.getAttributes().containsKey("CN")) + dnBuilder.append("CN=").append(fields.getAttributes().get("CN")).append(", "); + + if (fields.getAttributes().containsKey("C")) + dnBuilder.append("C=").append(fields.getAttributes().get("C")).append(", "); + + if (fields.getAttributes().containsKey("ST")) + dnBuilder.append("ST=").append(fields.getAttributes().get("ST")).append(", "); + + if (fields.getAttributes().containsKey("L")) + dnBuilder.append("L=").append(fields.getAttributes().get("L")).append(", "); + + if (fields.getAttributes().containsKey("O")) + dnBuilder.append("O=").append(fields.getAttributes().get("O")).append(", "); + + String DN = dnBuilder.toString().trim(); + if (DN.endsWith(",")) + DN = DN.substring(0, DN.length() - 1); + + + /* + * Create the valid dates + */ + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + end.add(Calendar.DAY_OF_MONTH, fields.getExpDays()); + + /* + * General cert fields + */ + final X509v3CertificateBuilder v1CertGen = new X509v3CertificateBuilder(new X500Name(fields.getSignerCert().getIssuerDN().toString()), + BigInteger.valueOf(generatePositiveRandom()), start.getTime(), end.getTime(), new X500Name(DN), SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); + + /* + * Auth Key ID + */ + v1CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, + new AuthorityKeyIdentifierStructure(fields.getSignerCert().getPublicKey())); + + + /* + * Extended Key Usage + */ + final KeyPurposeId[] keyPurposes = {KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}; + v1CertGen.addExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(keyPurposes)); + + + /* + * Basic Constraint (critial) + */ + v1CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); + + + /* + * Key Usage (critial) + */ + int keyUsage = KeyUsage.keyEncipherment | KeyUsage.digitalSignature; + v1CertGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(keyUsage)); + + + /* + * Subject Alt Names + */ + final String domain = (String)fields.getAttributes().get("DOMAIN"); + + final GeneralName subjectAltName = new GeneralName(GeneralName.dNSName, domain); + final GeneralName ftAltName = new GeneralName(GeneralName.dNSName, "ftproxystream." + domain); + final GeneralName groupChatAltName = new GeneralName(GeneralName.dNSName, "groupchat." + domain); + + final GeneralName[] names = new GeneralName[] {subjectAltName, ftAltName, groupChatAltName};//, xmppServerAltName, xmppClientAltName}; + final DERSequence namesSeq = new DERSequence(names); + + v1CertGen.addExtension(X509Extensions.SubjectAlternativeName, false, namesSeq); + + /* + * CRL Distribution Point + */ + final String crlURL = (String)fields.getAttributes().get("CRL"); + + final GeneralNames distPointGenNames = new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, crlURL)); + final DistributionPointName distPointName = new DistributionPointName(distPointGenNames); + final DistributionPoint point = new DistributionPoint(distPointName, null, null); + final DistributionPoint[] points = new DistributionPoint[] {point}; + final CRLDistPoint distPoint = new CRLDistPoint(points); + v1CertGen.addExtension(X509Extensions.CRLDistributionPoints, false, distPoint); + + /* + * Sign the certificate + */ + ContentSigner signer = new JcaContentSignerBuilder( "SHA256WithRSAEncryption" ).build((PrivateKey)fields.getSignerKey()); + X509CertificateHolder certHolder = v1CertGen.build(signer); + final X509Certificate newClientCert = new JcaX509CertificateConverter().getCertificate( certHolder ); + + // validate the certificate + newClientCert.verify(fields.getSignerCert().getPublicKey()); + + // write the certificate the file system + writeCertAndKey(newClientCert, keyPair.getPrivate(), fields); + + return fields; + } + + private static void writeCertAndKey(X509Certificate cert, PrivateKey key, CertCreateFields fields) throws Exception + { + // write the cert + FileUtils.writeByteArrayToFile(fields.getNewCertFile(), cert.getEncoded()); + + if (fields.getNewPassword() == null || fields.getNewPassword().length == 0) + { + // no password... just write the file + FileUtils.writeByteArrayToFile(fields.getNewKeyFile(), key.getEncoded()); + } + else + { + // encypt it, then write it + + // prime the salts + byte[] salt = new byte[8]; + VMPCRandomGenerator ranGen = new VMPCRandomGenerator(); + ranGen.addSeedMaterial(new SecureRandom().nextLong()); + ranGen.nextBytes(salt); + + // create PBE parameters from salt and iteration count + PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20); + + + PBEKeySpec pbeKeySpec = new PBEKeySpec(fields.getNewPassword()); + SecretKey sKey = SecretKeyFactory.getInstance("PBEWithMD5AndDES", "BC").generateSecret(pbeKeySpec); + + // encrypt + Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES", "BC"); + cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null); + byte[] plain = (byte[])key.getEncoded(); + byte[] encrKey = cipher.doFinal(plain, 0, plain.length); + + // set the algorithm parameters + AlgorithmParameters pbeParams = AlgorithmParameters.getInstance(PBE_WITH_MD5_AND_DES_CBC_OID, Security.getProvider("SunJCE")); + + pbeParams.init(pbeSpec); + + // place in a EncryptedPrivateKeyInfo to encode to the proper file format + EncryptedPrivateKeyInfo info = new EncryptedPrivateKeyInfo(pbeParams,encrKey); + + // now write it to the file + FileUtils.writeByteArrayToFile(fields.getNewKeyFile(), info.getEncoded()); + } + + if (fields.getSignerCert() == null) + fields.setSignerCert(cert); + + if (fields.getSignerKey() == null) + fields.setSignerKey(key); + } +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/CertLoader.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertLoader.java new file mode 100644 index 0000000..d84cf70 --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/CertLoader.java @@ -0,0 +1,131 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + * Loads certificates and associated private key files from the file system. Passwords are optional, but must be presend + * if the private key file is encrypted. + * @author Greg Meyer + * + */ +///CLOVER:OFF +public class CertLoader +{ + @SuppressWarnings("deprecation") + public static CertCreateFields loadCertificate(File certFile, File keyFile, char[] password) throws Exception + { + byte[] certData = loadFileData(certFile); + byte[] keyData = loadFileData(keyFile); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + InputStream inStr = new ByteArrayInputStream(certData); + java.security.cert.Certificate holdCert = cf.generateCertificate(inStr); + X509Certificate cert = (X509Certificate)holdCert; + + IOUtils.closeQuietly(inStr); + + KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); + PKCS8EncodedKeySpec keysp = null; + if (password != null && password.length > 0) + { + EncryptedPrivateKeyInfo encInfo = new EncryptedPrivateKeyInfo(keyData); + PBEKeySpec keySpec = new PBEKeySpec(password); + String alg = encInfo.getAlgName(); + + SecretKeyFactory secFactory = SecretKeyFactory.getInstance(alg, "BC"); + SecretKey secKey = secFactory.generateSecret(keySpec); + keysp = encInfo.getKeySpec(secKey, "BC"); + } + else + { + keysp = new PKCS8EncodedKeySpec ( keyData ); + } + + PrivateKey privKey = kf.generatePrivate (keysp); + + Map attributes = getAttributes(cert); + + Calendar now = Calendar.getInstance(); + Calendar exp = Calendar.getInstance(); + exp.setTime(cert.getNotAfter()); + + long diff = exp.getTimeInMillis() - now.getTimeInMillis(); + long diffDays = diff / (24 * 60 * 60 * 1000); + + // TODO: get the key strength + int keyStr = 2048; // just hard coded + + CertCreateFields retVal = new CertCreateFields(attributes, certFile, keyFile, password, (int)diffDays, keyStr, cert, privKey); + + return retVal; + } + + private static Map getAttributes(X509Certificate cert) + { + Map retVal = new HashMap(); + + // for now just do a simple parse of the DN + Map oidMap = new HashMap(); + oidMap.put("1.2.840.113549.1.9.1", "EMAILADDRESS"); // OID for email address + String prinName = cert.getSubjectX500Principal().getName(X500Principal.RFC1779, oidMap); + + String[] attributes = prinName.split(","); + if (attributes != null) + for (String attr : attributes) + { + String[] nameValue = attr.split("="); + if (nameValue != null && nameValue.length == 2) + retVal.put(nameValue[0].trim(), nameValue[1].trim()); + } + + return retVal; + } + + private static byte[] loadFileData(File file) throws Exception + { + return FileUtils.readFileToByteArray(file); + } +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/CreatePKCS12.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/CreatePKCS12.java new file mode 100644 index 0000000..b922a99 --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/CreatePKCS12.java @@ -0,0 +1,293 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +/** + * Application class for creating PKCS12 files from X509 DER encoded files and PKCS8 DER encoded private key files. Unlike the Java keytool application, + * CreatePKCS12 creates pcks12 files without a passphrase and can accept encrypted private key files. + * + * @author Greg Meyer + */ +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; + +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +///CLOVER:OFF +public class CreatePKCS12 +{ + + private static File certFile; + private static File keyFile; + private static String password; + private static String p12Pass = ""; + private static File createFile; + + /** + * Main entry point when running as an application. Use the -help option for usage. + * @param argv Application arguments. + */ + public static void main (String[] argv) + { + if (argv.length == 0) + { + printUsage(); + System.exit(-1); + } + + // Check parameters + for (int i = 0; i < argv.length; i++) + { + String arg = argv[i]; + + // Options + if (!arg.startsWith("-")) + { + System.err.println("Error: Unexpected argument [" + arg + "]\n"); + printUsage(); + System.exit(-1); + } + else if (arg.equalsIgnoreCase("-cert")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing X509 certificate file."); + System.exit(-1); + } + + certFile = new File(argv[++i]); + + } + else if (arg.equals("-key")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing PCKS8 key file."); + System.exit(-1); + } + keyFile = new File(argv[++i]); + } + else if (arg.equals("-pass")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing key file password."); + System.exit(-1); + } + password = argv[++i]; + } + else if (arg.equals("-p12pass")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing p12 file passphrase."); + System.exit(-1); + } + p12Pass = argv[++i]; + } + else if (arg.equals("-out")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing output file."); + System.exit(-1); + } + createFile = new File(argv[++i]); + } + else if (arg.equals("-help")) + { + printUsage(); + System.exit(-1); + } + else + { + System.err.println("Error: Unknown argument " + arg + "\n"); + printUsage(); + System.exit(-1); + } + } + + if (validateParameters()) + if (create(certFile, keyFile, password, createFile) != null) + System.out.println("Created pcks12 file " + createFile.getAbsolutePath()); + + System.exit(0); + } + + /* + * Validate the parameters when run from the command line. + */ + private static boolean validateParameters() + { + return (certFile != null && keyFile != null); + } + + /** + * Creates a PCKS12 file from the certificate and key files. + * @param certFile The X509 DER encoded certificate file. + * @param keyFile The PCKS8 DER encoded private key file. + * @param password Option password for the private key file. This is required if the private key file is encrypted. Should be null or empty + * if the private key file is not encrypted. + * @param createFile Optional file descriptor for the output file of the pkcs12 file. If this is null, the file name is based on the + * certificate file name. + * @return File descriptor of the created pcks12 file. Null if an error occurred. + */ + @SuppressWarnings("deprecation") + public static File create(File certFile, File keyFile, String password, File createFile) + { + File pkcs12File = null; + + CreatePKCS12.certFile = certFile; + CreatePKCS12.keyFile = keyFile; + + FileOutputStream outStr = null; + InputStream inStr = null; + // load cert file + try + { + KeyStore localKeyStore = KeyStore.getInstance("PKCS12", "BC"); + localKeyStore.load(null, null); + + byte[] certData = loadFileData(certFile); + byte[] keyData = loadFileData(keyFile); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + inStr = new ByteArrayInputStream(certData); + java.security.cert.Certificate cert = cf.generateCertificate(inStr); + + IOUtils.closeQuietly(inStr); + + KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); + PKCS8EncodedKeySpec keysp = null; + if (password != null && !password.isEmpty()) + { + EncryptedPrivateKeyInfo encInfo = new EncryptedPrivateKeyInfo(keyData); + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); + String alg = encInfo.getAlgName(); + + SecretKeyFactory secFactory = SecretKeyFactory.getInstance(alg, "BC"); + SecretKey secKey = secFactory.generateSecret(keySpec); + keysp = encInfo.getKeySpec(secKey, "BC"); + } + else + { + keysp = new PKCS8EncodedKeySpec ( keyData ); + } + + Key privKey = kf.generatePrivate (keysp); + + char[] array = "".toCharArray(); + + localKeyStore.setKeyEntry("privCert", privKey, array, new java.security.cert.Certificate[] {cert}); + + pkcs12File = getPKCS12OutFile(createFile); + outStr = new FileOutputStream(pkcs12File); + localKeyStore.store(outStr, p12Pass.toCharArray()); + } + catch (Exception e) + { + System.err.println("Failed to create pcks12 file: " + e.getMessage()); + e.printStackTrace(System.err); + return null; + } + finally + { + IOUtils.closeQuietly(outStr); + IOUtils.closeQuietly(inStr); + } + + + return pkcs12File; + } + + /* + * Creates the output file descriptor and creates the new file on the file system. + */ + private static File getPKCS12OutFile(File createFile) throws Exception + { + if (createFile == null) + { + + String fileName = certFile.getName(); + + int index = fileName.lastIndexOf("."); + if (index > -1) + fileName = fileName.substring(0, index); + + fileName += ".p12"; + CreatePKCS12.createFile = createFile = new File(fileName); + } + + if (createFile.exists()) + createFile.delete(); + + createFile.createNewFile(); + + return createFile; + } + + /* + * Loads the raw data from the provided file to a byte array. + */ + private static byte[] loadFileData(File file) throws Exception + { + return FileUtils.readFileToByteArray(file); + } + + /* + * Prints the command line usage. + */ + private static void printUsage() + { + StringBuffer use = new StringBuffer(); + use.append("Usage:\n"); + use.append("java CreatePKCS12 (options)...\n\n"); + use.append("options:\n"); + use.append("-cert X509 File X509 DER formatted certificate file.\n"); + use.append("\n"); + use.append("-key Key File PCKS8 DER formatted private key file.\n"); + use.append("\n"); + use.append("-pass Passwd Optional passphrase for private key file.\n"); + use.append(" Default: \"\"\n\n"); + use.append("-p12pass P12 Passwd Optional passphrase for the newly created p12 file.\n"); + use.append(" Default: \"\"\n\n"); + use.append("-out Out File Optional output file name.\n"); + use.append(" Default: .p12\n\n"); + + System.err.println(use); + } + +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/LeafCertGenDialog.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/LeafCertGenDialog.java new file mode 100644 index 0000000..3901c1c --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/LeafCertGenDialog.java @@ -0,0 +1,276 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.GraphicsEnvironment; +import java.awt.Point; +import java.io.File; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +/** + * UI component for creating leaf certificates. + * @author Greg Meyer + * + */ +///CLOVER:OFF +class LeafCertGenDialog extends JDialog +{ + static final long serialVersionUID = -4500679031509430866L; + + private LeafGenPanel genPanel; + + public LeafCertGenDialog(Frame parent, CertCreateFields signer) + { + super(parent, "Certificate Creation", true); + setResizable(false); + setSize(700, 280); + setResizable(false); + + Point pt = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint(); + + this.setLocation(pt.x - (150), pt.y - (130)); + + + initUI(signer); + } + + + private void initUI(CertCreateFields signer) + { + getContentPane().setLayout(new BorderLayout()); + + genPanel = new LeafGenPanel(signer); + + + getContentPane().add(genPanel); + } + + private static class LeafGenPanel extends CAPanel + { + static final long serialVersionUID = -3829240137598058532L; + + private CertCreateFields signer; + private X509Certificate signerCert; + private PrivateKey signerKey; + + private TextEntryField crlField; + + public LeafGenPanel(CertCreateFields signer) + { + super(); + + this.signer = signer; + signerCert = signer.getSignerCert(); + signerKey = (PrivateKey)signer.getSignerKey(); + + allowedToSign.setVisible(true); + keyEnc.setVisible(true); + digitalSig.setVisible(true); + + prePopulateFields(); + } + + @Override + protected void initUI() + { + super.initUI(); + + JPanel fieldsParentPanel = (JPanel)fieldsPanel.getParent(); + fieldsParentPanel.remove(fieldsParentPanel); + + JPanel newFieldsPanel = new JPanel(); + newFieldsPanel.setLayout(new BorderLayout()); + newFieldsPanel.add(fieldsPanel, BorderLayout.CENTER); + + + crlField = new TextEntryField("CRL URL:"); + newFieldsPanel.add(crlField, BorderLayout.SOUTH); + + fieldsParentPanel.add(newFieldsPanel, BorderLayout.CENTER); + + emailField.setLabelText("Domain:"); + createCA.setVisible(false); + loadCA.setVisible(false); + certFileField.setVisible(false); + keyFileField.setVisible(false); + } + + private void prePopulateFields() + { + keyEnc.setSelected(true); + digitalSig.setSelected(true); + + // get the fields from the cert and pre populate the new cert + + if (signer.getAttributes().containsKey("C")) + this.countryField.setText(signer.getAttributes().get("C").toString()); + else + this.countryField.setText(""); + + if (signer.getAttributes().containsKey("ST")) + this.stateField.setText(signer.getAttributes().get("ST").toString()); + else + this.stateField.setText(""); + + if (signer.getAttributes().containsKey("L")) + this.locField.setText(signer.getAttributes().get("L").toString()); + else + this.locField.setText(""); + + if (signer.getAttributes().containsKey("O")) + this.orgField.setText(signer.getAttributes().get("O").toString()); + else + this.orgField.setText(""); + + this.expField.setValue(signer.getExpDays()); + + } + + protected void createCACert() + { + if (passField.getPassword().length == 0) + { + int selection = JOptionPane.showConfirmDialog(this, "The password field is empty. Do you wish to create a private key file without a password?", + "Empty Password", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (selection == JOptionPane.NO_OPTION) + return; + } + + // CN needs to be filled out + if (cnField.getText().isEmpty()) + { + JOptionPane.showMessageDialog(this,"Common Name (CN) must have a value.", + "Invalid Cert File", JOptionPane.ERROR_MESSAGE ); + return; + } + + // must also have an email address + if (emailField.getText().isEmpty()) + { + JOptionPane.showMessageDialog(this,"A domain name is required.", + "Invalid Domain Name", JOptionPane.ERROR_MESSAGE ); + return; + } + + // create the new files + certFileField.setFile(createNewFileName(FileType.CERT)); + keyFileField.setFile(createNewFileName(FileType.PRIVATE_KEY)); + + // check if the files already exist + if (certFileField.getFile().exists() || keyFileField.getFile().exists()) + { + int selection = JOptionPane.showConfirmDialog(this, "The certificate or key file already exists for this email address.\r\n" + + "This operation will overwrite the file. Continue?", + "Certificate Confilct", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (selection == JOptionPane.NO_OPTION) + return; + } + + // make sure there is a CRL + if (crlField.getText().trim().isEmpty()) + { + JOptionPane.showMessageDialog(this,"A CRL URL is required.", + "Invalid CRL", JOptionPane.ERROR_MESSAGE ); + return; + } + + // get the fields + Map attributes = new HashMap(); + attributes.put("CN", cnField.getText()); + if (!countryField.getText().isEmpty()) + attributes.put("C", countryField.getText()); + if (!stateField.getText().isEmpty()) + attributes.put("ST", stateField.getText()); + if (!locField.getText().isEmpty()) + attributes.put("L", locField.getText()); + if (!orgField.getText().isEmpty()) + attributes.put("O", orgField.getText()); + if (!emailField.getText().isEmpty()) + attributes.put("DOMAIN", emailField.getText()); + + attributes.put("CRL", crlField.getText()); + + int exp = Integer.parseInt(expField.getValue().toString()); + int keyStre = Integer.parseInt(keyStr.getValue().toString()); + + CertCreateFields createFields = new CertCreateFields(attributes, certFileField.getFile(), keyFileField.getFile(), + passField.getPassword(), exp, + keyStre, signerCert, signerKey); + + // create the cert + CertCreateFields retCert; + try + { + retCert = CertGenerator.createCertificate(createFields); + } + catch (Exception e) + { + JOptionPane.showMessageDialog(this,"An error occured creating the certificate: " + e.getMessage(), + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + if (retCert == null) + { + JOptionPane.showMessageDialog(this,"An error occured creating the certificate: unknown error", + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + // SUCCESS... create the pkcs12 file + File pcks12File = CreatePKCS12.create(retCert.getNewCertFile(), retCert.getNewKeyFile(), new String(passField.getPassword()), null); + + if (pcks12File == null) + { + JOptionPane.showMessageDialog(this,"An error occured creating the pkcs12 file: unknown error", + "Certificate Creation Error", JOptionPane.ERROR_MESSAGE); + + return; + } + + JOptionPane.showMessageDialog(this,"Domain certificate and private key created successfully:\r\n" + + retCert.getNewCertFile().getName() + "\r\n" + + retCert.getNewKeyFile().getName() + "\r\n" + + pcks12File.getName(), + "SUCCESS", JOptionPane.PLAIN_MESSAGE); + + // clear the fields + cnField.setText(""); + emailField.setText(""); + passField.clear(); + } + } +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/java/org/directtruststandards/timplus/tools/certgen/TIMPlusCertGenerator.java b/src/main/java/org/directtruststandards/timplus/tools/certgen/TIMPlusCertGenerator.java new file mode 100644 index 0000000..98135ab --- /dev/null +++ b/src/main/java/org/directtruststandards/timplus/tools/certgen/TIMPlusCertGenerator.java @@ -0,0 +1,100 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +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. Neither the name of the The NHIN Direct Project (nhindirect.org). +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.directtruststandards.timplus.tools.certgen; + +import java.awt.AWTEvent; +import java.awt.BorderLayout; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Point; +import java.security.Security; + +import javax.swing.ImageIcon; +import javax.swing.JFrame; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +/** + * Simple Swing application for generating self signed certificates (CAs) and leaf certificates for TIM+. The certificates generated are + * streamlined to simple uses cases: it does not support the numerous options supported using tools such as openssl. + * @author Greg Meyer + * @since 1.0 + */ +///CLOVER:OFF +@SpringBootApplication +public class TIMPlusCertGenerator extends JFrame +{ + static + { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * + */ + private static final long serialVersionUID = -1362014092984111324L; + private CAPanel certAuth; + + public static void main(String[] args) + { + + new SpringApplicationBuilder(TIMPlusCertGenerator.class) + .headless(false).run(args); + + TIMPlusCertGenerator hi = new TIMPlusCertGenerator(); + hi.setVisible(true); + } + + + public TIMPlusCertGenerator() + { + super("TIM+ Certificate Generator"); + setDefaultLookAndFeelDecorated(true); + setSize(700, 310); + setResizable(false); + + Point pt = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint(); + + this.setLocation(pt.x - (150), pt.y - (120)); + + enableEvents(AWTEvent.WINDOW_EVENT_MASK); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + initUI(); + } + + private void initUI() + { + final Image img = new ImageIcon(getClass().getResource("/images/cert.png")).getImage(); + this.setIconImage(img); + + + getContentPane().setLayout(new BorderLayout()); + + certAuth = new CAPanel(); + + getContentPane().add(certAuth); + } +} +///CLOVER:ON \ No newline at end of file diff --git a/src/main/resources/images/cert.png b/src/main/resources/images/cert.png new file mode 100644 index 0000000..b6b87d9 Binary files /dev/null and b/src/main/resources/images/cert.png differ