Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of CryptoStore #3302

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

morph166955
Copy link

@morph166955 morph166955 commented Jan 7, 2023

Implements (#3289)

Creates the basis for a centralized crypto storage mechanism. Also creates several functions that are used across addon's to simplify addon creation.

Basic usage expectation:

CryptoStore myCryptoStore = new CryptoStore();
Key cryptoStoreKey = CryptoStore.generateEncryptionKey();

cryptoStoreKey is an AES encryption key used to encrypt the private key while in memory. This must be passed to any function which handles the private key.

Option 1) User has private key and certificate to provide (e.g. something provided by a device)
myCryptoStore.setPrivKey(String myPrivKey, cryptoStoreKey);
myCryptoStore.setCert(String cert); OR .setCert(Certificate cert);

Option 2) User needs a new keypair created.
myCryptoStore.generateNewKeyPair(cryptoStoreKey);

Option 3) User already has a java KeyStore stored on disk and imports it.
myCryptoStore.loadFromKeyStore(String keystoreFileName, String keystorePassword, cryptoStoreKey);

User can request a KeyStore via getKeyStore(String keystorePassword, cryptoStoreKey);

User can save the CryptoStore to a java KeyStore for persistence via loadFromKeyStore(String keystoreFileName, String keystorePassword, cryptoStoreKey);

Several other supporting functions exist and will be documented prior to merge.

This is the first part of the centralized crypto pieces. Part two will be the creation of a centralized storage location. Centralized crypto will store the private key in it's encrypted format as well as the certificate. It will also store an encrypted copy of the cryptoStoreKey. Both the store and key will be recovered by the add-on as needed for operation.

Users can also call encrypt(String data, Key key) or decrypt(String encryptedData, Key key) if they want to simply encrypt/decrypt strings for use. Key can be the cryptoStoreKey or a separate key as needed.

morph166955 and others added 7 commits January 3, 2023 16:05
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@J-N-K J-N-K added enhancement An enhancement or new feature of the Core work in progress A PR that is not yet ready to be merged labels Jan 9, 2023
@J-N-K J-N-K linked an issue Jan 9, 2023 that may be closed by this pull request
@openhab-bot
Copy link
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/openhab-4-0-wishlist/142388/304

Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@morph166955
Copy link
Author

pom.xml conflict should be fixed. This was the addon/binding change.

Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@morph166955
Copy link
Author

I'm down to just a few changes at this point. Code is very stable on the androidtv binding. I need to add a few extra functions to handle CA certs.

@morph166955
Copy link
Author

Just so this doesn't go stale, this is pending additional testing once the androidtv binding is reviewed and merged.

morph166955 and others added 2 commits July 10, 2023 08:52
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@morph166955
Copy link
Author

morph166955 commented Jul 10, 2023

@openhab/core-maintainers This is ready for review. I'm NOT expecting this to make it in for 4.0.0. I highly doubt any binding developer will be able to adapt their binding in the 6 days between now and the code freeze. I see this as a 4.1.0 feature. Also note, this is part 1 of 2. Part 2 will be the centralized crypto storage as discussed in #3289. In theory, CryptoStore replaces and enhances the need for a KeyStore in the bindings (or anywhere else that's relevant). CryptoStore implements several features such as key generation, encryption/decryption of strings, and self signed certificate creation. Code is all based off of the AndroidTV binding implementation which seems to work very well. Once this is locked down I'll work on part 2 with the goal of having both available for 4.1.0.

As a note, I considered adding in some things like TrustManagers to this, but I think that may be better separated out into a separate library. If the consensus is to add it here, I can easily add the functions.

@morph166955 morph166955 marked this pull request as ready for review July 10, 2023 14:51
@morph166955 morph166955 requested a review from a team as a code owner July 10, 2023 14:51
@morph166955 morph166955 changed the title [WIP] Centralized Crypto Library Implementation of CryptoStore Jul 10, 2023
morph166955 and others added 4 commits July 10, 2023 10:25
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@morph166955
Copy link
Author

morph166955 commented Jul 24, 2023

The builds are failing due to the automation integration tests, not due to any specific issue with this PR. This builds cleanly against 4.1.0 on my local system when just compiling the bundle.

@morph166955
Copy link
Author

@openhab/core-maintainers Respectfully requesting a review on this. Code is very stable on AndroidTV binding. I really want to start working on the second part of this to try and get both into 4.1. Thank you!

