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 PKCS#11 certificate generation tool #3729

Merged
merged 16 commits into from
Jun 12, 2018
Merged

Conversation

rolandshoemaker
Copy link
Contributor

@rolandshoemaker rolandshoemaker commented May 21, 2018

Tested against relevant hardware for generating both RSA and ECDSA roots and intermediates with keys generated using gen-key.

Also this makes a few changes to the gen-key tool after further experience with the HSM and more reading of the PCKS#11 specification. Main change is the removal of compatMode, which was intended to provide support for two naming schemes for EC used in subsequent PKCS#11 drafts. It turns out these schemes were changes in name only and the underlying structs/ints were the exact same (i.e. CKA_ECDSA_PARAMS == CKA_EC_PARAMS and CKM_ECDSA_KEY_PAIR_GEN == CKM_EC_KEY_PAIR_GEN) and just allowed using one of the two names based on preference. This meant with compatMode enabled or disabled the tool did the exact same thing.

Fixes #3697.

@rolandshoemaker rolandshoemaker requested a review from a team as a code owner May 21, 2018 18:51
Copy link
Contributor

@cpu cpu left a comment

Choose a reason for hiding this comment

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

I think cmd/gen-ca/main.go would benefit from some godoc comments on the types & functions defined within.

Overall this branch looks pretty good to me. I'd like to do another 🔍 after I've refreshed my memory on some of the PKCS11 details in use here. I flagged a few small nits from a first pass.

@@ -36,3 +36,4 @@ tags
# IDE support files
.idea

