2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.folderwatcher.internal.api.auth;
16 import java.security.MessageDigest;
17 import java.text.SimpleDateFormat;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Iterator;
21 import java.util.List;
23 import java.util.SimpleTimeZone;
24 import java.util.SortedMap;
25 import java.util.TreeMap;
27 import javax.crypto.Mac;
28 import javax.crypto.spec.SecretKeySpec;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils;
32 import org.openhab.binding.folderwatcher.internal.api.util.HttpUtils;
35 * The {@link AWS4SignerBase} class contains based methods for AWS S3 API authentication.
37 * Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
39 * @author Alexandr Salamatov - Initial contribution
42 public abstract class AWS4SignerBase {
44 public static final String EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
45 public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
46 public static final String SCHEME = "AWS4";
47 public static final String ALGORITHM = "HMAC-SHA256";
48 public static final String TERMINATOR = "aws4_request";
49 public static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'";
50 public static final String DateStringFormat = "yyyyMMdd";
51 protected URL endpointUrl;
52 protected String httpMethod;
53 protected String serviceName;
54 protected String regionName;
55 protected final SimpleDateFormat dateTimeFormat;
56 protected final SimpleDateFormat dateStampFormat;
58 public AWS4SignerBase(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
59 this.endpointUrl = endpointUrl;
60 this.httpMethod = httpMethod;
61 this.serviceName = serviceName;
62 this.regionName = regionName;
64 dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat);
65 dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
66 dateStampFormat = new SimpleDateFormat(DateStringFormat);
67 dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
70 protected static String getCanonicalizeHeaderNames(Map<String, String> headers) {
71 List<String> sortedHeaders = new ArrayList<String>();
72 sortedHeaders.addAll(headers.keySet());
73 Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
75 StringBuilder buffer = new StringBuilder();
76 for (String header : sortedHeaders) {
77 if (buffer.length() > 0) {
80 buffer.append(header.toLowerCase());
82 return buffer.toString();
85 protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
86 if (headers == null || headers.isEmpty()) {
90 List<String> sortedHeaders = new ArrayList<String>();
91 sortedHeaders.addAll(headers.keySet());
92 Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
94 StringBuilder buffer = new StringBuilder();
95 for (String key : sortedHeaders) {
96 buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
99 return buffer.toString();
102 protected static String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters,
103 String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) {
104 String canonicalRequest = httpMethod + "\n" + getCanonicalizedResourcePath(endpoint) + "\n" + queryParameters
105 + "\n" + canonicalizedHeaders + "\n" + canonicalizedHeaderNames + "\n" + bodyHash;
106 return canonicalRequest;
109 protected static String getCanonicalizedResourcePath(URL endpoint) {
110 if (endpoint == null) {
113 String path = endpoint.getPath();
114 if (path == null || path.isEmpty()) {
118 String encodedPath = HttpUtils.urlEncode(path, true);
119 if (encodedPath.startsWith("/")) {
122 return "/".concat(encodedPath);
126 public static String getCanonicalizedQueryString(Map<String, String> parameters) {
127 if (parameters == null || parameters.isEmpty()) {
131 SortedMap<String, String> sorted = new TreeMap<String, String>();
132 Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
134 while (pairs.hasNext()) {
135 Map.Entry<String, String> pair = pairs.next();
136 String key = pair.getKey();
137 String value = pair.getValue();
138 sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
141 StringBuilder builder = new StringBuilder();
142 pairs = sorted.entrySet().iterator();
143 while (pairs.hasNext()) {
144 Map.Entry<String, String> pair = pairs.next();
145 builder.append(pair.getKey());
147 builder.append(pair.getValue());
148 if (pairs.hasNext()) {
152 return builder.toString();
155 protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope,
156 String canonicalRequest) {
157 String stringToSign = scheme + "-" + algorithm + "\n" + dateTime + "\n" + scope + "\n"
158 + BinaryUtils.toHex(hash(canonicalRequest));
162 public static byte[] hash(String text) {
164 MessageDigest md = MessageDigest.getInstance("SHA-256");
165 md.update(text.getBytes("UTF-8"));
167 } catch (Exception e) {
168 throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
172 public static byte[] hash(byte[] data) {
174 MessageDigest md = MessageDigest.getInstance("SHA-256");
177 } catch (Exception e) {
178 throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
182 protected static byte[] sign(String stringData, byte[] key, String algorithm) {
184 byte[] data = stringData.getBytes("UTF-8");
185 Mac mac = Mac.getInstance(algorithm);
186 mac.init(new SecretKeySpec(key, algorithm));
187 return mac.doFinal(data);
188 } catch (Exception e) {
189 throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);