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

org.tomitribe.auth.signatures.Signature Maven / Gradle / Ivy

There is a newer version: 1.8
Show newest version
/*
 * 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.tomitribe.auth.signatures;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Signature {

    /**
     * REQUIRED.  The `keyId` field is an opaque string that the server can
     * use to look up the component they need to validate the signature.  It
     * could be an SSH key fingerprint, a URL to machine-readable key data,
     * an LDAP DN, etc.  Management of keys and assignment of `keyId` is out
     * of scope for this document.
     */
    private final String keyId;

    /**
     * REQUIRED.  The `algorithm` parameter is used to specify the digital
     * signature algorithm to use when generating the signature.  Valid
     * values for this parameter can be found in the Signature Algorithms
     * registry located at http://www.iana.org/assignments/signature-
     * algorithms and MUST NOT be marked "deprecated".
     */
    private final Algorithm algorithm;

    /**
     * OPTIONAL.  The `headers` parameter is used to specify the list of
     * HTTP headers included when generating the signature for the message.
     * If specified, it should be a lowercased, quoted list of HTTP header
     * fields, separated by a single space character.  If not specified,
     * implementations MUST operate as if the field were specified with a
     * single value, the `Date` header, in the list of HTTP headers.  Note
     * that the list order is important, and MUST be specified in the order
     * the HTTP header field-value pairs are concatenated together during
     * signing.
     */
    private final String signature;

    /**
     * REQUIRED.  The `signature` parameter is a base 64 encoded digital
     * signature, as described in RFC 4648 [RFC4648], Section 4 [4].  The
     * client uses the `algorithm` and `headers` signature parameters to
     * form a canonicalized `signing string`.  This `signing string` is then
     * signed with the key associated with `keyId` and the algorithm
     * corresponding to `algorithm`.  The `signature` parameter is then set
     * to the base 64 encoding of the signature.
     */
    private final List headers;

    public Signature(final String keyId, final String algorithm, final String signature, final String... headers) {
        this(keyId, getAlgorithm(algorithm), signature, headers);
    }

    private static Algorithm getAlgorithm(String algorithm) {
        if (algorithm == null) throw new IllegalArgumentException("Algorithm cannot be null");
        return Algorithm.get(algorithm);
    }

    public Signature(final String keyId, final Algorithm algorithm, final String signature, final String... headers) {
        this(keyId, algorithm, signature, Arrays.asList(headers));
    }

    public Signature(final String keyId, final String algorithm, final String signature, final List headers) {
        this(keyId, getAlgorithm(algorithm), signature, headers);
    }

    public Signature(final String keyId, Algorithm algorithm, final String signature, final List headers) {
        if (keyId == null || keyId.trim().isEmpty()) {
            throw new IllegalArgumentException("keyId is required.");
        }
        if (algorithm == null) {
            throw new IllegalArgumentException("algorithm is required.");
        }

        this.keyId = keyId;
        this.algorithm = algorithm;

        // this is the only one that can be null cause the object
        // can be used as a template/specification
        this.signature = signature;

        if (headers.size() == 0) {
            final List list = Arrays.asList("date");
            this.headers = Collections.unmodifiableList(list);
        } else {
            this.headers = Collections.unmodifiableList(lowercase(headers));
        }
    }

    private List lowercase(List headers) {
        final List list = new ArrayList(headers.size());
        for (String header : headers) {
            list.add(header.toLowerCase());
        }
        return list;
    }

    public String getKeyId() {
        return keyId;
    }

    public Algorithm getAlgorithm() {
        return algorithm;
    }

    public String getSignature() {
        return signature;
    }

    public List getHeaders() {
        return headers;
    }

    public static Signature fromString(String authorization) {
        try {
            authorization = normalize(authorization);

            final String[] split = authorization.split(",");

            final Map map = new HashMap();

            for (String s : split) {
                s = s.trim();
                final int i = s.indexOf("=\"");

                final String key = s.substring(0, i).toLowerCase();
                final String value = s.substring(i + 2, s.length() - 1);

                map.put(key, value);
            }

            final List headers = new ArrayList();
            final String headerString = map.get("headers");
            if (headerString != null) {
                Collections.addAll(headers, headerString.toLowerCase().split(" +"));
            }

            final String keyid = map.get("keyid");
            if (keyid == null) throw new MissingKeyIdException();

            final String algorithm = map.get("algorithm");
            if (algorithm == null) throw new MissingAlgorithmException();

            final String signature = map.get("signature");
            if (signature == null) throw new MissingSignatureException();

            final Algorithm parsedAlgorithm = Algorithm.get(algorithm);

            return new Signature(keyid, parsedAlgorithm, signature, headers);

        } catch (AuthenticationException e) {
            throw e;
        } catch (Throwable e) {
            throw new UnparsableSignatureException(authorization, e);
        }
    }

    private static String normalize(String authorization) {
        final String start = "signature ";

        final String prefix = authorization.substring(0, start.length()).toLowerCase();

        if (prefix.equals(start)) {
            authorization = authorization.substring(start.length());
        }
        return authorization.trim();
    }

    @Override
    public String toString() {
        return "Signature " +
                "keyId=\"" + keyId + '\"' +
                ",algorithm=\"" + algorithm + '\"' +
                ",headers=\"" + Join.join(" ", headers) + '\"' +
                ",signature=\"" + signature + '\"';
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy