summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2012-11-23 10:46:35 -0500
committerJames Moger <james.moger@gitblit.com>2012-11-23 10:46:35 -0500
commite8c417f4e63f84ac6e14f6d5540dcb1f0f9862fc (patch)
tree6d8982dc4f541f08195644cf3d964d2fa6347d4e /src
parentd8a0f1e6e9d2420be31200cf0554336e84eec843 (diff)
downloadgitblit-e8c417f4e63f84ac6e14f6d5540dcb1f0f9862fc.tar.gz
gitblit-e8c417f4e63f84ac6e14f6d5540dcb1f0f9862fc.zip
Command-line tool to generate client certificate bundles for existing users
Diffstat (limited to 'src')
-rw-r--r--src/com/gitblit/ConfigUserService.java30
-rw-r--r--src/com/gitblit/MailExecutor.java4
-rw-r--r--src/com/gitblit/authority/MakeClientCertificate.java230
-rw-r--r--src/com/gitblit/authority/NewCertificateConfig.java72
-rw-r--r--src/com/gitblit/authority/UserCertificateConfig.java69
-rw-r--r--src/com/gitblit/authority/UserCertificateModel.java134
-rw-r--r--src/com/gitblit/models/UserModel.java5
7 files changed, 544 insertions, 0 deletions
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java
index 9ad805b6..068bbe3a 100644
--- a/src/com/gitblit/ConfigUserService.java
+++ b/src/com/gitblit/ConfigUserService.java
@@ -66,6 +66,16 @@ public class ConfigUserService implements IUserService {
private static final String EMAILADDRESS = "emailAddress";
+ private static final String ORGANIZATIONALUNIT = "organizationalUnit";
+
+ private static final String ORGANIZATION = "organization";
+
+ private static final String LOCALITY = "locality";
+
+ private static final String STATEPROVINCE = "stateProvince";
+
+ private static final String COUNTRYCODE = "countryCode";
+
private static final String COOKIE = "cookie";
private static final String REPOSITORY = "repository";
@@ -817,6 +827,21 @@ public class ConfigUserService implements IUserService {
if (!StringUtils.isEmpty(model.emailAddress)) {
config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
}
+ if (!StringUtils.isEmpty(model.organizationalUnit)) {
+ config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
+ }
+ if (!StringUtils.isEmpty(model.organization)) {
+ config.setString(USER, model.username, ORGANIZATION, model.organization);
+ }
+ if (!StringUtils.isEmpty(model.locality)) {
+ config.setString(USER, model.username, LOCALITY, model.locality);
+ }
+ if (!StringUtils.isEmpty(model.stateProvince)) {
+ config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);
+ }
+ if (!StringUtils.isEmpty(model.countryCode)) {
+ config.setString(USER, model.username, COUNTRYCODE, model.countryCode);
+ }
// user roles
List<String> roles = new ArrayList<String>();
@@ -964,6 +989,11 @@ public class ConfigUserService implements IUserService {
user.password = config.getString(USER, username, PASSWORD);
user.displayName = config.getString(USER, username, DISPLAYNAME);
user.emailAddress = config.getString(USER, username, EMAILADDRESS);
+ user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
+ user.organization = config.getString(USER, username, ORGANIZATION);
+ user.locality = config.getString(USER, username, LOCALITY);
+ user.stateProvince = config.getString(USER, username, STATEPROVINCE);
+ user.countryCode = config.getString(USER, username, COUNTRYCODE);
user.cookie = config.getString(USER, username, COOKIE);
if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
user.cookie = StringUtils.getSHA1(user.username + user.password);
diff --git a/src/com/gitblit/MailExecutor.java b/src/com/gitblit/MailExecutor.java
index ea19edbf..9001e836 100644
--- a/src/com/gitblit/MailExecutor.java
+++ b/src/com/gitblit/MailExecutor.java
@@ -231,4 +231,8 @@ public class MailExecutor implements Runnable {
}
}
}
+
+ public void sendNow(Message message) throws Exception {
+ Transport.send(message);
+ }
}
diff --git a/src/com/gitblit/authority/MakeClientCertificate.java b/src/com/gitblit/authority/MakeClientCertificate.java
new file mode 100644
index 00000000..5829fc11
--- /dev/null
+++ b/src/com/gitblit/authority/MakeClientCertificate.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.authority;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.Date;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import com.gitblit.ConfigUserService;
+import com.gitblit.Constants;
+import com.gitblit.FileSettings;
+import com.gitblit.IUserService;
+import com.gitblit.Keys;
+import com.gitblit.MailExecutor;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+/**
+ * Utility class to generate self-signed certificates.
+ *
+ * @author James Moger
+ *
+ */
+public class MakeClientCertificate {
+
+ public static void main(String... args) throws Exception {
+ Params params = new Params();
+ JCommander jc = new JCommander(params);
+ try {
+ jc.parse(args);
+ } catch (ParameterException t) {
+ System.err.println(t.getMessage());
+ jc.usage();
+ System.exit(-1);
+ }
+
+ // Load the user list
+ String us = Params.FILESETTINGS.getString(Keys.realm.userService, "users.conf");
+ String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
+ IUserService service = null;
+ if (!ext.equals("conf") && !ext.equals("properties")) {
+ if (us.equals("com.gitblit.LdapUserService")) {
+ us = Params.FILESETTINGS.getString(Keys.realm.ldap.backingUserService, "users.conf");
+ } else if (us.equals("com.gitblit.LdapUserService")) {
+ us = Params.FILESETTINGS.getString(Keys.realm.redmine.backingUserService, "users.conf");
+ }
+ }
+
+ if (us.endsWith(".conf")) {
+ service = new ConfigUserService(new File(us));
+ } else {
+ throw new RuntimeException("Unsupported user service: " + us);
+ }
+
+ // Confirm the user exists
+ UserModel user = service.getUserModel(params.username);
+ if (user == null) {
+ System.out.println(MessageFormat.format("Failed to find user \"{0}\" in {1}", params.username, us));
+ System.exit(-1);
+ }
+
+ File folder = new File(System.getProperty("user.dir"));
+ X509Metadata serverMetadata = new X509Metadata("localhost", params.storePassword);
+ X509Utils.prepareX509Infrastructure(serverMetadata, folder);
+
+ File caStore = new File(folder, X509Utils.CA_KEY_STORE);
+
+ X509Metadata clientMetadata = new X509Metadata(params.username, params.password);
+ clientMetadata.userDisplayname = user.getDisplayName();
+ clientMetadata.emailAddress = user.emailAddress;
+ clientMetadata.serverHostname = params.serverHostname;
+ clientMetadata.passwordHint = params.hint;
+
+ UserCertificateModel ucm = null;
+
+ // set default values from config file
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ if (certificatesConfigFile.exists()) {
+ config.load();
+ NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
+ certificateConfig.update(clientMetadata);
+
+ ucm = UserCertificateConfig.KEY.parse(config).getUserCertificateModel(params.username);
+ }
+
+ // set user's specified OID values
+ if (!StringUtils.isEmpty(user.organizationalUnit)) {
+ clientMetadata.oids.put("OU", user.organizationalUnit);
+ }
+ if (!StringUtils.isEmpty(user.organization)) {
+ clientMetadata.oids.put("O", user.organization);
+ }
+ if (!StringUtils.isEmpty(user.locality)) {
+ clientMetadata.oids.put("L", user.locality);
+ }
+ if (!StringUtils.isEmpty(user.stateProvince)) {
+ clientMetadata.oids.put("ST", user.stateProvince);
+ }
+ if (!StringUtils.isEmpty(user.countryCode)) {
+ clientMetadata.oids.put("C", user.countryCode);
+ }
+
+ if (params.duration > 0) {
+ // overriding duration from command-line parameter
+ clientMetadata.notAfter = new Date(System.currentTimeMillis() + TimeUtils.ONEDAY * params.duration);
+ }
+
+ // generate zip bundle
+ File zip = X509Utils.newClientBundle(clientMetadata, caStore, params.storePassword);
+
+ String indent = " ";
+ System.out.println(MessageFormat.format("Client certificate bundle generated for {0}", params.username));
+ System.out.print(indent);
+ System.out.println(zip);
+
+ // update certificates.conf
+ if (ucm == null) {
+ ucm = new UserCertificateModel(new UserModel(params.username));
+ }
+
+ // save latest expiration date
+ if (ucm.expires == null || clientMetadata.notAfter.after(ucm.expires)) {
+ ucm.expires = clientMetadata.notAfter;
+ }
+ ucm.update(config);
+ config.save();
+
+ if (params.sendEmail) {
+ if (StringUtils.isEmpty(user.emailAddress)) {
+ System.out.print(indent);
+ System.out.println(MessageFormat.format("User \"{0}\" does not have an email address.", user.username));
+ } else {
+ // send email
+ MailExecutor mail = new MailExecutor(Params.FILESETTINGS);
+ if (mail.isReady()) {
+ Message message = mail.createMessage(user.emailAddress);
+ message.setSubject("Your Gitblit client certificate for " + clientMetadata.serverHostname);
+
+ // body of email
+ String body = X509Utils.processTemplate(new File(caStore.getParentFile(), "mail.tmpl"), clientMetadata);
+ if (StringUtils.isEmpty(body)) {
+ body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());
+ }
+ Multipart mp = new MimeMultipart();
+ MimeBodyPart messagePart = new MimeBodyPart();
+ messagePart.setText(body);
+ mp.addBodyPart(messagePart);
+
+ // attach zip
+ MimeBodyPart filePart = new MimeBodyPart();
+ FileDataSource fds = new FileDataSource(zip);
+ filePart.setDataHandler(new DataHandler(fds));
+ filePart.setFileName(fds.getName());
+ mp.addBodyPart(filePart);
+
+ message.setContent(mp);
+
+ mail.sendNow(message);
+ System.out.println();
+ System.out.println("Mail sent.");
+ } else {
+ System.out.print(indent);
+ System.out.println("Mail server is not properly configured. Can not send email.");
+ }
+ }
+ }
+ }
+
+ /**
+ * JCommander Parameters class for MakeClientCertificate.
+ */
+ @Parameters(separators = " ")
+ private static class Params {
+
+ private static final FileSettings FILESETTINGS = new FileSettings(Constants.PROPERTIES_FILE);
+
+ @Parameter(names = { "--username" }, description = "Username for certificate (CN)", required = true)
+ public String username;
+
+ @Parameter(names = { "--password" }, description = "Password to secure user's certificate (<=7 chars unless JCE Unlimited Strength installed)", required = true)
+ public String password;
+
+ @Parameter(names = { "--hint" }, description = "Hint for password", required = true)
+ public String hint;
+
+ @Parameter(names = "--duration", description = "Number of days from now until the certificate expires")
+ public int duration = 0;
+
+ @Parameter(names = "--storePassword", description = "Password for CA keystore.")
+ public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, "");
+
+ @Parameter(names = "--server", description = "Hostname or server identity")
+ public String serverHostname = Params.FILESETTINGS.getString(Keys.web.siteName, "localhost");
+
+ @Parameter(names = "--sendEmail", description = "Send an email to the user with their bundle")
+ public boolean sendEmail;
+
+ }
+}
diff --git a/src/com/gitblit/authority/NewCertificateConfig.java b/src/com/gitblit/authority/NewCertificateConfig.java
new file mode 100644
index 00000000..e4db130e
--- /dev/null
+++ b/src/com/gitblit/authority/NewCertificateConfig.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.authority;
+
+import java.util.Date;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+/**
+ * Certificate config file parser.
+ *
+ * @author James Moger
+ */
+public class NewCertificateConfig {
+ public static final SectionParser<NewCertificateConfig> KEY = new SectionParser<NewCertificateConfig>() {
+ public NewCertificateConfig parse(final Config cfg) {
+ return new NewCertificateConfig(cfg);
+ }
+ };
+
+ public final String OU;
+ public final String O;
+ public final String L;
+ public final String ST;
+ public final String C;
+
+ public final int duration;
+
+ private NewCertificateConfig(final Config c) {
+ duration = c.getInt("new", null, "duration", 0);
+ OU = c.getString("new", null, "organizationalUnit");
+ O = c.getString("new", null, "organization");
+ L = c.getString("new", null, "locality");
+ ST = c.getString("new", null, "stateProvince");
+ C = c.getString("new", null, "countryCode");
+ }
+
+ public void update(X509Metadata metadata) {
+ update(metadata, "OU", OU);
+ update(metadata, "O", O);
+ update(metadata, "L", L);
+ update(metadata, "ST", ST);
+ update(metadata, "C", C);
+ if (duration > 0) {
+ metadata.notAfter = new Date(System.currentTimeMillis() + duration*TimeUtils.ONEDAY);
+ }
+ }
+
+ private void update(X509Metadata metadata, String oid, String value) {
+ if (!StringUtils.isEmpty(value)) {
+ metadata.oids.put(oid, value);
+ }
+ }
+ } \ 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
index 00000000..47132a06
--- /dev/null
+++ b/src/com/gitblit/authority/UserCertificateConfig.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.authority;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.models.UserModel;
+
+/**
+ * User certificate config section parser.
+ *
+ * @author James Moger
+ */
+public class UserCertificateConfig {
+ public static final SectionParser<UserCertificateConfig> KEY = new SectionParser<UserCertificateConfig>() {
+ public UserCertificateConfig parse(final Config cfg) {
+ return new UserCertificateConfig(cfg);
+ }
+ };
+
+ public final List<UserCertificateModel> list;
+
+ private UserCertificateConfig(final Config c) {
+ SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
+ list = new ArrayList<UserCertificateModel>();
+ for (String username : c.getSubsections("user")) {
+ UserCertificateModel uc = new UserCertificateModel(new UserModel(username));
+ try {
+ uc.expires = df.parse(c.getString("user", username, "expires"));
+ } catch (ParseException e) {
+ LoggerFactory.getLogger(UserCertificateConfig.class).error("Failed to parse date!", e);
+ }
+ uc.notes = c.getString("user", username, "notes");
+ uc.revoked = new ArrayList<String>(Arrays.asList(c.getStringList("user", username, "revoked")));
+ list.add(uc);
+ }
+ }
+
+ public UserCertificateModel getUserCertificateModel(String username) {
+ for (UserCertificateModel ucm : list) {
+ if (ucm.user.username.equalsIgnoreCase(username)) {
+ return ucm;
+ }
+ }
+ return null;
+ }
+} \ 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
index 00000000..f5d71bb0
--- /dev/null
+++ b/src/com/gitblit/authority/UserCertificateModel.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.authority;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Config;
+
+import com.gitblit.Constants;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.RevocationReason;
+
+public class UserCertificateModel implements Comparable<UserCertificateModel> {
+ public UserModel user;
+ public Date expires;
+ public List<X509Certificate> certs;
+ public List<String> revoked;
+ public String notes;
+
+ public UserCertificateModel(UserModel user) {
+ this.user = user;
+ }
+
+ public void update(Config config) {
+ if (expires != null) {
+ SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
+ config.setString("user", user.username, "expires", df.format(expires));
+ }
+ if (notes != null) {
+ config.setString("user", user.username, "notes", notes);
+ }
+ if (!ArrayUtils.isEmpty(revoked)) {
+ config.setStringList("user", user.username, "revoked", revoked);
+ }
+ }
+
+ @Override
+ public int compareTo(UserCertificateModel o) {
+ return user.compareTo(o.user);
+ }
+
+ public void revoke(BigInteger serial, RevocationReason reason) {
+ if (revoked == null) {
+ revoked = new ArrayList<String>();
+ }
+ revoked.add(serial.toString() + ":" + reason.ordinal());
+ }
+
+ public boolean isRevoked(BigInteger serial) {
+ return isRevoked(serial.toString());
+ }
+
+ public boolean isRevoked(String serial) {
+ if (ArrayUtils.isEmpty(revoked)) {
+ return false;
+ }
+ String sn = serial + ":";
+ for (String s : revoked) {
+ if (s.startsWith(sn)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public RevocationReason getRevocationReason(BigInteger serial) {
+ try {
+ String sn = serial + ":";
+ for (String s : revoked) {
+ if (s.startsWith(sn)) {
+ String r = s.substring(sn.length());
+ int i = Integer.parseInt(r);
+ return RevocationReason.values()[i];
+ }
+ }
+ } catch (Exception e) {
+ }
+ return RevocationReason.unspecified;
+ }
+
+ public CertificateStatus getStatus() {
+ if (expires == null) {
+ return CertificateStatus.unknown;
+ } else if (isExpired(expires)) {
+ return CertificateStatus.expired;
+ } else if (isExpiring(expires)) {
+ return CertificateStatus.expiring;
+ }
+ return CertificateStatus.ok;
+ }
+
+ public boolean hasExpired() {
+ return expires != null && isExpiring(expires);
+ }
+
+ public CertificateStatus getStatus(X509Certificate cert) {
+ if (isRevoked(cert.getSerialNumber())) {
+ return CertificateStatus.revoked;
+ } else if (isExpired(cert.getNotAfter())) {
+ return CertificateStatus.expired;
+ } else if (isExpiring(cert.getNotAfter())) {
+ return CertificateStatus.expiring;
+ }
+ return CertificateStatus.ok;
+ }
+
+ private boolean isExpiring(Date date) {
+ return (date.getTime() - System.currentTimeMillis()) <= TimeUtils.ONEDAY * 30;
+ }
+
+ private boolean isExpired(Date date) {
+ return date.getTime() < System.currentTimeMillis();
+ }
+ } \ No newline at end of file
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index 1159905d..bd40985f 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -56,6 +56,11 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
public String cookie;
public String displayName;
public String emailAddress;
+ public String organizationalUnit;
+ public String organization;
+ public String locality;
+ public String stateProvince;
+ public String countryCode;
public boolean canAdmin;
public boolean canFork;
public boolean canCreate;