Skip to content

Instantly share code, notes, and snippets.

@tinnus-napbus
Created February 25, 2022 10:32
Show Gist options
  • Select an option

  • Save tinnus-napbus/fd35eb37ff763442525651344cd74b94 to your computer and use it in GitHub Desktop.

Select an option

Save tinnus-napbus/fd35eb37ff763442525651344cd74b94 to your computer and use it in GitHub Desktop.
filesharer notes

I was thinking through how to do the link shortening thing and here's the best solution I think: use AES-128 CBC to encrypt the ship and content ID and make a URL with it, then decode such requests and test whether the given ship has permission for the given resource. If it does, 301 redirect to the actual resource and if it doesn't, 403 forbidden.

This way you can give every subscribed ship its own unique urls for files you've shared, and you can revoke or change them individually. You also don't need to actually store these unique urls because you can just construct them when you send out updates to each ship, cos AES is jetted and very fast.

The approach for subscriptions would be to have a unique subscription path for each subscribed ship like /updates/[ship] or whatever so you can give the correct unique urls to the correct ship

Here's a basic set of data structures the app could use:

|%
+$  id  @
+$  file
  $:  title=@t
      note=(unit @t)
      url=@t
      ext=(unit @t)
  ==
+$  perms
  $:  white=(set ship)
      black=(set ship)
  ==
+$  user
  $:  rev=@
      files=(set id)
  ==
+$  decoded
  $:  =ship
      rev=@
      =id
  ==
+$  secret  [k=@ iv=@]
+$  host  (unit @t)
+$  users  (map ship user)
+$  public  (set id)
+$  local  (map id [=file =perms])
+$  remote  (map ship (map id file))

And state can look something like this:

+$  state-0  [%0 =secret =host =users =public =local =remote]

The content $id can be a random 64-bit atom. The rev field for the $user is also a 64-bit number and is used to salt the encrypted urls, so you can change them for that user down the line if you want to by changing that number.

The $secret structure in state is a 128-bit key and initialisation vector for the AES-128 CBC algo, to construct and decrypt the unique urls.

The unique url is made by concatenating [128-bit ship address][64-bit user revision number][64-bit content ID], encrypting it with ~(en cbca:aes:crypto k.secret iv.secret), and then encoding it in a cord in base32.

Here's how the encoding function might work:

++  encode
  |=  [=host =ship =user =id =file =secret]
  ^-  (unit @t)
  ?~  host  ~
  =/  raw=@  (can 3 ~[8^id 8^rev.user 16^ship])
  =/  hash=tape
    %-  (v-co:co 32)
    (~(en cbca:aes:crypto k.secret iv.secret) raw)
  =/  query=tape
    ?~  ext.file  ""
    "?ext={(trip u.ext.file)}"
  `(crip "http://{(trip u.host)}/share/{hash}{query}")
::
++  sample-encode-args
  :*  host=[~ 'example.com']
      ship=~tinnus-napbus
      user=[0 ~]
      id=9.595.219.608.286.410.566
      file=['foo' ~ 'xxx' ~ 'txt']
      secret=[0w3J.V~dU2.Um-6~.0TghF.MJLmv 0w1n.Yz4ZH.CBhuM.hdsft.LFkM~]
  ==

In the dojo:

> (encode:test sample-encode-args:test)
[~ 'http://example.com/share/18gboma04mas2tg23tl0vvs6viesn598e6ccqo8ib5n6i0ol8bdh?ext=txt']

And here's the decoding that'll happen when it receives a GET request for that url:

++  sample-decode-args
    :*  url='http://example.com/share/18gboma04mas2tg23tl0vvs6viesn598e6ccqo8ib5n6i0ol8bdh?ext=.txt'
        secret=[0w3J.V~dU2.Um-6~.0TghF.MJLmv 0w1n.Yz4ZH.CBhuM.hdsft.LFkM~]
    ==
::
++  decode
  |=  [url=@t =secret]
  ^-  (unit decoded)
  =/  purl=(unit purl:eyre)  (de-purl:html url)
  ?~  purl  ~
  =/  =pork:eyre  q.u.purl
  ?.  ?=([@ @ ~] q.pork)  ~
  =/  hash=@  (rash i.t.q.pork vum:ag)
  =/  raw=@  (~(de cbca:aes:crypto k.secret iv.secret) hash)
  :^    ~
      (cut 3 [16 16] raw)
    (cut 3 [8 8] raw)
  (end 6 raw)
--

in the dojo:

> (decode:test sample-decode-args:test)
[~ [ship=~tinnus-napbus rev=0 id=9.595.219.608.286.410.566]]

Upon receiving the get request and successfully decoding, it can check that the given user has permission to access the file with the given content ID, and if it does, it can return a 301 redirect to the actual url.

@tinnus-napbus
Copy link
Author

Other ships will subscribe to your %filesharer, so you need to be able to figure out which files are shared with them, so you need a map of ships to files for that, otherwise you'd have to iterate over all files to check which should be shared and there'd be some other complications too I think. Conversely, in the front end, you want to be able to share/unshare a file with particular users, so the front-end needs a map from file to users (so you can see file A is shared with ship X, Y and Z). So $users and $local cover these cases respectively. When you whitelisted/blacklisted a user for a particular file you'd need to update both of these so they're in sync.

The $public structure I was thinking was for files that you just shared with everybody, so anyone who subscribed to your %filesharer would receive these. I think you'd still wanna do the encoding thing for them cos you'd want to be able to revoke permissions later, it's really just necessary cos otherwise you'd have to add billions of ships to the whitelist.

Also the $local and $remote cases are for your files vs files others have shared with you.

You can change up this stuff if it doesn't suit btw, I was just sketching out roughly what you'd need off the top of my head, it's not set in stone

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment