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.mqtt.internal.ssl;
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;
25 import javax.net.ssl.SSLEngine;
26 import javax.net.ssl.X509ExtendedTrustManager;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
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.
35 * @author David Graeff - Initial contribution
38 public class PinTrustManager extends X509ExtendedTrustManager {
39 List<Pin> pins = new ArrayList<>();
40 protected @Nullable PinnedCallback callback;
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!
48 public void addPinning(Pin pin) {
52 public void setCallback(PinnedCallback callback) {
53 this.callback = callback;
57 public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType)
58 throws CertificateException {
59 throw new UnsupportedOperationException();
62 protected byte[] getEncoded(PinType type, X509Certificate cert) throws CertificateEncodingException {
64 case CERTIFICATE_TYPE:
65 return cert.getEncoded();
67 return cert.getPublicKey().getEncoded();
69 throw new CertificateEncodingException("Type unknown");
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}.
77 PinMessageDigest getMessageDigestForSigAlg(String sigAlg) throws CertificateException {
78 final Matcher matcher = Pattern.compile("(\\D*)(\\d+)").matcher(sigAlg);
80 final String sigAlgName = matcher.group(1);
81 final String sigAlgBits = matcher.group(2);
83 return new PinMessageDigest(sigAlgName + "-" + sigAlgBits);
84 } catch (NoSuchAlgorithmException e) {
85 throw new CertificateException(e);
90 public void checkServerTrusted(X509Certificate @Nullable [] chainN, @Nullable String authType)
91 throws CertificateException {
92 X509Certificate[] chain = chainN;
97 final PinMessageDigest digestForSigAlg = getMessageDigestForSigAlg(chain[0].getSigAlgName());
98 final PinnedCallback callback = this.callback;
100 // All pins have to accept the connection
101 for (Pin pin : pins) {
102 byte[] origData = getEncoded(pin.getType(), chain[0]);
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);
112 final PinMessageDigest hashDigest = pin.hashDigest;
113 if (hashDigest == null) {
114 throw new CertificateException("No hashDigest given!");
117 // Check if hash is equal
118 final byte[] digestData = hashDigest.digest(origData);
119 if (pin.isEqual(digestData)) {
122 // This pin does not accept the connection
123 if (callback != null) {
124 callback.pinnedConnectionDenied(pin);
126 throw new CertificateException(pin.getType().name() + " pinning denied access. Destination pin is "
127 + hashDigest.toHexString(digestData) + "' but expected: " + pin.toString());
130 // All pin instances passed, the connection is accepted
131 if (callback != null) {
132 callback.pinnedConnectionAccepted();
137 public X509Certificate[] getAcceptedIssuers() {
138 return new X509Certificate[0];
142 public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
143 @Nullable Socket socket) throws CertificateException {
144 checkClientTrusted(chain, authType);
148 public void checkClientTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
149 @Nullable SSLEngine sslEngine) throws CertificateException {
150 checkClientTrusted(chain, authType);
154 public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
155 @Nullable Socket socket) throws CertificateException {
156 checkServerTrusted(chain, authType);
160 public void checkServerTrusted(X509Certificate @Nullable [] chain, @Nullable String authType,
161 @Nullable SSLEngine sslEngine) throws CertificateException {
162 checkServerTrusted(chain, authType);