rabbit51

it's since Nov.30 2005
May.29 2014, transferred from broach

Panasonic KX-PD102DのFaxTiffWriterをWinFaxで代替え

2024-11-22 10:00:00 | 

Panasonic KX-PD102DのFaxTiffWriterがインストール出来ない」でFaxTiffWriterが作成するTIFFデータとWindowsのFAX Driverが作るTIFFを比較分析した。この分析結果をもとにWindows FAX Driver作成のTIFFをSDメモリーカードに記録し、Panasonic KX-PD102DでFAX送信してみた。

(1)Win FAX DriverのTIFFをスクリプトで変換
・Tagデータの次にImageデータとなるよう配置を変更
・Imageデータの開始位置が固定となるようTagデータを処理
・Resolutionの単位をインチからセンチへ変更。設定値はA4サイズ固定値
・複数ページデータを1ページ毎のファイルにする
・指定された出力ファイル名をページ毎に「ファイル名01」から「ファイル名99」とする
・TIFF拡張子は「tif」とする
splitwinfax.sh
#!/bin/bash
# split win fax pages to files

function readbinary {
  data=()
  local LC_ALL=C byte i=0 buff
  while IFS= read -r -d '' -n 1 byte; do
    data[i++]="'$byte"
  done
  printf -v buff '%d ' "${data[@]}"
  data=($buff)
  while ((--i>=0)); do ((data[i]&=0xFF)); done
}
function writebinary {
  local buff
  printf -v buff '\\x%x' "${odata[@]}"
  LC_ALL=C printf "$buff"
}

function getbyte()  {
  printf "%d" ${data[$1]} 
}
function get2bytes(){
  printf "%d" $((data[$1+1]*256+data[$1]))
}
function get4bytes(){
  printf "%d" $((data[$1+3]*16777216+data[$1+2]*65536+data[$1+1]*256+data[$1]))
}

function setbyte() {
    odata[$1]=$(printf "$(printf '%d' $2)")
}
function set2bytes() {
    odata[$1]=$(printf "$(printf '%d' $(($2%256)) )" )
    odata[$1+1]=$(printf "$(printf '%d' $(($2/256)) )" )
}
function set4bytes() {
    odata[$1]=$(printf "$(printf '%d' $(($2%256)) )" )
    odata[$1+1]=$(printf "$(printf '%d' $(($2%65536/256)) )" )
    odata[$1+2]=$(printf "$(printf '%d' $(($2%16777216/65536)) )" )
    odata[$1+3]=$(printf "$(printf '%d' $(($2/16777216)) )" )
}

## read tif file
if [[ $1 ]]; then
  readbinary < "$1"
else
  readbinary
fi

##
odata=(); cofs=0; fnum=1; bnum=1
DD=`date "+%Y%m%d%H%M"`
FN=$(printf $(printf '\\x%x' $((65+${DD:0:4}-2010))))
if [[ ${DD:4:2} -lt 10 ]]; then FN=$FN$(printf $(printf '\\x%x' $(expr 48+${DD:4:2}))); else FN=$FN$(printf $(printf '\\x%x' $((65+${DD:4:2}-10)))); fi
if [[ ${DD:6:2} -lt 10 ]]; then FN=FN$(printf $(printf '\\x%x' $(expr 48+${DD:6:2}))); else FN=$FN$(printf $(printf '\\x%x' $((65+${DD:6:2}-10)))); fi
for (( i=1; i<999; i++ )); do
    FNN=""
    FNN=$FN$(printf $(printf "%03d" $i))
    if [[ ! -d $FNN ]]; then break; fi
done
for (( i=1; i<100; i++ )); do
    FNNP=""; FNNP=$FNN$(printf $(printf "%02d" $i))".tif"
    if [[ ! -f $FNNP ]]; then fnum=$i; break; fi
done
#offset address 0x04 after '0x49 0x49 0x2a 0x00' is 1st IFD pointer
IFDP=$(get4bytes 4)
IFDC=$(get2bytes $IFDP)
while [ $IFDP -ne 0 ]; do
  for (( cofs; cofs<4; cofs++ )); do odata[$cofs]=${data[$cofs]}; done
  set4bytes $cofs 8; ((cofs+=4))
  set2bytes $cofs $IFDC; ((cofs+=2))
  sofs=$((IFDP+2))
  for (( i=0; i<$IFDC; i++ )); do
    TID=$(get2bytes $((IFDP+2+i*12)))
    Ttyp=$(get2bytes $((IFDP+2+i*12+2)))
    Tcnt=$(get4bytes $((IFDP+2+i*12+4)))
    Tval=$(get4bytes $((IFDP+2+i*12+8)))
    case ${TID} in
      "273") #0x111 Strip Offsets
        imgadr=$Tval; imgtvaladr=$((cofs+8));;
      "279") #0x117 StripByCounts
        imgsize=$Tval;;
      "282") #0x11A X Resolution
        xresadr=$Tval; xresval1=$(get4bytes $Tval); xresval2=$(get4bytes $((Tval+4))); xrestvaladr=$((cofs+8));;
      "283") #0x11B Y Resolution
        yresadr=$Tval; yresval1=$(get4bytes $Tval); yresval2=$(get4bytes $((Tval+4))); yrestvaladr=$((cofs+8));;
      "296") #0x128 Resolution Unit
        resunit=$Tval; resunittvaladr=$((cofs+8));;
      "305") #0x131 software
          softtextadr=$Tval; softsize=$Tcnt; softtexttvaladr=$((cofs+8));;
      *)
    esac
    for (( j=cofs; j<$((cofs+12)); j++,sofs++ )); do odata[j]=${data[sofs]}; done 
    ((cofs+=12))
  done
    set4bytes $cofs 0; ((cofs+=4)) #terminator for IFDP
# X and Y Resolution cm value
    set4bytes $cofs 80; set4bytes $xrestvaladr $cofs; ((cofs+=4))
    set4bytes $cofs 1; ((cofs+=4))
    set4bytes $cofs 77; set4bytes $yrestvaladr $cofs; ((cofs+=4))
    set4bytes $cofs 1; ((cofs+=4))
    set4bytes $resunittvaladr 3
# Software text value
    for (( i=$softtextadr,j=0; j<$softsize; i++,j++ )); do odata[((cofs+j))]=${data[i]}; done 
    set4bytes $softtexttvaladr $cofs
    ((cofs+=j))
# Image data must be start at offset 0x10b
    setbyte $cofs 32; ((cofs+=1))
    for (( i=imgadr, j=0; j<$imgsize; i++,j++ )); do odata[((cofs+j))]=${data[i]}; done
    set4bytes $imgtvaladr $cofs
    ((cofs+=j))
  IFDP=$(get4bytes $((IFDP+IFDC*12+2)) )
  IFDC=$(get2bytes $IFDP)
writebinary > $(printf "$FNN%02d.tif" $fnum)
printf "Page %02d to the $FNN%02d.tif\n" $bnum $fnum
((fnum+=1)); ((bnum+=1))
cofs=0
done
wordで2ページのfaxテストページを作成し、印刷のWinFaxドライバーで「FaxTest2pages.tif」を作成。
macOSターミナルで上記シェルスクリプトを実行してページ毎のTIFFファイルを作成。
macOS BigSur バージョン 11.7.10
$ uname -a
Darwin muffin2.matsuura 20.6.0 Darwin Kernel Version 20.6.0: Thu Jul  6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64 x86_64
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin20)
Copyright (C) 2007 Free Software Foundation, Inc.
$ ./splitwinfax.sh /Users/someone/FaxTest2pages.tif 
Page 01 to the OBL00101.tif
Page 02 to the OBL00102.tif
$ ls -al OBL*.tif
-rw-r--r--  1 someone  staff  925 11 21 16:56 OBL00101.tif
-rw-r--r--  1 someone  staff  925 11 21 16:56 OBL00102.tif
$ ./splitwinfax.sh /Users/someone/FaxTestExcell.tif 
Page 01 to the OBL00103.tif
$ ls -al OBL*.tif
-rw-r--r--  1 someone  staff  925 11 21 16:56 OBL00101.tif
-rw-r--r--  1 someone  staff  925 11 21 16:56 OBL00102.tif
-rw-r--r--  1 someone  staff  925 11 21 16:58 OBL00103.tif
word文章ファイルだけでなく、Excellファイル、PowerPointファイルなどもWinFaxでTIFFファイルを作成し、「splitwinfax.sh」でfax送信できるTIFFファイルに変換する。ファイル名は、ページ毎に「01」「02」「03」と付番されるようにした。
このままでは、fax送信できないので、SDメモリーカードで扱えるように「DOC_INF.BIN」ファイルを用意する。

