-
-
Notifications
You must be signed in to change notification settings - Fork 609
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
Conversation
There was a problem hiding this 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/* |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ❤️.
cmd/gen-ca/main.go
Outdated
return 0, err | ||
} | ||
if len(handles) == 0 { | ||
return 0, errors.New("no objects found matching label and ID") |
There was a problem hiding this comment.
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"?
cmd/gen-ca/main.go
Outdated
} | ||
|
||
const dateLayout = "2006-01-02 15:04:05" | ||
const serialLength = 16 |
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
} | ||
|
||
func main() { | ||
module := flag.String("module", "", "PKCS#11 module to use") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
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") |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
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") |
There was a problem hiding this comment.
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"
cmd/gen-ca/main.go
Outdated
if err := cert.CheckSignatureFrom(issuer); err != nil { | ||
log.Fatalf("Failed to verify certificate signature: %s", err) | ||
} | ||
log.Println("Verifed certificate signature") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: "Verifed" -> "Verified"
cmd/gen-ca/main.go
Outdated
"github.com/letsencrypt/boulder/pkcs11helpers" | ||
) | ||
|
||
type x509Signer struct { |
There was a problem hiding this comment.
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?
cmd/gen-ca/main.go
Outdated
return p.pub | ||
} | ||
|
||
func findObject(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) { |
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
return handles[0], nil | ||
} | ||
|
||
// getKey retrieves the private key handle and constructs the public key defined |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
cmd/gen-ca/main.go
Outdated
|
||
// 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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
return oid, nil | ||
} | ||
|
||
// constructCert generates the certificate template for use in x509.CreateCertificate |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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}, |
There was a problem hiding this comment.
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.
cmd/gen-ca/main.go
Outdated
log.Fatalf("Failed to parse public key: %s", err) | ||
} | ||
|
||
certTmpl, err := constructCert(ctx, &profile, pubPEM.Bytes, session) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
cmd/gen-key/ecdsa.go
Outdated
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) { |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
cmd/gen-ca/main.go
Outdated
|
||
// CommonName should contain the requested subject common name | ||
CommonName string | ||
// CommonName should contain the requested subject organization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/CommonName/Organization/
cmd/gen-ca/main.go
Outdated
CommonName string | ||
// CommonName should contain the requested subject organization | ||
Organization string | ||
// CommonName should contain the requested subject country code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/CommonName/Country/
cmd/gen-ca/main.go
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/OCSPURL/CRLURL/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
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 ofcompatMode
, 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
andCKM_ECDSA_KEY_PAIR_GEN == CKM_EC_KEY_PAIR_GEN
) and just allowed using one of the two names based on preference. This meant withcompatMode
enabled or disabled the tool did the exact same thing.Fixes #3697.