Java tutorial
/* * Copyright (c) Members of the EGEE Collaboration. 2004. See * http://www.eu-egee.org/partners/ for details on the copyright holders. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.glite.security.util.proxy; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.TimeZone; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DEREncodable; import org.bouncycastle.asn1.DERObject; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.asn1.x509.X509Name; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.JDKKeyPairGenerator; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.glite.security.util.PrivateKeyReader; /** * A class to make proxy certificates. * * @author Joni Hahkala */ public class ProxyCertificateGenerator { /** The logging facility. */ private static final Logger LOGGER = Logger.getLogger(ProxyCertificateGenerator.class); /** The parent cert. */ private X509Certificate m_parentCert = null; /** The parent cert chain. */ private X509Certificate[] m_parentCertChain = null; /** The issuer name the basis for the new DN. */ private X509Name m_baseName = null; /** The private key of the new proxy, can be null as it is only needed for getting the whole proxy as pem, have to be * explicitly set if needed and not generated. */ private PrivateKey m_privateKey = null; /** The public key of the new proxy, can be given as a request or by explicitly setting it. */ private PublicKey m_publicKey = null; /** The 12h default lifetime of the new proxy. */ private int m_lifetime = 12 * 60 * 60; /** The certificate generator to use. */ private X509V3CertificateGenerator m_certGen = null; /** The new generated proxy. */ private X509Certificate m_newProxy = null; /** The serial number of the new proxy. */ private BigInteger m_serialNumber = null; /** The oid of the rfc proxy policy. */ private String m_proxyPolicyOID = null; /** The contents of the rfc proxy policy. */ private DEROctetString m_proxyPolicyOctets = null; /** The DN of the new proxy. */ private X509Name m_newDN = null; /** The type of the proxy. */ private int m_type = -1; /** Whether the proxy is limited (invalid for job submission) or not. */ private boolean m_limited = false; /** The message digest algorithm to use, take it from the given parent certificate. */ private String m_hashAlgorithm = null; /** The keylegth to use for this proxy. */ private int m_keyLength = DEFAULT_KEY_LENGTH; /** The proxy path length limit for rfc proxies. */ private int m_pathLenLimit = ProxyCertInfoExtension.UNLIMITED; /** The default proxy type if none is set and the cert given is end entity cert. Default is RFC3820 proxy. */ public static final int DEFAULT_PROXY_TYPE = ProxyCertificateInfo.RFC3820_PROXY; // the default type of proxy /** The default key length for the proxy (1024 bits). */ public static final int DEFAULT_KEY_LENGTH = 1024; /** static class constructor to make sure the Boucycastle provider is set */ static { if (Security.getProvider("BC") == null) { Security.insertProviderAt(new BouncyCastleProvider(), 6); } } /** * Create a new proxy cert generator based on the parent cert chain. Useful when locally creating a proxy from * existing cert chain. * * @param parentCertChain the parent certificate chain of the proxy. */ public ProxyCertificateGenerator(X509Certificate[] parentCertChain) { m_parentCertChain = parentCertChain; m_parentCert = parentCertChain[0]; m_baseName = (X509Name) m_parentCert.getSubjectDN(); m_hashAlgorithm = m_parentCert.getSigAlgName(); ProxyCertificateInfo parentProxyInfo = new ProxyCertificateInfo(m_parentCert); // the new proxy will be of the same type as the parent. // the type can also be unknown, which will be handled in generate() m_type = parentProxyInfo.getProxyType(); m_certGen = new X509V3CertificateGenerator(); } /** * Create a new proxy cert generator based on the parent cert. Useful when locally creating a proxy from existing * cert. * * @param parentCert the parent certificate chain of the proxy. */ public ProxyCertificateGenerator(X509Certificate parentCert) { this(new X509Certificate[] { parentCert }); } /** * Create a new proxy cert generator based on certification request and a certificate chain. Used for example when * creating a proxy certificate on the client side from certificate request coming from a service. * * @param parentCertChain The parent cert chain of the proxy. * @param certReq The certification request to generate the certificate from. * @throws InvalidKeyException Thrown if the public key in the request is invalid. * @throws NoSuchAlgorithmException Thrown if the request uses unsupported algorithm. * @throws NoSuchProviderException Thrown if the bouncycastle provider was not found. */ public ProxyCertificateGenerator(X509Certificate[] parentCertChain, PKCS10CertificationRequest certReq) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException { this(parentCertChain); // m_certReq = certReq; m_publicKey = certReq.getPublicKey(); m_newDN = certReq.getCertificationRequestInfo().getSubject(); /* * // test for DN violation, the new DN must be composed of the parentDN // and and additional CN component. DN * baseDN = DNHandler.getSubject(m_parentCert); DN reqSubject = DNHandler.getDN(m_newDN); try{ * ProxyCertUtil.checkProxyDN(baseDN, reqSubject); } catch(IllegalArgumentException e){ throw new * IllegalArgumentException("Got an invalid proxy request subject, '" + reqSubject + * "' is not a valid proxy subject for '" + baseDN + "', error was: " + e.getMessage()); } */ // in case the parent was unknown type, deduce the type from the cert // req. in case it's not legacy and not set later, use default in generate(). if (m_type == ProxyCertificateInfo.UNKNOWN_PROXY_TYPE) { if (ProxyCertificateInfo.isLegacyDN(m_newDN)) { m_type = ProxyCertificateInfo.LEGACY_PROXY; } } // if the proxy is not legacy proxy, try to figure out the serial number from the DN of the request. if (m_type != ProxyCertificateInfo.LEGACY_PROXY) { BigInteger sn = ProxyCertUtil.getSN(m_newDN); if (sn != null) { m_serialNumber = sn; } } m_certGen = new X509V3CertificateGenerator(); } /** * Create a new proxy cert generator based on certification request and a certificate. Used for example when * creating a proxy certificate on the client side from certificate request coming from a service. * * @param parentCert * @param certReq * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ public ProxyCertificateGenerator(X509Certificate parentCert, PKCS10CertificationRequest certReq) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException { this(new X509Certificate[] { parentCert }, certReq); } /** * Set the proxy lifetime. If not set, the default is 12h. * * @param lifetime the lifetime in seconds. (+-5min grace period will be added to the lifetime.) */ public void setLifetime(int lifetime) { this.m_lifetime = lifetime; } /** * Add an extension to the proxy certificate to be generated. * * @param oid the object identifier of the extension. * @param critical whether the extension is critical or not. * @param value The extension value. */ public void addExtension(String oid, boolean critical, DEREncodable value) { m_certGen.addExtension(new DERObjectIdentifier(oid), critical, value); } /** * Set up the basic common proxy fields. */ private void setupBasicProxy() { setupDates(); m_certGen.setPublicKey(m_publicKey); m_certGen.setSignatureAlgorithm(m_parentCert.getSigAlgName()); m_certGen.addExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.dataEncipherment | KeyUsage.digitalSignature | KeyUsage.keyEncipherment)); } /** * Generate the proxy certificate object. * * @param privateKey the private key used to sign the proxy certificate. * @throws InvalidKeyException * @throws SignatureException * @throws NoSuchAlgorithmException * @throws CertificateEncodingException */ public void generate(PrivateKey privateKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException, CertificateEncodingException { // test if the keys are not set, and generate them if they aren't if (m_publicKey == null) { if (m_privateKey != null && m_publicKey == null) { throw new IllegalArgumentException( "Only private key of the proxy is set. As it is, also public key has to be set."); } generateKeys(); } // default to RFC proxy id all else fails. if (m_type == ProxyCertificateInfo.UNKNOWN_PROXY_TYPE) { m_type = DEFAULT_PROXY_TYPE; } switch (m_type) { case ProxyCertificateInfo.LEGACY_PROXY: setupOldProxy(m_limited); break; case ProxyCertificateInfo.RFC3820_PROXY: setupRFC3280Proxy(m_serialNumber, m_proxyPolicyOID, m_proxyPolicyOctets, m_pathLenLimit, true); break; case ProxyCertificateInfo.DRAFT_RFC_PROXY: setupRFC3280Proxy(m_serialNumber, m_proxyPolicyOID, m_proxyPolicyOctets, m_pathLenLimit, false); break; default: throw new IllegalArgumentException("Unknown or unsupported proxy type"); } m_certGen.setIssuerDN(m_baseName); m_certGen.setSignatureAlgorithm(m_hashAlgorithm); // if the new DN is set (cert request), use it, otherwise set it up. /* * if (m_newDN != null) { m_certGen.setSubjectDN(m_newDN); } else { String newCN = guessCN(m_baseName, false); * setupDNs(newCN); } */ m_newProxy = m_certGen.generate(privateKey); } /** * Generates the private and public keys if they are not given. */ private void generateKeys() { JDKKeyPairGenerator.RSA keyPairGen = new JDKKeyPairGenerator.RSA(); keyPairGen.initialize(m_keyLength, new SecureRandom()); KeyPair pair = keyPairGen.generateKeyPair(); m_privateKey = pair.getPrivate(); m_publicKey = pair.getPublic(); } /** * Returns the certificate chain of the proxy. * * @return the Certificate chain starting with the CA or end entity certificate and ending with the latest proxy. */ public X509Certificate[] getCertChain() { X509Certificate[] newChain = new X509Certificate[m_parentCertChain.length + 1]; for (int i = 0; i < m_parentCertChain.length; i++) { newChain[i + 1] = m_parentCertChain[i]; } newChain[0] = m_newProxy; return newChain; } /** * Returns the generated or set private key of this proxy. * * @return The private key. */ public PrivateKey getPrivateKey() { return m_privateKey; } /** * Gives the certificate chain containing the proxy in PEM format. * * @return the Certificate chain in PEM format, starting with the latest proxy and ending with either the end entity * user certificate or CA certificate, depending on the input given chen callin the constructor. * @throws IOException In case there are string manipulation problems. */ public String getCertChainAsPEM() throws IOException { X509Certificate[] newChain = getCertChain(); StringWriter writer = new StringWriter(); PEMWriter pemWriter = new PEMWriter(writer); for (int i = 0; i < newChain.length; i++) { pemWriter.writeObject(newChain[i]); } pemWriter.flush(); return writer.toString(); } /** * Gives the private key of the proxy if the keys were generated or set using setPrivateKey. * * @return The private key of the proxy in PEM format. */ public String getPrivateKeyAsPEM() { return PrivateKeyReader.getPEM(m_privateKey); } /** * Gives the proxy credentials in PEM encoded certificate chain containing the private key in unencrypted format. * See: http://dev.globus.org/wiki/Security/ProxyFileFormat * * @return The PEM encoded proxy credentials as a String. * @throws IOException In case the string manipulations fail. */ public String getProxyAsPEM() throws IOException { StringWriter writer = new StringWriter(); PEMWriter pemWriter = new PEMWriter(writer); pemWriter.writeObject(m_newProxy); pemWriter.write(getPrivateKeyAsPEM()); for (int i = m_parentCertChain.length - 1; i >= 0; i--) { pemWriter.writeObject(m_parentCertChain[i]); } pemWriter.flush(); return writer.toString(); } /** * Generates a new proxy DN based on the basename. If newCN is given, it is added to the end of the DN and the new * DN is returned. If newCN is null, the basename is analyzed. In case of old proxy DN, either "CN=proxy" or * "CN=limited proxy" is added depending on the value of limited argument. In case of new style proxy or nonproxy * DN, new style proxy is assumed and "CN=" with random number following it is added. * * @param basename The DN to use as the basis of the new DN. * @param inputCN If given, this is used as the new CN value. * @param limited in case the newCN is not given and the basename is old style proxy, setting this to true will * generate limited proxy. * @return the new DN. */ @SuppressWarnings("unchecked") public X509Name generateDN(X509Name basename, String inputCN, boolean limited) { if (basename == null) { throw new IllegalArgumentException("generateDN: no basename given, can't generate DN."); } String newCN; if (inputCN == null) { // if no CN part given, guess it newCN = guessCN(basename, limited); } else { newCN = inputCN; } // generate new cn part ASN1EncodableVector newCnPart = new ASN1EncodableVector(); newCnPart.add(X509Name.CN); newCnPart.add(new DERPrintableString(newCN)); // copy the RDNs to a new vector so that the new part can be added. ASN1Sequence subjectSequence = (ASN1Sequence) basename.getDERObject(); Enumeration subjectParts = subjectSequence.getObjects(); ASN1EncodableVector subjectVector = new ASN1EncodableVector(); while (subjectParts.hasMoreElements()) { DERObject part = (DERObject) subjectParts.nextElement(); subjectVector.add(part); } subjectVector.add(new DERSet(new DERSequence(newCnPart))); // transform the vector into a new X509Name DERSequence subjDerSeq = new DERSequence(subjectVector); X509Name proxySubject = new X509Name(subjDerSeq); LOGGER.debug("SubjectDN :" + proxySubject); return proxySubject; } /** * Guesses the value of the CN based on the basename DN. See generateDN for the logic. * * @param basename the DN to use as the base of the guessing. * @param addLimited whether the new proxy will be limited or not in case the guess is olds style proxy. * @return the new CN string. */ private String guessCN(X509Name basename, boolean addLimited) { String newCN; ASN1Sequence subjectSequence = (ASN1Sequence) basename.getDERObject(); int rdns = subjectSequence.size(); DERSet rdn = (DERSet) subjectSequence.getObjectAt(rdns - 1); DERSequence rdnSequence = (DERSequence) rdn.getObjectAt(0); DERObjectIdentifier oid = (DERObjectIdentifier) rdnSequence.getObjectAt(0); if (oid.equals(X509Name.CN)) { String cn = rdnSequence.getObjectAt(1).toString(); if (cn.equals("proxy")) { // old style unlimited proxy if (addLimited) { // new proxy will be limited newCN = "limited proxy"; } else { // new proxy will still be unlimited newCN = "proxy"; } } else { if (cn.equals("limited proxy")) { // in case the proxy is old // style limited proxy, new // one will be old style // limited too newCN = "limited proxy"; } else { // otherwise generate new random number to use as CN. newCN = getSerialNumber().toString(); } } } else { // in case the DN doesn't end with a CN, assume new style proxy newCN = getSerialNumber().toString(); } return newCN; } /** * Adds a new CN part to the end of the DN and sets it as the subject DN. Also sets the issuer DN. * * @param newCn The string to be added as the CN value. */ @SuppressWarnings("unchecked") private void setupDNs(String newCn) { ASN1Sequence seqSubject = (ASN1Sequence) m_baseName.getDERObject(); ASN1EncodableVector newCnPart = new ASN1EncodableVector(); newCnPart.add(X509Name.CN); newCnPart.add(new DERPrintableString(newCn)); Enumeration subjectParts = seqSubject.getObjects(); ASN1EncodableVector subjectVector = new ASN1EncodableVector(); while (subjectParts.hasMoreElements()) { DERObject part = (DERObject) subjectParts.nextElement(); subjectVector.add(part); } subjectVector.add(new DERSet(new DERSequence(newCnPart))); DERSequence subjDerSeq = new DERSequence(subjectVector); X509Name proxySubject = new X509Name(subjDerSeq); m_newDN = proxySubject; LOGGER.debug("SubjectDN :" + proxySubject); m_certGen.setSubjectDN(proxySubject); m_certGen.setIssuerDN(m_baseName); } /** * Used to set the type of the proxy. Useful only in case the parent certificate is user certificate, otherwise the * generator will generate same type of proxy as the parent is. And trying to set different type here than in the * parent will result in IllegalArgumentException. If the parent certificate is user certificate and this method is * not used, BasicProxyCertificate.RFC3820_PROXY will be assumed. * * @param type The type, see the type definitions in BasicProxyCertificate class. * @throws IllegalArgumentException In case trying to set the type to a different one than parent, if it is a proxy * certificate. */ public void setType(int type) throws IllegalArgumentException { // setting to same type as determined is OK. if (m_type == type) { return; } // in case the determined type is unknown, any type will be allowed. if (type == ProxyCertificateInfo.LEGACY_PROXY || type == ProxyCertificateInfo.RFC3820_PROXY || type == ProxyCertificateInfo.DRAFT_RFC_PROXY) { if (m_type == ProxyCertificateInfo.UNKNOWN_PROXY_TYPE) { m_type = type; return; } // otherwise at least warn that the types don't match, // maybe should throw an exception, but it might be useful // at least for test certs to be able to do invalid chains. LOGGER.warn( "The proxy type setting is not the one of the parent or the cert request. Setting is:" + type); m_type = type; } throw new IllegalArgumentException("Trying to set the proxy type into an unsupported type"); } /** * Generates an old style proxy (CN=proxy ending). * * @param limited whether the CN should have "CN=limited proxy" instead of just "CN=proxy". */ private void setupOldProxy(boolean limited) { if (limited) { setupDNs("limited proxy"); } else { setupDNs("proxy"); } setupBasicProxy(); BigInteger serialNum = null; serialNum = m_parentCert.getSerialNumber(); m_certGen.setSerialNumber(serialNum); } /** * Generates an rfc3820 style proxy (CN=234151 and proxy extension). * * @param cn Optional serial number to use as the CN part and certificate serial number. If it is null, The one from * the certification request is taken or one will be created. * @param policyOID optional proxy policy oid, if not given, the delegate all oid is used. * @param policy optional proxy policy. If no policy is given the oid is assumed to suffice. * @param pathLenLimit number of subproxies the proxy can have. If null, no restrictions are set. * @param isRfc flag to inform that the proxy is a normal RFC 3820 proxy. Setting this to false will result with a * draft rfc proxy, which has a different OID for the proxyInfoExtension. */ private void setupRFC3280Proxy(BigInteger cn, String policyOID, DEROctetString policy, int pathLenLimit, boolean isRfc) { setupBasicProxy(); BigInteger serial; if (cn == null) { serial = getSerialNumber(); } else { serial = cn; } m_certGen.setSerialNumber(serial); setupDNs(serial.toString()); ProxyPolicy proxyPolicy; if (m_limited) { if (policyOID == null || policyOID.equals(ProxyPolicy.LIMITED_PROXY_OID)) { proxyPolicy = new ProxyPolicy(ProxyPolicy.LIMITED_PROXY_OID, policy); } else { throw new IllegalArgumentException( "Proxy info extension policy OID set to conflicting value when limiting proxy. OID is: " + policyOID); } } else { proxyPolicy = new ProxyPolicy(policyOID, policy); } ProxyCertInfoExtension extension = new ProxyCertInfoExtension(pathLenLimit, proxyPolicy); if (isRfc) { m_certGen.addExtension(ProxyCertInfoExtension.PROXY_CERT_INFO_EXTENSION_OID, true, extension); } else { m_certGen.addExtension(ProxyCertInfoExtension.DRAFT_PROXY_CERT_INFO_EXTENSION_OID, true, extension); } } /** * Sets up the dates in the proxy. The new proxy lifetime is the one set by setLifetime method unless the parent * proxy lifetime is shorter in which case the parent lifetime is used. <br> * <br> * A 5 minute grace time is added to both ends to avoid problems with time skew between machines. So, the real * lifetime is the given lifetime +10 minutes. */ private void setupDates() { // set the not before to 5 minutes ago. GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); date.add(Calendar.MINUTE, -5); m_certGen.setNotBefore(date.getTime()); // reverse negative grace of not before and add 5min grace to the end. date.add(Calendar.MINUTE, 10); date.add(Calendar.SECOND, m_lifetime); // proxy can't be longer than the parent, so limit it to that. if (m_parentCert != null) { Date parentNotAfter = m_parentCert.getNotAfter(); if (parentNotAfter.before(date.getTime())) { date.setTime(parentNotAfter); } } m_certGen.setNotAfter(date.getTime()); } /** * Sets the length of the keys to be generated, only used if the keys are not set separately. If this method is not * used, the default is 1024 bits. * * @param length The key length in bits. */ public void setKeyLength(int length) { m_keyLength = length; } /** * Defines that the resulting proxy will be limited proxy, meaning job submission with is prevented. */ public void setLimited() { m_limited = true; } /** * Returns the given or discovered serial number of the new proxy, or generates the serial number if not previously * set. * * @return The previously set serial number or a new generated one. */ private BigInteger getSerialNumber() { if (m_serialNumber == null) { SecureRandom rand = new SecureRandom(); m_serialNumber = BigInteger.valueOf(rand.nextInt()).abs(); } return m_serialNumber; } /** * Sets the new proxy serial number. Only applicable for rfc proxies. * * @param sn The serial number for the new proxy. */ public void setSerialNumber(BigInteger sn) { m_serialNumber = sn; } /** * Set the RFC proxy proxy extension policy OID and octets of the policy. See RFC3820. Policy can be null in case * the OID in it self defines the behaviour, like with "inherit all" policy or "independent" policy. * * @param oid The oid of the policy language. * @param octets The actual policy info encoded as DEROctetString. */ public void setPolicy(String oid, DEROctetString octets) { if (m_type == ProxyCertificateInfo.LEGACY_PROXY) { throw new IllegalArgumentException("Legacy proxies do not support setting the proxy policy."); } m_proxyPolicyOID = oid; m_proxyPolicyOctets = octets; } /** * Sets the proxy path length limit of this certificate. Only works on rfc3820 and RFC draft proxies. * * @param pathLen The number of allowed proxy certificates in the chain allowed after this certificate. * ProxyCertInfoExtension.UNLIMITED if not set. */ public void setProxyPathLimit(int pathLen) { if (m_type == ProxyCertificateInfo.LEGACY_PROXY) { throw new IllegalArgumentException( "Legacy proxies do not support setting the proxy path length limit."); } m_pathLenLimit = pathLen; } /** * Sets the proxy source restriction data. * * @param data The data for the source restriction extension. */ public void setProxySourceRestrictions(ProxyRestrictionData data) { m_certGen.addExtension(ProxyRestrictionData.SOURCE_RESTRICTION_OID, false, data.getNameConstraints()); } /** * Sets the proxy target restriction data. * * @param data The data for the target restriction extension. */ public void setProxyTargetRestrictions(ProxyRestrictionData data) { m_certGen.addExtension(ProxyRestrictionData.TARGET_RESTRICTION_OID, false, data.getNameConstraints()); } /** * Sets the issuer URL for the proxy tracing. * * @param url The proxy tracing issuer URL in String format. */ public void setProxyTracingIssuer(String url) { ProxyTracingExtension extension = new ProxyTracingExtension(url); m_certGen.addExtension(ProxyTracingExtension.PROXY_TRACING_ISSUER_EXTENSION_OID, false, extension.getNames()); } /** * Sets the subject URL for the proxy tracing. * * @param url The proxy tracing subject URL in String format. */ public void setProxyTracingSubject(String url) { ProxyTracingExtension extension = new ProxyTracingExtension(url); m_certGen.addExtension(ProxyTracingExtension.PROXY_TRACING_SUBJECT_EXTENSION_OID, false, extension.getNames()); } }