.vscode/*
Copy link
Contributor

Choose a reason for hiding this comment

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

😲 Are you using vscode for Boulder & friends regularly?

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, visual code studio is great ❤️.

return 0, err
}
if len(handles) == 0 {
return 0, errors.New("no objects found matching label and ID")
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be clearer if this said "matching provided template"?

}

const dateLayout = "2006-01-02 15:04:05"
const serialLength = 16
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 this const serialLength and the const dateLayout above could be put inside of the constructCert function scope instead of being global.

}

func main() {
module := flag.String("module", "", "PKCS#11 module to use")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a default for this that we can set for the Linux distro we expect to use this on?

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 PKCS#11 module you use is dependent on the hardware/software you are communicating with. I think putting any defaults here would leak more information about things we use than we are probably comfortable with.

slot := flag.Uint("slot", 0, "Slot signing key is in")
pin := flag.String("pin", "", "PIN for slot if not using PED to login")
label := flag.String("label", "", "Signing key label")
id := flag.String("id", "", "Signing key ID hex")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there an expected format for the hex input? 0xAAAAAA vs AAAAAA?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This led me down a rabbit hole of trying to figure out if there are canonical names/specs for the different textual encoding formats for hexadecimal. Apparently there aren't really any.

id := flag.String("id", "", "Signing key ID hex")
profilePath := flag.String("profile", "", "Path to certificate profile")
pubKeyPath := flag.String("publicKey", "", "Path to public key for certificate") // this could also be in the profile
issuerPath := flag.String("issuer", "", "Path to issuer cert if generating a intermediate")
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "a intermediate" -> "an intermediate"

if err := cert.CheckSignatureFrom(issuer); err != nil {
log.Fatalf("Failed to verify certificate signature: %s", err)
}
log.Println("Verifed certificate signature")
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "Verifed" -> "Verified"

"github.com/letsencrypt/boulder/pkcs11helpers"
)

type x509Signer struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add some Godoc comments for this type & its functions?

return p.pub
}

func findObject(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
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 this function deserves a short comment describing its use/purpose.

return handles[0], nil
}

// getKey retrieves the private key handle and constructs the public key defined
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rewrite this comment? It doesn't really make sense to me. I think something like "constructs an x509Signer based on the PKCS#11 private key object with the provided label and ID."

Also, why do we take both label and ID? It seems like it would make more sense to just take label, and find the ID based on that label.

Copy link
Contributor Author

@rolandshoemaker rolandshoemaker May 29, 2018

Choose a reason for hiding this comment

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

This is mainly a spec thing, CKA_LABEL is not meant to be unique and CKA_ID is intended to differentiate objects in that case (as well as to identify key pairs).


// constructCert generates the certificate template for use in x509.CreateCertificate
func constructCert(ctx pkcs11helpers.PKCtx, profile *certProfile, pubKey []byte, session pkcs11.SessionHandle) (*x509.Certificate, error) {
dateLayout := "2006-01-02 15:04:05"
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to use time.RFC3339 for this? It's a bit annoying since it requires us to write out seconds and time zone, but probably better to be explicit and formal than to not be. Also, the format of these date fields should be documented somewhere. Maybe on the certProfile struct, and make that an exported type so it becomes part of the godoc?

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 think using RFC3339 is a bit of a footgun given it allows input of timezone, which will always need to be ignored/normalized to UTC to conform with 5280. We could use a truncated form of 3339 that just omits the timezone, but at that point we're just adding a T between the date and time for no real reason.

Given any format will need to be documented somewhere for people to know how to use it anyway I think going with this is simplest solution (and easier for humans to read in general).

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, works for me so long as it is well documented.

return oid, nil
}

// constructCert generates the certificate template for use in x509.CreateCertificate
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably called makeTemplate or something since it doesn't actually do the signing.

SignatureAlgorithm: sigAlg,
SerialNumber: big.NewInt(0).SetBytes(serial),
BasicConstraintsValid: true,
IsCA: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

The indentation here is weird. It doesn't line up with the line above. How does this past our gofmt test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, that is weird. /shrug

NotAfter: notAfter,
OCSPServer: []string{profile.OCSPURL},
CRLDistributionPoints: []string{profile.CRLURL},
IssuingCertificateURL: []string{profile.IssuerURL},
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 IssuerURL will be absent for a self-signed root, right? So we should specially handle the case where it's empty.

Relatedly, for each value we interpolate here we should ensure it's non-empty / non-default.

log.Fatalf("Failed to parse public key: %s", err)
}

certTmpl, err := constructCert(ctx, &profile, pubPEM.Bytes, session)
Copy link
Contributor

Choose a reason for hiding this comment

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

*certTemplate

// mechanism and attribute types should be used, for devices that implement
// a pre-2.11 version of the PKCS#11 specification compatMode should be true.
func ecArgs(label string, curve *elliptic.CurveParams, compatMode bool, keyID []byte) generateArgs {
// type of key should be generated.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add to the PR description explaining why compatMode is removed?

pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil),
})
// the correct curve type.
func ecPub(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, expectedCurve *elliptic.CurveParams) (*ecdsa.PublicKey, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as you're touching this line, let's split it into one line per argument.

}
log.Printf("\tX: %X\n", pubKey.X.Bytes())
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you mean to keep this log line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup.

"github.com/miekg/pkcs11"
)

type PKCtx interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

The exported types in this file need documentation.

Seeing this factored out, it really does feel like it duplicates a lot of pkcs11key. Are you sure you wouldn't rather just use the library? It has the benefit that it's used somewhat more, so people are more likely to find bugs.

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 went back and forth about whether to use pkcs11key or not and landed here. I think there are a handful of reasons not to, the largest among them being that it makes testing a bit more painful and and requires either a number of changes to how the ceremony process works or changes to pkcs11key that would only be used in this specific context (which means we're back to it not getting touched by (many) other people in order to find bugs).

I think on the whole I eventually came down on the side of just writing the code here. It allows the gen-ca and gen-key packages to be somewhat more easy to audit, in terms of how they work, and have restricted interfaces in terms of what they can do and what signature formats etc that they support and what kinds of objects they can extract.

Copy link
Contributor

@cpu cpu left a comment

Choose a reason for hiding this comment

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

Only a few tiny cosmetic changes to request.


// CommonName should contain the requested subject common name
CommonName string
// CommonName should contain the requested subject organization
Copy link
Contributor

Choose a reason for hiding this comment

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

s/CommonName/Organization/

CommonName string
// CommonName should contain the requested subject organization
Organization string
// CommonName should contain the requested subject country code
Copy link
Contributor

Choose a reason for hiding this comment

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

s/CommonName/Country/

// OCSPURL should contain the URL at which a OCSP responder that
// can respond to OCSP requests for this certificate operates
OCSPURL string
// OCSPURL should contain the URL at which CRLs for this certificate
Copy link
Contributor

Choose a reason for hiding this comment

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

s/OCSPURL/CRLURL/

Copy link
Contributor

@cpu cpu left a comment

Choose a reason for hiding this comment

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

🎉

@rolandshoemaker rolandshoemaker merged commit 6fe950b into master Jun 12, 2018
@cpu cpu deleted the pkcs11-gen-cert branch June 12, 2018 19:15
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.

3 participants