(2)「DOC_INF.BIN」の生成
mkdocinf.sh
#!/bin/bash
# make doc_inf.bin
dbg=0
DD=`date "+%Y%m%d%H%M"`
TT="WinFax${DD}" #タイトル:半角20文字以下
PN=1    #ファックス頁数
#
function readbinary {
  data=()
  local LC_ALL=C byte i=0 buff
  while IFS= read -r -d '' -n 1 byte; do
    data[i++]="'$byte"
  done
  printf -v buff '%d ' "${data[@]}"
  data=($buff)
  while ((--i>=0)); do ((data[i]&=0xFF)); done
}
function writebinary {
  local buff
  printf -v buff '\\x%x' "${odata[@]}"
  LC_ALL=C printf "$buff"
}
function getbyte()  {
  printf "%d" ${data[$1]}
}
function get2bytes(){
  printf "%d" $((data[$1+1]*256+data[$1]))
}
function get4bytes(){
  printf "%d" $((data[$1+3]*16777216+data[$1+2]*65536+data[$1+1]*256+data[$1]))
}

function setbyte() {
    odata[$1]=$(printf "$(printf '%d' $2)")
}
function set2bytes() {
    odata[$1]=$(printf "$(printf '%d' $(($2%256)) )" )
    odata[$1+1]=$(printf "$(printf '%d' $(($2/256)) )" )
}
function set4bytes() {
    odata[$1]=$(printf "$(printf '%d' $(($2%256)) )" )
    odata[$1+1]=$(printf "$(printf '%d' $(($2%65536/256)) )" )
    odata[$1+2]=$(printf "$(printf '%d' $(($2%16777216/65536)) )" )
    odata[$1+3]=$(printf "$(printf '%d' $(($2/16777216)) )" )
}

