com.joyent.http.signature.google.httpclient.RequestHttpSigner.java Source code

Java tutorial

Introduction

Here is the source code for com.joyent.http.signature.google.httpclient.RequestHttpSigner.java

Source

/*
 * Copyright (c) 2015-2017, Joyent, Inc. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.joyent.http.signature.google.httpclient;

import com.google.api.client.http.HttpRequest;
import com.joyent.http.signature.CryptoException;
import com.joyent.http.signature.KeyFingerprinter;
import com.joyent.http.signature.Signer;
import com.joyent.http.signature.ThreadLocalSigner;
import org.bouncycastle.util.encoders.Base64;

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

// I really really don't want to be using JUL for logging, but it is what the
// google library is using, so we are sticking with it. :(

/**
 * Class providing utility methods that allow you to sign a
 * {@link com.google.api.client.http.HttpRequest}.
 *
 * @see <a href="https://github.com/joyent/java-manta/blob/b2a180ff8a3ec3795ccc258904888f8305619756/src/main/java/com/joyent/manta/client/crypto/HttpSigner.java">Original Version</a>
 * @author Yunong Xiao
 * @author <a href="https://github.com/dekobon">Elijah Zupancic</a>
 * @since 1.0.0
 */
public class RequestHttpSigner {
    /**
     * The static logger instance.
     */
    private static final Logger LOG = Logger.getLogger(RequestHttpSigner.class.getName());

    /**
     * Public/private RSA keypair object used to sign HTTP requests.
     */
    private final KeyPair keyPair;

    /**
     * Login name/account name used in authorization header.
     */
    private final String login;

    /**
     * The RSA key fingerprint.
     */
    private final String fingerprint;

    /**
     * HTTP signature generator instance.
     */
    private final ThreadLocalSigner signer;

    /**
     * Creates a new instance allowing for HTTP signing.
     * @param keyPair Public/private RSA keypair object used to sign HTTP requests.
     * @param login Login name/account name used in authorization header
     * @param fingerprint rsa key fingerprint
     * @param useNativeCodeToSign true to enable native code acceleration of cryptographic singing
     *
     * @deprecated Prefer using the full configuration of {@link
     * com.joyent.http.signature.Signer.Builder} to the old boolean {@code useNativeCodeToSign}
     * switch.
     */
    @Deprecated
    public RequestHttpSigner(final KeyPair keyPair, final String login, final String fingerprint,
            final boolean useNativeCodeToSign) {
        this(keyPair, login, fingerprint, new ThreadLocalSigner(useNativeCodeToSign));
    }

    /**
     * Creates a new instance allowing for HTTP signing.
     * @param keyPair Public/private RSA keypair object used to sign HTTP requests.
     * @param login Login name/account name used in authorization header
     * @param fingerprint key fingerprint
     * @param signer reference to thread-local signer
     *
     * @deprecated The fingerprint is now calculated from the given key.
     */
    @Deprecated
    public RequestHttpSigner(final KeyPair keyPair, final String login, final String fingerprint,
            final ThreadLocalSigner signer) {
        if (keyPair == null) {
            throw new IllegalArgumentException("KeyPair must be present");
        }

        if (login == null) {
            throw new IllegalArgumentException("Login must be present");
        }

        this.keyPair = keyPair;
        this.login = login;
        this.fingerprint = fingerprint;
        this.signer = signer;
    }

    /**
     * Creates a new instance allowing for HTTP signing.
     * @param keyPair Public/private RSA keypair object used to sign HTTP requests.
     * @param login Login name/account name used in authorization header
     * @param signer reference to thread-local signer
     */
    public RequestHttpSigner(final KeyPair keyPair, final String login, final ThreadLocalSigner signer) {
        this(keyPair, login, null, signer);
    }

