]> git.basschouten.com Git - openhab-addons.git/blob
9a1a9f36ef6c68f81f6da642d12bd2089cfdf7dc
[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.mqtt.internal.ssl;
14
15 import java.net.Socket;
16 import java.security.NoSuchAlgorithmException;
17 import java.security.cert.CertificateEncodingException;
18 import java.security.cert.CertificateException;
19 import java.security.cert.X509Certificate;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import javax.net.ssl.SSLEngine;
26 import javax.net.ssl.X509ExtendedTrustManager;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30
31 /**
32  * This is a custom {@link X509ExtendedTrustManager}. {@link Pin} objects can be added and will
33  * be used in the checkServerTrusted() method to determine if a connection can be trusted.
34  *
35  * @author David Graeff - Initial contribution
36  */
37 @NonNullByDefault
38 public class PinTrustManager extends X509ExtendedTrustManager {
39     List<Pin> pins = new ArrayList<>();
40     protected @Nullable PinnedCallback callback;
41
42     /**
43      * Adds a pin (certificate key, public key) to the trust manager. If a connections has assigned pins,
44      * it will not accept any other certificates or public keys anymore!
45      *
46      * @param pin The pin
47      */
48     public void addPinning(Pin pin) {
49         pins.add(pin);
50     }
51
52     public void setCallback(PinnedCallback callback) {
53         this.callback = callback;
54     }
55
56     @Override
57     public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType)
58             throws CertificateException {
59         throw new UnsupportedOperationException();
60     }
61
62     protected byte[] getEncoded(PinType type, X509Certificate cert) throws CertificateEncodingException {
63         switch (type) {
64             case CERTIFICATE_TYPE:
65                 return cert.getEncoded();
66             case PUBLIC_KEY_TYPE:
67                 return cert.getPublicKey().getEncoded();
68         }
69         throw new CertificateEncodingException("Type unknown");
70     }
71
72     /**
73      * A signature name depends on the security provider but usually follows
74      * https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#signature-algorithms.
75      * E.g.: "SHA256withRSA". We need "SHA" and "256" to initialize a {@link PinMessageDigest}.
76      */
77     PinMessageDigest getMessageDigestForSigAlg(String sigAlg) throws CertificateException {
78         final Matcher matcher = Pattern.compile("(\\D*)(\\d+)").matcher(sigAlg);
79         matcher.find();
80         final String sigAlgName = matcher.group(1);
81         final String sigAlgBits = matcher.group(2);
82         try {
83             return new PinMessageDigest(sigAlgName + "-" + sigAlgBits);
84         } catch (NoSuchAlgorithmException e) {
85             throw new CertificateException(e);
86         }
87     }
88
89     @Override
90     public void checkServerTrusted(X509Certificate @Nullable [] chainN, @Nullable String authType)
91             throws CertificateException {
92         X509Certificate[] chain = chainN;
93         if (chain == null) {
94             return;
95         }
96
97         final PinMessageDigest digestForSigAlg = getMessageDigestForSigAlg(chain[0].getSigAlgName());
98         final PinnedCallback callback = this.callback;
99
100         // All pins have to accept the connection
101         for (Pin pin : pins) {
102             byte[] origData = getEncoded(pin.getType(), chain[0]);
103
104             // If in learning mode: Learn new signature algorithm and hash and notify listeners
105             if (pin.isLearning()) {
106                 pin.setCheckMode(digestForSigAlg, digestForSigAlg.digest(origData));
107                 if (callback != null) {
108                     callback.pinnedLearnedHash(pin);
109                 }
110                 continue;
111             } else {
112                 final PinMessageDigest hashDigest = pin.hashDigest;
113                 if (hashDigest == null) {
114                     throw new CertificateException("No hashDigest given!");
115                 }
116
117                 // Check if hash is equal
118                 final byte[] digestData = hashDigest.digest(origData);
119                 if (pin.isEqual(digestData)) {
120                     continue;
121                 }
122                 // This pin does not accept the connection
123                 if (callback != null) {
124                     callback.pinnedConnectionDenied(pin);
125                 }
126                 throw new CertificateException(pin.getType().name() + " pinning denied access. Destination pin is "
127                         + hashDigest.toHexString(digestData) + "' but expected: " + pin.toString());
128             }
129         }
130         // All pin instances passed, the connection is accepted
131         if (callback != null) {
132             callback.pinnedConnectionAccepted();
133         }
134     }
135
136     @Override
137     public X509Certificate[] getAcceptedIssuers() {
138         return new X509Certificate[0];
139     }
140
141     @Override
142     public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
143             @Nullable Socket socket) throws CertificateException {
144         checkClientTrusted(chain, authType);
145     }
146
147     @Override
148     public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
149             @Nullable SSLEngine sslEngine) throws CertificateException {
150         checkClientTrusted(chain, authType);
151     }
152
153     @Override
154     public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
155             @Nullable Socket socket) throws CertificateException {
156         checkServerTrusted(chain, authType);
157     }
158
159     @Override
160     public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
161             @Nullable SSLEngine sslEngine) throws CertificateException {
162         checkServerTrusted(chain, authType);
163     }
164 }