LoginSignup
1
0

More than 1 year has passed since last update.

mbed TLSでJWTを利用する

Last updated at Posted at 2021-07-18

記事の概要

mbet TLSを用いてJWT認証を利用します。

サンプルプログラムは以下に置いてあります。
https://github.com/matsuikosuke/mbedTLSTest

JWTとは何か

JWTについては以下の解説が詳しいです。
JSON Web Token(JWT)の紹介とYahoo! JAPANにおけるJWTの活用

JWTはJSON Web Tokenの略です。
JWTはヘッダーとペイロードと署名の3つから構成されています。

以下のサイトで実際にJWTを作成しながら、具体的な中身を見ていきます。
https://jwt.io/

ヘッダ

ヘッダには"typ"と"alg"を記述します。
"typ"には"JWT"を指定します。
"alg"は署名アルゴリズムを意味し、 "HS256"(SHA-256)や"RS256"(SHA-256)を指定します。
今回はRS256を指定します。

{
  "alg": "RS256",
  "typ": "JWT"
}

これをBase64urlエンコードすると、以下のようなヘッダになります。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

ペイロード

{
  "sub": "Hello,World!",
  "name": "Kosuke Matsui",
  "iat": 875674839284
}

これをBase64urlエンコードすると、以下のようなペイロードになります。

eyJzdWIiOiJIZWxsbyxXb3JsZCEiLCJuYW1lIjoiS29zdWtlIE1hdHN1aSIsImlhdCI6ODc1Njc0ODM5Mjg0fQ

署名

署名はヘッダーとペイロードを秘密鍵で暗号化し、Base64urlエンコードしたものです。
サイトの署名欄にPublic Key(公開鍵)とPrivate Key(秘密鍵)を代入すると以下を得ます。

N_zzSrKu4emL6OzpAQ7nBsR6XkV3I2TEkkrqM4Ldm7m42a78HEXsHx5X7AQQlApSPzCuIL3elXfrr4Tl2IRlstlsU6Im79V7hpWigAWnT4HyINEX74LKGeGHwve1iJpXKvTYIsjwTaKWwVGmHG2CPqVz_gVlNJJoe9PyMGzLnzZcUIYj20ATaE1NgdSoZEc9xA4T7EQWdTS4WRtSffPTREPG1Wgf0LVQZueW2P1kYsf9-_ItJTJk2GRqnzqaob-5hRANrCqWcxEr-HKw4PIftnxiVN3WqT3NWG7qT6UKfsKVNfFJZJOWcmr0UJGIpHavuvjnd-5P_9AbYchkKCucMg

鍵の生成は以下のサイトでRSA鍵ペアを作成しました。
RSA鍵ペアの生成

RSA鍵ペア生成.png

