Troubleshooting AWS CloudFront Signed URL Generation for Accessing S3 Images

I’m trying to generate a signed URL using AWS CloudFront to access images stored on AWS S3.

Here’s the code snippet to retrieve the private key.

defp load_private_key() do
    System.get_env("AWS_PRIVATE_KEY")
    |> Base.decode64!()
    |> :public_key.pem_decode()
    |> hd()
    |> :public_key.pem_entry_decode()
  end

And here’s the code snippet to generate the signed URL.

defp generate_cloudfront_signed_url(s3_object_key) do
    private_key = load_private_key()
    key_pair_id = System.get_env("AWS_KEY_PAIR_ID")

    # Generate CloudFront signed URL
    generate_cloudfront_signed_url(s3_object_key, private_key, key_pair_id)
  end

  defp generate_cloudfront_signed_url(s3_object_key, private_key, key_pair_id) do
    url = get_object_url(s3_object_key)
    expires_when = calculate_expiration_in_unix_time()
    policy = create_custom_policy(url, expires_when)

    signature = :public_key.sign(policy, :sha, private_key)
    # signature = :crypto.sign(:rsa, :sha, policy, private_key)
    encoded_signature = Base.url_encode64(signature)

    "#{url}?Expires=#{expires_when}&Signature=#{encoded_signature}&Key-Pair-Id=#{key_pair_id}"
  end

However, when accessing the generated URL, I always encounter an error.

<Error>
<Code>MalformedSignature</Code>
<Message>Could not unencode Signature</Message>
</Error>

If anyone has experience working with CloudFront and S3, please help me out. Thank you very much.

Hi! In my previous project, I was using Elixir Arc library for that:

Here is my blog post how to sign and extend default signature valid duration:

2 Likes

Thank you for your response. After referring to the code in the file above, I discovered the error was in the line

signature = :public_key.sign(policy, :sha, private_key)
encoded_signature = Base.url_encode64(signature)

Base.url_encode64 didn’t encode signature correctly as CloudFront requires. After changing it to

encoded_signature =
      policy
      |> :public_key.sign(:sha, private_key)
      |> Base.encode64()
      |> String.to_charlist()
      |> Enum.map(&replace/1)
      |> to_string()
#---------
@compile {:inline, replace: 1}
  defp replace(?+), do: ?-
  defp replace(?=), do: ?_
  defp replace(?/), do: ?~
  defp replace(c), do: c

I was able to create the signed URL normally.

Thank you.

1 Like