/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.component.auth;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAKey;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.List;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.util.X509CertHelper;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Bootstrapper;
import com.eucalyptus.bootstrap.DependsLocal;
import com.eucalyptus.bootstrap.DependsRemote;
import com.eucalyptus.bootstrap.Provides;
import com.eucalyptus.bootstrap.RunDuring;
import com.eucalyptus.component.ComponentId;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.NoSuchComponentException;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.crypto.Certs;
import com.eucalyptus.crypto.KeyStore;
import com.eucalyptus.crypto.util.AbstractKeyStore;
import com.eucalyptus.crypto.util.B64;
import com.eucalyptus.crypto.util.PEMFiles;
import com.eucalyptus.empyrean.Empyrean;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.system.SubDirectory;
import com.google.common.base.Predicate;
public class SystemCredentials {
private static Logger LOG = Logger.getLogger( SystemCredentials.class );
private static ConcurrentMap<String, Credentials> providers = new ConcurrentHashMap<String, Credentials>( );
public static <T extends ComponentId> Credentials lookup( ComponentId compId ) {
if ( providers.containsKey( compId.name( ) ) ) {
return providers.get( compId.name( ) );
} else {
try {
return new Credentials( compId );
} catch ( Exception ex ) {
LOG.error( ex, ex );
throw new RuntimeException( "Failed to lookup system credentials for: " + compId + " because: " + ex.getMessage( ), ex );
}
}
}
public static <T extends ComponentId> Credentials lookup( Class<T> compId ) {
return lookup( ComponentIds.lookup( compId ) );
}
public static class Credentials {
private final ComponentId componentId;
private final String name;
private final X509Certificate cert;
private final KeyPair keyPair;
private final String certFingerprint;
private Credentials( ComponentId componentId ) throws Exception {
this.componentId = componentId;
this.name = componentId.name( );
this.cert = loadCertificate( componentId );
this.keyPair = loadKeyPair( componentId );
this.certFingerprint = X509CertHelper.calcFingerprint(this.cert);
EventRecord.here( SystemCredentials.class, EventType.COMPONENT_INFO, "initialized", this.name, this.cert.getSubjectDN( ).toString( ) ).info( );
SystemCredentials.providers.put( this.name, this );
}
@Override
public int hashCode( ) {
final int prime = 31;
int result = 1;
result = prime * result + ( ( this.name == null )
? 0
: this.name.hashCode( ) );
return result;
}
@Override
public boolean equals( Object obj ) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass( ) != obj.getClass( ) ) {
return false;
}
Credentials other = ( Credentials ) obj;
if ( this.name == null ) {
if ( other.name != null ) {
return false;
}
} else if ( !this.name.equals( other.name ) ) {
return false;
}
return true;
}
private KeyPair loadKeyPair( ComponentId componentId ) throws Exception {
if ( this.componentId.hasCredentials( ) && EucaKeyStore.getInstance( ).containsEntry( this.name ) ) {
try {
EventRecord.here( SystemCredentials.class, EventType.COMPONENT_INFO, "initializing", this.name ).info( );
return EucaKeyStore.getInstance( ).getKeyPair( this.name, this.name );
} catch ( Exception e ) {
LOG.fatal( "Failed to read keys from the keystore: " + componentId + ". Please repair the keystore by hand." );
LOG.fatal( e, e );
throw e;
}
} else {
throw new NoSuchComponentException( "Failed to find credentials for: " + componentId );
}
}
private X509Certificate loadCertificate( ComponentId componentId ) throws Exception {
if ( this.componentId.hasCredentials( ) && EucaKeyStore.getInstance( ).containsEntry( this.name ) ) {
try {
EventRecord.here( SystemCredentials.class, EventType.COMPONENT_INFO, "initializing", this.name ).info( );
return EucaKeyStore.getInstance( ).getCertificate( this.name );
} catch ( Exception e ) {
LOG.fatal( "Failed to read certificate from the keystore: " + componentId + ". Please repair the keystore by hand." );
LOG.fatal( e, e );
throw e;
}
} else {
throw new NoSuchComponentException( "Failed to find credentials for: " + componentId );
}
}
public String getPem( ) {
return B64.url.encString( PEMFiles.getBytes( this.getCertificate( ) ) );
}
public X509Certificate getCertificate( ) {
return this.cert;
}
public PrivateKey getPrivateKey( ) {
return this.keyPair.getPrivate( );
}
public KeyPair getKeyPair( ) {
return this.keyPair;
}
public String getCertFingerprint() {
return this.certFingerprint;
}
}
static boolean checkKeystore( ComponentId name ) throws Exception {
return EucaKeyStore.getCleanInstance( ).containsEntry( name.name( ) );
}
static boolean check( Class<? extends ComponentId> compIdType ) {
return check( ComponentIds.lookup( compIdType ) );
}
static boolean check( ComponentId compId ) {
if (! compId.hasCredentials( ) ) {
return true;
} else {
return EucaKeyStore.getInstance( ).containsEntry( compId.name( ) );
}
}
private static Credentials create( ComponentId compId ) throws Exception {
if ( !SystemCredentials.check( compId ) ) {
try {
KeyPair sysKp = Certs.generateKeyPair( );
X509Certificate sysX509 = Certs.generateServiceCertificate( sysKp, compId.name( ) );
if ( ComponentIds.lookup( Eucalyptus.class ).name( ).equals( compId.name( ) ) ) {
PEMFiles.write( SubDirectory.KEYS.toString( ) + "/cloud-cert.pem", sysX509 );
PEMFiles.write( SubDirectory.KEYS.toString( ) + "/cloud-pk.pem", sysKp.getPrivate( ) );
}
EucaKeyStore.getInstance( ).addKeyPair( compId.name( ), sysX509, sysKp.getPrivate( ), compId.name( ) );
EucaKeyStore.getInstance( ).store( );
} catch ( Exception e ) {
throw e;
}
}
return new Credentials( compId );
}
private static boolean checkAllKeys( ) {
for ( ComponentId c : ComponentIds.list( ) ) {
if ( !c.hasCredentials( ) ) {
continue;
} else {
try {
if ( !EucaKeyStore.getCleanInstance( ).containsEntry( c.name( ) ) ) {
LOG.error( "Failed to lookup key for " + c.getCapitalizedName( ) + " with alias=" + c.name( ) + " in file "
+ EucaKeyStore.getInstance( ).getFileName( ) );
return false;
}
} catch ( Exception e ) {
LOG.error( e, e );
return false;
}
}
}
return true;
}
public static boolean initialize( ) {
try {
if ( !SystemCredentials.check( Eucalyptus.class ) ) {
SystemCredentials.create( Eucalyptus.INSTANCE );
}
for ( ComponentId c : ComponentIds.list( ) ) {
if ( !SystemCredentials.check( c ) ) {
SystemCredentials.create( c );
}
}
} catch ( Exception e ) {
LOG.error( e, e );
return false;
}
return true;
}
@Provides( Empyrean.class )
@RunDuring( Bootstrap.Stage.SystemCredentialsInit )
@DependsLocal( Eucalyptus.class )
public static class SystemCredentialBootstrapper extends Bootstrapper.Simple {
@Override
public boolean load( ) throws Exception {
try {
if ( !SystemCredentials.check( Eucalyptus.class ) ) {
SystemCredentials.lookup( Eucalyptus.INSTANCE );
}
for ( ComponentId c : ComponentIds.list( ) ) {
if ( !SystemCredentials.check( c ) ) {
try{
SystemCredentials.lookup( c );
}catch(final Exception ex){
SystemCredentials.create( c ); // this will create and lookup should succeed the next time cloud starts
}
}
}
} catch ( Exception e ) {
LOG.error( e, e );
return false;
}
return true;
}
}
@Provides( Empyrean.class )
@RunDuring( Bootstrap.Stage.SystemCredentialsInit )
@DependsRemote( Eucalyptus.class )
public static class RemoteComponentCredentialBootstrapper extends Bootstrapper.Simple {
@Override
public boolean load( ) throws Exception {
while ( !SystemCredentials.checkAllKeys( ) ) {
LOG.fatal( "Waiting for system credentials before proceeding with startup..." );
try {
Thread.sleep( 2000 );
} catch ( Exception e ) {
Thread.currentThread( ).interrupt( );
}
}
for ( ComponentId c : ComponentIds.list( ) ) {
if ( c.hasCredentials( ) ) {
LOG.info( "Initializing system credentials for " + c.name( ) );
SystemCredentials.lookup( c );
}
}
return true;
}
}
private static final class EucaKeyStore extends AbstractKeyStore {
public static String FORMAT = "pkcs12";
private static String KEY_STORE_PASS = "eucalyptus";
private static String FILENAME = "euca.p12";
/**
* Enforces that keys are less than the MAX_SIZE of 4096.
*/
private enum RestrictSize implements Predicate<Key> {
INSTANCE;
private static final int MAX_SIZE = 4096;
public boolean apply( Key arg0 ) {
RSAKey key = ( ( RSAKey ) arg0 );
if ( key.getModulus( ).bitLength( ) > MAX_SIZE ) {
SecurityException ex = new SecurityException( "Illegal key size: " + key.getModulus( ).bitLength( ) + " > " + MAX_SIZE + " (max key size)" );
LOG.trace( ex, ex );
throw ex;
} else {
return true;
}
}
}
private static volatile KeyStore singleton = EucaKeyStore.getInstance( );
public static KeyStore getInstance( ) {
if (singleton == null) {
synchronized (EucaKeyStore.class) {
if (EucaKeyStore.singleton == null) {
try {
singleton = new EucaKeyStore();
} catch (final Exception e) {
LOG.error(e, e);
}
}
}
}
return singleton;
}
public static KeyStore getCleanInstance( ) throws Exception {
synchronized ( EucaKeyStore.class ) {
singleton = new EucaKeyStore( );
}
return singleton;
}
private EucaKeyStore( ) throws GeneralSecurityException, IOException {
super( SubDirectory.KEYS.toString( ) + File.separator + EucaKeyStore.FILENAME, EucaKeyStore.KEY_STORE_PASS, EucaKeyStore.FORMAT );
}
@Override
public boolean check( ) throws GeneralSecurityException {
return this.getCertificate( ComponentIds.lookup( Eucalyptus.class ).name( ) ) != null;
}
@Override
public final KeyPair getKeyPair( String alias, String password ) throws GeneralSecurityException {
KeyPair keyPair = super.getKeyPair( alias, password );
RestrictSize.INSTANCE.apply( keyPair.getPrivate( ) );
RestrictSize.INSTANCE.apply( keyPair.getPublic( ) );
return keyPair;
}
@Override
public final X509Certificate getCertificate( String alias ) throws GeneralSecurityException {
X509Certificate certificate = super.getCertificate( alias );
RestrictSize.INSTANCE.apply( certificate.getPublicKey( ) );
return certificate;
}
@Override
public final List<X509Certificate> getCertificateChain( String alias) throws GeneralSecurityException {
List<X509Certificate> certificateChain = super.getCertificateChain(alias);
for (X509Certificate cert: certificateChain) {
RestrictSize.INSTANCE.apply(cert.getPublicKey());
}
return certificateChain;
}
@Override
public final Key getKey( String alias, String password ) throws GeneralSecurityException {
Key key = super.getKey( alias, password );
RestrictSize.INSTANCE.apply( key );
return key;
}
@Override
public final void addCertificate( String alias, X509Certificate cert ) throws IOException, GeneralSecurityException {
RestrictSize.INSTANCE.apply( cert.getPublicKey( ) );
super.addCertificate( alias, cert );
}
@Override
public final void addKeyPair( String alias, X509Certificate cert, PrivateKey privateKey, String keyPassword ) throws IOException, GeneralSecurityException {
RestrictSize.INSTANCE.apply( cert.getPublicKey( ) );
RestrictSize.INSTANCE.apply( privateKey );
super.addKeyPair( alias, cert, privateKey, keyPassword );
}
}
public static final KeyStore getKeyStore( ) {
return EucaKeyStore.getInstance( );
}
}