-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn7wJ2SLBWm0S88DPOn2qGLlbHCZ9sfjlpLtjV5JshGCUW/T8a6T/lGhud3W8RXRtmdLGeRJCLN3nl9TJhWXNb0uMtlApvMNfe/H6dOvScOWMZ+sRNse1TIZO7pSt6LS5DCx7trHwZLxYLE7nSLsA3ljOT7uIl5zV07raVpbw2GApnZJdnYcmTjnw+iHScEj+vO0MTCddyo7Mw8T0obMLe4GJtv1YlmS69krsRWABVfls9ZGxTvrk2SpAXHBcFwKfLiQ2q2jghYfYlwjKJMx78eUn+ODPfqB9qgtw5A8to3VpxijG2ifbGU4SQnTHe+vreLMSCfF3GJlYWwQx53NuxAgMBAAECggEAALUDpUAv4QAkQOVnk1Pd8+aSKNspkmdt4Ffj4CwDvoimjDe7iVgxhYNDPYFsdSFs89Tf17Fd4VO8+Jn1MryDZ8b2Cj/PtsTq0uII8xwyYgat1PoBRXsksmSiE+Mhih9Sd5l3VQnbt6+mkX+0fG0ghbtlHaVJdbO6zCtpXRB/hvzGLA5A0TI6GE166/9LHcq6+PhaaUIFX8vpiUuLt/XiOqOglaIfR/VSIPShlqzMEpYSpfyDatanVT0G5m5gu4/3LfrSzla55Q9VRu2YjgEIeVkImvBn7PMhYjyHUO7AaExZEAWWtbcCA1oIl3DSZjhjGK62ao2Iw5vtjJDyOmUXLwKBgQDkODKC7DGqP3d+uP116HGp8TKRDeV3MrjR7uwgFo9XkLzYc5eJTFHZHYJ4WNAQNxbDJnoTiobbLcxVv2Jw8nB34gDO3Am2+mdRYVlcdAbkyeJV+95Fb6OvZ/ZQKom9cnjNNu3TP1s6lrSaH+nHnfDoeRQiznbpfLxv/MKnJYiivwKBgQC8YC0y1XIHpOL78eMF8PKZAm9Ro9h9SA2D7rTaUchrzKYJjhcT2RS+j/Kv6HdeuqSAyjptLcI/i1QyfvJy2TK9ST1MayhRhX+dJ5Zp6QZDOX0s1O8ccTPNhjZf7DD24LcX8Plj56W8a100Aw1YsPoBvAP5vsmhnLsH6ta/AInNjwKBgQDat2DxSBtw5dJHiFKgpVwJWjbz/TVkvx+RUkDJn9VQPk49wsn/szzdrwJTBAqi/6i47i8geyoy8/lYVEqrpC1VNys/FHHbAq/xtjJGAIx6x2A6t1+VfCCLAj91JM9M2GAdi/7w7U8pHzWGX/9UZ3Fx2lgJ44Tiz8BoMvz542bHUQKBgCXSzeCCGRZN1kGuTN45hgyQ/5cN5f8pw4T1Hh0kBLEYc1JB1IJouRCWSK1naOh9Y2B0uoyHfpRRvoMxGC9VSynUldlNPtg3jOsaMsprPpWq6mZhDu3QEQ24YByciSxY7IqDGSAlOBMAH8O8xSZEyr4kaUvxMf0gQjGRxrKtQf4jAoGBAK7Xo8uDmBqHCnNjGUlHqtf4ipwBRI2aH/izHHL2JUkHMvWac0e6VdQxdLPsqulcaoHzJRrX/gGYJJ3YFJ4VjevRMirQ9ZKnT3mNJWU6XfU7TpH3Up8JQ0bQH/B8AV1Ogl4RNOVeZQwsSAf/Ba+VCHNhfFeMKqz2jS6+VRHEpvyd-----END PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+8CdkiwVptEvPAzzp9qhi5WxwmfbH45aS7Y1eSbIRglFv0/Guk/5Robnd1vEV0bZnSxnkSQizd55fUyYVlzW9LjLZQKbzDX3vx+nTr0nDljGfrETbHtUyGTu6Urei0uQwse7ax8GS8WCxO50i7AN5Yzk+7iJec1dO62laW8NhgKZ2SXZ2HJk458Poh0nBI/rztDEwnXcqOzMPE9KGzC3uBibb9WJZkuvZK7EVgAVX5bPWRsU765NkqQFxwXBcCny4kNqto4IWH2JcIyiTMe/HlJ/jgz36gfaoLcOQPLaN1acYoxton2xlOEkJ0x3vr63izEgnxdxiZWFsEMedzbsQIDAQAB-----END PUBLIC KEY-----

Base64url

Base64urlはBase64と似ていますが、少し違います。
Base64の+は、Base64urlでは-になります。
Base64の/は、Base64urlでは_になります。
Base64の=は、Base64urlでは削除します。

JWT認証について

以上のヘッダーとペイロードと署名を.で連結したものがJWTです。

例えばこれを受信したとします。
そして受信側はPublic Key(公開鍵)を知っているとします。

