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 keystoreFileName = "";
78 private String keystoreAlgorithm = "RSA";
79 private int keyLength = 2048;
80 private String alias = "openhab";
81 private String distName = "CN=openHAB, O=openHAB, L=None, ST=None, C=None";
82 private String cipher = "AES/GCM/NoPadding";
83 private String keyAlgorithm = "";
85 private @Nullable Cipher encryptionCipher;
87 public AndroidTVPKI() {
89 encryptionCipher = Cipher.getInstance(cipher);
90 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
91 logger.debug("Could not get cipher instance", e);
95 public byte[] generateEncryptionKey() {
98 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
99 keyGenerator.init(keySize);
100 key = keyGenerator.generateKey();
101 byte[] newKey = key.getEncoded();
102 this.keyAlgorithm = key.getAlgorithm();
104 } catch (NoSuchAlgorithmException e) {
105 logger.debug("Could not generate encryption keys", e);
110 private Key convertByteToKey(byte[] keyString) {
111 Key key = new SecretKeySpec(keyString, keyAlgorithm);
115 public String encrypt(String data, Key key) throws Exception {
116 return encrypt(data, key, this.cipher);
119 public String encrypt(String data, Key key, String cipher) throws Exception {
120 byte[] dataInBytes = data.getBytes();
121 Cipher encryptionCipher = this.encryptionCipher;
122 if (encryptionCipher != null) {
123 encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
124 byte[] encryptedBytes = encryptionCipher.doFinal(dataInBytes);
125 return Base64.getEncoder().encodeToString(encryptedBytes);
131 public String decrypt(String encryptedData, Key key) throws Exception {
132 return decrypt(encryptedData, key, this.cipher);
135 public String decrypt(String encryptedData, Key key, String cipher) throws Exception {
136 byte[] dataInBytes = Base64.getDecoder().decode(encryptedData);
137 Cipher decryptionCipher = Cipher.getInstance(cipher);
138 Cipher encryptionCipher = this.encryptionCipher;
139 if (encryptionCipher != null) {
140 GCMParameterSpec spec = new GCMParameterSpec(dataLength, encryptionCipher.getIV());
141 decryptionCipher.init(Cipher.DECRYPT_MODE, key, spec);
142 byte[] decryptedBytes = decryptionCipher.doFinal(dataInBytes);
143 return new String(decryptedBytes);
149 public void setPrivKey(String privKey, byte[] keyString) throws Exception {
150 Key key = convertByteToKey(keyString);
151 this.privKey = encrypt(privKey, key);
154 public String getPrivKey(byte[] keyString) throws Exception {
155 Key key = convertByteToKey(keyString);
156 return decrypt(this.privKey, key);
159 public void setCert(String cert) {
163 public void setCert(Certificate cert) throws CertificateEncodingException {
164 this.cert = new String(Base64.getEncoder().encode(cert.getEncoded()));
167 public Certificate getCert() throws CertificateException {
168 Certificate cert = CertificateFactory.getInstance("X.509")
169 .generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(this.cert.getBytes())));
173 public void setAlias(String alias) {
177 public String getAlias() {
181 public void setAlgorithm(String keystoreAlgorithm) {
182 this.keystoreAlgorithm = keystoreAlgorithm;
185 public String getAlgorithm() {
186 return this.keystoreAlgorithm;
189 public void setKeyLength(int keyLength) {
190 this.keyLength = keyLength;
193 public int getKeyLength() {
194 return this.keyLength;
197 public void setDistName(String distName) {
198 this.distName = distName;
201 public String getDistName() {
202 return this.distName;
205 public void setKeystoreFileName(String keystoreFileName) {
206 this.keystoreFileName = keystoreFileName;
209 public String getKeystoreFileName() {
210 return this.keystoreFileName;
213 public void setKeys(String privKey, byte[] keyString, String cert) throws GeneralSecurityException, Exception {
214 setPrivKey(privKey, keyString);
218 public void setKeyStore(String keystoreFileName) {
219 this.keystoreFileName = keystoreFileName;
222 public void loadFromKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
223 throws GeneralSecurityException, IOException, Exception {
224 this.keystoreFileName = keystoreFileName;
225 loadFromKeyStore(keystorePassword, keyString);
228 public void loadFromKeyStore(String keystorePassword, byte[] keyString)
229 throws GeneralSecurityException, IOException, Exception {
230 Key key = convertByteToKey(keyString);
231 KeyStore keystore = KeyStore.getInstance("JKS");
232 FileInputStream keystoreInputStream = new FileInputStream(this.keystoreFileName);
233 keystore.load(keystoreInputStream, keystorePassword.toCharArray());
234 byte[] byteKey = keystore.getKey(this.alias, keystorePassword.toCharArray()).getEncoded();
235 this.privKey = encrypt(new String(Base64.getEncoder().encode(byteKey)), key);
236 setCert(keystore.getCertificate(this.alias));
239 public KeyStore getKeyStore(String keystorePassword, byte[] keyString)
240 throws GeneralSecurityException, IOException, Exception {
241 KeyStore keystore = KeyStore.getInstance("JKS");
242 keystore.load(null, null);
243 byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(getPrivKey(keyString));
244 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
245 KeyFactory kf = KeyFactory.getInstance(this.keystoreAlgorithm);
246 keystore.setKeyEntry(this.alias, kf.generatePrivate(keySpec), keystorePassword.toCharArray(),
247 new java.security.cert.Certificate[] { getCert() });
251 public void saveKeyStore(String keystorePassword, byte[] keyString)
252 throws GeneralSecurityException, IOException, Exception {
253 saveKeyStore(this.keystoreFileName, keystorePassword, keyString);
256 public void saveKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
257 throws GeneralSecurityException, IOException, Exception {
258 FileOutputStream keystoreStream = new FileOutputStream(keystoreFileName);
259 KeyStore keystore = getKeyStore(keystorePassword, keyString);
260 keystore.store(keystoreStream, keystorePassword.toCharArray());
263 private X509Certificate generateSelfSignedCertificate(KeyPair keyPair, String distName)
264 throws GeneralSecurityException, OperatorCreationException {
265 final Instant now = Instant.now();
266 final Date notBefore = Date.from(now);
267 final Date notAfter = Date.from(now.plus(Duration.ofDays(365 * 10)));
268 X500Name name = new X500Name(distName);
269 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(name,
270 BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, name, keyPair.getPublic());
271 ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
272 return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider())
273 .getCertificate(certificateBuilder.build(contentSigner));
276 public void generateNewKeyPair(byte[] keyString)
277 throws GeneralSecurityException, OperatorCreationException, IOException, Exception {
278 Key key = convertByteToKey(keyString);
279 KeyPairGenerator kpg = KeyPairGenerator.getInstance(this.keystoreAlgorithm);
280 kpg.initialize(this.keyLength);
281 KeyPair kp = kpg.generateKeyPair();
282 Security.addProvider(new BouncyCastleProvider());
283 Signature signer = Signature.getInstance("SHA256withRSA", "BC");
284 signer.initSign(kp.getPrivate());
285 signer.update("openhab".getBytes(StandardCharsets.UTF_8));
287 X509Certificate signedcert = generateSelfSignedCertificate(kp, this.distName);
288 this.privKey = encrypt(new String(Base64.getEncoder().encode(kp.getPrivate().getEncoded())), key);