##
cofs=0
dentnum=0
# the initial data for doc_info.bin file.
odata=(0 0 0 0 12 4 0 0 0 0 12 0 255 255 255 255)
data=(0 0 0 0 12 4 0 0 0 0 12 0 255 255 255 255)
for (( i=12; i<$((1024+12)); i++ )); do odata[i]=255; data[i]=255; done
dentd=(0 0 0 0 87 0 2 3)
dentinfd=(1 0 9 0 0 0 0 0 1 1 3 2 6 232 7 11 11 11 11 3 6 232 7 11 11 11 11 6 10 0 0 0 0 0 0 0 0 0 0 8 20 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 10 2 1 0 11 13 32 32 32 32 32 32 0 0 0 0 0 0 0 12 1 0 14 2 2 0)
if [[ $# -lt 1 ]]; then printf "$0 PageNumber <current doc_inf.bin>\n"; exit; fi
if [[ $1 ]]; then PN=$1; fi
## read doc_inf.bin file to data()
if [[ $2 ]]; then  readbinary < "$2"; fi
#
dfirst4=$(get4bytes 0); ddfirst4=0
dentend=$(get4bytes 4); ddentend=1036
dentnum=$(get2bytes 8); ddentnum=0
dentap=12; ddentap=12
dentinfap=$(get2bytes 10); ddentinfap=12
dentinfape=$((dentinfap+dentnum*87)); ddentinfape=12

printf "Loaded ...\n"
if [[ $(get4bytes 4) -eq 0 ]]; then writebinary >"DOC_INF.BIN"; exit; fi
if [[ $dentnum -eq 0 ]]; then set4bytes 0 65536; ddfirst4=65536; fi
#Directory Entry
cofs=$dentap
for (( i=0; i<dentnum; i++ )); do
  for (( j=2; j<8; j++ )); do
    set2bytes $((cofs+i*8)) $(($(get2bytes $((dentap+i*8)))+8))
    odata[$((cofs+i*8+j))]=${data[$((dentap+i*8+j))]}
  done
done
((cofs+=8*dentnum));
if [[ $dfirst4 -ne 0 ]]; then ddfirst4=$dfirst4; fi
ddentnum=$dentnum; ddentinfap=$cofs; ddentinfape=$cofs; ddentend=$((ddentinfape+1024))
ddentinfap=$((cofs+8))
set2bytes $cofs $((ddentinfap+87*dentnum))
for (( j=2; j<8; j++ )); do odata[$((cofs+j))]=${dentd[$j]}; done
((cofs+=8))
set2bytes 10 $ddentinfap
((ddentnum+=1)); set2bytes 8 $ddentnum; ddentinfape=$ddentinfap; ddentend=$((ddentinfape+1024))
set2bytes 4 $ddentend
#copy dentinfo 87*dentnum
for (( i=0; i<dentnum; i++ )); do
  for (( j=0; j<87; j++ )); do  odata[$((cofs+i*87+j))]=${data[$((dentinfap+i*87+j))]}; done
done
((cofs+=dentnum*87))
ddentinfend=$cofs
ddentend=$((ddentinfend+1024))
# Directory Entry Information
for (( i=0; i<87; i++ )); do odata[$((cofs+i))]=${dentinfd[$i]}; done
# ID02-date: 11 data 13 length 6
# ID03-date: 19 data 21 length 6
odata[$((cofs+13))]=$(printf "%d" $((${DD:0:4}%256)))
odata[$((cofs+14))]=$(printf "%d" $((${DD:0:4}/256)))
odata[$((cofs+15))]=$(printf "%d" $(expr ${DD:4:2}))
odata[$((cofs+16))]=$(printf "%d" $(expr ${DD:6:2}))
odata[$((cofs+17))]=$(printf "%d" $(expr ${DD:8:2}))
odata[$((cofs+18))]=$(printf "%d" $(expr ${DD:10:2}))
for (( i=0; i<6; i++ )); do odata[$((cofs+21+i))]=${odata[$((cofs+13+i))]}; done
# ID08-Title: 39 data 41 length 20
TTX=($(printf "${TT}" | iconv -f utf8 -t sjis | od -An -tu1))
for (( i=0; i<${#TTX[@]}&&i<20; i++ )); do odata[$((cofs+41+i))]=${TTX[$i]}; done
# ID0A-Pages: 61 data 63 length 2(short)
odata[$((cofs+63))]=$(printf "%d" $((${PN}%256)))
odata[$((cofs+64))]=$(printf "%d" $((${PN}/256)))
# ID0B-DirectoryName: 65 data 67 length 13
odata[$((cofs+67))]=$((65+${DD:0:4}-2010))
if [[ ${DD:4:2} -lt 10 ]]; then odata[$((cofs+68))]=$(expr 48+${DD:4:2}); else odata[$((cofs+68))]=$((65+${DD:4:2}-10)); fi
if [[ ${DD:6:2} -lt 10 ]]; then odata[$((cofs+69))]=$(expr 48+${DD:6:2}); else odata[$((cofs+69))]=$((65+${DD:6:2}-10)); fi

for (( i=1; i<999; i++ )); do 
    DRN=""
    for (( j=0; j<3; j++ )); do DRN=$DRN$(printf $(printf '\x%x'  "${odata[$((cofs+67+j))]}")); done
    DRN=$DRN$(printf $(printf "%03d" $i))
    if [[ ! -d $DRN ]]; then
        printf "New directory name is $DRN\n"
        mkdir $DRN
        odata[$((cofs+70))]=$((48+${DRN:3:1}))
        odata[$((cofs+71))]=$((48+${DRN:4:1}))
        odata[$((cofs+72))]=$((48+${DRN:5:1}))
        break
    fi
done
((cofs+=87)); ddentinfape=$cofs
for (( i=$cofs; i<$((1024+cofs)); i++ )); do odata[i]=255; done
set2bytes 4 $((cofs+1024))
ddentend=$((cofs+1024))
writebinary >"DOC_INF.BIN"
送信するfax文書が3ページなので
$ ./mkdocinf.sh 3
Loaded ...
New directory name is OBL001
$ ls -d OBL*/
OBL001/
$ ls OBL*.tif
OBL00101.tif	OBL00102.tif	OBL00103.tif
$ mv OBL*.tif OBL001/
$ ls OBL001/
OBL00101.tif	OBL00102.tif	OBL00103.tif
$ ls DOC_INF.BIN
DOC_INF.BIN
$ 
実行後、作成されたディレクトリ「OBL001」にTIFFファイル「OBL00101.tif」「OBL00102.tif」「OBL00103.tif」を移動する。「DOC_INF.BIN」とディレクトリ「OBL001」をSDメモリーカードの「/PRIVATE/MEIGROPU/PCC/FAX/DOC/」にコピーする。

作成された「DOC_INF.BIN」の内容

「赤枠」部は、ページ数

SDメモリーカードをKX-PD102に挿入認識後、「機能」「SDカード」「SDのパソコンデータ送信」

作成したfaxデータのタイトルが表示される。(追記:DOC_INF.BINの時間と表示時間が1分ほどズレている。サンプルデータを作り直したため)
「表示」でページ毎の内容をディスプレーで確認できる。

「ファックス送信」を選択して送信先電話番号を設定してfax送信ができる。

(3)その他
「DOC/OBL001/OBL001.TXT」は、存在しなくとも機能する
「DOC/DOC_INF.BIN」は、SDメモリを挿入認識させると「隠し属性」が設定される
「DOC/DOC_INF.BAK」は、設定されていなくても動作。SDメモリの挿入認識で自動作成される。
SDメモリのファイル名やディレクトリ名は、「小文字」でも機能する


 

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Panasonic KX-PD102DのFaxTiffWriterがインストール出来ない

2024-10-21 13:00:00 | 
ひさびさにPanasonic KX-PD102(2015年10月発売)で「FAX」送信する事になった。

Panasonicから提供されるWindows用の「FAXTIFFWriter」ドライバーを使い、SDカードにFAXデータを生成し、KX-PD102から送信する。

Office Wordを使いファックス文書を作成し、印刷で「FAX TIFF Writer」を選択しようとしたが見つからない。
しかたないので「FAXTIFFWriter」を再インストールし、再起動。
何回か行うも印刷時にプリンターデバイスとして「FAX TIFF Writer」が表示されない。

インストール時に「プリンタードライバーが書き込めない」のエラー表示が出ている。
使用機材は、macOS BigSur(バージョン 11.7.10)にParallels Desktop 18 for Mac standard edtion(バージョン 18.3.2)を稼働させ、Windows 10 HOME(バージョン 22H2)

VAIO VJP131B01NのWindows 10 Pro(バージョン 22H2)では、インストール後にプリンターデバイスとして「FAX TIFF Writer」が選択出来るようになる。しかし、SDカードのインターフェースが内部コネクタ破損で使えない。

「紙」に印刷して2ページのファックスをKX-PD102で送信した。
「ゴムローラ」のへたりで、「紙」送りが時々スリップする。2枚同時に送ってしまう。失敗の連続。再送信を何度か行い、送り終えた。

2019年ごろ発売のPanasonic FAX機は、SDメモリーからのFAX送信機能が装備されていたが、2022年発売の最新機からは、SDメモリーカードからのファックス送信機能が無くなっている。「おたっくす」と言うワードも取説表紙以外から消えている。時代の流れを感じる。。。

今後のことを考え、SDメモリーカードに直接データを書き込めば、KX-PD102でFAX送信出来るのではないかと思いつき調べてみた。

(1)SDメモリーカードのデータを調査
KX-PD102Dのソフトウェアバージョンは、「機能」「#181」で表示される。
Appli: 4.01 / Linux: 4.01F

SDカードをフォーマットする
「機能」「SDカード(F2)」【SDをフォーマットする】を選択「決定」「はい(F1)」

フォーマット後の「ディレクトリ」と「ファイル」
Directory
-L0[PRIVATE] ----L1[MEIGROUP]  -L2[PCC] -L3[FAX] ----------------------L4[DOC]
-L0[IMEXPORT]                           -L3[PCC-DAT] -L4[ADDRESS]     -L4[REC]
取説の「本機で保存したデータをパソコン・テレビ(ビエラ)で使う」「フォルダー構造について」にも記載がある
Files
/PRIVATE/MEIGROPU/PCC/FAX/DOC/DOC_INF.BIN
/PRIVATE/MEIGROPU/PCC/FAX/DOC/DOC_INF.BAK
フォーマット直後のDOC_INF.BIN
$ hexdump DOC_INF.BIN
00000000  00 00 00 00 0c 04 00 00  00 00 0c 00 ff ff ff ff  |................|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000400  ff ff ff ff ff ff ff ff  ff ff ff ff              |............|
0000040c
$ hexdump DOC_INF.BAK
00000000  00 00 00 00 0c 04 00 00  00 00 0c 00 ff ff ff ff  |................|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000400  ff ff ff ff ff ff ff ff  ff ff ff ff              |............|
0000040c
「.BIN」も「.BAK」も同じデータ
「FAXTIFFWriter」でSDメモリに書く。タイトル名を求められるので「フォーマット後の最初」として作成した。2ページのファックス文書となる。
$ hexdump -C DOC_INF.BIN
00000000  00 00 01 00 6b 04 00 00  01 00 14 00 14 00 00 00  |....k...........|
00000010  57 00 02 03 01 00 09 00  00 00 00 00 01 01 03 02  |W...............|
00000020  06 e8 07 0a 03 0a 21 03  06 e8 07 0a 03 0a 21 06  |......!.......!.|
00000030  0a 00 00 00 00 00 00 00  00 00 00 08 14 83 74 83  |..............t.|
00000040  48 81 5b 83 7d 83 62 83  67 8c e3 82 cc 8d c5 8f  |H.[.}.b.g.......|
00000050  89 0a 02 02 00 0b 0d 4f  41 33 30 30 31 00 00 00  |.......OA3001...|
00000060  00 00 00 00 0c 01 01 0e  02 02 00 ff ff ff ff ff  |................|
00000070  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000460  ff ff ff ff ff ff ff ff  ff ff ff                 |...........|
0000046b
3個のファイルが「OA3001」ディレクトリ以下に作られる
/PRIVATE/MEIGROPU/PCC/FAX/DOC/OA3001/OA3001.TXT
/PRIVATE/MEIGROPU/PCC/FAX/DOC/OA3001/OA300101.tif
/PRIVATE/MEIGROPU/PCC/FAX/DOC/OA3001/OA300102.tif
Directory名及びファイル名は、「年月日数」で設定されている。
「年:H(2017), I(2018), J(2019), K(2020), L(2021), M(2022), N(2023), O(2024),P(2025), ..., Z(2035)」最大2035年までか
「月:1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C」
「日:1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, ... U, V」
「数字:000 - 999」は、同一日内のファックス毎
ファックスデータのTIFファイルは、頁ごとにファイル名に「01, 02, 03, ..., 99」が追加設定される
OA3001/OA3001.TXTの中身は
$ cat OA3001/OA3001.TXT | iconv -f SHIFT_JIS -t utf8
2024年  10月  03日 10時 33分
相手のファクス情報              
ナンバーディスプレイ          フォーマット後の最初
「ナンバーディスプレイ」の項目には、タイトル入力の内容がShift_JISで記録される。下記は、18件の受信したファックスと送信したファックスがSD保存された時のDOC_INF.BIN内容(Little Endian)。
DOC_INF.BINの調査内容
Address        Data
00000000    00 00 01 00  : ブロック数?
00000004    ba 0a 00 00  : 次のブロック開始アドレス
00000008    12 00 9c 00  : ファックス数(0x12)とファクス情報群の開始アドレス(0x009c)
0000000C    9c 00 00 00 57 00 02 03
          : ファクス情報インデックス アドレス(0x0000009c)サイズ(0x57)不明(0x020 x03)?
00000014    f3 00 00 00 57 00 02 03
0000001C    4a 01 00 00 57 00 02 03
00000024    a1 01 00 00 57 00 02 03
****省略0x0000000cから0x0000009bまで8バイトづつファックス数分続く
00000094    63 06 00 00 57 00 02 03
0000009C    01 00 09 00 00 00 00 00  : 0x000 1 不明、0x0009 Tag数
                    01 01 03  : Tag(0x01) Length(0x01) Value(0x03)
                    02 06 e1 07 02 07 0b 22
      : Tag(0x02) Length(0x06) 時間 (0x07e1=2017年 0x02=2月 0x07=7日 0x0b=11時 0x22=34分) 
                    03 06 e1 07 02 07 0b 22  : Tag(0x03) Length(0x06) 時間
                    06 0a 00 00 00 00 00 00 00 00 00 00  : Tag(0x06) Length(0x0a)
                    08 14 32 30 31 37 30 32 30 37 74 61 78 8c b9 90 f2 92 a5 8e fb 20
      : Tag(0x08) Length(0x14) タイトル名(ShifJIS)(20170207tax源泉徴収)
                    0a 02 04 00  : Tag(0x0a) Length(0x02) ファクスページ数(0x0004)
                    0b 0d 48 32 37 30 30 31 00 00 00 00 00 00 00
      : Tag(0x0b) Length(0x0d)  ファックスデータのあるディレクトリ名(H27001)
                    0c 01 00  : Tag(0x0c) Length(0x01) Value(0x00)
                    0e 02 02 00  : Tag(0x0e) Length(0x02) Value(0x0002)
000000f3    ****省略
0000014a    ****省略
000001a1    ****省略
****省略 0x0000009cから0x000006b9までファックス情報87バイトがファックス数分続く
00000663    01 00 09 00 00 00 00 00  : 0x0001 0x0009(=Tag数)
0000066b    01 01 03
0000066e    02 06 e8 07 0a 03 09 31  : 2024/10/3 9:49
00000676    03 06 e8 07 0a 03 09 31
0000067e    06 0a 00 00 00 00 00 00 00 00 00 00
0000068a    08 14 83 74 83 48 81 5b 83 7d 83 62 83 67 8c e3 82 cc 8d c5 8f 89
      : タイトル入力した「フォーマット後の最初」データ
000006a0    0a 02 02 00
000006a4    0b 0d 4f 41 33 30 30 31 00 00 00 00  00 00 00  : OA3001
000006b3    0c 01 00
000006b6    0e 02 02 00
000006ba    ff ff ff ff ff  : 0x6baから0xab9まで1024個の0xffが続く
000006c0    ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff
****省略
00000ab0    ff ff ff ff ff ff ff ff  ff ff
00000aba
FaxTiffWriterで送信ファックスが保存されると、ファックス数が+1され、ファックス情報インデックスの最後に8バイトのファックス情報インデックスが追記され、ファックス情報群の開始アドレスが+0x08される。ファックス情報群の最後に87バイトのファックス情報データが追記される。ファックス情報群の次に続く1024バイトの0xffは、そのまま95バイト(8バイト+87バイト)後ろに移動し、次のブロック開始アドレスが+0x5f(95バイト)される。

(2)送信データのTIFファイル
macOSプレビューの「書き出す」で「TIFF」を選択してもCCITT G4圧縮が出来ない(表示する事は可能)。
Windowsのプリンターデバイスで「FAX」を選択してCCITT G4圧縮の白黒2値データを作成できる。(Word から画像ファイル(TIF)を作成する方法)TIFファイルを保存する時、頁ごとに1ファイルとして保存する必要がある。

Word(メモ帳でも良い)でFAX文書を作成して、プリンターデバイスに「FAX」を選択してファックス原稿ファイルを生成する。複数ページのfaxデータは、1つのファイルに統合したTIFファイルが作成される。複数ページのファックスファイルを1頁づつ保存し、ファイル名を2024年10月10日作成で「OAA00101.tif」と「OAA00102.tif」として「OAA001」フォルダに保存した。フォーマット直後の「DOC_INF.BIN」のフォルダ名、ページ数、日付をバイナリーエディタで修正してSDメモリに書き込んだ。「OAA001/OAA001.TXT」もエディタで作成し書き込んだ。
「OAA00101.tif」「OAA00102.tif」両ファイルとも「macOSプレビュー」や「Windowsフォトビュアー」でTIF画像表示される。
作成したSDメモリーカードを「KX-PD102D」に挿入して確認するとタイトル表示がされるが、「表示」をしても画像が表示されない。
SDメモリーカード内の「OAA00101.tif」「OAA00102.tif」をFaxTiffWriter作成ファイルにファイル名を「OAA00101.tif」「OAA00102.tif」に書き換えると「KX-PD102D」で表示することができる。

FaxTiffWriterで作成されたファックス画像TIFファイルのタグを調べてみた
 FAXTIFFWriter
1ページ目
IFDP=00000008 IFDC=0011
Tag ID=00fe Type=4 Count=1 Val=00000002
Tag ID=0100 Type=3 Count=1 Val=000006c0
Tag ID=0101 Type=3 Count=1 Val=00000924
Tag ID=0102 Type=3 Count=1 Val=00000001
Tag ID=0103 Type=3 Count=1 Val=00000004
Tag ID=0106 Type=3 Count=1 Val=00000000
Tag ID=0107 Type=3 Count=1 Val=00000001
Tag ID=010a Type=3 Count=1 Val=00000002
Tag ID=0111 Type=4 Count=1 Val=0000010b
Tag ID=0115 Type=3 Count=1 Val=00000001
Tag ID=0116 Type=4 Count=1 Val=00000924
Tag ID=0117 Type=4 Count=1 Val=0000020c
Tag ID=011a Type=5 Count=1 Val=000000da
Tag ID=011b Type=5 Count=1 Val=000000e2
Tag ID=0128 Type=3 Count=1 Val=00000003
Tag ID=0129 Type=3 Count=2 Val=00000000
Tag ID=013b Type=2 Count=33 Val=000000ea
IFDP=00000000
2ページ目
IFDP=00000008 IFDC=0011
Tag ID=00fe Type=4 Count=1 Val=00000002
Tag ID=0100 Type=3 Count=1 Val=000006c0
Tag ID=0101 Type=3 Count=1 Val=00000924
Tag ID=0102 Type=3 Count=1 Val=00000001
Tag ID=0103 Type=3 Count=1 Val=00000004
Tag ID=0106 Type=3 Count=1 Val=00000000
Tag ID=0107 Type=3 Count=1 Val=00000001
Tag ID=010a Type=3 Count=1 Val=00000002
Tag ID=0111 Type=4 Count=1 Val=0000010b
Tag ID=0115 Type=3 Count=1 Val=00000001
Tag ID=0116 Type=4 Count=1 Val=00000924
Tag ID=0117 Type=4 Count=1 Val=0000020c
Tag ID=011a Type=5 Count=1 Val=000000da
Tag ID=011b Type=5 Count=1 Val=000000e2
Tag ID=0128 Type=3 Count=1 Val=00000003
Tag ID=0129 Type=3 Count=2 Val=00000000
Tag ID=013b Type=2 Count=33 Val=000000ea
IFDP=00000000
Windows FAX Printer Driver
IFDP=0000029a IFDC=0011
Tag ID=00fe Type=4 Count=1 Val=00000002
Tag ID=0100 Type=4 Count=1 Val=000006c0
Tag ID=0101 Type=4 Count=1 Val=00000923
Tag ID=0102 Type=3 Count=1 Val=00000001
Tag ID=0103 Type=3 Count=1 Val=00000004
Tag ID=0106 Type=3 Count=1 Val=00000000
Tag ID=010a Type=3 Count=1 Val=00000002
Tag ID=0111 Type=4 Count=1 Val=00000008
Tag ID=0115 Type=3 Count=1 Val=00000001
Tag ID=0116 Type=4 Count=1 Val=00000923
Tag ID=0117 Type=4 Count=1 Val=00000292
Tag ID=011a Type=5 Count=1 Val=00000370
Tag ID=011b Type=5 Count=1 Val=00000378
Tag ID=0124 Type=4 Count=1 Val=00000005
Tag ID=0128 Type=3 Count=1 Val=00000002
Tag ID=0129 Type=3 Count=2 Val=00000000
Tag ID=0131 Type=2 Count=32 Val=00000380
IFDP=000005a6 IFDC=0011
Tag ID=00fe Type=4 Count=1 Val=00000002
Tag ID=0100 Type=4 Count=1 Val=000006c0
Tag ID=0101 Type=4 Count=1 Val=00000923
Tag ID=0102 Type=3 Count=1 Val=00000001
Tag ID=0103 Type=3 Count=1 Val=00000004
Tag ID=0106 Type=3 Count=1 Val=00000000
Tag ID=010a Type=3 Count=1 Val=00000002
Tag ID=0111 Type=4 Count=1 Val=000003a0
Tag ID=0115 Type=3 Count=1 Val=00000001
Tag ID=0116 Type=4 Count=1 Val=00000923
Tag ID=0117 Type=4 Count=1 Val=00000206
Tag ID=011a Type=5 Count=1 Val=0000067c
Tag ID=011b Type=5 Count=1 Val=00000684
Tag ID=0124 Type=4 Count=1 Val=00000005
Tag ID=0128 Type=3 Count=1 Val=00000002
Tag ID=0129 Type=3 Count=2 Val=00000001
Tag ID=0131 Type=2 Count=32 Val=0000068c
IFDP=00000000
FaxTiffWriterが作成したTIFファイルのタグとWindows FAX driverの作成したTIFファイルのタグを比較してみると、「0x107(263)」「0x0124(292)」「0x131(305)」「0x13B(315)」の有無、「0x128(296)」Resolution Unit値が「インチ=2とセンチ=3」の違いとX,Y Resolution「0x11A(282)」「0x11B(283)」の値が異なる。

Windows FAX Driver作成のTIFファイルに「0x107(263)」を加え、「0x0124(292)」を削除、「0x131(305)」のテキスト文字列を変更せず「0x13B(315)」に変更して「KX-PD102D」に読み込ませたが表示されなかった。

FaxTiffWriterが作成したTIFファイルで「0x13B(315)」のテキスト文字列を変更せず「0x131(305)」に変更しても「KX-PD102D」で表示される。「0x131(Software)」と「0x13B(Artist)」は、どちらでも表示に影響を与えない。

FaxTiffWriterが作成したTIFファイルで「0x107(263)」を削除して、最後のタグ「0x13B」の後ろにダミータグを挿入、タグ数は、「0x10」としてみた。非表示。
「0x13B」を「0x131」に変更、ダミー挿入したタグ部に「0x13B」を挿入し(データは両者同じ)、タグ数を「0x11」としてみた。表示。
「0x107」削除、「0x124」追加、「0x131」タグ数「0x11」として、Windows FAX Driverの生成するTIFと同じタグにしてみた。表示。
「0x128」「0x11A」「0x11B 」のResolutionをWindows FAX Driverのインチと同じにしてみた。非表示。

テスト結果からタグ数は同じである必要がある。
タグ種「0x107」「0x124」「0x131」「0x13B」の違いは問題ない。
Resolutionは、センチ定義の必要がある。

FaxTiffWriterとWindows FAX Driverの作成するTIFファイルの相違は、Index File DirectoryとImage Dataの開始位置が異なる点となる。
Windows FAX Driverが生成したTIFファイルのタグ位置(IFD)を先頭に配置し、Image Dataを続けて配置し、Resolutionをセンチ定義に変更してみた。
結果、表示するようになった。
FaxTiffWriterが生成したTIFファイルのImage Dataは、「0x0000010b」から始まる。この位置を後ろに変更する(例えば、0x0000010c)と表示しなくなる。Image Dataは、「0x0000010b」から始まる必要がある。IFDとImage Dataの間には、「0x11A」「0x11B」のResolutionデータ値や「0x131」「0x13B」のテキストデータが配置される。特に、テキストデータが増えるとImage Dataの開始位置がズレるので注意が必要となる。
「0x131」「0x13B」のテキストデータを増加させる必要がある場合は、Image Dataの後にテキストデータを配置し、「0x131」「0x13B」のオフセットアドレスに設定する。

(3)その他
Windows FAX Driverが作成したTIFファイルを「KX-PD102D」が扱えるファイルに変換してSDメモリーに書き込むスクリプトの作成を検討する事にした。

 

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

法務省 不動産登記の電子署名用証明書をValidate.javaで検証してみた

2024-07-13 10:00:00 | 

電子署名に使われている証明書の検証を加えてみた。
証明書の発行元(ルート証明書)から電子署名に使われた証明書までの有効性確認(証明書パスと失効)。





法務省 不動産登記の複数人電子署名をValidate.javaで検証してみた」に電子署名用証明書の検証部を加えた(赤文字)。公的個人認証(JPKI)署名用証明書のSubjectAltNameだけでなく政府認証基盤(GPKI)官職証明書のSubjectAltNameに記載されたDistinguishNameも表示するよう対応した(青字)。
Validate.java
import java.io.File;
import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException; //--added

import java.security.cert.CertPathBuilder; //--added
import java.security.KeyStore; //--added
import java.security.cert.PKIXParameters; //--added
import java.security.cert.CertPathValidator; //--added
import java.security.cert.Certificate; //--added
import java.security.cert.CertificateFactory; //--added
import java.security.cert.CertPath; //--added
import java.security.cert.CertificateException; //--added
import java.security.cert.CertPathValidatorException; //--added
import java.security.KeyStoreException; //--added
import java.security.NoSuchAlgorithmException; //--added
import java.security.InvalidAlgorithmParameterException; //--added
import java.util.ArrayList; //--added
import java.io.IOException; //--added
import java.security.cert.PKIXRevocationChecker; //--added
import java.security.cert.PKIXRevocationChecker.Option; //--added
import java.util.EnumSet; //--added

import java.util.Iterator;
import java.util.List;
import java.util.HexFormat; //--added
import java.util.Arrays; //--added
import java.util.Collection; //--added
import java.lang.reflect.Array; //--added

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;  //-- added

/**
 * This is a simple example of validating an XML Signature using the JSR 105
 * API. It assumes the key needed to validate the signature is contained in a
 * KeyValue KeyInfo.
 */
public class Validate {
	//
	// Synopsis: java Validate [document]
	//
	// where "document" is the name of a file containing the XML document
	// to be validated.
	//
	public static void main(String[] args) throws Exception {
		String fileName = args[0];

		// Instantiate the document to be validated
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		Document doc = dbf.newDocumentBuilder().parse(
				new FileInputStream(fileName));

		// Find Signature element
		NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,
				"Signature");
		if (nl.getLength() == 0) {
			throw new Exception("Cannot find Signature element");
		}

//-- added(外部ファイルのベースディレクトリを指定)
String cpath = new File(fileName).getParent();
cpath = "file://" + cpath.replace('\\', '/') + "/";
System.out.println("setBaseURI-> " + cpath);

//-- added for multiple signature loop
System.out.println("Signature block(s) = " + nl.getLength() );
for ( int k = 0; k < nl.getLength() ; k++ ) {
String SignatureId = nl.item(k).getAttributes().item(0).getNodeValue() ;
System.out.println("\nSignature id = " + SignatureId );

		// Create a DOM XMLSignatureFactory that will be used to unmarshal the
		// document containing the XMLSignature
		XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

		// Create a DOMValidateContext and specify a KeyValue KeySelector
		// and document context
		DOMValidateContext valContext = new DOMValidateContext(
				new KeyValueKeySelector(), nl.item(k));

//-- added
valContext.setBaseURI(cpath);
//-- added
// 申請用総合ソフト 登録通知
NodeList tg = doc.getElementsByTagName("BODY");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "ID");
    System.out.println("Find BODY element and setIdAttibute="ID"");
}
// 医療費通知(保険組合)
tg = doc.getElementsByTagName("TEG700");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find TEG700 element and setIdAttribute="id"");
}
// e-Tax( 所得税 RKO0010. 証明書更新 PTE0010, 電子申請等証明書 TEB120
tg = doc.getElementsByTagName("RKO0010");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find RKO0010 element and setIdAttribute="id"");
}
tg = doc.getElementsByTagName("PTE0010");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find PTE0010 element and setIdAttribute="id"");
}
tg = doc.getElementsByTagName("TEB120");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find TEB120 element and setIdAttribute="id"");
}

 
		// unmarshal the XMLSignature
		XMLSignature signature = fac.unmarshalXMLSignature(valContext);

		// Validate the XMLSignature (generated above)
		boolean coreValidity = signature.validate(valContext);

		// Check core validation status
		if (coreValidity == false) {
			System.err.println("Signature failed core validation");
			boolean sv = signature.getSignatureValue().validate(valContext);
			System.out.println("signature validation status: " + sv);
			// check the validation status of each Reference
			Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
			for (int j = 0; i.hasNext(); j++) {
				boolean refValid = ((Reference) i.next()).validate(valContext);
				System.out.println("ref[" + j + "] validity status: "
						+ refValid);
			}
		} else {
			System.out.println("Signature passed core validation");
		}
//-- added for multiple signature loop
}
	}

	/**
	 * KeySelector which retrieves the public key out of the KeyValue element
	 * and returns it. NOTE: If the key algorithm doesn't match signature
	 * algorithm, then the public key will be ignored.
	 */
	private static class KeyValueKeySelector extends KeySelector {
		public KeySelectorResult select(KeyInfo keyInfo,
				KeySelector.Purpose purpose, AlgorithmMethod method,
				XMLCryptoContext context) throws KeySelectorException {
			
			if (keyInfo == null) {
				throw new KeySelectorException("Null KeyInfo object!");
			}
			SignatureMethod sm = (SignatureMethod) method;
			List<?> list = keyInfo.getContent();

			for (int i = 0; i < list.size(); i++) {
				XMLStructure xmlStructure = (XMLStructure) list.get(i);
				if (xmlStructure instanceof X509Data) {
					PublicKey pk = null;
					List<?> l = ((X509Data) xmlStructure).getContent();
					if (l.size() > 0) {
						X509Certificate cert = (X509Certificate) l.get(0);
						pk = cert.getPublicKey();
//-- added for subject and valid date printing
System.out.println("X509Certificate Subject -> " + cert.getSubjectX500Principal().getName() );
System.out.println("X509Certificate NotAfter -> " + cert.getNotAfter().toString() );
//-- added for SubjectAltName 
try { Collection<List<?>> alternativeNames = cert.getSubjectAlternativeNames();
if ( alternativeNames != null ) {
    for ( List<?> alternativeName : alternativeNames ) {
    	if (  (Integer) alternativeName.get(0) == 0 ) {
    	Object generalName = alternativeName.get(1);
    	if ( generalName instanceof String) {
    		System.out.println(generalName);
    	} else {
    		HexFormat formatHex = HexFormat.ofDelimiter("").withUpperCase();
    		String tmp = new String( formatHex.formatHex((byte[]) generalName));     		   		
    		if ( tmp.indexOf("2A83088C9B5508050501") > 0 ) {	//JPKI OID Common Name 1.2.392.200149.8.5.5.1
    			String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
    			System.out.println("署名者名前: " + tmp1) ; 
    		}
    	 	if ( tmp.indexOf("2A83088C9B5508050505") > 0 ) {	//JPKI OID Address 1.2.392.200149.8.5.5.5
    			String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
    			System.out.println("署名者住所: " + tmp1) ; 
    		}
    	} // else
    }  else if ( (Integer) alternativeName.get(0) == 4 ) { //-- added for Distinguish Name printing
    	System.out.println(" DN=\"" + (String) alternativeName.get(1) + "\"");
    }
  } // for
} 
} catch (CertificateParsingException e) { System.out.println("CertificateParsing ");}
//-- SubjectAltName end
//-- Validate certification
boolean isValidated;
try {
	String keyStoreFilePath = "/Library/Java/JavaVirtualMachines/jdk-22.jdk/Contents/Home/lib/security/cacerts";
	File keyStoreFile = new File(keyStoreFilePath);
	FileInputStream keyStoreStream = new FileInputStream(keyStoreFile);
	KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
	keyStore.load(keyStoreStream,"changeit".toCharArray());
	PKIXParameters parms = new PKIXParameters(keyStore);
	// parms.setRevocationEnabled(false);
	parms.setPolicyQualifiersRejected(false);
	
	CertPathValidator certValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); // PKIX
	
	PKIXRevocationChecker rc = (PKIXRevocationChecker)certValidator.getRevocationChecker();
	// rc.setOptions(EnumSet.of(Option.SOFT_FAIL));
	parms.addCertPathChecker(rc);
			
	ArrayList<Certificate> start = new ArrayList<>();
    start.add(cert);
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    CertPath certPath = certFactory.generateCertPath(start);
    certValidator.validate(certPath, parms);
    isValidated = true;
  } catch (KeyStoreException | IOException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException | CertPathValidatorException  e) {
	System.out.println("Cannot validate certificate.\r\n	Error is : " + e.getMessage() );
//        System.out.println("Certificate" + cert.toString());
    isValidated = false; // for debuging
}
System.out.println("CertPathValidator with OCSP -> " + isValidated );
//-- Validate certification end

 						if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
							return new SimpleKeySelectorResult(pk);
						}
					}
				}
				if (xmlStructure instanceof KeyValue) {
					PublicKey pk = null;
					try {
						pk = ((KeyValue) xmlStructure).getPublicKey();
					} catch (KeyException ke) {
						throw new KeySelectorException(ke);
					}
					// make sure algorithm is compatible with method
					if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
						return new SimpleKeySelectorResult(pk);
					}
				}
			}
			throw new KeySelectorException("No KeyValue element found!");
		}

		// @@@FIXME: this should also work for key types other than DSA/RSA
		static boolean algEquals(String algURI, String algName ) {
//-- added
System.out.println("Algorithm: " + algURI );
			if (algName.equalsIgnoreCase("DSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
				return true;
			} else if (algName.equalsIgnoreCase("RSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA256)) {
				return true;
//-- added sha1
			} else if (algName.equalsIgnoreCase("RSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
				return true;
			} else {
				return false;
			}
		}
	}

	private static class SimpleKeySelectorResult implements KeySelectorResult {
		private PublicKey pk;

		SimpleKeySelectorResult(PublicKey pk) {
			this.pk = pk;
		}

		public Key getKey() {
			return pk;
		}
	}
}
信頼する証明書を保存するキーストアファイルのパスは、適宜設定が必要(緑文字部分)。

申請用総合ソフトの公文書
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Users/someone/Documents/ShinseiyoSogoSoft/申請案件/2/取得公文書/complete_0001/complete_0001.xml 
setBaseURI-> file:///Users/someone/Documents/ShinseiyoSogoSoft/申請案件/2/取得公文書/complete_0001/
Signature block(s) = 1

Signature id = moj.go.jp9999999999999
Find BODY element and setIdAttibute="ID"
X509Certificate Subject -> CN=Registrar99,OU=XXXX Branch Bureau,OU=Nagano District Legal Affairs Bureau,OU=Ministry of Justice,O=Japanese Government,C=JP
X509Certificate NotAfter -> Tue Jan 14 23:59:59 JST 2025
DN="CN=登記官99,OU=XX支局,OU=長野地方法務局,OU=法務省,O=日本国政府,C=JP"
Cannot validate certificate. Error is : Path does not chain with any of the trust anchors
CertPathValidator with OCSP -> false
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)

