/*************************************************************************
* 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.crypto;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.codec.digest.Crypt;
import org.apache.commons.codec.digest.Sha2Crypt;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.cert.CertRuntimeException;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.util.encoders.UrlBase64;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Ints;
public final class DefaultCryptoProvider implements CryptoProvider, CertificateProvider, HmacProvider {
public static String KEY_ALGORITHM = "RSA";
private static final String KEY_SIGNING_ALGORITHM = "SHA512WithRSA";
private static final int KEY_SIZE = 2048;
public static String PROVIDER = "BC";
private static final String PRIVATE_KEY_FORMAT = System.getProperty( DefaultCryptoProvider.class.getName() + ".privateKeyFormat", "" );
private static Logger LOG = Logger.getLogger( DefaultCryptoProvider.class );
public DefaultCryptoProvider( ) {}
/**
* @see com.eucalyptus.crypto.CryptoProvider#generateHashedPassword(java.lang.String)
*/
@Override
public String generateHashedPassword( String password ) {
byte[] data = Digest.MD5.get( ).digest( password.getBytes( ) );
StringBuffer buf = new StringBuffer( );
for ( int i = 0; i < data.length; i++ ) {
int halfbyte = ( data[i] >>> 4 ) & 0x0F;
int two_halfs = 0;
do {
if ( ( 0 <= halfbyte ) && ( halfbyte <= 9 ) )
buf.append( ( char ) ( '0' + halfbyte ) );
else buf.append( ( char ) ( 'a' + ( halfbyte - 10 ) ) );
halfbyte = data[i] & 0x0F;
} while ( two_halfs++ < 1 );
}
return buf.toString( ).toLowerCase( );
}
/**
* @see com.eucalyptus.crypto.CryptoProvider#generateAlphanumericId(int)
*/
@Override
public String generateAlphanumericId( final int length ) {
return generateRandomAlphanumeric( length ).toUpperCase();//NOTE: this MUST be upper case.
}
/**
* @see com.eucalyptus.crypto.CryptoProvider#generateSecretKey()
*/
@Override
public String generateSecretKey() {
return generateRandomAlphanumeric(40);//NOTE: this MUST be 40-chars from base64.
}
/**
* Note that the output has not always been of fixed length.
*
* @see com.eucalyptus.crypto.CryptoProvider#generateSessionToken()
*/
@Override
public String generateSessionToken() {
return generateRandomAlphanumeric(80);
}
private String generateRandomAlphanumeric(int length) {
final StringBuilder randomBuilder = new StringBuilder( length + 90 );
while( randomBuilder.length() < length ) {
randomBuilder.append(generateRandomAlphanumeric() );
}
return randomBuilder.toString().substring( 0, length );
}
private String generateRandomAlphanumeric() {
// length from generateRandomAlphanumeric is not constant due to
// removal of punctuation characters
return Crypto.getRandom( 64 ).replaceAll("\\p{Punct}", "");
}
/**
* Note that the output is not standard Base64.
*
* <p>The output has the following substitutions:</p>
*
* <ul>
* <li> + -> - </li>
* <li> / -> _ </li>
* <li> = -> . </li>
* </ul>
*
* @see com.eucalyptus.crypto.CryptoProvider#getDigestBase64(java.lang.String, com.eucalyptus.crypto.Digest)
*/
@Override
public String getDigestBase64( final String input, final Digest hash ) {
final byte[] inputBytes = input.getBytes();
final MessageDigest digest = hash.get( );
digest.update( inputBytes );
final byte[] digestBytes = digest.digest( );
return new String( UrlBase64.encode( digestBytes ) );
}
public X509Certificate generateServiceCertificate( KeyPair keys, String serviceName ) {
X500Principal x500 = new X500Principal( String.format( "CN=%s, OU=Eucalyptus, O=Cloud, C=US", serviceName ) );
// if( !"eucalyptus".equals( serviceName ) ) {
// SystemCredentials sys = SystemCredentials.lookup( Eucalyptus.class );
// return generateCertificate( keys, x500, sys.getCertificate( ).getSubjectX500Principal( ), sys.getPrivateKey( ) );
// } else {
return generateCertificate( keys, x500, x500, null );
// }
}
public X509Certificate generateCertificate( KeyPair keys, String userName ) {
return generateCertificate( keys, new X500Principal( String.format( "CN=%s, OU=Eucalyptus, O=User, C=US", userName ) ) );
}
public X509Certificate generateCertificate( KeyPair keys, X500Principal dn ) {
return generateCertificate( keys, dn, dn, null );
}
@Override
public X509Certificate generateCertificate( KeyPair keys, X500Principal subjectDn, X500Principal signer, PrivateKey signingKey) {
Calendar cal = Calendar.getInstance( );
cal.add( Calendar.YEAR, 5 );
return generateCertificate(keys, subjectDn, signer, signingKey, cal.getTime() );
}
@Override
public X509Certificate generateCertificate( PublicKey key, X500Principal subjectDn, X500Principal signer, PrivateKey signingKey, Date notAfter ) {
if (signingKey == null){
LOG.error("No signing key is provided");
return null;
}
if (signer == null) {
LOG.error("No signiner principal is specified");
return null;
}
if (subjectDn == null) {
LOG.error("No subject principal is specified");
return null;
}
if (key == null) {
LOG.error("No requesting key is specified");
return null;
}
EventRecord.caller( DefaultCryptoProvider.class, EventType.GENERATE_CERTIFICATE, signer.toString( ), subjectDn.toString( ) ).info();
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator( );
certGen.setSerialNumber( BigInteger.valueOf( System.nanoTime( ) ).shiftLeft( 4 ).add( BigInteger.valueOf( ( long ) Math.rint( Math.random( ) * 1000 ) ) ) );
certGen.setIssuerDN( signer );
certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints( true ) );
try {
certGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new JcaX509ExtensionUtils( ).createSubjectKeyIdentifier( key ) );
} catch ( NoSuchAlgorithmException | CertRuntimeException e ) {
LOG.error( "Error adding subject key identifier extension.", e );
}
Calendar cal = Calendar.getInstance( );
certGen.setNotBefore( cal.getTime( ) );
certGen.setNotAfter(notAfter);
certGen.setSubjectDN( subjectDn );
certGen.setPublicKey( key );
certGen.setSignatureAlgorithm( KEY_SIGNING_ALGORITHM );
try {
X509Certificate cert = certGen.generate( signingKey, PROVIDER );
cert.checkValidity( );
return cert;
} catch ( Exception e ) {
LOG.fatal( e, e );
return null;
}
}
@Override
public X509Certificate generateCertificate( KeyPair keys, X500Principal subjectDn, X500Principal signer, PrivateKey signingKey, Date notAfter ) {
signer = ( signingKey == null ? signer : subjectDn );
signingKey = ( signingKey == null ? keys.getPrivate( ) : signingKey );
EventRecord.caller( DefaultCryptoProvider.class, EventType.GENERATE_CERTIFICATE, signer.toString( ), subjectDn.toString( ) ).info();
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator( );
certGen.setSerialNumber( BigInteger.valueOf( System.nanoTime( ) ).shiftLeft( 4 ).add( BigInteger.valueOf( ( long ) Math.rint( Math.random( ) * 1000 ) ) ) );
certGen.setIssuerDN( signer );
certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints( true ) );
try {
certGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new JcaX509ExtensionUtils( ).createSubjectKeyIdentifier( keys.getPublic( ) ) );
} catch ( NoSuchAlgorithmException | CertRuntimeException e ) {
LOG.error( "Error adding subject key identifier extension.", e );
}
Calendar cal = Calendar.getInstance( );
certGen.setNotBefore( cal.getTime( ) );
certGen.setNotAfter(notAfter);
certGen.setSubjectDN( subjectDn );
certGen.setPublicKey( keys.getPublic( ) );
certGen.setSignatureAlgorithm( KEY_SIGNING_ALGORITHM );
try {
X509Certificate cert = certGen.generate( signingKey, PROVIDER );
cert.checkValidity( );
return cert;
} catch ( Exception e ) {
LOG.fatal( e, e );
return null;
}
}
/**
* @see com.eucalyptus.crypto.CertificateProvider#generateKeyPair()
*/
@Override
public KeyPair generateKeyPair( ) {
KeyPairGenerator keyGen = null;
try {
EventRecord.caller( DefaultCryptoProvider.class, EventType.GENERATE_KEYPAIR );
keyGen = KeyPairGenerator.getInstance( KEY_ALGORITHM, PROVIDER );
SecureRandom random = Crypto.getSecureRandomSupplier( ).get( );
//TODO: RELEASE: see line:110
keyGen.initialize( KEY_SIZE, random );
KeyPair keyPair = keyGen.generateKeyPair( );
return keyPair;
} catch ( Exception e ) {
LOG.fatal( e, e );
return null;
}
}
/**
* Get the PKCS#8 encoded bytes for the key.
*
* @param privateKey The key to encode.
* @return The bytes
*/
@Override
public byte[] getEncoded( final PrivateKey privateKey ) {
if ( "pkcs8".equals( PRIVATE_KEY_FORMAT ) ) try {
return KeyFactory.getInstance( KEY_ALGORITHM, PROVIDER )
.getKeySpec( privateKey, PKCS8EncodedKeySpec.class ).getEncoded();
} catch ( Exception e ) {
LOG.error( e, e );
}
return privateKey.getEncoded();
}
@Override
public String generateSystemSignature( ) {
return this.generateSystemToken( ComponentIds.lookup( Eucalyptus.class ).name( ).getBytes( ) );
}
@Override
public String generateSystemToken( byte[] data ) {
PrivateKey pk = SystemCredentials.lookup( Eucalyptus.class ).getPrivateKey( );
return Signatures.SHA256withRSA.trySign( pk, data );
}
@Override
public String generateId( final String prefix ) {
final byte[] idBytes = new byte[4];
Crypto.getSecureRandomSupplier( ).get( ).nextBytes( idBytes );
return String.format( "%s-%08x", prefix, Ints.fromByteArray( idBytes ) );
}
@Override
public String generateLongId( final String prefix ) {
final byte[] idBytes = new byte[9];
Crypto.getSecureRandomSupplier( ).get( ).nextBytes( idBytes );
return String.format( "%s-%s", prefix, BaseEncoding.base16( ).encode( idBytes ).substring( 1 ) );
}
@Override
public String getFingerPrint( Key privKey ) {
return getFingerPrint( privKey.getEncoded( ) );
}
@Override
public String getFingerPrint( byte[] data ) {
try {
byte[] fp = Digest.SHA1.get( ).digest( data );
StringBuffer sb = new StringBuffer( );
for ( byte b : fp )
sb.append( String.format( "%02X:", b ) );
return sb.substring( 0, sb.length( ) - 1 ).toLowerCase( );
} catch ( Exception e ) {
LOG.error( e, e );
return null;
}
}
@Override
public String generateLinuxSaltedPassword(String password) {
return Sha2Crypt.sha512Crypt( password.getBytes( StandardCharsets.UTF_8 ) );
}
@Override
public boolean verifyLinuxSaltedPassword(String clear, String hashed) {
return MessageDigest.isEqual( // constant time comparison
hashed.getBytes( StandardCharsets.UTF_8 ),
Crypt.crypt( clear.getBytes( StandardCharsets.UTF_8 ), hashed ).getBytes( StandardCharsets.UTF_8 )
);
}
}