Skip to content

Commit

Permalink
[FAB-3456] cryptogen: Add support for x509 SANs
Browse files Browse the repository at this point in the history
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 <gregory.haskins@gmail.com>
  • Loading branch information
ghaskins committed Apr 28, 2017
1 parent cef4f79 commit 5031b0a
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 75 deletions.
6 changes: 3 additions & 3 deletions common/tools/cryptogen/ca/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,23 @@ func TestGenerateSignCertificate(t *testing.T) {
rootCA, err := ca.NewCA(caDir, testCA2Name, testCA2Name)
assert.NoError(t, err, "Error generating CA")

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

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

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

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

Expand Down
3 changes: 2 additions & 1 deletion common/tools/cryptogen/ca/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func NewCA(baseDir, org, name string) (*CA, error) {

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

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

template.Subject = subject
template.DNSNames = sans

cert, err := genCertificateECDSA(baseDir, name, &template, ca.SignCert,
pub, ca.Signer)
Expand Down
171 changes: 105 additions & 66 deletions common/tools/cryptogen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,23 @@ type HostnameData struct {
Domain string
}

type CommonNameData struct {
Hostname string
Domain string
type SpecData struct {
Hostname string
Domain string
CommonName string
}

type NodeTemplate struct {
Count int `yaml:"Count"`
Start int `yaml:"Start"`
Hostname string `yaml:"Hostname"`
Count int `yaml:"Count"`
Start int `yaml:"Start"`
Hostname string `yaml:"Hostname"`
SANS []string `yaml:"SANS"`
}

type NodeSpec struct {
Hostname string `yaml:"Hostname"`
AltHostnames []string `yaml:"AltHostnames"`
CommonName string `yaml:"CommonName"`
Hostname string `yaml:"Hostname"`
CommonName string `yaml:"CommonName"`
SANS []string `yaml:"SANS"`
}

type UsersSpec struct {
Expand Down Expand Up @@ -132,10 +134,20 @@ PeerOrgs:
#
# which obtains its values from the Spec.Hostname and
# Org.Domain, respectively.
# - SANS: (Optional) Specifies one or more Subject Alternative Names
# the be set in the resulting x509. Accepts template
# variables {{.Hostname}}, {{.Domain}}, {{.CommonName}}
# NOTE: Two implicit entries are created for you:
# - {{ .CommonName }}
# - {{ .Hostname }}
# ---------------------------------------------------------------------------
# Specs:
# - Hostname: foo # implicitly "foo.org1.example.com"
# CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above
# SANS:
# - "bar.{{.Domain}}"
# - "altfoo.{{.Domain}}"
# - "{{.Hostname}}.org6.net"
# - Hostname: bar
# - Hostname: baz
Expand All @@ -155,6 +167,8 @@ PeerOrgs:
Count: 1
# Start: 5
# Hostname: {{.Prefix}}{{.Index}} # default
# SANS:
# - "{{.Hostname}}.alt.{{.Domain}}"
# ---------------------------------------------------------------------------
# "Users"
Expand Down Expand Up @@ -234,7 +248,7 @@ func generate() {
}

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

for _, orgSpec := range config.OrdererOrgs {
generateNodeSpec(&orgSpec, "orderer")
renderOrgSpec(&orgSpec, "orderer")
if err != nil {
fmt.Printf("Error processing orderer configuration: %s", err)
os.Exit(-1)
Expand All @@ -252,12 +266,7 @@ func generate() {
}
}

func parseTemplate(input, defaultInput string, data interface{}) (string, error) {

// Use the default if the input is an empty string
if len(input) == 0 {
input = defaultInput
}
func parseTemplate(input string, data interface{}) (string, error) {

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

func renderCN(domain string, spec NodeSpec) (string, error) {
data := CommonNameData{
func parseTemplateWithDefault(input, defaultInput string, data interface{}) (string, error) {

// Use the default if the input is an empty string
if len(input) == 0 {
input = defaultInput
}

return parseTemplate(input, data)
}

func renderNodeSpec(domain string, spec *NodeSpec) error {
data := SpecData{
Hostname: spec.Hostname,
Domain: domain,
}

return parseTemplate(spec.CommonName, defaultCNTemplate, data)
// Process our CommonName
cn, err := parseTemplateWithDefault(spec.CommonName, defaultCNTemplate, data)
if err != nil {
return err
}

spec.CommonName = cn
data.CommonName = cn

// Save off our original, unprocessed SANS entries
origSANS := spec.SANS

// Set our implicit SANS entries for CN/Hostname
spec.SANS = []string{cn, spec.Hostname}

// Finally, process any remaining SANS entries
for _, _san := range origSANS {
san, err := parseTemplate(_san, data)
if err != nil {
return err
}

spec.SANS = append(spec.SANS, san)
}

return nil
}

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

hostname, err := parseTemplate(orgSpec.Template.Hostname, defaultHostnameTemplate, data)
hostname, err := parseTemplateWithDefault(orgSpec.Template.Hostname, defaultHostnameTemplate, data)
if err != nil {
return err
}

spec := NodeSpec{Hostname: hostname}
spec := NodeSpec{
Hostname: hostname,
SANS: orgSpec.Template.SANS,
}
orgSpec.Specs = append(orgSpec.Specs, spec)
}

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

orgSpec.Specs[idx].CommonName = finalCN
orgSpec.Specs[idx] = spec
}

// Process the CA node-spec in the same manner
if len(orgSpec.CA.Hostname) == 0 {
orgSpec.CA.Hostname = "ca"
}
finalCN, err := renderCN(orgSpec.Domain, orgSpec.CA)
err := renderNodeSpec(orgSpec.Domain, &orgSpec.CA)
if err != nil {
return err
}
orgSpec.CA.CommonName = finalCN

return nil
}
Expand Down Expand Up @@ -346,41 +392,40 @@ func generatePeerOrg(baseDir string, orgSpec OrgSpec) {
os.Exit(1)
}

peerNames := []string{}
for _, spec := range orgSpec.Specs {
peerNames = append(peerNames, spec.CommonName)
}
generateNodes(peersDir, peerNames, rootCA)
generateNodes(peersDir, orgSpec.Specs, rootCA)

// TODO: add ability to specify usernames
usernames := []string{}
users := []NodeSpec{}
for j := 1; j <= orgSpec.Users.Count; j++ {
usernames = append(usernames, fmt.Sprintf("%s%d@%s",
userBaseName, j, orgName))
user := NodeSpec{
CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName),
}

users = append(users, user)
}
// add an admin user
adminUserName := fmt.Sprintf("%s@%s",
adminBaseName, orgName)
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}

usernames = append(usernames, adminUserName)
generateNodes(usersDir, usernames, rootCA)
users = append(users, adminUser)
generateNodes(usersDir, users, rootCA)

// copy the admin cert to the org's MSP admincerts
err = copyAdminCert(usersDir, adminCertsDir, adminUserName)
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
orgName, err)
os.Exit(1)
}

// copy the admin cert to each of the org's peer's MSP admincerts
for _, peerName := range peerNames {
for _, spec := range orgSpec.Specs {
err = copyAdminCert(usersDir,
filepath.Join(peersDir, peerName, "msp", "admincerts"),
adminUserName)
filepath.Join(peersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s peer %s:\n%v\n",
orgName, peerName, err)
orgName, spec.CommonName, err)
os.Exit(1)
}
}
Expand All @@ -407,17 +452,16 @@ func copyAdminCert(usersDir, adminCertsDir, adminUserName string) error {

}

func generateNodes(baseDir string, nodeNames []string, rootCA *ca.CA) {
func generateNodes(baseDir string, nodes []NodeSpec, rootCA *ca.CA) {

for _, nodeName := range nodeNames {
nodeDir := filepath.Join(baseDir, nodeName)
err := msp.GenerateLocalMSP(nodeDir, nodeName, rootCA)
for _, node := range nodes {
nodeDir := filepath.Join(baseDir, node.CommonName)
err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, rootCA)
if err != nil {
fmt.Printf("Error generating local MSP for %s:\n%v\n", nodeName, err)
fmt.Printf("Error generating local MSP for %s:\n%v\n", node, err)
os.Exit(1)
}
}

}

func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {
Expand All @@ -442,38 +486,33 @@ func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {
os.Exit(1)
}

// TODO: add ability to specify orderer names
// for name just use default base name
ordererNames := []string{}
for _, spec := range orgSpec.Specs {
ordererNames = append(ordererNames, spec.CommonName)
}
generateNodes(orderersDir, ordererNames, rootCA)
generateNodes(orderersDir, orgSpec.Specs, rootCA)

adminUserName := fmt.Sprintf("%s@%s",
adminBaseName, orgName)
adminUser := NodeSpec{
CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
}

// generate an admin for the orderer org
usernames := []string{}
users := []NodeSpec{}
// add an admin user
usernames = append(usernames, adminUserName)
generateNodes(usersDir, usernames, rootCA)
users = append(users, adminUser)
generateNodes(usersDir, users, rootCA)

// copy the admin cert to the org's MSP admincerts
err = copyAdminCert(usersDir, adminCertsDir, adminUserName)
err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s:\n%v\n",
orgName, err)
os.Exit(1)
}

// copy the admin cert to each of the org's orderers's MSP admincerts
for _, ordererName := range ordererNames {
for _, spec := range orgSpec.Specs {
err = copyAdminCert(usersDir,
filepath.Join(orderersDir, ordererName, "msp", "admincerts"), adminUserName)
filepath.Join(orderersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
if err != nil {
fmt.Printf("Error copying admin cert for org %s orderer %s:\n%v\n",
orgName, ordererName, err)
orgName, spec.CommonName, err)
os.Exit(1)
}
}
Expand Down
4 changes: 2 additions & 2 deletions common/tools/cryptogen/msp/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/hyperledger/fabric/common/tools/cryptogen/csp"
)

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

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

cert, err := rootCA.SignCertificate(filepath.Join(mspDir, "signcerts"), name, ecPubKey)
cert, err := rootCA.SignCertificate(filepath.Join(mspDir, "signcerts"), name, sans, ecPubKey)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 5031b0a

Please sign in to comment.