3 # In order to get a Blob from Keep, you have to prove either
4 # [a] you have recently written it to Keep yourself, or
5 # [b] apiserver has recently decided that you should be able to read it
7 # To ensure that the requestor of a blob is authorized to read it,
8 # Keep requires clients to timestamp the blob locator with an expiry
9 # time, and to sign the timestamped locator with their API token.
11 # A signed blob locator has the form:
12 # locator_hash +A blob_signature @ timestamp
13 # where the timestamp is a Unix time expressed as a hexadecimal value,
14 # and the blob_signature is the signed locator_hash + API token + timestamp.
16 class InvalidSignatureError < StandardError
19 # Blob.sign_locator: return a signed and timestamped blob locator.
21 # The 'opts' argument should include:
22 # [required] :key - the Arvados server-side blobstore key
23 # [required] :api_token - user's API token
24 # [optional] :ttl - number of seconds before this request expires
26 def self.sign_locator blob_locator, opts
27 # We only use the hash portion for signatures.
28 blob_hash = blob_locator.split('+').first
30 # Generate an expiry timestamp (seconds since epoch, base 16)
31 timestamp = (Time.now.to_i + (opts[:ttl] || 600)).to_s(16)
34 # Generate a signature.
36 generate_signature opts[:key], blob_hash, opts[:api_token], timestamp
38 blob_locator + '+A' + signature + '@' + timestamp
41 # Blob.verify_signature
42 # Safely verify the signature on a blob locator.
43 # Return value: true if the locator has a valid signature, false otherwise
44 # Arguments: signed_blob_locator, opts
46 def self.verify_signature *args
48 self.verify_signature! *args
50 rescue Blob::InvalidSignatureError
55 # Blob.verify_signature!
56 # Verify the signature on a blob locator.
57 # Return value: true if the locator has a valid signature
58 # Arguments: signed_blob_locator, opts
60 # Blob::InvalidSignatureError if the blob locator does not include a
63 def self.verify_signature! signed_blob_locator, opts
64 blob_hash = signed_blob_locator.split('+').first
65 given_signature, timestamp = signed_blob_locator.
71 raise Blob::InvalidSignatureError.new 'No signature provided.'
73 if !timestamp.match /^[\da-f]+$/
74 raise Blob::InvalidSignatureError.new 'Timestamp is not a base16 number.'
76 if timestamp.to_i(16) < Time.now.to_i
77 raise Blob::InvalidSignatureError.new 'Signature expiry time has passed.'
81 generate_signature opts[:key], blob_hash, opts[:api_token], timestamp
83 if my_signature != given_signature
84 raise Blob::InvalidSignatureError.new 'Signature is invalid.'
90 def self.generate_signature key, blob_hash, api_token, timestamp
91 OpenSSL::HMAC.hexdigest('sha1', key,