@wborn wborn removed the work in progress A PR that is not yet ready to be merged label Jan 4, 2024
morph166955 and others added 6 commits January 4, 2024 18:55
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
@openhab-bot
Copy link
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/io-jetty-certificate-embeds-bouncy-castle-crypto-classes-resulting-in-classloader-error/153020/4

@holgerfriedrich
Copy link
Member

@morph166955 I think it would be helpful to integrate bcprov-jdk18on as part of core.
I tried to pull in bouncy castle into KNX addon for a proof of concept for TPMs openhab/openhab-addons#15326 - resulting in +5500 class files and +7MB size of my addon jar.
The same thing seems to happen with androidtv you mentioned.

@morph166955
Copy link
Author

morph166955 commented Jan 14, 2024

Fully agreed and this PR should do that already. My ultimate goal is to really pull as much crypto as we can into a (very) reviewed and hardened implementation to make sure we don't have security leaks/holes in bindings. bcpkix-jdk18on, bcprov-jdk18on, and bcutil-jdk18on are all added to bom/compile/pom.xml as part of this PR.

In respect to TPM usage, my is to do a second PR which will create a central storage location for crypto (certs, keys, passwords, etc). TPM would definitely be a good addition to that once it works. There was some discussion on this in #3289 but it definitely needs further thought.

Copy link
Member

@pacive pacive left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a quick review, but didn't have time to look at everything in detail unfortunately. Apart from the specific comments, JavaDoc needs to be added which will make further reviewing easier. Now I had to try to deduce the functionality and intended use by analyzing the code, which makes it take more time.

But keep up the good work, I think this would be a valuable feature!

}

