Slide 1

Slide 1 text

Writing Secure APIs Armin Ronacher for PyCon.ru 2014

Slide 2

Slide 2 text

Armin Ronacher Independent Contractor for Splash Damage / Fireteam Doing Online Infrastructure for Computer Games lucumr.pocoo.org/talks

Slide 3

Slide 3 text

… but does it support SAML?

Slide 4

Slide 4 text

≈ Why Secure APIs?

Slide 5

Slide 5 text

Starbucks killed the enencrypted connection

Slide 6

Slide 6 text

your enemy surfs on the same Wifi as you

Slide 7

Slide 7 text

Things to secure: Session Cookies Access Tokens Credit Card Numbers …

Slide 8

Slide 8 text

don't be afraid of government, your enemy is sitting on the same Wifi

Slide 9

Slide 9 text

? Which type of API?

Slide 10

Slide 10 text

Web vs Browser Web

Slide 11

Slide 11 text

It's all about the CAs Browsers trust Public CAs Services should trust Certi cates

Slide 12

Slide 12 text

Browser Web You use HTTP And there is a good old browser Websites, JavaScript APIs, etc.

Slide 13

Slide 13 text

Web You use HTTP There is no browser Or it's a browser under your control Service to Service communication, Custom APIs, etc.

Slide 14

Slide 14 text

If there is no browser I do not need a public CA

Slide 15

Slide 15 text

If there is a browser there is not much you can do :'(

Slide 16

Slide 16 text

? What does a CA do?

Slide 17

Slide 17 text

let's look at something else first

Slide 18

Slide 18 text

0 Understanding Trust

Slide 19

Slide 19 text

Authenticity Secrecy vs

Slide 20

Slide 20 text

Authenticity : the author of the message is the author the receiver knows and trusts.

Slide 21

Slide 21 text

Secrecy : nobody besides author and intended receiver read the message.

Slide 22

Slide 22 text

Authenticity Secrecy >

Slide 23

Slide 23 text

O O A O B E Authenticity: Eve

Slide 24

Slide 24 text

O O A O B E Secrecy

Slide 25

Slide 25 text

X Signatures

Slide 26

Slide 26 text

Signatures add: authentication, integrity and non-repudication

Slide 27

Slide 27 text

! non-repudication is pointless for APIs

Slide 28

Slide 28 text

“Signature without non-repudication’ MAC

Slide 29

Slide 29 text

Consumer gets shared key Signs request with shared key Sends request to server Server verifies request Server creates and signs response Client verifies response 1 2 3 4 5 6

Slide 30

Slide 30 text

the key is never on the wire! (Eve is a sad attacker)

Slide 31

Slide 31 text

O O A O B E what's

Slide 32

Slide 32 text

O O A O B E what's

Slide 33

Slide 33 text

O O A O B E what's

Slide 34

Slide 34 text

O O A O B E what's

Slide 35

Slide 35 text

O O A O B E what's

Slide 36

Slide 36 text

O O A O B E what's

Slide 37

Slide 37 text

zzz O O A O B E what's

Slide 38

Slide 38 text

O O A O B E what's

Slide 39

Slide 39 text

Why most of those things don't matter The core problem is that Eve can wiretap Even without Eve a client can never know if a message was sent! Idempotency needs to be implemented anyways

Slide 40

Slide 40 text

put the nonce in the cache!

Slide 41

Slide 41 text

def handle_request(request): signature = create_signature(request) if signature != request.supplied_signature: abort_with_error() if not nonce_has_been_used(request.nonce): perform_modification() remember_nonce(request.nonce) result = generate_result() return generate_response_with_signature(result)

Slide 42

Slide 42 text

t Signature Expiration

Slide 43

Slide 43 text

Make Signatures Expire or you store years and years of nonces

Slide 44

Slide 44 text

Synchronize your Clocks!

Slide 45

Slide 45 text

def verify_message(data): sig, message = split_signature(data) reference_sig = calculate_signature(message) if reference_sig != sig: raise InvalidSignature() header, payload = split_message(message) expires_at = get_expiration_time(header) if expires_at < current_time(): raise SignatureExpired() return header, payload

Slide 46

Slide 46 text

Message can only be used once only need to remember nonce for signature lifetime

Slide 47

Slide 47 text

itsdangerous pypi.python.org/pypi/itsdangerous

Slide 48

Slide 48 text

