ECC token decoding and encoding passed
This commit is contained in:
parent
0b75b521b3
commit
d2a3c19d1d
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "(gdb) Launch-simple",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/simple",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Enable pretty-printing for gdb",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -161,3 +161,4 @@ this library uses code and ideas from [jwtd][1] and [jwt-go][2]
|
||||||
[2]: https://github.com/dgrijalva/jwt-go
|
[2]: https://github.com/dgrijalva/jwt-go
|
||||||
[3]: http://semver.org
|
[3]: http://semver.org
|
||||||
[4]: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
|
[4]: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
|
||||||
|
[5]: https://github.com/benmcollins/libjwt
|
||||||
|
|
4
dub.json
4
dub.json
|
@ -9,9 +9,9 @@
|
||||||
"HuntLabs"
|
"HuntLabs"
|
||||||
],
|
],
|
||||||
"description": "A D implementation of JSON Web Tokens",
|
"description": "A D implementation of JSON Web Tokens",
|
||||||
"copyright": "Copyright © 2020, HuntLabs",
|
"copyright": "Copyright © 2021, HuntLabs",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hunt-openssl": "~>1.0.1"
|
"hunt-openssl": "~>1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
27
examples/simple/.vscode/launch.json
vendored
Normal file
27
examples/simple/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "(gdb) Launch",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/simple",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Enable pretty-printing for gdb",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
{
|
{
|
||||||
"authors": [
|
"authors": [
|
||||||
"Zhang"
|
"HuntLabs"
|
||||||
],
|
],
|
||||||
"description": "A minimal D application.",
|
"description": "A minimal D application.",
|
||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
"name": "simple",
|
"name": "simple",
|
||||||
|
"versions" : [
|
||||||
|
"HUNT_JWT_DEBUG"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hunt-jwt" : {"path": "../../"}
|
"hunt-jwt" : {"path": "../../"},
|
||||||
|
"hunt" : "~>1.6.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,6 @@ string urlsafeB64Encode(string inp) pure nothrow {
|
||||||
/**
|
/**
|
||||||
* Decode a string with URL-safe Base64.
|
* Decode a string with URL-safe Base64.
|
||||||
*/
|
*/
|
||||||
string urlsafeB64Decode(string inp) pure {
|
ubyte[] urlsafeB64Decode(string inp) pure {
|
||||||
return cast(string)Base64URLNoPadding.decode(inp);
|
return Base64URLNoPadding.decode(inp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,9 @@ import std.algorithm;
|
||||||
import std.array : split;
|
import std.array : split;
|
||||||
|
|
||||||
|
|
||||||
|
import hunt.logging;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
simple version that accepts only strings as values for payload and header fields
|
simple version that accepts only strings as values for payload and header fields
|
||||||
*/
|
*/
|
||||||
|
@ -49,6 +52,27 @@ string encode(in ubyte[] payload, string key, JwtAlgorithm algo = JwtAlgorithm.H
|
||||||
string encodedHeader = memoize!(getEncodedHeader, 64)(algo, header_fields);
|
string encodedHeader = memoize!(getEncodedHeader, 64)(algo, header_fields);
|
||||||
string encodedPayload = Base64URLNoPadding.encode(payload);
|
string encodedPayload = Base64URLNoPadding.encode(payload);
|
||||||
|
|
||||||
|
string signingInput = encodedHeader ~ "." ~ encodedPayload;
|
||||||
|
string signValue = sign(signingInput, key, algo);
|
||||||
|
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
tracef("sign: %(%02X %)", cast(ubyte[])signValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
string signature = Base64URLNoPadding.encode(cast(ubyte[])sign(signingInput, key, algo));
|
||||||
|
return signingInput ~ "." ~ signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// string fromDER(string data) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
string encode(string payload, string key, JwtAlgorithm algo,
|
||||||
|
string header_fields) {
|
||||||
|
|
||||||
|
string encodedHeader = Base64URLNoPadding.encode(cast(ubyte[])header_fields); // memoize!(getEncodedHeader, 64)(algo, header_fields);
|
||||||
|
string encodedPayload = Base64URLNoPadding.encode(cast(ubyte[])payload);
|
||||||
|
|
||||||
string signingInput = encodedHeader ~ "." ~ encodedPayload;
|
string signingInput = encodedHeader ~ "." ~ encodedPayload;
|
||||||
string signature = Base64URLNoPadding.encode(cast(ubyte[])sign(signingInput, key, algo));
|
string signature = Base64URLNoPadding.encode(cast(ubyte[])sign(signingInput, key, algo));
|
||||||
|
|
||||||
|
@ -78,7 +102,7 @@ JSONValue decode(string token, string delegate(ref JSONValue jose) lazyKey) {
|
||||||
|
|
||||||
JSONValue header;
|
JSONValue header;
|
||||||
try {
|
try {
|
||||||
header = parseJSON(urlsafeB64Decode(tokenParts[0]));
|
header = parseJSON(cast(string)urlsafeB64Decode(tokenParts[0]));
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new VerifyException("Header is incorrect.");
|
throw new VerifyException("Header is incorrect.");
|
||||||
}
|
}
|
||||||
|
@ -98,13 +122,13 @@ JSONValue decode(string token, string delegate(ref JSONValue jose) lazyKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = lazyKey(header);
|
const key = lazyKey(header);
|
||||||
if(!verifySignature(urlsafeB64Decode(tokenParts[2]), tokenParts[0]~"."~tokenParts[1], key, alg))
|
if(!verifySignature(tokenParts[0]~"."~tokenParts[1], tokenParts[2], key, alg))
|
||||||
throw new VerifyException("Signature is incorrect.");
|
throw new VerifyException("Signature is incorrect.");
|
||||||
|
|
||||||
JSONValue payload;
|
JSONValue payload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = parseJSON(urlsafeB64Decode(tokenParts[1]));
|
payload = parseJSON(cast(string)urlsafeB64Decode(tokenParts[1]));
|
||||||
} catch(JSONException e) {
|
} catch(JSONException e) {
|
||||||
// Code coverage has to miss this line because the signature test above throws before this does
|
// Code coverage has to miss this line because the signature test above throws before this does
|
||||||
throw new VerifyException("Payload JSON is incorrect.");
|
throw new VerifyException("Payload JSON is incorrect.");
|
||||||
|
|
|
@ -9,126 +9,12 @@ import deimos.openssl.err;
|
||||||
import hunt.jwt.Exceptions;
|
import hunt.jwt.Exceptions;
|
||||||
import hunt.jwt.JwtAlgorithm;
|
import hunt.jwt.JwtAlgorithm;
|
||||||
|
|
||||||
|
import hunt.logging;
|
||||||
|
|
||||||
EC_KEY* getESKeypair(uint curve_type, string key) {
|
import std.conv;
|
||||||
EC_GROUP* curve;
|
import std.range;
|
||||||
EVP_PKEY* pktmp;
|
|
||||||
BIO* bpo;
|
|
||||||
EC_POINT* pub;
|
|
||||||
|
|
||||||
if(null == (curve = EC_GROUP_new_by_curve_name(curve_type)))
|
import core.stdc.stdlib : alloca;
|
||||||
throw new Exception("Unsupported curve.");
|
|
||||||
scope(exit) EC_GROUP_free(curve);
|
|
||||||
|
|
||||||
bpo = BIO_new_mem_buf(cast(char*)key.ptr, -1);
|
|
||||||
if(bpo is null) {
|
|
||||||
throw new Exception("Can't load the key.");
|
|
||||||
}
|
|
||||||
scope(exit) BIO_free(bpo);
|
|
||||||
|
|
||||||
pktmp = PEM_read_bio_PrivateKey(bpo, null, null, null);
|
|
||||||
if(pktmp is null) {
|
|
||||||
throw new Exception("Can't load the evp_pkey.");
|
|
||||||
}
|
|
||||||
scope(exit) EVP_PKEY_free(pktmp);
|
|
||||||
|
|
||||||
EC_KEY* eckey;
|
|
||||||
eckey = EVP_PKEY_get1_EC_KEY(pktmp);
|
|
||||||
if(eckey is null) {
|
|
||||||
throw new Exception("Can't convert evp_pkey to EC_KEY.");
|
|
||||||
}
|
|
||||||
scope(failure) EC_KEY_free(eckey);
|
|
||||||
|
|
||||||
if(1 != EC_KEY_set_group(eckey, curve)) {
|
|
||||||
throw new Exception("Can't associate group with the key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const BIGNUM *prv = EC_KEY_get0_private_key(eckey);
|
|
||||||
if(null == prv) {
|
|
||||||
throw new Exception("Can't get private key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub = EC_POINT_new(curve);
|
|
||||||
if(null == pub) {
|
|
||||||
throw new Exception("Can't allocate EC point.");
|
|
||||||
}
|
|
||||||
scope(exit) EC_POINT_free(pub);
|
|
||||||
|
|
||||||
if (1 != EC_POINT_mul(curve, pub, prv, null, null, null)) {
|
|
||||||
throw new Exception("Can't calculate public key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(1 != EC_KEY_set_public_key(eckey, pub)) {
|
|
||||||
throw new Exception("Can't set public key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return eckey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
EC_KEY* getESPrivateKey(uint curve_type, string key) {
|
|
||||||
EC_GROUP* curve;
|
|
||||||
EVP_PKEY* pktmp;
|
|
||||||
BIO* bpo;
|
|
||||||
|
|
||||||
if(null == (curve = EC_GROUP_new_by_curve_name(curve_type)))
|
|
||||||
throw new Exception("Unsupported curve.");
|
|
||||||
scope(exit) EC_GROUP_free(curve);
|
|
||||||
|
|
||||||
bpo = BIO_new_mem_buf(cast(char*)key.ptr, -1);
|
|
||||||
if(bpo is null) {
|
|
||||||
throw new Exception("Can't load the key.");
|
|
||||||
}
|
|
||||||
scope(exit) BIO_free(bpo);
|
|
||||||
|
|
||||||
pktmp = PEM_read_bio_PrivateKey(bpo, null, null, null);
|
|
||||||
if(pktmp is null) {
|
|
||||||
throw new Exception("Can't load the evp_pkey.");
|
|
||||||
}
|
|
||||||
scope(exit) EVP_PKEY_free(pktmp);
|
|
||||||
|
|
||||||
EC_KEY * eckey;
|
|
||||||
eckey = EVP_PKEY_get1_EC_KEY(pktmp);
|
|
||||||
if(eckey is null) {
|
|
||||||
throw new Exception("Can't convert evp_pkey to EC_KEY.");
|
|
||||||
}
|
|
||||||
|
|
||||||
scope(failure) EC_KEY_free(eckey);
|
|
||||||
if(1 != EC_KEY_set_group(eckey, curve)) {
|
|
||||||
throw new Exception("Can't associate group with the key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return eckey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
EC_KEY* getESPublicKey(uint curve_type, string key) {
|
|
||||||
EC_GROUP* curve;
|
|
||||||
|
|
||||||
if(null == (curve = EC_GROUP_new_by_curve_name(curve_type)))
|
|
||||||
throw new Exception("Unsupported curve.");
|
|
||||||
scope(exit) EC_GROUP_free(curve);
|
|
||||||
|
|
||||||
EC_KEY* eckey;
|
|
||||||
|
|
||||||
BIO* bpo = BIO_new_mem_buf(cast(char*)key.ptr, -1);
|
|
||||||
if(bpo is null) {
|
|
||||||
throw new Exception("Can't load the key.");
|
|
||||||
}
|
|
||||||
scope(exit) BIO_free(bpo);
|
|
||||||
|
|
||||||
eckey = PEM_read_bio_EC_PUBKEY(bpo, null, null, null);
|
|
||||||
scope(failure) EC_KEY_free(eckey);
|
|
||||||
|
|
||||||
if(1 != EC_KEY_set_group(eckey, curve)) {
|
|
||||||
throw new Exception("Can't associate group with the key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(0 == EC_KEY_check_key(eckey))
|
|
||||||
throw new Exception("Public key is not valid.");
|
|
||||||
|
|
||||||
return eckey;
|
|
||||||
}
|
|
||||||
|
|
||||||
string sign(string msg, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
string sign(string msg, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
||||||
ubyte[] sign;
|
ubyte[] sign;
|
||||||
|
@ -151,41 +37,11 @@ string sign(string msg, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sign_rs(ubyte* hash, int type, uint len, uint signLen) {
|
|
||||||
sign = new ubyte[len];
|
|
||||||
|
|
||||||
RSA* rsa_private = RSA_new();
|
version(HUNT_JWT_DEBUG) {
|
||||||
scope(exit) RSA_free(rsa_private);
|
trace("msg: ", msg);
|
||||||
|
trace("key: ", key);
|
||||||
BIO* bpo = BIO_new_mem_buf(cast(char*)key.ptr, -1);
|
trace("algo: ", algo);
|
||||||
if(bpo is null)
|
|
||||||
throw new Exception("Can't load the key.");
|
|
||||||
scope(exit) BIO_free(bpo);
|
|
||||||
|
|
||||||
RSA* rsa = PEM_read_bio_RSAPrivateKey(bpo, &rsa_private, null, null);
|
|
||||||
if(rsa is null) {
|
|
||||||
throw new Exception("Can't create RSA key.");
|
|
||||||
}
|
|
||||||
if(0 == RSA_sign(type, hash, signLen, sign.ptr, &signLen, rsa_private)) {
|
|
||||||
throw new Exception("Can't sign RSA message digest.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sign_es(uint curve_type, ubyte* hash, int hashLen) {
|
|
||||||
EC_KEY* eckey = getESPrivateKey(curve_type, key);
|
|
||||||
scope(exit) EC_KEY_free(eckey);
|
|
||||||
|
|
||||||
ECDSA_SIG* sig = ECDSA_do_sign(hash, hashLen, eckey);
|
|
||||||
if(sig is null) {
|
|
||||||
throw new Exception("Digest sign failed.");
|
|
||||||
}
|
|
||||||
scope(exit) ECDSA_SIG_free(sig);
|
|
||||||
|
|
||||||
sign = new ubyte[ECDSA_size(eckey)];
|
|
||||||
ubyte* c = sign.ptr;
|
|
||||||
if(!i2d_ECDSA_SIG(sig, &c)) {
|
|
||||||
throw new Exception("Convert sign to DER format failed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(algo) {
|
switch(algo) {
|
||||||
|
@ -204,52 +60,168 @@ string sign(string msg, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
||||||
sign_hs(EVP_sha512(), SHA512_DIGEST_LENGTH);
|
sign_hs(EVP_sha512(), SHA512_DIGEST_LENGTH);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RSA */
|
||||||
case JwtAlgorithm.RS256: {
|
case JwtAlgorithm.RS256: {
|
||||||
ubyte[] hash = new ubyte[SHA256_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha256();
|
||||||
SHA256(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||||||
sign_rs(hash.ptr, NID_sha256, 256, SHA256_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.RS384: {
|
case JwtAlgorithm.RS384: {
|
||||||
ubyte[] hash = new ubyte[SHA384_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha384();
|
||||||
SHA384(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||||||
sign_rs(hash.ptr, NID_sha384, 384, SHA384_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.RS512: {
|
case JwtAlgorithm.RS512: {
|
||||||
ubyte[] hash = new ubyte[SHA512_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha512();
|
||||||
SHA512(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||||||
sign_rs(hash.ptr, NID_sha512, 512, SHA512_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ECC */
|
||||||
case JwtAlgorithm.ES256: {
|
case JwtAlgorithm.ES256: {
|
||||||
ubyte[] hash = new ubyte[SHA256_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha256();
|
||||||
SHA256(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||||||
sign_es(NID_secp256k1, hash.ptr, SHA256_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.ES384: {
|
case JwtAlgorithm.ES384: {
|
||||||
ubyte[] hash = new ubyte[SHA384_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha384();
|
||||||
SHA384(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||||||
sign_es(NID_secp384r1, hash.ptr, SHA384_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.ES512: {
|
case JwtAlgorithm.ES512: {
|
||||||
ubyte[] hash = new ubyte[SHA512_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha512();
|
||||||
SHA512(cast(const(ubyte)*)msg.ptr, msg.length, hash.ptr);
|
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||||||
sign_es(NID_secp521r1, hash.ptr, SHA512_DIGEST_LENGTH);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new SignException("Wrong algorithm.");
|
throw new SignException("Wrong algorithm: " ~ to!string(algo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cast(string)sign;
|
return cast(string)sign;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ported from https://github.com/benmcollins/libjwt/blob/master/libjwt/jwt-openssl.c
|
||||||
|
private static ubyte[] signShaPem(const(EVP_MD) *alg, int type, string key, string msg) {
|
||||||
|
BIO * bufkey = BIO_new_mem_buf(cast(void*)key.ptr, cast(int)key.length);
|
||||||
|
if(bufkey is null) {
|
||||||
|
throw new Exception("Can't load the private key.");
|
||||||
|
}
|
||||||
|
scope(exit) BIO_free(bufkey);
|
||||||
|
|
||||||
bool verifySignature(string signature, string signing_input, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
/* This uses OpenSSL's default passphrase callback if needed. The
|
||||||
|
* library caller can override this in many ways, all of which are
|
||||||
|
* outside of the scope of LibJWT and this is documented in jwt.h. */
|
||||||
|
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bufkey, null, null, null);
|
||||||
|
if (pkey is null)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
scope(exit) EVP_PKEY_free(pkey);
|
||||||
|
|
||||||
|
int pkey_type = EVP_PKEY_id(pkey);
|
||||||
|
if (pkey_type != type)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
|
||||||
|
if (mdctx is null)
|
||||||
|
throw new Exception("Out of memory");
|
||||||
|
scope(exit) EVP_MD_CTX_destroy(mdctx);
|
||||||
|
|
||||||
|
/* Initialize the DigestSign operation using alg */
|
||||||
|
if (EVP_DigestSignInit(mdctx, null, alg, null, pkey) != 1)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
/* Call update with the message */
|
||||||
|
if (EVP_DigestSignUpdate(mdctx, cast(void*)msg.ptr, msg.length) != 1)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
/* First, call EVP_DigestSignFinal with a null sig parameter to get length
|
||||||
|
* of sig. Length is returned in slen */
|
||||||
|
size_t slen;
|
||||||
|
if (EVP_DigestSignFinal(mdctx, null, &slen) != 1)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
/* Allocate memory for signature based on returned size */
|
||||||
|
// FIXME: Needing refactor or cleanup -@zhangxueping at 2021-03-03T19:38:11+08:00
|
||||||
|
// Crashed
|
||||||
|
// ubyte[] sig = new ubyte[slen];
|
||||||
|
ubyte* sig = cast(ubyte*)alloca(slen);
|
||||||
|
|
||||||
|
/* Get the signature */
|
||||||
|
if (EVP_DigestSignFinal(mdctx, sig, &slen) != 1)
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
ubyte[] resultSig;
|
||||||
|
|
||||||
|
if (pkey_type != EVP_PKEY_EC) {
|
||||||
|
resultSig = sig[0..slen].dup;
|
||||||
|
} else {
|
||||||
|
uint degree, bn_len, r_len, s_len, buf_len;
|
||||||
|
|
||||||
|
/* For EC we need to convert to a raw format of R/S. */
|
||||||
|
|
||||||
|
/* Get the actual ec_key */
|
||||||
|
EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
|
||||||
|
if (ec_key is null)
|
||||||
|
throw new Exception("Out of memory");
|
||||||
|
|
||||||
|
degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key));
|
||||||
|
|
||||||
|
EC_KEY_free(ec_key);
|
||||||
|
|
||||||
|
/* Get the sig from the DER encoded version. */
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
infof("slen: %d, sig: %(%02X %)", slen, sig[0..slen]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Needing refactor or cleanup -@zhangxueping at 2021-03-03T19:39:16+08:00
|
||||||
|
// Crashed here
|
||||||
|
// ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(null, cast(const(ubyte) **)sig.ptr, cast(long)slen);
|
||||||
|
ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(null, cast(const(ubyte) **)&sig, cast(long)slen);
|
||||||
|
if (ec_sig is null)
|
||||||
|
throw new Exception("Can't decode ECDSA signature.");
|
||||||
|
scope(exit) ECDSA_SIG_free(ec_sig);
|
||||||
|
|
||||||
|
// version(HUNT_JWT_DEBUG) {
|
||||||
|
// tracef("slen: %d, sig: %(%02X %)", slen, sig[0..slen]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
BIGNUM *ec_sig_r;
|
||||||
|
BIGNUM *ec_sig_s;
|
||||||
|
ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);
|
||||||
|
r_len = BN_num_bytes(ec_sig_r);
|
||||||
|
s_len = BN_num_bytes(ec_sig_s);
|
||||||
|
bn_len = (degree + 7) / 8;
|
||||||
|
if ((r_len > bn_len) || (s_len > bn_len))
|
||||||
|
throw new Exception("Invalid argument");
|
||||||
|
|
||||||
|
buf_len = 2 * bn_len;
|
||||||
|
ubyte[] raw_buf = new ubyte[buf_len];
|
||||||
|
|
||||||
|
/* Pad the bignums with leading zeroes. */
|
||||||
|
// memset(raw_buf, 0, buf_len);
|
||||||
|
BN_bn2bin(ec_sig_r, raw_buf.ptr + bn_len - r_len);
|
||||||
|
BN_bn2bin(ec_sig_s, raw_buf.ptr + buf_len - s_len);
|
||||||
|
|
||||||
|
resultSig = raw_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
tracef("%d, buffer: %(%02X %)", resultSig.length, resultSig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool verifySignature(string head, string signature, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
||||||
|
import hunt.jwt.Base64Codec;
|
||||||
|
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
infof("signature: %s", signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte[] decodedSign = cast(ubyte[])urlsafeB64Decode(signature);
|
||||||
|
|
||||||
bool verify_rs(ubyte* hash, int type, uint len, uint signLen) {
|
bool verify_rs(ubyte* hash, int type, uint len, uint signLen) {
|
||||||
RSA* rsa_public = RSA_new();
|
RSA* rsa_public = RSA_new();
|
||||||
|
@ -265,26 +237,11 @@ bool verifySignature(string signature, string signing_input, string key, JwtAlgo
|
||||||
throw new Exception("Can't create RSA key.");
|
throw new Exception("Can't create RSA key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte[] sign = cast(ubyte[])signature;
|
// ubyte[] sign = cast(ubyte[])signature;
|
||||||
int ret = RSA_verify(type, hash, signLen, sign.ptr, len, rsa_public);
|
int ret = RSA_verify(type, hash, signLen, decodedSign.ptr, len, rsa_public);
|
||||||
return ret == 1;
|
return ret == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verify_es(uint curve_type, ubyte* hash, int hashLen ) {
|
|
||||||
EC_KEY* eckey = getESPublicKey(curve_type, key);
|
|
||||||
scope(exit) EC_KEY_free(eckey);
|
|
||||||
|
|
||||||
ubyte* c = cast(ubyte*)signature.ptr;
|
|
||||||
ECDSA_SIG* sig = null;
|
|
||||||
sig = d2i_ECDSA_SIG(&sig, cast(const (ubyte)**)&c, cast(int) key.length);
|
|
||||||
if (sig is null) {
|
|
||||||
throw new Exception("Can't decode ECDSA signature.");
|
|
||||||
}
|
|
||||||
scope(exit) ECDSA_SIG_free(sig);
|
|
||||||
|
|
||||||
int ret = ECDSA_do_verify(hash, hashLen, sig, eckey);
|
|
||||||
return ret == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(algo) {
|
switch(algo) {
|
||||||
case JwtAlgorithm.NONE: {
|
case JwtAlgorithm.NONE: {
|
||||||
|
@ -293,38 +250,39 @@ bool verifySignature(string signature, string signing_input, string key, JwtAlgo
|
||||||
case JwtAlgorithm.HS256:
|
case JwtAlgorithm.HS256:
|
||||||
case JwtAlgorithm.HS384:
|
case JwtAlgorithm.HS384:
|
||||||
case JwtAlgorithm.HS512: {
|
case JwtAlgorithm.HS512: {
|
||||||
return signature == sign(signing_input, key, algo);
|
return decodedSign == cast(ubyte[])sign(head, key, algo);
|
||||||
}
|
|
||||||
case JwtAlgorithm.RS256: {
|
|
||||||
ubyte[] hash = new ubyte[SHA256_DIGEST_LENGTH];
|
|
||||||
SHA256(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
|
||||||
return verify_rs(hash.ptr, NID_sha256, 256, SHA256_DIGEST_LENGTH);
|
|
||||||
}
|
|
||||||
case JwtAlgorithm.RS384: {
|
|
||||||
ubyte[] hash = new ubyte[SHA384_DIGEST_LENGTH];
|
|
||||||
SHA384(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
|
||||||
return verify_rs(hash.ptr, NID_sha384, 384, SHA384_DIGEST_LENGTH);
|
|
||||||
}
|
|
||||||
case JwtAlgorithm.RS512: {
|
|
||||||
ubyte[] hash = new ubyte[SHA512_DIGEST_LENGTH];
|
|
||||||
SHA512(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
|
||||||
return verify_rs(hash.ptr, NID_sha512, 512, SHA512_DIGEST_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RSA */
|
||||||
|
case JwtAlgorithm.RS256: {
|
||||||
|
const(EVP_MD) *alg = EVP_sha256();
|
||||||
|
return verifyShaPem(alg, EVP_PKEY_RSA, head, decodedSign, key);
|
||||||
|
}
|
||||||
|
case JwtAlgorithm.RS384: {
|
||||||
|
const(EVP_MD) *alg = EVP_sha384();
|
||||||
|
return verifyShaPem(alg, EVP_PKEY_RSA, head, decodedSign, key);
|
||||||
|
}
|
||||||
|
case JwtAlgorithm.RS512: {
|
||||||
|
const(EVP_MD) *alg = EVP_sha512();
|
||||||
|
return verifyShaPem(alg, EVP_PKEY_RSA, head, decodedSign, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ECC */
|
||||||
case JwtAlgorithm.ES256: {
|
case JwtAlgorithm.ES256: {
|
||||||
ubyte[] hash = new ubyte[SHA256_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha256();
|
||||||
SHA256(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||||||
return verify_es(NID_secp256k1, hash.ptr, SHA256_DIGEST_LENGTH );
|
|
||||||
|
// ubyte[] hash = new ubyte[SHA256_DIGEST_LENGTH];
|
||||||
|
// SHA256(cast(const(ubyte)*)head.ptr, head.length, hash.ptr);
|
||||||
|
// return verify_es(NID_secp256k1, hash.ptr, SHA256_DIGEST_LENGTH );
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.ES384: {
|
case JwtAlgorithm.ES384: {
|
||||||
ubyte[] hash = new ubyte[SHA384_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha384();
|
||||||
SHA384(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||||||
return verify_es(NID_secp384r1, hash.ptr, SHA384_DIGEST_LENGTH );
|
|
||||||
}
|
}
|
||||||
case JwtAlgorithm.ES512: {
|
case JwtAlgorithm.ES512: {
|
||||||
ubyte[] hash = new ubyte[SHA512_DIGEST_LENGTH];
|
const(EVP_MD) *alg = EVP_sha512();
|
||||||
SHA512(cast(const(ubyte)*)signing_input.ptr, signing_input.length, hash.ptr);
|
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||||||
return verify_es(NID_secp521r1, hash.ptr, SHA512_DIGEST_LENGTH );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -332,3 +290,125 @@ bool verifySignature(string signature, string signing_input, string key, JwtAlgo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool verifyShaPem(const(EVP_MD) *alg, int type, string head, const(ubyte)[] sig, string key) {
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
tracef("head: %s", head);
|
||||||
|
tracef("sig: %(%02X %)", sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
int slen = cast(int)sig.length;
|
||||||
|
|
||||||
|
// sig = jwt_b64_decode(sig_b64, &slen);
|
||||||
|
if (sig.empty()) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BIO *bufkey = BIO_new_mem_buf(cast(void*)key.ptr, cast(int)key.length);
|
||||||
|
if (bufkey is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Out of memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope(exit) BIO_free(bufkey);
|
||||||
|
|
||||||
|
/* This uses OpenSSL's default passphrase callback if needed. The
|
||||||
|
* library caller can override this in many ways, all of which are
|
||||||
|
* outside of the scope of LibJWT and this is documented in jwt.h. */
|
||||||
|
EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bufkey, null, null, null);
|
||||||
|
if (pkey is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scope(exit) EVP_PKEY_free(pkey);
|
||||||
|
|
||||||
|
int pkey_type = EVP_PKEY_id(pkey);
|
||||||
|
if (pkey_type != type) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert EC sigs back to ASN1. */
|
||||||
|
if (pkey_type == EVP_PKEY_EC) {
|
||||||
|
uint degree, bn_len;
|
||||||
|
EC_KEY *ec_key;
|
||||||
|
|
||||||
|
ECDSA_SIG *ec_sig = ECDSA_SIG_new();
|
||||||
|
if (ec_sig is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Out of memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scope(exit) ECDSA_SIG_free(ec_sig);
|
||||||
|
|
||||||
|
/* Get the actual ec_key */
|
||||||
|
ec_key = EVP_PKEY_get1_EC_KEY(pkey);
|
||||||
|
if (ec_key is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Out of memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key));
|
||||||
|
|
||||||
|
EC_KEY_free(ec_key);
|
||||||
|
|
||||||
|
bn_len = (degree + 7) / 8;
|
||||||
|
if ((bn_len * 2) != slen) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BIGNUM *ec_sig_r = BN_bin2bn(cast(const(ubyte)*)sig.ptr, bn_len, null);
|
||||||
|
BIGNUM *ec_sig_s = BN_bin2bn(cast(const(ubyte)*)sig.ptr + bn_len, bn_len, null);
|
||||||
|
if (ec_sig_r is null || ec_sig_s is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECDSA_SIG_set0(ec_sig, ec_sig_r, ec_sig_s);
|
||||||
|
|
||||||
|
slen = i2d_ECDSA_SIG(ec_sig, null);
|
||||||
|
// sig = jwt_malloc(slen);
|
||||||
|
ubyte[] tempBuffer = new ubyte[slen];
|
||||||
|
ubyte*p = tempBuffer.ptr;
|
||||||
|
// ubyte* tempBuffer = cast(ubyte*)alloca(slen);
|
||||||
|
// ubyte*p = tempBuffer;
|
||||||
|
slen = i2d_ECDSA_SIG(ec_sig, &p);
|
||||||
|
if (slen == 0) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sig = tempBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
|
||||||
|
if (mdctx is null) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Out of memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scope(exit) EVP_MD_CTX_destroy(mdctx);
|
||||||
|
|
||||||
|
/* Initialize the DigestVerify operation using alg */
|
||||||
|
if (EVP_DigestVerifyInit(mdctx, null, alg, null, pkey) != 1){
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call update with the message */
|
||||||
|
if (EVP_DigestVerifyUpdate(mdctx, head.ptr, cast(int)head.length) != 1){
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
version(HUNT_JWT_DEBUG) {
|
||||||
|
tracef("slen: %d, sig: %(%02X %)", slen, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now check the sig for validity. */
|
||||||
|
if (EVP_DigestVerifyFinal(mdctx, cast(ubyte*)sig.ptr, slen) != 1) {
|
||||||
|
version(HUNT_JWT_DEBUG) warning("Invalid argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ import std.datetime;
|
||||||
import std.json;
|
import std.json;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
|
||||||
|
import hunt.logging;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* represents a token
|
* represents a token
|
||||||
*/
|
*/
|
||||||
|
@ -75,7 +78,8 @@ private {
|
||||||
string token = this.data ~ "." ~ this.signature(secret);
|
string token = this.data ~ "." ~ this.signature(secret);
|
||||||
|
|
||||||
version(HUNT_AUTH_DEBUG) {
|
version(HUNT_AUTH_DEBUG) {
|
||||||
tracef("secret: %s, token: %s", secret, token);
|
import std.stdio;
|
||||||
|
writeln("secret: %s, token: %s", secret, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
@ -113,7 +117,7 @@ private {
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
import std.uni : toUpper;
|
import std.uni : toUpper;
|
||||||
|
|
||||||
version(HUNT_AUTH_DEBUG) {
|
version(HUNT_JWT_DEBUG) {
|
||||||
tracef("token: %s", token);
|
tracef("token: %s", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +128,7 @@ private {
|
||||||
|
|
||||||
JSONValue header;
|
JSONValue header;
|
||||||
try {
|
try {
|
||||||
header = parseJSON(urlsafeB64Decode(tokenParts[0]));
|
header = parseJSON(cast(string)urlsafeB64Decode(tokenParts[0]));
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new VerifyException("Header is incorrect.");
|
throw new VerifyException("Header is incorrect.");
|
||||||
}
|
}
|
||||||
|
@ -144,13 +148,13 @@ private {
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = lazyKey(header);
|
const key = lazyKey(header);
|
||||||
if(!key.empty() && !verifySignature(urlsafeB64Decode(tokenParts[2]), tokenParts[0]~"."~tokenParts[1], key, alg))
|
if(!key.empty() && !verifySignature(tokenParts[0]~"."~tokenParts[1], tokenParts[2], key, alg))
|
||||||
throw new VerifyException("Signature is incorrect.");
|
throw new VerifyException("Signature is incorrect.");
|
||||||
|
|
||||||
JSONValue payload;
|
JSONValue payload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = parseJSON(urlsafeB64Decode(tokenParts[1]));
|
payload = parseJSON(cast(string)urlsafeB64Decode(tokenParts[1]));
|
||||||
} catch(JSONException e) {
|
} catch(JSONException e) {
|
||||||
// Code coverage has to miss this line because the signature test above throws before this does
|
// Code coverage has to miss this line because the signature test above throws before this does
|
||||||
throw new VerifyException("Payload JSON is incorrect.");
|
throw new VerifyException("Payload JSON is incorrect.");
|
||||||
|
@ -177,7 +181,7 @@ private {
|
||||||
|
|
||||||
string[] tokenParts = split(token, ".");
|
string[] tokenParts = split(token, ".");
|
||||||
|
|
||||||
string decHeader = urlsafeB64Decode(tokenParts[0]);
|
string decHeader = cast(string)urlsafeB64Decode(tokenParts[0]);
|
||||||
JSONValue header = parseJSON(decHeader);
|
JSONValue header = parseJSON(decHeader);
|
||||||
|
|
||||||
JwtAlgorithm alg;
|
JwtAlgorithm alg;
|
||||||
|
@ -194,7 +198,7 @@ private {
|
||||||
throw new VerifyException("Type is incorrect.");
|
throw new VerifyException("Type is incorrect.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifySignature(urlsafeB64Decode(tokenParts[2]), tokenParts[0]~"."~tokenParts[1], key, alg);
|
return verifySignature(tokenParts[0]~"."~tokenParts[1], tokenParts[2], key, alg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
382
source/hunt/logging.d
Normal file
382
source/hunt/logging.d
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
module hunt.logging;
|
||||||
|
|
||||||
|
version(HUNT_JWT_DEBUG) :
|
||||||
|
|
||||||
|
import core.stdc.stdlib;
|
||||||
|
import core.runtime;
|
||||||
|
import core.thread;
|
||||||
|
|
||||||
|
import std.conv;
|
||||||
|
import std.datetime;
|
||||||
|
import std.exception;
|
||||||
|
import std.format;
|
||||||
|
import std.range;
|
||||||
|
import std.regex;
|
||||||
|
import std.stdio;
|
||||||
|
import std.string;
|
||||||
|
import std.typecons;
|
||||||
|
import std.traits;
|
||||||
|
|
||||||
|
|
||||||
|
version (Posix) {
|
||||||
|
extern (C) nothrow @nogc size_t syscall(size_t ident, ...);
|
||||||
|
ThreadID getTid() {
|
||||||
|
version(FreeBSD) {
|
||||||
|
long tid;
|
||||||
|
enum SYS_thr_self = 432;
|
||||||
|
syscall(SYS_thr_self, &tid);
|
||||||
|
return cast(ThreadID)tid;
|
||||||
|
} else version(OSX) {
|
||||||
|
enum SYS_thread_selfid = 372;
|
||||||
|
return cast(ThreadID)syscall(SYS_thread_selfid);
|
||||||
|
} else version(linux) {
|
||||||
|
enum __NR_gettid = 186;
|
||||||
|
return cast(ThreadID)syscall(__NR_gettid);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
import core.sys.windows.winbase: GetCurrentThreadId;
|
||||||
|
ThreadID getTid() {
|
||||||
|
return GetCurrentThreadId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version (Windows) {
|
||||||
|
import core.sys.windows.wincon;
|
||||||
|
import core.sys.windows.winbase;
|
||||||
|
import core.sys.windows.windef;
|
||||||
|
import toolkit.os.windows.console;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
version (Posix) {
|
||||||
|
enum PRINT_COLOR_NONE = "\033[m";
|
||||||
|
enum PRINT_COLOR_RED = "\033[0;32;31m";
|
||||||
|
enum PRINT_COLOR_GREEN = "\033[0;32;32m";
|
||||||
|
enum PRINT_COLOR_YELLOW = "\033[1;33m";
|
||||||
|
}
|
||||||
|
|
||||||
|
version (Android) {
|
||||||
|
import core.stdc.stdarg : va_end, va_list, va_start;
|
||||||
|
import core.sys.posix.sys.types;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AASSET_MODE_UNKNOWN,
|
||||||
|
AASSET_MODE_RANDOM,
|
||||||
|
AASSET_MODE_STREAMING,
|
||||||
|
AASSET_MODE_BUFFER
|
||||||
|
}
|
||||||
|
|
||||||
|
enum android_LogPriority {
|
||||||
|
ANDROID_LOG_UNKNOWN,
|
||||||
|
ANDROID_LOG_DEFAULT,
|
||||||
|
ANDROID_LOG_VERBOSE,
|
||||||
|
ANDROID_LOG_DEBUG,
|
||||||
|
ANDROID_LOG_INFO,
|
||||||
|
ANDROID_LOG_WARN,
|
||||||
|
ANDROID_LOG_ERROR,
|
||||||
|
ANDROID_LOG_FATAL,
|
||||||
|
ANDROID_LOG_SILENT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LOG_TAG = "HUNT";
|
||||||
|
|
||||||
|
// dfmt off
|
||||||
|
extern (C):
|
||||||
|
@system:
|
||||||
|
nothrow:
|
||||||
|
@nogc:
|
||||||
|
// dfmt on
|
||||||
|
|
||||||
|
struct AAssetManager;
|
||||||
|
struct AAssetDir;
|
||||||
|
struct AAsset;
|
||||||
|
|
||||||
|
AAssetDir* AAssetManager_openDir(AAssetManager* mgr, const(char)* dirName);
|
||||||
|
AAsset* AAssetManager_open(AAssetManager* mgr, const(char)* filename, int mode);
|
||||||
|
const(char)* AAssetDir_getNextFileName(AAssetDir* assetDir);
|
||||||
|
void AAssetDir_rewind(AAssetDir* assetDir);
|
||||||
|
void AAssetDir_close(AAssetDir* assetDir);
|
||||||
|
int AAsset_read(AAsset* asset, void* buf, size_t count);
|
||||||
|
off_t AAsset_seek(AAsset* asset, off_t offset, int whence);
|
||||||
|
void AAsset_close(AAsset* asset);
|
||||||
|
const(void)* AAsset_getBuffer(AAsset* asset);
|
||||||
|
off_t AAsset_getLength(AAsset* asset);
|
||||||
|
off_t AAsset_getRemainingLength(AAsset* asset);
|
||||||
|
int AAsset_openFileDescriptor(AAsset* asset, off_t* outStart, off_t* outLength);
|
||||||
|
int AAsset_isAllocated(AAsset* asset);
|
||||||
|
|
||||||
|
int __android_log_write(int prio, const(char)* tag, const(char)* text);
|
||||||
|
int __android_log_print(int prio, const(char)* tag, const(char)* fmt, ...);
|
||||||
|
int __android_log_vprint(int prio, const(char)* tag, const(char)* fmt, va_list ap);
|
||||||
|
void __android_log_assert(const(char)* cond, const(char)* tag, const(char)* fmt, ...);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLevel {
|
||||||
|
Trace = 0,
|
||||||
|
Info = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Error = 3,
|
||||||
|
Fatal = 4,
|
||||||
|
Off = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class ConsoleLogger {
|
||||||
|
private __gshared LogLevel g_logLevel = LogLevel.Trace;
|
||||||
|
private enum traceLevel = toString(LogLevel.Trace);
|
||||||
|
private enum infoLevel = toString(LogLevel.Info);
|
||||||
|
private enum warningLevel = toString(LogLevel.Warning);
|
||||||
|
private enum errorLevel = toString(LogLevel.Error);
|
||||||
|
private enum fatalLevel = toString(LogLevel.Fatal);
|
||||||
|
private enum offlLevel = toString(LogLevel.Off);
|
||||||
|
|
||||||
|
static void setLogLevel(LogLevel level) {
|
||||||
|
g_logLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trace(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Trace, layout!(file, line, func)(logFormat(args), traceLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tracef(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Trace, layout!(file, line, func)(logFormatf(args), traceLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void info(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Info, layout!(file, line, func)(logFormat(args), infoLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void infof(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Info, layout!(file, line, func)(logFormatf(args), infoLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void warning(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Warning, layout!(file, line,
|
||||||
|
func)(logFormat(args), warningLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void warningf(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Warning, layout!(file, line,
|
||||||
|
func)(logFormatf(args), warningLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void error(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Error, layout!(file, line, func)(logFormat(args), errorLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void errorf(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Error, layout!(file, line, func)(logFormatf(args), errorLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fatal(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Fatal, layout!(file, line, func)(logFormat(args), fatalLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fatalf(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__, A...)(lazy A args) nothrow {
|
||||||
|
writeFormatColor(LogLevel.Fatal, layout!(file, line, func)(logFormatf(args), fatalLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string logFormatf(A...)(A args) {
|
||||||
|
Appender!string buffer;
|
||||||
|
formattedWrite(buffer, args);
|
||||||
|
return buffer.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string logFormat(A...)(A args) {
|
||||||
|
auto w = appender!string();
|
||||||
|
foreach (arg; args) {
|
||||||
|
alias A = typeof(arg);
|
||||||
|
static if (isAggregateType!A || is(A == enum)) {
|
||||||
|
import std.format : formattedWrite;
|
||||||
|
|
||||||
|
formattedWrite(w, "%s", arg);
|
||||||
|
} else static if (isSomeString!A) {
|
||||||
|
put(w, arg);
|
||||||
|
} else static if (isIntegral!A) {
|
||||||
|
import std.conv : toTextRange;
|
||||||
|
|
||||||
|
toTextRange(arg, w);
|
||||||
|
} else static if (isBoolean!A) {
|
||||||
|
put(w, arg ? "true" : "false");
|
||||||
|
} else static if (isSomeChar!A) {
|
||||||
|
put(w, arg);
|
||||||
|
} else {
|
||||||
|
import std.format : formattedWrite;
|
||||||
|
|
||||||
|
// Most general case
|
||||||
|
formattedWrite(w, "%s", arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string layout(string file = __FILE__, size_t line = __LINE__,
|
||||||
|
string func = __FUNCTION__)(string msg, string level) {
|
||||||
|
enum lineNum = std.conv.to!string(line);
|
||||||
|
string time_prior = Clock.currTime.toString();
|
||||||
|
string tid = std.conv.to!string(cast(int)getTid());
|
||||||
|
|
||||||
|
// writeln("fullname: ",func);
|
||||||
|
string fun = func;
|
||||||
|
ptrdiff_t index = lastIndexOf(func, '.');
|
||||||
|
if (index != -1) {
|
||||||
|
if (func[index - 1] != ')') {
|
||||||
|
ptrdiff_t idx = lastIndexOf(func, '.', index);
|
||||||
|
if (idx != -1)
|
||||||
|
index = idx;
|
||||||
|
}
|
||||||
|
fun = func[index + 1 .. $];
|
||||||
|
}
|
||||||
|
|
||||||
|
return time_prior ~ " | " ~ tid ~ " | " ~ level ~ " | " ~ fun ~ " | " ~ msg
|
||||||
|
~ " | " ~ file ~ ":" ~ lineNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private static string defaultLayout(string context, string msg, string level)
|
||||||
|
// {
|
||||||
|
// string time_prior = Clock.currTime.toString();
|
||||||
|
// string tid = std.conv.to!string(getTid());
|
||||||
|
|
||||||
|
// return time_prior ~ " | " ~ tid ~ " | " ~ level ~ context ~ msg;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static string toString(LogLevel level) nothrow {
|
||||||
|
string r;
|
||||||
|
final switch (level) with (LogLevel) {
|
||||||
|
case Trace:
|
||||||
|
r = "trace";
|
||||||
|
break;
|
||||||
|
case Info:
|
||||||
|
r = "info";
|
||||||
|
break;
|
||||||
|
case Warning:
|
||||||
|
r = "warning";
|
||||||
|
break;
|
||||||
|
case Error:
|
||||||
|
r = "error";
|
||||||
|
break;
|
||||||
|
case Fatal:
|
||||||
|
r = "fatal";
|
||||||
|
break;
|
||||||
|
case Off:
|
||||||
|
r = "off";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeFormatColor(LogLevel level, lazy string msg) nothrow {
|
||||||
|
if (level < g_logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
version (Posix) {
|
||||||
|
version (Android) {
|
||||||
|
string prior_color;
|
||||||
|
android_LogPriority logPrioity = android_LogPriority.ANDROID_LOG_INFO;
|
||||||
|
switch (level) with (LogLevel) {
|
||||||
|
case Error:
|
||||||
|
case Fatal:
|
||||||
|
prior_color = PRINT_COLOR_RED;
|
||||||
|
logPrioity = android_LogPriority.ANDROID_LOG_ERROR;
|
||||||
|
break;
|
||||||
|
case Warning:
|
||||||
|
prior_color = PRINT_COLOR_YELLOW;
|
||||||
|
logPrioity = android_LogPriority.ANDROID_LOG_WARN;
|
||||||
|
break;
|
||||||
|
case Info:
|
||||||
|
prior_color = PRINT_COLOR_GREEN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
prior_color = string.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
__android_log_write(logPrioity,
|
||||||
|
LOG_TAG, toStringz(prior_color ~ msg ~ PRINT_COLOR_NONE));
|
||||||
|
} catch(Exception ex) {
|
||||||
|
collectException( {
|
||||||
|
write(PRINT_COLOR_RED);
|
||||||
|
write(ex);
|
||||||
|
writeln(PRINT_COLOR_NONE);
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
string prior_color;
|
||||||
|
switch (level) with (LogLevel) {
|
||||||
|
case Error:
|
||||||
|
case Fatal:
|
||||||
|
prior_color = PRINT_COLOR_RED;
|
||||||
|
break;
|
||||||
|
case Warning:
|
||||||
|
prior_color = PRINT_COLOR_YELLOW;
|
||||||
|
break;
|
||||||
|
case Info:
|
||||||
|
prior_color = PRINT_COLOR_GREEN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
prior_color = string.init;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
writeln(prior_color ~ msg ~ PRINT_COLOR_NONE);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
collectException( {
|
||||||
|
write(PRINT_COLOR_RED);
|
||||||
|
write(ex);
|
||||||
|
writeln(PRINT_COLOR_NONE);
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else version (Windows) {
|
||||||
|
enum defaultColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE;
|
||||||
|
|
||||||
|
ushort color;
|
||||||
|
switch (level) with (LogLevel) {
|
||||||
|
case Error:
|
||||||
|
case Fatal:
|
||||||
|
color = FOREGROUND_RED;
|
||||||
|
break;
|
||||||
|
case Warning:
|
||||||
|
color = FOREGROUND_GREEN | FOREGROUND_RED;
|
||||||
|
break;
|
||||||
|
case Info:
|
||||||
|
color = FOREGROUND_GREEN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleHelper.writeWithAttribute(msg, color);
|
||||||
|
} else {
|
||||||
|
assert(false, "Unsupported OS.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias trace = ConsoleLogger.trace;
|
||||||
|
alias tracef = ConsoleLogger.tracef;
|
||||||
|
alias info = ConsoleLogger.info;
|
||||||
|
alias infof = ConsoleLogger.infof;
|
||||||
|
alias warning = ConsoleLogger.warning;
|
||||||
|
alias warningf = ConsoleLogger.warningf;
|
||||||
|
alias error = ConsoleLogger.error;
|
||||||
|
alias errorf = ConsoleLogger.errorf;
|
||||||
|
// alias critical = ConsoleLogger.critical;
|
||||||
|
// alias criticalf = ConsoleLogger.criticalf;
|
||||||
|
|
Loading…
Reference in a new issue