]> git.basschouten.com Git - openhab-addons.git/blob
575cc6d3b9b9e69d6703fc70a62160ada3ce3ba5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.folderwatcher.internal.api.auth;
14
15 import java.net.URL;
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;
22 import java.util.Map;
23 import java.util.SimpleTimeZone;
24 import java.util.SortedMap;
25 import java.util.TreeMap;
26
27 import javax.crypto.Mac;
28 import javax.crypto.spec.SecretKeySpec;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.folderwatcher.internal.api.exception.AuthException;
32 import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils;
33 import org.openhab.binding.folderwatcher.internal.api.util.HttpUtilException;
34 import org.openhab.binding.folderwatcher.internal.api.util.HttpUtils;
35
36 /**
37  * The {@link AWS4SignerBase} class contains based methods for AWS S3 API authentication.
38  * <p>
39  * Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
40  * 
41  * @author Alexandr Salamatov - Initial contribution
42  */
43 @NonNullByDefault
44 public abstract class AWS4SignerBase {
45
46     public static final String EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
47     public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
48     public static final String SCHEME = "AWS4";
49     public static final String ALGORITHM = "HMAC-SHA256";
50     public static final String TERMINATOR = "aws4_request";
51     public static final String ISO8601_BASIC_FORMAT = "yyyyMMdd'T'HHmmss'Z'";
52     public static final String DATESTRING_FORMAT = "yyyyMMdd";
53     protected URL endpointUrl;
54     protected String httpMethod;
55     protected String serviceName;
56     protected String regionName;
57     protected final SimpleDateFormat dateTimeFormat;
58     protected final SimpleDateFormat dateStampFormat;
59
60     public AWS4SignerBase(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
61         this.endpointUrl = endpointUrl;
62         this.httpMethod = httpMethod;
63         this.serviceName = serviceName;
64         this.regionName = regionName;
65
66         dateTimeFormat = new SimpleDateFormat(ISO8601_BASIC_FORMAT);
67         dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
68         dateStampFormat = new SimpleDateFormat(DATESTRING_FORMAT);
69         dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
70     }
71
72     protected static String getCanonicalizeHeaderNames(Map<String, String> headers) {
73         List<String> sortedHeaders = new ArrayList<String>();
74         sortedHeaders.addAll(headers.keySet());
75         Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
76
77         StringBuilder buffer = new StringBuilder();
78         for (String header : sortedHeaders) {
79             if (buffer.length() > 0) {
80                 buffer.append(";");
81             }
82             buffer.append(header.toLowerCase());
83         }
84         return buffer.toString();
85     }
86
87     protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
88         if (headers == null || headers.isEmpty()) {
89             return "";
90         }
91
92         List<String> sortedHeaders = new ArrayList<String>();
93         sortedHeaders.addAll(headers.keySet());
94         Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
95
96         StringBuilder buffer = new StringBuilder();
97         for (String key : sortedHeaders) {
98             buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
99             buffer.append("\n");
100         }
101         return buffer.toString();
102     }
103
104     protected static String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters,
105             String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) throws HttpUtilException {
106         return httpMethod + "\n" + getCanonicalizedResourcePath(endpoint) + "\n" + queryParameters + "\n"
107                 + canonicalizedHeaders + "\n" + canonicalizedHeaderNames + "\n" + bodyHash;
108     }
109
110     protected static String getCanonicalizedResourcePath(URL endpoint) throws HttpUtilException {
111         if (endpoint == null) {
112             return "/";
113         }
114         String path = endpoint.getPath();
115         if (path == null || path.isEmpty()) {
116             return "/";
117         }
118
119         String encodedPath = HttpUtils.urlEncode(path, true);
120         if (encodedPath.startsWith("/")) {
121             return encodedPath;
122         } else {
123             return "/".concat(encodedPath);
124         }
125     }
126
127     public static String getCanonicalizedQueryString(Map<String, String> parameters) throws HttpUtilException {
128         if (parameters == null || parameters.isEmpty()) {
129             return "";
130         }
131
132         SortedMap<String, String> sorted = new TreeMap<String, String>();
133         Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
134
135         while (pairs.hasNext()) {
136             Map.Entry<String, String> pair = pairs.next();
137             String key = pair.getKey();
138             String value = pair.getValue();
139             sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
140         }
141
142         StringBuilder builder = new StringBuilder();
143         pairs = sorted.entrySet().iterator();
144         while (pairs.hasNext()) {
145             Map.Entry<String, String> pair = pairs.next();
146             builder.append(pair.getKey());
147             builder.append("=");
148             builder.append(pair.getValue());
149             if (pairs.hasNext()) {
150                 builder.append("&");
151             }
152         }
153         return builder.toString();
154     }
155
156     protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope,
157             String canonicalRequest) throws AuthException {
158         return scheme + "-" + algorithm + "\n" + dateTime + "\n" + scope + "\n"
159                 + BinaryUtils.toHex(hash(canonicalRequest));
160     }
161
162     public static byte[] hash(String text) throws AuthException {
163         try {
164             MessageDigest md = MessageDigest.getInstance("SHA-256");
165             md.update(text.getBytes("UTF-8"));
166             return md.digest();
167         } catch (Exception e) {
168             throw new AuthException("Unable to compute hash while signing request: " + e.getMessage(), e);
169         }
170     }
171
172     public static byte[] hash(byte[] data) throws AuthException {
173         try {
174             MessageDigest md = MessageDigest.getInstance("SHA-256");
175             md.update(data);
176             return md.digest();
177         } catch (Exception e) {
178             throw new AuthException("Unable to compute hash while signing request: " + e.getMessage(), e);
179         }
180     }
181
182     protected static byte[] sign(String stringData, byte[] key, String algorithm) throws AuthException {
183         try {
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 AuthException("Unable to calculate a request signature: " + e.getMessage(), e);
190         }
191     }
192 }