受信者はヘッダーとペイロードをPublic Key(公開鍵)を用いて暗号化し、それをBase64urlデコードします。
それが署名と一致すれば、正しいデータであることが分かります。

Base64

受信したJWTの解読にはBase64urlデコードが必要になります。

以下の関数ではBase64でのエンコードとデコードを試しています。
実際にこれらの関数をJWTに適用する場合は、前もってBase64urlとBase64を変換する処理が必要になります。

static const unsigned char base64_test_dec[64] =
{
    0x24, 0x48, 0x6E, 0x56, 0x87, 0x62, 0x5A, 0xBD,
    0xBF, 0x17, 0xD9, 0xA2, 0xC4, 0x17, 0x1A, 0x01,
    0x94, 0xED, 0x8F, 0x1E, 0x11, 0xB3, 0xD7, 0x09,
    0x0C, 0xB6, 0xE9, 0x10, 0x6F, 0x22, 0xEE, 0x13,
    0xCA, 0xB3, 0x07, 0x05, 0x76, 0xC9, 0xFA, 0x31,
    0x6C, 0x08, 0x34, 0xFF, 0x8D, 0xC2, 0x6C, 0x38,
    0x00, 0x43, 0xE9, 0x54, 0x97, 0xAF, 0x50, 0x4B,
    0xD1, 0x41, 0xBA, 0x95, 0x31, 0x5A, 0x0B, 0x97
};

static const unsigned char base64_test_enc[] =
    "JEhuVodiWr2/F9mixBcaAZTtjx4Rs9cJDLbpEG8i7hPK"
    "swcFdsn6MWwINP+Nwmw4AEPpVJevUEvRQbqVMVoLlw==";

int mbedtls_base64_self_test(void)
{
    int ret = 1;
    size_t len;
    const unsigned char *src;
    unsigned char buffer[200];

    src = base64_test_dec;
    if( mbedtls_base64_encode( buffer, sizeof( buffer ), &len, src, 64 ) != 0 ||
         memcmp( base64_test_enc, buffer, 88 ) != 0 )
    {        
        printf("BASE64 encode error\r\n");
        return( 1 );
    }    
    printf("BASE64 encode success\r\n");

    src = base64_test_enc;
    if( mbedtls_base64_decode( buffer, sizeof( buffer ), &len, src, 88 ) != 0 ||
         memcmp( base64_test_dec, buffer, 64 ) != 0 )
    {
        printf("BASE64 decode error\r\n");
        return( 1 );
    }
    printf("BASE64 decode success\r\n");

    return( 0 );
}

JWT認証

以下の関数mbedtls_jwt_self_testにおいては、JWTデータをデータ部分(ヘッダとペイロード)と署名に分割し、Base64urlをBase64に変換した上で、署名の検証をrsa_jwt_verification_testにて行い、ペイロードをBase64デコードして属性データを取得しています。

