]> git.basschouten.com Git - openhab-addons.git/blob
cd872a2fa31725bc890c8a0f6c5139c33657324f
[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         return httpMethod + "\n" + getCanonicalizedResourcePath(endpoint) + "\n" + queryParameters + "\n"
105                 + canonicalizedHeaders + "\n" + canonicalizedHeaderNames + "\n" + bodyHash;
106     }
107
108     protected static String getCanonicalizedResourcePath(URL endpoint) {
109         if (endpoint == null) {
110             return "/";
111         }
112         String path = endpoint.getPath();
113         if (path == null || path.isEmpty()) {
114             return "/";
115         }
116
117         String encodedPath = HttpUtils.urlEncode(path, true);
118         if (encodedPath.startsWith("/")) {
119             return encodedPath;
120         } else {
121             return "/".concat(encodedPath);
122         }
123     }
124
125     public static String getCanonicalizedQueryString(Map<String, String> parameters) {
126         if (parameters == null || parameters.isEmpty()) {
127             return "";
128         }
129
130         SortedMap<String, String> sorted = new TreeMap<String, String>();
131         Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
132
133         while (pairs.hasNext()) {
134             Map.Entry<String, String> pair = pairs.next();
135             String key = pair.getKey();
136             String value = pair.getValue();
137             sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
138         }
139
140         StringBuilder builder = new StringBuilder();
141         pairs = sorted.entrySet().iterator();
142         while (pairs.hasNext()) {
143             Map.Entry<String, String> pair = pairs.next();
144             builder.append(pair.getKey());
145             builder.append("=");
146             builder.append(pair.getValue());
147             if (pairs.hasNext()) {
148                 builder.append("&");
149             }
150         }
151         return builder.toString();
152     }
153
154     protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope,
155             String canonicalRequest) {
156         return scheme + "-" + algorithm + "\n" + dateTime + "\n" + scope + "\n"
157                 + BinaryUtils.toHex(hash(canonicalRequest));
158     }
159
160     public static byte[] hash(String text) {
161         try {
162             MessageDigest md = MessageDigest.getInstance("SHA-256");
163             md.update(text.getBytes("UTF-8"));
164             return md.digest();
165         } catch (Exception e) {
166             throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
167         }
168     }
169
170     public static byte[] hash(byte[] data) {
171         try {
172             MessageDigest md = MessageDigest.getInstance("SHA-256");
173             md.update(data);
174             return md.digest();
175         } catch (Exception e) {
176             throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
177         }
178     }
179
180     protected static byte[] sign(String stringData, byte[] key, String algorithm) {
181         try {
182             byte[] data = stringData.getBytes("UTF-8");
183             Mac mac = Mac.getInstance(algorithm);
184             mac.init(new SecretKeySpec(key, algorithm));
185             return mac.doFinal(data);
186         } catch (Exception e) {
187             throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);
188         }
189     }
190 }