/**
* 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.model;
import org.candlepin.auth.Principal;
import org.candlepin.common.exceptions.BadRequestException;
import org.candlepin.common.exceptions.NotFoundException;
import org.candlepin.pki.PKIUtility;
import org.candlepin.pki.X509ByteExtensionWrapper;
import org.candlepin.pki.X509ExtensionWrapper;
import org.candlepin.service.UniqueIdGenerator;
import org.candlepin.util.X509ExtensionUtil;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.TimeZone;
/**
* UeberCertificateGenerator
*/
public class UeberCertificateGenerator {
private static final String UEBER_CERT_CONSUMER_TYPE = "uebercert";
private static final String UEBER_CERT_CONSUMER = "ueber_cert_consumer";
private static final String UEBER_CONTENT_NAME = "ueber_content";
private static final String UEBER_PRODUCT_POSTFIX = "_ueber_product";
private static Logger log = LoggerFactory.getLogger(UeberCertificateGenerator.class);
private UniqueIdGenerator idGenerator;
private PKIUtility pki;
private KeyPairCurator keyPairCurator;
private CertificateSerialCurator serialCurator;
private X509ExtensionUtil extensionUtil;
private OwnerCurator ownerCurator;
private UeberCertificateCurator ueberCertCurator;
private I18n i18n;
@Inject
public UeberCertificateGenerator(
UniqueIdGenerator idGenerator,
PKIUtility pki,
X509ExtensionUtil extensionUtil,
KeyPairCurator keyPairCurator,
CertificateSerialCurator serialCurator,
OwnerCurator ownerCurator,
UeberCertificateCurator ueberCertCurator,
I18n i18n) {
this.idGenerator = idGenerator;
this.pki = pki;
this.keyPairCurator = keyPairCurator;
this.serialCurator = serialCurator;
this.extensionUtil = extensionUtil;
this.ownerCurator = ownerCurator;
this.ueberCertCurator = ueberCertCurator;
this.i18n = i18n;
}
@Transactional
public UeberCertificate generate(String ownerKey, Principal principal) {
Owner owner = this.ownerCurator.lookupByKey(ownerKey);
if (owner == null) {
throw new NotFoundException(i18n.tr("Unable to find an owner with key: {0}", ownerKey));
}
this.ownerCurator.lock(owner);
try {
// There can only be one ueber certificate per owner, so delete the existing and regenerate it.
this.ueberCertCurator.deleteForOwner(owner);
return this.generateUeberCert(owner, principal.getUsername());
}
catch (Exception e) {
log.error("Problem generating ueber cert for owner: {}", ownerKey, e);
throw new BadRequestException(i18n.tr("Problem generating ueber cert for owner {0}", ownerKey),
e);
}
}
private UeberCertificate generateUeberCert(Owner owner, String generatedByUsername) throws Exception {
UeberCertData ueberCertData = new UeberCertData(owner, generatedByUsername);
CertificateSerial serial = new CertificateSerial(ueberCertData.getEndDate());
serialCurator.create(serial);
KeyPair keyPair = keyPairCurator.getKeyPair();
byte[] pemEncodedKeyPair = pki.getPemEncoded(keyPair.getPrivate());
X509Certificate x509Cert =
createX509Certificate(ueberCertData, BigInteger.valueOf(serial.getId()), keyPair);
UeberCertificate ueberCert = new UeberCertificate();
ueberCert.setSerial(serial);
ueberCert.setKeyAsBytes(pemEncodedKeyPair);
ueberCert.setOwner(owner);
ueberCert.setCert(new String(this.pki.getPemEncoded(x509Cert)));
ueberCert.setCreated(ueberCertData.getStartDate());
ueberCert.setUpdated(ueberCertData.getStartDate());
ueberCertCurator.create(ueberCert);
return ueberCert;
}
private X509Certificate createX509Certificate(UeberCertData data, BigInteger serialNumber,
KeyPair keyPair) throws GeneralSecurityException, IOException {
Set<X509ByteExtensionWrapper> byteExtensions = new LinkedHashSet<X509ByteExtensionWrapper>();
Set<X509ExtensionWrapper> extensions = new LinkedHashSet<X509ExtensionWrapper>();
extensions.addAll(extensionUtil.productExtensions(data.getProduct()));
extensions.addAll(extensionUtil.contentExtensions(data.getProduct().getProductContent(), null,
new HashMap<String, EnvironmentContent>(), new Consumer(), data.getProduct()));
extensions.addAll(extensionUtil.subscriptionExtensions(data.getEntitlement()));
extensions.addAll(extensionUtil.entitlementExtensions(data.getEntitlement()));
extensions.addAll(extensionUtil.consumerExtensions(data.getConsumer()));
if (log.isDebugEnabled()) {
log.debug("Ueber certificate extensions for Owner: {}", data.getOwner().getKey());
for (X509ExtensionWrapper eWrapper : extensions) {
log.debug("Extension {} with value {}", eWrapper.getOid(), eWrapper.getValue());
}
}
String dn = "O=" + data.getOwner().getKey();
return this.pki.createX509Certificate(dn, extensions, byteExtensions, data.getStartDate(),
data.getEndDate(), keyPair, serialNumber, null);
}
/**
* Contains the data that will be used to create an ueber certificate for the owner.
* This class should remain private and is used primarily for allowing a single definition
* of an ueber product/content.
*
*/
private class UeberCertData {
private Owner owner;
private Consumer consumer;
private Product product;
private Content content;
private Pool pool;
private Entitlement entitlement;
private Date startDate;
private Date endDate;
public UeberCertData(Owner owner, String generatedByUsername) {
startDate = Calendar.getInstance().getTime();
endDate = lateIn2049();
this.owner = owner;
this.consumer = createUeberConsumer(generatedByUsername, owner);
this.product = createUeberProductForOwner(idGenerator, owner);
this.content = createUeberContent(idGenerator, owner, product);
this.product.addContent(this.content, true);
this.pool = new Pool(this.owner, this.product, new LinkedList<Product>(), 1L, this.startDate,
this.endDate, "", "", "");
this.entitlement = new Entitlement(this.pool, this.consumer, 1);
}
public Product getProduct() {
return product;
}
public Owner getOwner() {
return owner;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Content getContent() {
return content;
}
public Consumer getConsumer() {
return consumer;
}
public Entitlement getEntitlement() {
return entitlement;
}
private Consumer createUeberConsumer(String username, Owner owner) {
ConsumerType type = new ConsumerType(UEBER_CERT_CONSUMER_TYPE);
Consumer consumer = new Consumer(UEBER_CERT_CONSUMER, username, owner, type);
return consumer;
}
private Product createUeberProductForOwner(UniqueIdGenerator idGenerator, Owner owner) {
String ueberProductName = owner.getKey() + UEBER_PRODUCT_POSTFIX;
return new Product(idGenerator.generateId(), ueberProductName, 1L);
}
private Content createUeberContent(UniqueIdGenerator idGenerator, Owner owner, Product product) {
Content ueberContent = new Content(idGenerator.generateId());
ueberContent.setName(UEBER_CONTENT_NAME);
ueberContent.setType("yum");
ueberContent.setLabel(product.getId() + "_" + UEBER_CONTENT_NAME);
ueberContent.setVendor("Custom");
ueberContent.setContentUrl("/" + owner.getKey());
ueberContent.setGpgUrl("");
ueberContent.setArches("");
return ueberContent;
}
/*
* RFC 5280 states in section 4.1.2.5:
*
* CAs conforming to this profile MUST always encode certificate
* validity dates through the year 2049 as UTCTime; certificate validity
* dates in 2050 or later MUST be encoded as GeneralizedTime.
* Conforming applications MUST be able to process validity dates that
* are encoded in either UTCTime or GeneralizedTime.
*
* But currently, python-rhsm is parsing certificates with either M2Crypto
* or a custom C binding (certificate.c) to OpenSSL's x509v3 (so that we can access
* the raw octets in the custom X509 extensions used in version 3 entitlement
* certificates). Both M2Crypto and our binding contain code that automatically
* converts the Not After time into a UTCTime. The conversion "succeeds" but the
* value of the resultant object is something like "Bad time value" which, when
* fed into Python's datetime, causes an exception.
*
* The quick fix is to not issue certificates that expire after January 1, 2050.
*
* See https://bugzilla.redhat.com/show_bug.cgi?id=1242310
*/
private Date lateIn2049() {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
// December 1, 2049 at 13:00 GMT
cal.set(1900 + 149, Calendar.DECEMBER, 1, 13, 0, 0);
Date late2049 = cal.getTime();
return late2049;
}
}
}