]> git.basschouten.com Git - openhab-addons.git/blob
53c8cacae26af8637f4f1b98bfb7d2a114f0071f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.util.BinaryUtils;
32 import org.openhab.binding.folderwatcher.internal.api.util.HttpUtils;
33
34 /**
35  * The {@link AWS4SignerBase} class contains based methods for AWS S3 API authentication.
36  * <p>
37  * Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
38  * 
39  * @author Alexandr Salamatov - Initial contribution
40  */
41 @NonNullByDefault
42 public abstract class AWS4SignerBase {
43
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;
57
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;
63
64         dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat);
65         dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
66         dateStampFormat = new SimpleDateFormat(DateStringFormat);
67         dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
68     }
69
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);
74
75         StringBuilder buffer = new StringBuilder();
76         for (String header : sortedHeaders) {
77             if (buffer.length() > 0) {
78                 buffer.append(";");
79             }
80             buffer.append(header.toLowerCase());
81         }
82         return buffer.toString();
83     }
84
85     protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
86         if (headers == null || headers.isEmpty()) {
87             return "";
88         }
89
90         List<String> sortedHeaders = new ArrayList<String>();
91         sortedHeaders.addAll(headers.keySet());
92         Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
93
94         StringBuilder buffer = new StringBuilder();
95         for (String key : sortedHeaders) {
96             buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
97             buffer.append("\n");
98         }
99         return buffer.toString();
100     }
101
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;
107     }
108
109     protected static String getCanonicalizedResourcePath(URL endpoint) {
110         if (endpoint == null) {
111             return "/";
112         }
113         String path = endpoint.getPath();
114         if (path == null || path.isEmpty()) {
115             return "/";
116         }
117
118         String encodedPath = HttpUtils.urlEncode(path, true);
119         if (encodedPath.startsWith("/")) {
120             return encodedPath;
121         } else {
122             return "/".concat(encodedPath);
123         }
124     }
125
126     public static String getCanonicalizedQueryString(Map<String, String> parameters) {
127         if (parameters == null || parameters.isEmpty()) {
128             return "";
129         }
130
131         SortedMap<String, String> sorted = new TreeMap<String, String>();
132         Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
133
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));
139         }
140
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());
146             builder.append("=");
147             builder.append(pair.getValue());
148             if (pairs.hasNext()) {
149                 builder.append("&");
150             }
151         }
152         return builder.toString();
153     }
154
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));
159         return stringToSign;
160     }
161
162     public static byte[] hash(String text) {
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 RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
169         }
170     }
171
172     public static byte[] hash(byte[] data) {
173         try {
174             MessageDigest md = MessageDigest.getInstance("SHA-256");
175             md.update(data);
176             return md.digest();
177         } catch (Exception e) {
178             throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
179         }
180     }
181
182     protected static byte[] sign(String stringData, byte[] key, String algorithm) {
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 RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);
190         }
191     }
192 }