SignatureHeaderUtils.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.httpsignature.utils;
import java.net.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Stream;
import org.apache.cxf.rs.security.httpsignature.exception.DigestFailureException;
public final class SignatureHeaderUtils {
private SignatureHeaderUtils() { }
/**
* Add a date header at the current time using the ZoneOffset. Date format is http
*/
public static void addDateHeader(Map<String, List<String>> messageHeaders, ZoneOffset zoneOffset) {
String date = DateTimeFormatter.RFC_1123_DATE_TIME
.format(LocalDateTime.now().atZone(Clock.system(zoneOffset).getZone()));
messageHeaders.put("Date", Collections.singletonList(date));
}
/**
* Maps a multimap to a normal map with comma-separated values in case of duplicate headers according to
* the draft-cavage guidelines
* @param multivaluedMap the multivalued map
* @return A map with comma-separated values
*/
public static Map<String, String> mapHeaders(Map<String, List<String>> multivaluedMap) {
Map<String, String> mappedStrings = new HashMap<>(multivaluedMap.size());
for (Map.Entry<String, List<String>> entry : multivaluedMap.entrySet()) {
mappedStrings.put(entry.getKey(), String.join(", ", entry.getValue()));
}
return mappedStrings;
}
/**
* Get a base64 encoded digest using the Algorithm specified, typically SHA-256
*
* @param messageBody The body of the message to be used to create the Digest
* @param digestAlgorithmName The name of the algorithm used to create the digest, SHA-256 and SHA-512 are valid
* @return A base64 encoded digest ready to be added as a header to the message
*/
public static String createDigestHeader(String messageBody, String digestAlgorithmName) {
MessageDigest messageDigest = createMessageDigestWithAlgorithm(digestAlgorithmName);
messageDigest.update(messageBody.getBytes());
String digest = Base64.getEncoder().encodeToString(messageDigest.digest());
StringBuilder sb = new StringBuilder(digestAlgorithmName.length() + 1 + digest.length());
sb.append(digestAlgorithmName).append('=').append(digest);
return sb.toString();
}
/**
* Get a MessageDigest object based on the algorithm in the digest string
*
* @return a valid MessageDigest object
*/
public static MessageDigest createMessageDigestWithAlgorithm(String algorithmName) {
try {
String foundAlgorithm = Stream.of("SHA-256", "SHA-512")
.filter(s -> s.equalsIgnoreCase(algorithmName))
.findAny()
.orElseThrow(() -> new NoSuchAlgorithmException("found no match in digest algorithm allow-list"));
return MessageDigest.getInstance(foundAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new DigestFailureException("failed to retrieve digest from digest string", e);
}
}
public static void inspectMessageHeaders(Map<String, List<String>> messageHeaders) {
Objects.requireNonNull(messageHeaders);
if (messageHeaders.isEmpty()) {
throw new IllegalStateException("message headers are empty");
}
messageHeaders.forEach((key, list) -> {
Objects.requireNonNull(list);
list.forEach(Objects::requireNonNull);
});
}
public static String createRequestTarget(URI uri) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(uri.getRawPath());
if (uri.getRawQuery() != null) {
stringBuilder.append('?');
stringBuilder.append(uri.getRawQuery());
}
return stringBuilder.toString();
}
}