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

Introduce DevID node attestor #2111

Merged
merged 40 commits into from
Jul 2, 2021

Conversation

marcosy
Copy link
Contributor

@marcosy marcosy commented Feb 13, 2021

Pull Request check list

  • Commit conforms to CONTRIBUTING.md?
  • Proper tests/regressions included?
  • Documentation updated?

Affected functionality

DevID node attestor.

Description of change

This PR introduces a DevID node attestor plugin according to the proposal made in #1003 (comment)
It is based on the external plugin prototype developed by @langbeck.

In addition to the behavior described in that proposal, an optional configurable was added (check_devid_residency) to validate that the DevID resides in a TPM whose endorsement certificate is signed by a particular manufacturer CA.

This validation is generally done in the TPM provisioning phase. However, SPIRE could also perform this verification as an extra security guarantee.

This work is functional but still in progress. Documentation, unit tests and integration tests are missing as well as some tooling to facilitate the creation of DevIDs. However, I am creating this draft PR now since it may help to illustrate the proposed approach. It would be great to get some early feedback, questions and suggestions at this point.

Which issue this PR fixes

Related to but not fully address #1003. There are other proposed approaches that implement node attestation using directly the TPM endorsement certificate instead of DevIDs. We may want to reserve the "tpm" plugin name for a future SPIRE implementation of those approaches.

Signed-off-by: Marcos Yedro [email protected]

