kolide / launcher

Osquery launcher, autoupdater, and packager
https://kolide.com/launcher
Other
505 stars 100 forks source link

We should experiment with better certificate validation #1202

Open directionless opened 1 year ago

directionless commented 1 year ago

We have a request to see if we can validate a certificate. This seems pretty interesting, though I have no idea how feasible it will prove to be.

There are 2 different kinds of validation. I'm not yet sure which the customer wants (internal slack thread). One kind is a digital signature, this would validate the the device has access to the private key. The second kind would be to verify the signature on the cert against a CA bundle, this would validate the signer. Either/both of these has a bunch of questions...

I think the biggest risk is about launcher being disallowed to read the private key material.

directionless commented 1 year ago

Maybe the first thing to look at are the MDM certs. All devices that are in an MDM have a certificate, usually delievered by SCEP.

directionless commented 1 year ago

I found certstore, which is a little out of date, but the underlying macOS framework calls look correct. Writing a test program with that I can:

  1. List the identities present (identities are things that have a cert and associated key)
  2. Sign things
  3. But only if keychain grants access to the key

And by default, the interesting identities do not allow access. For example, my client's MDM issued cert:

Screenshot 2023-06-28 at 16 38 07

I can bypass that with a password, but that's not going to work for launcher.

Test Code:

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "time"

    "github.com/github/smimesign/certstore"
)

func main() {

    if err := certList(); err != nil {
        fmt.Printf("Error: %s\n", err)
    }

}

func certList() error {
    store, err := certstore.Open()
    if err != nil {
        return fmt.Errorf("opening certstore: %w", err)
    }
    defer store.Close()

    idents, err := store.Identities()
    if err != nil {
        return fmt.Errorf("getting identities: %w", err)
    }

    for _, ident := range idents {
        tryIdent(ident)

    }

    return nil
}

func tryIdent(ident certstore.Identity) {
    defer fmt.Println("\n")

    crt, err := ident.Certificate()
    if err != nil {
        fmt.Printf("Not a cert: %s\n", err)
        return
    }

    fmt.Printf("Subject: %s\nIssuer: %s\n", crt.Subject.String(), crt.Issuer.String())

    nonce := time.Now().String()
    fmt.Printf("msg: %s\n", nonce)

    signer, err := ident.Signer()
    if err != nil {
        fmt.Printf("error getting signer: %s", err)
        return
    }

    // Digest and sign our message.
    digest := sha256.Sum256([]byte(nonce))
    signature, err := signer.Sign(rand.Reader, digest[:], crypto.SHA256)
    if err != nil {
        fmt.Printf("error signing: %s", err)
        return
    }

    fmt.Printf("sig: %s\n", base64.StdEncoding.EncodeToString(signature))
}
directionless commented 11 months ago

We've decided to not pursue this right now. But some notes before I mothball it.

Our usually pattern would be to have this brokered by launcher. That pattern would look like select * from cert_sign where message = 'test123' and then launcher would use the certificate to sign that data (plus some kolide extras) and pass the signature back to k2 for verification. Though this approach fits with our current model, it's still has launcher in the loop.

If we wanted to do something that fit the OS patterns and had more server verification, I could imagine having launcher trigger some outbound connection to a server we control that's tied to using these certs. We would then have that server force mTLS validation. And we'd need that server to communicate the connection back to k2. Not really sure how this would work -- we'd need run some servers somewhere we controlled the TLS termination. And we'd need a way to communicate that connection verification back to k2.