/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.service.impl; import org.candlepin.model.CandlepinQuery; import org.candlepin.model.CertificateSerial; import org.candlepin.model.CertificateSerialCurator; import org.candlepin.model.Consumer; import org.candlepin.model.ConsumerCurator; import org.candlepin.model.Content; import org.candlepin.model.ContentAccessCertificate; import org.candlepin.model.ContentAccessCertificateCurator; import org.candlepin.model.Entitlement; import org.candlepin.model.Environment; import org.candlepin.model.EnvironmentContent; import org.candlepin.model.KeyPairCurator; import org.candlepin.model.Owner; import org.candlepin.model.OwnerContentCurator; import org.candlepin.model.OwnerEnvContentAccess; import org.candlepin.model.OwnerEnvContentAccessCurator; import org.candlepin.model.Pool; import org.candlepin.model.Product; import org.candlepin.pki.PKIUtility; import org.candlepin.pki.X509ByteExtensionWrapper; import org.candlepin.pki.X509ExtensionWrapper; import org.candlepin.service.ContentAccessCertServiceAdapter; import org.candlepin.util.OIDUtil; import org.candlepin.util.Util; import org.candlepin.util.X509V3ExtensionUtil; import com.google.inject.Inject; import com.google.inject.persist.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * DefaultEntitlementCertServiceAdapter */ public class DefaultContentAccessCertServiceAdapter implements ContentAccessCertServiceAdapter { private PKIUtility pki; private KeyPairCurator keyPairCurator; private CertificateSerialCurator serialCurator; private OwnerContentCurator ownerContentCurator; private ContentAccessCertificateCurator contentAccessCertificateCurator; private X509V3ExtensionUtil v3extensionUtil; private OwnerEnvContentAccessCurator ownerEnvContentAccessCurator; private ConsumerCurator consumerCurator; private static Logger log = LoggerFactory.getLogger(DefaultContentAccessCertServiceAdapter.class); @Inject public DefaultContentAccessCertServiceAdapter(PKIUtility pki, X509V3ExtensionUtil v3extensionUtil, ContentAccessCertificateCurator contentAccessCertificateCurator, KeyPairCurator keyPairCurator, CertificateSerialCurator serialCurator, OwnerContentCurator ownerContentCurator, OwnerEnvContentAccessCurator ownerEnvContentAccessCurator, ConsumerCurator consumerCurator) { this.pki = pki; this.contentAccessCertificateCurator = contentAccessCertificateCurator; this.keyPairCurator = keyPairCurator; this.serialCurator = serialCurator; this.v3extensionUtil = v3extensionUtil; this.ownerContentCurator = ownerContentCurator; this.ownerEnvContentAccessCurator = ownerEnvContentAccessCurator; this.consumerCurator = consumerCurator; } @Transactional public ContentAccessCertificate getCertificate(Consumer consumer) throws GeneralSecurityException, IOException { Owner owner = consumer.getOwner(); // we only know about one mode right now. If add any, we will need to add the // appropriate cert generation if (!ORG_ENV_ACCESS_MODE.equals(owner.contentAccessMode()) || !consumer.isCertV3Capable()) { return null; } ContentAccessCertificate existing = consumer.getContentAccessCert(); ContentAccessCertificate result = new ContentAccessCertificate(); String pem = ""; if (existing != null && existing.getSerial().getExpiration().getTime() < (new Date()).getTime()) { consumer.setContentAccessCert(null); contentAccessCertificateCurator.delete(existing); existing = null; } if (existing == null) { Calendar cal = Calendar.getInstance(); cal.add(Calendar.HOUR, -1); Date startDate = cal.getTime(); cal.add(Calendar.YEAR, 1); Date endDate = cal.getTime(); CertificateSerial serial = new CertificateSerial(endDate); // We need the sequence generated id before we create the Certificate, // otherwise we could have used cascading create serialCurator.create(serial); KeyPair keyPair = keyPairCurator.getConsumerKeyPair(consumer); byte[] pemEncodedKeyPair = pki.getPemEncoded(keyPair.getPrivate()); X509Certificate x509Cert = createX509Certificate(consumer, BigInteger.valueOf(serial.getId()), keyPair, startDate, endDate); existing = new ContentAccessCertificate(); existing.setSerial(serial); existing.setKeyAsBytes(pemEncodedKeyPair); existing.setConsumer(consumer); log.info("Setting PEM encoded cert."); pem = new String(this.pki.getPemEncoded(x509Cert)); existing.setCert(pem); consumer.setContentAccessCert(existing); contentAccessCertificateCurator.create(existing); consumerCurator.merge(consumer); } else { pem = existing.getCert(); } Environment env = consumer.getEnvironment(); // we need to see if this is newer than the previous result OwnerEnvContentAccess oeca = ownerEnvContentAccessCurator.getContentAccess(owner.getId(), env == null ? null : env.getId()); if (oeca == null) { String contentJson = createPayloadAndSignature(owner, env); oeca = new OwnerEnvContentAccess(owner, env, contentJson); ownerEnvContentAccessCurator.saveOrUpdate(oeca); } pem += oeca.getContentJson(); result.setCert(pem); result.setCreated(existing.getCreated()); result.setUpdated(existing.getUpdated()); result.setId(existing.getId()); result.setConsumer(existing.getConsumer()); result.setKey(existing.getKey()); result.setSerial(existing.getSerial()); return result; } public boolean hasCertChangedSince(Consumer consumer, Date date) { if (date == null) { return true; } Environment env = consumer.getEnvironment(); Owner owner = consumer.getOwner(); OwnerEnvContentAccess oeca = ownerEnvContentAccessCurator.getContentAccess( owner.getId(), env == null ? null : env.getId()); return oeca == null || consumer.getContentAccessCert() == null || oeca.getUpdated().getTime() > date.getTime(); } public String createPayloadAndSignature(Owner owner, Environment environment) throws IOException { byte[] payloadBytes = createContentAccessDataPayload(owner, environment); String payload = "-----BEGIN ENTITLEMENT DATA-----\n"; payload += Util.toBase64(payloadBytes); payload += "-----END ENTITLEMENT DATA-----\n"; byte[] bytes = pki.getSHA256WithRSAHash(new ByteArrayInputStream(payloadBytes)); String signature = "-----BEGIN RSA SIGNATURE-----\n"; signature += Util.toBase64(bytes); signature += "-----END RSA SIGNATURE-----\n"; return payload + signature; } public X509Certificate createX509Certificate(Consumer consumer, BigInteger serialNumber, KeyPair keyPair, Date startDate, Date endDate) throws GeneralSecurityException, IOException { // fake a product dto as a container for the org content org.candlepin.model.dto.Product container = new org.candlepin.model.dto.Product(); org.candlepin.model.dto.Content dContent = new org.candlepin.model.dto.Content(); List<org.candlepin.model.dto.Content> dtoContents = new ArrayList<org.candlepin.model.dto.Content>(); dtoContents.add(dContent); dContent.setPath(getContentPrefix(consumer.getOwner(), consumer.getEnvironment())); container.setContent(dtoContents); Set<X509ExtensionWrapper> extensions = prepareV3Extensions(consumer, null, null); Set<X509ByteExtensionWrapper> byteExtensions = prepareV3ByteExtensions(consumer, container); X509Certificate x509Cert = this.pki.createX509Certificate( createDN(consumer), extensions, byteExtensions, startDate, endDate, keyPair, serialNumber, null); return x509Cert; } private Map<String, EnvironmentContent> getPromotedContent(Environment environment) { // Build a set of all content IDs promoted to the consumer's environment so // we can determine if anything needs to be skipped: Map<String, EnvironmentContent> promotedContent = new HashMap<String, EnvironmentContent>(); if (environment != null) { log.debug("Consumer has environment, checking for promoted content in: " + environment); for (EnvironmentContent envContent : environment.getEnvironmentContent()) { log.debug(" promoted content: " + envContent.getContent().getId()); promotedContent.put(envContent.getContent().getId(), envContent); } } return promotedContent; } private String getContentPrefix(Owner owner, Environment environment) throws IOException { StringBuffer contentPrefix = new StringBuffer(); contentPrefix.append("/"); contentPrefix.append(owner.getKey()); if (environment != null) { contentPrefix.append("/"); contentPrefix.append(environment.getId()); } return contentPrefix.toString(); } private String createDN(Consumer consumer) { StringBuilder sb = new StringBuilder("CN="); sb.append(consumer.getUuid()); sb.append(", O="); sb.append(consumer.getOwner().getKey()); if (consumer.getEnvironment() != null) { sb.append(", OU="); sb.append(consumer.getEnvironment().getId()); } return sb.toString(); } public Set<X509ExtensionWrapper> prepareV3Extensions(Consumer consumer, String contentPrefix, Map<String, EnvironmentContent> promotedContent) { Set<X509ExtensionWrapper> result = v3extensionUtil.getExtensions(null, contentPrefix, promotedContent); X509ExtensionWrapper typeExtension = new X509ExtensionWrapper(OIDUtil.REDHAT_OID + "." + OIDUtil.TOPLEVEL_NAMESPACES.get(OIDUtil.ENTITLEMENT_TYPE_KEY), false, "OrgLevel"); result.add(typeExtension); return result; } public Set<X509ByteExtensionWrapper> prepareV3ByteExtensions(Consumer consumer, org.candlepin.model.dto.Product container) throws IOException { List<org.candlepin.model.dto.Product> products = new ArrayList<org.candlepin.model.dto.Product>(); products.add(container); Set<X509ByteExtensionWrapper> result = v3extensionUtil.getByteExtensions(null, products, null, null, null); return result; } private byte[] createContentAccessDataPayload(Owner owner, Environment environment) throws IOException { // fake a product dto as a container for the org content Set<Product> containerSet = new HashSet<Product>(); CandlepinQuery<Content> ownerContent = ownerContentCurator.getContentByOwner(owner); Set<String> entitledProductIds = new HashSet<String>(); List<org.candlepin.model.dto.Product> productModels = new ArrayList<org.candlepin.model.dto.Product>(); Map<String, EnvironmentContent> promotedContent = getPromotedContent(environment); String contentPrefix = getContentPrefix(owner, environment); Product container = new Product(); Entitlement emptyEnt = new Entitlement(); Pool emptyPool = new Pool(); Product skuProduct = new Product(); Consumer emptyConsumer = new Consumer(); containerSet.add(container); container.setId("content_access"); container.setName(" Content Access"); for (Content c : ownerContent) { container.addContent(c, false); } emptyConsumer.setEnvironment(environment); emptyEnt.setPool(emptyPool); emptyEnt.setConsumer(emptyConsumer); emptyPool.setProduct(skuProduct); emptyPool.setStartDate(new Date()); emptyPool.setEndDate(new Date()); skuProduct.setName("Content Access"); skuProduct.setId("content_access"); entitledProductIds.add("content-access"); org.candlepin.model.dto.Product productModel = v3extensionUtil.mapProduct(container, skuProduct, contentPrefix, promotedContent, emptyConsumer, emptyEnt, entitledProductIds); productModels.add(productModel); return v3extensionUtil.createEntitlementDataPayload(skuProduct, productModels, emptyEnt, contentPrefix, promotedContent); } }