/************************************************************************* * 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.KeyPair; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.NoSuchElementException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import org.apache.log4j.Logger; import org.bouncycastle.util.encoders.Base64; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.ServerCertificate; import com.eucalyptus.auth.euare.persist.entities.ServerCertificateEntity; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.component.auth.SystemCredentials; import com.eucalyptus.component.id.Euare; import com.eucalyptus.crypto.Ciphers; import com.eucalyptus.crypto.Crypto; import com.eucalyptus.crypto.util.PEMFiles; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.Exceptions; import com.eucalyptus.auth.principal.OwnerFullName; import com.eucalyptus.util.RestrictedTypes.Resolver; import com.google.common.base.Charsets; import com.google.common.base.Function; /** * @author Sang-Min Park * */ public class ServerCertificates { private static Logger LOG = Logger.getLogger( ServerCertificates.class ); public static final class VerifiedCertInfo { private final Date expiration; public VerifiedCertInfo( final Date expiration ) { this.expiration = expiration; } @Nullable public Date getExpiration( ) { return expiration; } } @Nonnull public static VerifiedCertInfo verifyCertificate(final String certBody, final String certChain) throws AuthException{ final Date expiration; try{ final X509Certificate cert = PEMFiles.getCert(certBody.getBytes( Charsets.UTF_8 )); if(cert==null) throw new Exception("Malformed certificate"); expiration = cert.getNotAfter( ); }catch(final Exception ex) { throw new AuthException( String.format("%s (%s)", AuthException.SERVER_CERT_INVALID_FORMAT, "Certificate body is invalid - is the cert in PEM format?")); } return new VerifiedCertInfo( expiration ); } @Nonnull public static VerifiedCertInfo verifyCertificate(final String certBody, final String pk, final String certChain) throws AuthException{ final VerifiedCertInfo certInfo = verifyCertificate( certBody, certChain ); try{ final KeyPair kp = PEMFiles.getKeyPair(pk.getBytes( Charsets.UTF_8 )); if(kp == null) throw new Exception("Malformed pk"); }catch(final Exception ex){ LOG.error("Invalid private key is given", ex); throw new AuthException( String.format("%s (%s)", AuthException.SERVER_CERT_INVALID_FORMAT, "Private key is invalid - is the key in PEM format?")); } return certInfo; } @Resolver( ServerCertificateEntity.class ) public enum Lookup implements Function<String, ServerCertificateEntity> { INSTANCE; @Override public ServerCertificateEntity apply( final String arn ) { try{ //String.format("arn:aws:iam::%s:server-certificate%s%s", this.owningAccount.getAccountNumber(), path, this.certName); // extract account id from arn and find account if(!arn.startsWith("arn:aws:iam::")) throw new EucalyptusCloudException("malformed arn"); // get admin name of the account String token = arn.substring( "arn:aws:iam::".length() ); String acctId = token.substring( 0, token.indexOf( ":server-certificate" ) ); // get certname of the arn final String prefix = String.format("arn:aws:iam::%s:server-certificate", acctId); if(!arn.startsWith(prefix)) throw new EucalyptusCloudException("malformed arn"); String pathAndName = arn.replace(prefix, ""); String certName = pathAndName.substring(pathAndName.lastIndexOf("/")+1); ServerCertificateEntity found = null; try ( final TransactionResource db = Entities.transactionFor( ServerCertificateEntity.class ) ) { found = Entities.criteriaQuery( ServerCertificateEntity.named( AccountFullName.getInstance( acctId ), certName ) ).uniqueResult( ); db.rollback(); } catch(final NoSuchElementException ex){ ; } catch(final Exception ex){ throw ex; } return found; }catch(final Exception ex){ throw Exceptions.toUndeclared(ex); } } } public enum ToServerCertificate implements Function<ServerCertificateEntity, ServerCertificate> { INSTANCE; @Override public ServerCertificate apply(ServerCertificateEntity entity) { return toServerCertificate( entity, false ); } } public enum ToServerCertificateWithSecrets implements Function<ServerCertificateEntity, ServerCertificate> { INSTANCE; @Override public ServerCertificate apply(ServerCertificateEntity entity) { return toServerCertificate( entity, true ); } } /// TODO: should think about impact to services using the certificate public static void updateServerCertificate(final OwnerFullName user, final String certName, final String newCertName, final String newCertPath) throws NoSuchElementException, AuthException { try ( final TransactionResource db = Entities.transactionFor( ServerCertificateEntity.class ) ) { final ServerCertificateEntity found = Entities.criteriaQuery( ServerCertificateEntity.named( user, certName ) ).uniqueResult( ); try { if (newCertName != null && newCertName.length() > 0 && !certName.equals(newCertName)) found.setCertName(newCertName); } catch (final Exception ex) { throw new AuthException(AuthException.INVALID_SERVER_CERT_NAME); } try { if (newCertPath != null && newCertPath.length() > 0) found.setCertPath(newCertPath); } catch (final Exception ex) { throw new AuthException(AuthException.INVALID_SERVER_CERT_PATH); } Entities.persist(found); db.commit(); } catch (final Exception ex) { throw Exceptions.toUndeclared(ex); } } private static ServerCertificate toServerCertificate( final ServerCertificateEntity entity, final boolean includeSecrets ) { try { final ServerCertificate cert = new ServerCertificate( entity.getOwnerAccountNumber( ), entity.getCertName( ), entity.getCreationTimestamp( ), entity.getExpiration( ) ); cert.setCertificatePath(entity.getCertPath()); cert.setCertificateBody(entity.getCertBody()); cert.setCertificateChain(entity.getCertChain()); cert.setCertificateId(entity.getCertId()); if ( includeSecrets ) { byte[] encText = Base64.decode( entity.getPrivateKey( ) ); byte[] iv = Arrays.copyOfRange( encText, 0, 32 ); byte[] encPk = Arrays.copyOfRange( encText, 32, encText.length ); byte[] symKeyWrapped = Base64.decode( entity.getSessionKey( ) ); // get session key final PrivateKey euarePk = SystemCredentials.lookup( Euare.class ) .getPrivateKey( ); Cipher cipher = Ciphers.RSA_PKCS1.get( ); cipher.init( Cipher.UNWRAP_MODE, euarePk, Crypto.getSecureRandomSupplier( ).get( ) ); SecretKey sessionKey = (SecretKey) cipher.unwrap( symKeyWrapped, "AES/GCM/NoPadding", Cipher.SECRET_KEY ); cipher = Ciphers.AES_GCM.get( ); cipher.init( Cipher.DECRYPT_MODE, sessionKey, new IvParameterSpec( iv ), Crypto.getSecureRandomSupplier( ).get( ) ); cert.setPrivateKey( new String( cipher.doFinal( encPk ) ) ); } return cert; } catch (final Exception ex) { throw Exceptions.toUndeclared(ex); } } }