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 static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.mockito.ArgumentMatchers.eq;
18 import static org.mockito.Mockito.*;
20 import java.io.FileNotFoundException;
21 import java.io.InputStream;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.PublicKey;
24 import java.security.cert.CertificateException;
25 import java.security.cert.CertificateFactory;
26 import java.security.cert.X509Certificate;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.junit.jupiter.api.Test;
30 import org.openhab.core.util.HexUtils;
33 * Tests cases for {@link PinTrustManager}.
35 * @author David Graeff - Initial contribution
38 public class PinningSSLContextProviderTest {
41 public void getDigestDataFor() throws NoSuchAlgorithmException, CertificateException, FileNotFoundException {
42 // Load test certificate
43 InputStream inputCert = getClass().getResourceAsStream("cert.pem");
44 X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509")
45 .generateCertificate(inputCert);
47 PinTrustManager pinTrustManager = new PinTrustManager();
48 PinMessageDigest pinMessageDigest = pinTrustManager.getMessageDigestForSigAlg(certificate.getSigAlgName());
49 String hashForCert = HexUtils
50 .bytesToHex(pinMessageDigest.digest(pinTrustManager.getEncoded(PinType.CERTIFICATE_TYPE, certificate)));
51 String expectedHash = "41fa6d40d1e8f53ac81a395ac13b1efa10917718f1ebe3ac278925716d630b72".toUpperCase();
52 assertThat(hashForCert, is(expectedHash));
54 String hashForPublicKey = HexUtils
55 .bytesToHex(pinMessageDigest.digest(pinTrustManager.getEncoded(PinType.PUBLIC_KEY_TYPE, certificate)));
56 String expectedPubKeyHash = "9a6f30e67ae9723579da2575c35daf7da3b370b04ac0bde031f5e1f5e4617eb8".toUpperCase();
57 assertThat(hashForPublicKey, is(expectedPubKeyHash));
60 // Test if X509Certificate.getEncoded() is called if it is a certificate pin and
61 // X509Certificate.getPublicKey().getEncoded() is called if it is a public key pinning.
63 public void certPinCallsX509CertificateGetEncoded() throws NoSuchAlgorithmException, CertificateException {
64 PinTrustManager pinTrustManager = new PinTrustManager();
65 pinTrustManager.addPinning(Pin.learningPin(PinType.CERTIFICATE_TYPE));
68 X509Certificate certificate = mock(X509Certificate.class);
69 when(certificate.getEncoded()).thenReturn(new byte[0]);
70 when(certificate.getSigAlgName()).thenReturn("SHA256withRSA");
72 pinTrustManager.checkServerTrusted(new X509Certificate[] { certificate }, null);
73 verify(certificate).getEncoded();
76 // Test if X509Certificate.getEncoded() is called if it is a certificate pin and
77 // X509Certificate.getPublicKey().getEncoded() is called if it is a public key pinning.
79 public void pubKeyPinCallsX509CertificateGetPublicKey() throws NoSuchAlgorithmException, CertificateException {
80 PinTrustManager pinTrustManager = new PinTrustManager();
81 pinTrustManager.addPinning(Pin.learningPin(PinType.PUBLIC_KEY_TYPE));
84 PublicKey publicKey = mock(PublicKey.class);
85 when(publicKey.getEncoded()).thenReturn(new byte[0]);
87 X509Certificate certificate = mock(X509Certificate.class);
88 when(certificate.getSigAlgName()).thenReturn("SHA256withRSA");
89 when(certificate.getPublicKey()).thenReturn(publicKey);
91 pinTrustManager.checkServerTrusted(new X509Certificate[] { certificate }, null);
92 verify(publicKey).getEncoded();
96 * Overwrite {@link #getMessageDigestForSigAlg(String)} method and return a pre-defined {@link PinMessageDigest}.
98 public static class PinTrustManagerEx extends PinTrustManager {
99 private final PinMessageDigest pinMessageDigest;
101 PinTrustManagerEx(PinMessageDigest pinMessageDigest) {
102 this.pinMessageDigest = pinMessageDigest;
106 PinMessageDigest getMessageDigestForSigAlg(String sigAlg) throws CertificateException {
107 return pinMessageDigest;
112 public void learningMode() throws NoSuchAlgorithmException, CertificateException {
113 PinMessageDigest pinMessageDigest = new PinMessageDigest("SHA-256");
114 PinTrustManager pinTrustManager = new PinTrustManagerEx(pinMessageDigest);
115 byte[] testCert = { 1, 2, 3 };
116 byte[] digestOfTestCert = pinMessageDigest.digest(testCert);
118 // Add a certificate pin in learning mode to a trust manager
119 Pin pin = Pin.learningPin(PinType.CERTIFICATE_TYPE);
120 pinTrustManager.addPinning(pin);
121 assertThat(pinTrustManager.pins.size(), is(1));
124 PinnedCallback callback = mock(PinnedCallback.class);
125 pinTrustManager.setCallback(callback);
127 // Mock a certificate
128 X509Certificate certificate = mock(X509Certificate.class);
129 when(certificate.getEncoded()).thenReturn(testCert);
130 when(certificate.getSigAlgName()).thenReturn("SHA256withRSA");
132 // Perform an SSL certificate check
133 pinTrustManager.checkServerTrusted(new X509Certificate[] { certificate }, null);
135 // After a first connect learning mode should turn into check mode. It should have learned the hash data and
136 // message digest, returned by PinTrustManager.getMessageDigestForSigAlg().
137 assertThat(pin.learning, is(false));
138 assertThat(pin.pinData, is(digestOfTestCert));
139 assertThat(pin.hashDigest, is(pinMessageDigest));
140 // We expect callbacks
141 verify(callback).pinnedLearnedHash(eq(pin));
142 verify(callback).pinnedConnectionAccepted();
146 public void checkMode() throws NoSuchAlgorithmException, CertificateException {
147 PinTrustManager pinTrustManager = new PinTrustManager();
148 PinMessageDigest pinMessageDigest = new PinMessageDigest("SHA-256");
149 byte[] testCert = { 1, 2, 3 };
150 byte[] digestOfTestCert = pinMessageDigest.digest(testCert);
152 // Add a certificate pin in checking mode to a trust manager
153 Pin pin = Pin.checkingPin(PinType.CERTIFICATE_TYPE, pinMessageDigest, digestOfTestCert);
154 pinTrustManager.addPinning(pin);
155 assertThat(pinTrustManager.pins.size(), is(1));
158 PinnedCallback callback = mock(PinnedCallback.class);
159 pinTrustManager.setCallback(callback);
161 // Mock a certificate
162 X509Certificate certificate = mock(X509Certificate.class);
163 when(certificate.getEncoded()).thenReturn(testCert);
164 when(certificate.getSigAlgName()).thenReturn("SHA256withRSA");
166 // Perform an SSL certificate check
167 pinTrustManager.checkServerTrusted(new X509Certificate[] { certificate }, null);
169 // After a first connect learning mode should turn into check mode
170 assertThat(pin.learning, is(false));
171 assertThat(pin.pinData, is(digestOfTestCert));
172 assertThat(pin.hashDigest, is(pinMessageDigest));
173 // We expect callbacks
174 verify(callback, times(0)).pinnedLearnedHash(eq(pin));
175 verify(callback).pinnedConnectionAccepted();