Requests a public certificate from AWS Certificate Manager, using DNS validation.
The hosted zone ID must refer to a **public** Route53-managed DNS zone that is authoritative
for the suffix of the certificate's Common Name (CN).  For example, if the CN is
`*.example.com`, the hosted zone ID must point to a Route 53 zone authoritative
for `example.com`.

@param {string} requestId the CloudFormation request ID
@param {string} domainName the Common Name (CN) field for the requested certificate
@param {string} hostedZoneId the Route53 Hosted Zone ID
@returns {string} Validated certificate ARN
async function (requestId, domainName, subjectAlternativeNames, hostedZoneId) {
  const crypto = require('crypto');
  const acm = new aws.ACM();
  const route53 = new aws.Route53();
  if (waiter) {
    // Used by the test suite, since waiters aren't mockable yet
    route53.waitFor = acm.waitFor = waiter;
  }

  console.log(`Requesting certificate for ${domainName}`);

  const reqCertResponse = await acm.requestCertificate({
    DomainName: domainName,
    SubjectAlternativeNames: subjectAlternativeNames,
    IdempotencyToken: crypto.createHash('sha256').update(requestId).digest('hex').substr(0, 32),
    ValidationMethod: 'DNS'
  }).promise();

  console.log(`Certificate ARN: ${reqCertResponse.CertificateArn}`);

  console.log('Waiting for ACM to provide DNS records for validation...');

  var describeCertResponse;
  let attempt = 0;
  do {
    // Exponential backoff with jitter based on 100ms base
    await sleep(Math.random() * (Math.pow(attempt, 2) * 100));
    describeCertResponse = await acm.describeCertificate({
      CertificateArn: reqCertResponse.CertificateArn
    }).promise();
  } while (describeCertResponse.Certificate.DomainValidationOptions < 1 ||
    'ResourceRecord' in describeCertResponse.Certificate.DomainValidationOptions[0] === false);

  const record = describeCertResponse.Certificate.DomainValidationOptions[0].ResourceRecord;

  console.log(`Upserting DNS record into zone ${hostedZoneId}: ${record.Name} ${record.Type} ${record.Value}`);

  const changeBatch = await route53.changeResourceRecordSets({
    ChangeBatch: {
      Changes: [{
        Action: 'UPSERT',
        ResourceRecordSet: {
          Name: record.Name,
          Type: record.Type,
          TTL: 60,
          ResourceRecords: [{
            Value: record.Value
          }]
        }
      }]
    },
    HostedZoneId: hostedZoneId
  }).promise();

  console.log('Waiting for DNS records to commit...');
  await route53.waitFor('resourceRecordSetsChanged', {
    // Wait up to 5 minutes
    $waiter: {
      delay: 30,
      maxAttempts: 10
    },
    Id: changeBatch.ChangeInfo.Id
  }).promise();

  console.log('Waiting for validation...');
  await acm.waitFor('certificateValidated', {
    // Wait up to 5 minutes
    $waiter: {
      delay: 30,
      maxAttempts: 10
    },
    CertificateArn: reqCertResponse.CertificateArn
  }).promise();

  return reqCertResponse.CertificateArn;
}