/*************************************************************************
* Copyright 2009-2015 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.
************************************************************************/
package com.eucalyptus.auth.euare;
import java.security.MessageDigest;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.ServerCertificate;
import com.eucalyptus.auth.euare.principal.EuareAccount;
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.component.auth.SystemCredentials.Credentials;
import com.eucalyptus.component.id.Euare;
import com.eucalyptus.crypto.Certs;
import com.eucalyptus.crypto.Ciphers;
import com.eucalyptus.crypto.Crypto;
import com.eucalyptus.crypto.Digest;
import com.eucalyptus.crypto.util.B64;
import com.eucalyptus.crypto.util.PEMFiles;
import com.eucalyptus.util.Exceptions;
/**
* @author Sang-Min Park
*
*/
public class EuareServerCertificateUtil {
private static Logger LOG = Logger.getLogger( EuareServerCertificateUtil.class );
// return body and chain of server certificate in plain text
public static String getServerCertificate(final String certArn) throws AuthException {
final ServerCertificate targetCert = lookupServerCertificate(certArn);
String serverCert = targetCert.getCertificateBody();
final String chain = targetCert.getCertificateChain();
if(chain != null && chain.length()>0)
serverCert = String.format("%s\n%s", serverCert, chain);
return serverCert;
}
public static String getEncryptedKey(final String certArn, final String certPem) throws AuthException {
final ServerCertificate targetCert = lookupServerCertificate(certArn);
// generate symmetric key
final MessageDigest digest = Digest.SHA256.get();
final byte[] salt = new byte[32];
Crypto.getSecureRandomSupplier().get().nextBytes(salt);
digest.update( salt );
final SecretKey symmKey = new SecretKeySpec( digest.digest(), "AES" );
try{
// encrypt the server pk using symm key
Cipher cipher = Ciphers.AES_CBC.get();
final byte[] iv = new byte[16];
Crypto.getSecureRandomSupplier().get().nextBytes(iv);
cipher.init( Cipher.ENCRYPT_MODE, symmKey, new IvParameterSpec( iv ), Crypto.getSecureRandomSupplier( ).get( ) );
final byte[] cipherText = cipher.doFinal(Base64.encode( targetCert.getPrivateKey().getBytes() ));
final String encPrivKey = new String(Base64.encode(Arrays.concatenate(iv, cipherText)));
// encrypt the symmetric key using the certPem
X509Certificate x509Cert = PEMFiles.getCert( B64.standard.dec( certPem ) );
cipher = Ciphers.RSA_PKCS1.get();
cipher.init(Cipher.ENCRYPT_MODE, x509Cert.getPublicKey(), Crypto.getSecureRandomSupplier( ).get( ));
byte[] symmkey = cipher.doFinal(symmKey.getEncoded());
final String b64SymKey = new String(Base64.encode(symmkey));
return String.format("%s\n%s", b64SymKey, encPrivKey);
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
public static X509Certificate generateVMCertificate(
final RSAPublicKey publicKey,
final String principal,
final int expirationDays
) throws AuthException {
try {
final X500Principal subjectDn = new X500Principal( principal );
final Credentials euareCred = SystemCredentials.lookup( Euare.class );
final Principal signer = euareCred.getCertificate().getSubjectDN();
final PrivateKey signingKey = euareCred.getPrivateKey();
final Date notAfter = DateUtils.addDays(Calendar.getInstance().getTime(), expirationDays);
final X509Certificate cert =
Certs.generateCertificate( publicKey, subjectDn, new X500Principal( signer.getName( ) ), signingKey, notAfter );
if( cert == null ) {
throw new Exception( "Null returned" );
}
return cert;
} catch( final Exception ex ) {
throw new AuthException( "failed to generate VM certificate", ex);
}
}
public static String generateSignatureWithEuare(final String msg){
return generateSignature(SystemCredentials.lookup( Euare.class ).getPrivateKey( ), msg);
}
public static String generateSignature(final PrivateKey key, final String msg){
try{
final Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(key);
sig.update(msg.getBytes("UTF-8"));
final byte[] bsig = sig.sign();
return B64.standard.encString(bsig);
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
public static boolean verifyCertificate(final String certPem, final boolean checkSigner) {
try{
final X509Certificate cert = PEMFiles.getCert( B64.standard.dec( certPem ) );
cert.checkValidity();
if(checkSigner) {
final Credentials euareCred = SystemCredentials.lookup( Euare.class );
final X509Certificate signer = euareCred.getCertificate();
cert.verify(signer.getPublicKey());
}
return true;
}catch(final Exception ex) {
return false;
}
}
public static boolean verifySignature(final String certPem, final String msg, final String sigB64){
try{
final Signature sig = Signature.getInstance("SHA256withRSA");
final X509Certificate cert = PEMFiles.getCert( B64.standard.dec( certPem ) );
sig.initVerify( cert );
sig.update(msg.getBytes("UTF-8"));
return sig.verify(B64.standard.dec(sigB64.getBytes()));
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
public static boolean verifySignatureWithEuare(final String msg, final String sigB64){
final String euareCert =
B64.standard.encString( PEMFiles.getBytes( SystemCredentials.lookup( Euare.class ).getCertificate() ) );
return verifySignature(euareCert, msg, sigB64);
}
private static ServerCertificate lookupServerCertificate(final String certArn) throws AuthException{
if(!certArn.startsWith("arn:aws:iam::"))
Exceptions.toUndeclared(new Exception("ARN is not in valid format"));
String arn = certArn;
arn = arn.replace("arn:aws:iam::", "");
final int idx = arn.indexOf(":server-certificate");
if(idx <=0 ){
Exceptions.toUndeclared(new Exception("ARN is not in valid format"));
}
final String acctId = arn.substring(0, idx);
final EuareAccount owner = com.eucalyptus.auth.euare.Accounts.lookupAccountById( acctId );
final String prefix = String.format("arn:aws:iam::%s:server-certificate", acctId);
if(!certArn.startsWith(prefix))
throw new AuthException(AuthException.SERVER_CERT_NO_SUCH_ENTITY);
final String pathAndName = certArn.replace(prefix, "");
final String certName = pathAndName.substring(pathAndName.lastIndexOf("/")+1);
final ServerCertificate targetCert = owner.lookupServerCertificate(certName);
if(targetCert==null)
throw new AuthException(AuthException.SERVER_CERT_NO_SUCH_ENTITY);
return targetCert;
}
}