comparison src/ur/openid.ur @ 8:870d99055dd1

Diffie-Hellman started but not fully tested; successfully checked signature from AOL
author Adam Chlipala <adam@chlipala.net>
date Wed, 29 Dec 2010 12:16:32 -0500
parents 976121190b2d
children 426dd5c88df1
comparison
equal deleted inserted replaced
7:976121190b2d 8:870d99055dd1
25 val createInputs = 25 val createInputs =
26 is <- OpenidFfi.createInputs; 26 is <- OpenidFfi.createInputs;
27 OpenidFfi.addInput is "openid.ns" "http://specs.openid.net/auth/2.0"; 27 OpenidFfi.addInput is "openid.ns" "http://specs.openid.net/auth/2.0";
28 return is 28 return is
29 29
30 table associations : { Endpoint : string, Handle : string, Key : string, Expires : time } 30 datatype association_type = HMAC_SHA1 | HMAC_SHA256
31 datatype association_session_type = NoEncryption | DH_SHA1 | DH_SHA256
32
33 table associations : { Endpoint : string, Handle : string, Typ : serialized association_type, Key : string, Expires : time }
31 PRIMARY KEY Endpoint 34 PRIMARY KEY Endpoint
32 35
33 datatype association = Association of {Handle : string, Key : string} | AssError of string 36 datatype association = Association of {Handle : string, Typ : association_type, Key : string}
34 37 | AssError of string
35 fun association url = 38 | AssAlternate of {Atype : association_type, Stype : association_session_type}
36 secret <- oneOrNoRows1 (SELECT associations.Handle, associations.Key 39
40 fun atype_show v =
41 case v of
42 HMAC_SHA1 => "HMAC-SHA1"
43 | HMAC_SHA256 => "HMAC-SHA256"
44
45 val show_atype = mkShow atype_show
46
47 fun stype_show v =
48 case v of
49 NoEncryption => "no-encryption"
50 | DH_SHA1 => "DH-SHA1"
51 | DH_SHA256 => "DH-SHA256"
52
53 val show_stype = mkShow stype_show
54
55 fun atype_read s =
56 case s of
57 "HMAC-SHA1" => Some HMAC_SHA1
58 | "HMAC-SHA256" => Some HMAC_SHA256
59 | _ => None
60
61 val read_atype = mkRead' atype_read "association type"
62
63 fun stype_read s =
64 case s of
65 "no-encryption" => Some NoEncryption
66 | "DH-SHA1" => Some DH_SHA1
67 | "DH-SHA256" => Some DH_SHA256
68 | _ => None
69
70 val read_stype = mkRead' stype_read "association session type"
71
72 fun atype_eq v1 v2 =
73 case (v1, v2) of
74 (HMAC_SHA1, HMAC_SHA1) => True
75 | (HMAC_SHA256, HMAC_SHA256) => True
76 | _ => False
77
78 val eq_atype = mkEq atype_eq
79
80 fun stype_eq v1 v2 =
81 case (v1, v2) of
82 (NoEncryption, NoEncryption) => True
83 | (DH_SHA1, DH_SHA1) => True
84 | (DH_SHA256, DH_SHA256) => True
85 | _ => False
86
87 val eq_stype = mkEq stype_eq
88
89 fun errorResult atype stype os =
90 case OpenidFfi.getOutput os "error" of
91 Some v =>
92 (case (OpenidFfi.getOutput os "error_code", OpenidFfi.getOutput os "assoc_type", OpenidFfi.getOutput os "session_type") of
93 (Some "unsupported-type", at, st) => Some (AssAlternate {Atype = Option.get atype (Option.bind read at),
94 Stype = Option.get stype (Option.bind read st)})
95 | _ => Some (AssError ("OP error during association: " ^ v)))
96 | None => None
97
98 fun associateNoEncryption url atype =
99 is <- createInputs;
100 OpenidFfi.addInput is "openid.mode" "associate";
101 OpenidFfi.addInput is "openid.assoc_type" (show atype);
102 OpenidFfi.addInput is "openid.session_type" (show NoEncryption);
103
104 os <- OpenidFfi.direct url is;
105 case errorResult atype NoEncryption os of
106 Some v => return v
107 | None =>
108 case (OpenidFfi.getOutput os "assoc_handle", OpenidFfi.getOutput os "mac_key", OpenidFfi.getOutput os "expires_in") of
109 (Some handle, Some key, Some expires) =>
110 (case read expires of
111 None => return (AssError "Invalid 'expires_in' field")
112 | Some expires =>
113 tm <- now;
114 dml (INSERT INTO associations (Endpoint, Handle, Typ, Key, Expires)
115 VALUES ({[url]}, {[handle]}, {[serialize atype]}, {[key]}, {[addSeconds tm expires]}));
116 return (Association {Handle = handle, Typ = atype, Key = key}))
117 | (None, _, _) => return (AssError "Missing assoc_handle")
118 | (_, None, _) => return (AssError "Missing mac_key")
119 | _ => return (AssError "Missing expires_in")
120
121 fun associateDh url atype stype =
122 dh <- OpenidFfi.generate;
123
124 is <- createInputs;
125 OpenidFfi.addInput is "openid.mode" "associate";
126 OpenidFfi.addInput is "openid.assoc_type" (show atype);
127 OpenidFfi.addInput is "openid.session_type" (show stype);
128 OpenidFfi.addInput is "openid.dh_modulus" (OpenidFfi.modulus dh);
129 OpenidFfi.addInput is "openid.dh_gen" (OpenidFfi.generator dh);
130 OpenidFfi.addInput is "openid.dh_consumer_public" (OpenidFfi.public dh);
131
132 os <- OpenidFfi.direct url is;
133 case errorResult atype stype os of
134 Some v => return v
135 | None =>
136 case (OpenidFfi.getOutput os "assoc_handle", OpenidFfi.getOutput os "dh_server_public",
137 OpenidFfi.getOutput os "enc_mac_key", OpenidFfi.getOutput os "expires_in") of
138 (Some handle, Some pub, Some mac, Some expires) =>
139 (case read expires of
140 None => return (AssError "Invalid 'expires_in' field")
141 | Some expires =>
142 key <- OpenidFfi.compute dh pub;
143 tm <- now;
144 dml (INSERT INTO associations (Endpoint, Handle, Typ, Key, Expires)
145 VALUES ({[url]}, {[handle]}, {[serialize atype]}, {[key]}, {[addSeconds tm expires]}));
146 return (Association {Handle = handle, Typ = atype, Key = key}))
147 | (None, _, _, _) => return (AssError "Missing assoc_handle")
148 | (_, None, _, _) => return (AssError "Missing dh_server_public")
149 | (_, _, None, _) => return (AssError "Missing enc_mac_key")
150 | _ => return (AssError "Missing expires_in")
151
152 fun oldAssociation url =
153 secret <- oneOrNoRows1 (SELECT associations.Handle, associations.Typ, associations.Key
37 FROM associations 154 FROM associations
38 WHERE associations.Endpoint = {[url]}); 155 WHERE associations.Endpoint = {[url]});
39 case secret of 156 case secret of
157 Some r => return (Some (r -- #Typ ++ {Typ = deserialize r.Typ}))
158 | None => return None
159
160 fun newAssociation url atype stype =
161 case stype of
162 NoEncryption => associateNoEncryption url atype
163 | _ => associateDh url atype stype
164
165 fun association atype stype url =
166 secret <- oldAssociation url;
167 case secret of
40 Some r => return (Association r) 168 Some r => return (Association r)
41 | None => 169 | None =>
42 is <- createInputs; 170 stype <- return (case (stype, String.isPrefix {Full = url, Prefix = "https://"}) of
43 OpenidFfi.addInput is "openid.mode" "associate"; 171 (NoEncryption, False) => DH_SHA256
44 OpenidFfi.addInput is "openid.assoc_type" "HMAC-SHA256"; 172 | _ => stype);
45 OpenidFfi.addInput is "openid.session_type" "no-encryption"; 173 r <- newAssociation url atype stype;
46 174 case r of
47 debug ("Contacting " ^ url); 175 AssAlternate alt =>
48 176 if alt.Atype = atype && alt.Stype = stype then
49 os <- OpenidFfi.direct url is; 177 return (AssError "Suggested new modes match old ones!")
50 case OpenidFfi.getOutput os "error" of 178 else
51 Some v => return (AssError v) 179 newAssociation url alt.Atype alt.Stype
52 | None => 180 | v => return v
53 case (OpenidFfi.getOutput os "assoc_handle", OpenidFfi.getOutput os "mac_key", OpenidFfi.getOutput os "expires_in") of
54 (Some handle, Some key, Some expires) =>
55 (case read expires of
56 None => return (AssError "Invalid 'expires_in' field")
57 | Some expires =>
58 tm <- now;
59 dml (INSERT INTO associations (Endpoint, Handle, Key, Expires)
60 VALUES ({[url]}, {[handle]}, {[key]}, {[addSeconds tm expires]}));
61 return (Association {Handle = handle, Key = key}))
62 | (None, _, _) => return (AssError "Missing assoc_handle")
63 | (_, None, _) => return (AssError "Missing mac_key")
64 | _ => return (AssError "Missing fields in response from OP")
65 181
66 fun eatFragment s = 182 fun eatFragment s =
67 case String.split s #"#" of 183 case String.split s #"#" of
68 Some (_, s') => s' 184 Some (_, s') => s'
69 | _ => s 185 | _ => s
70 186
71 datatype handle_result = HandleOk of {Endpoint : string, Key : string} | HandleError of string 187 datatype handle_result = HandleOk of {Endpoint : string, Typ : association_type, Key : string} | HandleError of string
72 188
73 fun verifyHandle os id = 189 fun verifyHandle os id =
74 ep <- discover (eatFragment id); 190 ep <- discover (eatFragment id);
75 case ep of 191 case ep of
76 None => return (HandleError "Discovery failed on returned endpoint") 192 None => return (HandleError "Discovery failed on returned endpoint")
77 | Some ep => 193 | Some ep =>
78 case OpenidFfi.getOutput os "openid.assoc_handle" of 194 case OpenidFfi.getOutput os "openid.assoc_handle" of
79 None => return (HandleError "Missing association handle in response") 195 None => return (HandleError "Missing association handle in response")
80 | Some handle => 196 | Some handle =>
81 assoc <- association ep; 197 assoc <- oldAssociation ep;
82 case assoc of 198 case assoc of
83 AssError s => return (HandleError s) 199 None => return (HandleError "Couldn't find association handle")
84 | Association assoc => 200 | Some assoc =>
85 if assoc.Handle <> handle then 201 if assoc.Handle <> handle then
86 return (HandleError "Association handles don't match") 202 return (HandleError "Association handles don't match")
87 else 203 else
88 return (HandleOk {Endpoint = ep, Key = assoc.Key}) 204 return (HandleOk {Endpoint = ep, Typ = assoc.Typ, Key = assoc.Key})
89 205
90 table nonces : { Endpoint : string, Nonce : string, Expires : time } 206 table nonces : { Endpoint : string, Nonce : string, Expires : time }
91 PRIMARY KEY (Endpoint, Nonce) 207 PRIMARY KEY (Endpoint, Nonce)
92 208
93 fun timeOfNonce s = 209 fun timeOfNonce s =
121 debug ("Nonce expires: " ^ show exp); 237 debug ("Nonce expires: " ^ show exp);
122 dml (INSERT INTO nonces (Endpoint, Nonce, Expires) 238 dml (INSERT INTO nonces (Endpoint, Nonce, Expires)
123 VALUES ({[ep]}, {[nonce]}, {[exp]})); 239 VALUES ({[ep]}, {[nonce]}, {[exp]}));
124 return None 240 return None
125 241
126 fun verifySig os key = 242 fun verifySig os atype key =
127 case OpenidFfi.getOutput os "openid.signed" of 243 case OpenidFfi.getOutput os "openid.signed" of
128 None => return (Some "Missing openid.signed in OP response") 244 None => return (Some "Missing openid.signed in OP response")
129 | Some signed => 245 | Some signed =>
130 case OpenidFfi.getOutput os "openid.sig" of 246 case OpenidFfi.getOutput os "openid.sig" of
131 None => return (Some "Missing openid.sig in OP response") 247 None => return (Some "Missing openid.sig in OP response")
151 in 267 in
152 case gatherNvps signed "" of 268 case gatherNvps signed "" of
153 None => return (Some "openid.signed mentions missing field") 269 None => return (Some "openid.signed mentions missing field")
154 | Some nvps => 270 | Some nvps =>
155 let 271 let
156 val sign' = OpenidFfi.sha256 key nvps 272 val sign' = case atype of
273 HMAC_SHA256 => OpenidFfi.sha256 key nvps
274 | HMAC_SHA1 => OpenidFfi.sha1 key nvps
157 in 275 in
158 debug ("Fields: " ^ signed); 276 debug ("Fields: " ^ signed);
159 debug ("Nvps: " ^ nvps); 277 debug ("Nvps: " ^ nvps);
160 debug ("Key: " ^ key); 278 debug ("Key: " ^ key);
161 debug ("His: " ^ sign); 279 debug ("His: " ^ sign);
185 None => error <xml>Missing identity in OP response</xml> 303 None => error <xml>Missing identity in OP response</xml>
186 | Some id => 304 | Some id =>
187 errO <- verifyHandle os id; 305 errO <- verifyHandle os id;
188 case errO of 306 case errO of
189 HandleError s => error <xml>{[s]}</xml> 307 HandleError s => error <xml>{[s]}</xml>
190 | HandleOk {Endpoint = ep, Key = key} => 308 | HandleOk {Endpoint = ep, Typ = atype, Key = key} =>
191 errO <- verifyReturnTo os; 309 errO <- verifyReturnTo os;
192 case errO of 310 case errO of
193 Some s => error <xml>{[s]}</xml> 311 Some s => error <xml>{[s]}</xml>
194 | None => 312 | None =>
195 errO <- verifyNonce os ep; 313 errO <- verifyNonce os ep;
196 case errO of 314 case errO of
197 Some s => error <xml>{[s]}</xml> 315 Some s => error <xml>{[s]}</xml>
198 | None => 316 | None =>
199 errO <- verifySig os key; 317 errO <- verifySig os atype key;
200 case errO of 318 case errO of
201 Some s => error <xml>{[s]}</xml> 319 Some s => error <xml>{[s]}</xml>
202 | None => return <xml>Identity: {[id]}</xml>) 320 | None => return <xml>Identity: {[id]}</xml>)
203 | _ => error <xml>Unexpected <tt>openid.mode</tt>: <tt>{[mode]}</tt></xml> 321 | _ => error <xml>Unexpected <tt>openid.mode</tt>: <tt>{[mode]}</tt></xml>
204 322
209 if rt <> show (effectfulUrl returnTo) then 327 if rt <> show (effectfulUrl returnTo) then
210 return (Some "Wrong return_to in OP response") 328 return (Some "Wrong return_to in OP response")
211 else 329 else
212 return None 330 return None
213 331
214 fun authenticate id = 332 fun authenticate atype stype id =
215 dy <- discover id; 333 dy <- discover id;
216 case dy of 334 case dy of
217 None => return "Discovery failed" 335 None => return "Discovery failed"
218 | Some dy => 336 | Some dy =>
219 assoc <- association dy; 337 assoc <- association atype stype dy;
220 case assoc of 338 case assoc of
221 AssError msg => return msg 339 AssError msg => return ("Association failure: " ^ msg)
340 | AssAlternate _ => return "Association failure: server didn't accept its own alternate association modes"
222 | Association assoc => 341 | Association assoc =>
223 redirect (bless (dy ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id=" 342 redirect (bless (dy ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id="
224 ^ id ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.assoc_handle=" 343 ^ id ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.assoc_handle="
225 ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo))) 344 ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo)))
226 345