JAVAのキーストアにGPKIのルート証明書が無いのでエラーとなる。GPKIのルート証明書をキーストアにインストールする。
https://www.gpki.go.jp
官職認証局
sudo keytool -importcert -cacerts -noprompt -trustcacerts -alias JP-OFFICIAL-CA -file /Users/someone/Downloads/OSCAroot.der
sudo keytool -importcert -cacerts -noprompt -trustcacerts -alias JP-OFFICIAL-CA1 -file /Users/someone/Downloads/OSCAroot_1.der
sudo keytool -importcert -cacerts -noprompt -trustcacerts -alias JP-OFFICIAL-CA2 -file /Users/someone/Downloads/OSCAroot_2.der
ルート証明書をインストール後、再度検証する
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Users/someone/Documents/ShinseiyoSogoSoft/申請案件/2/取得公文書/complete_0001/complete_0001.xml 
setBaseURI-> file:///Users/someone/Documents/ShinseiyoSogoSoft/申請案件/2/取得公文書/complete_0001/
Signature block(s) = 1

Signature id = moj.go.jp9999999999999
Find BODY element and setIdAttibute="ID"
X509Certificate Subject -> CN=Registrar99,OU=XXXX Branch Bureau,OU=Nagano District Legal Affairs Bureau,OU=Ministry of Justice,O=Japanese Government,C=JP
X509Certificate NotAfter -> Tue Jan 14 23:59:59 JST 2025
DN="CN=登記官99,OU=XX支局,OU=長野地方法務局,OU=法務省,O=日本国政府,C=JP"
Cannot validate certificate. Error is : Certificate does not specify OCSP responder
CertPathValidator with OCSP -> false
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)

