JWTは作成できたので、今度は検証する方法を実装してみます。以前の記事のJavaコードには署名(sign)しか実装がありませんので、検証(verify)を追加しました。
import java.math.BigInteger;
import java.util.Base64;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.X509EncodedKeySpec;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
public class SHA256withRSA {
/**
* RSASSA-PKCS1-v1_5 with SHA-256によるデジタル署名の生成
*
* @param header BASE64エンコード済みのJWTヘッダー
* @param payload BASE64エンコード済みのJWTペイロード
* @param key PKCS#1形式でのRSA秘密鍵(BEGIN/END行なし、改行なし)
* @return デジタル署名 - BASE64エンコード済み
*/
public static String sign(String header, String payload, String key)
throws Exception
{
/* header and payload are both encoded in base64 */
byte[] data = new String(header + "." + payload).getBytes("UTF-8");
/* get PrivateKey instance from PKCS#1 */
byte[] pkdata = Base64.getDecoder().decode(key);
DerInputStream derReader = new DerInputStream(pkdata);
DerValue[] seq = derReader.getSequence(0);
// skip version seq[0];
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec =
new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
/* creating the object of Signature then sign */
Signature sr = Signature.getInstance("SHA256withRSA");
sr.initSign(privateKey);
sr.update(data);
byte[] bytes = sr.sign();
/* return signature in Base64 */
return Base64.getEncoder().encodeToString(bytes);
}
/**
* RSASSA-PKCS1-v1_5 with SHA-256によるデジタル署名の検証
*
* @param header BASE64エンコード済みのJWTヘッダー
* @param payload BASE64エンコード済みのJWTペイロード
* @param sig BASE64エンコードされた電子署名
* @param key RSA公開鍵(BEGIN/END行なし、改行なし)
* @return 検証結果
*/
public static boolean verify(String header, String payload, String sig, String key)
throws Exception
{
/* header and payload are both encoded in base64 */
byte[] data = new String(header + "." + payload).getBytes("UTF-8");
/* get signature in binary representation. */
byte[] dsig = Base64.getDecoder().decode(sig);
/* get PublicKey instance from X.509 */
byte[] pkdata = Base64.getDecoder().decode(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pkdata);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
/* creating the object of Signature then verify */
Signature sr = Signature.getInstance("SHA256withRSA");
sr.initVerify(publicKey);
sr.update(data);
return sr.verify(dsig);
}
}
データベースにロードするのは、前回とまったく同じloadjavaコマンドを使用します。
loadjava -force -verbose -resolve -u myworkspace/**********@localhost/service.world SHA256withRSA.java
追加したメソッドのラッパーとなるファンクションを定義します。
create or replace function sha256withrsa_verify ( header varchar2, payload varchar2, signature varchar2, public_key varchar2 ) return boolean as language java name 'SHA256withRSA.verify (java.lang.String, java.lang.String, java.lang.String, java.lang.String) return boolean'; /RSA公開鍵はこちらの記事で紹介したのと、同じ方法で生成します。まずはキー・ペアを生成し、
mkdir -p ~/.oci && openssl genrsa -out ~/.oci/poa_oci_api_key.pem 2048
それから公開鍵を取り出します。
openssl rsa -pubout -in ~/.oci/poa_oci_api_key.pem -out ~/.oci/poa_oci_api_key_public.pem
実際のデータの部分だけを一行にして取り出せるように、こちらの記事で紹介したスクリプトも以下に紹介しておきます。
#!/bin/sh
while read l
do
test ${l:0:1} != "-" && /bin/echo -n $l
done < ~/.oci/poa_oci_api_key_public.pem
echo
これで、データベース側の準備はできたので、JWTの検証処理を行ってみます。検証には以下のPL/SQLコードを使用しました。
set lines 1000
set serveroutput on
declare
l_public varchar2(32767) := 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMII***一部省略***dgaetswIDAQAB';
l_jwt varchar2(32767) := 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcWxwbHVzIiwic3ViIjoiVEVTVFVTRVIiLCJhdWQiOiJBUEVYIiwiaWF0***一部省略***hLzvCH7iy9OxZMXlZ9qN_doIqjV0Q';
l_username varchar2(32);
l_header_json json_object_t;
l_header_len number;
l_header_base64 varchar2(400);
l_payload_len number;
l_payload_json json_object_t;
l_payload_base64 varchar2(800);
l_hmac varchar2(1000);
-- Base64のデコード
function from_base64(t in varchar2) return varchar2 is
begin
return utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
-- Base64のエンコード
function to_base64(t in varchar2) return varchar2 is
l_base64 varchar2(32767);
begin
l_base64 := utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(t)));
l_base64 := replace(l_base64, chr(13)||chr(10), '');
return l_base64;
end to_base64;
begin
/* トークンの内容を印刷 */
l_header_len := instr(l_jwt, '.', 1);
l_header_base64 := substr(l_jwt, 1, l_header_len - 1);
l_header_json := json_object_t.parse(from_base64(l_header_base64));
dbms_output.put_line('Header = ' || l_header_json.to_string);
l_jwt := substr(l_jwt, l_header_len + 1);
l_payload_len := instr(l_jwt, '.', 1);
l_payload_base64 := substr(l_jwt, 1, l_payload_len - 1);
l_payload_json := json_object_t.parse(from_base64(l_payload_base64));
dbms_output.put_line('Payload = ' || l_payload_json.to_string);
l_username := l_payload_json.get_string('sub');
dbms_output.put_line('Username = ' || l_username);
l_hmac := substr(l_jwt, l_payload_len + 1);
dbms_output.put_line('Signature = ' || l_hmac);
/* シグネチャの検証をする。 */
l_hmac := trim(translate(l_hmac, '-_ ', '+/='));
if SHA256withRSA_verify(l_header_base64, l_payload_base64, l_hmac, l_public) then
dbms_output.put_line('Signature verified successfully.');
else
dbms_output.put_line('Signature verify failed.');
end if;
end;
/
exit;
l_publicには、RSA公開鍵を一行で指定します。l_jwtにはJWTをこれも一行で指定します。実行した結果は以下のようになります。
Header = {"alg":"RS256","typ":"JWT"}
Payload = {"iss":"sqlplus","sub":"TESTUSER","aud":"APEX","iat":1584430294,"exp":1584430304}
Username = TESTUSER
Signature = AJE1R2m_-8OTZLocdNaOfa5UF3EirLIsYkpD***一部省略***t6mSYFPshK3km_X9jNXQfHHP9As-2HnnrOSFMN9A
Signature verified successfully.
これで、Oracle APEXにて(正確にはOracle Databaseにて)RS256を使ったJWTの作成と検証の両方ができるようになっています。
完