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 }