浏览代码

Command-line tool to generate client certificate bundles for existing users

tags/v1.2.0
James Moger 11 年前
父节点
当前提交
e8c417f4e6

+ 2
- 0
build.xml 查看文件

@@ -183,6 +183,7 @@
<exclude name="federation.properties" />
<exclude name="openshift.mkd" />
<exclude name="authority.conf" />
<exclude name="*.tmpl" />
</fileset>
<fileset dir="${basedir}">
<include name="LICENSE" />
@@ -195,6 +196,7 @@
<mkdir dir="${project.deploy.dir}/certs"/>
<copy todir="${project.deploy.dir}/certs">
<fileset dir="${basedir}/distrib">
<include name="*.tmpl" />
<include name="authority.conf" />
</fileset>
</copy>

+ 123
- 0
distrib/instructions.tmpl 查看文件

@@ -0,0 +1,123 @@
********************************************************************************
Gitblit SSL Client Certificate for $serverHostname
********************************************************************************
Hello $userDisplayname,
Your private key, public certificate, and the Gitblit Certificate Authority
certificate for $serverHostname are stored in $username.p12, a PKCS#12 certificate
store[1], and also in $username.pem, a PEM certificate store.
Both of these certificate stores are password-protected.
Password Hint: $storePasswordHint
Git (All) Installation Instructions
=============================================
The provided PEM file can be directly used by your git client.
git config [--global] http.sslCert path/to/$username.pem
The supplied PEM file is password-protected and you may be prompted for your
password multiple times during an exchange with Gitblit. If you desire a
password-less git client workflow then you will need to decrypt and export your
private key with OpenSSL[2] and then update your git config to use that key.
openssl rsa -in path/to/$username.pem -out path/to/$username.key
git config [--global] http.sslKey path/to/$username.key
Obviously, you should protect access to any decrypted private key.
NOTE:
Some older git clients may have trouble using the PEM file without explicitly
extracting the private key. This has been observed, for example, on Ubuntu 12.04
with git 1.7.9.5.
Firefox (All) Installation Instructions
=============================================
Firefox maintains it's own certificate store which is separate from the operating
system.
1. Navigate to Options->Advanced->Encryption
2. Click "View Certificates"
3. Switch to the "Your Certificates" tab
4. Click "Import..."
5. Navigate your filesystem and select $username.p12
6. At the password prompt enter the certificate store password
You have now imported your private key, public certificate, and the CA certificate
but now we must manually set the trust settings of the CA certificate.
7. Switch to the "Authorities" tab
8. Scroll down and find "Gitblit-> Gitblit Certificate Authority"
9. Select it and click "Edit Trust..."
10. Check "This certificate can identify websites" and click OK.
Chrome/IE (Windows) Installation Instructions
=============================================
On Windows, Chrome and IE share their certificate store so configuring one will
automatically apply for both.
IE
------------------------------------
1. Navigate to Internet Options->Content
2. Click the "Certificates" button
Chrome
------------------------------------
1. Navigate to Settings->Show Advanced Settings->HTTP/SSL
2. Click the "Manage Certificates..." button
Both (Windows)
------------------------------------
3. Switch to the "Personal" tab
4. Click the "Import..." button
5. Follow the Import Wizard instructions.
You will need to change the selected file filter when navigating to $username.p12
6. At the password prompt enter the certificate store password
7. Because both your personal certificate and the CA certifcate are stored in
$username.p12, you must choose "Automatically select the certificate store based on the type of certificate".
If you choose the default you will not install the CA certificate.
Chrome (Linux) Installation Instructions
=============================================
On Linux, Chrome maintains it's own certificate store.
1. Navigate to Settings->Show Advanced Settings->HTTP/SSL
2. Click the "Manage Certificates..." button
3. Navigate your filesystem and select $username.p12
4. At the password prompt enter the certificate store password
You have now imported your private key, public certificate, and the CA certificate
but now we must manually set the trust settings of the CA certificate.
5. Switch to the "Authorities" tab
6. Scroll down and find "Gitblit-> Gitblit Certificate Authority"
7. Select it and click "Edit Trust..."
8. Check "This certificate can identify websites" and click OK.
Chrome/Safari (Mac OS X) Installation Instructions
=============================================
On Mac OS X, Chrome and Safari both use Keychain Access to store certificates
so configuring one will automatically apply for both.
1. Double-click $username.pem
2. At the password prompt enter the certificate store password
You have now imported your private key, public certificate, and the CA certificate
but now we must manually set the trust settings of the CA certificate.
3. Find the Gitblit Certificate Authority certificate, it should have a red
indicator meaning untrusted, and double-click it.
4. Open the "Trust" disclosure triangle and change "When using this certificate"
to "Always Trust".
5. Close the certificate view and enter your system password to save the changes
to your keychain.
[1] PKCS#12 is one of the standard container formats for sharing private keys and
public certificates.
[2] http://www.openssl.org

+ 7
- 0
distrib/mail.tmpl 查看文件

@@ -0,0 +1,7 @@
Hello $userDisplayname,
Your private key, public certificate, and the Gitblit Certificate Authority
certificate for $serverHostname are bundled together in the attached zip file.
There are also setup/installation instructions included in the zip for Git and
several major browsers to get you started.

+ 1
- 0
distrib/makeclientcertificate.cmd 查看文件

@@ -0,0 +1 @@
@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.authority.MakeClientCertificate

+ 2
- 0
distrib/makeclientcertificate.sh 查看文件

@@ -0,0 +1,2 @@
#!/bin/sh
java -cp gitblit.jar:$PWD/ext/* com.gitblit.authority.MakeClientCertificate

+ 30
- 0
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);

+ 4
- 0
src/com/gitblit/MailExecutor.java 查看文件

@@ -231,4 +231,8 @@ public class MailExecutor implements Runnable {
}
}
}
public void sendNow(Message message) throws Exception {
Transport.send(message);
}
}

+ 230
- 0
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;
}
}

+ 72
- 0
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);
}
}
}

+ 69
- 0
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;
}
}

+ 134
- 0
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();
}
}

+ 5
- 0
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;

正在加载...
取消
保存