Issue with generating CloudFront signed URL in Nest.js application

397 Views Asked by At

I'm trying to generate a signed URL for a CloudFront resource in my Nest.js application using the AWS SDK, but I'm encountering an error that I can't seem to resolve. The error message I'm receiving is:

Error: error:1E08010C:DECODER routines::unsupported
    at Sign.sign (node:internal/crypto/sig:131:29)
    at signPolicy (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:21:29)
    at signWithCannedPolicy (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:37:20)
    at Signer.getSignedUrl (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:186:19)
    at S3Service.generateSignedUrl (C:\Full Stack JS Apps\testCloudfront\api\src\common\services\aws\s3\s3.service.ts:57:36)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at AppController.getFile (C:\Full Stack JS Apps\testCloudfront\api\src\app.controller.ts:24:17) {
  library: 'DECODER routines',
  reason: 'unsupported',
  code: 'ERR_OSSL_UNSUPPORTED'
}

Here's the relevant code that I'm using to generate the signed URL:

async generateSignedUrl(objectKey: string): Promise<string> {
    const cloudFrontDomain = 'my-alternate-domain.com';
    const keyPairId = 'MyAWSCFPublicKeyId';

    try {
        const privateKey = await this.getCloudFrontPrivateKey();
        const cloudFront = new CloudFront.Signer(keyPairId, privateKey);

        console.log('Signer:', cloudFront); 

        const signedUrl = cloudFront.getSignedUrl({
            url: `https://${cloudFrontDomain}/${objectKey}`,
            expires: Math.floor(Date.now() / 1000) + 3600, // URL expiration time (1 hour)
        });

        return signedUrl;
    } catch (error) {
        console.log('Error generating signed URL:', error);
        throw error;
    }
}

My private key is stored in AWS Secret Manager. Here's the code I'm using to retrieve the private key:

private async getCloudFrontPrivateKey(): Promise<string> {
  try {
    const secretsManager = new SecretsManager({
      accessKeyId: 'MY_ACCESS_KEY',
      secretAccessKey: 'MY_SECRET_ACCESS_KEY',
      region: 'us-west-1',
    });
    const response = await secretsManager
      .getSecretValue({ SecretId: 'test/key' })
      .promise();

    if (response.SecretString) {
      const secretData = JSON.parse(response.SecretString);
      return secretData.privateKey;
    } else {
      throw new Error('SecretString is empty');
    }
  } catch (error) {
    console.log('Error fetching CloudFront private key:', error);
    throw error;
  }
}

Sample Signer Output when logging: When I log the cloudFront object (Signer) using console.log('Signer:', cloudFront);, the output looks like this:

Signer: Signer {
  keyPairId: 'MyAWSCFPublicKeyId',
  privateKey: '-----BEGIN RSA PRIVATE KEY----- contents here -----END RSA PRIVATE KEY-----'
}

Additional Information:

  1. I already tried this SET NODE_OPTIONS=--openssl-legacy-provider
  2. I generated the public-private key pair locally using the ff command:
openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
  1. I'm using node v18.15.0 and nest v9.5.0
1

There are 1 best solutions below

0
D Dev On

The issue lies with the private key. The line breaks are removed if you store it in AWS Secrets Manager. Therefore, I did base64 encode the private key and decode it in my code. The code will appear as follows:

  const privateKeyString = await this.getCloudFrontPrivateKey();
  const decodedPrivateKey = Buffer.from(
    privateKeyString,
    'base64',
  ).toString('utf-8');

  const cloudFrontSigner = new CloudFront.Signer(
    keyPairId,
    decodedPrivateKey,
  );

  const signedUrl = cloudFrontSigner.getSignedUrl({
    url: `https://${cloudFrontDomain}/${objectKey}`,
    expires: Math.floor(Date.now() / 1000) + 3600,
  });

  return signedUrl;