署名に使われた証明書は、有効期間内だが失効確認が出来ないためエラーとなる。ルート証明書には、CRLの配布ポイント記載されていない。
CRLの配布ポイントを明示し利用できるようにしてもらいたい。
「申請用総合ソフト」を使用して電子署名の検証をしてみる。証明書の有効性が確認されている。

「申請用総合ソフト」(バージョン:8.4A ( 8.4.1.19 ))の「公文書の検証」では、 公的個人認証の電子署名証明書の有効性確認も可能。
複数人が電子署名したPDFファイルの場合、最初の一人目だけ検証が出来る。
「2番目に署名した者」の検証は、検証対象のフォルダーをコピーし署名ファイルのSignatureブロックをエディターで入れ替えると出来るが手数が掛かる。
2人の署名した署名フォルダ
test1 ---- test1.pdf
        -- test1.xml

コピーした署名フォルダ
test1Copy ---- test1.pdf
            -- test1.xml
「test1Copy/test1.xml」のSignature順番をを入れ替える
<?xml version="1.0" encoding="utf-8"?><PDF署名 version="1.00">
<Signature Id="Signature" xmlns="http://www.w3.org/2000/09/xmldsig#">・・・</Signature>
<Signature Id="Signature1" xmlns="http://www.w3.org/2000/09/xmldsig#">・・・</Signature>
</PDF署名>
から
<?xml version="1.0" encoding="utf-8"?><PDF署名 version="1.00">
<Signature Id="Signature1" xmlns="http://www.w3.org/2000/09/xmldsig#">・・・</Signature>
<Signature Id="Signature" xmlns="http://www.w3.org/2000/09/xmldsig#">・・・</Signature>
</PDF署名>
この署名フォルダ「test1Copy」を検証すると2番目の署名を確認出来る


