1 module itsdangerous.dsigner;
2
3 import std.stdio;
4 import std.format;
5 import std.string: representation;
6 import std.digest.sha;
7 import std.digest.hmac;
8 import std.algorithm.searching;
9 import std.array;
10
11 import itsdangerous.encoding;
12 import itsdangerous.exc;
13
14 bool constantTimeCompare(string val1, string val2){ // not sure if we need this in D
15 /*Return ``True`` if the two strings are equal, ``False``
16 otherwise.
17 */
18 const len_eq = val1.length == val2.length;
19 int result;
20 string left;
21 if (len_eq){
22 result = 0;
23 left = val1;
24 } else {
25 result = 1;
26 left = val2;
27 }
28 import std.range: zip;
29 foreach (x, y; zip(cast(ubyte[])left, cast(ubyte[])val2))
30 result |= x ^ y;
31 return result == 0;
32 }
33
34 interface SigningAlgorithm {
35 /+Subclasses must implement :meth:`getSignature` to provide
36 signature generation functionality.
37 +/
38 string getSignature(ubyte[] key, string value);
39 bool verifySignature(ubyte[] key, string value, string sig);
40 }
41
42 class NoneAlgorithm: SigningAlgorithm {
43 string getSignature(ubyte[] key, string value){
44 return "";
45 }
46
47 bool verifySignature(ubyte[] key, string value, string sig){
48 return true;
49 }
50 }
51
52 class HMACAlgorithm(DigestMethod): SigningAlgorithm {
53
54 string getSignature(ubyte[] key, string value){
55 auto hmac = HMAC!DigestMethod(key);
56 ubyte[] hash = hmac.put(value.representation).finish.dup;
57 return cast(string)hash;
58 }
59
60 bool verifySignature(ubyte[] key, string value, string sig){
61 /+Verifies the given signature matches the expected
62 signature.
63 +/
64 //return sig == getSignature(key, value) ;
65 return constantTimeCompare(sig, getSignature(key, value));
66 }
67 }
68
69 class Signer(DigestMethod, AlgDigestMethod) {
70
71 this(string secretKey,
72 string salt = null,
73 char sep = '.',
74 string keyDerivation = "django-concat"
75 ){
76
77 if(salt is null)
78 this.salt = "itsdangerous.Signer";
79 else
80 this.salt = salt;
81
82 if(canFind(BASE64_ALPHABET, sep))
83 throw new Exception("The given separator cannot be used because it may be\n
84 contained in the signature itself. Alphanumeric\n
85 characters and `-_=` must not be used.");
86 this.secretKey = secretKey;
87 this.sep = sep;
88 this.keyDerivation = keyDerivation;
89
90 digester = new WrapperDigest!DigestMethod();
91 algorithm = new HMACAlgorithm!AlgDigestMethod();
92 }
93
94 char sep;
95
96 private {
97 string secretKey;
98 SigningAlgorithm algorithm;
99 string salt;
100 string keyDerivation;
101 WrapperDigest!DigestMethod digester;
102
103 }
104
105 void setAlgorithm(SigningAlgorithm alg){
106 this.algorithm = alg;
107 }
108
109 SigningAlgorithm getAlgorithm(){
110 return this.algorithm;
111 }
112
113 final ubyte[] deriveKey(){
114 /+This method is called to derive the key. The default key
115 derivation choices can be overridden here. Key derivation is not
116 intended to be used as a security method to make a complex key
117 out of a short password. Instead you should use large random
118 secret keys.
119 +/
120
121 if(keyDerivation == "concat"){
122 digester.put(salt.representation);
123 digester.put(secretKey.representation);
124 return digester.finish();
125 }
126 else if (keyDerivation == "django-concat"){
127
128 digester.put(salt.representation);
129 digester.put("signer".representation);
130 digester.put(secretKey.representation);
131
132 //digester.put(salt.representation ~ "signer".representation ~ secretKey.representation);
133 return digester.finish();
134 }
135 else if (keyDerivation == "hmac")
136 return salt.representation.hmac!DigestMethod(secretKey.representation).dup;
137 else if (keyDerivation == "none")
138 return secretKey.representation.dup;
139 else
140 throw new Exception("Unknown key derivation method");
141
142 }
143
144 final string getSignature(string value){
145 /+Returns the signature for the given value.+/
146 ubyte[] key = deriveKey();
147 string sig = algorithm.getSignature(key, value);
148 return base64Encode(sig);
149 }
150
151 string sign(string value){
152 /+Signs the given string.+/
153 return value ~ sep ~ getSignature(value);
154 }
155
156 final bool verifySignature(string value, string sig){
157 /+Verifies the signature for the given value.+/
158 ubyte[] key = deriveKey();
159 string decoded;
160 try{
161 decoded = base64Decode(sig);
162 } catch (Exception exc) {
163 return false;
164 }
165 return algorithm.verifySignature(key, value, decoded);
166 }
167
168 string unsign(string signedValue, int maxAge = 0, int* tstamp = null){
169 /+ Unsigns the given string. +/
170 if(!canFind(signedValue, sep))
171 throw new BadSignature(format("No %c found in value", sep));
172 immutable arr = signedValue.rsplit(sep);
173 auto value = arr[0];
174 auto sig = arr[1];
175 if (verifySignature(value, sig))
176 return value;
177 throw new BadSignature(format("Signature %s does not match", sig), value);
178 }
179
180 bool validate(string signedValue){
181 /+Only validates the given signed value. Returns ``True`` if
182 the signature exists and is valid.
183 +/
184 try{
185 unsign(signedValue);
186 return true;
187 } catch (BadSignature ex) {
188 return false;
189 }
190 }
191 }