1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package main
17
18 import (
19 "context"
20 "crypto"
21 "crypto/rand"
22 "crypto/x509"
23 "crypto/x509/pkix"
24 "encoding/asn1"
25 "errors"
26 "flag"
27 "fmt"
28 "log"
29 "os"
30 "path/filepath"
31 "time"
32
33 privateca "cloud.google.com/go/security/privateca/apiv1"
34 "cloud.google.com/go/security/privateca/apiv1/privatecapb"
35 "github.com/google/tink/go/keyset"
36 "github.com/sigstore/sigstore/pkg/cryptoutils"
37 "github.com/sigstore/timestamp-authority/pkg/signer"
38 tsx509 "github.com/sigstore/timestamp-authority/pkg/x509"
39 "google.golang.org/protobuf/types/known/durationpb"
40
41
42 "github.com/sigstore/sigstore/pkg/signature/kms"
43 _ "github.com/sigstore/sigstore/pkg/signature/kms/aws"
44 _ "github.com/sigstore/sigstore/pkg/signature/kms/azure"
45 _ "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
46 _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault"
47 )
48
49
69
70 var (
71
72 gcpCaParent = flag.String("gcp-ca-parent", "", "Resource path to GCP CA Service CA")
73
74 intermediateKMSKey = flag.String("intermediate-kms-resource", "", "Resource path to the asymmetric signing KMS key for the intermediate CA, starting with gcpkms://, awskms://, azurekms:// or hashivault://")
75
76 leafKMSKey = flag.String("leaf-kms-resource", "", "Resource path to the asymmetric signing KMS key for the leaf, starting with gcpkms://, awskms://, azurekms:// or hashivault://")
77 tinkKeysetPath = flag.String("tink-keyset-path", "", "Path to Tink keyset")
78 tinkKmsKey = flag.String("tink-kms-resource", "", "Resource path to symmetric encryption KMS key to decrypt Tink keyset, starting with gcp-kms:// or aws-kms://")
79 outputPath = flag.String("output", "", "Path to the output file")
80 )
81
82 func fetchCertificateChain(ctx context.Context, parent, intermediateKMSKey, leafKMSKey, tinkKeysetPath, tinkKmsKey string,
83 client *privateca.CertificateAuthorityClient) ([]*x509.Certificate, error) {
84 intermediateKMSSigner, err := kms.Get(ctx, intermediateKMSKey, crypto.SHA256)
85 if err != nil {
86 return nil, err
87 }
88 intermediateSigner, _, err := intermediateKMSSigner.CryptoSigner(ctx, func(err error) {})
89 if err != nil {
90 return nil, err
91 }
92
93 pemPubKey, err := cryptoutils.MarshalPublicKeyToPEM(intermediateSigner.Public())
94 if err != nil {
95 return nil, err
96 }
97
98
99 timestampExt, err := asn1.Marshal([]asn1.ObjectIdentifier{tsx509.EKUTimestampingOID})
100 if err != nil {
101 return nil, err
102 }
103 additionalExtensions := []*privatecapb.X509Extension{{
104 ObjectId: &privatecapb.ObjectId{ObjectIdPath: []int32{2, 5, 29, 37}},
105 Critical: true,
106 Value: timestampExt,
107 }}
108
109 isCa := true
110
111 var maxIssuerPathLength int32
112
113 csr := &privatecapb.CreateCertificateRequest{
114 Parent: parent,
115 Certificate: &privatecapb.Certificate{
116
117
118
119 Lifetime: durationpb.New(time.Hour * 24 * 365 * 20),
120 CertificateConfig: &privatecapb.Certificate_Config{
121 Config: &privatecapb.CertificateConfig{
122 PublicKey: &privatecapb.PublicKey{
123 Format: privatecapb.PublicKey_PEM,
124 Key: pemPubKey,
125 },
126 X509Config: &privatecapb.X509Parameters{
127 KeyUsage: &privatecapb.KeyUsage{
128 BaseKeyUsage: &privatecapb.KeyUsage_KeyUsageOptions{
129 CertSign: true,
130 CrlSign: true,
131 },
132 },
133 CaOptions: &privatecapb.X509Parameters_CaOptions{
134 IsCa: &isCa,
135 MaxIssuerPathLength: &maxIssuerPathLength,
136 },
137 AdditionalExtensions: additionalExtensions,
138 },
139 SubjectConfig: &privatecapb.CertificateConfig_SubjectConfig{
140 Subject: &privatecapb.Subject{
141 CommonName: "sigstore-tsa-intermediate",
142 Organization: "sigstore.dev",
143 },
144 },
145 },
146 },
147 },
148 }
149
150 resp, err := client.CreateCertificate(ctx, csr)
151 if err != nil {
152 return nil, err
153 }
154
155 var pemCerts []string
156 pemCerts = append(pemCerts, resp.PemCertificate)
157 pemCerts = append(pemCerts, resp.PemCertificateChain...)
158
159 var parsedCerts []*x509.Certificate
160 for _, c := range pemCerts {
161 certs, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(c))
162 if err != nil {
163 return nil, err
164 }
165 if len(certs) != 1 {
166 return nil, errors.New("unexpected number of certificates returned")
167 }
168 parsedCerts = append(parsedCerts, certs[0])
169 }
170 intermediate := parsedCerts[0]
171
172
173 var leafKMSSigner crypto.Signer
174 if len(leafKMSKey) > 0 {
175 kmsSigner, err := kms.Get(ctx, leafKMSKey, crypto.SHA256)
176 if err != nil {
177 return nil, err
178 }
179 leafKMSSigner, _, err = kmsSigner.CryptoSigner(ctx, func(err error) {})
180 if err != nil {
181 return nil, err
182 }
183 } else {
184 primaryKey, err := signer.GetPrimaryKey(ctx, tinkKmsKey, "")
185 if err != nil {
186 return nil, err
187 }
188 f, err := os.Open(filepath.Clean(tinkKeysetPath))
189 if err != nil {
190 return nil, err
191 }
192 defer f.Close()
193
194 kh, err := keyset.Read(keyset.NewJSONReader(f), primaryKey)
195 if err != nil {
196 return nil, err
197 }
198 leafKMSSigner, err = signer.KeyHandleToSigner(kh)
199 if err != nil {
200 return nil, err
201 }
202 }
203
204 leafPubKey := leafKMSSigner.Public()
205
206 sn, err := cryptoutils.GenerateSerialNumber()
207 if err != nil {
208 return nil, fmt.Errorf("generating serial number: %w", err)
209 }
210
211 skid, err := cryptoutils.SKID(leafPubKey)
212 if err != nil {
213 return nil, err
214 }
215
216 cert := &x509.Certificate{
217 SerialNumber: sn,
218 Subject: pkix.Name{
219 CommonName: "sigstore-tsa",
220 Organization: []string{"sigstore.dev"},
221 },
222 SubjectKeyId: skid,
223 NotBefore: intermediate.NotBefore,
224 NotAfter: intermediate.NotAfter,
225 IsCA: false,
226 KeyUsage: x509.KeyUsageDigitalSignature,
227
228 ExtraExtensions: []pkix.Extension{
229 {
230 Id: asn1.ObjectIdentifier{2, 5, 29, 37},
231 Critical: true,
232 Value: timestampExt,
233 },
234 },
235 }
236 certDER, err := x509.CreateCertificate(rand.Reader, cert, intermediate, leafPubKey, intermediateSigner)
237 if err != nil {
238 return nil, fmt.Errorf("creating tsa certificate: %w", err)
239 }
240 leafCert, err := x509.ParseCertificate(certDER)
241 if err != nil {
242 return nil, fmt.Errorf("parsing leaf certificate: %w", err)
243 }
244 parsedCerts = append([]*x509.Certificate{leafCert}, parsedCerts...)
245
246 return parsedCerts, nil
247 }
248
249 func main() {
250 flag.Parse()
251
252 if *gcpCaParent == "" {
253 log.Fatal("gcp-ca-parent must be set")
254 }
255 if *intermediateKMSKey == "" {
256 log.Fatal("intermediate-kms-resource must be set")
257 }
258 if *leafKMSKey == "" && *tinkKeysetPath == "" {
259 log.Fatal("either leaf-kms-resource or tink-keyset-path must be set")
260 }
261 if *tinkKeysetPath != "" && *tinkKmsKey == "" {
262 log.Fatal("tink-keyset-path must be set with tink-kms-resource must be set")
263 }
264 if *outputPath == "" {
265 log.Fatal("output must be set")
266 }
267
268 client, err := privateca.NewCertificateAuthorityClient(context.Background())
269 if err != nil {
270 log.Fatal(err)
271 }
272 parsedCerts, err := fetchCertificateChain(context.Background(), *gcpCaParent, *intermediateKMSKey, *leafKMSKey, *tinkKeysetPath, *tinkKmsKey, client)
273 if err != nil {
274 log.Fatal(err)
275 }
276 pemCerts, err := cryptoutils.MarshalCertificatesToPEM(parsedCerts)
277 if err != nil {
278 log.Fatal(err)
279 }
280
281 err = os.WriteFile(*outputPath, pemCerts, 0600)
282 if err != nil {
283 log.Fatal(err)
284 }
285 }
286
View as plain text