20220113のAndroidに関する記事は5件です。

OPPO Reno5 Aでマイナンバーカードを読めない問題を考える

はじめに 人気スマホのReno5 Aを買いました。 カメラ性能など素晴らしいプロダクトだと思っているのですが一点問題があります。 私のマイナンバーカードを読み取れないアプリがある 価格コムやTwitterによると このスマホで読み取れてる人、読み取れない人がそれぞれ居るようです。 また他のスマホでも同様の結果が出てることがあるようです。 私の環境においてGooglePlay で マイナンバーカード と検索して 上位に出てくるアプリでの検証結果は下記です。 アプリ名 制作 結果 / エラー番号 マイナポータル デジタル庁 ログインできる(読める) マイナポイント 総務省自治行政局地域情報化企画室 MKCZ405E 新型コロナワクチン接種証明書アプリ デジタル庁 62010 JPKI利用者ソフト 地方公共団体情報システム機構 E0055/00220009 IDリーダー OSSTech株式会社 読める! またマイナンバーカードを申請に利用するアプリなども同じようにエラーとなります。 ちなみに所有している別のスマホでは問題なく同じマイナンバーカードを読み取ります。 本題 検証のために先人たちを参考にAndroidアプリを開発をしてみました。 Androidで運転免許証のICを読み取る マイナンバーカード検証#1 - まえおき 今回初めてこの様なICカードの仕組みについて学びました。 ざっくり言うとカード内にディレクト構造があり、 cd → ls → cat の様なコマンドを送っていくことで内容を確認できるという認識をしました。 アプリ開発も ど素人です。以下はこの前提条件に沿ってお読み頂ければ幸いです。 検証結果 マイナンバーカードにある証番号は私の作成したアプリで確認できました。 どのようなアプリでもReno5 Aでマイナンバーカードの認識はできる様になると考えます。 OPPO RENO 5A 繰り返しになりますが、カメラ性能など このスマホは気に入っています。 スペック メーカーサイトにNFCの仕様詳細は見当たりませんでしたが、 マイナンバーカードに対応したNFCスマートフォン一覧 に取り上げられていますし、 読み取り位置の解説 で取り上げられているので一般的にはマイナンバーのICを読み取れるのだと思います。 マイナンバーカード マイナンバーカードに4つのディレクトリ(アプリケーション)があることは公表されています。 しかし運転免許証とは違い仕様書は公表されていないようです。 また、カード製造ベンダーが複数ありそれぞれOSが異なるという情報もあり、 カードによっては読み取り側と相性の悪い組み合わせがあるのかもしれません。 今回は先人たちの知恵を参照して基本的な機能で下記を確認できるかを検証します。 公的個人認証サービスによる電子証明書アプリケーション(JPKI-AP) 利用者証明用電子証明書 暗証番号 開発環境 Windows10 Android Studio Acrctic Fox| 2020.3.1 Patch 4 Reno5 A アプリ開発 前出のAndroidで運転免許証のICを読み取る をもとに開発していきます。 まずAndroidStudioでEmpty Activityを選択して、必要に応じてName、Package nameを入力しFinishを押します。 アプリにNFCの権限を追加するのでAndroidManifest.xmlに下記を追加します。 AndroidManifest.xml ... </application> <uses-permission android:name="android.permission.NFC" /> ... 以下のようにテキストにidを付加してレイアウトを調整します。 activity_main.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/activity_main_text_view" android:text="Hello JPKI!" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 次にMainActivity.kt に nfc が enableReaderMode であったら 下記3つのコマンドを送るというコードを記述します。 公的個人認証サービスによる電子証明書アプリケーション(JPKI-AP) 利用者証明用電子証明書暗証番号 暗証番号リトライ数確認 MainActivity.kt package com.example.myapplication import android.nfc.NfcAdapter import android.nfc.Tag import android.nfc.tech.IsoDep import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.TextView class MainActivity : AppCompatActivity() { private val textView by lazy { findViewById<TextView>(R.id.activity_main_text_view) } /** NFCを検出するのに使う */ private val nfcAdapter by lazy { NfcAdapter.getDefaultAdapter(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onResume() { super.onResume() nfcAdapter.enableReaderMode( this, { tag -> val isoDep = IsoDep.get(tag) isoDep.connect() // ここにコマンドを送信するコードを入れる val opcSelectCommand = byteArrayOf( 0x00.toByte(), 0xA4.toByte(), 0x04.toByte(), 0x0C.toByte(), 0x0A.toByte(), 0xD3.toByte(), 0x92.toByte(), 0xF0.toByte(), 0x00.toByte(), 0x26.toByte(), 0x01.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x01.toByte(), ) val opcSelectCommandResult = isoDep.transceive(opcSelectCommand) if (opcSelectCommandResult[0] == 0x90.toByte()) { val text = """ --- 公的個人認証AP 選択コマンド 成功 ${opcSelectCommandResult.toHexString()} """.trimIndent() textView.append(text) println(text) } val opcPinSelectCommand = byteArrayOf( 0x00.toByte(), 0xA4.toByte(), 0x02.toByte(), 0x0C.toByte(), 0x02.toByte(), 0x00.toByte(), 0x18.toByte(), ) val opcPinSelectCommandResult = isoDep.transceive(opcPinSelectCommand ) if (opcIefSelectCommandResult[0] == 0x90.toByte()) { val text = """ --- 認証用PIN 選択コマンド 成功 ${opcPinSelectCommandResult.toHexString()} """.trimIndent() textView.append(text) println(text) } // 暗証番号を照合する val pinCodeVerifyCommand = byteArrayOf( 0x00.toByte(), 0x20.toByte(), 0x00.toByte(), 0x80.toByte(), ) val pinCodeVerifyCommandResult = isoDep.transceive(pinCodeVerifyCommand) println(pinCode1VerifyCommandResult.toHexString()) if (pinCodeVerifyCommandResult[0] == 0x63.toByte()) { val text = """ --- 認証用PINの残りリトライ数 ${pinCodeVerifyCommandResult.toHexString()[5]} """.trimIndent() textView.append(text) println(text) } isoDep.close() }, NfcAdapter.FLAG_READER_NFC_B, null ) } override fun onPause() { super.onPause() nfcAdapter.disableReaderMode(this) } /** 16進数に変換するやつ */ fun ByteArray.toHexString() = this.joinToString { "%02x".format(it) } } このアプリをビルドし実施すると、今まで暗証番号を間違えていないかリトライ数が更新されていれば リトライ数3と応答があり、間違えていればそれに応じた返答があるはずです。 検証 以下はReno5 Aでリトライ数を確認した後に、マイナポータルでわざと暗証番号を間違えてリトライ数を減らし 再度リトライ数を確認したものです。 想定した結果が得られています。つきまして このアプリと私のReno5Aで 利用者認証用の暗証番号は確認できる、となります。 考察 本結果で分かったことはandroid.nfc.NfcAdapter を使って Reno 5Aでマイナンバーを読むことはできるということだけです。 既存のアプリも調整すればマイナンバーを読むことはできるのだと思います。 更なる検証として私のマイナンバーカードを他のReno5 Aでは読めるのか検証したいので Oppoさん
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android studio】ImageViewへ容量の大きい画像を読み込む方法

