I'm currently trying to get my elixir web server to generate signed urls for Google Cloud Storage so that I can generate file urls that expire. Unfortunately when I try to use the generated urls I get the following error:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
I am able to generate signed urls that work via the gsutil tool, although it is quite slow, and also via the python example given here:
Google Cloud Storage Signed URLs Example
My current implementation in Elixir is based on the above Python example and looks like this:
@default_expiration 1000
def construct_string(http_verb, content_md5, content_type, expiration_timestamp, canonicalized_extension_headers, canonicalized_resource) do
"#{http_verb}\n
#{content_md5}\n
#{content_type}\n
#{expiration_timestamp}\n
#{canonicalized_extension_headers}
#{canonicalized_resource}"
end
def load_secret_pem do
load_local_key_file("/path/to/key")
end
def load_local_key_file(path) do
{ok, pem_bin} = File.read(path)
[rsa_entry] = :public_key.pem_decode(pem_bin)
key = :public_key.pem_entry_decode(rsa_entry)
end
def base64Sign(plaintext) do
key = load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.url_encode64(signature_bytes)
|> String.replace("-", "%2B")
|> String.replace("_", "%2F")
|> URI.encode_www_form
end
def make_url(verb, path, content_md5 \\ "", content_type \\ "") do
client_id = GCloud.Credentials.client_email() |> URI.encode_www_form
expiration = :os.system_time(:seconds) + @default_expiration
base_url = GCloud.Storage.base_uri() <> path
signature_string = construct_string(verb, content_md5, content_type, expiration, "", path )
url_encoded_signature = base64Sign(signature_string)
IO.puts "#{base_url}?GoogleAccessId=#{client_id}&Expires=#{expiration}&Signature=#{url_encoded_signature}"
end
How are signed urls correctly signed using Elixir or Erlang?