func (p *Plugin) FetchAttestationData(stream nodeattestor.NodeAttestor_FetchAttestationDataServer) error {
conf := p.getConfig()
if conf == nil {
return devid.Error("not configured")
Copy link
Collaborator

Choose a reason for hiding this comment

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

since it is a GRPC call, maybe you can return status.Error here. (same comment for all errors)
for example:

https://github.com/spiffe/spire/blob/master/pkg/server/plugin/upstreamauthority/awspca/pca.go#L271

func (p *Plugin) FetchAttestationData(stream nodeattestor.NodeAttestor_FetchAttestationDataServer) error {
conf := p.getConfig()
if conf == nil {
return devid.Error("not configured")
Copy link
Collaborator

Choose a reason for hiding this comment

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

devid package can be confusing because you are in a devid package, can you set a name like common_devid on import?

// Send attestation request
err = stream.Send(&nodeattestor.FetchAttestationDataResponse{
AttestationData: &spc.AttestationData{
Type: devid.PluginName,
Copy link
Collaborator

Choose a reason for hiding this comment

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

mmm not sure we must have same name for both plugins at least no using the same constant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I can't think of any reason why this wouldn't be appropriate. We are already doing it in other attestors as well.


// If DevID residency verification configured
var credActChallengeResp []byte
if conf.checkDevIDResidency && challenges.CredActivation != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

what happens in case checkDevIDResidency == true and CredActivation == nil,
is it expected that it is a success challenge?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what happens in case checkDevIDResidency == true and CredActivation == nil

That state indicates that the agent was configured to perform the proof of residency but the server was configured to not require it. The server doesn't send any credential activation challenge to solve (nil).

is it expected that it is a success challenge?

I wouldn't say that it is a successful resolution of the challenge. The challenge is not send by the sever, so the agent can (and must) ignore the "solving" function.

}

func (p *Plugin) Configure(ctx context.Context, req *plugin.ConfigureRequest) (*plugin.ConfigureResponse, error) {
err := devid.ValidateGlobalConfig(req.GlobalConfig)
Copy link
Collaborator

Choose a reason for hiding this comment

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

global config with trustdomain will be always there, you added it as a belt and suspender approach right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I saw other node attestors doing that verification so I wanted to be consistent.

return nil, nil, fmt.Errorf("max attempts reached: %w", err)
}

log.Printf("TPM was not able to start the command. Retrying: attempt (%d/%d)", i, maxAttempts)
Copy link
Collaborator

Choose a reason for hiding this comment

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

you must be use some logs here maybe the one that is provided from plugin, but not stdout log.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I forgot to add it after a refactor 🤦

// SRKTemplateRSA returns SRK template used during provisioning.
func SRKTemplateRSA() tpm2.Public {
template := tpm2tools.SRKTemplateRSA()
template.RSAParameters.ModulusRaw = []byte{}
Copy link
Collaborator

Choose a reason for hiding this comment

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

is possible. that ModulusRaw has some value? is it expected it to be replaced?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The gotpm library does not have a method to create the default SRK template recommended by the latest version of the specification. So we create the template on our side. I’ll add a comment and rename the function to make this clearer.

According to TCG TPM v2.0 Provisioning Guidance (section 7.5.1) the Storage Root Key (SRK) template should be based on the Endorsement Key (EK) template.
The EK template is defined in TCG EK Credential Profile for TPM Family 2.0 (section B). The latest version of this document (published in july 2020) defined a new template (called H-1) for the RSA EK template.
The new template sets the unique field (named here as ModulusRaw) to an empty buffer instead of a well-known buffer (filled with 0s).


func validatePluginConfig(extConf *ExternalConfig) error {
// DevID bundle path is always required
if extConf.DevIDBundlePath == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

switch can simplify it

return nil
}

func verifyDevIDSignature(cert *x509.Certificate, roots *x509.CertPool) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

it is used only once, maybe you can avoid creating this function?

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 see your point, but I believe encapsulating this piece of code in a function is still useful to improve code readability.

"2.5.4.17": "POSTALCODE",
}

func FromCertificate(selectorType, prefix string, cert *x509.Certificate) []*spc.Selector {
Copy link
Collaborator

Choose a reason for hiding this comment

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

SelectorsFromCertificate?

Copy link
Collaborator

Choose a reason for hiding this comment

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

or move to a package called selectors

@evan2645
Copy link
Member

evan2645 commented Mar 9, 2021

Thank you for this @marcosy! I've started reviewing it, will probably take a little longer to complete. I do have one high level question though

In addition to the behavior described in that proposal, an optional configurable was added (check_devid_residency) to validate that the DevID resides in a TPM whose endorsement certificate is signed by a particular manufacturer CA.

This validation is generally done in the TPM provisioning phase. However, SPIRE could also perform this verification as an extra security guarantee.

I feel like being able to prove that the cert we're using is backed a key that resides in a TPM is the main motivating factor for making this a dedicated plugin rather than a feature in x509pop. If we're not proving this fact to the server, then it's hard for me to see a difference.

Is there a reason that someone wouldn't want to do this?

@marcosy
Copy link
Contributor Author

marcosy commented Mar 10, 2021

Thanks for your comments @MarcosDY and @evan2645!

Is there a reason that someone wouldn't want to do this?

The only reason I can think of for someone not doing this is due to the extra configurations that are needed.

To verify that the DevID is backed by a key that resides in a TPM from a particular manufacturer, the user has to additionally provide:

  • An attestation key pair:
    The attestation key pair can be generated using the TPM in the same way DevIDs are created. I don’t see any difficulty in getting them.

  • The endorsement certificate roots.
    The problem with this point is that not all TPMs have an endorsement certificate (some manufacturers only include an endorsement key).

However, we could find an alternative validation method in case the endorsement certificate is not available (e.g. using a public key allowed-list in the server-side plugin configuration)

- Setting 'common_devid' import name for 'devid' pkg
- Create errors using status pkg for gRPC handles
- Rename internalConfig/ExternalConfig to config/Config
- Change 'if' to 'switch' for config validation
- Rename some variables for better readibility
- Add some early returns
- Clarify comment for EK handle value

Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
- Rename functions that get selectors
- Change set of 'if' for switch
- Replace newCachedKey function for library function
- Add comment and rename function that creates SRK from default template

Signed-off-by: Marcos Yedro <[email protected]>
@marcosy
Copy link
Contributor Author

marcosy commented Mar 22, 2021

I addressed the initial round of comments. Thanks for the early feedback!
I am still working on unit tests and on some tooling (to facilitate DevID creation). However, I believe this PR can be marked as "Ready for review".

@marcosy marcosy marked this pull request as ready for review March 22, 2021 14:55
@marcosy marcosy changed the title [WIP] Introduce DevID node attestor Introduce DevID node attestor Mar 22, 2021
@azdagron
Copy link
Member

I've just merged #2180. Consequently, you'll need to touch up the import for the nodeattestor definitions.

Old import:
nodeattestorv0 "github.com/spiffe/spire/proto/spire/server/nodeattestor/v0"
New import:
nodeattestorv0 "github.com/spiffe/spire/proto/spire/plugin/server/nodeattestor/v0"

If you'd rather me touch it up and push a commit, I'm happy to oblige. Just let me know.

@marcosy
Copy link
Contributor Author

marcosy commented Apr 1, 2021

Thanks for letting me know! I'll update the PR 🙂

@marcosy
Copy link
Contributor Author

marcosy commented Apr 1, 2021

I’ve been working on some tools to facilitate the testing/use of this plugin.

  1. This repository contains some useful scripts to create and run a TPM emulator in a docker container.
    It contains two scripts. One creates a new TPM (it outputs a TPM state file). And the other one runs the TPM emulator (the user needs to provide a TPM state file).

  2. This repository contains example files to configure the node attestor with the emulator (DevIDs, AK, TPM state).

  3. This repository contains a provisioning tool to generate DevID and AK credentials at will.

I still need to add unit tests for this plugin.

Copy link
Member

@evan2645 evan2645 left a comment

Choose a reason for hiding this comment

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

This is looking GREAT @marcosy! Huge improvements here structurally, it is much much easier to read and review. Good job! :-D

I left some more comments here, much more narrowly scoped than before but still mostly about naming, typo/error cleanup, the autodetect logic etc. I need to do another pass analyzing the exchanges and flow between agent/server, key usages etc, but I think that overall we're looking pretty good and getting close. Thanks for your continued work on this ❤️

Comment on lines 17 to 19
key pair was generated and resides in a TPM. Additionally, the server verifies
that the TPM belongs to a trusted vendor by verifying that the endorsement
certificate is rooted to a trusted set of CAs.
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 the primary purpose for doing this is to ensure that the TPM is authentic (as opposed to a software TPM, where the keys may not be protected at all). Assuming my thinking there is correct, I suggest the following

Suggested change
key pair was generated and resides in a TPM. Additionally, the server verifies
that the TPM belongs to a trusted vendor by verifying that the endorsement
certificate is rooted to a trusted set of CAs.
key pair was generated and resides in a TPM. Additionally, the server verifies
that the TPM is authentic by verifying that the endorsement certificate is
rooted to a trusted set of manufacturer CAs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Looks clearer in that way, I'll update it.

| Configuration | Description | Default |
| ------------------------- | ----------- | ----------------------- |
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |
| `endorsement_bundle_path` | The path to the trusted CA bundle on disk for the endorsement certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |
Copy link
Member

Choose a reason for hiding this comment

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

Similar to above comments, minor nit on the wording:

Suggested change
| `endorsement_bundle_path` | The path to the trusted CA bundle on disk for the endorsement certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |
| `endorsement_ca_path` | The path to the trusted manufacturer CA certificate(s) on disk. The file must contain one or more PEM blocks forming the set of trusted manufacturer CA's for chain-of-trust verification. | |


| Configuration | Description | Default |
| ------------------------- | ----------- | ----------------------- |
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about using ca instead of bundle on the naming here? We use the word bundle a lot, and it frequently means different things ... I feel like ca is a little bit clearer.

Suggested change
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |
| `devid_ca_path` | The path to the trusted CA certificate(s) on disk to use for DevID validation. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | |

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 agree, CA looks more precise. I'll update it.

| Selector | Example | Description |
| ---------------------------- | ----------------------------------------------------------------- | --------------------------------- |
| Fingerprint |`tpm_devid:fingerprint:9ba51e2643bea24e91d24bdec3a1aaf8e967b6e5` | The certificate SHA1 fingerprint as a hex string.|
| Certificate serial number |`tpm_devid:certificate:serialnumber:835861456985135479204994168` | The certificate serial number. |
Copy link
Member

Choose a reason for hiding this comment

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

When we are talking about devid selectors, we are pretty much always talking about certs right? Perhaps we can omit the certificate part here? e.g. tpm_devid:fingerprint:1234, tpm_devid:serialnumber:1234, tpm_devid:subject:cn:foo etc?

Copy link
Member

Choose a reason for hiding this comment

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

nit: serial_number feels a little more consistent since the plugin name is snake case and x509 defines it as serialNumber

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good!

Copy link
Member

Choose a reason for hiding this comment

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

Reading your response to my other comment about IDevID vs LDevID, I realize now that perhaps we will have more than one serial_number type selector in the future if we grow support for IDevID? Maybe instead of omitting certificate from the selector, it should be replaced with ldevid?
tpm_devid:ldevid:serial_number:1234

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 agree with your comment below related to selectors. I believe that for this initial version of the attestor we should be good with having the subject common name and the fingerprints (same selectors than x509pop). We can add more selectors later if there is a use case not covered.

Regarding adding a "ldevid"/"idevid" prefix, since probably only one of them will be used at the time, maybe it is not necessary to specify if it is an IDevID or LDevID in the selectors?

| Fingerprint |`tpm_devid:fingerprint:9ba51e2643bea24e91d24bdec3a1aaf8e967b6e5` | The certificate SHA1 fingerprint as a hex string.|
| Certificate serial number |`tpm_devid:certificate:serialnumber:835861456985135479204994168` | The certificate serial number. |
| Subject common name |`tpm_devid:certificate:subject:cn:example.org` | The subject's common name. |
| Subject common name |`tpm_devid:certificate:subject:cn:example.org` | The subject's common name. |
Copy link
Member

Choose a reason for hiding this comment

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

duplicate?

// Verify DevID residency (if configured)
var nonce []byte
var credActivationChallenge *common_devid.CredActivation
if conf.checkDevIDResidency {
Copy link
Member

Choose a reason for hiding this comment

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

Should this still exist? I think it is left over?

}

// Verify credential activation challenge (if configured)
if conf.checkDevIDResidency {
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here

}

// Create SPIFFE ID and selectors
certSelectors := selectorsFromCertificate(common_devid.PluginName, "certificate", devIDCert)
Copy link
Member

Choose a reason for hiding this comment

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

The number of places that we're generating selectors from a cert is growing. We can already see some divergence between this and x509pop. I wonder if we should have a shared function for this. We've been talking about having similar shared functions exposed for other attestors (e.g. k8s attestor podspec -> selectors function).

I think @azdagron may have some thoughts 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.

Sounds good, I can create a shared function and put it in package "pkg/common/selector", what do you think?


// Create SPIFFE ID and selectors
certSelectors := selectorsFromCertificate(common_devid.PluginName, "certificate", devIDCert)
fingerprint := fingerprint(devIDCert)
Copy link
Member

Choose a reason for hiding this comment

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

This fingerprint part can also be inside selectorsFromCertificate if we are going to share with x509pop

"2.5.4.6": "c", // Country
"2.5.4.8": "st", // State
"2.5.4.10": "o", // Organization
"2.5.4.11": "ou", // Organizational unit
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to not deal with codepoints like this. The pkix name struct given by golang x509 certificate has all of this stuff already and we can create selectors from them explicitly?

I also wonder if we really need all of these. Selector naming and count is a general concern, and it would be good to have some clarity on the need before adding a bunch of them. The x509pop attestor emits only fingerprints and cn, and we have not had any request for more than that... it has been there for some time now.

To be clear, I'm all for having more selectors if it's useful to have them. What I'm trying to avoid is supporting selectors that users may be less likely to consume.

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 talked to some folks which are planning to use this plugin internally. They seem to be ok with using the subject common name and fingerprint. So I think that we should be fine generating the same set of selectors that are generated in x509pop (at least for this initial version).
I'll update this.

Copy link
Member

@amartinezfayo amartinezfayo left a comment

Choose a reason for hiding this comment

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

This is looking great @marcosy!
While I'm doing a more detailed review, I thought to leave a first set of comments.
Awesome job!

// DevID certificate, public and private key are always required
switch {
case c.DevIDCertPath == "":
return fmt.Errorf("devid_cert_path is required")
Copy link
Member

Choose a reason for hiding this comment

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

No need for formatted error:

Suggested change
return fmt.Errorf("devid_cert_path is required")
return errors.New("devid_cert_path is required")

return fmt.Errorf("devid_cert_path is required")

case c.DevIDPrivPath == "":
return fmt.Errorf("devid_priv_path is required")
Copy link
Member

Choose a reason for hiding this comment

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

No need for formatted error:

Suggested change
return fmt.Errorf("devid_priv_path is required")
return errors.New("devid_priv_path is required")

return fmt.Errorf("devid_priv_path is required")

case c.DevIDPubPath == "":
return fmt.Errorf("devid_pub_path is required")
Copy link
Member

Choose a reason for hiding this comment

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

No need for formatted error:

Suggested change
return fmt.Errorf("devid_pub_path is required")
return errors.New("devid_pub_path is required")

return "", errors.New("unable to autodetect TPM")
}

// VerifyTPM verifies that the given path belongs to a functional TPM 2.0
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
// VerifyTPM verifies that the given path belongs to a functional TPM 2.0
// isValidTPM verifies that the given path belongs to a functional TPM 2.0

}

default:
return nil, fmt.Errorf("bad key type: 0x%04x", pub.Type)
Copy link
Member

Choose a reason for hiding this comment

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

0x%04x is always returned instead of pub.Type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I think it is working ok? https://play.golang.org/p/2ecIQALLuu7
The type is printed in hexadecimal format to make it easier to search for it in the go-tpm constants.

Copy link
Member

Choose a reason for hiding this comment

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

Got it!

}

if keyFromCert.E != keyFromTemplate.E {
return fmt.Errorf("exponent missmatch")
Copy link
Member

Choose a reason for hiding this comment

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

No need for formatted error.
There is also a typo:

Suggested change
return fmt.Errorf("exponent missmatch")
return errors.New("exponent mismatch")

}

if keyFromCert.N.Cmp(keyFromTemplate.N) != 0 {
return fmt.Errorf("modulus missmatch")
Copy link
Member

Choose a reason for hiding this comment

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

No need for formatted error.
There is also a typo:

Suggested change
return fmt.Errorf("modulus missmatch")
return errors.New("modulus mismatch")

return params.Sign, nil

default:
return nil, fmt.Errorf("unsupported key type 0x%04x", pub.Type)
Copy link
Member

Choose a reason for hiding this comment

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

0x%04x is always returned instead of pub.Type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I think it is working ok? https://play.golang.org/p/2ecIQALLuu7 but maybe I'm missing something

Copy link
Member

Choose a reason for hiding this comment

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

You are right!


case isRetry(err):
if i == maxAttempts {
return nil, fmt.Errorf("max attempts reached: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

It would be useful to include information about the action involved, otherwise the error may be difficult to understand. Maybe something like max attempts reached while trying to sign the given data.


case isRetry(err):
if i == maxAttempts {
return nil, nil, fmt.Errorf("max attempts reached: %w", err)
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 that here also would benefit from being more detailed about the action that is being performed while the error happened.


1. A proof-of-possession challenge: This is required to verify the node is in
possession of the private key that corresponds to the DevID certificate.
Additionally, the server verifies that the DevID certificate is rooted to
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
Additionally, the server verifies that the DevID certificate is rooted to
Additionally, the server verifies that the DevID certificate is rooted to

}

var sig *tpm2.Signature
for i := 0; i <= maxAttempts; i++ {
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 that this count should start with 1 instead of 0, otherwise the maximum number of attempts will be maxAttempts + 1.

sig, err = tpm2.Sign(k.rw, k.Handle, "", digest, token, nil)
switch {
case err == nil:
break
Copy link
Member

@amartinezfayo amartinezfayo Jun 8, 2021

Choose a reason for hiding this comment

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

This break statement affects the switch only, it doesn't terminate the for loop, that means that the Sign operation will continue to happen until maxAttempts is reached.

Copy link
Member

@amartinezfayo amartinezfayo left a comment

Choose a reason for hiding this comment

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

Thank you for your patience with this @marcosy!
I've noticed that agent_full.conf and server_full.conf are missing updates for the agent and server plugins configs. We should update them.
I left a bunch more comments, but all of them should be easy to address.

return nil, fmt.Errorf("failed to read NV index %08x: %w", EKCertificateHandleRSA, err)
}

// In some TPMs, when we read bytes from a NV index, the readed content
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
// In some TPMs, when we read bytes from a NV index, the readed content
// In some TPMs, when we read bytes from an NV index, the content read

}

// In some TPMs, when we read bytes from a NV index, the readed content
// includes the DER encoded x509 certificate + trailing data. We need to
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
// includes the DER encoded x509 certificate + trailing data. We need to
// includes the DER encoded X.509 certificate + trailing data. We need to

func (c *Session) loadKey(pubKey, privKey []byte, parentKeyPassword, keyPassword string) (*SigningKey, error) {
pub, err := tpm2.DecodePublic(pubKey)
if err != nil {
return nil, fmt.Errorf("tpm2.Public decoding failed: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe tpm2.DecodePublic failed?

},
hSession, // policyHandle: Handle for the policy session being extended.
nil, // policyNonce: The policy nonce for the session (can be the Empty Buffer).
nil, // cpHash: Digest of the command parameters to which this authorization is limited. (If it is not limited, the parameter will be the Empty Buffer).
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
nil, // cpHash: Digest of the command parameters to which this authorization is limited. (If it is not limited, the parameter will be the Empty Buffer).
nil, // cpHash: Digest of the command parameters to which this authorization is limited (if it is not limited, the parameter will be the Empty Buffer).

Log: p.log,
})
if err != nil {
return status.Errorf(codes.Internal, "unable start a new TPM session: %v", err)
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
return status.Errorf(codes.Internal, "unable start a new TPM session: %v", err)
return status.Errorf(codes.Internal, "unable to start a new TPM session: %v", err)

// certificate and key are generated.
func NewProvisioningCA(c *ProvisioningConf) (*ProvisioningAuthority, error) {
if c == nil {
return nil, fmt.Errorf("provisioning config is nil")
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
return nil, fmt.Errorf("provisioning config is nil")
return nil, errors.New("provisioning config is nil")

}

default:
return nil, fmt.Errorf("the root certificate or private key is nil but not both")
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
return nil, fmt.Errorf("the root certificate or private key is nil but not both")
return nil, errors.New("the root certificate or private key is nil but not both")

return provisioningAuthority, nil
}

// Chain returns the leaf and intermediates certificates in DER format
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
// Chain returns the leaf and intermediates certificates in DER format
// Chain returns the leaf and intermediate certificates in DER format

return chain
}

// ChainPem returns the leaf and intermediates certificates in PEM format
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
// ChainPem returns the leaf and intermediates certificates in PEM format
// ChainPem returns the leaf and intermediate certificates in PEM format

privateKey = p.RootKey

default:
return nil, fmt.Errorf("the intermediate certificate or private key is nil but not both")
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
return nil, fmt.Errorf("the intermediate certificate or private key is nil but not both")
return nil, errors.New("the intermediate certificate or private key is nil but not both")

Copy link
Member

@amartinezfayo amartinezfayo left a comment

Choose a reason for hiding this comment

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

Thank you very much for this effort, @marcosy! 🎉

@amartinezfayo amartinezfayo merged commit 32b99d8 into spiffe:main Jul 2, 2021
rturner3 pushed a commit to rturner3/spire that referenced this pull request Aug 4, 2021
* Introduce DevID Node Attestor

Signed-off-by: Marcos Yedro <[email protected]>
rturner3 pushed a commit that referenced this pull request Aug 5, 2021
* Introduce DevID Node Attestor

Signed-off-by: Marcos Yedro <[email protected]>
@rgl
Copy link

rgl commented Oct 29, 2021

Can the documentation at https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_tpm_devid.md be updated to describe the out-of-band mechanism? From reading the @marcosy comment, it seems the tool to do that is at https://github.com/HewlettPackard/devid-provisioning-tool. Maybe add a link to that?

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.

6 participants