public static X509Certificate2Collection Build()

in Notation.Plugin.AzureKeyVault/Certificate/CertificateChain.cs [22:115]


        public static X509Certificate2Collection Build(X509Certificate2Collection certs)
        {
            /*
            Valid case:
            1. self-signed leaf cert
            2. cert chain: CA1 -> CA2 -> leaf

            Invalid cases:
            1. empty
            2. non-self-signed leaf cert
            3. missing intermediate cert: CA1 -> (x) -> leaf 
            4. missing root cert: (x) -> CA2 -> leaf
            5. unnecessary intermediate cert: CA1 -> CA2 -> leaf
                                              (x) -> CA3
            6. unnecessary root: CA1 -> CA2 -> leaf
                                 CA3
            6. unnecessary cert: CA1 -> CA2 -> leaf
                                 (x) -> CA3
            7. duplicated certs: CA1 -> CA2 -> leaf
            8. multiple leaf certs: CA1 -> CA2 -> leaf1
                                            └───> leaf2
            9. multiple chain: CA1 -> CA2 -> leaf1
                              CA3 -> leaf2
            10. DN cycle: CA1 -> CA2 -> leaf1
                           ^      |
                           └──────┘
            */
            if (certs.Count == 0)
            {
                throw new PluginException("The certificates that need to be built into a chain are empty.");
            }

            if (certs.Count == 1)
            {
                if (certs[0].SubjectName.Name != certs[0].IssuerName.Name)
                {
                    throw new PluginException("Only obtained one certificate but it is not a self-signed certificate. Please complete the certificate bundle by `ca_certs` through plugin config.");
                }
                return certs;
            }

            // subject distinguished name -> certificate map
            var certMap = new Dictionary<string, X509Certificate2>();
            var caSet = new HashSet<string>();
            foreach (var cert in certs)
            {
                if (certMap.ContainsKey(cert.SubjectName.Name))
                {
                    throw new PluginException($"Found duplicated certificates: {cert.SubjectName.Name}");
                }
                certMap[cert.SubjectName.Name] = cert;
                caSet.Add(cert.IssuerName.Name);
            }

            // count the leaf certificate
            if (certs.Count(x => !caSet.Contains(x.SubjectName.Name)) != 1)
            {
                // AKV certificates always contain the leaf certificate
                throw new PluginException("Found multiple leaf certificates or unreferenced intermediate CAs");
            }
            var leafCert = certs.First(x => !caSet.Contains(x.SubjectName.Name));

            // build the certificate chain
            var chain = new X509Certificate2Collection();
            var currentCert = leafCert;
            while (true)
            {
                chain.Add(currentCert);

                if (isRootCA(currentCert))
                {
                    break;
                }

                var subjectDN = currentCert.SubjectName.Name;
                var issuerDN = currentCert.IssuerName.Name;
                if (!caSet.Remove(issuerDN))
                {
                    throw new PluginException($"Found multiple certificates issued by {issuerDN}");
                }

                // check if the issuer is found in the certificate bundle
                if (!certMap.TryGetValue(issuerDN, out currentCert))
                {
                    throw new PluginException($"Certificate chain is not complete. The issuer of {subjectDN} is not found.");
                }
            }

            if (chain.Count != certs.Count)
            {
                throw new PluginException($"Obtained {certs.Count} certificates but the certificate chain only needs {chain.Count} certficates.");
            }
            return chain;
        }