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

NOC certificate questions #118

Open
jonsmirl opened this issue Apr 11, 2023 · 13 comments
Open

NOC certificate questions #118

jonsmirl opened this issue Apr 11, 2023 · 13 comments

Comments

@jonsmirl
Copy link

The Sample app is just making a demo controller using the default demo certificates. How do you set the controller up in order to make an actual vendor commissioner?

  1. How many entries can the NOC certificate chain have? Where is the root of that chain?

Should the chain look like this?
RootCA -- company owned (cloud)
IAC -- One per house (cloud)
IAC -- One per controller (stored in controller, shares CASE Authenticated Tag)
NOC - final certificates in the nodes

or like this?
RootCA -- One per house (cloud)
IAC -- One per controller (stored in controller, shares CASE Authenticated Tag)
NOC - final certificates in the nodes

or is the root of this chain a CSA issued certificate?

This model allows people to delete the controller app off from their phones, and then when they reinstall it a new IAC will be generated from the cloud-base house IAC.

  1. Matter NOC certificates need MatterRCACId, MatterFabricId, MatterICACId values encoded into the Issuer and Subject fields. Where do these IDs come from?

  2. So when instantiating a controller it needs parameters:
    setRootCertificate(ByteArray) -- how do you it build this ByteArray from a CRT file?
    setIntermediateCertificate(ByteArray)
    setFabricId(id) - this ID is also in the certificate, where does it come from?
    setIpk() -- it needs this, what is it, and how do I set it?

  3. What do you do with a CASE Authenticated Tag? I think it is for doing this, am I right? I want to have multiple controllers on the same fabric. When a node is commissioned, the nodeId of the commissioner gets written into the ACL. So now if I add a second commissioner, how do I get its nodeID into all of the nodes? I think the answer to this is to use a CASE Authenticated Tag. The first commissioner would update the nodes ACL entry to use the CASE Authenticated Tag instead of a normal nodeID. Then both commissioners would have the same CASE Authenticated Tag. How do you use this feature since the CASE Authenticated Tag has to be inside the NOC certificate? Page 327 of the spec.

@jonsmirl
Copy link
Author

Do you not use FabricId and a certificate at the same time? On IOS the controller API is different than the Java one.

image

@feige2
Copy link

feige2 commented Apr 23, 2023

@jonsmirl Hello, I have the same need, do you have any progress now?

@jonsmirl
Copy link
Author

you need to implement these callbacks

   class myKeypairDelegate : KeypairDelegate {
        /**
         * Ensure that a private key is generated when this method returns.
         * @throws KeypairException if a private key could not be generated or resolved
         */
        @Throws(KeypairException::class)
        override fun generatePrivateKey() {
            Timber.d("generatePrivateKey %p", this)
        }
        /**
         * Returns an operational PKCS#10 CSR in DER-encoded form, signed by the underlying private key.
         * @throws KeypairException if the CSR could not be generated
         */
        @Throws(KeypairException::class)
        override fun createCertificateSigningRequest(): ByteArray? {
            return null;
        }
        /**
         * Returns the DER-encoded X.509 public key, generating a new private key if one has not already
         * been created.
         * @throws KeypairException if a private key could not be resolved
         */
        @Throws(KeypairException::class)
        override fun getPublicKey(): ByteArray? {
            return null;
        }
        /**
         * Signs the given message with the private key (generating one if it has not yet been created)
         * using ECDSA and returns a DER-encoded signature.
         * @throws KeypairException if a private key could not be resolved, or the message could not be
         * signed
         */
        @Throws(KeypairException::class)
        override fun ecdsaSignMessage(message: ByteArray?): ByteArray? {
            return null;
        }

    }

    val operationalKeyConfig = OperationalKeyConfig(myKeypairDelegate(), trustedRootCertificate,
        trustedIntermediateCertificate, nodeOperationalCertificate, ipkEpochKey
    )

    // Lazily instantiate [ChipDeviceController] and hold a reference to it.
    val chipDeviceController: ChipDeviceController by lazy {
        ChipDeviceController.loadJni()
        AndroidChipPlatform(
            AndroidBleManager(),
            PreferencesKeyValueStoreManager(context),
            PreferencesConfigurationManager(context),
            NsdManagerServiceResolver(context),
            NsdManagerServiceBrowser(context),
            ChipMdnsCallbackImpl(),
            DiagnosticDataProviderImpl(context)
        )
        ChipDeviceController(
            ControllerParams.newBuilder(operationalKeyConfig).setUdpListenPort(0)
                .setControllerVendorId(VENDOR_ID).build()
        )
    }

@feige2
Copy link

feige2 commented Apr 23, 2023

@jonsmirl First of all, thank you for your answer. There are some parameters that I don’t understand their meaning, such as:

val operationalKeyConfig = OperationalKeyConfig(myKeypairDelegate(), trustedRootCertificate,
        trustedIntermediateCertificate, nodeOperationalCertificate, ipkEpochKey
    )