eTax用の「医療費控除申請用データファイル」(健康保険組合から入手)
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Volumes/home/確定申告/令和5年度-2023//IK2023999999999900099999_01.xml 
setBaseURI-> file:////Volumes/home/確定申告/令和5年度-2023/
Signature block(s) = 1

Signature id = _99999999999999999999999999999
Find TEG700 element and setIdAttribute="id"
X509Certificate Subject -> CN=Tarou Urashima,OU=SA000000009999,OU=DIACERT Service,O=DIACERT CA,C=JP
X509Certificate NotAfter -> Wed Dec 31 23:59:59 JST 2025
DN="CN=浦島 太郎,2.5.4.97=#0c104a434e39393939393939393939393939,O=健康保険組合,L=新宿区河田町一丁目1番1号,ST=東京都,C=JP"
Cannot validate certificate. 
        Error is: Path does not chain with any of the trust anchors
CertPathValidator with OCSP -> false
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)

「DIACERT CA」は、民間ルート証明書だが日本政府公認の「おれおれCA」。OCSPによるCRL配布がされている。
トラスト・キーストアに組み込む
https://www.diacert.jp
sudo keytool -importcert -cacerts -noprompt -trustcacerts -alias DIACERT-CA -file /Users/someone/Downloads/DIACERTCA-G3.cer
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Volumes/home/確定申告/令和5年度-2023//IK2023999999999900099999_01.xml 
setBaseURI-> file:////Volumes/home/確定申告/令和5年度-2023/
Signature block(s) = 1