状況 実機で操作時にImageViewへ2MB以上のjpg画像を挿入しようとしたところ、アプリが落ちてしまった。 メモリ不足に陥っている可能性が考えられた。 対処方法 1. jpg画像をBitmapに変換、 2. Bitmapのサイズを調整。 3. ImageViewに張り付ける。 MainActivity.kt val bitmap = adjustImage(resources, R.drawable.imagefilename) menuImage.setImageBitmap(bitmap) //・・・3 private fun adjustImage(res: Resources, resID: Int): Bitmap { var bmp = BitmapFactory.decodeResource(res, resID) //・・・1 return createScaledBitmap(bmp, 320, 264, true) //・・・2 } 懸念事項 BitmapFactory.decodeResource(res, resID)で容量の大きいjpg画像を読んでいるから、その際にもメモリを消費しているのでは? 本当は下記URLのようにBitmapとして読み込む前にリサイズしたいのだが、自分ではうまくいかず。 kotlinの理解が進んできたら、再度見直すこととしたい。 参考URL:https://developer.android.com/topic/performance/graphics/load-bitmap?hl=ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ConstraintLayoutでつまずきやすいViewが重複した時の解決策

はじめに ConstraintLayoutでつまずく中でも頻出するのがViewの重複問題、押出し問題かと思います。 そこで、今回自分が役立った二つの要素を紹介したいと思います layout_constrainedWidth <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!Hello World!HelloHello World!Hello World!HelloHello World!Hello World!Hello" android:ellipsize="end" android:maxLines="1" app:layout_constraintHorizontal_bias="0" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toSraerOf="@+id/view" app:layout_constraintTop_toTopOf="parent"/> <View android:id="@+id/view" android:layout_width="50dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@+id/text_view" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> こちらは、wrap_contentを設定した際に隣り合ってるViewを押し出してしまう状態のものに設定することで隣り合ってるViewの制約を破らない様に設定することができます。 そうすることで、重なってしまったり、押し出したりしてしまうことがなくなります。 layout_constraintHorizontal_bias <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintHorizontal_bias="0" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> 次にこちらです。 こちらはbiasを0から1の間で少数で指定することにより制約を設けてる間での位置を決めることができます。 0に近ければ左により1に近ければみぎによっていきます。 最後に 上の2つは組み合わせることが多く、併用することにより綺麗なUIを作成することができるかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebViewで基本認証させる際の注意点

