1 module itsdangerous.jws; 2 3 import std.stdio; 4 import std.conv; 5 import std.typecons; 6 import std.format; 7 import std.algorithm.searching; 8 import std.array; 9 import std.digest.sha; 10 import std.json; 11 import std.datetime; 12 import std.datetime.systime; 13 14 import itsdangerous.serializer; 15 import itsdangerous.encoding; 16 import itsdangerous.dsigner; 17 import itsdangerous.exc; 18 19 class JSONWebSignatureSerializer(DigestMethod, SignerType) : Serializer!SignerType { 20 /+ for DigestMethod use SHA256, SHA384, SHA512 21 SHA512 should be considered as default 22 +/ 23 24 this(string secretKey, string salt = null){ 25 super(secretKey, salt); 26 } 27 28 Tuple!(JSONValue, JSONValue) loadPayloadWithHeader(string payload){ 29 30 if(!canFind(payload, '.')) 31 throw new BadPayload("No \".\" found in value"); 32 33 auto arr = payload.split("."); 34 35 string base64dHeader = arr[0]; 36 string base64dPayload = arr[1]; 37 38 string jsonHeader, jsonPayload; 39 try{ 40 jsonHeader = base64Decode(base64dHeader); 41 } catch (BadData e){ 42 throw new BadHeader(format("Could not base64 decode the header because of an exception: %s", e.msg)); 43 } 44 45 try{ 46 jsonPayload = base64Decode(base64dPayload); 47 } catch (BadData e){ 48 throw new BadPayload(format("Could not base64 decode the payload because of an exception: %s", e.msg)); 49 } 50 51 JSONValue headerStruct; 52 try{ 53 headerStruct = this.Serializer.loadPayload(jsonHeader); 54 } catch (BadData e){ 55 throw new BadHeader(format("Could not unserialize header because it was malformed: %s", e.msg)); 56 } 57 58 JSONValue payloadStruct = this.Serializer.loadPayload(jsonPayload); 59 return tuple(headerStruct, payloadStruct); 60 } 61 62 override JSONValue loadPayload(string payload){ 63 64 if(!canFind(payload, '.')) 65 throw new BadPayload("No \".\" found in value"); 66 67 auto arr = payload.split("."); 68 69 string base64dHeader = arr[0]; 70 string base64dPayload = arr[1]; 71 72 string jsonHeader, jsonPayload; 73 try{ 74 jsonHeader = base64Decode(base64dHeader); 75 } catch (BadData e){ 76 throw new BadHeader(format("Could not base64 decode the header because of an exception: %s", e.msg)); 77 } 78 79 try{ 80 jsonPayload = base64Decode(base64dPayload); 81 } catch (BadData e){ 82 throw new BadPayload(format("Could not base64 decode the payload because of an exception: %s", e.msg)); 83 } 84 85 JSONValue headerStruct; 86 try{ 87 headerStruct = this.Serializer.loadPayload(jsonHeader); 88 } catch (BadData e){ 89 throw new BadHeader(format("Could not unserialize header because it was malformed: %s", e.msg)); 90 } 91 92 JSONValue payloadStruct = this.Serializer.loadPayload(jsonPayload); 93 return payloadStruct; 94 } 95 96 string dumpPayload(JSONValue header, JSONValue obj){ 97 string base64dHeader = base64Encode( 98 toJSON(header) 99 ); 100 string base64dPayload = base64Encode( 101 toJSON(obj) 102 ); 103 return base64dHeader ~ '.' ~ base64dPayload; 104 } 105 106 string digestName(){ 107 string digname; 108 static if (is(DigestMethod == SHA!(1024u, 512u))){ 109 digname = "HS512"; 110 } 111 else static if (is(DigestMethod == SHA!(1024u, 384u))){ 112 digname = "HS384"; 113 } 114 else static if (is(DigestMethod == SHA!(512u, 256u))){ 115 digname = "HS256"; 116 } 117 else { 118 static assert( false, "unsupported digest type!"); 119 } 120 return digname; 121 } 122 123 auto makeSignerForTJWT(string salt = null){ 124 string fsalt; 125 if (salt is null) 126 fsalt = this.salt; 127 else 128 fsalt = salt; 129 130 auto key_derivation = (fsalt is null)?"none":"django-concat"; 131 132 auto sigAlg = new HMACAlgorithm!SHA512(); 133 134 auto _signer = new Signer!(SHA1, SHA512)( 135 this.Serializer.secretKey, 136 fsalt, 137 '.', 138 "none" 139 ); 140 141 _signer.setAlgorithm(sigAlg); 142 return _signer; 143 } 144 145 JSONValue makeHeader(JSONValue header){ 146 header["alg"] = digestName(); 147 return header; 148 } 149 150 final string dumps(JSONValue obj, string salt = null, JSONValue headerFields = null){ 151 /+Like :meth:`.Serializer.dumps` but creates a JSON Web 152 Signature. It also allows for specifying additional fields to be 153 included in the JWS header. 154 +/ 155 JSONValue header = makeHeader(headerFields); 156 auto signer = makeSignerForTJWT(salt); 157 //signer.writeln; 158 //signer.getAlgorithm.writeln; 159 auto tmp = dumpPayload(header, obj); 160 return signer.sign(tmp); 161 } 162 163 override JSONValue loads(string s, string salt = null, int maxAge = 0, int *tstamp = null){ 164 /+Reverse of :meth:`dumps`. If requested via ``return_header`` 165 it will return a tuple of payload and header. 166 +/ 167 auto signer = makeSignerForTJWT(salt); 168 auto arr = loadPayloadWithHeader( 169 signer.unsign(s) 170 ); 171 auto header = arr[0]; 172 auto payload = arr[1]; 173 174 if (header["alg"].str != digestName()) 175 throw new BadHeader(format("Algorithm mismatch %s != %s", header["alg"], digestName())); 176 177 return payload; 178 } 179 180 Tuple!(JSONValue, JSONValue) loadsWithHeader(string s, string salt = null){ 181 /+Reverse of :meth:`dumps`. If requested via ``return_header`` 182 it will return a tuple of payload and header. 183 +/ 184 auto signer = makeSignerForTJWT(salt); 185 auto arr = loadPayloadWithHeader(signer.unsign(s)); 186 auto header = arr[0]; 187 auto payload = arr[1]; 188 189 if (header["alg"].str != digestName()) 190 throw new BadHeader(format("Algorithm mismatch %s != %s", header["alg"], digestName())); 191 192 return tuple(payload, header); 193 } 194 } 195 196 enum DEFAULT_EXPIRES_IN = 3600; 197 198 class TimedJSONWebSignatureSerializer(DigestMethod, SignerType) : 199 JSONWebSignatureSerializer!(DigestMethod, SignerType) { 200 201 this(string secretKey, int expiresIn = DEFAULT_EXPIRES_IN, string salt = "itsdangerous.Signer"){ 202 super(secretKey, salt); 203 this.expiresIn = expiresIn; 204 } 205 206 int expiresIn; 207 208 int now(){ 209 import std.datetime.systime; 210 return cast(int)Clock.currTime.toUnixTime(); 211 } 212 213 override JSONValue makeHeader(JSONValue header){ 214 header["alg"] = digestName(); 215 const iat = now(); 216 const exp = iat + expiresIn; 217 218 import std.conv; 219 header["iat"] = iat; 220 header["exp"] = exp; 221 222 return header; 223 } 224 225 override JSONValue loads(string s, string salt = null, int maxAge = 0, int *tstamp = null){ 226 auto tup = this.JSONWebSignatureSerializer.loadsWithHeader(s, salt); 227 JSONValue payload = tup[0]; 228 JSONValue header = tup[1]; 229 230 try{ 231 const exp = ("exp" in header); 232 }catch(JSONException ex){ 233 auto excn = new BadHeader("Missing expiry date"); 234 excn.payload = payload.toJSON; 235 throw excn; 236 } 237 238 auto int_date_error = new BadHeader("Expiry date is not an IntDate"); 239 240 try{ 241 header["exp"] = header["exp"].integer; 242 } catch (Exception ex){ 243 throw int_date_error; 244 } 245 246 if (header["exp"].integer < 0) 247 throw int_date_error; 248 249 if (header["exp"].integer < now()) 250 throw new SignatureExpired(format("Signature expired: date signed: %s", getIssueDate(header))); 251 return payload; 252 } 253 254 override Tuple!(JSONValue, JSONValue) loadsWithHeader(string s, string salt = null){ 255 auto tup = this.JSONWebSignatureSerializer.loadsWithHeader(s, salt); 256 JSONValue payload = tup[0]; 257 JSONValue header = tup[1]; 258 259 try{ 260 const exp = ("exp" in header); 261 }catch(JSONException ex){ 262 auto excn = new BadHeader("Missing expiry date!"); 263 excn.payload = payload.toJSON; 264 throw excn; 265 } 266 267 auto int_date_error = new BadHeader("Expiry date is not an IntDate!"); 268 269 try{ 270 header["exp"] = header["exp"].integer; 271 } catch (JSONException ex){ 272 throw int_date_error; 273 } 274 275 if (header["exp"].integer < 0) 276 throw int_date_error; 277 278 if (header["exp"].integer < now()){ 279 auto excn = new SignatureExpired(format("Signature expired: date signed: %s", getIssueDate(header))); 280 excn.payload = payload.toJSON; 281 throw excn; 282 } 283 284 return tuple(payload, header); 285 } 286 287 string getIssueDate(JSONValue header){ 288 int rv = header["iat"].integer.to!int; 289 290 return SysTime.fromUnixTime(rv).toString; 291 } 292 293 }