Signature id = _99999999999999999999999999999
Find TEG700 element and setIdAttribute="id"
X509Certificate Subject -> CN=Tarou Urashima,OU=SA000000009999,OU=DIACERT Service,O=DIACERT CA,C=JP
X509Certificate NotAfter -> Wed Dec 31 23:59:59 JST 2025
DN="CN=浦島 太郎,2.5.4.97=#0c104a434e39393939393939393939393939,O=健康保険組合,L=新宿区河田町一丁目1番1号,ST=東京都,C=JP"
Cannot validate certificate. 
        Error is: critical policy qualifiers present in certificate
CertPathValidator with OCSP -> false
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)

「parms.setPolicyQualifiersRejected(false);」で対応する
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Volumes/home/確定申告/令和5年度-2023//IK2023999999999900099999_01.xml 
setBaseURI-> file:////Volumes/home/確定申告/令和5年度-2023/
Signature block(s) = 1

Signature id = _99999999999999999999999999999
Find TEG700 element and setIdAttribute="id"
X509Certificate Subject -> CN=Tarou Urashima,OU=SA000000009999,OU=DIACERT Service,O=DIACERT CA,C=JP
X509Certificate NotAfter -> Wed Dec 31 23:59:59 JST 2025
DN="CN=浦島 太郎,2.5.4.97=#0c104a434e39393939393939393939393939,O=健康保険組合,L=新宿区河田町一丁目1番1号,ST=東京都,C=JP"
CertPathValidator with OCSP -> true
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation
(一部データを秘匿)

CRLのディストリビューションポイントへのパケットをキャプチャ確認する


 



 

法務省 不動産登記の電子署名をValidate.javaで検証してみた
法務省 不動産登記の電子署名を確認してみた
法務省 不動産登記の複数人電子署名をValidate.javaで検証してみた
法務省 不動産登記の電子署名用証明書をValidate.javaで検証してみた
 

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

法務省 不動産登記の複数人電子署名をValidate.javaで検証してみた

2024-07-04 14:00:00 | 

法務省 不動産登記の電子署名をValidate.javaで検証してみた」では、一人がPDF文書に電子署名した署名を検証した。
申請総合ソフトのPDF電子署名は、複数人が同一文書に電子署名する事が出来る。複数人が電子署名を行うとXML署名ファイル内に複数人の署名ブロック(Signature ブロック)が生成される。一人の場合は、SignatureブロックのidアトリビュートがSignatureで生成される。二人目は、idアトリビュートがSignature1、3人目は、idアトリビュートがSignature2と複数人のSignatureブロックを内包したXML電子署名ファイルが生成される。
法務省 不動産登記の電子署名をValidate.javaで検証してみた」では、最初の一人の電子署名だけが確認される。
複数人の電子署名を検証できるようプログラムを修正してみた。

複数人の電子署名に対応したValidate.java
import java.io.File;
import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException; //--added
import java.util.Iterator;
import java.util.List;
import java.util.HexFormat; //--added
import java.util.Arrays; //--added
import java.util.Collection; //--added
import java.lang.reflect.Array; //--added

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;  //-- added

/**
 * This is a simple example of validating an XML Signature using the JSR 105
 * API. It assumes the key needed to validate the signature is contained in a
 * KeyValue KeyInfo.
 */
public class Validate {

	//
	// Synopsis: java Validate [document]
	//
	// where "document" is the name of a file containing the XML document
	// to be validated.
	//
	public static void main(String[] args) throws Exception {
		String fileName = args[0];

		// Instantiate the document to be validated
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		Document doc = dbf.newDocumentBuilder().parse(
				new FileInputStream(fileName));

		// Find Signature element
		NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,
				"Signature");
		if (nl.getLength() == 0) {
			throw new Exception("Cannot find Signature element");
		}

//-- added(外部ファイルのベースディレクトリを指定)**移動
String cpath = new File(fileName).getParent(); //--**移動
cpath = "file://" + cpath.replace('\\', '/') + "/"; //--**移動
System.out.println("setBaseURI-> " + cpath); //--**移動
//--added for multiple signature loop
System.out.println("Signature block(s) = " + nl.getLength() );

for ( int k = 0; k  nl.getLength() ; k++ ) {
String SignatureId = nl.item(k).getAttributes().item(0).getNodeValue() ;
System.out.println("\nSignature id = " + SignatureId );

		// Create a DOM XMLSignatureFactory that will be used to unmarshal the
		// document containing the XMLSignature
		XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

		// Create a DOMValidateContext and specify a KeyValue KeySelector
		// and document context
		DOMValidateContext valContext = new DOMValidateContext(
				new KeyValueKeySelector(), nl.item(k)); //-- 変更
//--added
valContext.setBaseURI(cpath);
//-- added(署名対象XMLエレメントのアトリビュートを指定)**修正
// 申請用総合ソフト 登録通知
NodeList tg = doc.getElementsByTagName("BODY");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "ID");
    System.out.println("Find BODY element and setIdAttibute=\"ID\"");
}
// 医療費通知(保険組合)
tg = doc.getElementsByTagName("TEG700");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find TEG700 element and setIdAttribute=\"id\"");
}
// e-Tax( 所得税 RKO0010. 証明書更新 PTE0010, 電子申請等証明書 TEB120
tg = doc.getElementsByTagName("RKO0010");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find RKO0010 element and setIdAttribute=\"id\"");
}
tg = doc.getElementsByTagName("PTE0010");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find PTE0010 element and setIdAttribute=\"id\"");
}
tg = doc.getElementsByTagName("TEB120");
if ( tg.getLength() != 0 ) {
    valContext.setIdAttributeNS( (Element)tg.item(0), null, "id");
    System.out.println("Find TEB120 element and setIdAttribute=\"id\"");
}

		// unmarshal the XMLSignature
		XMLSignature signature = fac.unmarshalXMLSignature(valContext);

		// Validate the XMLSignature (generated above)
		boolean coreValidity = signature.validate(valContext);

		// Check core validation status
		if (coreValidity == false) {
			System.err.println("Signature failed core validation");
			boolean sv = signature.getSignatureValue().validate(valContext);
			System.out.println("signature validation status: " + sv);
			// check the validation status of each Reference
			Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
			for (int j = 0; i.hasNext(); j++) {
				boolean refValid = ((Reference) i.next()).validate(valContext);
				System.out.println("ref[" + j + "] validity status: "
						+ refValid);
			}
		} else {
			System.out.println("Signature passed core validation");
		}