import time from itsdangerous import URLSafeSerializer, BadSignature def get_serializer(): return URLSafeSerializer(secret_key=get_secret_key()) def make_auth_ticket(user_id, expires_in=60): return get_serializer().dumps({ 'user_id': user_id, 'expires_at': time.time() + expires_in, }) def verify_auth_ticket(ticket): data = get_serializer().loads(ticket) if data['expires_at'] < time.time(): raise BadSignature('Ticket expired.') return data['user_id']

Slide 49

Slide 49 text

∂ Signing killed oAuth 1.0a

Slide 50

Slide 50 text

People did not want to sign

Slide 51

Slide 51 text

… then only sign on the Server Enter: Token Based Authentication

Slide 52

Slide 52 text

Token Based Authentication requires SSL or another secure transport uses short lived tokens used for exchanging authentication information

Slide 53

Slide 53 text

This is what OAuth 2.0 is

Slide 54

Slide 54 text

Access Refresh Token & short

Slide 55

Slide 55 text

O O A O B E what's

Slide 56

Slide 56 text

Token Based Authentication is a Tradeoff It ‘limits’ what an attacker can do Stolen Access Token: ~24 hours of damage Refresh Tokens are only exchanged on Token Refresh only ever used in combination with SSL!

Slide 57

Slide 57 text

import uuid from itsdangerous import URLSafeSerializer def get_serializer(): return URLSafeSerializer(secret_key=get_secret_key()) def make_token(user): token_data = { 'user_id': user.id, 'generation': user.token_generation, } refresh_token = get_serializer().dumps(token_data) access_token = str(uuid.uuid4()) store_token(access_token, token_data) return access_token, refresh_token

Slide 58

Slide 58 text

CA / SSL without Public CAs

Slide 59

Slide 59 text

Certificate Revocations do work! not

Slide 60

Slide 60 text

Certificate says valid until 2020 Private Key Leaked

Slide 61

Slide 61 text

Certificate says valid until 2020 Private Key Leaked

Slide 62

Slide 62 text

Certificate says valid until 2020 Private Key Leaked

Slide 63

Slide 63 text

what now?

Slide 64

Slide 64 text

treat it like token based authentication

Slide 65

Slide 65 text

self signed certificates are good (just not if you deal with normal users)

Slide 66

Slide 66 text

Become your Own Private CA

Slide 67

Slide 67 text

make certificates expire every 24 hours

Slide 68

Slide 68 text

refresh token » root certi cate access token » connection certi cate They are not the same But you treat them the same

Slide 69

Slide 69 text

Certi cate Travels over Wire Private Key Does Not

Slide 70

Slide 70 text

create root certi cate trust root certi cate always have a cron job issue certi cates signed by that root every 12 hours distribute them to the web servers cycle certs every 12 hours how it works: 1 2 3 4 5 6

Slide 71

Slide 71 text

you can now looks your private key maximum damage is ~1 day your root's private key is on a box not connected to the internet with all ports closed. what makes it good:

Slide 72

Slide 72 text

Survives Heartbleed :-)

Slide 73

Slide 73 text

from requests import get resp = get('https://api.yourserver.com/', verify='your/certificate.bundle') Apple's crappy OpenSSL always trusts Keychain :-(

Slide 74

Slide 74 text

why not with public CAs?

Slide 75

Slide 75 text

why trust the whole world? you need to sign on their shitty web application on most CAs you pay for each signature and they are most of the time valid for a year 1 2 3 4

Slide 76

Slide 76 text

# Structure for Security

Slide 77

Slide 77 text

Request comes in Response goes out (you can explain that)

Slide 78

Slide 78 text

Annotate Views for Security Rules @requires_role('system_management') def manage_system(request): return Response(...) Example

Slide 79

Slide 79 text

Encapsulate Security Systems class UpdateUser(IdempotentEndpoint): id = Parameter(UUID) changes = Parameter(dict) def load_state(self): self.user = fetch_user(self.id) def update_state(self): self.user.update(self.changes) def get_response(self, state): return JSONResponse(self.user.to_json()) Example

Slide 80

Slide 80 text

Add Security Contexts class Context(object): def __init__(self): self.account = None self.role_subset = set() def load_token(self, token, role_subset): self.account = find_account(token['account_id']) self.role_subset = set(role_subset) def has_role(self, x): if self.account is None or x not in self.role_subset: return False return x in self.account['roles'] Example

Slide 81

Slide 81 text

? Feel Free To Ask Questions Talk slides will be online on lucumr.pocoo.org/talks You can find me on Twitter: @mitsuhiko And gittip: gittip.com/mitsuhiko Or hire me: [email protected]