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.androidtv.internal.utils;
15 import java.io.ByteArrayInputStream;
16 import java.io.FileInputStream;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.math.BigInteger;
20 import java.nio.charset.StandardCharsets;
21 import java.security.GeneralSecurityException;
22 import java.security.Key;
23 import java.security.KeyFactory;
24 import java.security.KeyPair;
25 import java.security.KeyPairGenerator;
26 import java.security.KeyStore;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.Security;
29 import java.security.Signature;
30 import java.security.cert.Certificate;
31 import java.security.cert.CertificateEncodingException;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateFactory;
34 import java.security.cert.X509Certificate;
35 import java.security.spec.PKCS8EncodedKeySpec;
36 import java.time.Duration;
37 import java.time.Instant;
38 import java.util.Base64;
39 import java.util.Date;
41 import javax.crypto.Cipher;
42 import javax.crypto.KeyGenerator;
43 import javax.crypto.NoSuchPaddingException;
44 import javax.crypto.spec.GCMParameterSpec;
45 import javax.crypto.spec.SecretKeySpec;
47 import org.bouncycastle.asn1.x500.X500Name;
48 import org.bouncycastle.cert.X509v3CertificateBuilder;
49 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
50 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
51 import org.bouncycastle.jce.provider.BouncyCastleProvider;
52 import org.bouncycastle.operator.ContentSigner;
53 import org.bouncycastle.operator.OperatorCreationException;
54 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
55 import org.eclipse.jdt.annotation.NonNullByDefault;
56 import org.eclipse.jdt.annotation.Nullable;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * The {@link AndroidTVPKI} class controls all aspects of the PKI/keyStore
63 * Some methods adapted from Bosch binding
65 * @author Ben Rosenblum - Initial contribution
68 public class AndroidTVPKI {
70 private final Logger logger = LoggerFactory.getLogger(AndroidTVPKI.class);
72 private final int keySize = 128;
73 private final int dataLength = 128;
75 private String privKey = "";
76 private String cert = "";
77 private String caCert = "";
78 private String keystoreFileName = "";
79 private String keystoreAlgorithm = "RSA";
80 private int keyLength = 2048;
81 private String alias = "openhab";
82 private String distName = "CN=openHAB, O=openHAB, L=None, ST=None, C=None";
83 private String cipher = "AES/GCM/NoPadding";
84 private String keyAlgorithm = "";
86 private @Nullable Cipher encryptionCipher;
88 public AndroidTVPKI() {
90 encryptionCipher = Cipher.getInstance(cipher);
91 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
92 logger.debug("Could not get cipher instance", e);
96 public byte[] generateEncryptionKey() {
99 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
100 keyGenerator.init(keySize);
101 key = keyGenerator.generateKey();
102 byte[] newKey = key.getEncoded();
103 this.keyAlgorithm = key.getAlgorithm();
105 } catch (NoSuchAlgorithmException e) {
106 logger.debug("Could not generate encryption keys", e);
111 private Key convertByteToKey(byte[] keyString) {
112 Key key = new SecretKeySpec(keyString, keyAlgorithm);
116 public String encrypt(String data, Key key) throws Exception {
117 return encrypt(data, key, this.cipher);
120 public String encrypt(String data, Key key, String cipher) throws Exception {
121 byte[] dataInBytes = data.getBytes();
122 Cipher encryptionCipher = this.encryptionCipher;
123 if (encryptionCipher != null) {
124 encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
125 byte[] encryptedBytes = encryptionCipher.doFinal(dataInBytes);
126 return Base64.getEncoder().encodeToString(encryptedBytes);
132 public String decrypt(String encryptedData, Key key) throws Exception {
133 return decrypt(encryptedData, key, this.cipher);
136 public String decrypt(String encryptedData, Key key, String cipher) throws Exception {
137 byte[] dataInBytes = Base64.getDecoder().decode(encryptedData);
138 Cipher decryptionCipher = Cipher.getInstance(cipher);
139 Cipher encryptionCipher = this.encryptionCipher;
140 if (encryptionCipher != null) {
141 GCMParameterSpec spec = new GCMParameterSpec(dataLength, encryptionCipher.getIV());
142 decryptionCipher.init(Cipher.DECRYPT_MODE, key, spec);
143 byte[] decryptedBytes = decryptionCipher.doFinal(dataInBytes);
144 return new String(decryptedBytes);
150 public void setPrivKey(String privKey, byte[] keyString) throws Exception {
151 Key key = convertByteToKey(keyString);
152 this.privKey = encrypt(privKey, key);
155 public String getPrivKey(byte[] keyString) throws Exception {
156 Key key = convertByteToKey(keyString);
157 return decrypt(this.privKey, key);
160 public void setCert(String cert) {
164 public void setCert(Certificate cert) throws CertificateEncodingException {
165 this.cert = new String(Base64.getEncoder().encode(cert.getEncoded()));
168 public Certificate getCert() throws CertificateException {
169 Certificate cert = CertificateFactory.getInstance("X.509")
170 .generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(this.cert.getBytes())));
174 public void setCaCert(String caCert) {
175 this.caCert = caCert;
178 public void setCaCert(Certificate caCert) throws CertificateEncodingException {
179 this.caCert = new String(Base64.getEncoder().encode(caCert.getEncoded()));
182 public Certificate getCaCert() throws CertificateException {
183 Certificate caCert = CertificateFactory.getInstance("X.509")
184 .generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(this.caCert.getBytes())));
188 public void setAlias(String alias) {
192 public String getAlias() {
196 public void setAlgorithm(String keystoreAlgorithm) {
197 this.keystoreAlgorithm = keystoreAlgorithm;
200 public String getAlgorithm() {
201 return this.keystoreAlgorithm;
204 public void setKeyLength(int keyLength) {
205 this.keyLength = keyLength;
208 public int getKeyLength() {
209 return this.keyLength;
212 public void setDistName(String distName) {
213 this.distName = distName;
216 public String getDistName() {
217 return this.distName;
220 public void setKeystoreFileName(String keystoreFileName) {
221 this.keystoreFileName = keystoreFileName;
224 public String getKeystoreFileName() {
225 return this.keystoreFileName;
228 public void setKeys(String privKey, byte[] keyString, String cert) throws GeneralSecurityException, Exception {
229 setPrivKey(privKey, keyString);
233 public void setKeyStore(String keystoreFileName) {
234 this.keystoreFileName = keystoreFileName;
237 public void loadFromKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
238 throws GeneralSecurityException, IOException, Exception {
239 this.keystoreFileName = keystoreFileName;
240 loadFromKeyStore(keystorePassword, keyString);
243 public void loadFromKeyStore(String keystorePassword, byte[] keyString)
244 throws GeneralSecurityException, IOException, Exception {
245 Key key = convertByteToKey(keyString);
246 KeyStore keystore = KeyStore.getInstance("JKS");
247 FileInputStream keystoreInputStream = new FileInputStream(this.keystoreFileName);
248 keystore.load(keystoreInputStream, keystorePassword.toCharArray());
249 byte[] byteKey = keystore.getKey(this.alias, keystorePassword.toCharArray()).getEncoded();
250 this.privKey = encrypt(new String(Base64.getEncoder().encode(byteKey)), key);
251 setCert(keystore.getCertificate(this.alias));
252 Certificate caCert = keystore.getCertificate("trustedCa");
253 if (caCert != null) {
258 public KeyStore getKeyStore(String keystorePassword, byte[] keyString)
259 throws GeneralSecurityException, IOException, Exception {
260 KeyStore keystore = KeyStore.getInstance("JKS");
261 keystore.load(null, null);
262 byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(getPrivKey(keyString));
263 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
264 KeyFactory kf = KeyFactory.getInstance(this.keystoreAlgorithm);
265 keystore.setKeyEntry(this.alias, kf.generatePrivate(keySpec), keystorePassword.toCharArray(),
266 new java.security.cert.Certificate[] { getCert() });
267 if (!caCert.isEmpty()) {
268 keystore.setCertificateEntry("trustedCa", getCaCert());
273 public void saveKeyStore(String keystorePassword, byte[] keyString)
274 throws GeneralSecurityException, IOException, Exception {
275 saveKeyStore(this.keystoreFileName, keystorePassword, keyString);
278 public void saveKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
279 throws GeneralSecurityException, IOException, Exception {
280 FileOutputStream keystoreStream = new FileOutputStream(keystoreFileName);
281 KeyStore keystore = getKeyStore(keystorePassword, keyString);
282 keystore.store(keystoreStream, keystorePassword.toCharArray());
285 private X509Certificate generateSelfSignedCertificate(KeyPair keyPair, String distName)
286 throws GeneralSecurityException, OperatorCreationException {
287 final Instant now = Instant.now();
288 final Date notBefore = Date.from(now);
289 final Date notAfter = Date.from(now.plus(Duration.ofDays(365 * 10)));
290 X500Name name = new X500Name(distName);
291 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(name,
292 BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, name, keyPair.getPublic());
293 ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
294 return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider())
295 .getCertificate(certificateBuilder.build(contentSigner));
298 public void generateNewKeyPair(byte[] keyString)
299 throws GeneralSecurityException, OperatorCreationException, IOException, Exception {
300 Key key = convertByteToKey(keyString);
301 KeyPairGenerator kpg = KeyPairGenerator.getInstance(this.keystoreAlgorithm);
302 kpg.initialize(this.keyLength);
303 KeyPair kp = kpg.generateKeyPair();
304 Security.addProvider(new BouncyCastleProvider());
305 Signature signer = Signature.getInstance("SHA256withRSA", "BC");
306 signer.initSign(kp.getPrivate());
307 signer.update("openhab".getBytes(StandardCharsets.UTF_8));
309 X509Certificate signedcert = generateSelfSignedCertificate(kp, this.distName);
310 this.privKey = encrypt(new String(Base64.getEncoder().encode(kp.getPrivate().getEncoded())), key);