Skip to content

Commit 5031b0a

Browse files
committed
[FAB-3456] cryptogen: Add support for x509 SANs
The "What" ================= This patch adds support for defining x509 "Subject Alternative Names" (SAN) (https://en.wikipedia.org/wiki/Subject_Alternative_Name). This feature allows an x509 to present multiple valid identities. For example, multiple DNS names representing one key-pair/cert. By default, all x509s generated are populated with two default SAN entries: CommonName and Hostname. Users may extend this with additional definitions via the template engine. See "cryptogen showtemplate" for details. The "Why" ================== Peers deployed in certain contexts such as container orchastration platforms may find certain DNS relationships that can be complex. For instance, two containers "foo" and "bar" might have FDQNs "foo.baz.cluster.local" and "bar.baz.cluster.local" within Kubernetes, just "foo" or "bar" from within the "baz.cluster.local" domain, or a completely different DNS name if the services are mapped outside of the Kubernetes platform. Different schemes may sometimes be easy to use in one context, and difficult to use in another. SAN extentions to x509 means that we don't have to choose. We can simply annotate the x509 for all the valid scenarios while still offering full security. Fixes FAB-3456 Change-Id: Ie6a3864c5675f51097e0b4348bf05ba8c4ef3870 Signed-off-by: Greg Haskins <[email protected]>
1 parent cef4f79 commit 5031b0a

File tree

5 files changed

+115
-75
lines changed

5 files changed

+115
-75
lines changed

common/tools/cryptogen/ca/ca_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,23 @@ func TestGenerateSignCertificate(t *testing.T) {
7171
rootCA, err := ca.NewCA(caDir, testCA2Name, testCA2Name)
7272
assert.NoError(t, err, "Error generating CA")
7373

74-
_, err = rootCA.SignCertificate(certDir, testName, ecPubKey)
74+
_, err = rootCA.SignCertificate(certDir, testName, nil, ecPubKey)
7575
assert.NoError(t, err, "Failed to generate signed certificate")
7676

7777
// check to make sure the signed public key was stored
7878
pemFile := filepath.Join(certDir, testName+"-cert.pem")
7979
assert.Equal(t, true, checkForFile(pemFile),
8080
"Expected to find file "+pemFile)
8181

82-
_, err = rootCA.SignCertificate(certDir, "empty/CA", ecPubKey)
82+
_, err = rootCA.SignCertificate(certDir, "empty/CA", nil, ecPubKey)
8383
assert.Error(t, err, "Bad name should fail")
8484

8585
// use an empty CA to test error path
8686
badCA := &ca.CA{
8787
Name: "badCA",
8888
SignCert: &x509.Certificate{},
8989
}
90-
_, err = badCA.SignCertificate(certDir, testName, &ecdsa.PublicKey{})
90+
_, err = badCA.SignCertificate(certDir, testName, nil, &ecdsa.PublicKey{})
9191
assert.Error(t, err, "Empty CA should not be able to sign")
9292
cleanup(testDir)
9393

common/tools/cryptogen/ca/generator.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func NewCA(baseDir, org, name string) (*CA, error) {
8686

8787
// SignCertificate creates a signed certificate based on a built-in template
8888
// and saves it in baseDir/name
89-
func (ca *CA) SignCertificate(baseDir, name string, pub *ecdsa.PublicKey) (*x509.Certificate, error) {
89+
func (ca *CA) SignCertificate(baseDir, name string, sans []string, pub *ecdsa.PublicKey) (*x509.Certificate, error) {
9090

9191
template := x509Template()
9292
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
@@ -96,6 +96,7 @@ func (ca *CA) SignCertificate(baseDir, name string, pub *ecdsa.PublicKey) (*x509
9696
subject.CommonName = name
9797

9898
template.Subject = subject
99+
template.DNSNames = sans
99100

100101
cert, err := genCertificateECDSA(baseDir, name, &template, ca.SignCert,
101102
pub, ca.Signer)

common/tools/cryptogen/main.go

+105-66
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,23 @@ type HostnameData struct {
4646
Domain string
4747
}
4848

49-
type CommonNameData struct {
50-
Hostname string
51-
Domain string
49+
type SpecData struct {
50+
Hostname string
51+
Domain string
52+
CommonName string
5253
}
5354

5455
type NodeTemplate struct {
55-
Count int `yaml:"Count"`
56-
Start int `yaml:"Start"`
57-
Hostname string `yaml:"Hostname"`
56+
Count int `yaml:"Count"`
57+
Start int `yaml:"Start"`
58+
Hostname string `yaml:"Hostname"`
59+
SANS []string `yaml:"SANS"`
5860
}
5961

6062
type NodeSpec struct {
61-
Hostname string `yaml:"Hostname"`
62-
AltHostnames []string `yaml:"AltHostnames"`
63-
CommonName string `yaml:"CommonName"`
63+
Hostname string `yaml:"Hostname"`
64+
CommonName string `yaml:"CommonName"`
65+
SANS []string `yaml:"SANS"`
6466
}
6567

6668
type UsersSpec struct {
@@ -132,10 +134,20 @@ PeerOrgs:
132134
#
133135
# which obtains its values from the Spec.Hostname and
134136
# Org.Domain, respectively.
137+
# - SANS: (Optional) Specifies one or more Subject Alternative Names
138+
# the be set in the resulting x509. Accepts template
139+
# variables {{.Hostname}}, {{.Domain}}, {{.CommonName}}
140+
# NOTE: Two implicit entries are created for you:
141+
# - {{ .CommonName }}
142+
# - {{ .Hostname }}
135143
# ---------------------------------------------------------------------------
136144
# Specs:
137145
# - Hostname: foo # implicitly "foo.org1.example.com"
138146
# CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above
147+
# SANS:
148+
# - "bar.{{.Domain}}"
149+
# - "altfoo.{{.Domain}}"
150+
# - "{{.Hostname}}.org6.net"
139151
# - Hostname: bar
140152
# - Hostname: baz
141153
@@ -155,6 +167,8 @@ PeerOrgs:
155167
Count: 1
156168
# Start: 5
157169
# Hostname: {{.Prefix}}{{.Index}} # default
170+
# SANS:
171+
# - "{{.Hostname}}.alt.{{.Domain}}"
158172
159173
# ---------------------------------------------------------------------------
160174
# "Users"
@@ -234,7 +248,7 @@ func generate() {
234248
}
235249

236250
for _, orgSpec := range config.PeerOrgs {
237-
err = generateNodeSpec(&orgSpec, "peer")
251+
err = renderOrgSpec(&orgSpec, "peer")
238252
if err != nil {
239253
fmt.Printf("Error processing peer configuration: %s", err)
240254
os.Exit(-1)
@@ -243,7 +257,7 @@ func generate() {
243257
}
244258

245259
for _, orgSpec := range config.OrdererOrgs {
246-
generateNodeSpec(&orgSpec, "orderer")
260+
renderOrgSpec(&orgSpec, "orderer")
247261
if err != nil {
248262
fmt.Printf("Error processing orderer configuration: %s", err)
249263
os.Exit(-1)
@@ -252,12 +266,7 @@ func generate() {
252266
}
253267
}
254268

255-
func parseTemplate(input, defaultInput string, data interface{}) (string, error) {
256-
257-
// Use the default if the input is an empty string
258-
if len(input) == 0 {
259-
input = defaultInput
260-
}
269+
func parseTemplate(input string, data interface{}) (string, error) {
261270

262271
t, err := template.New("parse").Parse(input)
263272
if err != nil {
@@ -273,16 +282,51 @@ func parseTemplate(input, defaultInput string, data interface{}) (string, error)
273282
return output.String(), nil
274283
}
275284

276-
func renderCN(domain string, spec NodeSpec) (string, error) {
277-
data := CommonNameData{
285+
func parseTemplateWithDefault(input, defaultInput string, data interface{}) (string, error) {
286+
287+
// Use the default if the input is an empty string
288+
if len(input) == 0 {
289+
input = defaultInput
290+
}
291+
292+
return parseTemplate(input, data)
293+
}
294+
295+
func renderNodeSpec(domain string, spec *NodeSpec) error {
296+
data := SpecData{
278297
Hostname: spec.Hostname,
279298
Domain: domain,
280299
}
281300

282-
return parseTemplate(spec.CommonName, defaultCNTemplate, data)
301+
// Process our CommonName
302+
cn, err := parseTemplateWithDefault(spec.CommonName, defaultCNTemplate, data)
303+
if err != nil {
304+
return err
305+
}
306+
307+
spec.CommonName = cn
308+
data.CommonName = cn
309+
310+
// Save off our original, unprocessed SANS entries
311+
origSANS := spec.SANS
312+
313+
// Set our implicit SANS entries for CN/Hostname
314+
spec.SANS = []string{cn, spec.Hostname}
315+
316+
// Finally, process any remaining SANS entries
317+
for _, _san := range origSANS {
318+
san, err := parseTemplate(_san, data)
319+
if err != nil {
320+
return err
321+
}
322+
323+
spec.SANS = append(spec.SANS, san)
324+
}
325+
326+
return nil
283327
}
284328

285-
func generateNodeSpec(orgSpec *OrgSpec, prefix string) error {
329+
func renderOrgSpec(orgSpec *OrgSpec, prefix string) error {
286330
// First process all of our templated nodes
287331
for i := 0; i < orgSpec.Template.Count; i++ {
288332
data := HostnameData{
@@ -291,34 +335,36 @@ func generateNodeSpec(orgSpec *OrgSpec, prefix string) error {
291335
Domain: orgSpec.Domain,
292336
}
293337

294-
hostname, err := parseTemplate(orgSpec.Template.Hostname, defaultHostnameTemplate, data)
338+
hostname, err := parseTemplateWithDefault(orgSpec.Template.Hostname, defaultHostnameTemplate, data)
295339
if err != nil {
296340
return err
297341
}
298342

299-
spec := NodeSpec{Hostname: hostname}
343+
spec := NodeSpec{
344+
Hostname: hostname,
345+
SANS: orgSpec.Template.SANS,
346+
}
300347
orgSpec.Specs = append(orgSpec.Specs, spec)
301348
}
302349

303350
// Touch up all general node-specs to add the domain
304351
for idx, spec := range orgSpec.Specs {
305-
finalCN, err := renderCN(orgSpec.Domain, spec)
352+
err := renderNodeSpec(orgSpec.Domain, &spec)
306353
if err != nil {
307354
return err
308355
}
309356

310-
orgSpec.Specs[idx].CommonName = finalCN
357+
orgSpec.Specs[idx] = spec
311358
}
312359

313360
// Process the CA node-spec in the same manner
314361
if len(orgSpec.CA.Hostname) == 0 {
315362
orgSpec.CA.Hostname = "ca"
316363
}
317-
finalCN, err := renderCN(orgSpec.Domain, orgSpec.CA)
364+
err := renderNodeSpec(orgSpec.Domain, &orgSpec.CA)
318365
if err != nil {
319366
return err
320367
}
321-
orgSpec.CA.CommonName = finalCN
322368

323369
return nil
324370
}
@@ -346,41 +392,40 @@ func generatePeerOrg(baseDir string, orgSpec OrgSpec) {
346392
os.Exit(1)
347393
}
348394

349-
peerNames := []string{}
350-
for _, spec := range orgSpec.Specs {
351-
peerNames = append(peerNames, spec.CommonName)
352-
}
353-
generateNodes(peersDir, peerNames, rootCA)
395+
generateNodes(peersDir, orgSpec.Specs, rootCA)
354396

355397
// TODO: add ability to specify usernames
356-
usernames := []string{}
398+
users := []NodeSpec{}
357399
for j := 1; j <= orgSpec.Users.Count; j++ {
358-
usernames = append(usernames, fmt.Sprintf("%s%d@%s",
359-
userBaseName, j, orgName))
400+
user := NodeSpec{
401+
CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName),
402+
}
403+
404+
users = append(users, user)
360405
}
361406
// add an admin user
362-
adminUserName := fmt.Sprintf("%s@%s",
363-
adminBaseName, orgName)
407+
adminUser := NodeSpec{
408+
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
409+
}
364410

365-
usernames = append(usernames, adminUserName)
366-
generateNodes(usersDir, usernames, rootCA)
411+
users = append(users, adminUser)
412+
generateNodes(usersDir, users, rootCA)
367413

368414
// copy the admin cert to the org's MSP admincerts
369-
err = copyAdminCert(usersDir, adminCertsDir, adminUserName)
415+
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
370416
if err != nil {
371417
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
372418
orgName, err)
373419
os.Exit(1)
374420
}
375421

376422
// copy the admin cert to each of the org's peer's MSP admincerts
377-
for _, peerName := range peerNames {
423+
for _, spec := range orgSpec.Specs {
378424
err = copyAdminCert(usersDir,
379-
filepath.Join(peersDir, peerName, "msp", "admincerts"),
380-
adminUserName)
425+
filepath.Join(peersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
381426
if err != nil {
382427
fmt.Printf("Error copying admin cert for org %s peer %s:\n%v\n",
383-
orgName, peerName, err)
428+
orgName, spec.CommonName, err)
384429
os.Exit(1)
385430
}
386431
}
@@ -407,17 +452,16 @@ func copyAdminCert(usersDir, adminCertsDir, adminUserName string) error {
407452

408453
}
409454

410-
func generateNodes(baseDir string, nodeNames []string, rootCA *ca.CA) {
455+
func generateNodes(baseDir string, nodes []NodeSpec, rootCA *ca.CA) {
411456

412-
for _, nodeName := range nodeNames {
413-
nodeDir := filepath.Join(baseDir, nodeName)
414-
err := msp.GenerateLocalMSP(nodeDir, nodeName, rootCA)
457+
for _, node := range nodes {
458+
nodeDir := filepath.Join(baseDir, node.CommonName)
459+
err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, rootCA)
415460
if err != nil {
416-
fmt.Printf("Error generating local MSP for %s:\n%v\n", nodeName, err)
461+
fmt.Printf("Error generating local MSP for %s:\n%v\n", node, err)
417462
os.Exit(1)
418463
}
419464
}
420-
421465
}
422466

423467
func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {
@@ -442,38 +486,33 @@ func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {
442486
os.Exit(1)
443487
}
444488

445-
// TODO: add ability to specify orderer names
446-
// for name just use default base name
447-
ordererNames := []string{}
448-
for _, spec := range orgSpec.Specs {
449-
ordererNames = append(ordererNames, spec.CommonName)
450-
}
451-
generateNodes(orderersDir, ordererNames, rootCA)
489+
generateNodes(orderersDir, orgSpec.Specs, rootCA)
452490

453-
adminUserName := fmt.Sprintf("%s@%s",
454-
adminBaseName, orgName)
491+
adminUser := NodeSpec{
492+
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
493+
}
455494

456495
// generate an admin for the orderer org
457-
usernames := []string{}
496+
users := []NodeSpec{}
458497
// add an admin user
459-
usernames = append(usernames, adminUserName)
460-
generateNodes(usersDir, usernames, rootCA)
498+
users = append(users, adminUser)
499+
generateNodes(usersDir, users, rootCA)
461500

462501
// copy the admin cert to the org's MSP admincerts
463-
err = copyAdminCert(usersDir, adminCertsDir, adminUserName)
502+
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
464503
if err != nil {
465504
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
466505
orgName, err)
467506
os.Exit(1)
468507
}
469508

470509
// copy the admin cert to each of the org's orderers's MSP admincerts
471-
for _, ordererName := range ordererNames {
510+
for _, spec := range orgSpec.Specs {
472511
err = copyAdminCert(usersDir,
473-
filepath.Join(orderersDir, ordererName, "msp", "admincerts"), adminUserName)
512+
filepath.Join(orderersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
474513
if err != nil {
475514
fmt.Printf("Error copying admin cert for org %s orderer %s:\n%v\n",
476-
orgName, ordererName, err)
515+
orgName, spec.CommonName, err)
477516
os.Exit(1)
478517
}
479518
}

common/tools/cryptogen/msp/generator.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
"github.com/hyperledger/fabric/common/tools/cryptogen/csp"
3030
)
3131

32-
func GenerateLocalMSP(baseDir, name string, rootCA *ca.CA) error {
32+
func GenerateLocalMSP(baseDir, name string, sans []string, rootCA *ca.CA) error {
3333

3434
// create folder structure
3535
mspDir := filepath.Join(baseDir, "msp")
@@ -60,7 +60,7 @@ func GenerateLocalMSP(baseDir, name string, rootCA *ca.CA) error {
6060
return err
6161
}
6262

63-
cert, err := rootCA.SignCertificate(filepath.Join(mspDir, "signcerts"), name, ecPubKey)
63+
cert, err := rootCA.SignCertificate(filepath.Join(mspDir, "signcerts"), name, sans, ecPubKey)
6464
if err != nil {
6565
return err
6666
}

0 commit comments

Comments
 (0)