Root issuers lack CA Chain + Chain Building Bug Fix (#15306)

* Return the ca_chain response from root issued cert api

* Fix parent selection in cert chain building

When building chains, we'd choose the next neighbor from Go's
unordered map. However, this doesn't necessarily result in the most
optimal path: we want to prefer to visit roots over other
intermediates, as this allows us to have a more consistent chain,
putting roots before their cross-signed equivalents rather than
potentially at the end.

We additionally now ensure chains are stable.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

Co-authored-by: Steve Clark <steven.clark@hashicorp.com>
This commit is contained in:
Alexander Scheel
2022-05-11 13:09:18 -04:00
committed by GitHub
parent 46ffd77ded
commit aa93464b95
6 changed files with 350 additions and 29 deletions

View File

@@ -4073,9 +4073,23 @@ func runFullCAChainTest(t *testing.T, keyType string) {
} }
fullChain := resp.Data["ca_chain"].(string) fullChain := resp.Data["ca_chain"].(string)
if strings.Count(fullChain, rootCert) != 1 { requireCertInCaChainString(t, fullChain, rootCert, "expected root cert within root cert/ca_chain")
t.Fatalf("expected full chain to contain root certificate; got %v occurrences", strings.Count(fullChain, rootCert))
} // Make sure when we issue a leaf certificate we get the full chain back.
resp, err = client.Logical().Write("pki-root/roles/example", map[string]interface{}{
"allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
})
require.NoError(t, err, "error setting up pki root role: %v", err)
resp, err = client.Logical().Write("pki-root/issue/example", map[string]interface{}{
"common_name": "test.example.com",
"ttl": "5m",
})
require.NoError(t, err, "error issuing certificate from pki root: %v", err)
fullChainArray := resp.Data["ca_chain"].([]interface{})
requireCertInCaChainArray(t, fullChainArray, rootCert, "expected root cert within root issuance pki-root/issue/example")
// Now generate an intermediate at /pki-intermediate, signed by the root. // Now generate an intermediate at /pki-intermediate, signed by the root.
err = client.Sys().Mount("pki-intermediate", &api.MountInput{ err = client.Sys().Mount("pki-intermediate", &api.MountInput{
@@ -4141,12 +4155,25 @@ func runFullCAChainTest(t *testing.T, keyType string) {
require.Equal(t, 0, len(crl.TBSCertList.RevokedCertificates)) require.Equal(t, 0, len(crl.TBSCertList.RevokedCertificates))
fullChain = resp.Data["ca_chain"].(string) fullChain = resp.Data["ca_chain"].(string)
if strings.Count(fullChain, intermediateCert) != 1 { requireCertInCaChainString(t, fullChain, intermediateCert, "expected full chain to contain intermediate certificate from pki-intermediate/cert/ca_chain")
t.Fatalf("expected full chain to contain intermediate certificate; got %v occurrences", strings.Count(fullChain, intermediateCert)) requireCertInCaChainString(t, fullChain, rootCert, "expected full chain to contain root certificate from pki-intermediate/cert/ca_chain")
}
if strings.Count(fullChain, rootCert) != 1 { // Make sure when we issue a leaf certificate we get the full chain back.
t.Fatalf("expected full chain to contain root certificate; got %v occurrences", strings.Count(fullChain, rootCert)) resp, err = client.Logical().Write("pki-intermediate/roles/example", map[string]interface{}{
} "allowed_domains": "example.com",
"allow_subdomains": "true",
"max_ttl": "1h",
})
require.NoError(t, err, "error setting up pki intermediate role: %v", err)
resp, err = client.Logical().Write("pki-intermediate/issue/example", map[string]interface{}{
"common_name": "test.example.com",
"ttl": "5m",
})
require.NoError(t, err, "error issuing certificate from pki intermediate: %v", err)
fullChainArray = resp.Data["ca_chain"].([]interface{})
requireCertInCaChainArray(t, fullChainArray, intermediateCert, "expected full chain to contain intermediate certificate from pki-intermediate/issue/example")
requireCertInCaChainArray(t, fullChainArray, rootCert, "expected full chain to contain root certificate from pki-intermediate/issue/example")
// Finally, import this signing cert chain into a new mount to ensure // Finally, import this signing cert chain into a new mount to ensure
// "external" CAs behave as expected. // "external" CAs behave as expected.
@@ -4206,6 +4233,23 @@ func runFullCAChainTest(t *testing.T, keyType string) {
requireSignedBy(t, issuedCrt, intermediaryCaCert.PublicKey) requireSignedBy(t, issuedCrt, intermediaryCaCert.PublicKey)
} }
func requireCertInCaChainArray(t *testing.T, chain []interface{}, cert string, msgAndArgs ...interface{}) {
var fullChain string
for _, caCert := range chain {
fullChain = fullChain + "\n" + caCert.(string)
}
requireCertInCaChainString(t, fullChain, cert, msgAndArgs)
}
func requireCertInCaChainString(t *testing.T, chain string, cert string, msgAndArgs ...interface{}) {
count := strings.Count(chain, cert)
if count != 1 {
failMsg := fmt.Sprintf("Found %d occurrances of the cert in the provided chain", count)
require.FailNow(t, failMsg, msgAndArgs...)
}
}
type MultiBool int type MultiBool int
const ( const (

View File

@@ -1319,6 +1319,7 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
PolicyIdentifiers: data.role.PolicyIdentifiers, PolicyIdentifiers: data.role.PolicyIdentifiers,
BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA, BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA,
NotBeforeDuration: data.role.NotBeforeDuration, NotBeforeDuration: data.role.NotBeforeDuration,
ForceAppendCaChain: caSign != nil,
}, },
SigningBundle: caSign, SigningBundle: caSign,
CSR: csr, CSR: csr,

View File

@@ -4,8 +4,10 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
vaulthttp "github.com/hashicorp/vault/http" vaulthttp "github.com/hashicorp/vault/http"
@@ -342,6 +344,114 @@ func (c CBUpdateIssuer) Run(t *testing.T, client *api.Client, mount string, know
} }
} }
// Issue a leaf
type CBIssueLeaf struct {
Issuer string
Role string
}
func (c CBIssueLeaf) Run(t *testing.T, client *api.Client, mount string, knownKeys map[string]string, knownCerts map[string]string) {
if len(c.Role) == 0 {
c.Role = "testing"
}
url := mount + "/roles/" + c.Role
data := make(map[string]interface{})
data["allow_localhost"] = true
data["ttl"] = "1s"
data["key_type"] = "ec"
_, err := client.Logical().Write(url, data)
if err != nil {
t.Fatalf("failed to update role (%v): %v / body: %v", c.Role, err, data)
}
url = mount + "/issuer/" + c.Issuer + "/issue/" + c.Role
data = make(map[string]interface{})
data["common_name"] = "localhost"
resp, err := client.Logical().Write(url, data)
if err != nil {
t.Fatalf("failed to issue cert (%v via %v): %v / body: %v", c.Issuer, c.Role, err, data)
}
if resp == nil {
t.Fatalf("failed to issue cert (%v via %v): nil response / body: %v", c.Issuer, c.Role, data)
}
}
// Stable ordering
func ensureStableOrderingOfChains(t *testing.T, client *api.Client, mount string, knownKeys map[string]string, knownCerts map[string]string) {
// Start by fetching all chains
certChains := make(map[string][]string)
for issuer := range knownCerts {
resp, err := client.Logical().Read(mount + "/issuer/" + issuer)
if err != nil {
t.Fatalf("failed to get chain for issuer (%v): %v", issuer, err)
}
rawCurrentChain := resp.Data["ca_chain"].([]interface{})
var currentChain []string
for _, entry := range rawCurrentChain {
currentChain = append(currentChain, strings.TrimSpace(entry.(string)))
}
certChains[issuer] = currentChain
}
// Now, generate a bunch of arbitrary roots and validate the chain is
// consistent.
var runs []time.Duration
for i := 0; i < 10; i++ {
name := "stable-order-root-" + strconv.Itoa(i)
step := CBGenerateRoot{
Key: name,
Name: name,
}
step.Run(t, client, mount, make(map[string]string), make(map[string]string))
before := time.Now()
_, err := client.Logical().Delete(mount + "/issuer/" + name)
if err != nil {
t.Fatalf("failed to delete temporary testing issuer %v: %v", name, err)
}
after := time.Now()
elapsed := after.Sub(before)
runs = append(runs, elapsed)
for issuer := range knownCerts {
resp, err := client.Logical().Read(mount + "/issuer/" + issuer)
if err != nil {
t.Fatalf("failed to get chain for issuer (%v): %v", issuer, err)
}
rawCurrentChain := resp.Data["ca_chain"].([]interface{})
for index, entry := range rawCurrentChain {
if strings.TrimSpace(entry.(string)) != certChains[issuer][index] {
t.Fatalf("iteration %d - chain for issuer %v differed at index %d\n%v\nvs\n%v", i, issuer, index, entry, certChains[issuer][index])
}
}
}
}
min := runs[0]
max := runs[0]
var avg time.Duration
for _, run := range runs {
if run < min {
min = run
}
if run > max {
max = run
}
avg += run
}
avg = avg / time.Duration(len(runs))
t.Logf("Chain building run time (deletion) - min: %v / avg: %v / max: %v - entries: %v", min, avg, max, runs)
}
type CBTestStep interface { type CBTestStep interface {
Run(t *testing.T, client *api.Client, mount string, knownKeys map[string]string, knownCerts map[string]string) Run(t *testing.T, client *api.Client, mount string, knownKeys map[string]string, knownCerts map[string]string)
} }
@@ -619,6 +729,14 @@ func Test_CAChainBuilding(t *testing.T) {
"full-cycle": "root-old-a,root-old-b,root-old-c,cross-old-new,cross-new-old,root-new-a,root-new-b", "full-cycle": "root-old-a,root-old-b,root-old-c,cross-old-new,cross-new-old,root-new-a,root-new-b",
}, },
}, },
CBIssueLeaf{Issuer: "root-old-a"},
CBIssueLeaf{Issuer: "root-old-b"},
CBIssueLeaf{Issuer: "root-old-c"},
CBIssueLeaf{Issuer: "cross-old-new"},
CBIssueLeaf{Issuer: "cross-new-old"},
CBIssueLeaf{Issuer: "root-new-a"},
CBIssueLeaf{Issuer: "root-new-b"},
CBIssueLeaf{Issuer: "inter-a-root-new"},
}, },
}, },
{ {
@@ -754,6 +872,15 @@ func Test_CAChainBuilding(t *testing.T) {
"d-chained-cross": "root-d,cross-c-d,root-c,cross-b-c,root-b,cross-a-b,root-a", "d-chained-cross": "root-d,cross-c-d,root-c,cross-b-c,root-b,cross-a-b,root-a",
}, },
}, },
CBIssueLeaf{Issuer: "root-a"},
CBIssueLeaf{Issuer: "cross-a-b"},
CBIssueLeaf{Issuer: "root-b"},
CBIssueLeaf{Issuer: "cross-b-c"},
CBIssueLeaf{Issuer: "root-c"},
CBIssueLeaf{Issuer: "cross-c-d"},
CBIssueLeaf{Issuer: "root-d"},
CBIssueLeaf{Issuer: "cross-d-e"},
CBIssueLeaf{Issuer: "root-e"},
// Importing the new e->a cross fails because the cycle // Importing the new e->a cross fails because the cycle
// it builds is too long. // it builds is too long.
CBGenerateIntermediate{ CBGenerateIntermediate{
@@ -777,6 +904,14 @@ func Test_CAChainBuilding(t *testing.T) {
CommonName: "root-a", CommonName: "root-a",
Parent: "root-e", Parent: "root-e",
}, },
CBIssueLeaf{Issuer: "root-a"},
CBIssueLeaf{Issuer: "cross-a-b"},
CBIssueLeaf{Issuer: "root-c"},
CBIssueLeaf{Issuer: "cross-c-d"},
CBIssueLeaf{Issuer: "root-d"},
CBIssueLeaf{Issuer: "cross-d-e"},
CBIssueLeaf{Issuer: "root-e"},
CBIssueLeaf{Issuer: "cross-e-a"},
}, },
}, },
{ {
@@ -818,6 +953,12 @@ func Test_CAChainBuilding(t *testing.T) {
Name: "root-f", Name: "root-f",
CommonName: "root", CommonName: "root",
}, },
CBIssueLeaf{Issuer: "root-a"},
CBIssueLeaf{Issuer: "root-b"},
CBIssueLeaf{Issuer: "root-c"},
CBIssueLeaf{Issuer: "root-d"},
CBIssueLeaf{Issuer: "root-e"},
CBIssueLeaf{Issuer: "root-f"},
// Seventh reissuance fails. // Seventh reissuance fails.
CBGenerateRoot{ CBGenerateRoot{
Key: "key-root", Key: "key-root",
@@ -834,6 +975,12 @@ func Test_CAChainBuilding(t *testing.T) {
Name: "root-g", Name: "root-g",
CommonName: "root", CommonName: "root",
}, },
CBIssueLeaf{Issuer: "root-b"},
CBIssueLeaf{Issuer: "root-c"},
CBIssueLeaf{Issuer: "root-d"},
CBIssueLeaf{Issuer: "root-e"},
CBIssueLeaf{Issuer: "root-f"},
CBIssueLeaf{Issuer: "root-g"},
}, },
}, },
{ {
@@ -965,6 +1112,92 @@ func Test_CAChainBuilding(t *testing.T) {
"all-root-old": "root-old-a,root-old-a-reissued,root-old-b,root-old-b-reissued,cross-root-old-b-sig-a,cross-root-old-a-sig-b", "all-root-old": "root-old-a,root-old-a-reissued,root-old-b,root-old-b-reissued,cross-root-old-b-sig-a,cross-root-old-a-sig-b",
}, },
}, },
CBIssueLeaf{Issuer: "root-new-a"},
CBIssueLeaf{Issuer: "root-new-b"},
CBIssueLeaf{Issuer: "cross-root-new-b-sig-a"},
CBIssueLeaf{Issuer: "cross-root-new-a-sig-b"},
CBIssueLeaf{Issuer: "root-old-a"},
CBIssueLeaf{Issuer: "root-old-a-reissued"},
CBIssueLeaf{Issuer: "root-old-b"},
CBIssueLeaf{Issuer: "root-old-b-reissued"},
CBIssueLeaf{Issuer: "cross-root-old-b-sig-a"},
CBIssueLeaf{Issuer: "cross-root-old-a-sig-b"},
CBIssueLeaf{Issuer: "cross-root-old-a-sig-root-new-a"},
},
},
{
// Test a dual-root of trust chaining example with different
// lengths of chains.
Steps: []CBTestStep{
CBGenerateRoot{
Key: "key-root-new",
Name: "root-new",
},
CBGenerateIntermediate{
Key: "key-inter-new",
Name: "inter-new",
Parent: "root-new",
},
CBGenerateRoot{
Key: "key-root-old",
Name: "root-old",
},
CBGenerateIntermediate{
Key: "key-inter-old-a",
Name: "inter-old-a",
Parent: "root-old",
},
CBGenerateIntermediate{
Key: "key-inter-old-b",
Name: "inter-old-b",
Parent: "inter-old-a",
},
// Now generate a cross-signed intermediate to merge these
// two chains.
CBGenerateIntermediate{
Key: "key-cross-old-new",
Name: "cross-old-new-signed-new",
CommonName: "cross-old-new",
Parent: "inter-new",
},
CBGenerateIntermediate{
Key: "key-cross-old-new",
Existing: true,
Name: "cross-old-new-signed-old",
CommonName: "cross-old-new",
Parent: "inter-old-b",
},
CBGenerateIntermediate{
Key: "key-leaf-inter",
Name: "leaf-inter",
Parent: "cross-old-new-signed-new",
},
CBValidateChain{
Chains: map[string][]string{
"root-new": {"self"},
"inter-new": {"self", "root-new"},
"cross-old-new-signed-new": {"self", "inter-new", "root-new"},
"root-old": {"self"},
"inter-old-a": {"self", "root-old"},
"inter-old-b": {"self", "inter-old-a", "root-old"},
"cross-old-new-signed-old": {"self", "inter-old-b", "inter-old-a", "root-old"},
"leaf-inter": {"self", "either-cross", "one-intermediate", "other-inter-or-root", "everything-else", "everything-else", "everything-else", "everything-else"},
},
Aliases: map[string]string{
"either-cross": "cross-old-new-signed-new,cross-old-new-signed-old",
"one-intermediate": "inter-new,inter-old-b",
"other-inter-or-root": "root-new,inter-old-a",
"everything-else": "cross-old-new-signed-new,cross-old-new-signed-old,inter-new,inter-old-b,root-new,inter-old-a,root-old",
},
},
CBIssueLeaf{Issuer: "root-new"},
CBIssueLeaf{Issuer: "inter-new"},
CBIssueLeaf{Issuer: "root-old"},
CBIssueLeaf{Issuer: "inter-old-a"},
CBIssueLeaf{Issuer: "inter-old-b"},
CBIssueLeaf{Issuer: "cross-old-new-signed-new"},
CBIssueLeaf{Issuer: "cross-old-new-signed-old"},
CBIssueLeaf{Issuer: "leaf-inter"},
}, },
}, },
} }
@@ -979,5 +1212,7 @@ func Test_CAChainBuilding(t *testing.T) {
testStep.Run(t, client, mount, knownKeys, knownCerts) testStep.Run(t, client, mount, knownKeys, knownCerts)
} }
t.Logf("Checking stable ordering of chains...")
ensureStableOrderingOfChains(t, client, mount, knownKeys, knownCerts)
} }
} }

