]> source.dussan.org Git - gitblit.git/commitdiff
Command-line tool to generate client certificate bundles for existing users
authorJames Moger <james.moger@gitblit.com>
Fri, 23 Nov 2012 15:46:35 +0000 (10:46 -0500)
committerJames Moger <james.moger@gitblit.com>
Fri, 23 Nov 2012 15:46:35 +0000 (10:46 -0500)
12 files changed:
build.xml
distrib/instructions.tmpl [new file with mode: 0644]
distrib/mail.tmpl [new file with mode: 0644]
distrib/makeclientcertificate.cmd [new file with mode: 0644]
distrib/makeclientcertificate.sh [new file with mode: 0644]
src/com/gitblit/ConfigUserService.java
src/com/gitblit/MailExecutor.java
src/com/gitblit/authority/MakeClientCertificate.java [new file with mode: 0644]
src/com/gitblit/authority/NewCertificateConfig.java [new file with mode: 0644]
src/com/gitblit/authority/UserCertificateConfig.java [new file with mode: 0644]
src/com/gitblit/authority/UserCertificateModel.java [new file with mode: 0644]
src/com/gitblit/models/UserModel.java

index 4e735195dd042ea74940b84305a8851780bfa352..7c5e8cc5a98608da92fb566694f43667aa205373 100644 (file)
--- a/build.xml
+++ b/build.xml
                                <exclude name="federation.properties" />\r
                                <exclude name="openshift.mkd" />\r
                                <exclude name="authority.conf" />\r
+                               <exclude name="*.tmpl" />\r
                        </fileset>\r
                        <fileset dir="${basedir}">\r
                                <include name="LICENSE" />\r
                <mkdir dir="${project.deploy.dir}/certs"/>\r
                <copy todir="${project.deploy.dir}/certs">\r
                        <fileset dir="${basedir}/distrib">\r
+                               <include name="*.tmpl" />\r
                                <include name="authority.conf" />\r
                        </fileset>\r
                </copy>\r