//-- added for multiple signature loop
}
	}

	/**
	 * KeySelector which retrieves the public key out of the KeyValue element
	 * and returns it. NOTE: If the key algorithm doesn't match signature
	 * algorithm, then the public key will be ignored.
	 */
	private static class KeyValueKeySelector extends KeySelector {
		public KeySelectorResult select(KeyInfo keyInfo,
				KeySelector.Purpose purpose, AlgorithmMethod method,
				XMLCryptoContext context) throws KeySelectorException {
			
			if (keyInfo == null) {
				throw new KeySelectorException("Null KeyInfo object!");
			}
			SignatureMethod sm = (SignatureMethod) method;
			List<?> list = keyInfo.getContent();

			for (int i = 0; i < list.size(); i++) {
				XMLStructure xmlStructure = (XMLStructure) list.get(i);
				if (xmlStructure instanceof X509Data) {
					PublicKey pk = null;
					List<?> l = ((X509Data) xmlStructure).getContent();
					if (l.size() > 0) {
						X509Certificate cert = (X509Certificate) l.get(0);
						pk = cert.getPublicKey();
//-- added(署名公開鍵のDNと有効期限を表示)
System.out.println("X509Certificate Subject -> " + cert.getSubjectX500Principal().getName() );
System.out.println("X509Certificate NotAfter -> " + cert.getNotAfter().toString() );
//-- added for SubjectAltName
try { Collection<List<?>> alternativeNames = cert.getSubjectAlternativeNames();
if ( alternativeNames != null ) {
    for ( List<?> alternativeName : alternativeNames ) {
    	if (  (Integer) alternativeName.get(0) == 0 ) {
    	Object generalName = alternativeName.get(1);
    	if ( generalName instanceof String) {
    		System.out.println(generalName);
    	} else {
    		HexFormat formatHex = HexFormat.ofDelimiter("").withUpperCase();
    		String tmp = new String( formatHex.formatHex((byte[]) generalName));     		   		
    		if ( tmp.indexOf("2A83088C9B5508050501") > 0 ) {	//JPKI OID Common Name 1.2.392.200149.8.5.5.1
    			String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
    			System.out.println("署名者名前: " + tmp1) ; 
    		}
    		if ( tmp.indexOf("2A83088C9B5508050505") > 0 ) {	//JPKI OID Address 1.2.392.200149.8.5.5.5
    			String tmp1 = new String(Arrays.copyOfRange((byte[]) generalName,18, Array.getLength(generalName)) );
    			System.out.println("署名者住所: " + tmp1) ; 
    		}
    	} // else
    }
  } // for
} 
} catch (CertificateParsingException e) { System.out.println("CertificateParsing ");}


						if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
							return new SimpleKeySelectorResult(pk);
						}
					}
				}
				if (xmlStructure instanceof KeyValue) {
					PublicKey pk = null;
					try {
						pk = ((KeyValue) xmlStructure).getPublicKey();
					} catch (KeyException ke) {
						throw new KeySelectorException(ke);
					}
					// make sure algorithm is compatible with method
					if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
						return new SimpleKeySelectorResult(pk);
					}
				}
			}
			throw new KeySelectorException("No KeyValue element found!");
		}

		// @@@FIXME: this should also work for key types other than DSA/RSA
		static boolean algEquals(String algURI, String algName) {
//-- added(署名に指定されたアルゴリズム)
System.out.println("Algorithm: " + algURI );
			if (algName.equalsIgnoreCase("DSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
				return true;
			} else if (algName.equalsIgnoreCase("RSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA256)) {
				return true;
//-- added (sha1も対象とする設定)
			} else if (algName.equalsIgnoreCase("RSA")
					&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
				return true;
			} else {
				return false;
			}
		}
	}

	private static class SimpleKeySelectorResult implements KeySelectorResult {
		private PublicKey pk;

		SimpleKeySelectorResult(PublicKey pk) {
			this.pk = pk;
		}

		public Key getKey() {
			return pk;
		}
	}
}
」半角文字を使うとブログ表示が乱れるので全角文字で記載している。
署名に使用された証明書の「SubjectAltName」に記載された「名前」と「住所」を表記するようにした。ASN.1で抽出されるのでASN.1を扱うライブラリィを使うのが簡単だが、JDKの標準パッケージだけで抽出して表示してみた。
署名に使用された証明書の有効性確認が出来ていない。

二つの署名ファイル検証
$ java -Djava.util.logging.config.file=./logging.properties -Djava.security.properties=./java.security Validate /Users/someone/Documents/関連/登記関連/署名テスト/複数署名/協議書/協議書/協議書.xml 
setBaseURI-> file:///Users/someone/Documents/関連/登記関連/署名テスト/複数署名/協議書/協議書/
Signature block(s) = 2

Signature id = Signature
X509Certificate Subject -> CN=202299999999999999999999993B,L=XXXX,L=Tokyo-to,C=JP
X509Certificate NotAfter -> Mon Jan 99 23:59:59 JST 2027
署名者名前: 松山 一郎
署名者住所: 東京都目黒区三田一丁目4番1号
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation

Signature id = Signature1
X509Certificate Subject -> CN=202299999999999999999999991B,L=XXXX,L=Tokyo-to,C=JP
X509Certificate NotAfter -> Thu Feb 99 23:59:59 JST 2027
署名者名前: 鈴木 洋子
署名者住所: 東京都文京区関口ニ丁目10番8号
Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Signature passed core validation

(一部データを秘匿)


 

法務省 不動産登記の電子署名をValidate.javaで検証してみた
法務省 不動産登記の電子署名を確認してみた
法務省 不動産登記の複数人電子署名をValidate.javaで検証してみた
法務省 不動産登記の電子署名用証明書をValidate.javaで検証してみた
 

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

ゆうちょ銀行 明細csvファイル用 Felica2moneyのcsv変換定義ファイル

2024-07-01 08:00:00 | 
ゆうちょ銀行の「ゆうちょダイレクト」を使い取引明細をCSVでダウンロードした。Felica2MoneyでCSVからMoneyファイルに変換しようとしたら未定義だったので作成した。


%Userprofile%¥AppData¥Local¥tmurakam.org¥Felica2Money¥YucyoCSV.xml
<?xml version="1.0" encoding="UTF-8"?>
<CsvRules>
  <Version>20240629.01</Version>
  <Rule>
    <Ident>YubinCyokin</Ident>
    <Name>郵便貯金</Name>
    <BankId>9900</BankId>
    <FirstLine>取引日,入出金明細ID,受入金額(円),払出金額(円),詳細1,詳細2,現在(貸付)高,</FirstLine>
    <Format>Date,Dummy,Income,Outgo,Desc,Dummy,Balance,Dummy</Format>
    <Order>Ascent</Order>
  </Rule>
</CsvRules>


 

りそな銀行の明細ofxファイルがダウンロード出来なくなった
三井住友銀行 明細csvファイル用 Felica2moneyのcsv変換定義ファイル
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする