com.vmware.identity.rest.core.server.util.VerificationUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.rest.core.server.util.VerificationUtil.java

Source

/*
 *  Copyright (c) 2012-2015 VMware, Inc.  All Rights Reserved.
 *
 *  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 com.vmware.identity.rest.core.server.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.util.Calendar;
import java.util.Date;

import javax.ws.rs.container.ContainerRequestContext;

import org.apache.commons.codec.DecoderException;
import org.glassfish.jersey.message.internal.ReaderWriter;

import com.vmware.identity.diagnostics.IDiagnosticsLogger;
import com.vmware.identity.rest.core.server.authorization.exception.InvalidTokenException;
import com.vmware.identity.rest.core.server.authorization.token.AccessToken;
import com.vmware.identity.rest.core.server.authorization.token.TokenType;
import com.vmware.identity.rest.core.util.RequestSigner;
import com.vmware.identity.rest.core.util.StringManager;

/**
 * Utility class for performing request verification.
 */
public class VerificationUtil {

    /**
     * Verifies the token type in an access token.
     *
     * @param token the token to verify
     * @param type the expected type of the token
     * @param sm a string manager to get the exception message from
     * @param log a logger to log the error to
     *
     * @throws InvalidTokenException if the token is of an invalid or incorrect type
     */
    public static void verifyTokenType(AccessToken token, TokenType type, StringManager sm,
            IDiagnosticsLogger log) {
        if (!type.getJsonName().equals(token.getTokenType())) {
            log.error("JWT was expected to have the token type '{}', but was '{}'", type.getJsonName(),
                    token.getTokenType());
            throw new InvalidTokenException(sm.getString("auth.ite.bad.type"));
        }
    }

    /**
     * Perform signature verification using the signed request, the HTTP context, and a public key.
     *
     * @param signedRequestHex the request signature, in hex encoding, that was sent by the client
     * @param context the HTTP context that will be used for validation
     * @param pubKey the public key to verify the signature with
     *
     * @return true if the signature was verified, false if not
     *
     * @throws InvalidKeyException if the key is invalid
     * @throws SignatureException if the signature object is not initialized properly
     * @throws DecoderException if the signedRequestHex is not in a valid hex format
     */
    public static boolean verifySignature(String signedRequestHex, ContainerRequestContext context,
            PublicKey pubKey) throws InvalidKeyException, SignatureException, DecoderException {
        if (signedRequestHex == null) {
            return false;
        }

        String stringToSign = buildStringToSign(context);

        return RequestSigner.verify(signedRequestHex, stringToSign, pubKey);
    }

    /**
     * Perform signature verification using the signed request, the HTTP context, and a certificate.
     *
     * @param signedRequestHex the request signature, in hex encoding, that was sent by the client
     * @param context the HTTP context that will be used for validation
     * @param certificate the certificate to verify the signature with
     *
     * @return true if the signature was verified, false if not
     *
     * @throws InvalidKeyException if the key is invalid
     * @throws SignatureException if the signature object is not initialized properly
     * @throws DecoderException if the signedRequestHex is not in a valid hex format
     */
    public static boolean verifySignature(String signedRequestHex, ContainerRequestContext context,
            Certificate certificate) throws InvalidKeyException, SignatureException, DecoderException {
        if (signedRequestHex == null) {
            return false;
        }

        String stringToSign = buildStringToSign(context);

        return RequestSigner.verify(signedRequestHex, stringToSign, certificate);
    }

    /**
     * Verify the issued-at and expires-at dates in an access token
     *
     * @param token the token to verify
     * @param skew the amount of skew to allow in milliseconds
     * @param sm a string manager to get the exception messages from
     *
     * @throws InvalidTokenException if the token is at an invalid date
     */
    public static void verifyTimestamps(AccessToken token, long skew, StringManager sm)
            throws InvalidTokenException {
        Calendar now = Calendar.getInstance();
        Calendar issuedAt = Calendar.getInstance();
        Calendar expiresAt = Calendar.getInstance();

        if (token.getIssueTime() != null) {
            issuedAt.setTimeInMillis(token.getIssueTime().getTime() - skew);
        }

        if (token.getExpirationTime() != null) {
            expiresAt.setTimeInMillis(token.getExpirationTime().getTime() + skew);
        }

        if (token.getIssueTime() == null || issuedAt.after(now)) {
            throw new InvalidTokenException(sm.getString("auth.ite.bad.issue", issuedAt.getTime(), now.getTime()));
        }

        if (token.getExpirationTime() == null || expiresAt.before(now)) {
            throw new InvalidTokenException(
                    sm.getString("auth.ite.bad.expiry", expiresAt.getTime(), now.getTime()));
        }
    }

    public static void verifyAudience(AccessToken token, String audience, StringManager sm, IDiagnosticsLogger log)
            throws InvalidTokenException {
        if (!token.getAudience().contains(audience)) {
            log.error("Invalid audience '{}'. Expected to contain '{}'", token.getAudience(), audience);
            throw new InvalidTokenException(sm.getString("auth.ite.bad.audience"));
        }
    }

    /**
     * Verifies the request was issued within a certain amount of skew.
     *
     * @param context the request context to verify
     * @param skew the amount of skew to allow in milliseconds
     * @param log a logger to log the error to
     *
     * @return true if the request time is valid, false otherwise
     */
    public static boolean verifyRequestTime(ContainerRequestContext context, long skew, IDiagnosticsLogger log) {
        long currentTime = System.currentTimeMillis();
        Date beginsAt = new Date(currentTime - skew);
        Date expiresAt = new Date(currentTime + skew);

        Date requestTime = context.getDate();

        if (requestTime.before(beginsAt) || requestTime.after(expiresAt)) {
            log.error(String.format(
                    "Request time outside of acceptable range. Request time: '%s'. Acceptable range: ['%s', '%s']",
                    requestTime, beginsAt, expiresAt));
            return false;
        }

        return true;
    }

    private static String getMD5(ContainerRequestContext context) {
        if (!context.hasEntity()) {
            return "";
        }

        InputStream in = context.getEntityStream();

        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());

            final byte[] data = new byte[ReaderWriter.BUFFER_SIZE];
            int len = 0;

            while ((len = in.read(data)) > 0) {
                out.write(data, 0, len);
            }

            // Reset our entity stream since we consumed it and it may need to be read for object marshalling
            context.setEntityStream(new ByteArrayInputStream(out.toByteArray()));

            return RequestSigner.computeMD5(out.toString());
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static String buildStringToSign(ContainerRequestContext context) {
        return RequestSigner.createSigningString(context.getMethod(), getMD5(context),
                context.getMediaType().toString(), context.getDate(), context.getUriInfo().getRequestUri());
    }

}