Oracle ApExの2FA

Oracle Application Expressでの2FAの実装に注目してください。 2番目の要因として、認証システムアプリケーションが電話にインストールされたGoogleのソリューションが使用されます。







この実装はベストプラクティスであるとは主張していません。この記事の目的は、この決定を共有し、使用するコードのセキュリティを改善するための推奨事項を取得することです。



このタスクを開始するとすぐに、組み込みプロシージャapex_authentication.loginが入力として2つのパラメータのみを受け入れることがわかりました。他のWebアプリケーションに影響を及ぼす可能性があるため、このプロシージャを自分のニーズに合わせて変更する可能性も考慮しませんでした。 この実装では、ログインとパスワードのペアをチェックする組み込み認証プロシージャを呼び出す前に、ログインとワンタイムパスワードのペアをチェックするPreauth自己記述プロシージャが呼び出されます。



初期データ:Webアプリケーションでの認証は、対応するスキーム(各Webアプリケーションには独自のスキームがあります)のユーザーテーブルに保存されているログインとパスワードのペアのチェックに基づいています。 このテーブルでは、2番目の要素の秘密キーが保存される列を追加する必要があります。 セキュリティを強化するために、ユーザーIDとキーが暗号化されるアクセス権が制限された別のテーブルを作成できます。



キーは、大文字の16文字のbase32文字列です。 Google Authenticatorアプリケーションに秘密鍵を保存するために、たとえばこのソリューションを使用してQRコードを生成できます。



ApExコンストラクターの[ログイン]ページには、通常、ユーザー名とパスワードの2つのフィールドしかありません。 たとえばP101_TOTPという名前のフィールドを追加する必要があります。ここで、ユーザーはGoogle認証システムアプリケーションから6桁を入力します。 同じページで、プロセスは、Preauthプロシージャを開始する送信後イベントによってトリガーされます。 次のようになります。







Preauthプロシージャは、ワンタイムパスワードと同じ秘密キーを使用してサーバーで生成されたコードを比較します。



手続きコード
create or replace PROCEDURE "PREAUTH" (p_username IN VARCHAR2 ,p_totp IN VARCHAR2) AS l_value NUMBER; usersToken VARCHAR2(20); tempToken VARCHAR2(20); l_current_sid number; BEGIN SELECT token INTO usersToken FROM users WHERE upper(users.login) = upper(p_username); IF usersToken != '0' THEN BEGIN tempToken := TOTP(cSecret => usersToken); SELECT 1 INTO l_value FROM users WHERE 1 = 1 AND upper(users.login) = upper(p_username) AND USERS.IS_LOCKED = 0 AND p_totp = tempToken; EXCEPTION WHEN no_data_found OR too_many_rows THEN l_value := 0; WHEN OTHERS THEN l_value := 0; END; END IF; l_current_sid := apex_custom_auth.get_session_id_from_cookie; IF l_value = 0 THEN raise_application_error (-20000,'Please, try again'); apex_util.set_authentication_result(4); APEX_AUTHENTICATION.LOGOUT( p_session_id => l_current_sid, p_app_id => v('APP_ID')); END IF; END PREAUTH;
      
      





ご覧のとおり、このプロシージャはTOTPという関数を参照しています 。関数の作成者はここで公開しています



TOTP機能コード
 create or replace FUNCTION "TOTP" (cSecret IN VARCHAR2) RETURN VARCHAR IS cBASE32 CONSTANT VARCHAR2(32) := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; szBits VARCHAR2(500) := ''; szTmp VARCHAR2(500) := ''; szTmp2 VARCHAR2(500) := ''; nPos NUMBER; nEpoch NUMBER(38); szEpoch VARCHAR2(16); rHMAC RAW(100); nOffSet NUMBER; nPart1 NUMBER; nPart2 NUMBER := 2147483647; nPart3 NUMBER; l_obfuscated_password users.pass%TYPE; calculatedCode VARCHAR2(6); FUNCTION to_binary(inNum NUMBER) RETURN VARCHAR2 IS szBin VARCHAR2(8); nRem NUMBER := inNum; BEGIN IF inNum = 0 THEN RETURN '0'; END IF; WHILE nRem > 0 LOOP szBin := MOD(nRem, 2) || szBin; nRem := TRUNC(nRem / 2 ); END LOOP; RETURN szBin; END to_binary; BEGIN FOR c IN 1..LENGTH(cSecret) LOOP nPos := INSTR( cBASE32, SUBSTR(cSecret, c, 1))-1; szBits := szBits || LPAD( to_binary(nPos), 5, '0'); END LOOP; nPos := 1; WHILE nPos < LENGTH(szBits) LOOP SELECT LTRIM(TO_CHAR(BIN_TO_NUM( TO_NUMBER(SUBSTR(szBits, nPos, 1)), TO_NUMBER(SUBSTR(szBits, nPos+1, 1)), TO_NUMBER(SUBSTR(szBits, nPos+2, 1)), TO_NUMBER(SUBSTR(szBits, nPos+3, 1)) ), 'x')) INTO szTmp2 FROM dual; szTmp := szTmp || szTmp2; nPos := nPos + 4; END LOOP; SELECT EXTRACT(DAY FROM (CURRENT_TIMESTAMP-TIMESTAMP '1970-01-01 00:00:00 +00:00'))*86400+ EXTRACT(HOUR FROM (CURRENT_TIMESTAMP-TIMESTAMP '1970-01-01 00:00:00 +00:00'))*3600+ EXTRACT(MINUTE FROM (CURRENT_TIMESTAMP-TIMESTAMP '1970-01-01 00:00:00 +00:00'))*60+ EXTRACT(SECOND FROM (CURRENT_TIMESTAMP-TIMESTAMP '1970-01-01 00:00:00 +00:00')) n INTO nEpoch FROM dual; SELECT LPAD(LTRIM(TO_CHAR( FLOOR(nEpoch/30), 'xxxxxxxxxxxxxxxx' )), 16, '0') INTO szEpoch FROM dual; rHMAC := DBMS_CRYPTO.MAC( src => hextoraw(szEpoch), typ => DBMS_CRYPTO.HMAC_SH1, key => hextoraw(szTmp) ); nOffSet := TO_NUMBER( SUBSTR( RAWTOHEX(rHMAC), -1, 1), 'x'); nPart1 := TO_NUMBER( SUBSTR( RAWTOHEX(rHMAC), nOffSet*2+1, 8), 'xxxxxxxx'); calculatedCode := SUBSTR(BITAND( nPart1, nPart2), -6, 6); RETURN calculatedCode; END "TOTP";
      
      







この関数は期待どおりに機能しましたが、生成されたワンタイムパスワードの上位に数字のゼロがあった場合を除きます-戻り値はNumber型であり、このゼロはOracleによって無視されました。 そのため、VARCHAR2型が返されるように変更しました。



重要:事前認証は、ログインを試みるユーザーの秘密鍵をチェックします。 これは、Webアプリケーションの2番目の要素の使用を「スムーズに」有効にするために、意図的に行われます。 すべてのユーザーが同時にではありません。 このチェックをオフにするには、次の行をコメントアウトします。



 IF usersToken != '0' THEN
      
      





ご清聴ありがとうございました。



All Articles