最初に結論 AndroidアプリのWebViewで基本認証させようと思って検索するとよく見かけるサンプルコードについて、これではコンテンツに埋め込まれている外部サイトのオブジェクト(画像とかロギングツールとか広告オブジェクトとか)へ認証情報を流してしまうかもしれないので、注意が必要。 つまり、認証情報を提示するホスト/URLを、きちんと制限しましょう。という至極当然の結論。 よく見るサンプルコード webView.setWebViewClient(new WebViewClient(){ @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { handler.proceed("ユーザ名", "パスワード"); } }); つまり、WebViewClientクラスの「onReceivedHttpAuthRequest」をオーバーライドして、ユーザ名とパスワードを与えてあげればいい。 というもの。 まぁ、確かにそうなんだが、これでは、任意のホストの任意のレルムに認証情報を流してしまうかもしれないので、注意が必要。 実際に試してみる 環境 こんな感じ トップの192.0.2.1のコンテンツ内部に、192.0.2.2のコンテンツ(広告の画像とか、外部スクリプトとか)を参照しているような場合。 192.0.2.2 側で故意に認証要求させると認証情報がそちらに漏れるかもしれない。 アプリのコード protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // これはトップのhttp://192.0.2.1:90/Auth/test.htmqだけに // 関係する認証情報のはず、だと思ってコーディングしていると思う String username = "sanaki"; String password = "password123!"; // WebView webView = (WebView)findViewById(R.id.webView); webView.setWebViewClient(new WebViewClient(){ @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { handler.proceed(username, password); Log.i("BasicAuth", "Host=" + host + ",realm=" + realm); } }); // JavaScriptを有効化 webView.getSettings().setJavaScriptEnabled(true); // JavaScriptを有効にする // webView.loadUrl("http://192.0.2.1:90/Auth/test.htm"); } こんな感じ。 アプリを起動すると「192.0.2.1:90」にアクセスして、そのコンテンツを表示する。というやつ。 ネットにアクセスするために AndroidManifest.xmlに 「<uses-permission android:name="android.permission.INTERNET"/>」 と、(検証のためにhttpsにするのは面倒なので)平文のhttpをWebViewで許可するために 「android:usesCleartextTraffic="true"」 は記述している まぁ、基本認証をしているということは、主に管理系のWebサイトだと思うけど、管理系に広告とかアクセス履歴系のやつとか埋め込んでいるのも、まぁ、たしかに想定としておかしな話かもしれんけどねぇ~ 結果(まとめ) きちんと、192.0.2.2側にも認証情報が洩れている事が、下記から確認できると思う。 結果(ログ) Logの方には、こんな感じで、ログが出てる。 2022-01-12 15:36:06.175 18740-18740/jp.dip.rocketeer.webviewtest I/BasicAuth: Host=192.0.2.1,realm=192.0.2.1 2022-01-12 15:36:06.379 18740-18740/jp.dip.rocketeer.webviewtest I/BasicAuth: Host=192.0.2.2,realm=192.0.2.2 子フレーム側の192.0.2.2側にも認証情報が洩れているということになるだろう。 結果(192.0.2.1側{親、想定しているホスト側}) 192.0.2.1 の方は、こんな感じの通信内容となっている。 RAW 通信データ C:&gt;StreamRelay.NET.exe -localport 90 -remoteport 80 -remotehost 127.0.0.1 -logging GET /Auth/test0.htm HTTP/1.1 Host: 192.0.2.1:90 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 HTTP/1.1 401 Unauthorized ← 192.0.2.1側のこれは想定している認証要求 Cache-Control: private Content-Type: text/html; charset=utf-8 Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET WWW-Authenticate: Basic realm="192.0.2.1" Date: Wed, 12 Jan 2022 07:11:14 GMT Content-Length: 6396 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>IIS 10.0 エラーの詳細 - 401.2 - Unauthorized</title> <style type="text/css"> <!-- body{margin:0;font-size:.7em;font-family:Verdana,Arial,Helvetica,sans-serif;} code{margin:0;color:#006600;font-size:1.1em;font-weight:bold;} .config_source code{font-size:.8em;color:#000000;} pre{margin:0;font-size:1.4em;word-wrap:break-word;} ul,ol{margin:10px 0 10px 5px;} ul.first,ol.first{margin-top:5px;} fieldset{padding:0 15px 10px 15px;word-break:break-all;} .summary-container fieldset{padding-bottom:5px;margin-top:4px;} legend.no-expand-all{padding:2px 15px 4px 10px;margin:0 0 0 -12px;} legend{color:#333333;;margin:4px 0 8px -12px;_margin-top:0px; font-weight:bold;font-size:1em;} a:link,a:visited{color:#007EFF;font-weight:bold;} a:hover{text-decoration:none;} h1{font-size:2.4em;margin:0;color:#FFF;} h2{font-size:1.7em;margin:0;color:#CC0000;} h3{font-size:1.4em;margin:10px 0 0 0;color:#CC0000;} h4{font-size:1.2em;margin:10px 0 5px 0; }#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS",Verdana,sans-serif; color:#FFF;background-color:#5C87B2; }#content{margin:0 0 0 2%;position:relative;} .summary-container,.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;} .content-container p{margin:0 0 10px 0; }#details-left{width:35%;float:left;margin-right:2%; }#details-right{width:63%;float:left;overflow:hidden; }#server_version{width:96%;_height:1px;min-height:1px;margin:0 0 5px 0;padding:11px 2% 8px 2%;color:#FFFFFF; background-color:#5A7FA5;border-bottom:1px solid #C1CFDD;border-top:1px solid #4A6C8E;font-weight:normal; font-size:1em;color:#FFF;text-align:right; }#server_version p{margin:5px 0;} table{margin:4px 0 4px 0;width:100%;border:none;} td,th{vertical-align:top;padding:3px 0;text-align:left;font-weight:normal;border:none;} th{width:30%;text-align:right;padding-right:2%;font-weight:bold;} thead th{background-color:#ebebeb;width:25%; }#details-right th{width:20%;} table tr.alt td,table tr.alt th{} .highlight-code{color:#CC0000;font-weight:bold;font-style:italic;} .clear{clear:both;} .preferred{padding:0 5px 2px 5px;font-weight:normal;background:#006633;color:#FFF;font-size:.8em;} --> </style> </head> <body> <div id="content"> <div class="content-container"> <h3>HTTP エラー 401.2 - Unauthorized</h3> <h4>認証ヘッダーが無効なため、このページを表示することができません。</h4> </div> <div class="content-container"> <fieldset><h4>可能性のある原因:</h4> <ul> <li>IIS は認証プロトコル (匿名のものも含む) を選択しません。</li> <li>統合認証のみ有効です。統合認証をサポートしないクライアント ブラウザーが使用されました。</li> <li>統合認証は有効で、Web サーバーに到達する前に認証ヘッダーを変更するプロキシ サーバーをとおして要求が送信されています。</li> <li>Web サーバーは匿名アクセスに対して構成されてなく、必要な認証ヘッダーが受信されませんでした。</li> <li>"configuration/system.webServer/authorization" 構成セクションが明示的にユーザーのアクセスを拒否しています。</li> </ul> </fieldset> </div> <div class="content-container"> <fieldset><h4>対処方法:</h4> <ul> <li>リソースに対する認証設定を確認した後、その認証方法を使用するリソースを要求します。</li> <li>クライアント ブラウザーが統合認証をサポートしていることを確認します。</li> <li>統合認証が使用される場合、要求がプロキシ サーバーをとおしていないことを確認します。</li> <li>"configuration/system.webServer/authorization" 構成セクションによって、ユーザーが明示的にアクセスを拒否されていないことを確認します。</li> <li>この HTTP 状態コードに対して失敗した要求を追跡するトレース規則を作成します。失敗した要求のトレース規則の作成の詳細については、<a href="http://go.microsoft.com/fwlink/?LinkID=66439">ここ</a>をクリックします。</li> </ul> </fieldset> </div> <div class="content-container"> <fieldset><h4>エラー情報の詳細:</h4> <div id="details-left"> <table border="0" cellpadding="0" cellspacing="0"> <tr class="alt"><th>モジュール</th><td>&nbsp;&nbsp;&nbsp;IIS Web Core</td></tr> <tr><th>通知</th><td>&nbsp;&nbsp;&nbsp;AuthenticateRequest</td></tr> <tr class="alt"><th>ハンドラー</th><td>&nbsp;&nbsp;&nbsp;StaticFile</td></tr> <tr><th>エラー コード</th><td>&nbsp;&nbsp;&nbsp;0x80070005</td></tr> </table> </div> <div id="details-right"> <table border="0" cellpadding="0" cellspacing="0"> <tr class="alt"><th>要求された URL</th><td>&nbsp;&nbsp;&nbsp;http://192.168.0.11:80/Auth/test0.htm</td></tr> <tr><th>物理パス</th><td>&nbsp;&nbsp;&nbsp;C:\inetpub\Rocketeer\Auth\test0.htm</td></tr> <tr class="alt"><th>ログオン方法</th><td>&nbsp;&nbsp;&nbsp;未定義です</td></tr> <tr><th>ログオン ユーザー</th><td>&nbsp;&nbsp;&nbsp;未定義です</td></tr> </table> <div class="clear"></div> </div> </fieldset> </div> <div class="content-container"> <fieldset><h4>詳細情報:</h4> このエラーは、Web サーバーに送信された WWW-Authenticate ヘッダーがサーバーの構成でサポートされていないときに発生します。リソースの認証方法を確認し、クライアントが使用している認証方法を確認します。それらの認証方法が異なる場合、このエラーが発生します。クライアントが使用している認証の種類を判断するには、クライアントの認証設定を確認します。 <p><a href="https://go.microsoft.com/fwlink/?LinkID=62293&amp;IIS70Error=401,2,0x80070005,17763">詳細情報の表示 &raquo;</a></p> <p>マイクロソフト サポート技術情報の記事:</p> <ul><li>907273</li><li>253667</li></ul> </fieldset> </div> </div> </body> </html> GET /Auth/test0.htm HTTP/1.1 Host: 192.0.2.1:90 Connection: keep-alive Authorization: Basic c2FuYWtpOnBhc3N3b3JkMTIzIQ== ← これは想定している認証情報 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 HTTP/1.1 200 OK Content-Type: text/html Last-Modified: Wed, 12 Jan 2022 06:47:42 GMT Accept-Ranges: bytes ETag: "e418764c807d81:0" Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET Date: Wed, 12 Jan 2022 07:11:14 GMT Content-Length: 157 <html> <head><title>Basic Auth Test</title></head> <body>This is Test <hr> <iframe src="http://192.0.2.2/Auth/testIn.htm"></iframe> </body> </html> 結果(192.0.2.2側{子、想定していないホスト側}) 192.0.2.2 の方は、こんな感じの通信内容となっている。 認証情報が漏れている事が確認できる RAW 通信データ C:&gt;StreamRelay.NET.exe -localport 80 -remoteport 80 -remotehost 192.0.2.1 -logging GET /Auth/testIn.htm HTTP/1.1 Host: 192.0.2.2 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Referer: http://192.0.2.1:90/Auth/test0.htm Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 HTTP/1.1 401 Unauthorized ← 192.0.2.2側のこちらは想定されていない場面が多いと思う Content-Type: text/html Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET WWW-Authenticate: Basic realm="192.0.2.2" Date: Wed, 12 Jan 2022 07:11:14 GMT Content-Length: 1313 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=shift_jis"/> <title>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</title> <style type="text/css"> <!-- body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}fieldset{padding:0 15px 10px 15px;}h1{font-size:2.4em;margin:0;color:#FFF;}h2{font-size:1.7em;margin:0;color:#CC0000;}h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;background-color:#555555;}#content{margin:0 0 0 2%;position:relative;}.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;} --> </style> </head> <body> <div id="header"><h1>サーバー エラー</h1></div> <div id="content"> <div class="content-container"><fieldset> <h2>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</h2> <h3>指定した資格情報を使用して、このディレクトリまたはページを表示するアクセス許可がありません。</h 3> </fieldset></div> </div> </body> </html> GET /Auth/testIn.htm HTTP/1.1 Host: 192.0.2.2 Connection: keep-alive Authorization: Basic c2FuYWtpOnBhc3N3b3JkMTIzIQ== ← 想定外のホスト(192.0.2.2)へ認証情報が洩れている Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Referer: http://192.0.2.1:90/Auth/test0.htm Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 HTTP/1.1 200 OK Content-Type: text/html Last-Modified: Wed, 12 Jan 2022 04:31:28 GMT Accept-Ranges: bytes ETag: "d424f3436d7d81:0" Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET Date: Wed, 12 Jan 2022 07:11:14 GMT Content-Length: 19 This is ifame page. まとめ まぁ、つまり、オーバーライドする際に、hostやrealmで制限する必要があるということ。 例えば、 webView.setWebViewClient(new WebViewClient(){ String relmStr = ""; @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { // hostやrealmをチェックして、認証情報をチェックする必要がある if(host.equals("192.0.2.1") == true){ // 例えば、ホストで制限する if(this.relmStr.length() == 0){ // ここは一番最初の要求 this.relmStr = realm; handler.proceed("ユーザ名", "パスワード"); // ← 想定している範囲内だけに制限することが大切 }else if(this.relmStr.equals(realm) == true){ // 2番目からは記憶しているレルムの時だけに制限する handler.proceed("ユーザ名", "パスワード"); // ← 想定している範囲内だけに制限することが大切 }else{ handler.cancel(); } }else{ handler.cancel(); } } }); こんな感じ。 192.0.2.2側ではちゃんと認証情報を漏らさずに認証エラーとなっている RAW 通信データ C:&gt;StreamRelay.NET.exe -localport 80 -remoteport 80 -remotehost 192.0.2.1 -logging GET /Auth/testIn.htm HTTP/1.1 Host: 192.0.2.2 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Referer: http://192.0.2.1:90/Auth/test0.htm Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 If-None-Match: "d424f3436d7d81:0" If-Modified-Since: Wed, 12 Jan 2022 04:31:28 GMT HTTP/1.1 401 Unauthorized Content-Type: text/html Server: Microsoft-IIS/10.0 X-Powered-By: ASP.NET WWW-Authenticate: Basic realm="192.0.2.2" Date: Thu, 13 Jan 2022 00:46:32 GMT Content-Length: 1313 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-s trict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=shift_jis"/> <title>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</title> <style type="text/css"> <!--body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE; }fieldset{padding:0 15px 10px 15px;}h1{font-size:2.4em;margin:0;color:#FFF;}h2{font-size:1.7em;margin:0;color:#CC0000;}h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;background-color:#555555;}#content{margin:0 0 0 2%;position:relative;}.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}--> </style> </head> <body> <div id="header"><h1>サーバー エラー</h1></div> <div id="content"> <div class="content-container"><fieldset> <h2>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</h2> <h3>指定した資格情報を使用して、このディレクトリまたはページを表示するアクセス許可がありません。</h 3> </fieldset></div> </div> </body> </html> ここから通信が途切れて、最終的に認証エラーとなっている(認証情報は漏れなくなった) これでも、悪意あるコンテンツと、レンタルサーバを共有しているような場合は、突破されるので、さらなる制限が必要だよ。 (第一引数のWebView viewを使ってポートとか、URLのパスとかに基づいてさらに制限をかける) リンク JSSECの「Androidアプリのセキュア設計・セキュアコーディングガイド」は読んでおこう。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

記事というよりただのメモ?

Androidアプリで複数フラグメントで共有のViewModelを持つ方法について その前にViewModelの概念についてよくわからないまま使用していたので、いろいろ調べたら以下の単語が出てきた ・activityViewModels そのアクティビティで定義されてるViewModel? ・viewModels こいつはなんだ???どうやって使うんだ? ・navGraphViewModels こいつはnav_graph.xmlで同じnavigation内に定義されているfragmentの時に使えるらしい。 testFragmentで以下のような実装をしてみた。 private val sharedViewModel: SharedViewModel by activityViewModels() private val test2ViewModel: Test2ViewModel by activityViewModels() SharedViewModelはMainActivityで定義されている Test2ViewModelは別のfragmentで生成されたViewModelであり、それを読み取りたい。 SharedViewModelは取れたがTestViewModelが取れてなさそう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む