In this code, in addition to passing in KeypairDelegate, you also need to pass in trustedRootCertificate, trustedIntermediateCertificate, nodeOperationalCertificate, and ipkEpochKey. Can I generate these certificates myself using openssl? What is ipkEpochKey?

Finally, based on the keywords you mentioned, I found a related PR for this feature that may help us better understand it.

Add support to delegate key storage and signing to the Java layer by g-coppock · Pull Request #19545 · project-chip/connectedhomeip · GitHub

@jonsmirl
Copy link
Author

jonsmirl commented Apr 23, 2023

Google really needs to supply some sample code for this. This sample code does not need to be cloud based, in comments it can simply say -- do this in the cloud. Every Matter controller app has to implement this. You can't use the built-in demo version, it leaves the private key sitting in the config file. There is absolutely zero documentation for doing this. I figured it out by reading the code, making guesses about what to do, until I finally got it working.

You make the root key and cert in the cloud. Code on the phone makes the NOC key and CSR. Those get sent to the cloud to be signed. The NOC private key is kept in phone's trust zone. You can use null for trustedIntermediateCertificate. ipkEpochKey is a random string which I think can be null too. Then you implement KeypairDelegate to utilize that Root key off in the cloud. ecdsaSignMessage() has to ship the message off to the cloud, sign it, and return the response. The trustedIntermediateCertificate can be used to avoid needing to access the cloud for each newly commissioned device.

Also note, each house needs a different root key. Don't use one root key for everything! Those keys should be made using Google Cloud Key Management and stored with protection level SOFTWARE ($0.06/mth). The hardware level protection is unaffordable for this use case. Don't just stick them in a database, because if you lose that database you've given access to every customer.

The root key is per house, not per user. So if two people are in the same house you need to invite the second person into your home and share access to the root key with them. You have to give your IOS app access to this same home root key. So the cloud access has to be cross platform.

@feige2
Copy link

feige2 commented Apr 23, 2023

@jonsmirl Thank you very much, this will be very helpful to me. Google should have written this sample more comprehensively, many key points were not mentioned.

@jonsmirl
Copy link
Author

These keys actually need to be secure. Matter is going to add support for cameras in the next version, and if you lose control of these keys hackers will be able to get into the cameras. This applies even if you don't make cameras. The keys let you onto the entire fabric, not just your own devices on that fabric.

@jonsmirl
Copy link
Author

I am starting to think this Android CHIP controller API is simply broken. IOS started off with the same API Android has then they deprecated it and switched to this new API.

image

In the Android API you can't just pass in the MTRKeypair class like you can on IOS, Instead you have to pass in the OperationalKeyConfig structure which which contains the MTRKeypair reference, but it also has to have the nodeOperationalCertificate as a parameter in that struct and it can't be NULL. And now you are stuck, because CHIP has to call KeypairDelegate to make the nodeOperationalCertificate and you have to pass in the nodeOperationalCertificate using the same struct as you use to pass in KeypairDelegate. IOS deprecated the old API and made the new one which fixes this dependency loop. To make CHIP work with cloud certificates you need that first IOS API.

So the Android work around seems to be that I need to make the controller nodeOperationalCertificate using my own code before CHIP starts so that I can pass it in.

@tnguyen-alarm
Copy link

@jonsmirl Were you ever able to successfully your own KeyPairDelegate and NOC and supply them to CHIP? When I try, I'm running into CHIP_ERROR_INVALID_PUBLIC_KEY because CHIP thinks the public key in the delegate and the NOC don't match. As far as I can tell, they do though...

@jonsmirl
Copy link
Author

They need to match, most likely you don't have one of then encoded right

                       chain = ks.getCertificateChain(pref.activeHome)
                        val sequence = DERSequence.getInstance(chain[0].publicKey.encoded)
                        val subjectPublicKey = sequence.getObjectAt(1) as DERBitString
                        nocKey = subjectPublicKey.bytes
                        @Throws(KeypairException::class)
                        override fun getPublicKey(): ByteArray {
                            return nocKey
                        }

@tnguyen-alarm
Copy link

tnguyen-alarm commented May 31, 2023

@jonsmirl Thank you! That resolved my issue. For some reason, I assumed that chain[0].publicKey.encoded was already DER-encoded.

@tnguyen-alarm
Copy link

@jonsmirl You might have already figured this out, but this helped me understand why we need a NOC before initializing the device controller: project-chip/connectedhomeip#27027

@jonsmirl
Copy link
Author

jonsmirl commented Jun 3, 2023

The controller is just another Matter device, there is nothing special about it, so it needs a NOC just like every other device. Note that a controller is not really making the NOC for the other devices, it is just forwarding that request to NOCChainIssuer. And then it is NOCChainIssuer that makes the NOC certificate. If you have not built your own NOCChainIssuer then you are using the sample one included in the Java library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants