You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nest.js应用生成CloudFront签名URL遇ERR_OSSL_UNSUPPORTED错误求助

问题:Nest.js中使用AWS SDK生成CloudFront签名URL时出现ERR_OSSL_UNSUPPORTED错误

错误信息

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'
}

相关代码

生成签名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;
    }
}

获取CloudFront私钥的代码

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;
  }
}

Signer对象打印输出

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

补充信息

  • 已尝试执行SET NODE_OPTIONS=--openssl-legacy-provider
  • 本地生成公私钥对的命令:
    openssl genrsa -out private_key.pem 2048
    openssl rsa -pubout -in private_key.pem -out public_key.pem
    
  • 使用环境:node v18.15.0、nest v9.5.0

解决方案

1. 修复私钥格式(还原换行符)

从Secrets Manager获取的私钥会因JSON存储丢失实际换行符,导致OpenSSL无法解析。修改getCloudFrontPrivateKey方法,还原换行符:

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.replace(/\\n/g, '\n');
    } else {
      throw new Error('SecretString is empty');
    }
  } catch (error) {
    console.log('Error fetching CloudFront private key:', error);
    throw error;
  }
}

2. 升级到AWS SDK v3

AWS SDK v1在Node.js 18+环境下存在兼容性问题,建议切换到v3:

安装依赖

npm install @aws-sdk/client-cloudfront-signer @aws-sdk/client-secrets-manager

重写签名代码

import { CloudFrontSigner } from '@aws-sdk/client-cloudfront-signer';
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

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

    try {
        const privateKey = await this.getCloudFrontPrivateKey();
        const signer = new CloudFrontSigner({
            keyPairId,
            privateKey,
        });

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

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

private async getCloudFrontPrivateKey(): Promise<string> {
  try {
    const client = new SecretsManagerClient({
      region: 'us-west-1',
      credentials: {
        accessKeyId: 'MY_ACCESS_KEY',
        secretAccessKey: 'MY_SECRET_ACCESS_KEY',
      },
    });
    const command = new GetSecretValueCommand({ SecretId: 'test/key' });
    const response = await client.send(command);

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

3. 验证私钥有效性

用OpenSSL命令验证本地私钥格式是否正确:

openssl rsa -check -in private_key.pem

若输出显示私钥有效,说明算法符合要求;若报错,重新生成私钥并正确上传到Secrets Manager。

4. 确保环境变量生效(Windows)

临时生效:在启动应用的命令行先执行

set NODE_OPTIONS=--openssl-legacy-provider

再启动Nest.js应用;永久生效则在系统环境变量中添加NODE_OPTIONS,值为--openssl-legacy-provider。


内容的提问来源于stack exchange,提问作者D Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 03:17:06