Permalink
Browse files

Encrypt LDAP password for LIC, with new tools for helping create LIC.

  • Loading branch information...
1 parent e7fc1ac commit db1c46ecfe4011659a91ef1c4141f3ec9525563d wenye committed Apr 19, 2011
@@ -0,0 +1,143 @@
+package com.eucalyptus.auth.crypto;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.Timestamp;
+import java.util.Arrays;
+import java.util.Date;
+import java.security.Security;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.SimpleLayout;
+import org.bouncycastle.util.encoders.UrlBase64;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import com.eucalyptus.cloud.ws.VMwareBrokerProperties;
+import com.eucalyptus.component.auth.AbstractKeyStore;
+import com.eucalyptus.component.auth.EucaKeyStore;
+
+public class StringCrypto {
+
+ private final String ALIAS = "eucalyptus"; // TODO: don't hardcode these?
+ private final String PASSWORD = "eucalyptus";
+
+ private static AbstractKeyStore keystore;
+ private final String symmetricFormat = "DESede/CBC/PKCS5Padding";
+ private String asymmetricFormat = "RSA/ECB/PKCS1Padding";
+ private String provider = "BC";
+
+ private static byte [] cat (byte[] bs, byte[] bs2) {
+ byte [] result = Arrays.copyOf (bs, bs.length + bs2.length);
+ System.arraycopy(bs2, 0, result, bs.length, bs2.length);
+ return result;
+ }
+
+ public StringCrypto () { }
+
+ public StringCrypto (String format, String provider)
+ {
+ this.asymmetricFormat = format;
+ this.provider = provider;
+ Security.addProvider( new BouncyCastleProvider( ) );
+ if (Security.getProvider (this.provider) == null)
+ throw new RuntimeException("cannot find security provider " + this.provider);
+ keystore = EucaKeyStore.getInstance();
+ if (keystore==null || !keystore.containsEntry("eucalyptus"))
+ throw new RuntimeException ("cannot load keystore or find the key");
+ }
+
+ public byte[] encrypt (String password)
+ throws GeneralSecurityException
+ {
+ Key pk = keystore.getCertificate(ALIAS).getPublicKey();
+ Cipher cipher = Cipher.getInstance(this.asymmetricFormat, this.provider);
+ cipher.init(Cipher.ENCRYPT_MODE, pk);
+ byte [] passwordEncrypted = cipher.doFinal(password.getBytes());
+ return cat (VMwareBrokerProperties.ENCRYPTION_FORMAT.getBytes(), UrlBase64.encode(passwordEncrypted)); // prepend format
+ }
+
+ public String decrypt (String passwordEncoded)
+ throws GeneralSecurityException
+ {
+ String withoutPrefix = passwordEncoded.substring(VMwareBrokerProperties.ENCRYPTION_FORMAT.length(), passwordEncoded.length());
+ byte[] passwordEncrypted = UrlBase64.decode(withoutPrefix);
+ Key pk = keystore.getKey(ALIAS, PASSWORD);
+ Cipher cipher = Cipher.getInstance(this.asymmetricFormat, this.provider);
+ cipher.init(Cipher.DECRYPT_MODE, pk);
+ return new String(cipher.doFinal(passwordEncrypted));
+ }
+
+ /**
+ * Decrypt base64 encoded password generated by openssl.
+ * @param passwordEncrypted in base64
+ * @return
+ * @throws GeneralSecurityException
+ */
+ public String decryptOpenssl(String passwordEncoded) throws GeneralSecurityException {
+ // Somehow, UrlBase64 in BC can not decode openssl generated base64 string correctly.
+ // We have to use the Base64 from Commons codec library.
+ byte[] passwordEncrypted = Base64.decodeBase64(passwordEncoded.getBytes());
+ Key pk = keystore.getKey(ALIAS, PASSWORD);
+ Cipher cipher = Cipher.getInstance(this.asymmetricFormat, this.provider);
+ cipher.init(Cipher.DECRYPT_MODE, pk);
+ return new String(cipher.doFinal(passwordEncrypted));
+ }
+
+ private byte[] makeKey (String secret) throws NoSuchAlgorithmException
+ {
+ // TODO: not sure about all this hanky-panky (this is from stackoverflow.com)
+ final MessageDigest md = MessageDigest.getInstance("md5");
+ final byte[] digestOfPassword = md.digest(secret.getBytes());
+ final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
+ for (int j = 0, k = 16; j < 8;)
+ {
+ keyBytes[k++] = keyBytes[j++];
+ }
+ return keyBytes;
+ }
+
+ public byte[] encrypt (String string, String secret)
+ throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
+ {
+ final byte[] keyBytes = makeKey(secret);
+ final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
+ final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
+ final Cipher cipher = Cipher.getInstance(this.symmetricFormat);
+ cipher.init(Cipher.ENCRYPT_MODE, key, iv);
+ final byte[] stringEncrypted = cipher.doFinal(string.getBytes());
+ return UrlBase64.encode(stringEncrypted);
+ }
+
+ public String decrypt (byte[] stringEncoded, String secret) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
+ {
+ final byte[] keyBytes = makeKey(secret);
+ byte[] stringEncrypted = UrlBase64.decode(stringEncoded);
+ final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
+ final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
+ final Cipher cipher = Cipher.getInstance(this.symmetricFormat);
+ cipher.init(Cipher.DECRYPT_MODE, key, iv);
+ return new String(cipher.doFinal(stringEncrypted));
+ }
+}
@@ -0,0 +1,37 @@
+package com.eucalyptus.auth.crypto;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.util.encoders.UrlBase64;
+
+public class StringCryptoTest {
+
+ /**
+ * @param args
+ */
+ public static void main( String[] args ) throws Exception {
+ // TODO Auto-generated method stub
+ StringCrypto sc = new StringCrypto( "RSA/ECB/PKCS1Padding", "BC" );
+ System.out.println( sc.decryptOpenssl( "N8MFkU9cbxKHvtD9Xq0JaAu2X65d90J1lD6wJ5UkcdX4LyUZv/sBtaa0HlXZlW64YoAzn02P+312GTTsGiUlBzbK8o5LbY8DHyOqH/thv3JhvLVLpQRTLBH+YnGzBwqybUnwGTz4dNxkKu52vA/FvGC7UNC/PHzxjN07CwZ1riJPoYB6vSyH41dVYbs+oLSm2FMXx+mLxKVYq4NoewSPiwn0fZHTITm6nvWi5IV2cNF+K+Ibgx9/QUanKHRjAmmvEHVIGQoXu72POkTjdNu+tqqNFN7jF3dD0/CuXVeSYx/auOHhQ6zTnDJdqPHWd2H9CQQU+nfHtsR3VG91vE73yA==" ) );
+ }
+
+ private static void printDec( byte[] ba ) {
+ for ( byte b : ba ) {
+ System.out.print( b + " " );
+ }
+ System.out.print( '\n' );
+ }
+
+ private static byte[] readfile( String filename ) throws Exception {
+ FileInputStream fis = new FileInputStream( filename );
+ ByteArrayOutputStream baos = new ByteArrayOutputStream( );
+ byte[] block = new byte[512];
+ int n;
+ while ( ( n = fis.read( block ) ) > 0 ) {
+ baos.write( block, 0, n );
+ }
+ return baos.toByteArray( );
+ }
+
+}
@@ -1,5 +1,6 @@
package com.eucalyptus.auth.ldap;
+import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -13,10 +14,14 @@
import javax.naming.ldap.LdapContext;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.LdapException;
+import com.eucalyptus.auth.crypto.StringCrypto;
import com.google.common.collect.Lists;
public class LdapClient {
+ public static final String CRYPTO_FORMAT = "RSA/ECB/PKCS1Padding";
+ public static final String CRYPTO_PROVIDER = "BC";
+
public static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
public static final int TIMEOUT_IN_MILLIS = 60000; // a minute
@@ -25,6 +30,7 @@
private static Logger LOG = Logger.getLogger( LdapClient.class );
+ private StringCrypto sc = new StringCrypto( CRYPTO_FORMAT, CRYPTO_PROVIDER );
private Properties env;
private LdapContext context;
@@ -39,14 +45,19 @@ public LdapClient( LdapIntegrationConfiguration lic ) throws LdapException {
}
}
- private void prepareLdapContextEnv( LdapIntegrationConfiguration lic ) {
+ private void prepareLdapContextEnv( LdapIntegrationConfiguration lic ) throws LdapException {
env.put( Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY );
env.put( Context.PROVIDER_URL, lic.getServerUrl( ) );
env.put( Context.SECURITY_AUTHENTICATION, lic.getAuthMethod( ) );
if ( !LicParser.LDAP_AUTH_METHOD_SASL_GSSAPI.equals( lic.getAuthMethod( ) ) ) {
env.put( Context.SECURITY_PRINCIPAL, lic.getAuthPrincipal( ) );
- // TODO(wenye): should decrypt credentials here.
- env.put( Context.SECURITY_CREDENTIALS, lic.getAuthCredentials( ) );
+ try {
+ String credentials = sc.decryptOpenssl( lic.getAuthCredentials( ) );
+ env.put( Context.SECURITY_CREDENTIALS, credentials );
+ } catch ( GeneralSecurityException e ) {
+ LOG.error( e, e );
+ throw new LdapException( "Decryption failure", e );
+ }
}
if ( lic.isUseSsl( ) ) {
env.put( Context.SECURITY_PROTOCOL, "ssl" );
@@ -23,6 +23,8 @@
private static final Logger LOG = Logger.getLogger( LicParser.class );
+ private static final String COMMENT = "_comment";
+
private static final String LDAP_URL_PREFIX = "ldap://";
// Supported authentication methods
@@ -115,6 +117,9 @@ private void parseGroupsPartition( JSONObject licJson, LdapIntegrationConfigurat
JSONObject groupsPartition = JsonUtils.getByType( JSONObject.class, licJson, LicSpec.GROUPS_PARTITION );
for ( Object t : groupsPartition.keySet( ) ) {
String partitionName = ( String ) t;
+ if ( partitionName.equalsIgnoreCase( COMMENT ) ) {
+ continue;
+ }
Set<String> groupSet = Sets.newHashSet( );
groupSet.addAll( JsonUtils.getArrayByType( String.class, groupsPartition, partitionName ) );
lic.getGroupsPartition( ).put( partitionName, groupSet );
@@ -152,6 +157,9 @@ private void parseUsers( JSONObject licJson, LdapIntegrationConfiguration lic )
private void parseUserInfoMap( JSONObject map, LdapIntegrationConfiguration lic ) throws JSONException {
for ( Object m : map.keySet( ) ) {
String attr = ( String ) m;
+ if ( attr.equalsIgnoreCase( COMMENT ) ) {
+ continue;
+ }
String name = JsonUtils.getByType( String.class, map, attr );
lic.getUserInfoAttributes( ).put( attr, name );
}
@@ -1,48 +1,70 @@
{
+
+ "_comment":"This is a template of LDAP integration config file in JSON. For security, please create this template by using create_lic.pl command, which fill the <auth-credentials> in <ldap-service> section with encrypted password. After you make the changes, you can remove <_comment> lines in all sections (but you do not have to). Among all the sections, only <sync> section is mandatory. However, if sync is enabled, <ldap-service>, !!ONLY!! ONE of <accounting-groups> and <groups-partition>, <groups> and <users> become mandatory. The simplest configuration is disabled sync, with only <sync> section, in which <enable> is false.",
+
"ldap-service":{
- "server-url":"ldap://ldapserver.foo.com:7733",
+
+ "_comment":"This section defines configurations for LDAP service. <server-url> defines LDAP service URL. <auth-method> defines the LDAP authentication method. <auth-principal> defines the LDAP authentication user (for whom to access LDAP). <auth-credentials> field should show the encrypted password. <use-ssl> specifies whether to use SSL for LDAP connection for extra safety.",
+
+ "server-url":"ldap://localhost:7733",
"auth-method":"simple",
"auth-principal":"cn=ldapadmin,dc=foo,dc=com",
- "auth-credentials":"blahblah",
- "use-ssl":"false",
+ "auth-credentials":"ENCRYPTED_PASSWORD",
+ "use-ssl":"false",
},
- "accounting-groups":{
- "base-dn":"ou=groups,dc=foo,dc=com",
- "id-attribute":"cn",
- "member-attribute":"member",
- "selection":{
- "filter":"objectClass=accountingGroup",
- "select":["cn=engAccounting,ou=Groups,dc=eucalyptus,dc=com"],
- "not-select":["cn=marketAccounting,ou=Groups,dc=eucalyptus,dc=com"],
- }
+
+ "sync":{
+
+ "_comment":"This section defines configurations for sync behavior. <enable> turns on/off sync. <auto> specifies if sync is automated. <interval> defines the period between syncs.",
+
+ "enable":"true",
+ "auto":"true",
+ "interval":"900000",
+ },
+
+ "groups-partition":{
+
+ "_comment":"This section defines configurations for groups partitions. !!REMOVE!! this section if you have accounting groups and will use accounting groups to define accounts. Each field defines an account and gives the IDs of the groups for the account.",
+
+ "fooAccount":["fooGroup1", "fooGroup2"],
+ "barAccount":["barGroup1", "barGroup2"],
},
+
"groups":{
+
+ "_comment":"This section defines configurations for groups to sync. <base-dn> defines the base DN for searching groups. <id-attribute> specifies the attribute name that which serves as the unique ID of a group. <member-attribute> is the attribute name that specifies members of the group. <selection> is a construct to define how to pick groups in the tree.",
+
"base-dn":"ou=groups,dc=foo,dc=com",
"id-attribute":"cn",
"member-attribute":"member",
"selection":{
+
+ "_comment":"This construct defines what entities to select from LDAP tree. <filter> is mandatory, which is an LDAP search filter. <select> and <not-select> are optional, which specify one-offs.",
+
"filter":"objectClass=groupOfNames",
- "select":["cn=engineering,ou=Groups,dc=eucalyptus,dc=com"],
- "not-select":["cn=marketing,ou=Groups,dc=eucalyptus,dc=com"],
+ "select":["cn=groupToSelect,ou=Groups,dc=eucalyptus,dc=com"],
+ "not-select":["cn=groupToIgnore,ou=Groups,dc=eucalyptus,dc=com"],
}
},
+
"users":{
+
+ "_comment":"This section defines configurations for users to sync. <base-dn> defines the base DN for searching users. <id-attribute> specifies the attribute name that which serves as the unique ID of a user. <user-info-attribute> defines which of the user attributes are chosen, with which, each field defines a mapping between LDAP attribute name and the name to be used in Eucalyptus. <password-attribute> specifies the name of the password attribute, which is used for Eucalyptus Web UI authentication. <selection> is a construct to define how to pick users in the tree.",
+
"base-dn":"ou=people,dc=foo,dc=com",
"id-attribute":"uid",
"user-info-attributes":{
"name":"Name",
- "fullName":"Full Name"
+ "email":"Email"
},
"password-attribute":"password",
"selection":{
+
+ "_comment":"This construct defines what entities to select from LDAP tree. <filter> is mandatory, which is an LDAP search filter. <select> and <not-select> are optional, which specify one-offs.",
+
"filter":"objectClass=inetOrgPersion",
- "select":["uid=johndoe,ou=People,dc=eucalyptus,dc=com", "uid=jackbauer,ou=People,dc=eucalyptus,dc=com"],
- "not-select":["uid=jackbauer,ou=People,dc=eucalyptus,dc=com"],
+ "select":["uid=john,ou=People,dc=foo,dc=com", "uid=jack,ou=People,dc=foo,dc=com"],
+ "not-select":["uid=tom,ou=People,dc=eucalyptus,dc=com"],
}
},
- "sync":{
- "enable":"true",
- "auto":"true",
- "interval":"900000",
- },
-}
+}
View
@@ -56,6 +56,9 @@ install: build
@$(INSTALL) -m 644 libvirt-kvm-windows-example.xml $(DESTDIR)$(datarootdir)/eucalyptus/doc/
@$(INSTALL) -m 644 libvirt-xen-windows-example.xml $(DESTDIR)$(datarootdir)/eucalyptus/doc/
@$(INSTALL) -m 755 getstats.pl $(DESTDIR)$(datarootdir)/eucalyptus
+ @$(INSTALL) -m 755 lictool.pl $(DESTDIR)$(sbindir)
+ @$(INSTALL) -m 755 lic_template $(DESTDIR)$(datarootdir)/eucalyptus
+ @$(INSTALL) -m 755 lic_default $(DESTDIR)$(datarootdir)/eucalyptus
uninstall:
@$(RM) -f $(DESTDIR)$(etcdir)/init.d/eucalyptus-cloud
@@ -78,3 +81,6 @@ uninstall:
@$(RM) -f $(DESTDIR)$(etcdir)/bash_completion.d/euca_conf
@$(RM) -f $(DESTDIR)$(sbindir)/euca_sync_key
@$(RM) -f $(DESTDIR)$(datarootdir)/eucalyptus/euca_vmware
+ @$(RM) -f $(DESTDIR)$(datarootdir)/eucalyptus/lic_template
+ @$(RM) -f $(DESTDIR)$(datarootdir)/eucalyptus/lic_default
+ @$(RM) -f $(DESTDIR)$(sbindir)/lictool.pl
View
@@ -0,0 +1,5 @@
+{
+ "sync": {
+ "enable":"false",
+ }
+}
Oops, something went wrong.

0 comments on commit db1c46e

Please sign in to comment.