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

add secure enclave signatures to local server response #1658

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

Conversation

James-Pickett
Copy link
Contributor

@James-Pickett James-Pickett commented Mar 20, 2024

This PR updates the local server response to include a hardware signature. On macOS the hardware signer is the secure enclave. The secure enclave can only be used by a process running in the user security context. To accomplish this, launcher uses launchctl to exec itself in the user security context.

One thing we wanted to focus on preventing was using a signed launcher binary to sign arbitrary things. To do this, the launcher secure enclave command will only work when it has been provided validated a local server challenge. Additionally, the command adds kolide: & :kolide to either ends of the data it's signing. As well as a nonce and a timestamp to the signed data.

This was not required for windows and linux since the TPM can be used by the root user. However, this has been added to both the linux and TPM signing so that the data structure returned by launcher is the same for all platforms.

Here are some more detailed docs https://github.com/kolide/monorepo/pull/166


// Msg is the base64 encoded message that contains timestamp, nonce, and
// the response from the endpoint called.
Msg string `json:"msg"`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure if this is the best way to pacakge this data, maybe we should continue to return the endpoint response as it is and add on a HardwareSignatureBox something like:

type kryptoEcMiddlewareResponse struct {
        // Data what the endpoint returned
	Data string `json:"data"`
	// HardwareSigBox is b64 encoded []byte of msgpack containing
	// msg, sig, and key []byte fields
	HardwareSigBox string `json:"hardware_sig_box"`
}

type hardwareSigBox struct {
	Msg    []byte `msgpack:"msg"`
	Sig    []byte `msgpack:"sig"`
	PubKey []byte `msgpack:"pub_key"`
}

@James-Pickett James-Pickett marked this pull request as ready for review March 26, 2024 20:52
Copy link
Contributor

@RebeccaMahany RebeccaMahany 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 it partway through -- didn't get a chance to look at localserver in detail yet. I'll come back tomorrow. 🙂

"github.com/kolide/launcher/ee/secureenclavesigner"
)

const secureEnclaveTimestampValiditySeconds = 10

// runSecureEnclave performs either a create-key operation using the secure enclave.
// It's available as a separate command because launcher runs as root by default and since it's
// not in a user security context, it can't use the secure enclave directly. However, this command
// can be run in the user context using launchctl.
func runSecureEnclave(args []string) error {
// currently we are just creating key, but plan to add sign command in future
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// currently we are just creating key, but plan to add sign command in future

// tag the ends of the data to sign, this is intended to ensure that launcher wont
// sign arbitrary things, any party verifying the signature will need to
// handle these tags
dataToSign := []byte(fmt.Sprintf("kolide:%s:kolide", signRequest.Data))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think maybe I'm missing something (I will revisit once you've got a diagram available) -- why do we add kolide: + :kolide on either side of the data at this point? Shouldn't whatever is calling this command have already put kolide on either side so that we can validate that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created a PR for a doc here https://github.com/kolide/monorepo/pull/166.

We have to do it at this point because there is no way to ensure that the process which is execing launcher in the user context adds the tags.

The inability for a process to confidently detect it's parent process is the primary driver for all this tag hoopla.

Copy link
Contributor

@RebeccaMahany RebeccaMahany left a comment

Choose a reason for hiding this comment

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

shape of it looks good to me overall! just a couple questions

Timestamp: time.Now().UTC().Unix(),
// add the kolide:%s:kolide tags since the secure enclave cmd
// wont be execed and add them
Data: []byte(fmt.Sprintf("kolide:%s:kolide", string(data))),
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional -- maybe could add a small helper function in secureenclavesigner to do the kolide:%s:kolide formatting? Then cmd/launcher/secure_enclave_darwin.go could call that when it needs it too (also line 41 in krypto-ec-middleware-response_other.go), and we'd only have one place to remember/update it.

ctx, span := traces.StartSpan(ctx)
defer span.End()

if e.hardwareSigner == nil || e.hardwareSigner.Public() == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

When/why would the hardware signer be nil? Early on in registration/enrollment before we've generated keys? Or another reason?

"err", err,
)
traces.SetError(span, fmt.Errorf("signing with console user hardware key, %w", err))
return e.responseWithoutHardwareSig(o, data)
Copy link
Contributor

Choose a reason for hiding this comment

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

When/why do we choose to return responseWithoutHardwareSig versus returning nil,err?

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

Successfully merging this pull request may close these issues.

None yet

2 participants