ECC token decoding and encoding passed

This commit is contained in:
heromyth 2021-03-04 20:38:00 +08:00
parent 0b75b521b3
commit d2a3c19d1d
10 changed files with 782 additions and 233 deletions

27
.vscode/launch.json vendored Normal file
View 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
}
]
}
]
}

View file

@ -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

View file

@ -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
View 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
}
]
}
]
}

View file

@ -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"
} }
} }

View file

@ -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);
} }

View file

@ -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.");

View file

@ -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;
}

View file

@ -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
View 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;