1 module ddata.jwt.sign;
2 
3 import std.format;
4 
5 import deimos.openssl.pem;
6 import deimos.openssl.rsa;
7 import deimos.openssl.hmac;
8 import deimos.openssl.sha;
9 import deimos.openssl.err;
10 
11 import ddata.common;
12 import ddata.jwt.algorithm;
13 
14 // These functions are added to the bindings post version 2.0, but vibe-d seems to
15 // import version ~>1.0 of the bindings. So you get compile errors when including
16 // in a vibe project. This is for that.
17 static if (!__traits(compiles, {
18     auto ctx = HMAC_CTX_new;
19     HMAC_CTX_reset(ctx);
20     HMAC_CTX_free(ctx);
21 })) {
22     extern(C) nothrow {
23         HMAC_CTX * HMAC_CTX_new();
24         void HMAC_CTX_free(HMAC_CTX *ctx);
25         void HMAC_CTX_reset(HMAC_CTX * ctx);
26     }
27 }
28 
29 private string signHs(string message, string key, uint digestLength, const(EVP_MD)* evp) @trusted {
30     auto signedData = new ubyte[digestLength];
31 
32     auto ctx = HMAC_CTX_new();
33     if (ctx is null) {
34         throw new Exception("Failed to create HMAC context");
35     }
36     scope(exit) HMAC_CTX_free(ctx);
37 
38     if (!HMAC_Init_ex(ctx, key.ptr, cast(int)key.length, evp, null)) {
39         throw new Exception("Failed to initialize HMAC context - %s".format(getLastError));
40     }
41     if (!HMAC_Update(ctx, cast(const(ubyte)*)message.ptr, cast(ulong)message.length)) {
42         throw new Exception("Failed to update HMAC - %s".format(getLastError));
43     }
44     if (!HMAC_Final(ctx, cast(ubyte*)signedData.ptr, &digestLength)) {
45         throw new Exception("Failed to finalize HMAC - %s".format(getLastError));
46     }
47 
48     return cast(string)signedData;
49 }
50 
51 private string signRs(string message, string key, uint digestLength, int type) @trusted {
52     auto sha256 = new ubyte[digestLength];
53     SHA256(cast(const(ubyte)*)message.ptr, message.length, sha256.ptr);
54 
55     auto signedData = new ubyte[digestLength * 8];
56 
57     RSA* ctx = RSA_new();
58     if (ctx is null) {
59         throw new Exception("Failed to create RSA context - %s".format(getLastError));
60     }
61     scope(exit) RSA_free(ctx);
62 
63     BIO* bio = BIO_new_mem_buf(cast(char*)key.ptr, cast(int)key.length);
64     if (bio is null) {
65         throw new Exception("Failed to load key in memory bio - %s".format(getLastError));
66     }
67     scope(exit) BIO_free(bio);
68 
69     RSA* rsaPrivate = PEM_read_bio_RSAPrivateKey(bio, &ctx, null, null);
70     if(rsaPrivate is null) {
71         throw new Exception("Failed to create RSA private key - %s".format(getLastError));
72     }
73     if (!RSA_sign(type, cast(const(ubyte)*)sha256.ptr, digestLength, signedData.ptr, &digestLength, rsaPrivate)) {
74         throw new Exception("Failed to sign RSA message digest - %s".format(getLastError));
75     }
76 
77     return cast(string)signedData;
78 }
79 
80 package string sign(string message, string key, Algorithm algorithm = Algorithm.hs256) @safe {    
81     final switch (algorithm) {
82         case Algorithm.hs256:
83             return signHs(message, key, SHA256_DIGEST_LENGTH, () @trusted { return EVP_sha256(); } ());
84         case Algorithm.rs256: {
85             return signRs(message, key, SHA256_DIGEST_LENGTH, NID_sha256);
86         }
87     }
88 }