Introduce a singleton module to collect apex certs

`all_apex_certs` will provide two kinds of output files:
1. x509 certificate in pem format
2. x509 certificate in der format

filenames of the certs are not part of `all_apex_certs` api. In fact,
the der certs will be named with int indexes.

This singleton module will be used by `CtsSecurityTestCases`
(specifically `PackageSignatureTest`) to enforce that the apexes are
signed with release keys.

To implement this, `ctx.ReverseDepenendcy` will be used in deps mutator
of apex. An alternative would have been to use `ctx.VisitAllDeps` in
`GenerateAndroidBuildActions` of the singleton, but this would make it
unusable in the cts test (circular dep).

Make has a similar implementation that collects the certs of apexes and
dists that file. This has been kept separate for now to prevent b/304914238

Bug: 329299639
Test: m nothing --no-skip-soong-tests
Change-Id: I742d8ae1ccc344a78ae04263382750508b2aedec
diff --git a/apex/key.go b/apex/key.go
index e4214f0..9fa9d1e 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -18,6 +18,7 @@
 	"fmt"
 
 	"android/soong/android"
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -29,6 +30,7 @@
 
 func registerApexKeyBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
+	ctx.RegisterParallelSingletonModuleType("all_apex_certs", allApexCertsFactory)
 }
 
 type apexKey struct {
@@ -155,3 +157,59 @@
 	android.WriteFileRuleVerbatim(ctx, path, entry.String())
 	return path
 }
+
+var (
+	pemToDer = pctx.AndroidStaticRule("pem_to_der",
+		blueprint.RuleParams{
+			Command:     `openssl x509 -inform PEM -outform DER -in $in -out $out`,
+			Description: "Convert certificate from PEM to DER format",
+		},
+	)
+)
+
+// all_apex_certs is a singleton module that collects the certs of all apexes in the tree.
+// It provides two types of output files
+// 1. .pem: This is usually the checked-in x509 certificate in PEM format
+// 2. .der: This is DER format of the certificate, and is generated from the PEM certificate using `openssl x509`
+func allApexCertsFactory() android.SingletonModule {
+	m := &allApexCerts{}
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+type allApexCerts struct {
+	android.SingletonModuleBase
+}
+
+func (_ *allApexCerts) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var certificatesPem android.Paths
+	ctx.VisitDirectDeps(func(m android.Module) {
+		if apex, ok := m.(*apexBundle); ok {
+			pem, _ := apex.getCertificateAndPrivateKey(ctx)
+			if !android.ExistentPathForSource(ctx, pem.String()).Valid() {
+				if ctx.Config().AllowMissingDependencies() {
+					return
+				} else {
+					ctx.ModuleErrorf("Path %s is not valid\n", pem.String())
+				}
+			}
+			certificatesPem = append(certificatesPem, pem)
+		}
+	})
+	certificatesPem = android.SortedUniquePaths(certificatesPem) // For hermiticity
+	var certificatesDer android.Paths
+	for index, certificatePem := range certificatesPem {
+		certificateDer := android.PathForModuleOut(ctx, fmt.Sprintf("x509.%v.der", index))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   pemToDer,
+			Input:  certificatePem,
+			Output: certificateDer,
+		})
+		certificatesDer = append(certificatesDer, certificateDer)
+	}
+	ctx.SetOutputFiles(certificatesPem, ".pem")
+	ctx.SetOutputFiles(certificatesDer, ".der")
+}
+
+func (_ *allApexCerts) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+}