Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Thank you for the quicky reply! that's exactly the kind of detail I was missing.

For anyone that stumbled on to this, here's some sample code. I'n using openstack4j as my communication library, I wrote a thin wrapper to request the certificates from OS as a String, and bouncy castle for the signature verification.

This code needs more cleanup / handle multiple certificates but it shows the workflow:


<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.51</version>
</dependency>

<dependency>
    <groupId>org.pacesys</groupId>
    <artifactId>openstack4j</artifactId>
    <version>1.0.1</version>

</dependency>

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.openstack4j.openstack.identity.domain.KeystoneAccess;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection;
import java.util.Date;

    public static SignerInformationVerifier createTokenVerifier(String aInSigningCertificate) throws IOException, OperatorCreationException
{
    // The cert is PEM encoded - need to translate those bytes into a PEM object
    Reader lCertBufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(aInSigningCertificate.getBytes())));
    PemObject lPemObj = new PemReader(lCertBufferedReader).readPemObject();

    // Create our verify builder - basically we need to make an object that will verify the cert
    BcRSASignerInfoVerifierBuilder signerInfoBuilder = new BcRSASignerInfoVerifierBuilder(
            new DefaultCMSSignatureAlgorithmNameGenerator(),
            new DefaultSignatureAlgorithmIdentifierFinder(),
            new DefaultDigestAlgorithmIdentifierFinder(),
            new BcDigestCalculatorProvider());

    // Using the PEM object, create a cert holder and a verifier for the cert
    SignerInformationVerifier lVerifier = signerInfoBuilder.build(new X509CertificateHolder(lPemObj.getContent()));

    return lVerifier;
}

    public static CMSSignedData getSignedDataFromRawToken(String lRawKeystoneToken) throws CMSException
{
    // Keystone takes all '/' characters and replaced them by '-' in order to
    // encode the token into base 64. Let's reverse that..
    String lRealTokenData = lRawKeystoneToken.replace("-", "/");
    byte[] lData = Base64.decodeBase64(lRealTokenData.getBytes());

    // Now that we have the raw encoded token we can make a CMSSigned data out of it
    CMSSignedData lSignedData = new CMSSignedData(lData);
    return lSignedData;
}

    public static String getTokenContentAsString(CMSSignedData aInSignedData)
{
    Object lObj = aInSignedData.getSignedContent().getContent();
    if (lObj instanceof byte[])
    {
        String lObjString = new String((byte[]) lObj);
        return lObjString;
    }
    return null;
}

    public static boolean isValidTokenSignature(CMSSignedData aInSignedData, SignerInformationVerifier aInVerifier) throws CMSException
{
    // The token contained the signer Info and has been parsed out
    // For each signer on the token, attempt to verify against the certificate
    SignerInformationStore lSignerInfo = aInSignedData.getSignerInfos();
    Collection lSigners = lSignerInfo.getSigners();
    for (Object lObj : lSigners)
    {
        if (lObj instanceof SignerInformation)
        {
            SignerInformation lSigner = (SignerInformation) lObj;
            boolean lIsValid = lSigner.verify(aInVerifier);
            if (lIsValid)
            {
                return true;
            }
        }
    }
    return false;
}


    // Pki client is a just a class that requests cert info from Keystone
    PkiClient lPki = new PkiClient(lConfig);
    String lCaCert = lPki.getCaCertificate();
    String lSigningCert = lPki.getSigningCertificate();

    ObjectMapper lObjMapper = new ObjectMapper();
    lObjMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
    lObjMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
    lObjMapper.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    lObjMapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);

    try
    {
        CMSSignedData lSignedData = getSignedDataFromRawToken(lClient.getToken().getId());
        SignerInformationVerifier lVerifier = createTokenVerifier(lSigningCert);
        boolean isValidToken = isValidTokenSignature(lSignedData, lVerifier);
        if (isValidToken)
        {
            String lJsonToken = getTokenContentAsString(lSignedData);

            try
            {
                KeystoneAccess lKeystoneAccess = lObjMapper.readValue(lJsonToken, KeystoneAccess.class);
                Date lExpiresAt = lKeystoneAccess.getToken().getExpires();
                if (lExpiresAt.getTime() <= System.currentTimeMillis())
                {
                    System.out.println("Token for user " + lKeystoneAccess.getUser().getId() + " has expired!");
                }

                // todo check revoked list
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
        throw new RuntimeException();
    }
click to hide/show revision 2
No.2 Revision

Thank you for the quicky reply! that's exactly the kind of detail I was missing.

For anyone that stumbled on to this, here's some sample code. I'n using openstack4j as my communication library, I wrote a thin wrapper to request the certificates from OS as a String, and bouncy castle for the signature verification.

This code needs more cleanup / handle multiple certificates but it shows the workflow:


<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.51</version>
</dependency>

<dependency>
    <groupId>org.pacesys</groupId>
    <artifactId>openstack4j</artifactId>
    <version>1.0.1</version>
</dependency>

</dependency>


import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.openstack4j.openstack.identity.domain.KeystoneAccess;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection;
import java.util.Date;

    public static SignerInformationVerifier createTokenVerifier(String aInSigningCertificate) throws IOException, OperatorCreationException
{
    // The cert is PEM encoded - need to translate those bytes into a PEM object
    Reader lCertBufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(aInSigningCertificate.getBytes())));
    PemObject lPemObj = new PemReader(lCertBufferedReader).readPemObject();

    // Create our verify builder - basically we need to make an object that will verify the cert
    BcRSASignerInfoVerifierBuilder signerInfoBuilder = new BcRSASignerInfoVerifierBuilder(
            new DefaultCMSSignatureAlgorithmNameGenerator(),
            new DefaultSignatureAlgorithmIdentifierFinder(),
            new DefaultDigestAlgorithmIdentifierFinder(),
            new BcDigestCalculatorProvider());

    // Using the PEM object, create a cert holder and a verifier for the cert
    SignerInformationVerifier lVerifier = signerInfoBuilder.build(new X509CertificateHolder(lPemObj.getContent()));

    return lVerifier;
}

    public static CMSSignedData getSignedDataFromRawToken(String lRawKeystoneToken) throws CMSException
{
    // Keystone takes all '/' characters and replaced them by '-' in order to
    // encode the token into base 64. Let's reverse that..
    String lRealTokenData = lRawKeystoneToken.replace("-", "/");
    byte[] lData = Base64.decodeBase64(lRealTokenData.getBytes());

    // Now that we have the raw encoded token we can make a CMSSigned data out of it
    CMSSignedData lSignedData = new CMSSignedData(lData);
    return lSignedData;
}

    public static String getTokenContentAsString(CMSSignedData aInSignedData)
{
    Object lObj = aInSignedData.getSignedContent().getContent();
    if (lObj instanceof byte[])
    {
        String lObjString = new String((byte[]) lObj);
        return lObjString;
    }
    return null;
}

    public static boolean isValidTokenSignature(CMSSignedData aInSignedData, SignerInformationVerifier aInVerifier) throws CMSException
{
    // The token contained the signer Info and has been parsed out
    // For each signer on the token, attempt to verify against the certificate
    SignerInformationStore lSignerInfo = aInSignedData.getSignerInfos();
    Collection lSigners = lSignerInfo.getSigners();
    for (Object lObj : lSigners)
    {
        if (lObj instanceof SignerInformation)
        {
            SignerInformation lSigner = (SignerInformation) lObj;
            boolean lIsValid = lSigner.verify(aInVerifier);
            if (lIsValid)
            {
                return true;
            }
        }
    }
    return false;
}


    // Pki client is a just a class that requests cert info from Keystone
    PkiClient lPki = new PkiClient(lConfig);
    String lCaCert = lPki.getCaCertificate();
    String lSigningCert = lPki.getSigningCertificate();

    ObjectMapper lObjMapper = new ObjectMapper();
    lObjMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
    lObjMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
    lObjMapper.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    lObjMapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);

    try
    {
        CMSSignedData lSignedData = getSignedDataFromRawToken(lClient.getToken().getId());
        SignerInformationVerifier lVerifier = createTokenVerifier(lSigningCert);
        boolean isValidToken = isValidTokenSignature(lSignedData, lVerifier);
        if (isValidToken)
        {
            String lJsonToken = getTokenContentAsString(lSignedData);

            try
            {
                KeystoneAccess lKeystoneAccess = lObjMapper.readValue(lJsonToken, KeystoneAccess.class);
                Date lExpiresAt = lKeystoneAccess.getToken().getExpires();
                if (lExpiresAt.getTime() <= System.currentTimeMillis())
                {
                    System.out.println("Token for user " + lKeystoneAccess.getUser().getId() + " has expired!");
                }

                // todo check revoked list
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
        throw new RuntimeException();
    }