diff --git a/distrib/instructions.tmpl b/distrib/instructions.tmpl
new file mode 100644 (file)
index 0000000..11ea78f
--- /dev/null
@@ -0,0 +1,123 @@
+********************************************************************************\r
+ Gitblit SSL Client Certificate for $serverHostname\r
+********************************************************************************\r
+\r
+ Hello $userDisplayname,\r
+\r
+ Your private key, public certificate, and the Gitblit Certificate Authority \r
+ certificate for $serverHostname are stored in $username.p12, a PKCS#12 certificate\r
+ store[1], and also in $username.pem, a PEM certificate store.\r
+\r
+ Both of these certificate stores are password-protected. \r
+ Password Hint: $storePasswordHint\r
+\r
+\r
+Git (All) Installation Instructions\r
+=============================================\r
+\r
+ The provided PEM file can be directly used by your git client.\r
\r
+    git config [--global] http.sslCert path/to/$username.pem\r
+    \r
+ The supplied PEM file is password-protected and you may be prompted for your\r
+ password multiple times during an exchange with Gitblit.  If you desire a\r
+ password-less git client workflow then you will need to decrypt and export your\r
+ private key with OpenSSL[2] and then update your git config to use that key.\r
\r
+    openssl rsa -in path/to/$username.pem -out path/to/$username.key    \r
+    git config [--global] http.sslKey path/to/$username.key\r
+\r
+ Obviously, you should protect access to any decrypted private key.\r
\r
+ NOTE:\r
+ Some older git clients may have trouble using the PEM file without explicitly\r
+ extracting the private key.  This has been observed, for example, on Ubuntu 12.04\r
+ with git 1.7.9.5.\r
+\r
+\r
+Firefox (All) Installation Instructions\r
+=============================================\r
+\r
+ Firefox maintains it's own certificate store which is separate from the operating\r
+ system.\r
+\r
+ 1. Navigate to Options->Advanced->Encryption\r
+ 2. Click "View Certificates"\r
+ 3. Switch to the "Your Certificates" tab\r
+ 4. Click "Import..."\r
+ 5. Navigate your filesystem and select $username.p12\r
+ 6. At the password prompt enter the certificate store password\r
+    You have now imported your private key, public certificate, and the CA certificate\r
+    but now we must manually set the trust settings of the CA certificate.\r
+ 7. Switch to the "Authorities" tab\r
+ 8. Scroll down and find "Gitblit-> Gitblit Certificate Authority"\r
+ 9. Select it and click "Edit Trust..."\r
+ 10. Check "This certificate can identify websites" and click OK.\r
+\r
+\r
+Chrome/IE (Windows) Installation Instructions\r
+=============================================\r
+\r
+ On Windows, Chrome and IE share their certificate store so configuring one will\r
+ automatically apply for both.\r
+\r
+ IE\r
+ ------------------------------------\r
+ 1. Navigate to Internet Options->Content\r
+ 2. Click the "Certificates" button\r
+\r
+ Chrome\r
+ ------------------------------------\r
+ 1. Navigate to Settings->Show Advanced Settings->HTTP/SSL\r
+ 2. Click the "Manage Certificates..." button\r
+\r
+ Both (Windows)\r
+ ------------------------------------\r
+ 3. Switch to the "Personal" tab\r
+ 4. Click the "Import..." button\r
+ 5. Follow the Import Wizard instructions.\r
+    You will need to change the selected file filter when navigating to $username.p12\r
+ 6. At the password prompt enter the certificate store password\r
+ 7. Because both your personal certificate and the CA certifcate are stored in \r
+    $username.p12, you must choose "Automatically select the certificate store based on the type of certificate".\r
+    If you choose the default you will not install the CA certificate.\r
+\r
+\r
+Chrome (Linux) Installation Instructions\r
+=============================================\r
\r
+ On Linux, Chrome maintains it's own certificate store.\r
\r
+ 1. Navigate to Settings->Show Advanced Settings->HTTP/SSL\r
+ 2. Click the "Manage Certificates..." button\r
+ 3. Navigate your filesystem and select $username.p12\r
+ 4. At the password prompt enter the certificate store password\r
+    You have now imported your private key, public certificate, and the CA certificate\r
+    but now we must manually set the trust settings of the CA certificate.\r
+ 5. Switch to the "Authorities" tab\r
+ 6. Scroll down and find "Gitblit-> Gitblit Certificate Authority"\r
+ 7. Select it and click "Edit Trust..."\r
+ 8. Check "This certificate can identify websites" and click OK.\r
+\r
+\r
+Chrome/Safari (Mac OS X) Installation Instructions\r
+=============================================\r
+\r
+On Mac OS X, Chrome and Safari both use Keychain Access to store certificates\r
+so configuring one will automatically apply for both.\r
+\r
+ 1. Double-click $username.pem\r
+ 2. At the password prompt enter the certificate store password\r
+    You have now imported your private key, public certificate, and the CA certificate\r
+    but now we must manually set the trust settings of the CA certificate.\r
+ 3. Find the Gitblit Certificate Authority certificate, it should have a red\r
+    indicator meaning untrusted, and double-click it.\r
+ 4. Open the "Trust" disclosure triangle and change "When using this certificate"\r
+    to "Always Trust".\r
+ 5. Close the certificate view and enter your system password to save the changes\r
+    to your keychain. \r
+  \r
+    \r
+[1] PKCS#12 is one of the standard container formats for sharing private keys and\r
+    public certificates.\r
+[2] http://www.openssl.org\r
diff --git a/distrib/mail.tmpl b/distrib/mail.tmpl
new file mode 100644 (file)
index 0000000..463e124
--- /dev/null
@@ -0,0 +1,7 @@
+ Hello $userDisplayname,\r
+\r
+ Your private key, public certificate, and the Gitblit Certificate Authority \r
+ certificate for $serverHostname are bundled together in the attached zip file.\r
+\r
+ There are also setup/installation instructions included in the zip for Git and\r
+ several major browsers to get you started.
\ No newline at end of file
diff --git a/distrib/makeclientcertificate.cmd b/distrib/makeclientcertificate.cmd
new file mode 100644 (file)
index 0000000..7294142
--- /dev/null
@@ -0,0 +1 @@
+@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.authority.MakeClientCertificate\r
diff --git a/distrib/makeclientcertificate.sh b/distrib/makeclientcertificate.sh
new file mode 100644 (file)
index 0000000..76a195e
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+java -cp gitblit.jar:$PWD/ext/* com.gitblit.authority.MakeClientCertificate
index 9ad805b674d2636572761f340eff858f60f60ca2..068bbe3a8d595a2ad98de317d9cd7237d9bf720c 100644 (file)
@@ -66,6 +66,16 @@ public class ConfigUserService implements IUserService {
        \r
        private static final String EMAILADDRESS = "emailAddress";\r
        \r
+       private static final String ORGANIZATIONALUNIT = "organizationalUnit";\r
+       \r
+       private static final String ORGANIZATION = "organization";\r
+       \r
+       private static final String LOCALITY = "locality";\r
+       \r
+       private static final String STATEPROVINCE = "stateProvince";\r
+       \r
+       private static final String COUNTRYCODE = "countryCode";\r
+       \r
        private static final String COOKIE = "cookie";\r
 \r
        private static final String REPOSITORY = "repository";\r
@@ -817,6 +827,21 @@ public class ConfigUserService implements IUserService {
                        if (!StringUtils.isEmpty(model.emailAddress)) {\r
                                config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);\r
                        }\r
+                       if (!StringUtils.isEmpty(model.organizationalUnit)) {\r
+                               config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);\r
+                       }\r
+                       if (!StringUtils.isEmpty(model.organization)) {\r
+                               config.setString(USER, model.username, ORGANIZATION, model.organization);\r
+                       }\r
+                       if (!StringUtils.isEmpty(model.locality)) {\r
+                               config.setString(USER, model.username, LOCALITY, model.locality);\r
+                       }\r
+                       if (!StringUtils.isEmpty(model.stateProvince)) {\r
+                               config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);\r
+                       }\r
+                       if (!StringUtils.isEmpty(model.countryCode)) {\r
+                               config.setString(USER, model.username, COUNTRYCODE, model.countryCode);\r
+                       }\r
 \r
                        // user roles\r
                        List<String> roles = new ArrayList<String>();\r
@@ -964,6 +989,11 @@ public class ConfigUserService implements IUserService {
                                        user.password = config.getString(USER, username, PASSWORD);                                     \r
                                        user.displayName = config.getString(USER, username, DISPLAYNAME);\r
                                        user.emailAddress = config.getString(USER, username, EMAILADDRESS);\r
+                                       user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);\r
+                                       user.organization = config.getString(USER, username, ORGANIZATION);\r
+                                       user.locality = config.getString(USER, username, LOCALITY);\r
+                                       user.stateProvince = config.getString(USER, username, STATEPROVINCE);\r
+                                       user.countryCode = config.getString(USER, username, COUNTRYCODE);\r
                                        user.cookie = config.getString(USER, username, COOKIE);\r
                                        if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {\r
                                                user.cookie = StringUtils.getSHA1(user.username + user.password);\r
index ea19edbfeebd7379f03b121ef0b0f3db43d6d299..9001e836d1e6cee72fe5b8472d3b1dd177408e02 100644 (file)
@@ -231,4 +231,8 @@ public class MailExecutor implements Runnable {
                        }\r
                }\r
        }\r
+       \r
+       public void sendNow(Message message) throws Exception {\r
+               Transport.send(message);\r
+       }\r
 }\r
diff --git a/src/com/gitblit/authority/MakeClientCertificate.java b/src/com/gitblit/authority/MakeClientCertificate.java
new file mode 100644 (file)
index 0000000..5829fc1
--- /dev/null
@@ -0,0 +1,230 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.authority;\r
+\r
+import java.io.File;\r
+import java.text.MessageFormat;\r
+import java.util.Date;\r
+\r
+import javax.activation.DataHandler;\r
+import javax.activation.FileDataSource;\r
+import javax.mail.Message;\r
+import javax.mail.Multipart;\r
+import javax.mail.internet.MimeBodyPart;\r
+import javax.mail.internet.MimeMultipart;\r
+\r
+import org.eclipse.jgit.storage.file.FileBasedConfig;\r
+import org.eclipse.jgit.util.FS;\r
+\r
+import com.beust.jcommander.JCommander;\r
+import com.beust.jcommander.Parameter;\r
+import com.beust.jcommander.ParameterException;\r
+import com.beust.jcommander.Parameters;\r
+import com.gitblit.ConfigUserService;\r
+import com.gitblit.Constants;\r
+import com.gitblit.FileSettings;\r
+import com.gitblit.IUserService;\r
+import com.gitblit.Keys;\r
+import com.gitblit.MailExecutor;\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.utils.TimeUtils;\r
+import com.gitblit.utils.X509Utils;\r
+import com.gitblit.utils.X509Utils.X509Metadata;\r
+\r
+/**\r
+ * Utility class to generate self-signed certificates.\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+public class MakeClientCertificate {\r
+\r
+       public static void main(String... args) throws Exception {\r
+               Params params = new Params();\r
+               JCommander jc = new JCommander(params);\r
+               try {\r
+                       jc.parse(args);\r
+               } catch (ParameterException t) {\r
+                       System.err.println(t.getMessage());\r
+                       jc.usage();\r
+                       System.exit(-1);\r
+               }\r
+\r
+               // Load the user list\r
+               String us = Params.FILESETTINGS.getString(Keys.realm.userService, "users.conf");\r
+               String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();\r
+               IUserService service = null;\r
+               if (!ext.equals("conf") && !ext.equals("properties")) {\r
+                       if (us.equals("com.gitblit.LdapUserService")) {\r
+                               us = Params.FILESETTINGS.getString(Keys.realm.ldap.backingUserService, "users.conf");           \r
+                       } else if (us.equals("com.gitblit.LdapUserService")) {\r
+                               us = Params.FILESETTINGS.getString(Keys.realm.redmine.backingUserService, "users.conf");\r
+                       }\r
+               }\r
+\r
+               if (us.endsWith(".conf")) {\r
+                       service = new ConfigUserService(new File(us));\r
+               } else {\r
+                       throw new RuntimeException("Unsupported user service: " + us);\r
+               }\r
+               \r
+               // Confirm the user exists\r
+               UserModel user = service.getUserModel(params.username);\r
+               if (user == null) {\r
+                       System.out.println(MessageFormat.format("Failed to find user \"{0}\" in {1}", params.username, us));\r
+                       System.exit(-1);\r
+               }\r
+                               \r
+               File folder = new File(System.getProperty("user.dir"));\r
+               X509Metadata serverMetadata = new X509Metadata("localhost", params.storePassword);              \r
+               X509Utils.prepareX509Infrastructure(serverMetadata, folder);\r
+               \r
+               File caStore = new File(folder, X509Utils.CA_KEY_STORE);\r
+               \r
+               X509Metadata clientMetadata = new X509Metadata(params.username, params.password);\r
+               clientMetadata.userDisplayname = user.getDisplayName();\r
+               clientMetadata.emailAddress = user.emailAddress;\r
+               clientMetadata.serverHostname = params.serverHostname;\r
+               clientMetadata.passwordHint = params.hint;\r
+               \r
+               UserCertificateModel ucm = null;\r
+               \r
+               // set default values from config file\r
+               File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);\r
+               FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());\r
+               if (certificatesConfigFile.exists()) {\r
+                       config.load();\r
+                       NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);\r
+                       certificateConfig.update(clientMetadata);\r
+                       \r
+                       ucm = UserCertificateConfig.KEY.parse(config).getUserCertificateModel(params.username);\r
+               }\r
+               \r
+               // set user's specified OID values\r
+               if (!StringUtils.isEmpty(user.organizationalUnit)) {\r
+                       clientMetadata.oids.put("OU", user.organizationalUnit);\r
+               }\r
+               if (!StringUtils.isEmpty(user.organization)) {\r
+                       clientMetadata.oids.put("O", user.organization);\r
+               }\r
+               if (!StringUtils.isEmpty(user.locality)) {\r
+                       clientMetadata.oids.put("L", user.locality);\r
+               }\r
+               if (!StringUtils.isEmpty(user.stateProvince)) {\r
+                       clientMetadata.oids.put("ST", user.stateProvince);\r
+               }\r
+               if (!StringUtils.isEmpty(user.countryCode)) {\r
+                       clientMetadata.oids.put("C", user.countryCode);\r
+               }\r
+\r
+               if (params.duration > 0) {\r
+                       // overriding duration from command-line parameter\r
+                       clientMetadata.notAfter = new Date(System.currentTimeMillis() + TimeUtils.ONEDAY * params.duration);\r
+               }\r
+\r
+               // generate zip bundle\r
+               File zip = X509Utils.newClientBundle(clientMetadata, caStore, params.storePassword);            \r
+               \r
+               String indent = "  ";\r
+               System.out.println(MessageFormat.format("Client certificate bundle generated for {0}", params.username));\r
+               System.out.print(indent);\r
+               System.out.println(zip);\r
+               \r
+               // update certificates.conf\r
+               if (ucm == null) {\r
+                       ucm = new UserCertificateModel(new UserModel(params.username));\r
+               }\r
+\r
+               // save latest expiration date\r
+               if (ucm.expires == null || clientMetadata.notAfter.after(ucm.expires)) {\r
+                       ucm.expires = clientMetadata.notAfter;\r
+               }\r
+               ucm.update(config);\r
+               config.save();\r
+               \r
+               if (params.sendEmail) {\r
+                       if (StringUtils.isEmpty(user.emailAddress)) {\r
+                               System.out.print(indent);\r
+                               System.out.println(MessageFormat.format("User \"{0}\" does not have an email address.", user.username));\r
+                       } else {\r
+                               // send email\r
+                               MailExecutor mail = new MailExecutor(Params.FILESETTINGS);\r
+                               if (mail.isReady()) {\r
+                                       Message message = mail.createMessage(user.emailAddress);\r
+                                       message.setSubject("Your Gitblit client certificate for " + clientMetadata.serverHostname);\r
+\r
+                                       // body of email\r
+                                       String body = X509Utils.processTemplate(new File(caStore.getParentFile(), "mail.tmpl"), clientMetadata);\r
+                                       if (StringUtils.isEmpty(body)) {\r
+                                               body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());\r
+                                       }\r
+                                       Multipart mp = new MimeMultipart();\r
+                                       MimeBodyPart messagePart = new MimeBodyPart();\r
+                                       messagePart.setText(body);\r
+                                       mp.addBodyPart(messagePart);\r
+\r
+                                       // attach zip\r
+                                       MimeBodyPart filePart = new MimeBodyPart();\r
+                                       FileDataSource fds = new FileDataSource(zip);\r
+                                       filePart.setDataHandler(new DataHandler(fds));\r
+                                       filePart.setFileName(fds.getName());\r
+                                       mp.addBodyPart(filePart);\r
+\r
+                                       message.setContent(mp);\r
+\r
+                                       mail.sendNow(message);\r
+                                       System.out.println();\r
+                                       System.out.println("Mail sent.");\r
+                               } else {\r
+                                       System.out.print(indent);\r
+                                       System.out.println("Mail server is not properly configured.  Can not send email.");\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * JCommander Parameters class for MakeClientCertificate.\r
+        */\r
+       @Parameters(separators = " ")\r
+       private static class Params {\r
+\r
+               private static final FileSettings FILESETTINGS = new FileSettings(Constants.PROPERTIES_FILE);\r
+\r
+               @Parameter(names = { "--username" }, description = "Username for certificate (CN)", required = true)\r
+               public String username;\r
+\r
+               @Parameter(names = { "--password" }, description = "Password to secure user's certificate (<=7 chars unless JCE Unlimited Strength installed)", required = true)\r
+               public String password;\r
+\r
+               @Parameter(names = { "--hint" }, description = "Hint for password", required = true)\r
+               public String hint;\r
+               \r
+               @Parameter(names = "--duration", description = "Number of days from now until the certificate expires")\r
+               public int duration = 0;\r
+\r
+               @Parameter(names = "--storePassword", description = "Password for CA keystore.")\r
+               public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, "");\r
+               \r
+               @Parameter(names = "--server", description = "Hostname or server identity")\r
+               public String serverHostname = Params.FILESETTINGS.getString(Keys.web.siteName, "localhost");\r
+\r
+               @Parameter(names = "--sendEmail", description = "Send an email to the user with their bundle")\r
+               public boolean sendEmail;\r
+               \r
+       }\r
+}\r
diff --git a/src/com/gitblit/authority/NewCertificateConfig.java b/src/com/gitblit/authority/NewCertificateConfig.java
new file mode 100644 (file)
index 0000000..e4db130
--- /dev/null
@@ -0,0 +1,72 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.authority;\r
+\r
+import java.util.Date;\r
+\r
+import org.eclipse.jgit.lib.Config;\r
+import org.eclipse.jgit.lib.Config.SectionParser;\r
+\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.utils.TimeUtils;\r
+import com.gitblit.utils.X509Utils.X509Metadata;\r
+\r
+/**\r
+ * Certificate config file parser.\r
+ *  \r
+ * @author James Moger\r
+ */\r
+public class NewCertificateConfig {\r
+               public static final SectionParser<NewCertificateConfig> KEY = new SectionParser<NewCertificateConfig>() {\r
+                       public NewCertificateConfig parse(final Config cfg) {\r
+                               return new NewCertificateConfig(cfg);\r
+                       }\r
+               };\r
+\r
+               public final String OU;\r
+               public final String O;\r
+               public final String L;\r
+               public final String ST;\r
+               public final String C;\r
+               \r
+               public final int duration;\r
+               \r
+               private NewCertificateConfig(final Config c) {\r
+                       duration = c.getInt("new",  null, "duration", 0);\r
+                       OU = c.getString("new", null, "organizationalUnit");\r
+                       O = c.getString("new", null, "organization");\r
+                       L = c.getString("new", null, "locality");\r
+                       ST = c.getString("new", null, "stateProvince");\r
+                       C = c.getString("new", null, "countryCode");                    \r
+               }\r
+               \r
+               public void update(X509Metadata metadata) {\r
+                       update(metadata, "OU", OU);\r
+                       update(metadata, "O", O);\r
+                       update(metadata, "L", L);\r
+                       update(metadata, "ST", ST);\r
+                       update(metadata, "C", C);\r
+                       if (duration > 0) {\r
+                               metadata.notAfter = new Date(System.currentTimeMillis() + duration*TimeUtils.ONEDAY);\r
+                       }\r
+               }\r
+               \r
+               private void update(X509Metadata metadata, String oid, String value) {\r
+                       if (!StringUtils.isEmpty(value)) {\r
+                               metadata.oids.put(oid, value);\r
+                       }\r
+               }\r
+       }
\ No newline at end of file
diff --git a/src/com/gitblit/authority/UserCertificateConfig.java b/src/com/gitblit/authority/UserCertificateConfig.java
new file mode 100644 (file)
index 0000000..47132a0
--- /dev/null
@@ -0,0 +1,69 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.authority;\r
+\r
+import java.text.ParseException;\r
+import java.text.SimpleDateFormat;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import org.eclipse.jgit.lib.Config;\r
+import org.eclipse.jgit.lib.Config.SectionParser;\r
+import org.slf4j.LoggerFactory;\r
+\r
+import com.gitblit.Constants;\r
+import com.gitblit.models.UserModel;\r
+\r
+/**\r
+ * User certificate config section parser.\r
+ * \r
+ * @author James Moger\r
+ */\r
+public class UserCertificateConfig {\r
+       public static final SectionParser<UserCertificateConfig> KEY = new SectionParser<UserCertificateConfig>() {\r
+               public UserCertificateConfig parse(final Config cfg) {                  \r
+                       return new UserCertificateConfig(cfg);\r
+               }\r
+       };\r
+       \r
+       public final List<UserCertificateModel> list;\r
+\r
+       private UserCertificateConfig(final Config c) {\r
+               SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);\r
+               list = new ArrayList<UserCertificateModel>(); \r
+               for (String username : c.getSubsections("user")) {\r
+                       UserCertificateModel uc = new UserCertificateModel(new UserModel(username));\r
+                       try {\r
+                               uc.expires = df.parse(c.getString("user", username, "expires"));\r
+                       } catch (ParseException e) {\r
+                               LoggerFactory.getLogger(UserCertificateConfig.class).error("Failed to parse date!", e);\r
+                       }\r
+                       uc.notes = c.getString("user", username, "notes");\r
+                       uc.revoked = new ArrayList<String>(Arrays.asList(c.getStringList("user", username, "revoked")));                        \r
+                       list.add(uc);\r
+               }\r
+       }\r
+       \r
+       public UserCertificateModel getUserCertificateModel(String username) {\r
+               for (UserCertificateModel ucm : list) {\r
+                       if (ucm.user.username.equalsIgnoreCase(username)) {\r
+                               return ucm;\r
+                       }\r
+               }\r
+               return null;\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/com/gitblit/authority/UserCertificateModel.java b/src/com/gitblit/authority/UserCertificateModel.java
new file mode 100644 (file)
index 0000000..f5d71bb
--- /dev/null
@@ -0,0 +1,134 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.authority;\r
+\r
+import java.math.BigInteger;\r
+import java.security.cert.X509Certificate;\r
+import java.text.SimpleDateFormat;\r
+import java.util.ArrayList;\r
+import java.util.Date;\r
+import java.util.List;\r
+\r
+import org.eclipse.jgit.lib.Config;\r
+\r
+import com.gitblit.Constants;\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.ArrayUtils;\r
+import com.gitblit.utils.TimeUtils;\r
+import com.gitblit.utils.X509Utils.RevocationReason;\r
+\r
+public class UserCertificateModel implements Comparable<UserCertificateModel> {\r
+               public UserModel user;\r
+               public Date expires;\r
+               public List<X509Certificate> certs;\r
+               public List<String> revoked;\r
+               public String notes;\r
+\r
+               public UserCertificateModel(UserModel user) {\r
+                       this.user = user;\r
+               }\r
+               \r
+               public void update(Config config) {\r
+                       if (expires != null) {\r
+                               SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);\r
+                               config.setString("user", user.username, "expires", df.format(expires));\r
+                       }\r
+                       if (notes != null) {\r
+                               config.setString("user", user.username, "notes", notes);\r
+                       }\r
+                       if (!ArrayUtils.isEmpty(revoked)) {\r
+                               config.setStringList("user", user.username, "revoked", revoked);\r
+                       }\r
+               }\r
+\r
+               @Override\r
+               public int compareTo(UserCertificateModel o) {\r
+                       return user.compareTo(o.user);\r
+               }\r
+               \r
+               public void revoke(BigInteger serial, RevocationReason reason) {\r
+                       if (revoked == null) {\r
+                               revoked = new ArrayList<String>();\r
+                       }\r
+                       revoked.add(serial.toString() + ":" + reason.ordinal());\r
+               }\r
+               \r
+               public boolean isRevoked(BigInteger serial) {\r
+                       return isRevoked(serial.toString());\r
+               }\r
+\r
+               public boolean isRevoked(String serial) {\r
+                       if (ArrayUtils.isEmpty(revoked)) {\r
+                               return false;\r
+                       }\r
+                       String sn = serial + ":";\r
+                       for (String s : revoked) {\r
+                               if (s.startsWith(sn)) {\r
+                                       return true;\r
+                               }\r
+                       }\r
+                       return false;\r
+               }\r
+               \r
+               public RevocationReason getRevocationReason(BigInteger serial) {\r
+                       try {\r
+                               String sn = serial + ":";\r
+                               for (String s : revoked) {\r
+                                       if (s.startsWith(sn)) {\r
+                                               String r = s.substring(sn.length());\r
+                                               int i = Integer.parseInt(r);\r
+                                               return RevocationReason.values()[i];\r
+                                       }\r
+                               }\r
+                       } catch (Exception e) {\r
+                       }\r
+                       return RevocationReason.unspecified;\r
+               }\r
+               \r
+               public CertificateStatus getStatus() {\r
+                       if (expires == null) {\r
+                               return CertificateStatus.unknown;\r
+                       } else if (isExpired(expires)) {\r
+                               return CertificateStatus.expired;\r
+                       } else if (isExpiring(expires)) {\r
+                               return CertificateStatus.expiring;\r
+                       }\r
+                       return CertificateStatus.ok;\r
+               }\r
+\r
+               public boolean hasExpired() {\r
+                       return expires != null && isExpiring(expires);\r
+               }\r
+\r
+               public CertificateStatus getStatus(X509Certificate cert) {\r
+                       if (isRevoked(cert.getSerialNumber())) {\r
+                               return CertificateStatus.revoked;\r
+                       } else if (isExpired(cert.getNotAfter())) {\r
+                               return CertificateStatus.expired;\r
+                       } else if (isExpiring(cert.getNotAfter())) {\r
+                               return CertificateStatus.expiring;\r
+                       }\r
+                       return CertificateStatus.ok;\r
+               }\r
+               \r
+               private boolean isExpiring(Date date) {\r
+                       return (date.getTime() - System.currentTimeMillis()) <= TimeUtils.ONEDAY * 30;\r
+               }\r
+               \r
+               private boolean isExpired(Date date) {\r
+                       return date.getTime() < System.currentTimeMillis();\r
+               }\r
+       }
\ No newline at end of file
index 1159905dc3da40674f5cae6d8a4aa434538b71fa..bd40985f8c7ed9d5574160924cd6804b2f6e819c 100644 (file)
@@ -56,6 +56,11 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
        public String cookie;\r
        public String displayName;\r
        public String emailAddress;\r
+       public String organizationalUnit;\r
+       public String organization;\r
+       public String locality;\r
+       public String stateProvince;\r
+       public String countryCode;\r
        public boolean canAdmin;\r
        public boolean canFork;\r
        public boolean canCreate;\r