public void setCert(Certificate cert) throws CertificateEncodingException {
this.cert = new String(Base64.getEncoder().encode(cert.getEncoded()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it stored as a String instead of as a Certificate object?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth on this several times. It was just easier to store and manipulate as a Base64 encoded String in the end. If you look through some older versions of the AndroidTV PR I had it in as a Certificate and ended up changing it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kinds of manipulations would be needed? If a certificate is modified after being signed, the signature (and therefore the entire certificate) becomes invalid.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to go back and look to see what the issue was. I believe part of it was simply the way exceptions were handled (where Certificate throws several that we would have to catch and deal with, String did not which made the code much cleaner). Another part of it was that it's very likely that the bindings are dealing with them in base64 (ATV was for example being sent a certificate as part of something else that I had to deal with) so flipping back and forth was becoming complicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was mainly a thought because the getter for the certificate returns a Certificate object, so there are more conversions when storing as a string that it would be the other way around. Providing a string as a setter should definitely be supported, but it would probably be better to get an exception when trying to set an invalid string-encoded cert than getting it when retrieving the cert.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and looked at the old commits and why I changed this. This is absolutely because of exception handling. I was trying to avoid a try/catch inside the class (so that the binding could handle the try/catch and logs were more accurate). By storing as a string I avoided that (and could simply put it in a throws line for the end user to catch).

throws GeneralSecurityException, IOException, Exception {
Key key = convertByteToKey(keyString);
KeyStore keystore = KeyStore.getInstance("JKS");
FileInputStream keystoreInputStream = new FileInputStream(this.keystoreFileName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the filename an absolute path? How do the binding developers make sure that it works properly on all supported operating systems?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is absolute path. When implemented in ATV I used "String folderName = OpenHAB.getUserDataFolder() + "/androidtv";" to create a path supported accross implementations. I suppose looking at it now that Windows could have an issue with the / versus . I'll check into that. Either way, it's absolute so the binding can decide.

https://github.com/openhab/openhab-addons/blob/06b8c73537f0c03af55b0105cdeaa521ea1e239e/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/googletv/GoogleTVConnectionManager.java#L453

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use OpenHAB.getUserDataFolder() + "/keystore/" + this.filename or something like it to make it easier to use.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the question I have is, is this something we want to force onto the developers. What if there is a use case to interact with something outside of the user data folder for example?

}

public void loadFromKeyStore(String keystorePassword, byte[] keyString)
throws GeneralSecurityException, IOException, Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does any of the methods called throw Exception? Otherwise it's best to be as specific as possible regarding the possible exceptions thrown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does actually (and unfortunately). I'd have to go back through my notes on which. I agree, I hate putting Exception but in this case it actually just throws that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for convenience we should catch it and throw a more specific exception to avoid that the users neet to catch Exception (which may cause SAT warnings about raw exception types?).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if I can narrow down which specific line throws Exception. Not a bad idea.

}
}

public KeyStore getKeyStore(String keystorePassword, byte[] keyString)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for creating a new keystore? Would be better to name it accordingly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this generates a new keystore. What would you suggest to adjust the name too? getKeyStore seemed clean.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSomething is usually used to get a field, createKeyStore would better reflect the purpose.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. In my original thinking, since we were never actually storing a KeyStore, a get seemed like a good facade to the binding developer where we were returning it. I can change this easily enough.


public void saveKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
throws GeneralSecurityException, IOException, Exception {
FileOutputStream keystoreStream = new FileOutputStream(keystoreFileName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If two bindings accidentally choose the same filename, they could overwrite each other's keystore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I'm not sure how to resolve that in this structure. In theory, the binding review process should catch accidental use of another binding name (or something generic like my.keystore). The ultimate goal is to create the next step of this which is effectively a KeyStore warehouse for OH which would handle all keys across all bindings securely and remove this issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a binding need more than one keystore? Otherwise it might be best to just use the binding id? (Together with my other suggestion above)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. Even at the individual thing level yes. For example, ATV uses a mechanism that creates a MITM for purposes of debugs. I have to maintain a keystore for each side of that just for that connection. Also, ATV works with two different protocol stacks in the case of ShieldTV, each uses it's own KeyStore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then this would probably have to be solved with an instruction on how to name the file (e.g bindingId + identifier) that's in the javadoc so it can be (more) easily checked when doing reviews

Copy link

@dbadia dbadia Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building on the discussion of "would a binding need more than one keystore"... technically they wouldn't.

You can store any number of keys in a keystore file. My suggestion would be that each binding have a single keystore file. Expose the keystore object itself for bindings which have complex/multi-key requirements and let them manage key naming themselves.

I'm also thinking of my work on zwave S2 security where every device has its own set of keys. Think it's much better to store those in a single keystore vs a hundred keystore files just for one binding.

This would allow for the use of binding id as keystore name (as suggested above) and resolve the filename collision issue

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went down the road of a single keystore and there were several issues. First, working across things made it complex. Second, the CA path was different for each which added complexity (because they were self signed certs by the device). Ultimately, the work involved with making a single keystore wasn't worth it when maintaining multiple keystores was easy and kept a natural divide across the keys.

Comment on lines +320 to +323
Signature signer = Signature.getInstance("SHA256withRSA", "BC");
signer.initSign(kp.getPrivate());
signer.update("openhab".getBytes(StandardCharsets.UTF_8));
signer.sign();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: Let me get back to you on that. I think that's how I was originally signing the certs. It can probably come out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I understand of the JavaDoc, this only creates a signature for the string "openhab", and the returned signature gets discarded anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think lines 308-310 is what signs the certificate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looking back this looks like an artifact I missed from a previous revision. I can remove it.

keystore.store(keystoreStream, keystorePassword.toCharArray());
}

private X509Certificate generateSelfSignedCertificate(KeyPair keyPair, String distName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is distName passed as an argument instead of using the instance variable?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I could pull that directly. It's only called once internally and when it's called it uses this.distName.

this.privKey = encrypt(privKey, key);
}

public String getPrivKey(byte[] keyString) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to have methods for sign/verify and encrypt/decrypt in this class instead of exposing the private key? Or what other use case would a binding developer have to use the key?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added those into this in the event someone had a use case where they needed to access their private keys. You're absolutely right, it should be held in the class IMHO. I don't use these functions in ATV. I was just trying to be complete by providing access should someone have a use case. I can pull them out if they aren't needed.

Comment on lines +113 to +145
public String encrypt(String data, Key key) throws Exception {
return encrypt(data, key, this.cipher);
}

public String encrypt(String data, Key key, String cipher) throws Exception {
byte[] dataInBytes = data.getBytes();
Cipher encryptionCipher = this.encryptionCipher;
if (encryptionCipher != null) {
encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = encryptionCipher.doFinal(dataInBytes);
return Base64.getEncoder().encodeToString(encryptedBytes);
} else {
return "";
}
}

public String decrypt(String encryptedData, Key key) throws Exception {
return decrypt(encryptedData, key, this.cipher);
}

public String decrypt(String encryptedData, Key key, String cipher) throws Exception {
byte[] dataInBytes = Base64.getDecoder().decode(encryptedData);
Cipher decryptionCipher = Cipher.getInstance(cipher);
Cipher encryptionCipher = this.encryptionCipher;
if (encryptionCipher != null) {
GCMParameterSpec spec = new GCMParameterSpec(keySize, encryptionCipher.getIV());
decryptionCipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] decryptedBytes = decryptionCipher.doFinal(dataInBytes);
return new String(decryptedBytes);
} else {
return "";
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a bit confusing to have a CryptoStore for managing public/private keys and certificates, but at the same time it has encrypt/decrypt functions using symmetric encryption. I don't really see the need for encrypting the keys in-memory (what threat is it supposed to protect against?), it just adds complexity for the binding developers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ultimate goal was to provide a set of functions that are uniform to handle keys rather than have different implementations in the bindings. Different implementations could lead to security risks. Having a central location to handle the functions helps to prevent (notice I didn't say prevents definitively) bad implementations.

The purpose of encrypting the keys is for when we go to the next step of this. It was the though process that each binding would maintain a symmetric key for itself so that no other binding could access keys it wasn't supposed to. In theory, when OH starts and the warehouse is unlocked, that symmetric key would be released to the bindings in some secure manner (still working this part out). Then, when the binding wanted to access it's public/private keys it would ask the warehouse for them, but also have to provide a key to decrypt them properly on the way out. Just keeps everyone honest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but no matter how many keys you use to encrypt other keys, one must be stored somewhere in plaintext in memory (unless you want to ask the user to input it every time the private key is needed) so if someone is in a position to retrieve the private key, they can just as easily get the symmetric key and then decrypt it.

I think your basic idea of a central warehouse is a good one, but as you say, cryptography is very hard to get right. I would be happy to continue discussing how it can be implemented in a good way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and it's a step along the path. Long term goal is to utilize things like TPMs to store keys. I believe it will be a tiered mechanism that the user will ultimately decide. For example, we could (for basic users) have them store a password in a file that's read on start. For more advanced, we could query the user for that password on start to unlock the system. For expert users, maybe we go with TPMs to store everything. This is something I'm planning on dealing with in the second part of this. This simply establishes the foundation for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what is the actual threat here? Who is the attacker that would benefit from retrieving the key? What would they gain? The ability to control your TV? And if they can access memory on your OH server, they could probably do much more damage than that without using the key. Don't get me wrong, having a central credential storage would be a great addition to OH, but the cost of added complexity needs to be weighed against the actual risk.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading a bit more on this, the KeyStore already is an implementation to store the keys securely (both in memory and on disk), so instead of implementing your own security solution, just retrieve the private key and certificate(s) from the KeyStore when needed and don't store it in your class' variables.

Copy link

@dbadia dbadia Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with pacive that encrypting keys in memory adds unnecessary complexity. It's a nice goal, but it's a chicken and egg situation. At some point, something has to be in cleartext which will unlock everything else.

The key isolation goal a very good one, but is very hard to achieve with shared code running on the same filesystem

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concern here was a "rogue binding" more than anything else. For example, a marketplace binding. We didn't want to risk that binding being able to get a hold of credentials or something like that. Also, this is very much for the warehouse than really for this part of the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that solved in an equally secure way by the password on the KeyStore? Also, @J-N-K had a proposal for isolating bindings from each other in this comment, which wouldn't complicate things for binding developers.

@morph166955
Copy link
Author

@pacive Thank you for the review! I believe I've replied to all of your notes above. Absolutely happy to keep talking about any if you don't feel the answer is solid. As I mentioned above, the goal here is two fold.

  1. To create a central set of functions to deal with crypto so that the binding developers are not required to work that (especially since crypto can be confusing to those who don't work on it). This also helps the overall security posture of the platform by reducing the risk of a bad/improper/mistake in those implementations.

  2. To prepare for a key warehouseing function to go into the core. Several of the functions are designed with the concept of shared storage in mind and how to secure the bindings across (e.g. encryption of the keys). The goal would be for the warehouse to hand a CryptoStore and a key to the binding when it asks for it (which would likely be in a recall manner as opposed to a "give me new" which CryptoStore would handle directly).

@holgerfriedrich
Copy link
Member

I played around a bit with your PR and wrote a simple test as well.
For most of the methods the crypto algorithms seem to be provided by the JDK - not BC. If I explicitly add “BC” to the getter, it does not find provider BC.
So I think we should make sure to add the provider BC - as you did for generating keys.
This could be done in the CTOR - and maybe using an Activate tag to ensure BC is properly added during start of the OSGI bundle.
Not sure if we should play with the precedence or query explicitly for “BC”.


== Source Code

https://github.com/openhab/openhab-core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
https://github.com/openhab/openhab-core
https://github.com/openhab/openhab-core
== Third-party Content
bouncy castle
* License: MIT license
* Project: https://www.bouncycastle.org/
* Source: https://www.bouncycastle.org/latest_releases.html

@@ -18,6 +18,7 @@

<properties>
<californium.version>2.7.4</californium.version>
<bouncycastle.version>1.75</bouncycastle.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<bouncycastle.version>1.75</bouncycastle.version>
<bouncycastle.version>1.77</bouncycastle.version>

could we switch to 1.77 - or is there a reason to stick with 1.75 for now?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can switch. 1.77 came out after my last push I believe.

public void loadFromKeyStore(String keystorePassword, byte[] keyString)
throws GeneralSecurityException, IOException, Exception {
Key key = convertByteToKey(keyString);
KeyStore keystore = KeyStore.getInstance("JKS");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of suggestions with regards to keystore type:

  1. Change from JKS format (which can only store asymmetric keys) to "JCEKS" which can store both asym and symmetric keys.
  2. Write the keystore type to a file on the filesystem, perhaps alongside the keystore itself. This allows for:
    a. migration to a different keystore type in the future if necessary. (There's some drama in the keystore space - Oracle recently change their default keystore from JKS to PCKS12, and JKS seems to be deprecated at this point)
    b. There may be cases where a FIPS compliant keystore is required which would warrant a different keystore type. I don't think we need to go all the way down that rabbit hole just yet, but it would be good to have some scaffolding in place

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the use case for storing symmetric keys? In general those are generated as needed for things like IPSEC connections.

I am tracking the Oracle JKS/PKCS12. We could migrate to PKCS12 if we wanted to. I had some issues with it early on and JKS just worked.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The openhab binding has symmetric (AES) keys for secure communication with the devices. Looks like PKCS12 has basic support for symmetric (AES) keys.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AES bits there are just for storing the private key in the memory. It's not used at all for interaction with the device.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are referring to the AES key in the CryptoStore. I am referring to the AES keys in the zwave binding. If the CryptoStore is going to be adopted, it needs to support storage of symmetric keys. JKS does not allow that and JKS format is basically deprecated by Oracle.

Use PCKS12 or JCEKS instead. Use <keystore_name>.pkcs12 or <keykstore_name>.jceks so the format is obvious.. in case it needs to change in the future. Best to change the default now instead of just creating a problem to have to solve later

@J-N-K
Copy link
Member

J-N-K commented Jan 21, 2024

We could add a new interface CredentialConsumer which is automatically added to a CredentialSupplierServlet that allows entering credentials on a web-servlet. The CredentialConsumer could provide a description what is needed (a password, a private/public key, ...) and after reception store it in the crypto store. If it is bound to a thing, it could also implement ThingHandlerService which creates an OSGi component and injects the ThingHandler.

return decrypt(encryptedData, key, this.cipher);
}

public String decrypt(String encryptedData, Key key, String cipher) throws Exception {
Copy link

@dbadia dbadia Jan 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to change throws Exception to throws GeneralSecurityException. Same for the other occurances

Cipher decryptionCipher = Cipher.getInstance(cipher);
Cipher encryptionCipher = this.encryptionCipher;
if (encryptionCipher != null) {
GCMParameterSpec spec = new GCMParameterSpec(keySize, encryptionCipher.getIV());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are using a GCMParameterSpec object here but I don't see one used in the encrypt method. You should probably just remove it from here as the cipher you selected has NoPadding, so IV is irrelevant. Also, invoking encryptionCipher.getIV probably just returns an empty byte array

@J-N-K
Copy link
Member

J-N-K commented Jan 28, 2024

@morph166955 I'll wait with a review until you discussions with @dbadia are finished. I'm not an expert on the crypto-parts, so it's probably better to discuss those issues first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature of the Core
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Centralized Crypto Library
7 participants