// 256 byte
#define RSA1_TEST_PRIVATE_KEY \
"-----BEGIN PRIVATE KEY-----\r\n"  \
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn7wJ2SLBWm0S8\r\n"  \
"8DPOn2qGLlbHCZ9sfjlpLtjV5JshGCUW/T8a6T/lGhud3W8RXRtmdLGeRJCLN3nl\r\n"  \
"9TJhWXNb0uMtlApvMNfe/H6dOvScOWMZ+sRNse1TIZO7pSt6LS5DCx7trHwZLxYL\r\n"  \
"E7nSLsA3ljOT7uIl5zV07raVpbw2GApnZJdnYcmTjnw+iHScEj+vO0MTCddyo7Mw\r\n"  \
"8T0obMLe4GJtv1YlmS69krsRWABVfls9ZGxTvrk2SpAXHBcFwKfLiQ2q2jghYfYl\r\n"  \
"wjKJMx78eUn+ODPfqB9qgtw5A8to3VpxijG2ifbGU4SQnTHe+vreLMSCfF3GJlYW\r\n"  \
"wQx53NuxAgMBAAECggEAALUDpUAv4QAkQOVnk1Pd8+aSKNspkmdt4Ffj4CwDvoim\r\n"  \
"jDe7iVgxhYNDPYFsdSFs89Tf17Fd4VO8+Jn1MryDZ8b2Cj/PtsTq0uII8xwyYgat\r\n"  \
"1PoBRXsksmSiE+Mhih9Sd5l3VQnbt6+mkX+0fG0ghbtlHaVJdbO6zCtpXRB/hvzG\r\n"  \
"LA5A0TI6GE166/9LHcq6+PhaaUIFX8vpiUuLt/XiOqOglaIfR/VSIPShlqzMEpYS\r\n"  \
"pfyDatanVT0G5m5gu4/3LfrSzla55Q9VRu2YjgEIeVkImvBn7PMhYjyHUO7AaExZ\r\n"  \
"EAWWtbcCA1oIl3DSZjhjGK62ao2Iw5vtjJDyOmUXLwKBgQDkODKC7DGqP3d+uP11\r\n"  \
"6HGp8TKRDeV3MrjR7uwgFo9XkLzYc5eJTFHZHYJ4WNAQNxbDJnoTiobbLcxVv2Jw\r\n"  \
"8nB34gDO3Am2+mdRYVlcdAbkyeJV+95Fb6OvZ/ZQKom9cnjNNu3TP1s6lrSaH+nH\r\n"  \
"nfDoeRQiznbpfLxv/MKnJYiivwKBgQC8YC0y1XIHpOL78eMF8PKZAm9Ro9h9SA2D\r\n"  \
"7rTaUchrzKYJjhcT2RS+j/Kv6HdeuqSAyjptLcI/i1QyfvJy2TK9ST1MayhRhX+d\r\n"  \
"J5Zp6QZDOX0s1O8ccTPNhjZf7DD24LcX8Plj56W8a100Aw1YsPoBvAP5vsmhnLsH\r\n"  \
"6ta/AInNjwKBgQDat2DxSBtw5dJHiFKgpVwJWjbz/TVkvx+RUkDJn9VQPk49wsn/\r\n"  \
"szzdrwJTBAqi/6i47i8geyoy8/lYVEqrpC1VNys/FHHbAq/xtjJGAIx6x2A6t1+V\r\n"  \
"fCCLAj91JM9M2GAdi/7w7U8pHzWGX/9UZ3Fx2lgJ44Tiz8BoMvz542bHUQKBgCXS\r\n"  \
"zeCCGRZN1kGuTN45hgyQ/5cN5f8pw4T1Hh0kBLEYc1JB1IJouRCWSK1naOh9Y2B0\r\n"  \
"uoyHfpRRvoMxGC9VSynUldlNPtg3jOsaMsprPpWq6mZhDu3QEQ24YByciSxY7IqD\r\n"  \
"GSAlOBMAH8O8xSZEyr4kaUvxMf0gQjGRxrKtQf4jAoGBAK7Xo8uDmBqHCnNjGUlH\r\n"  \
"qtf4ipwBRI2aH/izHHL2JUkHMvWac0e6VdQxdLPsqulcaoHzJRrX/gGYJJ3YFJ4V\r\n"  \
"jevRMirQ9ZKnT3mNJWU6XfU7TpH3Up8JQ0bQH/B8AV1Ogl4RNOVeZQwsSAf/Ba+V\r\n"  \
"CHNhfFeMKqz2jS6+VRHEpvyd\r\n"  \
-"----END PRIVATE KEY-----\r\n"