View File

@@ -81,7 +81,7 @@ func rebuildIssuersChains(ctx context.Context, s logical.Storage, referenceCert
// are the same across multiple calls to rebuildIssuersChains with the same // are the same across multiple calls to rebuildIssuersChains with the same
// input data. // input data.
sort.SliceStable(issuers, func(i, j int) bool { sort.SliceStable(issuers, func(i, j int) bool {
return issuers[i] < issuers[j] return issuers[i] > issuers[j]
}) })
// We expect each of these maps to be the size of the number of issuers // We expect each of these maps to be the size of the number of issuers
@@ -351,9 +351,33 @@ func rebuildIssuersChains(ctx context.Context, s logical.Storage, referenceCert
// ...and add all parents into it. Note that we have to tell if // ...and add all parents into it. Note that we have to tell if
// that parent was already visited or not. // that parent was already visited or not.
if ok && len(parentCerts) > 0 { if ok && len(parentCerts) > 0 {
// Split children into two categories: roots and intermediates.
// When building a straight-line chain, we want to prefer the
// root (thus, ending the verification) to any cross-signed
// intermediates. If a root is cross-signed, we'll include it's
// cross-signed cert in _its_ chain, thus ignoring our duplicate
// parent here.
//
// Why? When you step from the present node ("issuer") onto one
// of its parents, if you step onto a root, it is a no-op: you
// can still visit all of the neighbors (because any neighbors,
// if they exist, must be cross-signed alternative paths).
// However, if you directly step onto the cross-signed, now you're
// taken in an alternative direction (via its chain), and must
// revisit any roots later.
var roots []issuerID
var intermediates []issuerID
for _, parentCertId := range parentCerts {
if bytes.Equal(issuerIdCertMap[parentCertId].RawSubject, issuerIdCertMap[parentCertId].RawIssuer) {
roots = append(roots, parentCertId)
} else {
intermediates = append(intermediates, parentCertId)
}
}
includedParentCerts := make(map[string]bool, len(parentCerts)+1) includedParentCerts := make(map[string]bool, len(parentCerts)+1)
includedParentCerts[entry.Certificate] = true includedParentCerts[entry.Certificate] = true
for _, parentCert := range parentCerts { for _, parentCert := range append(roots, intermediates...) {
// See discussion of the algorithm above as to why this is // See discussion of the algorithm above as to why this is
// in the correct order. However, note that we do need to // in the correct order. However, note that we do need to
// exclude duplicate certs, hence the map above. // exclude duplicate certs, hence the map above.
@@ -533,7 +557,7 @@ func processAnyCliqueOrCycle(
// cross-signing chains; the latter ensures that any cliques can be // cross-signing chains; the latter ensures that any cliques can be
// strictly bypassed from cycles (but the chain construction later // strictly bypassed from cycles (but the chain construction later
// ensures we pull in the cliques into the cycles). // ensures we pull in the cliques into the cycles).
foundCycles, err := findCyclesNearClique(processedIssuers, issuerIdChildrenMap, allCliqueNodes) foundCycles, err := findCyclesNearClique(processedIssuers, issuerIdCertMap, issuerIdChildrenMap, allCliqueNodes)
if err != nil { if err != nil {
// Cycle is too large. // Cycle is too large.
return toVisit, err return toVisit, err
@@ -685,7 +709,7 @@ func processAnyCliqueOrCycle(
// Cliques should've been processed by now, if they were necessary // Cliques should've been processed by now, if they were necessary
// for processable cycles, so ignore them from here to avoid // for processable cycles, so ignore them from here to avoid
// bloating our search paths. // bloating our search paths.
cycles, err := findAllCyclesWithNode(processedIssuers, issuerIdChildrenMap, issuer, allCliqueNodes) cycles, err := findAllCyclesWithNode(processedIssuers, issuerIdCertMap, issuerIdChildrenMap, issuer, allCliqueNodes)
if err != nil { if err != nil {
// To large of cycle. // To large of cycle.
return nil, err return nil, err
@@ -965,6 +989,7 @@ func canonicalizeCycle(cycle []issuerID) []issuerID {
func findCyclesNearClique( func findCyclesNearClique(
processedIssuers map[issuerID]bool, processedIssuers map[issuerID]bool,
issuerIdCertMap map[issuerID]*x509.Certificate,
issuerIdChildrenMap map[issuerID][]issuerID, issuerIdChildrenMap map[issuerID][]issuerID,
cliqueNodes []issuerID, cliqueNodes []issuerID,
) ([][]issuerID, error) { ) ([][]issuerID, error) {
@@ -993,7 +1018,7 @@ func findCyclesNearClique(
} }
// Find cycles containing this node. // Find cycles containing this node.
newCycles, err := findAllCyclesWithNode(processedIssuers, issuerIdChildrenMap, child, excludeNodes) newCycles, err := findAllCyclesWithNode(processedIssuers, issuerIdCertMap, issuerIdChildrenMap, child, excludeNodes)
if err != nil { if err != nil {
// Found too large of a cycle // Found too large of a cycle
return nil, err return nil, err
@@ -1009,11 +1034,17 @@ func findCyclesNearClique(
excludeNodes = append(excludeNodes, child) excludeNodes = append(excludeNodes, child)
} }
// Sort cycles from longest->shortest.
sort.SliceStable(knownCycles, func(i, j int) bool {
return len(knownCycles[i]) < len(knownCycles[j])
})
return knownCycles, nil return knownCycles, nil
} }
func findAllCyclesWithNode( func findAllCyclesWithNode(
processedIssuers map[issuerID]bool, processedIssuers map[issuerID]bool,
issuerIdCertMap map[issuerID]*x509.Certificate,
issuerIdChildrenMap map[issuerID][]issuerID, issuerIdChildrenMap map[issuerID][]issuerID,
source issuerID, source issuerID,
exclude []issuerID, exclude []issuerID,
@@ -1112,6 +1143,7 @@ func findAllCyclesWithNode(
if _, ok := pathsTo[child]; !ok { if _, ok := pathsTo[child]; !ok {
pathsTo[child] = make([][]issuerID, 0) pathsTo[child] = make([][]issuerID, 0)
} }
for _, path := range pathsTo[current] { for _, path := range pathsTo[current] {
if child != source { if child != source {
// We only care about source->source cycles. If this // We only care about source->source cycles. If this
@@ -1160,7 +1192,7 @@ func findAllCyclesWithNode(
} }
} }
// Visit this child next. // Add this child as a candidate to visit next.
visitQueue = append(visitQueue, child) visitQueue = append(visitQueue, child)
// If there's a new parent or we found a new path, then we should // If there's a new parent or we found a new path, then we should
@@ -1206,6 +1238,11 @@ func findAllCyclesWithNode(
cycles = appendCycleIfNotExisting(cycles, reversed) cycles = appendCycleIfNotExisting(cycles, reversed)
} }
// Sort cycles from longest->shortest.
sort.SliceStable(cycles, func(i, j int) bool {
return len(cycles[i]) > len(cycles[j])
})
return cycles, nil return cycles, nil
} }

View File

@@ -871,16 +871,25 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen
} }
if data.SigningBundle != nil { if data.SigningBundle != nil {
if len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 && if (len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 &&
!bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId) { !bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId)) ||
data.Params.ForceAppendCaChain {
var chain []*CertBlock
result.CAChain = []*CertBlock{ signingChain := data.SigningBundle.CAChain
{ // Some bundles already include the root included in the chain, so don't include it twice.
if len(signingChain) == 0 || !bytes.Equal(signingChain[0].Bytes, data.SigningBundle.CertificateBytes) {
chain = append(chain, &CertBlock{
Certificate: data.SigningBundle.Certificate, Certificate: data.SigningBundle.Certificate,
Bytes: data.SigningBundle.CertificateBytes, Bytes: data.SigningBundle.CertificateBytes,
}, })
} }
result.CAChain = append(result.CAChain, data.SigningBundle.CAChain...)
if len(signingChain) > 0 {
chain = append(chain, signingChain...)
}
result.CAChain = chain
} }
} }
@@ -1158,7 +1167,7 @@ func signCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertBun
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
} }
result.CAChain = data.SigningBundle.GetCAChain() result.CAChain = data.SigningBundle.GetFullChain()
return result, nil return result, nil
} }

View File

@@ -704,13 +704,7 @@ func (b *CAInfoBundle) GetCAChain() []*CertBlock {
(len(b.Certificate.AuthorityKeyId) == 0 && (len(b.Certificate.AuthorityKeyId) == 0 &&
!bytes.Equal(b.Certificate.RawIssuer, b.Certificate.RawSubject)) { !bytes.Equal(b.Certificate.RawIssuer, b.Certificate.RawSubject)) {
chain = append(chain, &CertBlock{ chain = b.GetFullChain()
Certificate: b.Certificate,
Bytes: b.CertificateBytes,
})
if b.CAChain != nil && len(b.CAChain) > 0 {
chain = append(chain, b.CAChain...)
}
} }
return chain return chain
@@ -771,6 +765,7 @@ type CreationParameters struct {
PolicyIdentifiers []string PolicyIdentifiers []string
BasicConstraintsValidForNonCA bool BasicConstraintsValidForNonCA bool
SignatureBits int SignatureBits int
ForceAppendCaChain bool
// Only used when signing a CA cert // Only used when signing a CA cert
UseCSRValues bool UseCSRValues bool