1package awsauth
2
3import (
4	"context"
5	"crypto/x509"
6	"encoding/base64"
7	"encoding/pem"
8	"fmt"
9	"math/big"
10	"strings"
11
12	"github.com/hashicorp/vault/sdk/framework"
13	"github.com/hashicorp/vault/sdk/logical"
14)
15
16// dsaSignature represents the contents of the signature of a signed
17// content using digital signature algorithm.
18type dsaSignature struct {
19	R, S *big.Int
20}
21
22// This certificate is used to verify the PKCS#7 signature of the instance
23// identity document. As per AWS documentation, this public key is valid for
24// US East (N. Virginia), US West (Oregon), US West (N. California), EU
25// (Ireland), EU (Frankfurt), Asia Pacific (Tokyo), Asia Pacific (Seoul), Asia
26// Pacific (Singapore), Asia Pacific (Sydney), and South America (Sao Paulo).
27//
28// It's also the same certificate, but for some reason listed separately, for
29// GovCloud (US)
30const genericAWSPublicCertificatePkcs7 = `-----BEGIN CERTIFICATE-----
31MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
32FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
33VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
34ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
35IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
36cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
37ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
38VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
39hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
40k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
41hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
42lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
43MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
44MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
45vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
467HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
47-----END CERTIFICATE-----
48`
49
50// This certificate is used to verify the instance identity document using the
51// RSA digest of the same
52const genericAWSPublicCertificateIdentity = `-----BEGIN CERTIFICATE-----
53MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV
54BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw
55FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu
56Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC
57VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV
58BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w
59gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3
60e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD
61jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL
62XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs
6377VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq
64MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh
65dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h
66em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
67BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T
68C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ
697zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0=
70-----END CERTIFICATE-----`
71
72// pathListCertificates creates a path that enables listing of all
73// the AWS public certificates registered with Vault.
74func pathListCertificates(b *backend) *framework.Path {
75	return &framework.Path{
76		Pattern: "config/certificates/?",
77
78		Callbacks: map[logical.Operation]framework.OperationFunc{
79			logical.ListOperation: b.pathCertificatesList,
80		},
81
82		HelpSynopsis:    pathListCertificatesHelpSyn,
83		HelpDescription: pathListCertificatesHelpDesc,
84	}
85}
86
87func pathConfigCertificate(b *backend) *framework.Path {
88	return &framework.Path{
89		Pattern: "config/certificate/" + framework.GenericNameRegex("cert_name"),
90		Fields: map[string]*framework.FieldSchema{
91			"cert_name": {
92				Type:        framework.TypeString,
93				Description: "Name of the certificate.",
94			},
95			"aws_public_cert": {
96				Type:        framework.TypeString,
97				Description: "Base64 encoded AWS Public cert required to verify PKCS7 signature of the EC2 instance metadata.",
98			},
99			"type": {
100				Type:    framework.TypeString,
101				Default: "pkcs7",
102				Description: `
103Takes the value of either "pkcs7" or "identity", indicating the type of
104document which can be verified using the given certificate. The reason is that
105the PKCS#7 document will have a DSA digest and the identity signature will have
106an RSA signature, and accordingly the public certificates to verify those also
107vary. Defaults to "pkcs7".`,
108			},
109		},
110
111		ExistenceCheck: b.pathConfigCertificateExistenceCheck,
112
113		Callbacks: map[logical.Operation]framework.OperationFunc{
114			logical.CreateOperation: b.pathConfigCertificateCreateUpdate,
115			logical.UpdateOperation: b.pathConfigCertificateCreateUpdate,
116			logical.ReadOperation:   b.pathConfigCertificateRead,
117			logical.DeleteOperation: b.pathConfigCertificateDelete,
118		},
119
120		HelpSynopsis:    pathConfigCertificateSyn,
121		HelpDescription: pathConfigCertificateDesc,
122	}
123}
124
125// Establishes dichotomy of request operation between CreateOperation and UpdateOperation.
126// Returning 'true' forces an UpdateOperation, CreateOperation otherwise.
127func (b *backend) pathConfigCertificateExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
128	certName := data.Get("cert_name").(string)
129	if certName == "" {
130		return false, fmt.Errorf("missing cert_name")
131	}
132
133	entry, err := b.lockedAWSPublicCertificateEntry(ctx, req.Storage, certName)
134	if err != nil {
135		return false, err
136	}
137	return entry != nil, nil
138}
139
140// pathCertificatesList is used to list all the AWS public certificates registered with Vault
141func (b *backend) pathCertificatesList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
142	b.configMutex.RLock()
143	defer b.configMutex.RUnlock()
144
145	certs, err := req.Storage.List(ctx, "config/certificate/")
146	if err != nil {
147		return nil, err
148	}
149	return logical.ListResponse(certs), nil
150}
151
152// Decodes the PEM encoded certiticate and parses it into a x509 cert
153func decodePEMAndParseCertificate(certificate string) (*x509.Certificate, error) {
154	// Decode the PEM block and error out if a block is not detected in the first attempt
155	decodedPublicCert, rest := pem.Decode([]byte(certificate))
156	if len(rest) != 0 {
157		return nil, fmt.Errorf("invalid certificate; should be one PEM block only")
158	}
159
160	// Check if the certificate can be parsed
161	publicCert, err := x509.ParseCertificate(decodedPublicCert.Bytes)
162	if err != nil {
163		return nil, err
164	}
165	if publicCert == nil {
166		return nil, fmt.Errorf("invalid certificate; failed to parse certificate")
167	}
168	return publicCert, nil
169}
170
171// awsPublicCertificates returns a slice of all the parsed AWS public
172// certificates, which are used to verify either the SHA256 RSA signature, or
173// the PKCS7 signatures of the instance identity documents. This method will
174// append the certificates registered using `config/certificate/<cert_name>`
175// endpoint, along with the default certificate in the backend.
176func (b *backend) awsPublicCertificates(ctx context.Context, s logical.Storage, isPkcs bool) ([]*x509.Certificate, error) {
177	// Lock at beginning and use internal method so that we are consistent as
178	// we iterate through
179	b.configMutex.RLock()
180	defer b.configMutex.RUnlock()
181
182	var certs []*x509.Certificate
183
184	defaultCert := genericAWSPublicCertificateIdentity
185	if isPkcs {
186		defaultCert = genericAWSPublicCertificatePkcs7
187	}
188
189	// Append the generic certificate provided in the AWS EC2 instance metadata documentation
190	decodedCert, err := decodePEMAndParseCertificate(defaultCert)
191	if err != nil {
192		return nil, err
193	}
194	certs = append(certs, decodedCert)
195
196	// Get the list of all the registered certificates
197	registeredCerts, err := s.List(ctx, "config/certificate/")
198	if err != nil {
199		return nil, err
200	}
201
202	// Iterate through each certificate, parse and append it to a slice
203	for _, cert := range registeredCerts {
204		certEntry, err := b.nonLockedAWSPublicCertificateEntry(ctx, s, cert)
205		if err != nil {
206			return nil, err
207		}
208		if certEntry == nil {
209			return nil, fmt.Errorf("certificate storage has a nil entry under the name: %q", cert)
210		}
211		// Append relevant certificates only
212		if (isPkcs && certEntry.Type == "pkcs7") ||
213			(!isPkcs && certEntry.Type == "identity") {
214			decodedCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert)
215			if err != nil {
216				return nil, err
217			}
218			certs = append(certs, decodedCert)
219		}
220	}
221
222	return certs, nil
223}
224
225// lockedSetAWSPublicCertificateEntry is used to store the AWS public key in
226// the storage. This method acquires lock before creating or updating a storage
227// entry.
228func (b *backend) lockedSetAWSPublicCertificateEntry(ctx context.Context, s logical.Storage, certName string, certEntry *awsPublicCert) error {
229	if certName == "" {
230		return fmt.Errorf("missing certificate name")
231	}
232
233	if certEntry == nil {
234		return fmt.Errorf("nil AWS public key certificate")
235	}
236
237	b.configMutex.Lock()
238	defer b.configMutex.Unlock()
239
240	return b.nonLockedSetAWSPublicCertificateEntry(ctx, s, certName, certEntry)
241}
242
243// nonLockedSetAWSPublicCertificateEntry is used to store the AWS public key in
244// the storage. This method does not acquire lock before reading the storage.
245// If locking is desired, use lockedSetAWSPublicCertificateEntry instead.
246func (b *backend) nonLockedSetAWSPublicCertificateEntry(ctx context.Context, s logical.Storage, certName string, certEntry *awsPublicCert) error {
247	if certName == "" {
248		return fmt.Errorf("missing certificate name")
249	}
250
251	if certEntry == nil {
252		return fmt.Errorf("nil AWS public key certificate")
253	}
254
255	entry, err := logical.StorageEntryJSON("config/certificate/"+certName, certEntry)
256	if err != nil {
257		return err
258	}
259	if entry == nil {
260		return fmt.Errorf("failed to create storage entry for AWS public key certificate")
261	}
262
263	return s.Put(ctx, entry)
264}
265
266// lockedAWSPublicCertificateEntry is used to get the configured AWS Public Key
267// that is used to verify the PKCS#7 signature of the instance identity
268// document.
269func (b *backend) lockedAWSPublicCertificateEntry(ctx context.Context, s logical.Storage, certName string) (*awsPublicCert, error) {
270	b.configMutex.RLock()
271	defer b.configMutex.RUnlock()
272
273	return b.nonLockedAWSPublicCertificateEntry(ctx, s, certName)
274}
275
276// nonLockedAWSPublicCertificateEntry reads the certificate information from
277// the storage. This method does not acquire lock before reading the storage.
278// If locking is desired, use lockedAWSPublicCertificateEntry instead.
279func (b *backend) nonLockedAWSPublicCertificateEntry(ctx context.Context, s logical.Storage, certName string) (*awsPublicCert, error) {
280	entry, err := s.Get(ctx, "config/certificate/"+certName)
281	if err != nil {
282		return nil, err
283	}
284	if entry == nil {
285		return nil, nil
286	}
287	var certEntry awsPublicCert
288	if err := entry.DecodeJSON(&certEntry); err != nil {
289		return nil, err
290	}
291
292	// Handle upgrade for certificate type
293	persistNeeded := false
294	if certEntry.Type == "" {
295		certEntry.Type = "pkcs7"
296		persistNeeded = true
297	}
298
299	if persistNeeded {
300		if err := b.nonLockedSetAWSPublicCertificateEntry(ctx, s, certName, &certEntry); err != nil {
301			return nil, err
302		}
303	}
304
305	return &certEntry, nil
306}
307
308// pathConfigCertificateDelete is used to delete the previously configured AWS
309// Public Key that is used to verify the PKCS#7 signature of the instance
310// identity document.
311func (b *backend) pathConfigCertificateDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
312	b.configMutex.Lock()
313	defer b.configMutex.Unlock()
314
315	certName := data.Get("cert_name").(string)
316	if certName == "" {
317		return logical.ErrorResponse("missing cert_name"), nil
318	}
319
320	return nil, req.Storage.Delete(ctx, "config/certificate/"+certName)
321}
322
323// pathConfigCertificateRead is used to view the configured AWS Public Key that
324// is used to verify the PKCS#7 signature of the instance identity document.
325func (b *backend) pathConfigCertificateRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
326	certName := data.Get("cert_name").(string)
327	if certName == "" {
328		return logical.ErrorResponse("missing cert_name"), nil
329	}
330
331	certificateEntry, err := b.lockedAWSPublicCertificateEntry(ctx, req.Storage, certName)
332	if err != nil {
333		return nil, err
334	}
335	if certificateEntry == nil {
336		return nil, nil
337	}
338
339	return &logical.Response{
340		Data: map[string]interface{}{
341			"aws_public_cert": certificateEntry.AWSPublicCert,
342			"type":            certificateEntry.Type,
343		},
344	}, nil
345}
346
347// pathConfigCertificateCreateUpdate is used to register an AWS Public Key that
348// is used to verify the PKCS#7 signature of the instance identity document.
349func (b *backend) pathConfigCertificateCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
350	certName := data.Get("cert_name").(string)
351	if certName == "" {
352		return logical.ErrorResponse("missing certificate name"), nil
353	}
354
355	b.configMutex.Lock()
356	defer b.configMutex.Unlock()
357
358	// Check if there is already a certificate entry registered
359	certEntry, err := b.nonLockedAWSPublicCertificateEntry(ctx, req.Storage, certName)
360	if err != nil {
361		return nil, err
362	}
363	if certEntry == nil {
364		certEntry = &awsPublicCert{}
365	}
366
367	// Check if type information is provided
368	certTypeRaw, ok := data.GetOk("type")
369	if ok {
370		certEntry.Type = strings.ToLower(certTypeRaw.(string))
371	} else if req.Operation == logical.CreateOperation {
372		certEntry.Type = data.Get("type").(string)
373	}
374
375	switch certEntry.Type {
376	case "pkcs7":
377	case "identity":
378	default:
379		return logical.ErrorResponse(fmt.Sprintf("invalid certificate type %q", certEntry.Type)), nil
380	}
381
382	// Check if the value is provided by the client
383	certStrData, ok := data.GetOk("aws_public_cert")
384	if ok {
385		if certBytes, err := base64.StdEncoding.DecodeString(certStrData.(string)); err == nil {
386			certEntry.AWSPublicCert = string(certBytes)
387		} else {
388			certEntry.AWSPublicCert = certStrData.(string)
389		}
390	} else {
391		// aws_public_cert should be supplied for both create and update operations.
392		// If it is not provided, throw an error.
393		return logical.ErrorResponse("missing aws_public_cert"), nil
394	}
395
396	// If explicitly set to empty string, error out
397	if certEntry.AWSPublicCert == "" {
398		return logical.ErrorResponse("invalid aws_public_cert"), nil
399	}
400
401	// Verify the certificate by decoding it and parsing it
402	publicCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert)
403	if err != nil {
404		return nil, err
405	}
406	if publicCert == nil {
407		return logical.ErrorResponse("invalid certificate; failed to decode and parse certificate"), nil
408	}
409
410	// If none of the checks fail, save the provided certificate
411	if err := b.nonLockedSetAWSPublicCertificateEntry(ctx, req.Storage, certName, certEntry); err != nil {
412		return nil, err
413	}
414
415	return nil, nil
416}
417
418// Struct awsPublicCert holds the AWS Public Key that is used to verify the PKCS#7 signature
419// of the instance identity document.
420type awsPublicCert struct {
421	AWSPublicCert string `json:"aws_public_cert"`
422	Type          string `json:"type"`
423}
424
425const pathConfigCertificateSyn = `
426Adds the AWS Public Key that is used to verify the PKCS#7 signature of the identity document.
427`
428
429const pathConfigCertificateDesc = `
430AWS Public Key which is used to verify the PKCS#7 signature of the identity document,
431varies by region. The public key(s) can be found in AWS EC2 instance metadata documentation.
432The default key that is used to verify the signature is the one that is applicable for
433following regions: US East (N. Virginia), US West (Oregon), US West (N. California),
434EU (Ireland), EU (Frankfurt), Asia Pacific (Tokyo), Asia Pacific (Seoul), Asia Pacific (Singapore),
435Asia Pacific (Sydney), and South America (Sao Paulo).
436
437If the instances belongs to region other than the above, the public key(s) for the
438corresponding regions should be registered using this endpoint. PKCS#7 is verified
439using a collection of certificates containing the default certificate and all the
440certificates that are registered using this endpoint.
441`
442const pathListCertificatesHelpSyn = `
443Lists all the AWS public certificates that are registered with the backend.
444`
445const pathListCertificatesHelpDesc = `
446Certificates will be listed by their respective names that were used during registration.
447`
448