/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.capedwarf.appidentity; import java.io.IOException; import java.io.StringWriter; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.UUID; import com.google.appengine.api.appidentity.AppIdentityService; import com.google.appengine.api.appidentity.AppIdentityServiceFailureException; import com.google.appengine.api.appidentity.PublicCertificate; import com.google.appengine.api.memcache.Expiration; import com.google.appengine.api.memcache.MemcacheService; import com.google.appengine.api.memcache.MemcacheServiceFactory; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemWriter; import org.jboss.capedwarf.common.app.Application; import org.jboss.capedwarf.environment.Environment; import org.jboss.capedwarf.environment.EnvironmentFactory; import org.jboss.capedwarf.shared.compatibility.Compatibility; import org.jboss.capedwarf.shared.reflection.ReflectionUtils; /** * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a> * @author <a href="mailto:marko.luksa@gmail.com">Marko Luksa</a> */ public class CapedwarfAppIdentityService implements AppIdentityService { public static final String MEMCACHE_NAMESPACE = "_ah_"; public static final String MEMCACHE_KEY_PREFIX = "_ah_app_identity_"; public static final long OFFSET = 300000L; public static final long VALID = 6 * 30 * 24 * 60 * 60 * 1000L; public SigningResult signForApp(byte[] bytes) { rotateCertificatesIfNeeded(); CertificateBundle bundle = getCertificateStore().getCurrentBundle(); byte[] signature = sign(bytes, bundle.getPrivateKey()); return new SigningResult(bundle.getName(), signature); } public Collection<PublicCertificate> getPublicCertificatesForApp() { rotateCertificatesIfNeeded(); Collection<PublicCertificate> certificates = new ArrayList<PublicCertificate>(); for (CertificateBundle bundle : getCertificateStore().getAllBundles()) { if (!bundle.isStale()) { certificates.add(new PublicCertificate(bundle.getName(), convertToPEMFormat(bundle.getCertificate()))); } } return certificates; } private void rotateCertificatesIfNeeded() { if (rotationNeeded()) { rotateCertificates(); } } private boolean rotationNeeded() { CertificateBundle bundle = getCertificateStore().getCurrentBundle(); return bundle == null || bundle.isStale(); } public void rotateCertificates() { CertificateStore certificateStore = getCertificateStore(); certificateStore.store(createNewCertificate()); certificateStore.removeStaleCertificates(); } private CertificateStore getCertificateStore() { return CertificateStoreFactory.getCertificateStore(); } private CertificateBundle createNewCertificate() { KeyPair keyPair = CertificateGenerator.getInstance().generateKeyPair(); String domain = EnvironmentFactory.getEnvironment().getDomain(); X509Certificate certificate = CertificateGenerator.getInstance().generateCertificate(keyPair, domain); return new CertificateBundle(certificate.getSerialNumber().toString(), keyPair, certificate); } private byte[] sign(byte[] bytes, PrivateKey privateKey) { try { Signature dsa = Signature.getInstance("SHA256WithRSA", "BC"); dsa.initSign(privateKey); dsa.update(bytes); return dsa.sign(); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) { throw new AppIdentityServiceFailureException("Cannot sign: " + e); } } public String convertToPEMFormat(final X509Certificate certificate) { try { StringWriter stringWriter = new StringWriter(); PemWriter pemWriter = new PemWriter(stringWriter); pemWriter.writeObject(new PemObject(certificate.getType(), certificate.getEncoded())); pemWriter.flush(); return stringWriter.toString(); } catch (IOException | CertificateEncodingException e) { throw new RuntimeException("Cannot format certificate to PEM format", e); } } public String getServiceAccountName() { final String appId = Application.getAppId(); final String domain = EnvironmentFactory.getEnvironment().getDomain(); return appId + "@" + domain; } public GetAccessTokenResult getAccessTokenUncached(final Iterable<String> scopes) { // TODO -- proper token, expDate generation final String token = UUID.randomUUID().toString(); final Date now = new Date(); final Date expDate = new Date(now.getTime() + VALID); return new GetAccessTokenResult(token, expDate); } public GetAccessTokenResult getAccessToken(final Iterable<String> scopes) { final MemcacheService cache = MemcacheServiceFactory.getMemcacheService(MEMCACHE_NAMESPACE); final String key = toKey(scopes); GetAccessTokenResult result = (GetAccessTokenResult) cache.get(key); if (result == null) { result = getAccessTokenUncached(scopes); final Date expDate = new Date(result.getExpirationTime().getTime() - OFFSET); cache.put(key, result, Expiration.onDate(expDate)); } return result; } public ParsedAppId parseFullAppId(String fullAppId) { Environment env = EnvironmentFactory.getEnvironment(); final String partition = env.getPartition(); final String domain = env.getDomain(); final String appId = Application.getAppId(); return ReflectionUtils.newInstance( ParsedAppId.class, new Class[]{String.class, String.class, String.class}, new Object[]{partition, domain, appId} ); } public String getDefaultGcsBucketName() { String value = Compatibility.getInstance().getValue(Compatibility.Feature.DEFAULT_GCS_BUCKET_NAME); return (value != null) ? value : "CAPEDWARF_GCS_BUCKET"; } protected static String toKey(final Iterable<String> scopes) { final Iterator<String> iter = scopes.iterator(); if (iter.hasNext() == false) return MEMCACHE_KEY_PREFIX + "[]"; final StringBuilder builder = new StringBuilder(MEMCACHE_KEY_PREFIX + "["); while (true) { builder.append(iter.next()); if (iter.hasNext() == false) return builder.append(']').toString(); builder.append(", "); } } }