437 lines
13 KiB
D
437 lines
13 KiB
D
module hunt.jwt.JwtOpenSSL;
|
||
|
||
import deimos.openssl.ssl;
|
||
import deimos.openssl.pem;
|
||
import deimos.openssl.rsa;
|
||
import deimos.openssl.hmac;
|
||
import deimos.openssl.err;
|
||
|
||
import hunt.jwt.Exceptions;
|
||
import hunt.jwt.JwtAlgorithm;
|
||
|
||
import hunt.logging;
|
||
|
||
import std.conv;
|
||
import std.range;
|
||
|
||
import core.stdc.stdlib : alloca;
|
||
import core.stdc.config : c_long;
|
||
|
||
string sign(string msg, string key, JwtAlgorithm algo = JwtAlgorithm.HS256) {
|
||
ubyte[] sign;
|
||
|
||
void sign_hs(const(EVP_MD)* evp, uint signLen) {
|
||
sign = new ubyte[signLen];
|
||
|
||
HMAC_CTX ctx;
|
||
scope(exit) HMAC_CTX_reset(&ctx);
|
||
HMAC_CTX_reset(&ctx);
|
||
|
||
if(0 == HMAC_Init_ex(&ctx, key.ptr, cast(int)key.length, evp, null)) {
|
||
throw new Exception("Can't initialize HMAC context.");
|
||
}
|
||
if(0 == HMAC_Update(&ctx, cast(const(ubyte)*)msg.ptr, cast(ulong)msg.length)) {
|
||
throw new Exception("Can't update HMAC.");
|
||
}
|
||
if(0 == HMAC_Final(&ctx, cast(ubyte*)sign.ptr, &signLen)) {
|
||
throw new Exception("Can't finalize HMAC.");
|
||
}
|
||
}
|
||
|
||
|
||
version(HUNT_JWT_DEBUG) {
|
||
trace("msg: ", msg);
|
||
trace("key: ", key);
|
||
trace("algo: ", algo);
|
||
}
|
||
|
||
switch(algo) {
|
||
case JwtAlgorithm.NONE: {
|
||
break;
|
||
}
|
||
case JwtAlgorithm.HS256: {
|
||
sign_hs(EVP_sha256(), SHA256_DIGEST_LENGTH);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.HS384: {
|
||
sign_hs(EVP_sha384(), SHA384_DIGEST_LENGTH);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.HS512: {
|
||
sign_hs(EVP_sha512(), SHA512_DIGEST_LENGTH);
|
||
break;
|
||
}
|
||
|
||
/* RSA */
|
||
case JwtAlgorithm.RS256: {
|
||
const(EVP_MD) *alg = EVP_sha256();
|
||
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.RS384: {
|
||
const(EVP_MD) *alg = EVP_sha384();
|
||
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.RS512: {
|
||
const(EVP_MD) *alg = EVP_sha512();
|
||
sign = signShaPem(alg, EVP_PKEY_RSA, key, msg);
|
||
break;
|
||
}
|
||
|
||
/* ECC */
|
||
case JwtAlgorithm.ES256: {
|
||
const(EVP_MD) *alg = EVP_sha256();
|
||
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.ES384: {
|
||
const(EVP_MD) *alg = EVP_sha384();
|
||
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||
break;
|
||
}
|
||
case JwtAlgorithm.ES512: {
|
||
const(EVP_MD) *alg = EVP_sha512();
|
||
sign = signShaPem(alg, EVP_PKEY_EC, key, msg);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
throw new SignException("Wrong algorithm: " ~ to!string(algo));
|
||
}
|
||
|
||
return cast(string)sign;
|
||
}
|
||
|
||
// Ported from https://github.com/benmcollins/libjwt/blob/master/libjwt/jwt-openssl.c
|
||
private 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.");
|
||
warning("Can't load the private key.");
|
||
return null;
|
||
}
|
||
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_PrivateKey(bufkey, null, null, null);
|
||
if (pkey is null) {
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
scope(exit) EVP_PKEY_free(pkey);
|
||
|
||
int pkey_type = EVP_PKEY_id(pkey);
|
||
if (pkey_type != type){
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
|
||
if (mdctx is null){
|
||
warning("Out of memory");
|
||
return null;
|
||
}
|
||
scope(exit) EVP_MD_CTX_destroy(mdctx);
|
||
|
||
/* Initialize the DigestSign operation using alg */
|
||
if (EVP_DigestSignInit(mdctx, null, alg, null, pkey) != 1){
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
/* Call update with the message */
|
||
if (EVP_DigestSignUpdate(mdctx, cast(void*)msg.ptr, msg.length) != 1){
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
/* 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){
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
/* 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) {
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
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) {
|
||
warning("Out of memory");
|
||
return null;
|
||
}
|
||
|
||
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(c_long)slen);
|
||
if (ec_sig is null) {
|
||
warning("Can't decode ECDSA signature.");
|
||
return null;
|
||
}
|
||
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)){
|
||
warning("Invalid argument");
|
||
return null;
|
||
}
|
||
|
||
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) {
|
||
RSA* rsa_public = RSA_new();
|
||
scope(exit) RSA_free(rsa_public);
|
||
|
||
BIO* bpo = BIO_new_mem_buf(cast(char*)key.ptr, -1);
|
||
if(bpo is null)
|
||
throw new Exception("Can't load key to the BIO.");
|
||
scope(exit) BIO_free(bpo);
|
||
|
||
RSA* rsa = PEM_read_bio_RSA_PUBKEY(bpo, &rsa_public, null, null);
|
||
if(rsa is null) {
|
||
throw new Exception("Can't create RSA key.");
|
||
}
|
||
|
||
// ubyte[] sign = cast(ubyte[])signature;
|
||
int ret = RSA_verify(type, hash, signLen, decodedSign.ptr, len, rsa_public);
|
||
return ret == 1;
|
||
}
|
||
|
||
|
||
switch(algo) {
|
||
case JwtAlgorithm.NONE: {
|
||
return key.length == 0;
|
||
}
|
||
case JwtAlgorithm.HS256:
|
||
case JwtAlgorithm.HS384:
|
||
case JwtAlgorithm.HS512: {
|
||
return decodedSign == cast(ubyte[])sign(head, key, algo);
|
||
}
|
||
|
||
/* 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: {
|
||
const(EVP_MD) *alg = EVP_sha256();
|
||
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||
|
||
// 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: {
|
||
const(EVP_MD) *alg = EVP_sha384();
|
||
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||
}
|
||
case JwtAlgorithm.ES512: {
|
||
const(EVP_MD) *alg = EVP_sha512();
|
||
return verifyShaPem(alg, EVP_PKEY_EC, head, decodedSign, key);
|
||
}
|
||
|
||
default:
|
||
throw new VerifyException("Wrong algorithm.");
|
||
}
|
||
}
|
||
|
||
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;
|
||
|
||
} |