    /**
     * Sign an {@link com.google.api.client.http.HttpRequest}.
     *
     * @param request The {@link com.google.api.client.http.HttpRequest} to sign.
     * @throws CryptoException If unable to sign the request.
     */
    public void signRequest(final HttpRequest request) {
        if (LOG.getLevel() != null && LOG.getLevel().equals(Level.FINER)) {
            LOG.finer(String.format("Signing request: %s", request.getHeaders()));
        }

        final String date;
        final String headerDate = request.getHeaders().getDate();
        if (headerDate != null && !headerDate.isEmpty()) {
            date = request.getHeaders().getDate();
        } else {
            date = signer.get().defaultSignDateAsString();
            request.getHeaders().setDate(date);
        }

        final String authzHeader = signer.get().createAuthorizationHeader(login, keyPair, date);
        request.getHeaders().setAuthorization(authzHeader);
    }

    /**
     * Signs an arbitrary URL using the Manta-compatible HTTP signature
     * method.
     *
     * Deprecated: Use method provided inside the Java Manta SDK.
     *
     * @param uri URI with no query pointing to a downloadable resource
     * @param method HTTP request method to be used in the signature
     * @param expires epoch time in seconds when the resource will no longer
     *                be available
     * @return a signed version of the input URI
     * @throws IOException thrown when we can't sign or read char data
     */
    @Deprecated
    public URI signURI(final URI uri, final String method, final long expires) throws IOException {
        Objects.requireNonNull(method, "Method must be present");
        Objects.requireNonNull(uri, "URI must be present");

        if (uri.getQuery() != null && !uri.getQuery().isEmpty()) {
            throw new IllegalArgumentException("Query must be empty");
        }

        final String charset = "UTF-8";
        final String algorithm = signer.get().getHttpHeaderAlgorithm().toUpperCase();
        final String keyId = String.format("/%s/keys/%s", getLogin(),
                KeyFingerprinter.md5Fingerprint(getKeyPair()));
        final String keyIdEncoded = URLEncoder.encode(keyId, charset);

        StringBuilder sigText = new StringBuilder();
        sigText.append(method).append("\n").append(uri.getHost()).append("\n").append(uri.getPath()).append("\n")
                .append("algorithm=").append(algorithm).append("&").append("expires=").append(expires).append("&")
                .append("keyId=").append(keyIdEncoded);

        StringBuilder request = new StringBuilder();
        final byte[] sigBytes = sigText.toString().getBytes(StandardCharsets.US_ASCII);
        final byte[] signed = signer.get().sign(getLogin(), getKeyPair(), sigBytes);
        final String encoded = new String(Base64.encode(signed), charset);
        final String urlEncoded = URLEncoder.encode(encoded, charset);

        request.append(uri).append("?").append("algorithm=").append(algorithm).append("&").append("expires=")
                .append(expires).append("&").append("keyId=").append(keyIdEncoded).append("&").append("signature=")
                .append(urlEncoded);

        return URI.create(request.toString());
    }

    /**
     * Verifies the signature on a Google HTTP Client request.
     *
     * @param request request object to verify signature from
     * @return true if signature was verified correctly, otherwise false
     */
    public boolean verifyRequest(final HttpRequest request) {
        if (LOG.getLevel() != null && LOG.getLevel().equals(Level.FINER)) {
            LOG.finer(String.format("Verifying request: %s", request.getHeaders()));
        }
        String date = request.getHeaders().getDate();
        if (date == null) {
            throw new CryptoException("No date header in request");
        }
        return signer.get().verifyAuthorizationHeader(this.keyPair, request.getHeaders().getAuthorization(), date);
    }

    /**
     * @return Public/private RSA keypair object used to sign HTTP requests.
     */
    public KeyPair getKeyPair() {
        return keyPair;
    }

    /**
     * @return Login name/account name used in authorization header.
     */
    public String getLogin() {
        return login;
    }

    /**
     * @return The RSA key fingerprint.
     *
     * @deprecated The fingerprint is now calculated from the given key.
     */
    @Deprecated
    public String getFingerprint() {
        return fingerprint;
    }

    /**
     * @return reference to thread-local {@link Signer}
     */
    public ThreadLocalSigner getSignerThreadLocal() {
        return signer;
    }
}