All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.est.HttpAuth Maven / Gradle / Ivy

Go to download

The Bouncy Castle Java APIs for CMS, PKCS, EAC, TSP, CMP, CRMF, OCSP, and certificate generation. The APIs are designed primarily to be used in conjunction with the BC FIPS provider. The APIs may also be used with other providers although if being used in a FIPS context it is the responsibility of the user to ensure that any other providers used are FIPS certified.

There is a newer version: 2.0.7
Show newest version
package org.bouncycastle.est;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

/**
 * Provides stock implementations for basic auth and digest auth.
 */
public class HttpAuth
    implements ESTAuth
{
    private static final DigestAlgorithmIdentifierFinder digestAlgorithmIdentifierFinder = new DefaultDigestAlgorithmIdentifierFinder();

    private final String realm;
    private final String username;
    private final char[] password;
    private final SecureRandom nonceGenerator;
    private final DigestCalculatorProvider digestCalculatorProvider;

    private static final Set validParts;

    static
    {
        HashSet s = new HashSet();
        s.add("realm");
        s.add("nonce");
        s.add("opaque");
        s.add("algorithm");
        s.add("qop");
        validParts = Collections.unmodifiableSet(s);
    }

    /**
     * Base constructor for basic auth.
     *
     * @param username user id.
     * @param password user's password.
     */
    public HttpAuth(String username, char[] password)
    {
        this(null, username, password, null, null);
    }

    /**
     * Constructor for basic auth with a specified realm.
     *
     * @param realm    expected server realm.
     * @param username user id.
     * @param password user's password.
     */
    public HttpAuth(String realm, String username, char[] password)
    {
        this(realm, username, password, null, null);
    }

    /**
     * Base constructor for digest auth. The realm will be set by
     *
     * @param username                 user id.
     * @param password                 user's password.
     * @param nonceGenerator           random source for generating nonces.
     * @param digestCalculatorProvider provider for digest calculators needed for calculating hashes.
     */
    public HttpAuth(String username, char[] password, SecureRandom nonceGenerator, DigestCalculatorProvider digestCalculatorProvider)
    {
        this(null, username, password, nonceGenerator, digestCalculatorProvider);
    }

    /**
     * Constructor for digest auth with a specified realm.
     *
     * @param realm                    expected server realm.
     * @param username                 user id.
     * @param password                 user's password.
     * @param nonceGenerator           random source for generating nonces.
     * @param digestCalculatorProvider provider for digest calculators needed for calculating hashes.
     */
    public HttpAuth(String realm, String username, char[] password, SecureRandom nonceGenerator, DigestCalculatorProvider digestCalculatorProvider)
    {
        this.realm = realm;
        this.username = username;
        this.password = password;
        this.nonceGenerator = nonceGenerator;
        this.digestCalculatorProvider = digestCalculatorProvider;
    }

    public void applyAuth(final ESTRequestBuilder reqBldr)
    {
        reqBldr.withHijacker(new ESTHijacker()
        {
            public ESTResponse hijack(ESTRequest req, Source sock)
                throws IOException
            {
                ESTResponse res = new ESTResponse(req, sock);

                if (res.getStatusCode() == 401)
                {
                    String authHeader = res.getHeader("WWW-Authenticate");
                    if (authHeader == null)
                    {
                        throw new ESTException("Status of 401 but no WWW-Authenticate header");
                    }

                    authHeader = Strings.toLowerCase(authHeader);

                    if (authHeader.startsWith("digest"))
                    {
                        res = doDigestFunction(res);
                    }
                    else if (authHeader.startsWith("basic"))
                    {
                        res.close(); // Close off the last reqBldr.

                        //
                        // Check realm field from header.
                        //
                        Map s = HttpUtil.splitCSL("Basic", res.getHeader("WWW-Authenticate"));

                        //
                        // If no realm supplied it will not check the server realm. TODO elaborate in documentation.
                        //
                        if (realm != null)
                        {
                            if (!realm.equals(s.get("realm")))
                            {
                                // Not equal then fail.
                                throw new ESTException("Supplied realm '" + realm + "' does not match server realm '" + s.get("realm") + "'", null, 401, null);
                            }
                        }

                        //
                        // Prepare basic auth answer.
                        //
                        ESTRequestBuilder answer = new ESTRequestBuilder(req).withHijacker(null);

                        if (realm != null && realm.length() > 0)
                        {
                            answer.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
                        }
                        if (username.contains(":"))
                        {
                            throw new IllegalArgumentException("User must not contain a ':'");
                        }
                        //userPass = username + ":" + password;
                        char[]  userPass = new char[username.length() + 1 + password.length];
                        System.arraycopy(username.toCharArray(), 0, userPass, 0, username.length());
                        userPass[username.length()] = ':';
                        System.arraycopy(password, 0, userPass, username.length() + 1, password.length);

                        answer.setHeader("Authorization", "Basic " + Base64.toBase64String(Strings.toByteArray(userPass)));

                        res = req.getClient().doRequest(answer.build());

                        Arrays.fill(userPass, (char)0);
                    }
                    else
                    {
                        throw new ESTException("Unknown auth mode: " + authHeader);
                    }


                    return res;
                }
                return res;
            }
        });
    }

    private ESTResponse doDigestFunction(ESTResponse res)
        throws IOException
    {
        res.close(); // Close off the last request.
        ESTRequest req = res.getOriginalRequest();


        Map parts = null;
        try
        {
            parts = HttpUtil.splitCSL("Digest", res.getHeader("WWW-Authenticate"));
        }
        catch (Throwable t)
        {
            throw new ESTException(
                "Parsing WWW-Authentication header: " + t.getMessage(),
                t,
                res.getStatusCode(),
                new ByteArrayInputStream(res.getHeader("WWW-Authenticate").getBytes()));
        }


        String uri = null;
        try
        {
            uri = req.getURL().toURI().getPath();
        }
        catch (Exception e)
        {
            throw new IOException("unable to process URL in request: " + e.getMessage());
        }

        for (Iterator it = parts.keySet().iterator(); it.hasNext();)
        {
            Object k = it.next();
            if (!validParts.contains(k))
            {
                throw new ESTException("Unrecognised entry in WWW-Authenticate header: '" + k + "'");
            }
        }

        String method = req.getMethod();
        String realm = parts.get("realm");
        String nonce = parts.get("nonce");
        String opaque = parts.get("opaque");
        String algorithm = parts.get("algorithm");
        String qop = parts.get("qop");


        List qopMods = new ArrayList(); // Preserve ordering.

        if (this.realm != null)
        {
            if (!this.realm.equals(realm))
            {
                // Not equal then fail.
                throw new ESTException("Supplied realm '" + this.realm + "' does not match server realm '" + realm + "'", null, 401, null);
            }
        }

        // If an algorithm is not specified, default to MD5.
        if (algorithm == null)
        {
            algorithm = "MD5";
        }

        if (algorithm.length() == 0)
        {
            throw new ESTException("WWW-Authenticate no algorithm defined.");
        }

        algorithm = Strings.toUpperCase(algorithm);

        if (qop != null)
        {
            if (qop.length() == 0)
            {
                throw new ESTException("QoP value is empty.");
            }

            qop = Strings.toLowerCase(qop);
            String[] s = qop.split(",");
            for (int j = 0; j != s.length; j++)
            {
                if (!s[j].equals("auth") && !s[j].equals("auth-int"))
                {
                    throw new ESTException("QoP value unknown: '" + j + "'");
                }

                String jt = s[j].trim();
                if (qopMods.contains(jt))
                {
                    continue;
                }
                qopMods.add(jt);
            }
        }
        else
        {
            throw new ESTException("Qop is not defined in WWW-Authenticate header.");
        }


        AlgorithmIdentifier digestAlg = lookupDigest(algorithm);
        if (digestAlg == null || digestAlg.getAlgorithm() == null)
        {
            throw new IOException("auth digest algorithm unknown: " + algorithm);
        }

        DigestCalculator dCalc = getDigestCalculator(algorithm, digestAlg);
        OutputStream dOut = dCalc.getOutputStream();

        String crnonce = makeNonce(10); // TODO arbitrary?

        update(dOut, username);
        update(dOut, ":");
        update(dOut, realm);
        update(dOut, ":");
        update(dOut, password);

        dOut.close();

        byte[] ha1 = dCalc.getDigest();

        if (algorithm.endsWith("-SESS"))
        {
            DigestCalculator sessCalc = getDigestCalculator(algorithm, digestAlg);
            OutputStream sessOut = sessCalc.getOutputStream();

            String cs = Hex.toHexString(ha1);

            update(sessOut, cs);
            update(sessOut, ":");
            update(sessOut, nonce);
            update(sessOut, ":");
            update(sessOut, crnonce);

            sessOut.close();

            ha1 = sessCalc.getDigest();
        }

        String hashHa1 = Hex.toHexString(ha1);

        DigestCalculator authCalc = getDigestCalculator(algorithm, digestAlg);
        OutputStream authOut = authCalc.getOutputStream();

        if (qopMods.get(0).equals("auth-int"))
        {
            DigestCalculator reqCalc = getDigestCalculator(algorithm, digestAlg);
            OutputStream reqOut = reqCalc.getOutputStream();

            req.writeData(reqOut);

            reqOut.close();

            byte[] b = reqCalc.getDigest();

            update(authOut, method);
            update(authOut, ":");
            update(authOut, uri);
            update(authOut, ":");
            update(authOut, Hex.toHexString(b));
        }
        else if (qopMods.get(0).equals("auth"))
        {
            update(authOut, method);
            update(authOut, ":");
            update(authOut, uri);
        }

        authOut.close();

        String hashHa2 = Hex.toHexString(authCalc.getDigest());

        DigestCalculator responseCalc = getDigestCalculator(algorithm, digestAlg);
        OutputStream responseOut = responseCalc.getOutputStream();

        if (qopMods.contains("missing"))
        {
            update(responseOut, hashHa1);
            update(responseOut, ":");
            update(responseOut, nonce);
            update(responseOut, ":");
            update(responseOut, hashHa2);
        }
        else
        {
            update(responseOut, hashHa1);
            update(responseOut, ":");
            update(responseOut, nonce);
            update(responseOut, ":");
            update(responseOut, "00000001");
            update(responseOut, ":");
            update(responseOut, crnonce);
            update(responseOut, ":");

            if (qopMods.get(0).equals("auth-int"))
            {
                update(responseOut, "auth-int");
            }
            else
            {
                update(responseOut, "auth");
            }

            update(responseOut, ":");
            update(responseOut, hashHa2);
        }

        responseOut.close();

        String digest = Hex.toHexString(responseCalc.getDigest());

        Map hdr = new HashMap();
        hdr.put("username", username);
        hdr.put("realm", realm);
        hdr.put("nonce", nonce);
        hdr.put("uri", uri);
        hdr.put("response", digest);
        if (qopMods.get(0).equals("auth-int"))
        {
            hdr.put("qop", "auth-int");
            hdr.put("nc", "00000001");
            hdr.put("cnonce", crnonce);
        }
        else if (qopMods.get(0).equals("auth"))
        {
            hdr.put("qop", "auth");
            hdr.put("nc", "00000001");
            hdr.put("cnonce", crnonce);
        }
        hdr.put("algorithm", algorithm);

        if (opaque == null || opaque.length() == 0)
        {
            hdr.put("opaque", makeNonce(20));
        }

        ESTRequestBuilder answer = new ESTRequestBuilder(req).withHijacker(null);

        answer.setHeader("Authorization", HttpUtil.mergeCSL("Digest", hdr));

        return req.getClient().doRequest(answer.build());
    }

    private DigestCalculator getDigestCalculator(String algorithm, AlgorithmIdentifier digestAlg)
        throws IOException
    {
        DigestCalculator dCalc;
        try
        {
            dCalc = digestCalculatorProvider.get(digestAlg);
        }
        catch (OperatorCreationException e)
        {
            throw new IOException("cannot create digest calculator for " + algorithm + ": " + e.getMessage());
        }
        return dCalc;
    }

    private AlgorithmIdentifier lookupDigest(String algorithm)
    {
        if (algorithm.endsWith("-SESS"))
        {
            algorithm = algorithm.substring(0, algorithm.length() - "-SESS".length());
        }

        if (algorithm.equals("SHA-512-256"))
        {
            return new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512_256, DERNull.INSTANCE);
        }

        return digestAlgorithmIdentifierFinder.find(algorithm);
    }

    private void update(OutputStream dOut, char[] value)
        throws IOException
    {
        dOut.write(Strings.toUTF8ByteArray(value));
    }

    private void update(OutputStream dOut, String value)
        throws IOException
    {
        dOut.write(Strings.toUTF8ByteArray(value));
    }

    private String makeNonce(int len)
    {
        byte[] b = new byte[len];
        nonceGenerator.nextBytes(b);
        return Hex.toHexString(b);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy