質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.77%

GASでダイジェスト認証を通過したい

解決済

回答 1

投稿

  • 評価
  • クリップ 3
  • VIEW 431

score 59

GASを使ってネットワークカメラから画像を取得したいと思っていますが、完全にハマってしまいました。

過去の質問↓
GASでネットワークカメラから静止画を取得したい
連想配列から値を取り出せない

色々な方にアドバイスを頂き、ようやくダイジェスト認証の2回めのリクエストにチャレンジするところまで来ているのですが、エラーが出てしまいます。

function camera(){
  var url = "http://グローバルIP****************:ポート番号*********/snapshot.jpg" //ポート開放済み
  var options = {
    "method": "GET",
    "muteHttpExceptions":true
  };
  var firstRequest = UrlFetchApp.fetch(url,options).getHeaders();
  Logger.log(firstRequest)
  //レスポンスヘッダー{Server=, Content-Type=text/html, Content-Length=351, Date=Wed, 24 Feb 2021 07:22:38 GMT, WWW-Authenticate=Digest realm="Network Camera", nonce="9e55a4cbae641acb2d0adb43890abf84", qop="auth"}

  var data = firstRequest["WWW-Authenticate"]
  var nonce = data.slice(data.indexOf("nonce")+7,-13) //nonceの文字列切り出し
  var nc = "00000001"
  var q = "auth"
  var cNonce = cnonce()
  var a1 = "admin:Network Camera:パスワード************"
  var a2 = "GET:/snapshot.jpg"
 
  //ハッシュ値計算
  var a1digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, a1);
  var a2digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, a2);
  var responsestr = `${a1digest}:${nonce}:${nc}:${cNonce}:${q}:${a2digest}`;
  var response = getMD5Hash(responsestr);//16進数32桁の文字列に

  //ヘッダー
  var headers = {
    Authorization:"Digest username:admin",
    realm:"Network Camera",
    nonce:nonce,
    uri:"/snapshot.jpg",
    response:response,
    qop:q,
    nc:nc,
    cnonce:cNonce,
    algorithm:"MD5"
  }
  var options2 = {
    "muteHttpExceptions":true,
    "headers":headers
  };
  var secondRequest = UrlFetchApp.fetch(url,options2);
    Logger.log(secondRequest) //400 - Bad Request
}

function getMD5Hash(input) {
  var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, input);
  var txtHash = '';
  for (j = 0; j < rawHash.length; j++) {
    var hashVal = rawHash[j];
    if (hashVal < 0)
      hashVal += 256;
    if (hashVal.toString(16).length == 1)
      txtHash += "0";
    txtHash += hashVal.toString(16);
  }
  return txtHash;
}

function cnonce(){
  // 生成する文字列の長さ
  var l = 16;
  // 生成する文字列に含める文字セット
  var c = "abcdefghijklmnopqrstuvwxyz0123456789";
  var cl = c.length;
  var r = "";
  for(var i=0; i<l; i++){
    r += c[Math.floor(Math.random()*cl)];
  }
  return r
}

もはや何がおかしいのか自分でもよくわからないのですが、特にハッシュ値の計算とヘッダーの記述部分に自信がありません。
他にもおかしいところがありましたらご指摘いただけないでしょうか。

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

ネットワークカメラの実機がないので試せていませんが、とりあえず直せそうなところが3つあります。

コードの流れに沿って上から順に説明しますが、優先順位は、3=2>1です。
 

1.nonceの文字列切り出し

  var nonce = data.slice(data.indexOf("nonce")+7,-13) //nonceの文字列切り出し


ですが、サーバーが返すレスポンスヘッダの中身は順番が固定ではないことがあるので、固定位置を前提とした切り出しだと、うまくnonceが切り出せない場合があります。

したがって、安全に切り出すならば、位置に関係なく抜き出せるように正規表現を使用して

  var re=/nonce=\"([\x0-\xf]+)\"/
  nonce = data.match(re)[1]


とするか、nonceが32文字であることが確定しているならば

    var pos = data.indexOf("nonce")
    var nonce = data.slice(pos+7,pos+7+32) //nonceの文字列切り出し


とします。
 
 
 
2.Utilities.computeDigest()関数について
→この関数の戻り値は、バイト列です。したがって、

  var a1digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, a1);
  var a2digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, a2);
  var responsestr = `${a1digest}:${nonce}:${nc}:${cNonce}:${q}:${a2digest}`;


としてしまうと、
3行目のresponsestr の中身は、

-121,26,67,-51,103,-96,25,108,-12,1,-8,-109,89,115,-34,-79:555a06e8db3c55837408acb1a7a69f5a:36a1333e1a98906f378dba493613942e...


というように、カンマで区切られた整数列が混ざってしまいます。

a1digest, a2digest は nonce と同じく16進数表現で渡す必要があるので、getMD5Hash()関数を使いましょう。

  var a1digest = getMD5Hash(a1);
  var a2digest = getMD5Hash(a2);


 
 
 
3.ヘッダーの組み立て方について
ヘッダーのAuthorizationをオブジェクトを使って組み立てていますが、
掲題のコードでは400 Bad requestが返ってきているとのコメントから察するに、サーバーがヘッダーの内容を正しく解析できていないと考えられます。
(もしサーバーがヘッダの内容を解析できていていて認証に失敗しているならば、401 Unauthorizedが返ってくるはず)

調べた結果、Authorizationは、オブジェクトではなく、平文で組み立てて渡さなければならないようです。

http://x68000.q-e-d.net/~68user/net/http-auth-2.htmlの「クライアントが返すべき Authorization ヘッダ」を見ると、項目によっては二重引用符を付ける必要があったり、逆につけてはいけなかったりします。

したがって、ヘッダーの組み立ての部分は下記のようになります。

var user = "admin";
var realm = "Network Camera";
var uri= "/snapshot.jpg";

// nc, algorithm, qopには二重引用符を付けない。
var headers = {
  Authorization : `Digest username="${user}", realm="${realm}", nonce="${nonce}", uri="${uri}", cnonce="${cNonce}", nc=00000001, algorithm=MD5, response="${response}", qop=auth`
}

最初に述べたように、実機で検証できていないため、他の要因で認証失敗するかもしれません。
その場合は、curlで

curl --digest -o test.jpg -u <UserName>:<Password> http://<IPAddress>:<HttpPort>/snapshot.jpg -v

-vオプションを付けて出力された結果を、質問文に掲載してもらうと、より解析が進められるかもしれません。

・参考にしたサイト他
68user's pageさん
python-requestsのソースコード
http://jigsaw.w3.org/HTTP/Digest/ (ダイジェスト認証の検証に使用)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/02/25 11:16

    qnoir様のアドバイスを頂いて試したところ、一発で成功しました!

    神!

    キャンセル

  • 2021/02/25 11:59

    よかったです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る