HawkAuthorizationScheme.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.cxf.rs.security.oauth2.tokens.hawk;

import java.security.SecureRandom;
import java.util.Map;

import org.apache.cxf.common.util.Base64Utility;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.rs.security.oauth2.client.HttpRequestProperties;
import org.apache.cxf.rs.security.oauth2.common.AccessToken;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
import org.apache.cxf.rt.security.crypto.HmacUtils;
// https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05
// ->
// https://github.com/hueniverse/hawk/blob/master/README.md
public class HawkAuthorizationScheme {
    private static final String SEPARATOR = "\n";
    private static final String HAWK_1_HEADER = "hawk.1.header";

    private HttpRequestProperties props;
    private String macKey;
    private String timestamp;
    private String nonce;

    public HawkAuthorizationScheme(HttpRequestProperties props,
                                  AccessToken token) {
        this.props = props;
        this.macKey = token.getTokenKey();
        this.timestamp = Long.toString(System.currentTimeMillis());
        this.nonce = generateNonce();
    }

    public HawkAuthorizationScheme(HttpRequestProperties props,
                                  Map<String, String> schemeParams) {
        this.props = props;
        this.macKey = schemeParams.get(OAuthConstants.HAWK_TOKEN_ID);
        this.timestamp = schemeParams.get(OAuthConstants.HAWK_TOKEN_TIMESTAMP);
        this.nonce = schemeParams.get(OAuthConstants.HAWK_TOKEN_NONCE);
    }

    public String getMacKey() {
        return macKey;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public String getNonce() {
        return nonce;
    }

    public String toAuthorizationHeader(String macAlgo, String macSecret) {

        String data = getNormalizedRequestString();
        String signature = HmacUtils.encodeHmacString(macSecret,
                                                      HmacAlgorithm.toHmacAlgorithm(macAlgo).getJavaName(),
                                                      data);

        StringBuilder sb = new StringBuilder();
        sb.append(OAuthConstants.HAWK_AUTHORIZATION_SCHEME).append(' ');
        addParameter(sb, OAuthConstants.HAWK_TOKEN_ID, macKey, false);
        addParameter(sb, OAuthConstants.HAWK_TOKEN_TIMESTAMP, timestamp, false);
        addParameter(sb, OAuthConstants.HAWK_TOKEN_NONCE, nonce, false);
        addParameter(sb, OAuthConstants.HAWK_TOKEN_SIGNATURE, signature, true);

        return sb.toString();
    }

    private static void addParameter(StringBuilder sb, String name, String value, boolean last) {
        sb.append(name).append('=')
          .append('\"').append(value).append('\"');
        if (!last) {
            sb.append(',');
        }
    }

    public String getNormalizedRequestString() {
        String requestURI = props.getRequestPath();
        if (!StringUtils.isEmpty(props.getRequestQuery())) {
            requestURI += "?" + normalizeQuery(props.getRequestQuery());
        }


        return HAWK_1_HEADER + SEPARATOR
            + timestamp + SEPARATOR
            + nonce + SEPARATOR
            + props.getHttpMethod().toUpperCase() + SEPARATOR
            + requestURI + SEPARATOR
            + props.getHostName() + SEPARATOR
            + props.getPort() + SEPARATOR
            + SEPARATOR
            + SEPARATOR;
    }

    private static String normalizeQuery(String query) {
        return query;
    }

    private static String generateNonce() {
        byte[] randomBytes = new byte[20];
        new SecureRandom().nextBytes(randomBytes);
        return Base64Utility.encode(randomBytes);
    }

}