// 256 byte
#define RSA1_TEST_PUBLIC_KEY \
"-----BEGIN PUBLIC KEY-----\r\n"  \
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+8CdkiwVptEvPAzzp9q\r\n"  \
"hi5WxwmfbH45aS7Y1eSbIRglFv0/Guk/5Robnd1vEV0bZnSxnkSQizd55fUyYVlz\r\n"  \
"W9LjLZQKbzDX3vx+nTr0nDljGfrETbHtUyGTu6Urei0uQwse7ax8GS8WCxO50i7A\r\n"  \
"N5Yzk+7iJec1dO62laW8NhgKZ2SXZ2HJk458Poh0nBI/rztDEwnXcqOzMPE9KGzC\r\n"  \
"3uBibb9WJZkuvZK7EVgAVX5bPWRsU765NkqQFxwXBcCny4kNqto4IWH2JcIyiTMe\r\n"  \
"/HlJ/jgz36gfaoLcOQPLaN1acYoxton2xlOEkJ0x3vr63izEgnxdxiZWFsEMedzb\r\n"  \
"sQIDAQAB\r\n"  \
"-----END PUBLIC KEY-----\r\n"

// 466 chars
static unsigned char jwt_sign_sample[] =    
    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJIZWxsbyxXb3JsZCEiLCJuYW1lIjoiS29zdWtlIE1hdHN1aSIsImlhdCI6ODc1Njc0ODM5Mjg0fQ.N_zzSrKu4emL6OzpAQ7nBsR6XkV3I2TEkkrqM4Ldm7m42a78HEXsHx5X7AQQlApSPzCuIL3elXfrr4Tl2IRlstlsU6Im79V7hpWigAWnT4HyINEX74LKGeGHwve1iJpXKvTYIsjwTaKWwVGmHG2CPqVz_gVlNJJoe9PyMGzLnzZcUIYj20ATaE1NgdSoZEc9xA4T7EQWdTS4WRtSffPTREPG1Wgf0LVQZueW2P1kYsf9-_ItJTJk2GRqnzqaob-5hRANrCqWcxEr-HKw4PIftnxiVN3WqT3NWG7qT6UKfsKVNfFJZJOWcmr0UJGIpHavuvjnd-5P_9AbYchkKCucMg";

static int rsa_jwt_verification_test(uint8_t *data, uint16_t data_len, uint8_t *signature, uint16_t signature_len)
{
    int ret = 1;

    mbedtls_pk_context p_pk;
    mbedtls_rsa_context *p_rsa;
    uint8_t hash[32] = {0};

    // hash SHA-256
    ret =  mbedtls_sha256_ret( data, data_len, hash, 0 );
    if( 0 != ret)
        goto exit;

    // public key
    mbedtls_rsa_init( p_rsa, MBEDTLS_RSA_PKCS_V15, 0 );
    mbedtls_pk_init(&p_pk);

    ret = mbedtls_pk_parse_public_key(&p_pk, RSA1_TEST_PUBLIC_KEY, strlen(RSA1_TEST_PUBLIC_KEY) + 1);
    if (ret != 0)
        goto exit;
 
    p_rsa = mbedtls_pk_rsa( p_pk );

    // 64Base decode signature
    size_t len;
    unsigned char jwt_decoded_buffer[256] = {0};

    ret = mbedtls_base64_decode( jwt_decoded_buffer, sizeof( jwt_decoded_buffer ), &len, signature, signature_len );
    if (ret != 0)
        goto exit;
 
    // verification
    ret = mbedtls_rsa_rsassa_pkcs1_v15_verify(p_rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, 0, hash, jwt_decoded_buffer);
     if (ret != 0)
        goto exit;

    // free context
    mbedtls_rsa_free(p_rsa);
    mbedtls_pk_free(&p_pk);

    printf("JWT validation success\r\n");
    return ret;

exit:
    // free context
    mbedtls_rsa_free(p_rsa);
    mbedtls_pk_free(&p_pk);

    printf("JWT validation error\r\n");
    return ret;
}

