
org.globus.gsi.X509Credential Maven / Gradle / Ivy
The newest version!
/*
* Copyright 1999-2010 University of Chicago
*
* 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.globus.gsi;
import org.globus.gsi.util.CertificateIOUtil;
import org.globus.gsi.util.CertificateLoadUtil;
import org.globus.gsi.util.CertificateUtil;
import org.globus.gsi.util.ProxyCertificateUtil;
import org.globus.gsi.trustmanager.X509ProxyCertPathValidator;
import org.globus.gsi.stores.ResourceSigningPolicyStore;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import java.security.cert.CertStore;
import java.security.KeyStore;
import org.globus.common.CoGProperties;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.security.cert.CertificateException;
import org.globus.gsi.bc.BouncyCastleUtil;
import java.security.interfaces.RSAPrivateKey;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Vector;
import org.bouncycastle.util.encoders.Base64;
import org.globus.gsi.stores.Stores;
import org.globus.gsi.bc.BouncyCastleOpenSSLKey;
/**
* FILL ME
*
* This class equivalent was called GlobusCredential in CoG -maybe a better name?
*
* @author [email protected]
*/
// COMMENT: Added methods from GlobusCredential
// COMMENT: Do we need the getDefaultCred functionality?
public class X509Credential implements Serializable {
private static final long serialVersionUID = 1L;
public static final int BUFFER_SIZE = Integer.MAX_VALUE;
private static Log logger = LogFactory.getLog(X509Credential.class.getCanonicalName());
private OpenSSLKey opensslKey;
private X509Certificate[] certChain;
private static X509Credential defaultCred;
private static long credentialLastModified = -1;
// indicates if default credential was explicitely set
// and if so - if the credential expired it try
// to load the proxy from a file.
private static boolean credentialSet = false;
private static File credentialFile = null;
static {
new ProviderLoader();
}
public X509Credential(PrivateKey initKey, X509Certificate[] initCertChain) {
if (initKey == null) {
throw new IllegalArgumentException("Key cannot be null");
}
if ((initCertChain == null) || (initCertChain.length < 1)) {
throw new IllegalArgumentException("At least one public certificate required");
}
this.certChain = new X509Certificate[initCertChain.length];
System.arraycopy(initCertChain, 0, this.certChain, 0, initCertChain.length);
this.opensslKey = new BouncyCastleOpenSSLKey(initKey);
}
public X509Credential(InputStream certInputStream, InputStream keyInputStream) throws CredentialException {
if (certInputStream.markSupported()) {
certInputStream.mark(BUFFER_SIZE);
}
loadKey(keyInputStream);
loadCertificate(certInputStream);
validateCredential();
}
public X509Credential(String certFile, String keyFile) throws CredentialException, IOException {
loadKey(new FileInputStream(new File(keyFile)));
loadCertificate(new FileInputStream(new File(certFile)));
validateCredential();
}
public X509Credential(String proxyFile) throws CredentialException {
if (proxyFile == null) {
throw new IllegalArgumentException("proxy file is null");
}
logger.debug("Loading proxy file: " + proxyFile);
try {
InputStream in = new FileInputStream(proxyFile);
load(in);
} catch (FileNotFoundException f) {
throw new CredentialException("proxy not found");
}
}
public X509Credential(InputStream input) throws CredentialException {
load(input);
}
public X509Certificate[] getCertificateChain() {
X509Certificate[] returnArray = new X509Certificate[this.certChain.length];
System.arraycopy(this.certChain, 0, returnArray, 0, this.certChain.length);
return returnArray;
}
public PrivateKey getPrivateKey() throws CredentialException {
return getPrivateKey(null);
}
public PrivateKey getPrivateKey(String password) throws CredentialException {
if (this.opensslKey.isEncrypted()) {
if (password == null) {
throw new CredentialException("Key encrypted, password required");
} else {
try {
this.opensslKey.decrypt(password);
} catch (GeneralSecurityException exp) {
throw new CredentialException(exp.getMessage(), exp);
}
}
}
return this.opensslKey.getPrivateKey();
}
public boolean isEncryptedKey() {
return this.opensslKey.isEncrypted();
}
/**
* Reads Base64 encoded data from the stream and returns its decoded value. The reading continues until
* the "END" string is found in the data. Otherwise, returns null.
*/
private static byte[] getDecodedPEMObject(BufferedReader reader) throws IOException {
String line;
StringBuffer buf = new StringBuffer();
while ((line = reader.readLine()) != null) {
if (line.indexOf("--END") != -1) { // found end
return Base64.decode(buf.toString().getBytes());
} else {
buf.append(line);
}
}
throw new EOFException("Missing PEM end footer");
}
public void saveKey(OutputStream out) throws IOException {
this.opensslKey.writeTo(out);
out.flush();
}
// COMMENT Used to be "key cert cert cert ...", which is wrong afaik. must be "cert key cert cert ..."
public void saveCertificateChain(OutputStream out) throws IOException, CertificateEncodingException {
CertificateIOUtil.writeCertificate(out, this.certChain[0]);
for (int i = 1; i < this.certChain.length; i++) {
// skip the self-signed certificates
if (this.certChain[i].getSubjectDN().equals(certChain[i].getIssuerDN())) {
continue;
}
CertificateIOUtil.writeCertificate(out, this.certChain[i]);
}
out.flush();
}
public void save(OutputStream out) throws IOException, CertificateEncodingException {
CertificateIOUtil.writeCertificate(out, this.certChain[0]);
saveKey(out);
for (int i = 1; i < this.certChain.length; i++) {
// This will skip the self-signed certificates?
if (this.certChain[i].getSubjectDN().equals(certChain[i].getIssuerDN())) {
continue;
}
CertificateIOUtil.writeCertificate(out, this.certChain[i]);
}
out.flush();
}
public void writeToFile(File file) throws IOException, CertificateEncodingException {
writeToFile(file, file);
}
public void writeToFile(File certFile, File keyFile) throws IOException, CertificateEncodingException {
FileOutputStream keyOutputStream = null;
FileOutputStream certOutputStream = null;
try {
keyOutputStream = new FileOutputStream(keyFile);
certOutputStream = new FileOutputStream(certFile);
saveKey(keyOutputStream);
saveCertificateChain(certOutputStream);
} finally {
try {
if (keyOutputStream != null) {
keyOutputStream.close();
}
} catch (IOException e) {
logger.warn("Could not close stream on save of key to file. " + keyFile.getPath());
}
try {
if (certOutputStream != null) {
certOutputStream.close();
}
} catch (IOException e) {
logger.warn("Could not close stream on save certificate chain to file. " + certFile.getPath());
}
}
}
public Date getNotBefore() {
Date notBefore = this.certChain[0].getNotBefore();
for (int i = 1; i < this.certChain.length; i++) {
Date date = this.certChain[i].getNotBefore();
if (date.before(notBefore)) {
notBefore = date;
}
}
return notBefore;
}
/**
* Returns the number of certificates in the credential without the self-signed certificates.
*
* @return number of certificates without counting self-signed certificates
*/
public int getCertNum() {
for (int i = this.certChain.length - 1; i >= 0; i--) {
if (!this.certChain[i].getSubjectDN().equals(this.certChain[i].getIssuerDN())) {
return i + 1;
}
}
return this.certChain.length;
}
/**
* Returns strength of the private/public key in bits.
*
* @return strength of the key in bits. Returns -1 if unable to determine it.
*/
public int getStrength() throws CredentialException {
return getStrength(null);
}
/**
* Returns strength of the private/public key in bits.
*
* @return strength of the key in bits. Returns -1 if unable to determine it.
*/
public int getStrength(String password) throws CredentialException {
if (opensslKey == null) {
return -1;
}
if (this.opensslKey.isEncrypted()) {
if (password == null) {
throw new CredentialException("Key encrypted, password required");
} else {
try {
this.opensslKey.decrypt(password);
} catch (GeneralSecurityException exp) {
throw new CredentialException(exp.getMessage(), exp);
}
}
}
return ((RSAPrivateKey)opensslKey.getPrivateKey()).getModulus().bitLength();
}
/**
* Returns the subject DN of the first certificate in the chain.
*
* @return subject DN.
*/
public String getSubject() {
return this.certChain[0].getSubjectDN().getName();
}
/**
* Returns the issuer DN of the first certificate in the chain.
*
* @return issuer DN.
*/
public String getIssuer() {
return this.certChain[0].getIssuerDN().getName();
}
/**
* Returns the certificate type of the first certificate in the chain. Returns -1 if unable to determine
* the certificate type (an error occurred)
*
* @see BouncyCastleUtil#getCertificateType(X509Certificate)
*
* @return the type of first certificate in the chain. -1 if unable to determine the certificate type.
*/
public GSIConstants.CertificateType getProxyType() {
try {
return BouncyCastleUtil.getCertificateType(this.certChain[0]);
} catch (CertificateException e) {
logger.error("Error getting certificate type.", e);
return GSIConstants.CertificateType.UNDEFINED;
}
}
/**
* Returns time left of this credential. The time left of the credential is based on the certificate with
* the shortest validity time.
*
* @return time left in seconds. Returns 0 if the certificate has expired.
*/
public long getTimeLeft() {
Date earliestTime = null;
for (int i = 0; i < this.certChain.length; i++) {
Date time = this.certChain[i].getNotAfter();
if (earliestTime == null || time.before(earliestTime)) {
earliestTime = time;
}
}
long diff = (earliestTime.getTime() - System.currentTimeMillis()) / 1000;
return (diff < 0) ? 0 : diff;
}
/**
* Returns the identity of this credential.
* @see #getIdentityCertificate()
*
* @return The identity cert in Globus format (e.g. /C=US/..). Null,
* if unable to get the identity (an error occurred)
*/
public String getIdentity() {
try {
return BouncyCastleUtil.getIdentity(this.certChain);
} catch (CertificateException e) {
logger.debug("Error getting certificate identity.", e);
return null;
}
}
/**
* Returns the identity certificate of this credential. The identity certificate is the first certificate
* in the chain that is not an impersonation proxy certificate.
*
* @return X509Certificate
the identity cert. Null, if unable to get the identity certificate
* (an error occurred)
*/
public X509Certificate getIdentityCertificate() {
try {
return BouncyCastleUtil.getIdentityCertificate(this.certChain);
} catch (CertificateException e) {
logger.debug("Error getting certificate identity.", e);
return null;
}
}
/**
* Returns the path length constraint. The shortest length in the chain of
* certificates is returned as the credential's path length.
*
* @return The path length constraint of the credential. -1 is any error
* occurs.
*/
public int getPathConstraint() {
int pathLength = Integer.MAX_VALUE;
try {
for (int i=0; i
* The credential will be loaded on the initial call. It must not be expired. All subsequent calls to this
* function return cached credential object. Once the credential is cached, and the underlying file
* changes, the credential will be reloaded.
*
* @return the default credential.
* @exception CredentialException
* if the credential expired or some other error with the credential.
*/
public synchronized static X509Credential getDefaultCredential() throws CredentialException {
if (defaultCred == null) {
reloadDefaultCredential();
} else if (!credentialSet) {
if (credentialFile.lastModified() == credentialLastModified) {
defaultCred.verify();
} else {
defaultCred = null;
reloadDefaultCredential();
}
}
return defaultCred;
}
private static void reloadDefaultCredential()
throws CredentialException {
String proxyLocation = CoGProperties.getDefault().getProxyFile();
defaultCred = new X509Credential(proxyLocation);
credentialFile = new File(proxyLocation);
credentialLastModified = credentialFile.lastModified();
defaultCred.verify();
}
/**
* Sets default credential.
*
* @param cred
* the credential to set a default.
*/
public synchronized static void setDefaultCredential(X509Credential cred) {
defaultCred = cred;
credentialSet = (cred != null);
}
// COMMENT: In case of an exception because of missing password with an
// encrypted key: put in -1 as strength
public String toString() {
String lineSep = System.getProperty("line.separator");
StringBuffer buf = new StringBuffer();
buf.append("subject : ").append(getSubject()).append(lineSep);
buf.append("issuer : ").append(getIssuer()).append(lineSep);
int strength = -1;
try {
strength = this.getStrength();
} catch(Exception e) {}
buf.append("strength : ").append(strength).append(lineSep);
buf.append("timeleft : ").append(getTimeLeft() + " sec").append(lineSep);
buf.append("proxy type : ").append(ProxyCertificateUtil.getProxyTypeAsString(getProxyType()));
return buf.toString();
}
protected void load(InputStream input) throws CredentialException {
if (input == null) {
throw new IllegalArgumentException("input stream cannot be null");
}
X509Certificate cert = null;
Vector chain = new Vector(3);
String line;
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(input));
while ((line = reader.readLine()) != null) {
if (line.indexOf("BEGIN CERTIFICATE") != -1) {
byte[] data = getDecodedPEMObject(reader);
cert = CertificateLoadUtil.loadCertificate(new ByteArrayInputStream(data));
chain.addElement(cert);
} else if (line.indexOf("BEGIN RSA PRIVATE KEY") != -1) {
byte[] data = getDecodedPEMObject(reader);
this.opensslKey = new BouncyCastleOpenSSLKey("RSA", data);
}
}
} catch (Exception e) {
throw new CredentialException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
int size = chain.size();
if (size == 0) {
throw new CredentialException("no certs");
}
if (opensslKey == null) {
throw new CredentialException("no key");
}
// set chain
this.certChain = new X509Certificate[size];
chain.copyInto(certChain);
}
protected void loadCertificate(InputStream input) throws CredentialException {
if (input == null) {
throw new IllegalArgumentException("Input stream to load X509Credential is null");
}
X509Certificate cert;
Vector chain = new Vector();
String line;
BufferedReader reader = null;
try {
if (input.markSupported()) {
input.reset();
}
reader = new BufferedReader(new InputStreamReader(input));
while ((line = reader.readLine()) != null) {
if (line.indexOf("BEGIN CERTIFICATE") != -1) {
byte[] data = getDecodedPEMObject(reader);
cert = CertificateLoadUtil.loadCertificate(new ByteArrayInputStream(data));
chain.addElement(cert);
}
}
} catch (IOException e) {
throw new CredentialException(e);
} catch (GeneralSecurityException e) {
throw new CredentialException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.debug("error closing reader", e);
// This is ok
}
}
}
int size = chain.size();
if (size > 0) {
this.certChain = new X509Certificate[size];
chain.copyInto(this.certChain);
}
}
protected void loadKey(InputStream input) throws CredentialException {
// JGLOBUS-95: BC seems to have some PEM utility but the actual
// load is in private methods and cannot be leveraged.
// Investigate availability of standard libraries for these
// low level reads. FOr now, copying from CoG
try {
this.opensslKey = new BouncyCastleOpenSSLKey(input);
} catch (IOException e) {
throw new CredentialException(e.getMessage(), e);
} catch (GeneralSecurityException e) {
throw new CredentialException(e.getMessage(), e);
}
}
private void validateCredential() throws CredentialException {
if (this.certChain == null) {
throw new CredentialException("No certificates found");
}
int size = this.certChain.length;
if (size < 0) {
throw new CredentialException("No certificates found.");
}
if (this.opensslKey == null) {
throw new CredentialException("NO private key found");
}
}
@Override
public boolean equals(Object object) {
if(object == this) {
return true;
}
if(!(object instanceof X509Credential)) {
return false;
}
X509Credential other = (X509Credential) object;
return Arrays.equals(this.certChain, other.certChain) &&
this.opensslKey.equals(other.opensslKey);
}
@Override
public int hashCode() {
return (certChain == null ? 0 : Arrays.hashCode(certChain)) ^
opensslKey.hashCode();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy