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