int mbedtls_jwt_self_test(void)
{
    int ret = 1; 
    uint16_t data_len;
    uint16_t payload_len;
    uint16_t signature_len;
    uint16_t token_len;

    size_t len;
    uint8_t dot_count = 0;
    uint16_t payload_start_index = 0;
    uint16_t signatue_start_index = 0;

    sys_timer_count[TEST_TIMER] = 0;
    uint8_t expand_jwt_sign_test[500] = {0};
    for(int i=0; i<466; i++)
    {
        expand_jwt_sign_test[i] = jwt_sign_sample[i];
    }

    for(int i=0; i<JWT_SIZE; i++)
    {
        if(0x00 == expand_jwt_sign_test[i])
        {
            token_len = i;
            break;
        }
    }

        
    for(int i=0; i<token_len; i++)
    {
        if(*(expand_jwt_sign_test+i) == '-')
        {
            *(expand_jwt_sign_test+i) = '+';
        }

        if(*(expand_jwt_sign_test+i) == '_')
        {
            *(expand_jwt_sign_test+i) = '/';
        }
    }   

    uint8_t jwt_buffer[200];
    
    // check data format 
    // 01234.6789.BCDEF(token_len = 16chars)
    // data_len = A(10)    -> sig_len = token_len - data_len - 1 = 16 - 10 - 1 = 5chars
    // pay_start_index = 6 -> pay_len = 16 - pay_start_index - sig_len - 1 = 16 - 6 - 5 - 1 = 4chars
    for(int i=0; i<token_len; i++)
    {
        if(*(expand_jwt_sign_test+i) == '.')
        {
            dot_count += 1;
            if(1 == dot_count)
            {
                payload_start_index = i+1;                
                dot_count = 0;
                break;
            }
        }
    }    
    
    for(int i=0; i<token_len; i++)
    {
        if(*(expand_jwt_sign_test+i) == '.')
        {
            dot_count += 1;

            if(2 == dot_count)
            {
                signatue_start_index = i+1;
                data_len = i;
                signature_len = token_len - data_len -1; 
                break;
            }
        }
    }
    payload_len = token_len - payload_start_index - signature_len -1;

    // check data format error
    if(2 != dot_count)
        return 0x0FFF;

    // separate JWT into data and signature
    uint8_t data[data_len];
    uint8_t payload[payload_len];
    uint8_t signature[signature_len];

    for(int i=0; i<data_len; i++)
    {
        *(data+i) = *(expand_jwt_sign_test+i);
    }

    for(int i=0; i<payload_len; i++)
    {
        *(payload+i) = *(expand_jwt_sign_test+payload_start_index+i);
    }    

    for(int i=0; i<signature_len; i++)
    {
        *(signature+i) = *(expand_jwt_sign_test+signatue_start_index+i);
    }    

    // signature must be a multiple of 4
    uint16_t old_signature_len = signature_len;
    uint16_t signature_len_mod = signature_len%4;
    if(0 != signature_len_mod)
    {
        signature_len = signature_len + (4 - signature_len_mod);

        for(int i=old_signature_len; i<signature_len; i++)
        {
            *(signature+i) = '=';
        }
    }

    // verification
    ret = rsa_jwt_verification_test(data, data_len, signature, signature_len);

    // decode Base64
    mbedtls_base64_decode(jwt_buffer, sizeof( jwt_buffer ), &len, payload, payload_len);
    printf("JWT=");
     for (uint16_t i=0; i<len; i++) {
         printf("%c", jwt_buffer[i]);
     }
     printf("\r\n");

    printf("Execution time of rsa_jwt_verification_test %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;


    return ret;
}

署名の検証

rsa_jwt_verification_testにおいては、まずデータをmbedtls_sha256_retによりSHA256で32バイトのデータにハッシュ化しています。

次にPublic Keyを取り込み、署名(事前にBase64urlをBase64に変換済み)をBase64デコードします。

そしてmbedtls_rsa_rsassa_pkcs1_v15_verify関数にハッシュ化したデータとBase64デコードした署名を渡すと、署名の検証を行ってくれます。

参照

mbed TLSでAES-GCMを利用する
mbed TLSでRSAを利用する「RSA-OAEPの暗号化と復号」
mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」
mbed TLSおよびOberonでECDHを利用する
mbed TLSでHKDFを利用する
mbed TLSでJWTを利用する

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0