├── .codebeatignore ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── _exp ├── mynag │ ├── Makefile │ ├── README.md │ ├── bindata.go │ ├── mynag.go │ ├── mynag.sh │ └── usagi.png ├── mynaqt │ ├── Makefile │ ├── about.go │ ├── bindata.go │ ├── cert.go │ ├── cms.go │ ├── mynaqt.go │ └── prompt.go └── mynaui │ ├── Makefile │ └── mynaui.go ├── asn1 ├── asn1.go ├── asn1_test.go ├── common.go ├── marshal.go └── marshal_test.go ├── ci ├── myna-build-linux-amd64.sh └── myna-build-windows-x64.sh ├── cmd ├── jpki.go ├── jpki_cms.go ├── pin.go ├── root.go ├── test.go ├── text.go ├── tool.go └── visual.go ├── go.mod ├── go.sum ├── libmyna ├── apdu.go ├── apdu_test.go ├── api.go ├── common.go ├── errors.go ├── jpki_ap.go ├── reader.go ├── text_ap.go ├── utils.go ├── validate.go ├── version.go └── visual_ap.go ├── main.go ├── myna.sh └── mynaqt.png /.codebeatignore: -------------------------------------------------------------------------------- 1 | exp/** 2 | mynaqt/** 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | myna 2 | myna.exe 3 | 4 | .idea 5 | .vscode -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | VERSION: "0.4.2" 3 | 4 | cache: 5 | paths: 6 | - cache 7 | 8 | before_script: 9 | - pwd 10 | 11 | build-linux-amd64: 12 | image: hamano4.lan.osstech.co.jp:5000/ci/myna-devel-linux-amd64:latest 13 | script: 14 | - ./ci/myna-build-linux-amd64.sh 15 | variables: 16 | DIST_DIR: myna-$VERSION-linux-amd64 17 | artifacts: 18 | paths: 19 | - $DIST_DIR 20 | 21 | build-windows-x64: 22 | image: hamano4.lan.osstech.co.jp:5000/ci/myna-devel-windows-x64:latest 23 | script: 24 | - ./ci/myna-build-windows-x64.sh 25 | variables: 26 | DIST_DIR: myna-$VERSION-windows-x64 27 | artifacts: 28 | paths: 29 | - $DIST_DIR 30 | 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - "1.12.13" 5 | env: 6 | - GO111MODULE=on 7 | addons: 8 | apt: 9 | packages: 10 | - libpcsclite-dev 11 | os: 12 | - linux 13 | - osx 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 HAMANO Tsukasa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | native: 3 | go build 4 | 5 | linux: 6 | GOOS=linux GOARCH=amd64 go build -o myna_linux 7 | 8 | windows: 9 | GOOS=windows GOARCH=amd64 go build -o myna.exe 10 | 11 | osx: 12 | GOOS=darwin GOARCH=amd64 go build -o myna 13 | 14 | clean: 15 | rm -rf myna myna.exe 16 | 17 | get-deps: 18 | go mod download 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | myna - マイナンバーカード・ユーティリティ 2 | ========================================= 3 | 4 | [![Build Status](https://travis-ci.org/jpki/myna.svg?branch=master)](https://travis-ci.org/jpki/myna) 5 | [![codebeat](https://codebeat.co/badges/0bbab46f-5683-4848-92e7-eed36e660b0f)](https://codebeat.co/projects/github-com-jpki-myna-master) 6 | [![Go Report Card](https://goreportcard.com/badge/jpki/myna)](https://goreportcard.com/report/jpki/myna) 7 | 8 | ## できること 9 | 10 | - 券面確認AP・券面入力補助APの読み取り 11 | - 公的個人認証の各種証明書の読み取り 12 | - 公的個人認証の署名 13 | - 各種PINステータスの確認 14 | - 各種PINの変更 15 | 16 | ## 動作プラットホーム 17 | 18 | - Windows 19 | - OS X 20 | - Linux 21 | - FreeBSD 22 | 23 | ## ダウンロード 24 | 25 | 26 | 27 | ## 使い方 28 | 29 | 詳しくは `myna --help` や `サブコマンド --help` `孫コマンド --help` を実行してください。 30 | 31 | ~~~ 32 | Usage: 33 | myna [command] 34 | 35 | Available Commands: 36 | card 券面APおよび券面事項入力補助AP 37 | jpki 公的個人認証関連コマンド 38 | pin PIN関連操作 39 | test リーダーの動作確認 40 | help Help about any command 41 | ~~~ 42 | 43 | ### 4属性を取得 44 | 45 | ~~~ 46 | $ myna card attr 47 | ~~~ 48 | 49 | ### 顔写真を取得 50 | 51 | ~~~ 52 | $ myna card photo -o photo.jpg 53 | ~~~ 54 | 55 | ### PINのステータスを確認 56 | 57 | ~~~ 58 | $ myna pin status 59 | ~~~ 60 | 61 | 62 | ### JPKI認証用証明書を取得 63 | 64 | ~~~ 65 | $ myna jpki cert auth 66 | ~~~ 67 | 68 | ### JPKI署名用証明書を取得 69 | 70 | ~~~ 71 | $ myna jpki cert sign 72 | ~~~ 73 | 74 | ### JPKI署名用証明書でCMS署名 75 | 76 | ~~~ 77 | $ myna jpki cms sign -i 署名対象ファイル -o 署名ファイル 78 | ~~~ 79 | 80 | ### JPKI署名用CA証明書でCMS署名を検証 81 | 82 | ~~~ 83 | $ myna jpki cms verify 署名ファイル 84 | ~~~ 85 | 86 | OpenSSLコマンドで検証 87 | 88 | ~~~ 89 | $ openssl cms -verify -CAfile 署名用CA証明書 -inform der -in 署名ファイル 90 | ~~~ 91 | 92 | 93 | ## GUI版(バージョン0.2) 94 | 95 | ![mynaqt](mynaqt.png) 96 | 97 | ## ビルド環境 98 | 99 | golang 1.7 or later 100 | 101 | ## mynaコマンドのビルド・インストール 102 | 103 | ~~~ 104 | % go get -u github.com/jpki/myna 105 | ~~~ 106 | 107 | 108 | ### 依存パッケージのインストール 109 | 110 | - Debian/Ubuntu 111 | 112 | ~~~ 113 | # apt-get install libpcsclite-dev 114 | ~~~ 115 | 116 | - RHEL/CentOS 117 | 118 | ~~~ 119 | # yum install pcsc-lite-devel 120 | ~~~ 121 | 122 | - Windows 123 | 124 | ~~~ 125 | PS> choco install -y git golang 126 | ~~~ 127 | 128 | - OSX 129 | 130 | ~~~ 131 | # brew install go 132 | ~~~ 133 | 134 | - FreeBSD 135 | 136 | ~~~ 137 | # pkg install pcsc-lite ccid pkgconf 138 | ~~~ 139 | -------------------------------------------------------------------------------- /_exp/mynag/Makefile: -------------------------------------------------------------------------------- 1 | 2 | mynag: mynag.go bindata.go 3 | go build 4 | 5 | bindata.go: 6 | go-bindata usagi.png ../LICENSE 7 | 8 | get-deps: 9 | go get -u github.com/mattn/go-gtk/gtk 10 | 11 | clean: 12 | rm -rf mynag 13 | -------------------------------------------------------------------------------- /_exp/mynag/README.md: -------------------------------------------------------------------------------- 1 | # mynag 2 | 3 | obsoluteです、mynaqtを使ってください 4 | -------------------------------------------------------------------------------- /_exp/mynag/mynag.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jpki/myna/libmyna" 4 | 5 | import ( 6 | "crypto/x509" 7 | _ "errors" 8 | "fmt" 9 | "github.com/mattn/go-gtk/gdkpixbuf" 10 | "github.com/mattn/go-gtk/glib" 11 | "github.com/mattn/go-gtk/gtk" 12 | "github.com/urfave/cli" 13 | "os" 14 | "strings" 15 | ) 16 | 17 | func main() { 18 | app := cli.NewApp() 19 | app.Name = "mynag" 20 | app.Description = "マイナクライアント(GUI)" 21 | app.Author = "HAMANO Tsukasa" 22 | app.Email = "hamano@osstech.co.jp" 23 | app.Version = libmyna.Version 24 | app.Action = mynag 25 | app.Flags = []cli.Flag{ 26 | cli.BoolFlag{ 27 | Name: "debug, d", 28 | Usage: "詳細出力", 29 | }, 30 | } 31 | app.Run(os.Args) 32 | } 33 | 34 | func mynag(c *cli.Context) error { 35 | gtk.Init(&os.Args) 36 | window := gtk.NewWindow(gtk.WINDOW_TOPLEVEL) 37 | window.SetPosition(gtk.WIN_POS_CENTER) 38 | window.SetTitle(c.App.Description) 39 | window.SetIconName("application-certificate") 40 | window.Connect("destroy", func(ctx *glib.CallbackContext) { 41 | gtk.MainQuit() 42 | }) 43 | 44 | // create menu 45 | menu := gtk.NewMenuBar() 46 | menuHelp := gtk.NewMenuItemWithMnemonic("Menu") 47 | menu.Append(menuHelp) 48 | 49 | menuSub := gtk.NewMenu() 50 | menuHelp.SetSubmenu(menuSub) 51 | menuItemAbout := gtk.NewMenuItemWithMnemonic("About") 52 | menuItemAbout.Connect("activate", onAbout, c) 53 | menuSub.Append(menuItemAbout) 54 | 55 | menuItemQuit := gtk.NewMenuItemWithMnemonic("Quit") 56 | menuItemQuit.Connect("activate", onQuit, c) 57 | menuSub.Append(menuItemQuit) 58 | 59 | // create button box 60 | boxButton := gtk.NewVBox(false, 1) 61 | buttonTest := gtk.NewButtonWithLabel("動作確認") 62 | buttonTest.Clicked(onTest, c) 63 | boxButton.Add(buttonTest) 64 | 65 | buttonCardInfo := gtk.NewButtonWithLabel("券面事項確認") 66 | buttonCardInfo.Clicked(onCardInfo, c) 67 | boxButton.Add(buttonCardInfo) 68 | 69 | buttonShowCert := gtk.NewButtonWithLabel("証明書表示") 70 | buttonShowCert.Clicked(onShowCert, c) 71 | boxButton.Add(buttonShowCert) 72 | 73 | buttonSign := gtk.NewButtonWithLabel("署名") 74 | buttonSign.Clicked(onSign, c) 75 | boxButton.Add(buttonSign) 76 | 77 | buttonPinStatus := gtk.NewButtonWithLabel("PINステータス") 78 | buttonPinStatus.Clicked(onPinStatus, c) 79 | boxButton.Add(buttonPinStatus) 80 | 81 | // create hbox 82 | hbox := gtk.NewHBox(false, 1) 83 | //dir, _ := filepath.Split(os.Args[0]) 84 | //imagefile := filepath.Join(dir, "usagi.png") 85 | //imageLogo := gtk.NewImageFromFile(imagefile) 86 | imageData, _ := Asset("usagi.png") 87 | imageBuf, _ := gdkpixbuf.NewPixbufFromData(imageData) 88 | imageLogo := gtk.NewImageFromPixbuf(imageBuf) 89 | hbox.Add(imageLogo) 90 | hbox.Add(boxButton) 91 | 92 | // create main box 93 | boxMain := gtk.NewVBox(false, 1) 94 | boxMain.PackStart(menu, false, false, 0) 95 | boxMain.Add(hbox) 96 | 97 | window.Add(boxMain) 98 | //window.SetSizeRequest(400, 400) 99 | window.ShowAll() 100 | gtk.Main() 101 | return nil 102 | } 103 | 104 | func onAbout(ctx *glib.CallbackContext) { 105 | c := ctx.Data().(*cli.Context) 106 | dialog := gtk.NewAboutDialog() 107 | dialog.SetProgramName(c.App.Name) 108 | dialog.SetVersion(c.App.Version) 109 | dialog.SetAuthors([]string{c.App.Author}) 110 | comments := ` 111 | このソフトウェアはMITライセンスで配布されます。 112 | GTK+ は LGPLライセンス 113 | mattn ware の go-gtk 114 | かわいいウサギのイラストはいらすとやの著作物です 115 | ` 116 | dialog.SetComments(comments) 117 | dialog.SetWebsite("https://github.com/jpki/myna") 118 | license, _ := Asset("../LICENSE") 119 | dialog.SetLicense(string(license)) 120 | dialog.SetWrapLicense(true) 121 | imageData, _ := Asset("usagi.png") 122 | imageBuf, _ := gdkpixbuf.NewPixbufFromData(imageData) 123 | dialog.SetLogo(imageBuf) 124 | dialog.Run() 125 | dialog.Destroy() 126 | } 127 | 128 | func onQuit(ctx *glib.CallbackContext) { 129 | gtk.MainQuit() 130 | } 131 | 132 | func showErrorMsg(msg string) { 133 | dialog := gtk.NewMessageDialog( 134 | nil, 135 | gtk.DIALOG_MODAL, 136 | gtk.MESSAGE_INFO, 137 | gtk.BUTTONS_OK, 138 | msg) 139 | dialog.Response(func() { 140 | dialog.Destroy() 141 | }) 142 | dialog.Run() 143 | } 144 | 145 | func showMsg(msg string) { 146 | dialog := gtk.NewMessageDialog( 147 | nil, 148 | gtk.DIALOG_MODAL, 149 | gtk.MESSAGE_INFO, 150 | gtk.BUTTONS_OK, 151 | msg) 152 | dialog.Response(func() { 153 | dialog.Destroy() 154 | }) 155 | dialog.Run() 156 | } 157 | 158 | func onTest(ctx *glib.CallbackContext) { 159 | c := ctx.Data().(*cli.Context) 160 | err := libmyna.CheckCard(c) 161 | var msg string 162 | if err != nil { 163 | msg = err.Error() 164 | } else { 165 | msg = "正常です。" 166 | } 167 | showErrorMsg(msg) 168 | } 169 | 170 | func popupPrompt(title string) string { 171 | dialog := gtk.NewDialog() 172 | dialog.SetTitle(title) 173 | vbox := dialog.GetVBox() 174 | label := gtk.NewLabel(title) 175 | vbox.Add(label) 176 | pinEntry := gtk.NewEntry() 177 | pinEntry.SetVisibility(false) 178 | pinEntry.Connect("activate", func() { 179 | dialog.Hide() 180 | dialog.Response(gtk.RESPONSE_OK) 181 | }) 182 | vbox.Add(pinEntry) 183 | dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 184 | dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK) 185 | dialog.SetDefaultResponse(gtk.RESPONSE_OK) 186 | dialog.ShowAll() 187 | res := dialog.Run() 188 | pin := pinEntry.GetText() 189 | dialog.Destroy() 190 | if res == gtk.RESPONSE_OK { 191 | return pin 192 | } else { 193 | return "" 194 | } 195 | } 196 | 197 | func popupPinPrompt() string { 198 | return popupPrompt("暗証番号(4桁)") 199 | } 200 | 201 | func popupPasswordPrompt() string { 202 | pass := popupPrompt("署名用パスワード(6-16桁)") 203 | if len(pass) < 6 || 16 < len(pass) { 204 | showErrorMsg("署名用パスワード(6-16桁)を入力してください。") 205 | return "" 206 | } 207 | return strings.ToUpper(pass) 208 | } 209 | 210 | func onCardInfo(ctx *glib.CallbackContext) { 211 | c := ctx.Data().(*cli.Context) 212 | err := libmyna.CheckCard(c) 213 | if err != nil { 214 | showErrorMsg(err.Error()) 215 | return 216 | } 217 | pin := popupPinPrompt() 218 | if pin == "" { 219 | return 220 | } 221 | info, _ := libmyna.GetCardInfo(c, pin) 222 | var msg string 223 | msg += fmt.Sprintf("個人番号: %s\n", info["number"]) 224 | msg += fmt.Sprintf("氏名: %s\n", info["name"]) 225 | msg += fmt.Sprintf("住所: %s\n", info["address"]) 226 | msg += fmt.Sprintf("生年月日: %s\n", info["birth"]) 227 | msg += fmt.Sprintf("性別: %s", info["sex"]) 228 | showMsg(msg) 229 | } 230 | 231 | func onPinStatus(ctx *glib.CallbackContext) { 232 | c := ctx.Data().(*cli.Context) 233 | status, err := libmyna.GetPinStatus(c) 234 | if err != nil { 235 | showErrorMsg(err.Error()) 236 | return 237 | } 238 | var msg string 239 | msg += fmt.Sprintf("認証用PIN: のこり%d回\n", status["auth"]) 240 | msg += fmt.Sprintf("署名用PIN: のこり%d回\n", status["sign"]) 241 | msg += fmt.Sprintf("券面入力補助PIN: のこり%d回\n", status["card"]) 242 | msg += fmt.Sprintf("謎のPIN1: のこり%d回\n", status["unknown1"]) 243 | msg += fmt.Sprintf("謎のPIN2: のこり%d回", status["unknown2"]) 244 | showMsg(msg) 245 | } 246 | 247 | func onShowCert(ctx *glib.CallbackContext) { 248 | c := ctx.Data().(*cli.Context) 249 | rc := selectCert() 250 | var title string 251 | var ef string 252 | var pin string 253 | switch rc { 254 | case 0: 255 | // キャンセル 256 | return 257 | case 1: 258 | title = "認証用証明書" 259 | ef = "00 0A" 260 | case 2: 261 | title = "署名用証明書" 262 | ef = "00 01" 263 | pin = popupPasswordPrompt() 264 | if pin == "" { 265 | return 266 | } 267 | case 3: 268 | title = "認証用CA証明書" 269 | ef = "00 0B" 270 | case 4: 271 | title = "署名用CA証明書" 272 | ef = "00 02" 273 | } 274 | cert, err := libmyna.GetCert(c, ef, pin) 275 | if err != nil { 276 | showErrorMsg(err.Error()) 277 | return 278 | } 279 | popupCertDialog(title, cert) 280 | } 281 | 282 | func popupCertDialog(title string, cert *x509.Certificate) { 283 | dialog := gtk.NewDialog() 284 | defer dialog.Destroy() 285 | dialog.SetTitle(title) 286 | vbox := dialog.GetVBox() 287 | 288 | vbox.PackStart(gtk.NewLabel("Subject: "), false, false, 10) 289 | subjectEntry := gtk.NewEntry() 290 | subjectEntry.SetText(libmyna.Name2String(cert.Subject)) 291 | subjectEntry.SetWidthChars(64) 292 | vbox.PackStart(subjectEntry, false, false, 10) 293 | vbox.PackStart(gtk.NewLabel("Issuer: "), false, false, 10) 294 | issuerEntry := gtk.NewEntry() 295 | issuerEntry.SetText(libmyna.Name2String(cert.Issuer)) 296 | issuerEntry.SetWidthChars(64) 297 | vbox.PackStart(issuerEntry, false, false, 10) 298 | dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK) 299 | dialog.ShowAll() 300 | dialog.Run() 301 | } 302 | 303 | func selectCert() int { 304 | dialog := gtk.NewDialog() 305 | defer dialog.Destroy() 306 | dialog.SetTitle("証明書選択") 307 | vbox := dialog.GetVBox() 308 | radio1 := gtk.NewRadioButtonWithLabel(nil, "認証用証明書") 309 | radio2 := gtk.NewRadioButtonWithLabel(radio1.GetGroup(), "署名用証明書") 310 | radio3 := gtk.NewRadioButtonWithLabel(radio1.GetGroup(), "認証用CA証明書") 311 | radio4 := gtk.NewRadioButtonWithLabel(radio1.GetGroup(), "署名用CA証明書") 312 | 313 | vbox.PackStart(radio1, false, false, 10) 314 | vbox.PackStart(radio2, false, false, 10) 315 | vbox.PackStart(radio3, false, false, 10) 316 | vbox.PackStart(radio4, false, false, 10) 317 | 318 | dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 319 | dialog.AddButton(gtk.STOCK_OK, gtk.RESPONSE_OK) 320 | dialog.SetDefaultResponse(gtk.RESPONSE_OK) 321 | dialog.ShowAll() 322 | res := dialog.Run() 323 | if res == gtk.RESPONSE_OK { 324 | if radio1.GetActive() { 325 | return 1 326 | } else if radio2.GetActive() { 327 | return 2 328 | } else if radio3.GetActive() { 329 | return 3 330 | } else if radio4.GetActive() { 331 | return 4 332 | } else { 333 | return 0 334 | } 335 | } else { 336 | return 0 337 | } 338 | } 339 | 340 | func onSign(ctx *glib.CallbackContext) { 341 | c := ctx.Data().(*cli.Context) 342 | dialog := gtk.NewDialog() 343 | defer dialog.Destroy() 344 | dialog.SetTitle("署名") 345 | 346 | inputEntry := gtk.NewEntry() 347 | inputEntry.SetWidthChars(64) 348 | inputEntry.SetEditable(false) 349 | outputEntry := gtk.NewEntry() 350 | outputEntry.SetWidthChars(64) 351 | outputEntry.SetEditable(false) 352 | 353 | vbox := dialog.GetVBox() 354 | inputButton := gtk.NewButtonWithLabel("入力ファイル") 355 | inputButton.Clicked(func() { 356 | filedialog := gtk.NewFileChooserDialog( 357 | "ファイル選択", 358 | nil, 359 | gtk.FILE_CHOOSER_ACTION_OPEN, 360 | gtk.STOCK_OK, 361 | gtk.RESPONSE_OK, 362 | gtk.STOCK_CANCEL, 363 | gtk.RESPONSE_CANCEL) 364 | res := filedialog.Run() 365 | if res == gtk.RESPONSE_OK { 366 | filename := filedialog.GetFilename() 367 | inputEntry.SetText(filename) 368 | if outputEntry.GetText() == "" { 369 | outputEntry.SetText(filename + ".p7s") 370 | } 371 | } 372 | filedialog.Destroy() 373 | }) 374 | row := gtk.NewHBox(false, 1) 375 | row.Add(inputEntry) 376 | row.Add(inputButton) 377 | vbox.Add(row) 378 | 379 | outputButton := gtk.NewButtonWithLabel("出力ファイル") 380 | outputButton.Clicked(func() { 381 | filedialog := gtk.NewFileChooserDialog( 382 | "ファイル選択", 383 | nil, 384 | gtk.FILE_CHOOSER_ACTION_SAVE, 385 | gtk.STOCK_OK, 386 | gtk.RESPONSE_OK, 387 | gtk.STOCK_CANCEL, 388 | gtk.RESPONSE_CANCEL) 389 | res := filedialog.Run() 390 | if res == gtk.RESPONSE_OK { 391 | outputEntry.SetText(filedialog.GetFilename()) 392 | } 393 | filedialog.Destroy() 394 | }) 395 | row = gtk.NewHBox(false, 1) 396 | row.Add(outputEntry) 397 | row.Add(outputButton) 398 | vbox.Add(row) 399 | 400 | dialog.AddButton(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 401 | dialog.AddButton("署名", gtk.RESPONSE_OK) 402 | dialog.ShowAll() 403 | res := dialog.Run() 404 | if res != gtk.RESPONSE_OK { 405 | return 406 | } 407 | inFile := inputEntry.GetText() 408 | if inFile == "" { 409 | showErrorMsg("入力ファイルを指定してください") 410 | return 411 | } 412 | outFile := outputEntry.GetText() 413 | if outFile == "" { 414 | showErrorMsg("出力ファイルを指定してください") 415 | return 416 | } 417 | pass := popupPasswordPrompt() 418 | if pass == "" { 419 | return 420 | } 421 | 422 | err := libmyna.Sign(c, pass, inFile, outFile) 423 | if err != nil { 424 | showErrorMsg(err.Error()) 425 | return 426 | } 427 | showMsg("署名完了") 428 | } 429 | -------------------------------------------------------------------------------- /_exp/mynag/mynag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run *.go "$@" 4 | -------------------------------------------------------------------------------- /_exp/mynag/usagi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpki/myna/55ab7a81cca6130fd1cbb8def74ec6b9d598c20e/_exp/mynag/usagi.png -------------------------------------------------------------------------------- /_exp/mynaqt/Makefile: -------------------------------------------------------------------------------- 1 | 2 | mynaqt: *.go 3 | go build 4 | 5 | run: *.go 6 | go run $^ 7 | 8 | clean: 9 | rm -rf mynaqt 10 | 11 | -------------------------------------------------------------------------------- /_exp/mynaqt/about.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jpki/myna/libmyna" 6 | "github.com/therecipe/qt/core" 7 | "github.com/therecipe/qt/gui" 8 | "github.com/therecipe/qt/widgets" 9 | ) 10 | 11 | type AboutDialog struct { 12 | *widgets.QDialog 13 | } 14 | 15 | func NewAboutDialog() *AboutDialog { 16 | windowFlag := core.Qt__Window | core.Qt__WindowCloseButtonHint 17 | dialog := widgets.NewQDialog(nil, windowFlag) 18 | dialog.SetModal(true) 19 | dialog.SetMinimumSize2(400, 0) 20 | dialog.SetWindowTitle("マイナクライアント(GUI版)について") 21 | layout := widgets.NewQVBoxLayout() 22 | // add logo 23 | logoLabel := widgets.NewQLabel(nil, 0) 24 | logoData, err := Asset("usagi.png") 25 | if err != nil { 26 | return nil 27 | } 28 | pixmap := gui.NewQPixmap() 29 | pixmap.LoadFromData(string(logoData), uint(len(logoData)), 30 | "PNG", core.Qt__AutoColor) 31 | logoLabel.SetPixmap(pixmap) 32 | layout.AddWidget(logoLabel, 0, core.Qt__AlignCenter) 33 | 34 | // add version 35 | label := widgets.NewQLabel2( 36 | fmt.Sprintf("mynaqt %s", libmyna.Version), nil, 0) 37 | layout.AddWidget(label, 0, core.Qt__AlignCenter) 38 | 39 | // add url 40 | url := "https://github.com/jpki/myna" 41 | urlButton := widgets.NewQPushButton2(url, nil) 42 | urlButton.ConnectClicked(func(bool) { 43 | gui.QDesktopServices_OpenUrl(core.NewQUrl3(url, 0)) 44 | }) 45 | layout.AddWidget(urlButton, 0, 0) 46 | 47 | label = widgets.NewQLabel2( 48 | ` 49 | このソフトウェアはMITライセンスで開発されています。 50 | Qt は LGPLライセンス 51 | かわいいウサギのイラストはいらすとやの著作物です`, nil, 0) 52 | layout.AddWidget(label, 0, 0) 53 | buttons := widgets.NewQDialogButtonBox(nil) 54 | closeButton := widgets.NewQPushButton2("閉じる", nil) 55 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 56 | buttons.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 57 | layout.AddWidget(buttons, 0, 0) 58 | dialog.SetLayout(layout) 59 | return &AboutDialog{dialog} 60 | } 61 | -------------------------------------------------------------------------------- /_exp/mynaqt/cert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/x509" 5 | "github.com/jpki/myna/libmyna" 6 | "github.com/therecipe/qt/core" 7 | "github.com/therecipe/qt/widgets" 8 | ) 9 | 10 | type SelectCertDialog struct { 11 | *widgets.QDialog 12 | } 13 | 14 | func NewSelectCertDialog() *SelectCertDialog { 15 | windowFlag := core.Qt__Window | core.Qt__WindowCloseButtonHint 16 | dialog := widgets.NewQDialog(nil, windowFlag) 17 | dialog.SetModal(true) 18 | dialog.SetMinimumSize2(300, 300) 19 | dialog.SetWindowTitle("証明書選択") 20 | 21 | radio1 := widgets.NewQRadioButton2("認証用証明書", nil) 22 | radio2 := widgets.NewQRadioButton2("署名用証明書", nil) 23 | radio3 := widgets.NewQRadioButton2("認証用CA証明書", nil) 24 | radio4 := widgets.NewQRadioButton2("署名用CA証明書", nil) 25 | radio1.SetChecked(true) 26 | 27 | layout := widgets.NewQVBoxLayout() 28 | layout.AddWidget(radio1, 0, 0) 29 | layout.AddWidget(radio2, 0, 0) 30 | layout.AddWidget(radio3, 0, 0) 31 | layout.AddWidget(radio4, 0, 0) 32 | 33 | group := widgets.NewQButtonGroup(nil) 34 | group.AddButton(radio1, 1) 35 | group.AddButton(radio2, 2) 36 | group.AddButton(radio3, 3) 37 | group.AddButton(radio4, 4) 38 | 39 | buttons := widgets.NewQDialogButtonBox(nil) 40 | closeButton := widgets.NewQPushButton2("閉じる", nil) 41 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 42 | 43 | showButton := widgets.NewQPushButton2("表示", nil) 44 | showButton.ConnectClicked(func(bool) { 45 | id := group.CheckedId() 46 | var name string 47 | var ef string 48 | var password string 49 | if id == 1 { 50 | name = "認証用証明書" 51 | ef = "00 0A" 52 | } else if id == 2 { 53 | name = "署名用証明書" 54 | ef = "00 01" 55 | prompt := NewPasswordPromptDialog() 56 | rc := prompt.Exec() 57 | if rc != int(widgets.QDialog__Accepted) { 58 | return 59 | } 60 | password = prompt.GetPin() 61 | } else if id == 3 { 62 | name = "認証用CA証明書" 63 | ef = "00 0B" 64 | } else if id == 4 { 65 | name = "署名用CA証明書" 66 | ef = "00 02" 67 | } else { 68 | return 69 | } 70 | 71 | cert, err := libmyna.GetCert(ctx, ef, password) 72 | if err != nil { 73 | widgets.QMessageBox_Warning(nil, "エラー", err.Error(), 74 | widgets.QMessageBox__Ok, 0) 75 | return 76 | } 77 | certDialog := NewShowCertDialog(name, cert) 78 | certDialog.Show() 79 | }) 80 | 81 | buttons.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 82 | buttons.AddButton(showButton, widgets.QDialogButtonBox__AcceptRole) 83 | layout.AddWidget(buttons, 0, 0) 84 | dialog.SetLayout(layout) 85 | return &SelectCertDialog{dialog} 86 | } 87 | 88 | type ShowCertDialog struct { 89 | *widgets.QDialog 90 | } 91 | 92 | func NewShowCertDialog(name string, cert *x509.Certificate) *ShowCertDialog { 93 | if cert == nil { 94 | widgets.QMessageBox_Warning(nil, "エラー", "証明書がみつかりません", 95 | widgets.QMessageBox__Ok, 0) 96 | return nil 97 | /* 98 | data, _ := ioutil.ReadFile("../test.pem") 99 | block, _ := pem.Decode(data) 100 | cert, _ = x509.ParseCertificate(block.Bytes) 101 | */ 102 | } 103 | 104 | windowFlag := core.Qt__Window | core.Qt__WindowCloseButtonHint 105 | dialog := widgets.NewQDialog(nil, windowFlag) 106 | dialog.SetModal(true) 107 | dialog.SetWindowTitle("証明書ビューア: " + name) 108 | dialog.SetMinimumSize2(600, 400) 109 | 110 | layout := widgets.NewQVBoxLayout() 111 | labelSubject := widgets.NewQLabel2("発行先", nil, 0) 112 | layout.AddWidget(labelSubject, 0, 0) 113 | textSubject := widgets.NewQLineEdit(dialog) 114 | textSubject.SetReadOnly(true) 115 | textSubject.SetText(libmyna.Name2String(cert.Subject)) 116 | textSubject.AdjustSize() 117 | layout.AddWidget(textSubject, 0, 0) 118 | 119 | labelIssuer := widgets.NewQLabel2("発行元", nil, 0) 120 | layout.AddWidget(labelIssuer, 0, 0) 121 | textIssuer := widgets.NewQLineEdit(nil) 122 | textIssuer.SetReadOnly(true) 123 | textIssuer.SetText(libmyna.Name2String(cert.Issuer)) 124 | layout.AddWidget(textIssuer, 0, 0) 125 | 126 | labelNotBefore := widgets.NewQLabel2("発行日", nil, 0) 127 | layout.AddWidget(labelNotBefore, 0, 0) 128 | textNotBefore := widgets.NewQLineEdit(nil) 129 | textNotBefore.SetReadOnly(true) 130 | textNotBefore.SetText(cert.NotBefore.Local().String()) 131 | layout.AddWidget(textNotBefore, 0, 0) 132 | 133 | labelNotAfter := widgets.NewQLabel2("有効期限", nil, 0) 134 | layout.AddWidget(labelNotAfter, 0, 0) 135 | textNotAfter := widgets.NewQLineEdit(nil) 136 | textNotAfter.SetReadOnly(true) 137 | textNotAfter.SetText(cert.NotAfter.Local().String()) 138 | layout.AddWidget(textNotAfter, 0, 0) 139 | 140 | buttons := widgets.NewQDialogButtonBox(nil) 141 | closeButton := widgets.NewQPushButton2("閉じる", nil) 142 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 143 | buttons.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 144 | layout.AddWidget(buttons, 0, 0) 145 | 146 | dialog.SetLayout(layout) 147 | return &ShowCertDialog{dialog} 148 | } 149 | -------------------------------------------------------------------------------- /_exp/mynaqt/cms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/jpki/myna/libmyna" 5 | "github.com/therecipe/qt/core" 6 | "github.com/therecipe/qt/widgets" 7 | ) 8 | 9 | type CmsSignDialog struct { 10 | *widgets.QDialog 11 | } 12 | 13 | func NewCmsSignDialog() *CmsSignDialog { 14 | windowFlag := core.Qt__Window | core.Qt__WindowCloseButtonHint 15 | dialog := widgets.NewQDialog(nil, windowFlag) 16 | dialog.SetModal(true) 17 | dialog.SetMinimumSize2(500, 0) 18 | dialog.SetWindowTitle("CMS署名") 19 | 20 | layout := widgets.NewQVBoxLayout() 21 | layout.AddWidget(widgets.NewQLabel2("入力ファイル", nil, 0), 0, 0) 22 | 23 | row := widgets.NewQHBoxLayout() 24 | inputFileEdit := widgets.NewQLineEdit(nil) 25 | outputFileEdit := widgets.NewQLineEdit(nil) 26 | inputFileEdit.SetReadOnly(true) 27 | inputFileEdit.SetSizePolicy2( 28 | widgets.QSizePolicy__Expanding, 29 | widgets.QSizePolicy__Ignored) 30 | row.AddWidget(inputFileEdit, 0, 0) 31 | inputFileButton := widgets.NewQPushButton2("選択", nil) 32 | inputFileButton.ConnectClicked(func(bool) { 33 | dialog := widgets.NewQFileDialog2(nil, "入力ファイル", "", "") 34 | dialog.SetFileMode(widgets.QFileDialog__ExistingFile) 35 | rc := dialog.Exec() 36 | if rc != int(widgets.QDialog__Accepted) { 37 | return 38 | } 39 | filename := dialog.SelectedFiles()[0] 40 | inputFileEdit.SetText(filename) 41 | if outputFileEdit.Text() == "" { 42 | outputFileEdit.SetText(filename + ".p7s") 43 | } 44 | }) 45 | row.AddWidget(inputFileButton, 0, 0) 46 | layout.AddLayout(row, 0) 47 | 48 | layout.AddWidget(widgets.NewQLabel2("出力ファイル", nil, 0), 0, 0) 49 | row = widgets.NewQHBoxLayout() 50 | outputFileEdit.SetReadOnly(true) 51 | outputFileEdit.SetSizePolicy2( 52 | widgets.QSizePolicy__Expanding, 53 | widgets.QSizePolicy__Ignored) 54 | row.AddWidget(outputFileEdit, 0, 0) 55 | outputFileButton := widgets.NewQPushButton2("選択", nil) 56 | outputFileButton.ConnectClicked(func(bool) { 57 | dialog := widgets.NewQFileDialog2(nil, "出力ファイル", "", "") 58 | dialog.SetAcceptMode(widgets.QFileDialog__AcceptSave) 59 | rc := dialog.Exec() 60 | if rc != int(widgets.QDialog__Accepted) { 61 | return 62 | } 63 | filename := dialog.SelectedFiles()[0] 64 | outputFileEdit.SetText(filename) 65 | }) 66 | row.AddWidget(outputFileButton, 0, 0) 67 | layout.AddLayout(row, 0) 68 | 69 | buttons := widgets.NewQDialogButtonBox(nil) 70 | closeButton := widgets.NewQPushButton2("閉じる", nil) 71 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 72 | signButton := widgets.NewQPushButton2("署名", nil) 73 | signButton.ConnectClicked(func(bool) { 74 | if inputFileEdit.Text() == "" { 75 | widgets.QMessageBox_Warning(nil, "エラー", 76 | "入力ファイルを選択してください", 77 | widgets.QMessageBox__Ok, 0) 78 | return 79 | } 80 | if outputFileEdit.Text() == "" { 81 | widgets.QMessageBox_Warning(nil, "エラー", 82 | "出力ファイルを選択してください", 83 | widgets.QMessageBox__Ok, 0) 84 | return 85 | } 86 | prompt := NewPasswordPromptDialog() 87 | rc := prompt.Exec() 88 | if rc != int(widgets.QDialog__Accepted) { 89 | return 90 | } 91 | password := prompt.GetPin() 92 | err := libmyna.Sign(ctx, password, 93 | inputFileEdit.Text(), outputFileEdit.Text()) 94 | if err != nil { 95 | widgets.QMessageBox_Warning(nil, "エラー", err.Error(), 96 | widgets.QMessageBox__Ok, 0) 97 | return 98 | } 99 | widgets.QMessageBox_Information(nil, "CMS署名", 100 | "正常に署名しました", 101 | widgets.QMessageBox__Ok, 0) 102 | return 103 | dialog.Close() 104 | }) 105 | buttons.AddButton(signButton, widgets.QDialogButtonBox__AcceptRole) 106 | buttons.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 107 | layout.AddWidget(buttons, 0, 0) 108 | dialog.SetLayout(layout) 109 | return &CmsSignDialog{dialog} 110 | } 111 | -------------------------------------------------------------------------------- /_exp/mynaqt/mynaqt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "bytes" 5 | _ "errors" 6 | "fmt" 7 | "github.com/jpki/myna/libmyna" 8 | "github.com/therecipe/qt/core" 9 | "github.com/therecipe/qt/gui" 10 | "github.com/therecipe/qt/widgets" 11 | "github.com/urfave/cli" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | app := cli.NewApp() 17 | app.Name = "mynaqt" 18 | app.Description = "マイナクライアント(GUI)" 19 | app.Author = "HAMANO Tsukasa" 20 | app.Email = "hamano@osstech.co.jp" 21 | app.Version = libmyna.Version 22 | app.Action = mynaqt 23 | app.Flags = []cli.Flag{ 24 | cli.BoolFlag{ 25 | Name: "debug, d", 26 | Usage: "詳細出力", 27 | }, 28 | } 29 | app.Run(os.Args) 30 | } 31 | 32 | var ctx *cli.Context 33 | 34 | func mynaqt(c *cli.Context) error { 35 | ctx = c 36 | app := widgets.NewQApplication(len(os.Args), os.Args) 37 | core.QCoreApplication_SetApplicationName(c.App.Name) 38 | core.QCoreApplication_SetApplicationVersion(c.App.Version) 39 | 40 | window := widgets.NewQMainWindow(nil, 0) 41 | window.SetWindowTitle(c.App.Description) 42 | window.SetMinimumSize2(500, 0) 43 | menu := window.MenuBar().AddMenu2("Menu") 44 | actionAbout := menu.AddAction("About") 45 | actionAbout.ConnectTriggered(func(checked bool) { 46 | NewAboutDialog().Show() 47 | }) 48 | actionQuit := menu.AddAction("終了") 49 | actionQuit.ConnectTriggered(func(checked bool) { 50 | window.Close() 51 | }) 52 | 53 | hBox := widgets.NewQHBoxLayout() 54 | vBox := widgets.NewQVBoxLayout() 55 | logoLabel := widgets.NewQLabel(nil, 0) 56 | logoData, err := Asset("usagi.png") 57 | if err != nil { 58 | return err 59 | } 60 | pixmap := gui.NewQPixmap() 61 | pixmap.LoadFromData(string(logoData), uint(len(logoData)), 62 | "PNG", core.Qt__AutoColor) 63 | logoLabel.SetPixmap(pixmap) 64 | 65 | hBox.AddWidget(logoLabel, 0, 0) 66 | hBox.AddLayout(vBox, 1) 67 | 68 | widget := widgets.NewQWidget(nil, 0) 69 | widget.SetLayout(hBox) 70 | 71 | buttonCardCheck := widgets.NewQPushButton2("動作確認", widget) 72 | buttonCardCheck.ConnectClicked(onCardCheck) 73 | vBox.AddWidget(buttonCardCheck, 0, 0) 74 | 75 | buttonCardInfo := widgets.NewQPushButton2("券面事項確認", widget) 76 | buttonCardInfo.ConnectClicked(onCardInfo) 77 | vBox.AddWidget(buttonCardInfo, 0, 0) 78 | 79 | buttonShowCert := widgets.NewQPushButton2("証明書表示", widget) 80 | buttonShowCert.ConnectClicked(onShowCert) 81 | vBox.AddWidget(buttonShowCert, 0, 0) 82 | 83 | buttonCmsSign := widgets.NewQPushButton2("CMS署名", widget) 84 | buttonCmsSign.ConnectClicked(onCmsSign) 85 | vBox.AddWidget(buttonCmsSign, 0, 0) 86 | 87 | buttonPinStatus := widgets.NewQPushButton2("PINステータス", widget) 88 | buttonPinStatus.ConnectClicked(onPinStatus) 89 | vBox.AddWidget(buttonPinStatus, 0, 0) 90 | 91 | window.SetCentralWidget(widget) 92 | window.Show() 93 | app.Exec() 94 | return nil 95 | } 96 | 97 | func onCardCheck(checked bool) { 98 | err := libmyna.CheckCard(ctx) 99 | if err != nil { 100 | widgets.QMessageBox_Warning(nil, "エラー", err.Error(), 101 | widgets.QMessageBox__Ok, 0) 102 | } else { 103 | widgets.QMessageBox_Information(nil, "動作確認", "正常です", 104 | widgets.QMessageBox__Close, 0) 105 | } 106 | } 107 | 108 | func onCardInfo(checked bool) { 109 | prompt := NewPinPromptDialog() 110 | rc := prompt.Exec() 111 | if rc != int(widgets.QDialog__Accepted) { 112 | return 113 | } 114 | 115 | pin := prompt.GetPin() 116 | info, err := libmyna.GetCardInfo(ctx, pin) 117 | if err != nil { 118 | widgets.QMessageBox_Warning(nil, "エラー", err.Error(), 119 | widgets.QMessageBox__Ok, 0) 120 | return 121 | } 122 | var msg string 123 | msg += fmt.Sprintf("個人番号: %s\n", info["number"]) 124 | msg += fmt.Sprintf("氏名: %s\n", info["name"]) 125 | msg += fmt.Sprintf("住所: %s\n", info["address"]) 126 | msg += fmt.Sprintf("生年月日: %s\n", info["birth"]) 127 | msg += fmt.Sprintf("性別: %s", libmyna.ToISO5218String(info["sex"])) 128 | widgets.QMessageBox_Information(nil, "券面事項確認", msg, 129 | widgets.QMessageBox__Close, 0) 130 | } 131 | 132 | func onShowCert(checked bool) { 133 | dialog := NewSelectCertDialog() 134 | dialog.Show() 135 | } 136 | 137 | func onPinStatus(checked bool) { 138 | status, err := libmyna.GetPinStatus(ctx) 139 | if err != nil { 140 | widgets.QMessageBox_Warning(nil, "エラー", err.Error(), 141 | widgets.QMessageBox__Ok, 0) 142 | return 143 | } 144 | var msg string 145 | msg += fmt.Sprintf("券面事項PIN(A): のこり%2d回\n", 146 | status["card_info_pin_a"]) 147 | msg += fmt.Sprintf("券面事項PIN(B): のこり%2d回\n", 148 | status["card_info_pin_b"]) 149 | msg += fmt.Sprintf("入力補助PIN: のこり%2d回\n", 150 | status["card_input_helper_pin"]) 151 | msg += fmt.Sprintf("入力補助PIN(A): のこり%2d回\n", 152 | status["card_input_helper_pin_a"]) 153 | msg += fmt.Sprintf("入力補助PIN(B): のこり%2d回\n", 154 | status["card_input_helper_pin_b"]) 155 | msg += fmt.Sprintf("JPKI認証用PIN: のこり%2d回\n", status["jpki_auth"]) 156 | msg += fmt.Sprintf("JPKI署名用PIN: のこり%2d回\n", status["jpki_sign"]) 157 | widgets.QMessageBox_Information(nil, "PINステータス", msg, 158 | widgets.QMessageBox__Ok, 0) 159 | } 160 | 161 | func onCmsSign(checked bool) { 162 | dialog := NewCmsSignDialog() 163 | dialog.Show() 164 | } 165 | -------------------------------------------------------------------------------- /_exp/mynaqt/prompt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/therecipe/qt/widgets" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | type PromptDialog struct { 10 | *widgets.QDialog 11 | input *widgets.QLineEdit 12 | } 13 | 14 | func NewPinPromptDialog() *PromptDialog { 15 | dialog := widgets.NewQDialog(nil, 0) 16 | dialog.SetModal(true) 17 | dialog.SetWindowTitle("暗証番号入力") 18 | layout := widgets.NewQVBoxLayout() 19 | 20 | label := widgets.NewQLabel2("暗証番号(4桁)を入力してください", nil, 0) 21 | layout.AddWidget(label, 0, 0) 22 | 23 | input := widgets.NewQLineEdit(nil) 24 | input.SetPlaceholderText("暗証番号(4桁)") 25 | input.SetEchoMode(widgets.QLineEdit__Password) 26 | layout.AddWidget(input, 0, 0) 27 | 28 | buttonBox := widgets.NewQDialogButtonBox(nil) 29 | closeButton := widgets.NewQPushButton2("閉じる", nil) 30 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 31 | 32 | okButton := widgets.NewQPushButton2("OK", nil) 33 | okButton.ConnectClicked(func(bool) { 34 | pin := input.Text() 35 | match, _ := regexp.MatchString("^\\d{4}$", pin) 36 | if !match { 37 | label.SetStyleSheet("color: red") 38 | return 39 | } 40 | dialog.Accept() 41 | dialog.Close() 42 | }) 43 | 44 | buttonBox.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 45 | buttonBox.AddButton(okButton, widgets.QDialogButtonBox__AcceptRole) 46 | 47 | layout.AddWidget(buttonBox, 0, 0) 48 | dialog.SetLayout(layout) 49 | return &PromptDialog{dialog, input} 50 | } 51 | 52 | func NewPasswordPromptDialog() *PromptDialog { 53 | dialog := widgets.NewQDialog(nil, 0) 54 | dialog.SetModal(true) 55 | dialog.SetWindowTitle("パスワード入力") 56 | layout := widgets.NewQVBoxLayout() 57 | 58 | label := widgets.NewQLabel2("パスワード(6-16桁)を入力してください", nil, 0) 59 | layout.AddWidget(label, 0, 0) 60 | 61 | input := widgets.NewQLineEdit(nil) 62 | input.SetPlaceholderText("パスワード(6-16桁)") 63 | input.SetEchoMode(widgets.QLineEdit__Password) 64 | layout.AddWidget(input, 0, 0) 65 | 66 | buttons := widgets.NewQDialogButtonBox(nil) 67 | closeButton := widgets.NewQPushButton2("閉じる", nil) 68 | closeButton.ConnectClicked(func(bool) { dialog.Close() }) 69 | 70 | okButton := widgets.NewQPushButton2("OK", nil) 71 | okButton.ConnectClicked(func(bool) { 72 | pin := strings.ToUpper(input.Text()) 73 | match, _ := regexp.MatchString("^[a-zA-Z0-9]{6,16}$", pin) 74 | if !match { 75 | label.SetStyleSheet("color: red") 76 | return 77 | } 78 | dialog.Accept() 79 | dialog.Close() 80 | }) 81 | 82 | buttons.AddButton(closeButton, widgets.QDialogButtonBox__RejectRole) 83 | buttons.AddButton(okButton, widgets.QDialogButtonBox__AcceptRole) 84 | 85 | layout.AddWidget(buttons, 0, 0) 86 | dialog.SetLayout(layout) 87 | return &PromptDialog{dialog, input} 88 | } 89 | 90 | func (d *PromptDialog) GetPin() string { 91 | return strings.ToUpper(d.input.Text()) 92 | } 93 | -------------------------------------------------------------------------------- /_exp/mynaui/Makefile: -------------------------------------------------------------------------------- 1 | 2 | mynaui: mynaui.go 3 | go build 4 | 5 | clean: 6 | rm -rf mynaui 7 | -------------------------------------------------------------------------------- /_exp/mynaui/mynaui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/andlabs/ui" 6 | "github.com/jpki/myna/libmyna" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | var window *ui.Window 12 | 13 | var rootCmd = &cobra.Command{ 14 | Use: "mynaui", 15 | Short: fmt.Sprintf("マイナクライアント(GUI) - %s", libmyna.Version), 16 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 17 | libmyna.Debug, _ = cmd.Flags().GetBool("debug") 18 | }, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | err := ui.Main(uiMain) 21 | if err != nil { 22 | panic(err) 23 | } 24 | }, 25 | } 26 | 27 | func main() { 28 | rootCmd.PersistentFlags().BoolP("debug", "d", false, "デバッグ出力") 29 | rootCmd.Execute() 30 | } 31 | 32 | func uiMain() { 33 | label := ui.NewLabel("マイナクライアント") 34 | buttonCheck := ui.NewButton("動作確認") 35 | buttonCardInfo := ui.NewButton("券面事項確認") 36 | buttonCardInfo.OnClicked(onClickCardInfo) 37 | buttonShowCert := ui.NewButton("証明書表示") 38 | buttonShowCert.OnClicked(onClickShowCert) 39 | buttonSign := ui.NewButton("署名") 40 | buttonSign.OnClicked(onClickSign) 41 | buttonPinStatus := ui.NewButton("PINステータス") 42 | buttonPinStatus.OnClicked(onClickPinStatus) 43 | buttonQuit := ui.NewButton("終了") 44 | buttonQuit.OnClicked(onClickQuit) 45 | 46 | box := ui.NewVerticalBox() 47 | box.SetPadded(true) 48 | box.Append(label, true) 49 | box.Append(buttonCheck, true) 50 | box.Append(buttonCardInfo, true) 51 | box.Append(buttonShowCert, true) 52 | box.Append(buttonSign, true) 53 | box.Append(buttonPinStatus, true) 54 | box.Append(buttonQuit, true) 55 | window = ui.NewWindow("マイナクライアント", 1, 1, true) 56 | window.SetMargined(true) 57 | window.SetChild(box) 58 | buttonCheck.OnClicked(onClickCheck) 59 | 60 | /* 61 | button1.OnClicked(func(*ui.Button) { 62 | }) 63 | button2.OnClicked(func(*ui.Button) { 64 | ui.OpenFile(window) 65 | //status.SetText("Button2") 66 | }) 67 | */ 68 | window.OnClosing(func(*ui.Window) bool { 69 | ui.Quit() 70 | return true 71 | }) 72 | window.Show() 73 | } 74 | 75 | func showPinPrompt() string { 76 | promptWindow := ui.NewWindow("暗証番号(4桁)", 1, 1, false) 77 | promptWindow.OnClosing(func(*ui.Window) bool { 78 | return true 79 | }) 80 | box := ui.NewVerticalBox() 81 | box.SetPadded(true) 82 | 83 | entry := ui.NewEntry() 84 | var text string 85 | buttonAuth := ui.NewButton("認証") 86 | buttonAuth.OnClicked(func(*ui.Button) { 87 | text = entry.Text() 88 | promptWindow.Destroy() 89 | }) 90 | box.Append(entry, true) 91 | box.Append(buttonAuth, true) 92 | 93 | promptWindow.SetChild(box) 94 | promptWindow.SetMargined(true) 95 | promptWindow.Show() 96 | fmt.Printf("text: %v\n", text) 97 | return text 98 | } 99 | 100 | func onClickCheck(b *ui.Button) { 101 | b.Disable() 102 | defer b.Enable() 103 | err := libmyna.CheckCard() 104 | if err != nil { 105 | ui.MsgBoxError(window, "エラー", err.Error()) 106 | return 107 | } 108 | ui.MsgBox(window, "動作確認", "問題ありません。") 109 | } 110 | 111 | func onClickCardInfo(b *ui.Button) { 112 | b.Disable() 113 | defer b.Enable() 114 | 115 | pin := showPinPrompt() 116 | 117 | fmt.Printf("pin: %s\n", pin) 118 | /* 119 | err := libmyna.CheckCard(c) 120 | if err != nil { 121 | ui.MsgBoxError(window, "エラー", err.Error()) 122 | return 123 | } 124 | */ 125 | } 126 | 127 | func onClickShowCert(b *ui.Button) { 128 | window.Disable() 129 | defer window.Enable() 130 | selectCertWindow := ui.NewWindow("証明書選択", 1, 1, false) 131 | selectCertWindow.OnClosing(func(*ui.Window) bool { 132 | return true 133 | }) 134 | buttonShowAuthCert := ui.NewButton("認証用証明") 135 | buttonShowSignCert := ui.NewButton("署名用証明書") 136 | buttonShowAuthCACert := ui.NewButton("認証用CA証明") 137 | buttonShowSignCACert := ui.NewButton("署名用CA証明書") 138 | 139 | box := ui.NewVerticalBox() 140 | box.SetPadded(true) 141 | box.Append(buttonShowAuthCert, true) 142 | box.Append(buttonShowSignCert, true) 143 | box.Append(buttonShowAuthCACert, true) 144 | box.Append(buttonShowSignCACert, true) 145 | selectCertWindow.SetChild(box) 146 | selectCertWindow.SetMargined(true) 147 | selectCertWindow.Show() 148 | } 149 | 150 | func onClickSign(b *ui.Button) { 151 | os.Exit(0) 152 | } 153 | 154 | func onClickPinStatus(b *ui.Button) { 155 | b.Disable() 156 | defer b.Enable() 157 | status, err := libmyna.GetPinStatus() 158 | if err != nil { 159 | ui.MsgBoxError(window, "エラー", err.Error()) 160 | return 161 | } 162 | var msg string 163 | msg += fmt.Sprintf("認証用PIN: のこり%d回\n", status["auth"]) 164 | msg += fmt.Sprintf("署名用PIN: のこり%d回\n", status["sign"]) 165 | msg += fmt.Sprintf("券面入力補助PIN: のこり%d回\n", status["card"]) 166 | msg += fmt.Sprintf("謎のPIN1: のこり%d回\n", status["unknown1"]) 167 | msg += fmt.Sprintf("謎のPIN2: のこり%d回", status["unknown2"]) 168 | ui.MsgBox(window, "PINステータス", msg) 169 | } 170 | 171 | func onClickQuit(b *ui.Button) { 172 | ui.Quit() 173 | } 174 | -------------------------------------------------------------------------------- /asn1/asn1.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package asn1 implements parsing of DER-encoded ASN.1 data structures, 6 | // as defined in ITU-T Rec X.690. 7 | // 8 | // See also ``A Layman's Guide to a Subset of ASN.1, BER, and DER,'' 9 | // http://luca.ntop.org/Teaching/Appunti/asn1.html. 10 | package asn1 11 | 12 | // ASN.1 is a syntax for specifying abstract objects and BER, DER, PER, XER etc 13 | // are different encoding formats for those objects. Here, we'll be dealing 14 | // with DER, the Distinguished Encoding Rules. DER is used in X.509 because 15 | // it's fast to parse and, unlike BER, has a unique encoding for every object. 16 | // When calculating hashes over objects, it's important that the resulting 17 | // bytes be the same at both ends and DER removes this margin of error. 18 | // 19 | // ASN.1 is very complex and this package doesn't attempt to implement 20 | // everything by any means. 21 | 22 | import ( 23 | "errors" 24 | "fmt" 25 | "math" 26 | "math/big" 27 | "reflect" 28 | "strconv" 29 | "time" 30 | "unicode/utf16" 31 | "unicode/utf8" 32 | ) 33 | 34 | // A StructuralError suggests that the ASN.1 data is valid, but the Go type 35 | // which is receiving it doesn't match. 36 | type StructuralError struct { 37 | Msg string 38 | } 39 | 40 | func (e StructuralError) Error() string { return "asn1: structure error: " + e.Msg } 41 | 42 | // A SyntaxError suggests that the ASN.1 data is invalid. 43 | type SyntaxError struct { 44 | Msg string 45 | } 46 | 47 | func (e SyntaxError) Error() string { return "asn1: syntax error: " + e.Msg } 48 | 49 | // We start by dealing with each of the primitive types in turn. 50 | 51 | // BOOLEAN 52 | 53 | func parseBool(bytes []byte) (ret bool, err error) { 54 | if len(bytes) != 1 { 55 | err = SyntaxError{"invalid boolean"} 56 | return 57 | } 58 | 59 | // DER demands that "If the encoding represents the boolean value TRUE, 60 | // its single contents octet shall have all eight bits set to one." 61 | // Thus only 0 and 255 are valid encoded values. 62 | switch bytes[0] { 63 | case 0: 64 | ret = false 65 | case 0xff: 66 | ret = true 67 | default: 68 | err = SyntaxError{"invalid boolean"} 69 | } 70 | 71 | return 72 | } 73 | 74 | // INTEGER 75 | 76 | // checkInteger returns nil if the given bytes are a valid DER-encoded 77 | // INTEGER and an error otherwise. 78 | func checkInteger(bytes []byte) error { 79 | if len(bytes) == 0 { 80 | return StructuralError{"empty integer"} 81 | } 82 | if len(bytes) == 1 { 83 | return nil 84 | } 85 | if (bytes[0] == 0 && bytes[1]&0x80 == 0) || (bytes[0] == 0xff && bytes[1]&0x80 == 0x80) { 86 | return StructuralError{"integer not minimally-encoded"} 87 | } 88 | return nil 89 | } 90 | 91 | // parseInt64 treats the given bytes as a big-endian, signed integer and 92 | // returns the result. 93 | func parseInt64(bytes []byte) (ret int64, err error) { 94 | err = checkInteger(bytes) 95 | if err != nil { 96 | return 97 | } 98 | if len(bytes) > 8 { 99 | // We'll overflow an int64 in this case. 100 | err = StructuralError{"integer too large"} 101 | return 102 | } 103 | for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { 104 | ret <<= 8 105 | ret |= int64(bytes[bytesRead]) 106 | } 107 | 108 | // Shift up and down in order to sign extend the result. 109 | ret <<= 64 - uint8(len(bytes))*8 110 | ret >>= 64 - uint8(len(bytes))*8 111 | return 112 | } 113 | 114 | // parseInt treats the given bytes as a big-endian, signed integer and returns 115 | // the result. 116 | func parseInt32(bytes []byte) (int32, error) { 117 | if err := checkInteger(bytes); err != nil { 118 | return 0, err 119 | } 120 | ret64, err := parseInt64(bytes) 121 | if err != nil { 122 | return 0, err 123 | } 124 | if ret64 != int64(int32(ret64)) { 125 | return 0, StructuralError{"integer too large"} 126 | } 127 | return int32(ret64), nil 128 | } 129 | 130 | var bigOne = big.NewInt(1) 131 | 132 | // parseBigInt treats the given bytes as a big-endian, signed integer and returns 133 | // the result. 134 | func parseBigInt(bytes []byte) (*big.Int, error) { 135 | if err := checkInteger(bytes); err != nil { 136 | return nil, err 137 | } 138 | ret := new(big.Int) 139 | if len(bytes) > 0 && bytes[0]&0x80 == 0x80 { 140 | // This is a negative number. 141 | notBytes := make([]byte, len(bytes)) 142 | for i := range notBytes { 143 | notBytes[i] = ^bytes[i] 144 | } 145 | ret.SetBytes(notBytes) 146 | ret.Add(ret, bigOne) 147 | ret.Neg(ret) 148 | return ret, nil 149 | } 150 | ret.SetBytes(bytes) 151 | return ret, nil 152 | } 153 | 154 | // BIT STRING 155 | 156 | // BitString is the structure to use when you want an ASN.1 BIT STRING type. A 157 | // bit string is padded up to the nearest byte in memory and the number of 158 | // valid bits is recorded. Padding bits will be zero. 159 | type BitString struct { 160 | Bytes []byte // bits packed into bytes. 161 | BitLength int // length in bits. 162 | } 163 | 164 | // At returns the bit at the given index. If the index is out of range it 165 | // returns false. 166 | func (b BitString) At(i int) int { 167 | if i < 0 || i >= b.BitLength { 168 | return 0 169 | } 170 | x := i / 8 171 | y := 7 - uint(i%8) 172 | return int(b.Bytes[x]>>y) & 1 173 | } 174 | 175 | // RightAlign returns a slice where the padding bits are at the beginning. The 176 | // slice may share memory with the BitString. 177 | func (b BitString) RightAlign() []byte { 178 | shift := uint(8 - (b.BitLength % 8)) 179 | if shift == 8 || len(b.Bytes) == 0 { 180 | return b.Bytes 181 | } 182 | 183 | a := make([]byte, len(b.Bytes)) 184 | a[0] = b.Bytes[0] >> shift 185 | for i := 1; i < len(b.Bytes); i++ { 186 | a[i] = b.Bytes[i-1] << (8 - shift) 187 | a[i] |= b.Bytes[i] >> shift 188 | } 189 | 190 | return a 191 | } 192 | 193 | // parseBitString parses an ASN.1 bit string from the given byte slice and returns it. 194 | func parseBitString(bytes []byte) (ret BitString, err error) { 195 | if len(bytes) == 0 { 196 | err = SyntaxError{"zero length BIT STRING"} 197 | return 198 | } 199 | paddingBits := int(bytes[0]) 200 | if paddingBits > 7 || 201 | len(bytes) == 1 && paddingBits > 0 || 202 | bytes[len(bytes)-1]&((1< 0 { 243 | s += "." 244 | } 245 | s += strconv.Itoa(v) 246 | } 247 | 248 | return s 249 | } 250 | 251 | // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and 252 | // returns it. An object identifier is a sequence of variable length integers 253 | // that are assigned in a hierarchy. 254 | func parseObjectIdentifier(bytes []byte) (s ObjectIdentifier, err error) { 255 | if len(bytes) == 0 { 256 | err = SyntaxError{"zero length OBJECT IDENTIFIER"} 257 | return 258 | } 259 | 260 | // In the worst case, we get two elements from the first byte (which is 261 | // encoded differently) and then every varint is a single byte long. 262 | s = make([]int, len(bytes)+1) 263 | 264 | // The first varint is 40*value1 + value2: 265 | // According to this packing, value1 can take the values 0, 1 and 2 only. 266 | // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2, 267 | // then there are no restrictions on value2. 268 | v, offset, err := parseBase128Int(bytes, 0) 269 | if err != nil { 270 | return 271 | } 272 | if v < 80 { 273 | s[0] = v / 40 274 | s[1] = v % 40 275 | } else { 276 | s[0] = 2 277 | s[1] = v - 80 278 | } 279 | 280 | i := 2 281 | for ; offset < len(bytes); i++ { 282 | v, offset, err = parseBase128Int(bytes, offset) 283 | if err != nil { 284 | return 285 | } 286 | s[i] = v 287 | } 288 | s = s[0:i] 289 | return 290 | } 291 | 292 | // ENUMERATED 293 | 294 | // An Enumerated is represented as a plain int. 295 | type Enumerated int 296 | 297 | // FLAG 298 | 299 | // A Flag accepts any data and is set to true if present. 300 | type Flag bool 301 | 302 | // parseBase128Int parses a base-128 encoded int from the given offset in the 303 | // given byte slice. It returns the value and the new offset. 304 | func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) { 305 | offset = initOffset 306 | var ret64 int64 307 | for shifted := 0; offset < len(bytes); shifted++ { 308 | // 5 * 7 bits per byte == 35 bits of data 309 | // Thus the representation is either non-minimal or too large for an int32 310 | if shifted == 5 { 311 | err = StructuralError{"base 128 integer too large"} 312 | return 313 | } 314 | ret64 <<= 7 315 | b := bytes[offset] 316 | ret64 |= int64(b & 0x7f) 317 | offset++ 318 | if b&0x80 == 0 { 319 | ret = int(ret64) 320 | // Ensure that the returned value fits in an int on all platforms 321 | if ret64 > math.MaxInt32 { 322 | err = StructuralError{"base 128 integer too large"} 323 | } 324 | return 325 | } 326 | } 327 | err = SyntaxError{"truncated base 128 integer"} 328 | return 329 | } 330 | 331 | // UTCTime 332 | 333 | func parseUTCTime(bytes []byte) (ret time.Time, err error) { 334 | s := string(bytes) 335 | 336 | formatStr := "0601021504Z0700" 337 | ret, err = time.Parse(formatStr, s) 338 | if err != nil { 339 | formatStr = "060102150405Z0700" 340 | ret, err = time.Parse(formatStr, s) 341 | } 342 | if err != nil { 343 | return 344 | } 345 | 346 | if serialized := ret.Format(formatStr); serialized != s { 347 | err = fmt.Errorf("asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %q", s, serialized) 348 | return 349 | } 350 | 351 | if ret.Year() >= 2050 { 352 | // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 353 | ret = ret.AddDate(-100, 0, 0) 354 | } 355 | 356 | return 357 | } 358 | 359 | // parseGeneralizedTime parses the GeneralizedTime from the given byte slice 360 | // and returns the resulting time. 361 | func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) { 362 | const formatStr = "20060102150405Z0700" 363 | s := string(bytes) 364 | 365 | if ret, err = time.Parse(formatStr, s); err != nil { 366 | return 367 | } 368 | 369 | if serialized := ret.Format(formatStr); serialized != s { 370 | err = fmt.Errorf("asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %q", s, serialized) 371 | } 372 | 373 | return 374 | } 375 | 376 | // NumericString 377 | 378 | // parseNumericString parses an ASN.1 NumericString from the given byte array 379 | // and returns it. 380 | func parseNumericString(bytes []byte) (ret string, err error) { 381 | for _, b := range bytes { 382 | if !isNumeric(b) { 383 | return "", SyntaxError{"NumericString contains invalid character"} 384 | } 385 | } 386 | return string(bytes), nil 387 | } 388 | 389 | // isNumeric reports whether the given b is in the ASN.1 NumericString set. 390 | func isNumeric(b byte) bool { 391 | return '0' <= b && b <= '9' || 392 | b == ' ' 393 | } 394 | 395 | // PrintableString 396 | 397 | // parsePrintableString parses an ASN.1 PrintableString from the given byte 398 | // array and returns it. 399 | func parsePrintableString(bytes []byte) (ret string, err error) { 400 | for _, b := range bytes { 401 | if !isPrintable(b, allowAsterisk, allowAmpersand) { 402 | err = SyntaxError{"PrintableString contains invalid character"} 403 | return 404 | } 405 | } 406 | ret = string(bytes) 407 | return 408 | } 409 | 410 | type asteriskFlag bool 411 | type ampersandFlag bool 412 | 413 | const ( 414 | allowAsterisk asteriskFlag = true 415 | rejectAsterisk asteriskFlag = false 416 | 417 | allowAmpersand ampersandFlag = true 418 | rejectAmpersand ampersandFlag = false 419 | ) 420 | 421 | // isPrintable reports whether the given b is in the ASN.1 PrintableString set. 422 | // If asterisk is allowAsterisk then '*' is also allowed, reflecting existing 423 | // practice. If ampersand is allowAmpersand then '&' is allowed as well. 424 | func isPrintable(b byte, asterisk asteriskFlag, ampersand ampersandFlag) bool { 425 | return 'a' <= b && b <= 'z' || 426 | 'A' <= b && b <= 'Z' || 427 | '0' <= b && b <= '9' || 428 | '\'' <= b && b <= ')' || 429 | '+' <= b && b <= '/' || 430 | b == ' ' || 431 | b == ':' || 432 | b == '=' || 433 | b == '?' || 434 | // This is technically not allowed in a PrintableString. 435 | // However, x509 certificates with wildcard strings don't 436 | // always use the correct string type so we permit it. 437 | (bool(asterisk) && b == '*') || 438 | // This is not technically allowed either. However, not 439 | // only is it relatively common, but there are also a 440 | // handful of CA certificates that contain it. At least 441 | // one of which will not expire until 2027. 442 | (bool(ampersand) && b == '&') 443 | } 444 | 445 | // IA5String 446 | 447 | // parseIA5String parses an ASN.1 IA5String (ASCII string) from the given 448 | // byte slice and returns it. 449 | func parseIA5String(bytes []byte) (ret string, err error) { 450 | for _, b := range bytes { 451 | if b >= utf8.RuneSelf { 452 | err = SyntaxError{"IA5String contains invalid character"} 453 | return 454 | } 455 | } 456 | ret = string(bytes) 457 | return 458 | } 459 | 460 | // T61String 461 | 462 | // parseT61String parses an ASN.1 T61String (8-bit clean string) from the given 463 | // byte slice and returns it. 464 | func parseT61String(bytes []byte) (ret string, err error) { 465 | return string(bytes), nil 466 | } 467 | 468 | // UTF8String 469 | 470 | // parseUTF8String parses an ASN.1 UTF8String (raw UTF-8) from the given byte 471 | // array and returns it. 472 | func parseUTF8String(bytes []byte) (ret string, err error) { 473 | if !utf8.Valid(bytes) { 474 | return "", errors.New("asn1: invalid UTF-8 string") 475 | } 476 | return string(bytes), nil 477 | } 478 | 479 | // BMPString 480 | 481 | // parseBMPString parses an ASN.1 BMPString (Basic Multilingual Plane of 482 | // ISO/IEC/ITU 10646-1) from the given byte slice and returns it. 483 | func parseBMPString(bmpString []byte) (string, error) { 484 | if len(bmpString)%2 != 0 { 485 | return "", errors.New("pkcs12: odd-length BMP string") 486 | } 487 | 488 | // Strip terminator if present. 489 | if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { 490 | bmpString = bmpString[:l-2] 491 | } 492 | 493 | s := make([]uint16, 0, len(bmpString)/2) 494 | for len(bmpString) > 0 { 495 | s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) 496 | bmpString = bmpString[2:] 497 | } 498 | 499 | return string(utf16.Decode(s)), nil 500 | } 501 | 502 | // A RawValue represents an undecoded ASN.1 object. 503 | type RawValue struct { 504 | Class, Tag int 505 | IsCompound bool 506 | Bytes []byte 507 | FullBytes []byte // includes the tag and length 508 | } 509 | 510 | // RawContent is used to signal that the undecoded, DER data needs to be 511 | // preserved for a struct. To use it, the first field of the struct must have 512 | // this type. It's an error for any of the other fields to have this type. 513 | type RawContent []byte 514 | 515 | // Tagging 516 | 517 | // parseTagAndLength parses an ASN.1 tag and length pair from the given offset 518 | // into a byte slice. It returns the parsed data and the new offset. SET and 519 | // SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we 520 | // don't distinguish between ordered and unordered objects in this code. 521 | func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset int, err error) { 522 | offset = initOffset 523 | // parseTagAndLength should not be called without at least a single 524 | // byte to read. Thus this check is for robustness: 525 | if offset >= len(bytes) { 526 | err = errors.New("asn1: internal error in parseTagAndLength") 527 | return 528 | } 529 | b := bytes[offset] 530 | offset++ 531 | ret.class = int(b >> 6) 532 | ret.isCompound = b&0x20 == 0x20 533 | ret.tag = int(b & 0x1f) 534 | 535 | // If the bottom five bits are set, then the tag number is actually base 128 536 | // encoded afterwards 537 | if ret.tag == 0x1f { 538 | ret.tag, offset, err = parseBase128Int(bytes, offset) 539 | if err != nil { 540 | return 541 | } 542 | } 543 | if offset >= len(bytes) { 544 | err = SyntaxError{"truncated tag or length"} 545 | return 546 | } 547 | b = bytes[offset] 548 | offset++ 549 | if b&0x80 == 0 { 550 | // The length is encoded in the bottom 7 bits. 551 | ret.length = int(b & 0x7f) 552 | } else { 553 | // Bottom 7 bits give the number of length bytes to follow. 554 | numBytes := int(b & 0x7f) 555 | if numBytes == 0 { 556 | err = SyntaxError{"indefinite length found (not DER)"} 557 | return 558 | } 559 | ret.length = 0 560 | for i := 0; i < numBytes; i++ { 561 | if offset >= len(bytes) { 562 | err = SyntaxError{"truncated tag or length"} 563 | return 564 | } 565 | b = bytes[offset] 566 | offset++ 567 | if ret.length >= 1<<23 { 568 | // We can't shift ret.length up without 569 | // overflowing. 570 | err = StructuralError{"length too large"} 571 | return 572 | } 573 | ret.length <<= 8 574 | ret.length |= int(b) 575 | } 576 | // Short lengths must be encoded in short form. 577 | if ret.length < 0x80 { 578 | err = StructuralError{"non-minimal length"} 579 | return 580 | } 581 | } 582 | 583 | return 584 | } 585 | 586 | // parseSequenceOf is used for SEQUENCE OF and SET OF values. It tries to parse 587 | // a number of ASN.1 values from the given byte slice and returns them as a 588 | // slice of Go values of the given type. 589 | func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type) (ret reflect.Value, err error) { 590 | matchAny, expectedTag, compoundType, ok := getUniversalType(elemType) 591 | if !ok { 592 | err = StructuralError{"unknown Go type for slice"} 593 | return 594 | } 595 | 596 | // First we iterate over the input and count the number of elements, 597 | // checking that the types are correct in each case. 598 | numElements := 0 599 | for offset := 0; offset < len(bytes); { 600 | var t tagAndLength 601 | t, offset, err = parseTagAndLength(bytes, offset) 602 | if err != nil { 603 | return 604 | } 605 | switch t.tag { 606 | case TagIA5String, TagGeneralString, TagT61String, TagUTF8String, TagNumericString, TagBMPString: 607 | // We pretend that various other string types are 608 | // PRINTABLE STRINGs so that a sequence of them can be 609 | // parsed into a []string. 610 | t.tag = TagPrintableString 611 | case TagGeneralizedTime, TagUTCTime: 612 | // Likewise, both time types are treated the same. 613 | t.tag = TagUTCTime 614 | } 615 | 616 | if !matchAny && (t.class != ClassUniversal || t.isCompound != compoundType || t.tag != expectedTag) { 617 | err = StructuralError{"sequence tag mismatch"} 618 | return 619 | } 620 | if invalidLength(offset, t.length, len(bytes)) { 621 | err = SyntaxError{"truncated sequence"} 622 | return 623 | } 624 | offset += t.length 625 | numElements++ 626 | } 627 | ret = reflect.MakeSlice(sliceType, numElements, numElements) 628 | params := fieldParameters{} 629 | offset := 0 630 | for i := 0; i < numElements; i++ { 631 | offset, err = parseField(ret.Index(i), bytes, offset, params) 632 | if err != nil { 633 | return 634 | } 635 | } 636 | return 637 | } 638 | 639 | var ( 640 | bitStringType = reflect.TypeOf(BitString{}) 641 | objectIdentifierType = reflect.TypeOf(ObjectIdentifier{}) 642 | enumeratedType = reflect.TypeOf(Enumerated(0)) 643 | flagType = reflect.TypeOf(Flag(false)) 644 | timeType = reflect.TypeOf(time.Time{}) 645 | rawValueType = reflect.TypeOf(RawValue{}) 646 | rawContentsType = reflect.TypeOf(RawContent(nil)) 647 | bigIntType = reflect.TypeOf(new(big.Int)) 648 | ) 649 | 650 | // invalidLength reports whether offset + length > sliceLength, or if the 651 | // addition would overflow. 652 | func invalidLength(offset, length, sliceLength int) bool { 653 | return offset+length < offset || offset+length > sliceLength 654 | } 655 | 656 | // parseField is the main parsing function. Given a byte slice and an offset 657 | // into the array, it will try to parse a suitable ASN.1 value out and store it 658 | // in the given Value. 659 | func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParameters) (offset int, err error) { 660 | offset = initOffset 661 | fieldType := v.Type() 662 | 663 | // If we have run out of data, it may be that there are optional elements at the end. 664 | if offset == len(bytes) { 665 | if !setDefaultValue(v, params) { 666 | err = SyntaxError{"sequence truncated"} 667 | } 668 | return 669 | } 670 | 671 | // Deal with the ANY type. 672 | if ifaceType := fieldType; ifaceType.Kind() == reflect.Interface && ifaceType.NumMethod() == 0 { 673 | var t tagAndLength 674 | t, offset, err = parseTagAndLength(bytes, offset) 675 | if err != nil { 676 | return 677 | } 678 | if invalidLength(offset, t.length, len(bytes)) { 679 | err = SyntaxError{"data truncated"} 680 | return 681 | } 682 | var result interface{} 683 | if !t.isCompound && t.class == ClassUniversal { 684 | innerBytes := bytes[offset : offset+t.length] 685 | switch t.tag { 686 | case TagPrintableString: 687 | result, err = parsePrintableString(innerBytes) 688 | case TagNumericString: 689 | result, err = parseNumericString(innerBytes) 690 | case TagIA5String: 691 | result, err = parseIA5String(innerBytes) 692 | case TagT61String: 693 | result, err = parseT61String(innerBytes) 694 | case TagUTF8String: 695 | result, err = parseUTF8String(innerBytes) 696 | case TagInteger: 697 | result, err = parseInt64(innerBytes) 698 | case TagBitString: 699 | result, err = parseBitString(innerBytes) 700 | case TagOID: 701 | result, err = parseObjectIdentifier(innerBytes) 702 | case TagUTCTime: 703 | result, err = parseUTCTime(innerBytes) 704 | case TagGeneralizedTime: 705 | result, err = parseGeneralizedTime(innerBytes) 706 | case TagOctetString: 707 | result = innerBytes 708 | case TagBMPString: 709 | result, err = parseBMPString(innerBytes) 710 | default: 711 | // If we don't know how to handle the type, we just leave Value as nil. 712 | } 713 | } 714 | offset += t.length 715 | if err != nil { 716 | return 717 | } 718 | if result != nil { 719 | v.Set(reflect.ValueOf(result)) 720 | } 721 | return 722 | } 723 | 724 | t, offset, err := parseTagAndLength(bytes, offset) 725 | if err != nil { 726 | return 727 | } 728 | if params.explicit { 729 | expectedClass := ClassContextSpecific 730 | if params.application { 731 | expectedClass = ClassApplication 732 | } 733 | if offset == len(bytes) { 734 | err = StructuralError{"explicit tag has no child"} 735 | return 736 | } 737 | if t.class == expectedClass && t.tag == *params.tag && (t.length == 0 || t.isCompound) { 738 | if fieldType == rawValueType { 739 | // The inner element should not be parsed for RawValues. 740 | } else if t.length > 0 { 741 | t, offset, err = parseTagAndLength(bytes, offset) 742 | if err != nil { 743 | return 744 | } 745 | } else { 746 | if fieldType != flagType { 747 | err = StructuralError{"zero length explicit tag was not an asn1.Flag"} 748 | return 749 | } 750 | v.SetBool(true) 751 | return 752 | } 753 | } else { 754 | // The tags didn't match, it might be an optional element. 755 | ok := setDefaultValue(v, params) 756 | if ok { 757 | offset = initOffset 758 | } else { 759 | err = StructuralError{"explicitly tagged member didn't match"} 760 | } 761 | return 762 | } 763 | } 764 | 765 | matchAny, universalTag, compoundType, ok1 := getUniversalType(fieldType) 766 | if !ok1 { 767 | err = StructuralError{fmt.Sprintf("unknown Go type: %v", fieldType)} 768 | return 769 | } 770 | 771 | // Special case for strings: all the ASN.1 string types map to the Go 772 | // type string. getUniversalType returns the tag for PrintableString 773 | // when it sees a string, so if we see a different string type on the 774 | // wire, we change the universal type to match. 775 | if universalTag == TagPrintableString { 776 | if t.class == ClassUniversal { 777 | switch t.tag { 778 | case TagIA5String, TagGeneralString, TagT61String, TagUTF8String, TagNumericString, TagBMPString: 779 | universalTag = t.tag 780 | } 781 | } else if params.stringType != 0 { 782 | universalTag = params.stringType 783 | } 784 | } 785 | 786 | // Special case for time: UTCTime and GeneralizedTime both map to the 787 | // Go type time.Time. 788 | if universalTag == TagUTCTime && t.tag == TagGeneralizedTime && t.class == ClassUniversal { 789 | universalTag = TagGeneralizedTime 790 | } 791 | 792 | if params.set { 793 | universalTag = TagSet 794 | } 795 | 796 | matchAnyClassAndTag := matchAny 797 | expectedClass := ClassUniversal 798 | expectedTag := universalTag 799 | 800 | if !params.explicit && params.tag != nil { 801 | expectedClass = ClassContextSpecific 802 | expectedTag = *params.tag 803 | matchAnyClassAndTag = false 804 | } 805 | 806 | if !params.explicit && params.application && params.tag != nil { 807 | expectedClass = ClassApplication 808 | expectedTag = *params.tag 809 | matchAnyClassAndTag = false 810 | } 811 | 812 | if !params.explicit && params.private && params.tag != nil { 813 | expectedClass = ClassPrivate 814 | expectedTag = *params.tag 815 | matchAnyClassAndTag = false 816 | } 817 | 818 | // We have unwrapped any explicit tagging at this point. 819 | if !matchAnyClassAndTag && (t.class != expectedClass || t.tag != expectedTag) || 820 | (!matchAny && t.isCompound != compoundType) { 821 | // Tags don't match. Again, it could be an optional element. 822 | ok := setDefaultValue(v, params) 823 | if ok { 824 | offset = initOffset 825 | } else { 826 | err = StructuralError{fmt.Sprintf("tags don't match (%d vs %+v) %+v %s @%d", expectedTag, t, params, fieldType.Name(), offset)} 827 | } 828 | return 829 | } 830 | if invalidLength(offset, t.length, len(bytes)) { 831 | err = SyntaxError{"data truncated"} 832 | return 833 | } 834 | innerBytes := bytes[offset : offset+t.length] 835 | offset += t.length 836 | 837 | // We deal with the structures defined in this package first. 838 | switch fieldType { 839 | case rawValueType: 840 | result := RawValue{t.class, t.tag, t.isCompound, innerBytes, bytes[initOffset:offset]} 841 | v.Set(reflect.ValueOf(result)) 842 | return 843 | case objectIdentifierType: 844 | newSlice, err1 := parseObjectIdentifier(innerBytes) 845 | v.Set(reflect.MakeSlice(v.Type(), len(newSlice), len(newSlice))) 846 | if err1 == nil { 847 | reflect.Copy(v, reflect.ValueOf(newSlice)) 848 | } 849 | err = err1 850 | return 851 | case bitStringType: 852 | bs, err1 := parseBitString(innerBytes) 853 | if err1 == nil { 854 | v.Set(reflect.ValueOf(bs)) 855 | } 856 | err = err1 857 | return 858 | case timeType: 859 | var time time.Time 860 | var err1 error 861 | if universalTag == TagUTCTime { 862 | time, err1 = parseUTCTime(innerBytes) 863 | } else { 864 | time, err1 = parseGeneralizedTime(innerBytes) 865 | } 866 | if err1 == nil { 867 | v.Set(reflect.ValueOf(time)) 868 | } 869 | err = err1 870 | return 871 | case enumeratedType: 872 | parsedInt, err1 := parseInt32(innerBytes) 873 | if err1 == nil { 874 | v.SetInt(int64(parsedInt)) 875 | } 876 | err = err1 877 | return 878 | case flagType: 879 | v.SetBool(true) 880 | return 881 | case bigIntType: 882 | parsedInt, err1 := parseBigInt(innerBytes) 883 | if err1 == nil { 884 | v.Set(reflect.ValueOf(parsedInt)) 885 | } 886 | err = err1 887 | return 888 | } 889 | switch val := v; val.Kind() { 890 | case reflect.Bool: 891 | parsedBool, err1 := parseBool(innerBytes) 892 | if err1 == nil { 893 | val.SetBool(parsedBool) 894 | } 895 | err = err1 896 | return 897 | case reflect.Int, reflect.Int32, reflect.Int64: 898 | if val.Type().Size() == 4 { 899 | parsedInt, err1 := parseInt32(innerBytes) 900 | if err1 == nil { 901 | val.SetInt(int64(parsedInt)) 902 | } 903 | err = err1 904 | } else { 905 | parsedInt, err1 := parseInt64(innerBytes) 906 | if err1 == nil { 907 | val.SetInt(parsedInt) 908 | } 909 | err = err1 910 | } 911 | return 912 | // TODO(dfc) Add support for the remaining integer types 913 | case reflect.Struct: 914 | structType := fieldType 915 | 916 | for i := 0; i < structType.NumField(); i++ { 917 | if structType.Field(i).PkgPath != "" { 918 | err = StructuralError{"struct contains unexported fields"} 919 | return 920 | } 921 | } 922 | 923 | if structType.NumField() > 0 && 924 | structType.Field(0).Type == rawContentsType { 925 | bytes := bytes[initOffset:offset] 926 | val.Field(0).Set(reflect.ValueOf(RawContent(bytes))) 927 | } 928 | 929 | innerOffset := 0 930 | for i := 0; i < structType.NumField(); i++ { 931 | field := structType.Field(i) 932 | if i == 0 && field.Type == rawContentsType { 933 | continue 934 | } 935 | innerOffset, err = parseField(val.Field(i), innerBytes, innerOffset, parseFieldParameters(field.Tag.Get("asn1"))) 936 | if err != nil { 937 | return 938 | } 939 | } 940 | // We allow extra bytes at the end of the SEQUENCE because 941 | // adding elements to the end has been used in X.509 as the 942 | // version numbers have increased. 943 | return 944 | case reflect.Slice: 945 | sliceType := fieldType 946 | if sliceType.Elem().Kind() == reflect.Uint8 { 947 | val.Set(reflect.MakeSlice(sliceType, len(innerBytes), len(innerBytes))) 948 | reflect.Copy(val, reflect.ValueOf(innerBytes)) 949 | return 950 | } 951 | newSlice, err1 := parseSequenceOf(innerBytes, sliceType, sliceType.Elem()) 952 | if err1 == nil { 953 | val.Set(newSlice) 954 | } 955 | err = err1 956 | return 957 | case reflect.String: 958 | var v string 959 | switch universalTag { 960 | case TagPrintableString: 961 | v, err = parsePrintableString(innerBytes) 962 | case TagNumericString: 963 | v, err = parseNumericString(innerBytes) 964 | case TagIA5String: 965 | v, err = parseIA5String(innerBytes) 966 | case TagT61String: 967 | v, err = parseT61String(innerBytes) 968 | case TagUTF8String: 969 | v, err = parseUTF8String(innerBytes) 970 | case TagGeneralString: 971 | // GeneralString is specified in ISO-2022/ECMA-35, 972 | // A brief review suggests that it includes structures 973 | // that allow the encoding to change midstring and 974 | // such. We give up and pass it as an 8-bit string. 975 | v, err = parseT61String(innerBytes) 976 | case TagBMPString: 977 | v, err = parseBMPString(innerBytes) 978 | 979 | default: 980 | err = SyntaxError{fmt.Sprintf("internal error: unknown string type %d", universalTag)} 981 | } 982 | if err == nil { 983 | val.SetString(v) 984 | } 985 | return 986 | } 987 | err = StructuralError{"unsupported: " + v.Type().String()} 988 | return 989 | } 990 | 991 | // canHaveDefaultValue reports whether k is a Kind that we will set a default 992 | // value for. (A signed integer, essentially.) 993 | func canHaveDefaultValue(k reflect.Kind) bool { 994 | switch k { 995 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 996 | return true 997 | } 998 | 999 | return false 1000 | } 1001 | 1002 | // setDefaultValue is used to install a default value, from a tag string, into 1003 | // a Value. It is successful if the field was optional, even if a default value 1004 | // wasn't provided or it failed to install it into the Value. 1005 | func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { 1006 | if !params.optional { 1007 | return 1008 | } 1009 | ok = true 1010 | if params.defaultValue == nil { 1011 | return 1012 | } 1013 | if canHaveDefaultValue(v.Kind()) { 1014 | v.SetInt(*params.defaultValue) 1015 | } 1016 | return 1017 | } 1018 | 1019 | // Unmarshal parses the DER-encoded ASN.1 data structure b 1020 | // and uses the reflect package to fill in an arbitrary value pointed at by val. 1021 | // Because Unmarshal uses the reflect package, the structs 1022 | // being written to must use upper case field names. 1023 | // 1024 | // An ASN.1 INTEGER can be written to an int, int32, int64, 1025 | // or *big.Int (from the math/big package). 1026 | // If the encoded value does not fit in the Go type, 1027 | // Unmarshal returns a parse error. 1028 | // 1029 | // An ASN.1 BIT STRING can be written to a BitString. 1030 | // 1031 | // An ASN.1 OCTET STRING can be written to a []byte. 1032 | // 1033 | // An ASN.1 OBJECT IDENTIFIER can be written to an 1034 | // ObjectIdentifier. 1035 | // 1036 | // An ASN.1 ENUMERATED can be written to an Enumerated. 1037 | // 1038 | // An ASN.1 UTCTIME or GENERALIZEDTIME can be written to a time.Time. 1039 | // 1040 | // An ASN.1 PrintableString, IA5String, or NumericString can be written to a string. 1041 | // 1042 | // Any of the above ASN.1 values can be written to an interface{}. 1043 | // The value stored in the interface has the corresponding Go type. 1044 | // For integers, that type is int64. 1045 | // 1046 | // An ASN.1 SEQUENCE OF x or SET OF x can be written 1047 | // to a slice if an x can be written to the slice's element type. 1048 | // 1049 | // An ASN.1 SEQUENCE or SET can be written to a struct 1050 | // if each of the elements in the sequence can be 1051 | // written to the corresponding element in the struct. 1052 | // 1053 | // The following tags on struct fields have special meaning to Unmarshal: 1054 | // 1055 | // application specifies that an APPLICATION tag is used 1056 | // private specifies that a PRIVATE tag is used 1057 | // default:x sets the default value for optional integer fields (only used if optional is also present) 1058 | // explicit specifies that an additional, explicit tag wraps the implicit one 1059 | // optional marks the field as ASN.1 OPTIONAL 1060 | // set causes a SET, rather than a SEQUENCE type to be expected 1061 | // tag:x specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC 1062 | // 1063 | // If the type of the first field of a structure is RawContent then the raw 1064 | // ASN1 contents of the struct will be stored in it. 1065 | // 1066 | // If the type name of a slice element ends with "SET" then it's treated as if 1067 | // the "set" tag was set on it. This can be used with nested slices where a 1068 | // struct tag cannot be given. 1069 | // 1070 | // Other ASN.1 types are not supported; if it encounters them, 1071 | // Unmarshal returns a parse error. 1072 | func Unmarshal(b []byte, val interface{}) (rest []byte, err error) { 1073 | return UnmarshalWithParams(b, val, "") 1074 | } 1075 | 1076 | // UnmarshalWithParams allows field parameters to be specified for the 1077 | // top-level element. The form of the params is the same as the field tags. 1078 | func UnmarshalWithParams(b []byte, val interface{}, params string) (rest []byte, err error) { 1079 | v := reflect.ValueOf(val).Elem() 1080 | offset, err := parseField(v, b, 0, parseFieldParameters(params)) 1081 | if err != nil { 1082 | return nil, err 1083 | } 1084 | return b[offset:], nil 1085 | } 1086 | -------------------------------------------------------------------------------- /asn1/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package asn1 6 | 7 | import ( 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // ASN.1 objects have metadata preceding them: 14 | // the tag: the type of the object 15 | // a flag denoting if this object is compound or not 16 | // the class type: the namespace of the tag 17 | // the length of the object, in bytes 18 | 19 | // Here are some standard tags and classes 20 | 21 | // ASN.1 tags represent the type of the following object. 22 | const ( 23 | TagBoolean = 1 24 | TagInteger = 2 25 | TagBitString = 3 26 | TagOctetString = 4 27 | TagNull = 5 28 | TagOID = 6 29 | TagEnum = 10 30 | TagUTF8String = 12 31 | TagSequence = 16 32 | TagSet = 17 33 | TagNumericString = 18 34 | TagPrintableString = 19 35 | TagT61String = 20 36 | TagIA5String = 22 37 | TagUTCTime = 23 38 | TagGeneralizedTime = 24 39 | TagGeneralString = 27 40 | TagBMPString = 30 41 | ) 42 | 43 | // ASN.1 class types represent the namespace of the tag. 44 | const ( 45 | ClassUniversal = 0 46 | ClassApplication = 1 47 | ClassContextSpecific = 2 48 | ClassPrivate = 3 49 | ) 50 | 51 | type tagAndLength struct { 52 | class, tag, length int 53 | isCompound bool 54 | } 55 | 56 | // ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead 57 | // of" and "in addition to". When not specified, every primitive type has a 58 | // default tag in the UNIVERSAL class. 59 | // 60 | // For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1 61 | // doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT 62 | // CONTEXT-SPECIFIC 42], that means that the tag is replaced by another. 63 | // 64 | // On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an 65 | // /additional/ tag would wrap the default tag. This explicit tag will have the 66 | // compound flag set. 67 | // 68 | // (This is used in order to remove ambiguity with optional elements.) 69 | // 70 | // You can layer EXPLICIT and IMPLICIT tags to an arbitrary depth, however we 71 | // don't support that here. We support a single layer of EXPLICIT or IMPLICIT 72 | // tagging with tag strings on the fields of a structure. 73 | 74 | // fieldParameters is the parsed representation of tag string from a structure field. 75 | type fieldParameters struct { 76 | optional bool // true iff the field is OPTIONAL 77 | explicit bool // true iff an EXPLICIT tag is in use. 78 | application bool // true iff an APPLICATION tag is in use. 79 | private bool // true iff a PRIVATE tag is in use. 80 | defaultValue *int64 // a default value for INTEGER typed fields (maybe nil). 81 | tag *int // the EXPLICIT or IMPLICIT tag (maybe nil). 82 | stringType int // the string tag to use when marshaling. 83 | timeType int // the time tag to use when marshaling. 84 | set bool // true iff this should be encoded as a SET 85 | omitEmpty bool // true iff this should be omitted if empty when marshaling. 86 | 87 | // Invariants: 88 | // if explicit is set, tag is non-nil. 89 | } 90 | 91 | // Given a tag string with the format specified in the package comment, 92 | // parseFieldParameters will parse it into a fieldParameters structure, 93 | // ignoring unknown parts of the string. 94 | func parseFieldParameters(str string) (ret fieldParameters) { 95 | for _, part := range strings.Split(str, ",") { 96 | switch { 97 | case part == "optional": 98 | ret.optional = true 99 | case part == "explicit": 100 | ret.explicit = true 101 | if ret.tag == nil { 102 | ret.tag = new(int) 103 | } 104 | case part == "generalized": 105 | ret.timeType = TagGeneralizedTime 106 | case part == "utc": 107 | ret.timeType = TagUTCTime 108 | case part == "ia5": 109 | ret.stringType = TagIA5String 110 | case part == "printable": 111 | ret.stringType = TagPrintableString 112 | case part == "numeric": 113 | ret.stringType = TagNumericString 114 | case part == "utf8": 115 | ret.stringType = TagUTF8String 116 | case strings.HasPrefix(part, "default:"): 117 | i, err := strconv.ParseInt(part[8:], 10, 64) 118 | if err == nil { 119 | ret.defaultValue = new(int64) 120 | *ret.defaultValue = i 121 | } 122 | case strings.HasPrefix(part, "tag:"): 123 | i, err := strconv.Atoi(part[4:]) 124 | if err == nil { 125 | ret.tag = new(int) 126 | *ret.tag = i 127 | } 128 | case part == "set": 129 | ret.set = true 130 | case part == "application": 131 | ret.application = true 132 | if ret.tag == nil { 133 | ret.tag = new(int) 134 | } 135 | case part == "private": 136 | ret.private = true 137 | if ret.tag == nil { 138 | ret.tag = new(int) 139 | } 140 | case part == "omitempty": 141 | ret.omitEmpty = true 142 | } 143 | } 144 | return 145 | } 146 | 147 | // Given a reflected Go type, getUniversalType returns the default tag number 148 | // and expected compound flag. 149 | func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) { 150 | switch t { 151 | case rawValueType: 152 | return true, -1, false, true 153 | case objectIdentifierType: 154 | return false, TagOID, false, true 155 | case bitStringType: 156 | return false, TagBitString, false, true 157 | case timeType: 158 | return false, TagUTCTime, false, true 159 | case enumeratedType: 160 | return false, TagEnum, false, true 161 | case bigIntType: 162 | return false, TagInteger, false, true 163 | } 164 | switch t.Kind() { 165 | case reflect.Bool: 166 | return false, TagBoolean, false, true 167 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 168 | return false, TagInteger, false, true 169 | case reflect.Struct: 170 | return false, TagSequence, true, true 171 | case reflect.Slice: 172 | if t.Elem().Kind() == reflect.Uint8 { 173 | return false, TagOctetString, false, true 174 | } 175 | if strings.HasSuffix(t.Name(), "SET") { 176 | return false, TagSet, true, true 177 | } 178 | return false, TagSequence, true, true 179 | case reflect.String: 180 | return false, TagPrintableString, false, true 181 | } 182 | return false, 0, false, false 183 | } 184 | -------------------------------------------------------------------------------- /asn1/marshal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package asn1 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "math/big" 11 | "reflect" 12 | "time" 13 | "unicode/utf8" 14 | ) 15 | 16 | var ( 17 | byte00Encoder encoder = byteEncoder(0x00) 18 | byteFFEncoder encoder = byteEncoder(0xff) 19 | ) 20 | 21 | // encoder represents an ASN.1 element that is waiting to be marshaled. 22 | type encoder interface { 23 | // Len returns the number of bytes needed to marshal this element. 24 | Len() int 25 | // Encode encodes this element by writing Len() bytes to dst. 26 | Encode(dst []byte) 27 | } 28 | 29 | type byteEncoder byte 30 | 31 | func (c byteEncoder) Len() int { 32 | return 1 33 | } 34 | 35 | func (c byteEncoder) Encode(dst []byte) { 36 | dst[0] = byte(c) 37 | } 38 | 39 | type bytesEncoder []byte 40 | 41 | func (b bytesEncoder) Len() int { 42 | return len(b) 43 | } 44 | 45 | func (b bytesEncoder) Encode(dst []byte) { 46 | if copy(dst, b) != len(b) { 47 | panic("internal error") 48 | } 49 | } 50 | 51 | type stringEncoder string 52 | 53 | func (s stringEncoder) Len() int { 54 | return len(s) 55 | } 56 | 57 | func (s stringEncoder) Encode(dst []byte) { 58 | if copy(dst, s) != len(s) { 59 | panic("internal error") 60 | } 61 | } 62 | 63 | type multiEncoder []encoder 64 | 65 | func (m multiEncoder) Len() int { 66 | var size int 67 | for _, e := range m { 68 | size += e.Len() 69 | } 70 | return size 71 | } 72 | 73 | func (m multiEncoder) Encode(dst []byte) { 74 | var off int 75 | for _, e := range m { 76 | e.Encode(dst[off:]) 77 | off += e.Len() 78 | } 79 | } 80 | 81 | type taggedEncoder struct { 82 | // scratch contains temporary space for encoding the tag and length of 83 | // an element in order to avoid extra allocations. 84 | scratch [8]byte 85 | tag encoder 86 | body encoder 87 | } 88 | 89 | func (t *taggedEncoder) Len() int { 90 | return t.tag.Len() + t.body.Len() 91 | } 92 | 93 | func (t *taggedEncoder) Encode(dst []byte) { 94 | t.tag.Encode(dst) 95 | t.body.Encode(dst[t.tag.Len():]) 96 | } 97 | 98 | type int64Encoder int64 99 | 100 | func (i int64Encoder) Len() int { 101 | n := 1 102 | 103 | for i > 127 { 104 | n++ 105 | i >>= 8 106 | } 107 | 108 | for i < -128 { 109 | n++ 110 | i >>= 8 111 | } 112 | 113 | return n 114 | } 115 | 116 | func (i int64Encoder) Encode(dst []byte) { 117 | n := i.Len() 118 | 119 | for j := 0; j < n; j++ { 120 | dst[j] = byte(i >> uint((n-1-j)*8)) 121 | } 122 | } 123 | 124 | func base128IntLength(n int64) int { 125 | if n == 0 { 126 | return 1 127 | } 128 | 129 | l := 0 130 | for i := n; i > 0; i >>= 7 { 131 | l++ 132 | } 133 | 134 | return l 135 | } 136 | 137 | func appendBase128Int(dst []byte, n int64) []byte { 138 | l := base128IntLength(n) 139 | 140 | for i := l - 1; i >= 0; i-- { 141 | o := byte(n >> uint(i*7)) 142 | o &= 0x7f 143 | if i != 0 { 144 | o |= 0x80 145 | } 146 | 147 | dst = append(dst, o) 148 | } 149 | 150 | return dst 151 | } 152 | 153 | func makeBigInt(n *big.Int) (encoder, error) { 154 | if n == nil { 155 | return nil, StructuralError{"empty integer"} 156 | } 157 | 158 | if n.Sign() < 0 { 159 | // A negative number has to be converted to two's-complement 160 | // form. So we'll invert and subtract 1. If the 161 | // most-significant-bit isn't set then we'll need to pad the 162 | // beginning with 0xff in order to keep the number negative. 163 | nMinus1 := new(big.Int).Neg(n) 164 | nMinus1.Sub(nMinus1, bigOne) 165 | bytes := nMinus1.Bytes() 166 | for i := range bytes { 167 | bytes[i] ^= 0xff 168 | } 169 | if len(bytes) == 0 || bytes[0]&0x80 == 0 { 170 | return multiEncoder([]encoder{byteFFEncoder, bytesEncoder(bytes)}), nil 171 | } 172 | return bytesEncoder(bytes), nil 173 | } else if n.Sign() == 0 { 174 | // Zero is written as a single 0 zero rather than no bytes. 175 | return byte00Encoder, nil 176 | } else { 177 | bytes := n.Bytes() 178 | if len(bytes) > 0 && bytes[0]&0x80 != 0 { 179 | // We'll have to pad this with 0x00 in order to stop it 180 | // looking like a negative number. 181 | return multiEncoder([]encoder{byte00Encoder, bytesEncoder(bytes)}), nil 182 | } 183 | return bytesEncoder(bytes), nil 184 | } 185 | } 186 | 187 | func appendLength(dst []byte, i int) []byte { 188 | n := lengthLength(i) 189 | 190 | for ; n > 0; n-- { 191 | dst = append(dst, byte(i>>uint((n-1)*8))) 192 | } 193 | 194 | return dst 195 | } 196 | 197 | func lengthLength(i int) (numBytes int) { 198 | numBytes = 1 199 | for i > 255 { 200 | numBytes++ 201 | i >>= 8 202 | } 203 | return 204 | } 205 | 206 | func appendTagAndLength(dst []byte, t tagAndLength) []byte { 207 | b := uint8(t.class) << 6 208 | if t.isCompound { 209 | b |= 0x20 210 | } 211 | if t.tag >= 31 { 212 | b |= 0x1f 213 | dst = append(dst, b) 214 | dst = appendBase128Int(dst, int64(t.tag)) 215 | } else { 216 | b |= uint8(t.tag) 217 | dst = append(dst, b) 218 | } 219 | 220 | if t.length >= 128 { 221 | l := lengthLength(t.length) 222 | dst = append(dst, 0x80|byte(l)) 223 | dst = appendLength(dst, t.length) 224 | } else { 225 | dst = append(dst, byte(t.length)) 226 | } 227 | 228 | return dst 229 | } 230 | 231 | type bitStringEncoder BitString 232 | 233 | func (b bitStringEncoder) Len() int { 234 | return len(b.Bytes) + 1 235 | } 236 | 237 | func (b bitStringEncoder) Encode(dst []byte) { 238 | dst[0] = byte((8 - b.BitLength%8) % 8) 239 | if copy(dst[1:], b.Bytes) != len(b.Bytes) { 240 | panic("internal error") 241 | } 242 | } 243 | 244 | type oidEncoder []int 245 | 246 | func (oid oidEncoder) Len() int { 247 | l := base128IntLength(int64(oid[0]*40 + oid[1])) 248 | for i := 2; i < len(oid); i++ { 249 | l += base128IntLength(int64(oid[i])) 250 | } 251 | return l 252 | } 253 | 254 | func (oid oidEncoder) Encode(dst []byte) { 255 | dst = appendBase128Int(dst[:0], int64(oid[0]*40+oid[1])) 256 | for i := 2; i < len(oid); i++ { 257 | dst = appendBase128Int(dst, int64(oid[i])) 258 | } 259 | } 260 | 261 | func makeObjectIdentifier(oid []int) (e encoder, err error) { 262 | if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) { 263 | return nil, StructuralError{"invalid object identifier"} 264 | } 265 | 266 | return oidEncoder(oid), nil 267 | } 268 | 269 | func makePrintableString(s string) (e encoder, err error) { 270 | for i := 0; i < len(s); i++ { 271 | // The asterisk is often used in PrintableString, even though 272 | // it is invalid. If a PrintableString was specifically 273 | // requested then the asterisk is permitted by this code. 274 | // Ampersand is allowed in parsing due a handful of CA 275 | // certificates, however when making new certificates 276 | // it is rejected. 277 | if !isPrintable(s[i], allowAsterisk, rejectAmpersand) { 278 | return nil, StructuralError{"PrintableString contains invalid character"} 279 | } 280 | } 281 | 282 | return stringEncoder(s), nil 283 | } 284 | 285 | func makeIA5String(s string) (e encoder, err error) { 286 | for i := 0; i < len(s); i++ { 287 | if s[i] > 127 { 288 | return nil, StructuralError{"IA5String contains invalid character"} 289 | } 290 | } 291 | 292 | return stringEncoder(s), nil 293 | } 294 | 295 | func makeNumericString(s string) (e encoder, err error) { 296 | for i := 0; i < len(s); i++ { 297 | if !isNumeric(s[i]) { 298 | return nil, StructuralError{"NumericString contains invalid character"} 299 | } 300 | } 301 | 302 | return stringEncoder(s), nil 303 | } 304 | 305 | func makeUTF8String(s string) encoder { 306 | return stringEncoder(s) 307 | } 308 | 309 | func appendTwoDigits(dst []byte, v int) []byte { 310 | return append(dst, byte('0'+(v/10)%10), byte('0'+v%10)) 311 | } 312 | 313 | func appendFourDigits(dst []byte, v int) []byte { 314 | var bytes [4]byte 315 | for i := range bytes { 316 | bytes[3-i] = '0' + byte(v%10) 317 | v /= 10 318 | } 319 | return append(dst, bytes[:]...) 320 | } 321 | 322 | func outsideUTCRange(t time.Time) bool { 323 | year := t.Year() 324 | return year < 1950 || year >= 2050 325 | } 326 | 327 | func makeUTCTime(t time.Time) (e encoder, err error) { 328 | dst := make([]byte, 0, 18) 329 | 330 | dst, err = appendUTCTime(dst, t) 331 | if err != nil { 332 | return nil, err 333 | } 334 | 335 | return bytesEncoder(dst), nil 336 | } 337 | 338 | func makeGeneralizedTime(t time.Time) (e encoder, err error) { 339 | dst := make([]byte, 0, 20) 340 | 341 | dst, err = appendGeneralizedTime(dst, t) 342 | if err != nil { 343 | return nil, err 344 | } 345 | 346 | return bytesEncoder(dst), nil 347 | } 348 | 349 | func appendUTCTime(dst []byte, t time.Time) (ret []byte, err error) { 350 | year := t.Year() 351 | 352 | switch { 353 | case 1950 <= year && year < 2000: 354 | dst = appendTwoDigits(dst, year-1900) 355 | case 2000 <= year && year < 2050: 356 | dst = appendTwoDigits(dst, year-2000) 357 | default: 358 | return nil, StructuralError{"cannot represent time as UTCTime"} 359 | } 360 | 361 | return appendTimeCommon(dst, t), nil 362 | } 363 | 364 | func appendGeneralizedTime(dst []byte, t time.Time) (ret []byte, err error) { 365 | year := t.Year() 366 | if year < 0 || year > 9999 { 367 | return nil, StructuralError{"cannot represent time as GeneralizedTime"} 368 | } 369 | 370 | dst = appendFourDigits(dst, year) 371 | 372 | return appendTimeCommon(dst, t), nil 373 | } 374 | 375 | func appendTimeCommon(dst []byte, t time.Time) []byte { 376 | _, month, day := t.Date() 377 | 378 | dst = appendTwoDigits(dst, int(month)) 379 | dst = appendTwoDigits(dst, day) 380 | 381 | hour, min, sec := t.Clock() 382 | 383 | dst = appendTwoDigits(dst, hour) 384 | dst = appendTwoDigits(dst, min) 385 | dst = appendTwoDigits(dst, sec) 386 | 387 | _, offset := t.Zone() 388 | 389 | switch { 390 | case offset/60 == 0: 391 | return append(dst, 'Z') 392 | case offset > 0: 393 | dst = append(dst, '+') 394 | case offset < 0: 395 | dst = append(dst, '-') 396 | } 397 | 398 | offsetMinutes := offset / 60 399 | if offsetMinutes < 0 { 400 | offsetMinutes = -offsetMinutes 401 | } 402 | 403 | dst = appendTwoDigits(dst, offsetMinutes/60) 404 | dst = appendTwoDigits(dst, offsetMinutes%60) 405 | 406 | return dst 407 | } 408 | 409 | func stripTagAndLength(in []byte) []byte { 410 | _, offset, err := parseTagAndLength(in, 0) 411 | if err != nil { 412 | return in 413 | } 414 | return in[offset:] 415 | } 416 | 417 | func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error) { 418 | switch value.Type() { 419 | case flagType: 420 | return bytesEncoder(nil), nil 421 | case timeType: 422 | t := value.Interface().(time.Time) 423 | if params.timeType == TagGeneralizedTime || outsideUTCRange(t) { 424 | return makeGeneralizedTime(t) 425 | } 426 | return makeUTCTime(t) 427 | case bitStringType: 428 | return bitStringEncoder(value.Interface().(BitString)), nil 429 | case objectIdentifierType: 430 | return makeObjectIdentifier(value.Interface().(ObjectIdentifier)) 431 | case bigIntType: 432 | return makeBigInt(value.Interface().(*big.Int)) 433 | } 434 | 435 | switch v := value; v.Kind() { 436 | case reflect.Bool: 437 | if v.Bool() { 438 | return byteFFEncoder, nil 439 | } 440 | return byte00Encoder, nil 441 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 442 | return int64Encoder(v.Int()), nil 443 | case reflect.Struct: 444 | t := v.Type() 445 | 446 | for i := 0; i < t.NumField(); i++ { 447 | if t.Field(i).PkgPath != "" { 448 | return nil, StructuralError{"struct contains unexported fields"} 449 | } 450 | } 451 | 452 | startingField := 0 453 | 454 | n := t.NumField() 455 | if n == 0 { 456 | return bytesEncoder(nil), nil 457 | } 458 | 459 | // If the first element of the structure is a non-empty 460 | // RawContents, then we don't bother serializing the rest. 461 | if t.Field(0).Type == rawContentsType { 462 | s := v.Field(0) 463 | if s.Len() > 0 { 464 | bytes := s.Bytes() 465 | /* The RawContents will contain the tag and 466 | * length fields but we'll also be writing 467 | * those ourselves, so we strip them out of 468 | * bytes */ 469 | return bytesEncoder(stripTagAndLength(bytes)), nil 470 | } 471 | 472 | startingField = 1 473 | } 474 | 475 | switch n1 := n - startingField; n1 { 476 | case 0: 477 | return bytesEncoder(nil), nil 478 | case 1: 479 | return makeField(v.Field(startingField), parseFieldParameters(t.Field(startingField).Tag.Get("asn1"))) 480 | default: 481 | m := make([]encoder, n1) 482 | for i := 0; i < n1; i++ { 483 | m[i], err = makeField(v.Field(i+startingField), parseFieldParameters(t.Field(i+startingField).Tag.Get("asn1"))) 484 | if err != nil { 485 | return nil, err 486 | } 487 | } 488 | 489 | return multiEncoder(m), nil 490 | } 491 | case reflect.Slice: 492 | sliceType := v.Type() 493 | if sliceType.Elem().Kind() == reflect.Uint8 { 494 | return bytesEncoder(v.Bytes()), nil 495 | } 496 | 497 | var fp fieldParameters 498 | 499 | switch l := v.Len(); l { 500 | case 0: 501 | return bytesEncoder(nil), nil 502 | case 1: 503 | return makeField(v.Index(0), fp) 504 | default: 505 | m := make([]encoder, l) 506 | 507 | for i := 0; i < l; i++ { 508 | m[i], err = makeField(v.Index(i), fp) 509 | if err != nil { 510 | return nil, err 511 | } 512 | } 513 | 514 | return multiEncoder(m), nil 515 | } 516 | case reflect.String: 517 | switch params.stringType { 518 | case TagIA5String: 519 | return makeIA5String(v.String()) 520 | case TagPrintableString: 521 | return makePrintableString(v.String()) 522 | case TagNumericString: 523 | return makeNumericString(v.String()) 524 | default: 525 | return makeUTF8String(v.String()), nil 526 | } 527 | } 528 | 529 | return nil, StructuralError{"unknown Go type"} 530 | } 531 | 532 | func makeField(v reflect.Value, params fieldParameters) (e encoder, err error) { 533 | if !v.IsValid() { 534 | return nil, fmt.Errorf("asn1: cannot marshal nil value") 535 | } 536 | // If the field is an interface{} then recurse into it. 537 | if v.Kind() == reflect.Interface && v.Type().NumMethod() == 0 { 538 | return makeField(v.Elem(), params) 539 | } 540 | 541 | if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty { 542 | return bytesEncoder(nil), nil 543 | } 544 | 545 | if params.optional && params.defaultValue != nil && canHaveDefaultValue(v.Kind()) { 546 | defaultValue := reflect.New(v.Type()).Elem() 547 | defaultValue.SetInt(*params.defaultValue) 548 | 549 | if reflect.DeepEqual(v.Interface(), defaultValue.Interface()) { 550 | return bytesEncoder(nil), nil 551 | } 552 | } 553 | 554 | // If no default value is given then the zero value for the type is 555 | // assumed to be the default value. This isn't obviously the correct 556 | // behavior, but it's what Go has traditionally done. 557 | if params.optional && params.defaultValue == nil { 558 | if reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) { 559 | return bytesEncoder(nil), nil 560 | } 561 | } 562 | 563 | if v.Type() == rawValueType { 564 | rv := v.Interface().(RawValue) 565 | if len(rv.FullBytes) != 0 { 566 | return bytesEncoder(rv.FullBytes), nil 567 | } 568 | 569 | t := new(taggedEncoder) 570 | 571 | t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound})) 572 | t.body = bytesEncoder(rv.Bytes) 573 | 574 | return t, nil 575 | } 576 | 577 | matchAny, tag, isCompound, ok := getUniversalType(v.Type()) 578 | if !ok || matchAny { 579 | return nil, StructuralError{fmt.Sprintf("unknown Go type: %v", v.Type())} 580 | } 581 | 582 | if params.timeType != 0 && tag != TagUTCTime { 583 | return nil, StructuralError{"explicit time type given to non-time member"} 584 | } 585 | 586 | if params.stringType != 0 && tag != TagPrintableString { 587 | return nil, StructuralError{"explicit string type given to non-string member"} 588 | } 589 | 590 | switch tag { 591 | case TagPrintableString: 592 | if params.stringType == 0 { 593 | // This is a string without an explicit string type. We'll use 594 | // a PrintableString if the character set in the string is 595 | // sufficiently limited, otherwise we'll use a UTF8String. 596 | for _, r := range v.String() { 597 | if r >= utf8.RuneSelf || !isPrintable(byte(r), rejectAsterisk, rejectAmpersand) { 598 | if !utf8.ValidString(v.String()) { 599 | return nil, errors.New("asn1: string not valid UTF-8") 600 | } 601 | tag = TagUTF8String 602 | break 603 | } 604 | } 605 | } else { 606 | tag = params.stringType 607 | } 608 | case TagUTCTime: 609 | if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) { 610 | tag = TagGeneralizedTime 611 | } 612 | } 613 | 614 | if params.set { 615 | if tag != TagSequence { 616 | return nil, StructuralError{"non sequence tagged as set"} 617 | } 618 | tag = TagSet 619 | } 620 | 621 | t := new(taggedEncoder) 622 | 623 | t.body, err = makeBody(v, params) 624 | if err != nil { 625 | return nil, err 626 | } 627 | 628 | bodyLen := t.body.Len() 629 | 630 | class := ClassUniversal 631 | if params.tag != nil { 632 | if params.application { 633 | class = ClassApplication 634 | } else if params.private { 635 | class = ClassPrivate 636 | } else { 637 | class = ClassContextSpecific 638 | } 639 | 640 | if params.explicit { 641 | t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{ClassUniversal, tag, bodyLen, isCompound})) 642 | 643 | tt := new(taggedEncoder) 644 | 645 | tt.body = t 646 | 647 | tt.tag = bytesEncoder(appendTagAndLength(tt.scratch[:0], tagAndLength{ 648 | class: class, 649 | tag: *params.tag, 650 | length: bodyLen + t.tag.Len(), 651 | isCompound: true, 652 | })) 653 | 654 | return tt, nil 655 | } 656 | 657 | // implicit tag. 658 | tag = *params.tag 659 | } 660 | 661 | t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{class, tag, bodyLen, isCompound})) 662 | 663 | return t, nil 664 | } 665 | 666 | // Marshal returns the ASN.1 encoding of val. 667 | // 668 | // In addition to the struct tags recognised by Unmarshal, the following can be 669 | // used: 670 | // 671 | // ia5: causes strings to be marshaled as ASN.1, IA5String values 672 | // omitempty: causes empty slices to be skipped 673 | // printable: causes strings to be marshaled as ASN.1, PrintableString values 674 | // utf8: causes strings to be marshaled as ASN.1, UTF8String values 675 | // utc: causes time.Time to be marshaled as ASN.1, UTCTime values 676 | // generalized: causes time.Time to be marshaled as ASN.1, GeneralizedTime values 677 | func Marshal(val interface{}) ([]byte, error) { 678 | return MarshalWithParams(val, "") 679 | } 680 | 681 | // MarshalWithParams allows field parameters to be specified for the 682 | // top-level element. The form of the params is the same as the field tags. 683 | func MarshalWithParams(val interface{}, params string) ([]byte, error) { 684 | e, err := makeField(reflect.ValueOf(val), parseFieldParameters(params)) 685 | if err != nil { 686 | return nil, err 687 | } 688 | b := make([]byte, e.Len()) 689 | e.Encode(b) 690 | return b, nil 691 | } 692 | -------------------------------------------------------------------------------- /asn1/marshal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package asn1 6 | 7 | import ( 8 | "bytes" 9 | "encoding/hex" 10 | "math/big" 11 | "reflect" 12 | "strings" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | type intStruct struct { 18 | A int 19 | } 20 | 21 | type twoIntStruct struct { 22 | A int 23 | B int 24 | } 25 | 26 | type bigIntStruct struct { 27 | A *big.Int 28 | } 29 | 30 | type nestedStruct struct { 31 | A intStruct 32 | } 33 | 34 | type rawContentsStruct struct { 35 | Raw RawContent 36 | A int 37 | } 38 | 39 | type implicitTagTest struct { 40 | A int `asn1:"implicit,tag:5"` 41 | } 42 | 43 | type explicitTagTest struct { 44 | A int `asn1:"explicit,tag:5"` 45 | } 46 | 47 | type flagTest struct { 48 | A Flag `asn1:"tag:0,optional"` 49 | } 50 | 51 | type generalizedTimeTest struct { 52 | A time.Time `asn1:"generalized"` 53 | } 54 | 55 | type ia5StringTest struct { 56 | A string `asn1:"ia5"` 57 | } 58 | 59 | type printableStringTest struct { 60 | A string `asn1:"printable"` 61 | } 62 | 63 | type genericStringTest struct { 64 | A string 65 | } 66 | 67 | type optionalRawValueTest struct { 68 | A RawValue `asn1:"optional"` 69 | } 70 | 71 | type omitEmptyTest struct { 72 | A []string `asn1:"omitempty"` 73 | } 74 | 75 | type defaultTest struct { 76 | A int `asn1:"optional,default:1"` 77 | } 78 | 79 | type applicationTest struct { 80 | A int `asn1:"application,tag:0"` 81 | B int `asn1:"application,tag:1,explicit"` 82 | } 83 | 84 | type privateTest struct { 85 | A int `asn1:"private,tag:0"` 86 | B int `asn1:"private,tag:1,explicit"` 87 | C int `asn1:"private,tag:31"` // tag size should be 2 octet 88 | D int `asn1:"private,tag:128"` // tag size should be 3 octet 89 | } 90 | 91 | type numericStringTest struct { 92 | A string `asn1:"numeric"` 93 | } 94 | 95 | type testSET []int 96 | 97 | var PST = time.FixedZone("PST", -8*60*60) 98 | 99 | type marshalTest struct { 100 | in interface{} 101 | out string // hex encoded 102 | } 103 | 104 | func farFuture() time.Time { 105 | t, err := time.Parse(time.RFC3339, "2100-04-05T12:01:01Z") 106 | if err != nil { 107 | panic(err) 108 | } 109 | return t 110 | } 111 | 112 | var marshalTests = []marshalTest{ 113 | {10, "02010a"}, 114 | {127, "02017f"}, 115 | {128, "02020080"}, 116 | {-128, "020180"}, 117 | {-129, "0202ff7f"}, 118 | {intStruct{64}, "3003020140"}, 119 | {bigIntStruct{big.NewInt(0x123456)}, "30050203123456"}, 120 | {twoIntStruct{64, 65}, "3006020140020141"}, 121 | {nestedStruct{intStruct{127}}, "3005300302017f"}, 122 | {[]byte{1, 2, 3}, "0403010203"}, 123 | {implicitTagTest{64}, "3003850140"}, 124 | {explicitTagTest{64}, "3005a503020140"}, 125 | {flagTest{true}, "30028000"}, 126 | {flagTest{false}, "3000"}, 127 | {time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"}, 128 | {time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"}, 129 | {time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"}, 130 | {farFuture(), "180f32313030303430353132303130315a"}, 131 | {generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"}, 132 | {BitString{[]byte{0x80}, 1}, "03020780"}, 133 | {BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"}, 134 | {ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"}, 135 | {ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"}, 136 | {ObjectIdentifier([]int{2, 100, 3}), "0603813403"}, 137 | {"test", "130474657374"}, 138 | { 139 | "" + 140 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 141 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 142 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 143 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x' 144 | "137f" + 145 | "7878787878787878787878787878787878787878787878787878787878787878" + 146 | "7878787878787878787878787878787878787878787878787878787878787878" + 147 | "7878787878787878787878787878787878787878787878787878787878787878" + 148 | "78787878787878787878787878787878787878787878787878787878787878", 149 | }, 150 | { 151 | "" + 152 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 153 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 154 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 155 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x' 156 | "138180" + 157 | "7878787878787878787878787878787878787878787878787878787878787878" + 158 | "7878787878787878787878787878787878787878787878787878787878787878" + 159 | "7878787878787878787878787878787878787878787878787878787878787878" + 160 | "7878787878787878787878787878787878787878787878787878787878787878", 161 | }, 162 | {ia5StringTest{"test"}, "3006160474657374"}, 163 | {optionalRawValueTest{}, "3000"}, 164 | {printableStringTest{"test"}, "3006130474657374"}, 165 | {printableStringTest{"test*"}, "30071305746573742a"}, 166 | {genericStringTest{"test"}, "3006130474657374"}, 167 | {genericStringTest{"test*"}, "30070c05746573742a"}, 168 | {genericStringTest{"test&"}, "30070c057465737426"}, 169 | {rawContentsStruct{nil, 64}, "3003020140"}, 170 | {rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"}, 171 | {RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"}, 172 | {testSET([]int{10}), "310302010a"}, 173 | {omitEmptyTest{[]string{}}, "3000"}, 174 | {omitEmptyTest{[]string{"1"}}, "30053003130131"}, 175 | {"Σ", "0c02cea3"}, 176 | {defaultTest{0}, "3003020100"}, 177 | {defaultTest{1}, "3000"}, 178 | {defaultTest{2}, "3003020102"}, 179 | {applicationTest{1, 2}, "30084001016103020102"}, 180 | {privateTest{1, 2, 3, 4}, "3011c00101e103020102df1f0103df81000104"}, 181 | {numericStringTest{"1 9"}, "30051203312039"}, 182 | } 183 | 184 | func TestMarshal(t *testing.T) { 185 | for i, test := range marshalTests { 186 | data, err := Marshal(test.in) 187 | if err != nil { 188 | t.Errorf("#%d failed: %s", i, err) 189 | } 190 | out, _ := hex.DecodeString(test.out) 191 | if !bytes.Equal(out, data) { 192 | t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) 193 | 194 | } 195 | } 196 | } 197 | 198 | type marshalWithParamsTest struct { 199 | in interface{} 200 | params string 201 | out string // hex encoded 202 | } 203 | 204 | var marshalWithParamsTests = []marshalWithParamsTest{ 205 | {intStruct{10}, "set", "310302010a"}, 206 | {intStruct{10}, "application", "600302010a"}, 207 | {intStruct{10}, "private", "e00302010a"}, 208 | } 209 | 210 | func TestMarshalWithParams(t *testing.T) { 211 | for i, test := range marshalWithParamsTests { 212 | data, err := MarshalWithParams(test.in, test.params) 213 | if err != nil { 214 | t.Errorf("#%d failed: %s", i, err) 215 | } 216 | out, _ := hex.DecodeString(test.out) 217 | if !bytes.Equal(out, data) { 218 | t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) 219 | 220 | } 221 | } 222 | } 223 | 224 | type marshalErrTest struct { 225 | in interface{} 226 | err string 227 | } 228 | 229 | var marshalErrTests = []marshalErrTest{ 230 | {bigIntStruct{nil}, "empty integer"}, 231 | {numericStringTest{"a"}, "invalid character"}, 232 | {ia5StringTest{"\xb0"}, "invalid character"}, 233 | {printableStringTest{"!"}, "invalid character"}, 234 | } 235 | 236 | func TestMarshalError(t *testing.T) { 237 | for i, test := range marshalErrTests { 238 | _, err := Marshal(test.in) 239 | if err == nil { 240 | t.Errorf("#%d should fail, but success", i) 241 | continue 242 | } 243 | 244 | if !strings.Contains(err.Error(), test.err) { 245 | t.Errorf("#%d got: %v want %v", i, err, test.err) 246 | } 247 | } 248 | } 249 | 250 | func TestInvalidUTF8(t *testing.T) { 251 | _, err := Marshal(string([]byte{0xff, 0xff})) 252 | if err == nil { 253 | t.Errorf("invalid UTF8 string was accepted") 254 | } 255 | } 256 | 257 | func TestMarshalOID(t *testing.T) { 258 | var marshalTestsOID = []marshalTest{ 259 | {[]byte("\x06\x01\x30"), "0403060130"}, // bytes format returns a byte sequence \x04 260 | // {ObjectIdentifier([]int{0}), "060100"}, // returns an error as OID 0.0 has the same encoding 261 | {[]byte("\x06\x010"), "0403060130"}, // same as above "\x06\x010" = "\x06\x01" + "0" 262 | {ObjectIdentifier([]int{2, 999, 3}), "0603883703"}, // Example of ITU-T X.690 263 | {ObjectIdentifier([]int{0, 0}), "060100"}, // zero OID 264 | } 265 | for i, test := range marshalTestsOID { 266 | data, err := Marshal(test.in) 267 | if err != nil { 268 | t.Errorf("#%d failed: %s", i, err) 269 | } 270 | out, _ := hex.DecodeString(test.out) 271 | if !bytes.Equal(out, data) { 272 | t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out) 273 | } 274 | } 275 | } 276 | 277 | func TestIssue11130(t *testing.T) { 278 | data := []byte("\x06\x010") // == \x06\x01\x30 == OID = 0 (the figure) 279 | var v interface{} 280 | // v has Zero value here and Elem() would panic 281 | _, err := Unmarshal(data, &v) 282 | if err != nil { 283 | t.Errorf("%v", err) 284 | return 285 | } 286 | if reflect.TypeOf(v).String() != reflect.TypeOf(ObjectIdentifier{}).String() { 287 | t.Errorf("marshal OID returned an invalid type") 288 | return 289 | } 290 | 291 | data1, err := Marshal(v) 292 | if err != nil { 293 | t.Errorf("%v", err) 294 | return 295 | } 296 | 297 | if !bytes.Equal(data, data1) { 298 | t.Errorf("got: %q, want: %q \n", data1, data) 299 | return 300 | } 301 | 302 | var v1 interface{} 303 | _, err = Unmarshal(data1, &v1) 304 | if err != nil { 305 | t.Errorf("%v", err) 306 | return 307 | } 308 | if !reflect.DeepEqual(v, v1) { 309 | t.Errorf("got: %#v data=%q , want : %#v data=%q\n ", v1, data1, v, data) 310 | } 311 | } 312 | 313 | func BenchmarkMarshal(b *testing.B) { 314 | b.ReportAllocs() 315 | 316 | for i := 0; i < b.N; i++ { 317 | for _, test := range marshalTests { 318 | Marshal(test.in) 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /ci/myna-build-linux-amd64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | set -x 4 | 5 | make 6 | 7 | mkdir -p ${DIST_DIR} 8 | 9 | strip myna 10 | cp myna ${DIST_DIR}/ 11 | upx ${DIST_DIR}/myna 12 | 13 | exit 0 14 | 15 | (cd mynaqt && qtdeploy build) 16 | 17 | find mynaqt/deploy 18 | cp mynaqt/deploy/linux/mynaqt ${DIST_DIR}/ 19 | cp -rp mynaqt/deploy/linux/lib ${DIST_DIR}/ 20 | mkdir -p ${DIST_DIR}/plugins 21 | cp -rp mynaqt/deploy/linux/plugins/platforms ${DIST_DIR}/plugins 22 | chrpath -r '$ORIGIN/lib' ${DIST_DIR}/mynaqt 23 | cp mynaqt/deploy/linux/mynaqt.sh ${DIST_DIR}/ 24 | upx ${DIST_DIR}/mynaqt 25 | -------------------------------------------------------------------------------- /ci/myna-build-windows-x64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | set -x 4 | 5 | make windows 6 | 7 | mkdir -p ${DIST_DIR} 8 | 9 | upx myna.exe 10 | cp myna.exe ${DIST_DIR}/ 11 | 12 | exit 0 13 | 14 | (cd mynaqt && qtdeploy build windows) 15 | 16 | find mynaqt/deploy 17 | upx mynaqt/deploy/windows/mynaqt.exe 18 | cp mynaqt/deploy/windows/mynaqt.exe ${DIST_DIR}/ 19 | 20 | -------------------------------------------------------------------------------- /cmd/jpki.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "fmt" 8 | "os" 9 | "strings" 10 | 11 | "github.com/ianmcmahon/encoding_ssh" 12 | "github.com/spf13/cobra" 13 | 14 | "github.com/jpki/myna/libmyna" 15 | ) 16 | 17 | var jpkiCmd = &cobra.Command{ 18 | Use: "jpki", 19 | Short: "公的個人認証関連コマンド", 20 | Long: `公的個人認証関連コマンド 21 | 各種証明書の取得や署名・検証を行います 22 | `, 23 | } 24 | 25 | var jpkiCertCmd = &cobra.Command{ 26 | Use: "cert auth|sign|authca|signca", 27 | Short: "JPKI証明書を表示", 28 | Long: `公的個人認証の証明書を表示します。 29 | 30 | - auth 利用者認証用証明書(スマホJPKIの場合PIN入力が必要) 31 | - authca 利用者認証用CA証明書 32 | - sign 電子署名用証明書(署名用パスワードが必要) 33 | - signca 電子署名用CA証明書 34 | `, 35 | RunE: jpkiCert, 36 | } 37 | 38 | func jpkiCert(cmd *cobra.Command, args []string) error { 39 | if len(args) != 1 { 40 | cmd.Help() 41 | return nil 42 | } 43 | var cert *x509.Certificate 44 | var err error 45 | var pin string 46 | reader, err := libmyna.NewReader(libmyna.OptionDebug) 47 | if err != nil { 48 | return err 49 | } 50 | defer reader.Finalize() 51 | err = reader.Connect() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | jpkiAP, err := reader.SelectJPKIAP() 57 | if err != nil { 58 | return err 59 | } 60 | token, err := jpkiAP.ReadToken() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | switch strings.ToUpper(args[0]) { 66 | case "AUTH": 67 | if token == "JPKIAPGPSETOKEN" { 68 | // スマホJPKI 69 | pin, err = cmd.Flags().GetString("pin") 70 | if pin == "" { 71 | pin, err = inputPin("認証用パスワード(4桁): ") 72 | if err != nil { 73 | return nil 74 | } 75 | } 76 | pin = strings.ToUpper(pin) 77 | err = jpkiAP.VerifyAuthPin(pin) 78 | if err != nil { 79 | return nil 80 | } 81 | } 82 | cert, err = jpkiAP.ReadAuthCert() 83 | case "AUTHCA": 84 | cert, err = jpkiAP.ReadAuthCACert() 85 | case "SIGN": 86 | pin, err = cmd.Flags().GetString("pin") 87 | if pin == "" { 88 | pin, err = inputPin("署名用パスワード(6-16桁): ") 89 | if err != nil { 90 | return nil 91 | } 92 | } 93 | pin = strings.ToUpper(pin) 94 | err = jpkiAP.VerifySignPin(pin) 95 | cert, err = jpkiAP.ReadSignCert() 96 | case "SIGNCA": 97 | cert, err = jpkiAP.ReadSignCACert() 98 | default: 99 | cmd.Usage() 100 | return nil 101 | } 102 | if err != nil { 103 | return err 104 | } 105 | 106 | err = outputCert(cert, cmd) 107 | if err != nil { 108 | return err 109 | } 110 | return nil 111 | } 112 | 113 | func outputCert(cert *x509.Certificate, cmd *cobra.Command) error { 114 | form, _ := cmd.Flags().GetString("form") 115 | jpkiCert := &libmyna.JPKICertificate{cert} 116 | switch form { 117 | case "text": 118 | println(jpkiCert.ToString()) 119 | case "pem": 120 | printCertPem(cert) 121 | case "der": 122 | os.Stdout.Write(cert.Raw) 123 | case "ssh": 124 | printCertSsh(cert) 125 | default: 126 | cmd.Usage() 127 | return nil 128 | } 129 | return nil 130 | } 131 | 132 | func printCertPem(cert *x509.Certificate) { 133 | var block pem.Block 134 | block.Type = "CERTIFICATE" 135 | block.Bytes = cert.Raw 136 | pem.Encode(os.Stdout, &block) 137 | } 138 | 139 | func printCertSsh(cert *x509.Certificate) { 140 | rsaPubkey := cert.PublicKey.(*rsa.PublicKey) 141 | sshPubkey, _ := ssh.EncodePublicKey(*rsaPubkey, "") 142 | fmt.Println(sshPubkey) 143 | } 144 | 145 | func init() { 146 | jpkiCmd.AddCommand(jpkiCertCmd) 147 | jpkiCertCmd.Flags().StringP( 148 | "form", "f", "text", "出力形式(text|pem|der|ssh)") 149 | jpkiCertCmd.Flags().StringP( 150 | "pin", "p", "", "パスワード(署名用証明書のみ)") 151 | } 152 | -------------------------------------------------------------------------------- /cmd/jpki_cms.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/jpki/myna/libmyna" 12 | ) 13 | 14 | var jpkiCmsCmd = &cobra.Command{ 15 | Use: "cms", 16 | Short: "CMS署名と検証を行います", 17 | } 18 | 19 | var jpkiCmsSignCmd = &cobra.Command{ 20 | Use: "sign", 21 | Short: "CMS署名を行います", 22 | RunE: jpkiCmsSign, 23 | } 24 | 25 | var jpkiCmsVerifyCmd = &cobra.Command{ 26 | Use: "verify", 27 | Short: "CMS署名を検証します", 28 | RunE: jpkiCmsVerify, 29 | } 30 | 31 | func jpkiCmsSign(cmd *cobra.Command, args []string) error { 32 | in, _ := cmd.Flags().GetString("in") 33 | if in == "" { 34 | cmd.Usage() 35 | return errors.New("署名対象ファイルを指定してください") 36 | } 37 | out, _ := cmd.Flags().GetString("out") 38 | if out == "" { 39 | cmd.Usage() 40 | return errors.New("出力ファイルを指定してください") 41 | } 42 | 43 | pin, err := cmd.Flags().GetString("pin") 44 | if pin == "" { 45 | pin, err = inputPin("署名用パスワード(6-16桁): ") 46 | if err != nil { 47 | return nil 48 | } 49 | } 50 | pin = strings.ToUpper(pin) 51 | 52 | md, _ := cmd.Flags().GetString("md") 53 | form, _ := cmd.Flags().GetString("form") 54 | detached, _ := cmd.Flags().GetBool("detached") 55 | opts := libmyna.CmsSignOpts{md, form, detached} 56 | err = libmyna.CmsSignJPKISign(pin, in, out, opts) 57 | return err 58 | } 59 | 60 | func jpkiCmsVerify(cmd *cobra.Command, args []string) error { 61 | detached, _ := cmd.Flags().GetBool("detached") 62 | 63 | if len(args) != 1 { 64 | cmd.Usage() 65 | 66 | if detached { 67 | return errors.New("署名ファイルを指定してください") 68 | } else { 69 | return errors.New("検証対象ファイルを指定してください") 70 | } 71 | } 72 | 73 | form, _ := cmd.Flags().GetString("form") 74 | 75 | content, _ := cmd.Flags().GetString("content") 76 | if detached && content == "" { 77 | cmd.Usage() 78 | return errors.New("検証対象ファイルを-cで指定してください") 79 | } else if !detached && content != "" { 80 | fmt.Fprintf(os.Stderr, 81 | "警告: -c は --detached時のみ有効です。'%s'の内容は無視されます。\n", content) 82 | } 83 | 84 | opts := libmyna.CmsVerifyOpts{form, detached, content} 85 | err := libmyna.CmsVerifyJPKISign(args[0], opts) 86 | if err != nil { 87 | return err 88 | } 89 | fmt.Printf("Verification successful\n") 90 | return nil 91 | } 92 | 93 | func init() { 94 | jpkiCmd.AddCommand(jpkiCmsCmd) 95 | jpkiCmsCmd.AddCommand(jpkiCmsSignCmd) 96 | jpkiCmsSignCmd.Flags().StringP( 97 | "pin", "p", "", "署名用パスワード(6-16桁)") 98 | jpkiCmsSignCmd.Flags().StringP( 99 | "in", "i", "", "署名対象ファイル") 100 | jpkiCmsSignCmd.Flags().StringP( 101 | "out", "o", "", "出力ファイル") 102 | jpkiCmsSignCmd.Flags().StringP( 103 | "md", "m", "sha1", "ダイジェストアルゴリズム(sha1|sha256|sha512)") 104 | jpkiCmsSignCmd.Flags().StringP("form", "f", "der", "出力形式(pem,der)") 105 | jpkiCmsSignCmd.Flags().Bool("detached", false, "デタッチ署名 (Detached Signature)") 106 | 107 | jpkiCmsCmd.AddCommand(jpkiCmsVerifyCmd) 108 | jpkiCmsVerifyCmd.Flags().StringP("content", "c", "", "デタッチ署名の検証対象ファイル (--detached時のみ有効)") 109 | jpkiCmsVerifyCmd.Flags().Bool("detached", false, "デタッチ署名 (Detached Signature)") 110 | jpkiCmsVerifyCmd.Flags().StringP("form", "f", "der", "入力形式(pem,der)") 111 | } 112 | -------------------------------------------------------------------------------- /cmd/pin.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/howeyc/gopass" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/jpki/myna/libmyna" 11 | ) 12 | 13 | var pinCmd = &cobra.Command{ 14 | Use: "pin", 15 | Short: "PIN関連操作", 16 | } 17 | 18 | var pinStatusCmd = &cobra.Command{ 19 | Use: "status", 20 | Short: "PINステータスを表示", 21 | RunE: pinStatus, 22 | PreRunE: checkCard, 23 | } 24 | 25 | func pinStatus(cmd *cobra.Command, args []string) error { 26 | status, err := libmyna.GetPinStatus() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if visual_pin_a, ok := status["visual_pin_a"]; ok { 32 | fmt.Printf("券面事項PIN(A):\tのこり%2d回\n", visual_pin_a) 33 | } 34 | if visual_pin_b, ok := status["visual_pin_b"]; ok { 35 | fmt.Printf("券面事項PIN(B):\tのこり%2d回\n", visual_pin_b) 36 | } 37 | if text_pin, ok := status["text_pin"]; ok { 38 | fmt.Printf("入力補助PIN:\tのこり%2d回\n", text_pin) 39 | } 40 | if text_pin_a, ok := status["text_pin_a"]; ok { 41 | fmt.Printf("入力補助PIN(A):\tのこり%2d回\n", text_pin_a) 42 | } 43 | if text_pin_b, ok := status["text_pin_b"]; ok { 44 | fmt.Printf("入力補助PIN(B):\tのこり%2d回\n", text_pin_b) 45 | } 46 | if jpki_auth, ok := status["jpki_auth"]; ok { 47 | fmt.Printf("JPKI認証用PIN:\tのこり%2d回\n", jpki_auth) 48 | } 49 | if jpki_sign, ok := status["jpki_sign"]; ok { 50 | fmt.Printf("JPKI署名用PIN:\tのこり%2d回\n", jpki_sign) 51 | } 52 | return nil 53 | } 54 | 55 | var pinChangeCmd = &cobra.Command{ 56 | Use: "change", 57 | Short: "各種PINを変更", 58 | } 59 | 60 | var pinChangeCardCmd = &cobra.Command{ 61 | Use: "card", 62 | Short: "券面入力補助用PINを変更", 63 | Long: `券面入力補助用PINを変更します 64 | 暗証番号は4桁の数字を入力してください 65 | `, 66 | RunE: pinChangeCard, 67 | } 68 | 69 | func pinChangeCard(cmd *cobra.Command, args []string) error { 70 | fmt.Println(cmd.Long) 71 | pinName := "券面入力補助用PIN(4桁)" 72 | pin, err := cmd.Flags().GetString("pin") 73 | if pin == "" { 74 | pin, err = inputPin(fmt.Sprintf("現在の%s: ", pinName)) 75 | if err != nil { 76 | return nil 77 | } 78 | } 79 | 80 | newpin, err := cmd.Flags().GetString("newpin") 81 | if newpin == "" { 82 | newpin, err = inputPin(fmt.Sprintf("新しい%s: ", pinName)) 83 | if err != nil { 84 | return nil 85 | } 86 | } 87 | 88 | err = libmyna.ChangeCardInputHelperPin(pin, newpin) 89 | if err != nil { 90 | return err 91 | } 92 | fmt.Printf("%sを変更しました", pinName) 93 | return nil 94 | } 95 | 96 | var pinChangeJPKIAuthCmd = &cobra.Command{ 97 | Use: "auth", 98 | Short: "JPKI認証用PINを変更", 99 | Long: `JPKI認証用PINを変更します 100 | 暗証番号は4桁の数字を入力してください 101 | `, 102 | RunE: pinChangeJPKIAuth, 103 | } 104 | 105 | func pinChangeJPKIAuth(cmd *cobra.Command, args []string) error { 106 | fmt.Println(cmd.Long) 107 | pinName := "JPKI認証用PIN(4桁)" 108 | pin, err := cmd.Flags().GetString("pin") 109 | if pin == "" { 110 | pin, err = inputPin(fmt.Sprintf("現在の%s: ", pinName)) 111 | if err != nil { 112 | return nil 113 | } 114 | } 115 | 116 | newpin, _ := cmd.Flags().GetString("newpin") 117 | if newpin == "" { 118 | newpin, err = inputPin(fmt.Sprintf("新しい%s: ", pinName)) 119 | if err != nil { 120 | return nil 121 | } 122 | } 123 | 124 | err = libmyna.ChangeJPKIAuthPin(pin, newpin) 125 | if err != nil { 126 | return err 127 | } 128 | fmt.Printf("%sを変更しました", pinName) 129 | return nil 130 | } 131 | 132 | var pinChangeJPKISignCmd = &cobra.Command{ 133 | Use: "sign", 134 | Short: "JPKI署名用パスワードを変更", 135 | Long: `JPKI署名用パスワードを変更します 136 | パスワードに利用できる文字種は以下のとおり 137 | 138 | ABCDEFGHIJKLMNOPQRSTUVWXYZ 139 | 0123456789 140 | 141 | 文字数は6文字から16文字まで 142 | アルファベットは大文字のみ使うことができます。 143 | 小文字を入力した場合は、大文字に変換されます。 144 | `, 145 | RunE: pinChangeJPKISign, 146 | } 147 | 148 | func pinChangeJPKISign(cmd *cobra.Command, args []string) error { 149 | fmt.Println(cmd.Long) 150 | pinName := "JPKI署名用パスワード(6-16文字)" 151 | pin, err := cmd.Flags().GetString("pin") 152 | if pin == "" { 153 | pin, err = inputPin(fmt.Sprintf("現在の%s: ", pinName)) 154 | if err != nil { 155 | return nil 156 | } 157 | } 158 | newpin, err := cmd.Flags().GetString("newpin") 159 | if newpin == "" { 160 | newpin, err = inputPin(fmt.Sprintf("新しい%s: ", pinName)) 161 | if err != nil { 162 | return nil 163 | } 164 | } 165 | err = libmyna.ChangeJPKISignPin(pin, newpin) 166 | if err != nil { 167 | return err 168 | } 169 | fmt.Printf("%sを変更しました", pinName) 170 | return nil 171 | } 172 | 173 | func inputPin(prompt string) (string, error) { 174 | input, err := gopass.GetPasswdPrompt(prompt, true, os.Stdin, os.Stderr) 175 | if err != nil { 176 | return "", err 177 | } 178 | pin := string(input) 179 | return pin, err 180 | } 181 | 182 | func init() { 183 | pinCmd.AddCommand(pinStatusCmd) 184 | pinCmd.AddCommand(pinChangeCmd) 185 | 186 | pinChangeCardCmd.Flags().String("pin", "", "現在の暗証番号(4桁)") 187 | pinChangeCardCmd.Flags().String("newpin", "", "新しい暗証番号(4桁)") 188 | pinChangeCmd.AddCommand(pinChangeCardCmd) 189 | 190 | pinChangeJPKIAuthCmd.Flags().String("pin", "", "現在の暗証番号(4桁)") 191 | pinChangeJPKIAuthCmd.Flags().String("newpin", "", "新しい暗証番号(4桁)") 192 | pinChangeCmd.AddCommand(pinChangeJPKIAuthCmd) 193 | 194 | pinChangeJPKISignCmd.Flags().String("pin", "", "現在のパスワード(6-16文字)") 195 | pinChangeJPKISignCmd.Flags().String("newpin", "", "新しいパスワード(6-16文字)") 196 | pinChangeCmd.AddCommand(pinChangeJPKISignCmd) 197 | } 198 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/jpki/myna/libmyna" 10 | ) 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "myna", 14 | Version: libmyna.Version, 15 | Short: "マイナクライアント", 16 | Long: fmt.Sprintf(`Name: 17 | myna %s - マイナクライアント 18 | 19 | マイナンバーカード・ユーティリティ・JPKI署名ツール 20 | 21 | Author: 22 | HAMANO Tsukasa 23 | 24 | `, libmyna.Version), 25 | SilenceUsage: true, 26 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 27 | debug, _ := cmd.Flags().GetBool("debug") 28 | libmyna.OptionDebug = libmyna.Debug(debug) 29 | }, 30 | } 31 | 32 | // Execute adds all child commands to the root command and sets flags appropriately. 33 | // This is called by main.main(). It only needs to happen once to the rootCmd. 34 | 35 | func Execute() { 36 | if err := rootCmd.Execute(); err != nil { 37 | os.Exit(1) 38 | } 39 | } 40 | 41 | func init() { 42 | cobra.EnableCommandSorting = false 43 | rootCmd.PersistentFlags().BoolP("debug", "d", false, "デバッグ出力") 44 | rootCmd.AddCommand(textCmd) 45 | rootCmd.AddCommand(visualCmd) 46 | rootCmd.AddCommand(jpkiCmd) 47 | rootCmd.AddCommand(pinCmd) 48 | rootCmd.AddCommand(testCmd) 49 | } 50 | 51 | func checkCard(cmd *cobra.Command, args []string) error { 52 | return libmyna.CheckCard() 53 | } 54 | -------------------------------------------------------------------------------- /cmd/test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ebfe/scard" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var testCmd = &cobra.Command{ 11 | Use: "test", 12 | Short: "リーダーの動作確認", 13 | Long: "カードリーダーの動作確認を行います", 14 | RunE: test, 15 | } 16 | 17 | func test(cmd *cobra.Command, args []string) error { 18 | fmt.Printf("SCardEstablishContext: ") 19 | ctx, err := scard.EstablishContext() 20 | if err != nil { 21 | fmt.Printf("NG %s", err) 22 | return nil 23 | } 24 | defer ctx.Release() 25 | fmt.Printf("OK\n") 26 | 27 | fmt.Printf("SCardListReaders: ") 28 | readers, err := ctx.ListReaders() 29 | if err != nil { 30 | fmt.Printf("NG %s", err) 31 | return nil 32 | } 33 | fmt.Printf("OK\n") 34 | 35 | for i, reader := range readers { 36 | fmt.Printf(" Reader %d: %s\n", i, reader) 37 | } 38 | 39 | if testStatusChange(ctx, readers[0]); err != nil { 40 | return nil 41 | } 42 | 43 | if err = testCard(ctx, readers[0]); err != nil { 44 | return nil 45 | } 46 | 47 | err = testReleaseContext(ctx) 48 | return err 49 | } 50 | 51 | func testStatusChange(ctx *scard.Context, reader string) error { 52 | fmt.Printf("SCardGetStatusChange: ") 53 | rs := make([]scard.ReaderState, 1) 54 | rs[0].Reader = reader 55 | err := ctx.GetStatusChange(rs, -1) 56 | if err != nil { 57 | fmt.Printf("NG %s", err) 58 | return nil 59 | } 60 | fmt.Printf("OK\n") 61 | printEventState(rs[0].EventState) 62 | return nil 63 | } 64 | 65 | func testCard(ctx *scard.Context, reader string) error { 66 | fmt.Printf("SCardConnect: ") 67 | card, err := ctx.Connect(reader, scard.ShareExclusive, scard.ProtocolAny) 68 | if err != nil { 69 | fmt.Printf("NG %s", err) 70 | return nil 71 | } 72 | fmt.Printf("OK\n") 73 | 74 | fmt.Printf("SCardStatus: ") 75 | cs, err := card.Status() 76 | if err != nil { 77 | fmt.Printf("NG %s", err) 78 | return nil 79 | } 80 | fmt.Printf("OK\n") 81 | 82 | printCardState(cs) 83 | return nil 84 | } 85 | 86 | func testReleaseContext(ctx *scard.Context) error { 87 | fmt.Printf("SCardReleaseContext: ") 88 | err := ctx.Release() 89 | if err != nil { 90 | fmt.Printf("NG %s", err) 91 | return nil 92 | } 93 | fmt.Printf("OK\n") 94 | return nil 95 | } 96 | 97 | var eventStateFlags = [][]interface{}{ 98 | []interface{}{scard.StateIgnore, "STATE_IGNORE"}, 99 | []interface{}{scard.StateChanged, "STATE_CHANGED"}, 100 | []interface{}{scard.StateUnknown, "STATE_UNKNOWN"}, 101 | []interface{}{scard.StateUnavailable, "STATE_UNAVAILABLE"}, 102 | []interface{}{scard.StateEmpty, "STATE_EMPTY"}, 103 | []interface{}{scard.StatePresent, "STATE_PRESENT"}, 104 | []interface{}{scard.StateAtrmatch, "STATE_ATRMATCH"}, 105 | []interface{}{scard.StateExclusive, "STATE_EXCLUSIVE"}, 106 | []interface{}{scard.StateInuse, "STATE_INUSE"}, 107 | []interface{}{scard.StateMute, "STATE_MUTE"}, 108 | []interface{}{scard.StateUnpowered, "STATE_UNPOWERED"}, 109 | } 110 | 111 | func printEventState(eventState scard.StateFlag) { 112 | fmt.Printf(" EventState: 0x%08x\n", eventState) 113 | for _, flag := range eventStateFlags { 114 | if eventState&flag[0].(scard.StateFlag) != 0 { 115 | fmt.Printf(" %s\n", flag[1]) 116 | } 117 | } 118 | } 119 | 120 | func printCardState(cs *scard.CardStatus) { 121 | fmt.Printf(" Reader: %s\n", cs.Reader) 122 | fmt.Printf(" State: 0x%08x\n", cs.State) 123 | fmt.Printf(" ActiveProtocol: %d\n", cs.ActiveProtocol) 124 | fmt.Printf(" Atr: % 02X\n", cs.Atr) 125 | } 126 | 127 | func init() { 128 | } 129 | -------------------------------------------------------------------------------- /cmd/text.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/jpki/myna/libmyna" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var textCmd = &cobra.Command{ 12 | Use: "text", 13 | Short: "券面入力補助AP", 14 | } 15 | 16 | var showMyNumberCmd = &cobra.Command{ 17 | Use: "mynumber", 18 | Short: "券面入力補助APのマイナンバーを表示します", 19 | RunE: showMyNumber, 20 | PreRunE: checkCard, 21 | } 22 | 23 | var showAttributesCmd = &cobra.Command{ 24 | Use: "attr", 25 | Short: "券面入力補助APの4属性を表示します", 26 | RunE: showAttributes, 27 | PreRunE: checkCard, 28 | } 29 | 30 | var showSignatureCmd = &cobra.Command{ 31 | Use: "signature", 32 | Short: "券面入力補助APの署名値を表示します", 33 | RunE: showSignature, 34 | PreRunE: checkCard, 35 | } 36 | 37 | var showCertificateCmd = &cobra.Command{ 38 | Use: "cert", 39 | Short: "券面入力補助APの証明書を表示します", 40 | RunE: showCertificate, 41 | PreRunE: checkCard, 42 | } 43 | 44 | var showBasicInfoCmd = &cobra.Command{ 45 | Use: "info", 46 | Short: "券面入力補助APの基本情報を表示します", 47 | RunE: showBasicInfo, 48 | PreRunE: checkCard, 49 | } 50 | 51 | func showMyNumber(cmd *cobra.Command, args []string) error { 52 | pin, err := cmd.Flags().GetString("pin") 53 | if pin == "" { 54 | pin, err = inputPin("暗証番号(4桁): ") 55 | if err != nil { 56 | return nil 57 | } 58 | } 59 | err = libmyna.Validate4DigitPin(pin) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mynumber, err := libmyna.GetMyNumber(pin) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | fmt.Printf("%s\n", mynumber) 70 | return nil 71 | } 72 | 73 | func showAttributes(cmd *cobra.Command, args []string) error { 74 | pin, err := cmd.Flags().GetString("pin") 75 | if pin == "" { 76 | pin, err = inputPin("暗証番号(4桁): ") 77 | if err != nil { 78 | return nil 79 | } 80 | } 81 | err = libmyna.Validate4DigitPin(pin) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | attr, err := libmyna.GetAttrInfo(pin) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | form, _ := cmd.Flags().GetString("form") 92 | outputTextAttrs(attr, form) 93 | return nil 94 | } 95 | 96 | func outputTextAttrs(attr *libmyna.TextAttrs, form string) { 97 | switch form { 98 | case "json": 99 | obj := map[string]string{ 100 | "header: ": attr.HeaderString(), 101 | "name": attr.Name, 102 | "address": attr.Address, 103 | "birth": attr.Birth, 104 | "sex": attr.SexString(), 105 | } 106 | out, _ := json.MarshalIndent(obj, "", " ") 107 | fmt.Printf("%s", out) 108 | default: 109 | fmt.Printf("謎ヘッダ: %s\n", attr.HeaderString()) 110 | fmt.Printf("氏名: %s\n", attr.Name) 111 | fmt.Printf("住所: %s\n", attr.Address) 112 | fmt.Printf("生年月日: %s\n", attr.Birth) 113 | fmt.Printf("性別: %s\n", attr.SexString()) 114 | } 115 | } 116 | 117 | func showSignature(cmd *cobra.Command, args []string) error { 118 | debug, _ := cmd.Flags().GetBool("debug") 119 | pin, err := cmd.Flags().GetString("pin") 120 | if pin == "" { 121 | pin, err = inputPin("暗証番号(4桁): ") 122 | if err != nil { 123 | return nil 124 | } 125 | } 126 | err = libmyna.Validate4DigitPin(pin) 127 | if err != nil { 128 | return err 129 | } 130 | reader, err := libmyna.NewReader(libmyna.Debug(debug)) 131 | if err != nil { 132 | return err 133 | } 134 | defer reader.Finalize() 135 | err = reader.Connect() 136 | if err != nil { 137 | return err 138 | } 139 | textAP, err := reader.SelectTextAP() 140 | if err != nil { 141 | return err 142 | } 143 | err = textAP.VerifyPin(pin) 144 | if err != nil { 145 | return err 146 | } 147 | signature, err := textAP.ReadSignature() 148 | if err != nil { 149 | return err 150 | } 151 | fmt.Printf("MyNumHash: %X\n", signature.MyNumDigest) 152 | fmt.Printf("AttrsHash: %X\n", signature.AttrsDigest) 153 | fmt.Printf("Signature: %X\n", signature.Signature) 154 | return nil 155 | } 156 | 157 | func showCertificate(cmd *cobra.Command, args []string) error { 158 | debug, _ := cmd.Flags().GetBool("debug") 159 | reader, err := libmyna.NewReader(libmyna.Debug(debug)) 160 | if err != nil { 161 | return err 162 | } 163 | defer reader.Finalize() 164 | err = reader.Connect() 165 | if err != nil { 166 | return err 167 | } 168 | textAP, err := reader.SelectTextAP() 169 | if err != nil { 170 | return err 171 | } 172 | certificate, err := textAP.ReadCertificate() 173 | if err != nil { 174 | return err 175 | } 176 | fmt.Printf("cert: %v\n", certificate) 177 | return nil 178 | } 179 | 180 | func showBasicInfo(cmd *cobra.Command, args []string) error { 181 | debug, _ := cmd.Flags().GetBool("debug") 182 | reader, err := libmyna.NewReader(libmyna.Debug(debug)) 183 | if err != nil { 184 | return err 185 | } 186 | defer reader.Finalize() 187 | err = reader.Connect() 188 | if err != nil { 189 | return err 190 | } 191 | textAP, err := reader.SelectTextAP() 192 | if err != nil { 193 | return err 194 | } 195 | basicInfo, err := textAP.ReadBasicInfo() 196 | if err != nil { 197 | return err 198 | } 199 | 200 | fmt.Printf("APInfo: %X\n", basicInfo.APInfo) 201 | fmt.Printf("KeyID: %X\n", basicInfo.KeyID) 202 | fmt.Printf("Version: %d\n", basicInfo.APInfo[0]) 203 | fmt.Printf("ExtAPDU: %d\n", basicInfo.APInfo[1]) 204 | fmt.Printf("Vendor: %d\n", basicInfo.APInfo[2]) 205 | fmt.Printf("Option: %d\n", basicInfo.APInfo[3]) 206 | return nil 207 | } 208 | 209 | func init() { 210 | textCmd.AddCommand(showMyNumberCmd) 211 | showMyNumberCmd.Flags().StringP("pin", "p", "", "暗証番号(4桁)") 212 | textCmd.AddCommand(showAttributesCmd) 213 | showAttributesCmd.Flags().StringP("pin", "p", "", "暗証番号(4桁)") 214 | showAttributesCmd.Flags().StringP("form", "f", "text", "出力形式(txt,json)") 215 | textCmd.AddCommand(showSignatureCmd) 216 | showSignatureCmd.Flags().StringP("pin", "p", "", "暗証番号(4桁)") 217 | textCmd.AddCommand(showCertificateCmd) 218 | textCmd.AddCommand(showBasicInfoCmd) 219 | } 220 | -------------------------------------------------------------------------------- /cmd/tool.go: -------------------------------------------------------------------------------- 1 | // +build tool 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/jpki/myna/libmyna" 11 | ) 12 | 13 | var toolCmd = &cobra.Command{ 14 | Use: "tool", 15 | Short: "種々様々なツール", 16 | } 17 | 18 | var beepCmd = &cobra.Command{ 19 | Use: "beep on|off", 20 | Short: "ACS Readerのbeep音設定", 21 | Long: `ACS Readerのbeep音を切り替えます 22 | 23 | - on ビープ音を有効化します 24 | - off ビープ音を無効化します 25 | `, 26 | RunE: beep, 27 | } 28 | 29 | func beep(cmd *cobra.Command, args []string) error { 30 | if len(args) != 1 { 31 | cmd.Help() 32 | return nil 33 | } 34 | 35 | if args[0] != "on" && args[0] != "off" { 36 | cmd.Help() 37 | return nil 38 | } 39 | 40 | reader, err := libmyna.NewReader() 41 | if reader == nil { 42 | return err 43 | } 44 | defer reader.Finalize() 45 | debug, _ := cmd.Flags().GetBool("debug") 46 | reader.SetDebug(debug) 47 | err = reader.Connect() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | var apdu *libmyna.APDU 53 | if args[0] != "on" { 54 | apdu, _ = libmyna.NewAPDU("FF 00 52 FF 00") 55 | } else if args[0] != "off" { 56 | apdu, _ = libmyna.NewAPDU("FF 00 52 00 00") 57 | } 58 | reader.Trans(apdu) 59 | return nil 60 | } 61 | 62 | var findAPCmd = &cobra.Command{ 63 | Use: "find_ap", 64 | Short: "APを探索", 65 | RunE: findAP, 66 | } 67 | 68 | func findAP(cmd *cobra.Command, args []string) error { 69 | var prefix = []byte{} 70 | 71 | reader, err := libmyna.NewReader() 72 | if reader == nil { 73 | return err 74 | } 75 | defer reader.Finalize() 76 | debug, _ := cmd.Flags().GetBool("debug") 77 | reader.SetDebug(debug) 78 | err = reader.Connect() 79 | if err != nil { 80 | return err 81 | } 82 | ret := findDF(reader, prefix) 83 | for _, ap := range ret { 84 | fmt.Printf("found ap: % X\n", ap) 85 | } 86 | return nil 87 | } 88 | 89 | func findDF(reader *libmyna.Reader, prefix []byte) [][]byte { 90 | var tmp [][]byte 91 | i := len(prefix) 92 | buf := append(prefix, 0) 93 | for n := 0; n < 255; n++ { 94 | buf[i] = byte(n) 95 | err := reader.SelectDF(libmyna.ToHexString(buf)) 96 | if err == nil { 97 | fmt.Printf("FOUND: % X\n", buf) 98 | ret := findDF(reader, buf) 99 | if len(ret) == 0 { 100 | dup := make([]byte, len(buf)) 101 | copy(dup, buf) 102 | tmp = append(tmp, dup) 103 | } else { 104 | tmp = append(tmp, ret...) 105 | } 106 | } 107 | } 108 | return tmp 109 | } 110 | 111 | /* 112 | func findEF(c *cli.Context, df string) { 113 | reader, err := libmyna.Ready(c) 114 | if err != nil { 115 | return 116 | } 117 | defer reader.Finalize() 118 | reader.SelectDF(df) 119 | for i := 0; i < 255; i++ { 120 | for j := 0; j < 255; j++ { 121 | ef := fmt.Sprintf("%02X %02X", i, j) 122 | sw1, _ := reader.SelectEF(ef) 123 | if sw1 == 0x90 { 124 | fmt.Printf("FOUND %s\n", ef) 125 | sw1, sw2, data := reader.Tx("00 20 00 80") 126 | fmt.Printf("-> %x, %x, % X\n", sw1, sw2, data) 127 | } 128 | } 129 | } 130 | } 131 | */ 132 | 133 | func init() { 134 | toolCmd.AddCommand(beepCmd) 135 | 136 | toolCmd.AddCommand(findAPCmd) 137 | rootCmd.AddCommand(toolCmd) 138 | } 139 | -------------------------------------------------------------------------------- /cmd/visual.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/jpki/myna/libmyna" 9 | ) 10 | 11 | var visualCmd = &cobra.Command{ 12 | Use: "visual", 13 | Short: "券面確認AP", 14 | } 15 | 16 | var cardFrontPhotoCmd = &cobra.Command{ 17 | Use: "photo -o [output.jpg|-]", 18 | Short: "券面確認APの顔写真を取得", 19 | RunE: showCardFrontPhoto, 20 | PreRunE: checkCard, 21 | } 22 | 23 | func showCardFrontPhoto(cmd *cobra.Command, args []string) error { 24 | output, err := cmd.Flags().GetString("output") 25 | if output == "" { 26 | cmd.Usage() 27 | return nil 28 | } 29 | pin, err := cmd.Flags().GetString("pin") 30 | if pin == "" { 31 | pin, err = inputPin("暗証番号(4桁): ") 32 | if err != nil { 33 | return nil 34 | } 35 | } 36 | err = libmyna.Validate4DigitPin(pin) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | mynumber, err := libmyna.GetMyNumber(pin) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | info, err := libmyna.GetVisualInfo(mynumber) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | var file *os.File 52 | if output == "-" { 53 | file = os.Stdout 54 | } else { 55 | file, err = os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 56 | defer file.Close() 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | 62 | file.Write(info.Photo) 63 | return nil 64 | } 65 | 66 | func init() { 67 | visualCmd.AddCommand(cardFrontPhotoCmd) 68 | cardFrontPhotoCmd.Flags().StringP("pin", "p", "", "暗証番号(4桁)") 69 | cardFrontPhotoCmd.Flags().StringP("output", "o", "", "出力ファイル(JPEG2000)") 70 | } 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jpki/myna 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/ebfe/scard v0.0.0-20230420082256-7db3f9b7c8a7 7 | github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef 8 | github.com/ianmcmahon/encoding_ssh v0.0.0-20190330023458-31ed23ea1a8a 9 | github.com/smallstep/pkcs7 v0.2.1 10 | github.com/spf13/cobra v1.7.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/ebfe/scard v0.0.0-20230420082256-7db3f9b7c8a7 h1:HYAhfGa9dEemCZgGZWL5AvVsctBCsHxl2CI0HUXzHQE= 3 | github.com/ebfe/scard v0.0.0-20230420082256-7db3f9b7c8a7/go.mod h1:BkYEeWL6FbT4Ek+TcOBnPzEKnL7kOq2g19tTQXkorHY= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= 6 | github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= 7 | github.com/ianmcmahon/encoding_ssh v0.0.0-20190330023458-31ed23ea1a8a h1:u3BET8NCo5BLmRWrAQNvEYybEwVBODWgvCJLbbmncYU= 8 | github.com/ianmcmahon/encoding_ssh v0.0.0-20190330023458-31ed23ea1a8a/go.mod h1:lesZTgpLs8d6huznNlcKXBK5esmDqJBQnAYMdj5okM0= 9 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 10 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 11 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 12 | github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA= 13 | github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0= 14 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 15 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 16 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 17 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 18 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 21 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 22 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 23 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 24 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 25 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 26 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 27 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 28 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 29 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 30 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 31 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 32 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 33 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 34 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 35 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 36 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 37 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 38 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 43 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 44 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 45 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 46 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 47 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 55 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 56 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 57 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 58 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 59 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 60 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 61 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 62 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 63 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 64 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 65 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 66 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 67 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 70 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 71 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 72 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 73 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 74 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 75 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 76 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 77 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 78 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 79 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 80 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 81 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 82 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 83 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | -------------------------------------------------------------------------------- /libmyna/apdu.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type APDU struct { 8 | cmd []uint8 9 | } 10 | 11 | func NewAPDU(s string) (*APDU, error) { 12 | cmd := ToBytes(s) 13 | if len(cmd) < 4 { 14 | return nil, fmt.Errorf("invalid apdu %s", s) 15 | } 16 | apdu := APDU{cmd} 17 | return &apdu, nil 18 | } 19 | 20 | func NewAPDUCase1(cla uint8, ins uint8, p1 uint8, p2 uint8) *APDU { 21 | apdu := APDU{[]uint8{cla, ins, p1, p2}} 22 | return &apdu 23 | } 24 | 25 | func NewAPDUCase2(cla uint8, ins uint8, p1 uint8, p2 uint8, le uint8) *APDU { 26 | apdu := APDU{[]uint8{cla, ins, p1, p2, le}} 27 | return &apdu 28 | } 29 | 30 | func NewAPDUCase3(cla uint8, ins uint8, p1 uint8, p2 uint8, data []uint8) *APDU { 31 | cmd := append([]uint8{cla, ins, p1, p2, uint8(len(data))}, data...) 32 | apdu := APDU{cmd} 33 | return &apdu 34 | } 35 | 36 | func NewAPDUCase4(cla uint8, ins uint8, p1 uint8, p2 uint8, data []uint8, le uint8) *APDU { 37 | cmd := append([]uint8{cla, ins, p1, p2, uint8(len(data))}, data...) 38 | cmd = append(cmd, le) 39 | apdu := APDU{cmd} 40 | return &apdu 41 | } 42 | 43 | func (self *APDU) ToString() string { 44 | return fmt.Sprintf("% X", self.cmd) 45 | } 46 | -------------------------------------------------------------------------------- /libmyna/apdu_test.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | _ "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | var invalidAPDU = []string{ 10 | "", 11 | "FF", 12 | "FF FF", 13 | "FF FF FF", 14 | } 15 | 16 | func TestNewAPDUInvalid(t *testing.T) { 17 | for _, s := range invalidAPDU { 18 | _, err := NewAPDU(s) 19 | if err == nil { 20 | t.Errorf("NewAPDU should fail: %s", s) 21 | } 22 | } 23 | } 24 | 25 | var validAPDU = []string{ 26 | "00 00 00 00", 27 | "FF FF FF FF", 28 | } 29 | 30 | func TestNewAPDU(t *testing.T) { 31 | for _, s := range validAPDU { 32 | apdu, err := NewAPDU(s) 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | if s != apdu.ToString() { 37 | t.Errorf("%s != %s", s, apdu.ToString()) 38 | } 39 | } 40 | } 41 | 42 | var validAPDUCase1 = [][]byte{ 43 | {0x00, 0x00, 0x00, 0x00}, 44 | } 45 | 46 | func TestNewAPDUCase1(t *testing.T) { 47 | for _, cmd := range validAPDUCase1 { 48 | apdu := NewAPDUCase1(cmd[0], cmd[1], cmd[2], cmd[3]) 49 | if !reflect.DeepEqual(cmd, apdu.cmd) { 50 | t.Errorf("% X != % X", cmd, apdu.cmd) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /libmyna/api.go: -------------------------------------------------------------------------------- 1 | // High-Level API 2 | 3 | package libmyna 4 | 5 | import ( 6 | "crypto" 7 | "crypto/x509" 8 | "encoding/asn1" 9 | "encoding/pem" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "strings" 16 | 17 | "github.com/smallstep/pkcs7" 18 | ) 19 | 20 | func CheckCard() error { 21 | reader, err := NewReader(OptionDebug) 22 | if err != nil { 23 | return err 24 | } 25 | defer reader.Finalize() 26 | err = reader.Connect() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | jpkiAP, err := reader.SelectJPKIAP() 32 | if err != nil { 33 | return errors.New("個人番号カードではありません") 34 | } 35 | 36 | err = reader.SelectEF("00 06") 37 | if err != nil { 38 | return errors.New("トークン情報を取得できません") 39 | } 40 | 41 | token, err := jpkiAP.ReadToken() 42 | if token == "JPKIAPICCTOKEN2" { 43 | return nil 44 | } else if token == "JPKIAPGPSETOKEN" { 45 | return nil 46 | } else if token == "JPKIAPICCTOKEN" { 47 | return errors.New("これは住基カードですね?") 48 | } else { 49 | return fmt.Errorf("不明なトークン情報: %s", token) 50 | } 51 | } 52 | 53 | // 券面入力補助APのマイナンバーを取得します 54 | func GetMyNumber(pin string) (string, error) { 55 | reader, err := NewReader(OptionDebug) 56 | if err != nil { 57 | return "", err 58 | } 59 | defer reader.Finalize() 60 | err = reader.Connect() 61 | if err != nil { 62 | return "", err 63 | } 64 | textAP, err := reader.SelectTextAP() 65 | if err != nil { 66 | return "", err 67 | } 68 | err = textAP.VerifyPin(pin) 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | mynumber, err := textAP.ReadMyNumber() 74 | if err != nil { 75 | return "", err 76 | } 77 | return mynumber, nil 78 | } 79 | 80 | // 券面入力補助APの4属性情報を取得します 81 | func GetAttrInfo(pin string) (*TextAttrs, error) { 82 | reader, err := NewReader(OptionDebug) 83 | if err != nil { 84 | return nil, err 85 | } 86 | defer reader.Finalize() 87 | err = reader.Connect() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | textAP, err := reader.SelectTextAP() 93 | if err != nil { 94 | return nil, err 95 | } 96 | err = textAP.VerifyPin(pin) 97 | if err != nil { 98 | return nil, err 99 | } 100 | attr, err := textAP.ReadAttributes() 101 | return attr, err 102 | } 103 | 104 | type CardInfo struct { 105 | } 106 | 107 | // 券面AP表面 108 | func GetVisualInfo(mynumber string) (*VisualInfo, error) { 109 | reader, err := NewReader(OptionDebug) 110 | if err != nil { 111 | return nil, err 112 | } 113 | defer reader.Finalize() 114 | err = reader.Connect() 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | visualAP, err := reader.SelectVisualAP() 120 | if err != nil { 121 | return nil, err 122 | } 123 | err = visualAP.VerifyPinA(mynumber) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | front, err := visualAP.GetVisualInfo() 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | return front, nil 134 | } 135 | 136 | func ChangeCardInputHelperPin(pin string, newpin string) error { 137 | return Change4DigitPin(pin, newpin, "CARD_INPUT_HELPER") 138 | } 139 | 140 | func ChangeJPKIAuthPin(pin string, newpin string) error { 141 | return Change4DigitPin(pin, newpin, "JPKI_AUTH") 142 | } 143 | 144 | func Change4DigitPin(pin string, newpin string, pintype string) error { 145 | 146 | err := Validate4DigitPin(pin) 147 | if err != nil { 148 | return err 149 | } 150 | 151 | err = Validate4DigitPin(newpin) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | reader, err := NewReader(OptionDebug) 157 | if err != nil { 158 | return err 159 | } 160 | defer reader.Finalize() 161 | err = reader.Connect() 162 | if err != nil { 163 | return err 164 | } 165 | 166 | switch pintype { 167 | case "CARD_INPUT_HELPER": 168 | reader.SelectTextAP() 169 | reader.SelectEF("0011") // 券面入力補助PIN 170 | case "JPKI_AUTH": 171 | reader.SelectJPKIAP() 172 | reader.SelectEF("0018") //JPKI認証用PIN 173 | } 174 | 175 | err = reader.Verify(pin) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | res := reader.ChangePin(newpin) 181 | if !res { 182 | return errors.New("PINの変更に失敗しました") 183 | } 184 | return nil 185 | } 186 | 187 | func ChangeJPKISignPin(pin string, newpin string) error { 188 | pin = strings.ToUpper(pin) 189 | err := ValidateJPKISignPassword(pin) 190 | if err != nil { 191 | return err 192 | } 193 | 194 | newpin = strings.ToUpper(newpin) 195 | err = ValidateJPKISignPassword(newpin) 196 | if err != nil { 197 | return err 198 | } 199 | 200 | reader, err := NewReader(OptionDebug) 201 | if err != nil { 202 | return err 203 | } 204 | defer reader.Finalize() 205 | err = reader.Connect() 206 | if err != nil { 207 | return err 208 | } 209 | 210 | reader.SelectJPKIAP() 211 | reader.SelectEF("00 1B") // IEF for SIGN 212 | 213 | err = reader.Verify(pin) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | res := reader.ChangePin(newpin) 219 | if !res { 220 | return errors.New("PINの変更に失敗しました") 221 | } 222 | return nil 223 | } 224 | 225 | func GetJPKICert(efid string, pin string, pass string) (*x509.Certificate, error) { 226 | reader, err := NewReader(OptionDebug) 227 | if err != nil { 228 | return nil, err 229 | } 230 | defer reader.Finalize() 231 | err = reader.Connect() 232 | if err != nil { 233 | return nil, err 234 | } 235 | 236 | jpkiAP, err := reader.SelectJPKIAP() 237 | if err != nil { 238 | return nil, err 239 | } 240 | 241 | if pin != "" { 242 | err = jpkiAP.VerifyAuthPin(pin) 243 | if err != nil { 244 | return nil, err 245 | } 246 | } 247 | if pass != "" { 248 | err = jpkiAP.VerifySignPin(pass) 249 | if err != nil { 250 | return nil, err 251 | } 252 | } 253 | cert, err := jpkiAP.ReadCertificate(efid) 254 | return cert, nil 255 | } 256 | 257 | func GetJPKISignCert(pass string) (*x509.Certificate, error) { 258 | return GetJPKICert("00 01", "", pass) 259 | } 260 | 261 | func GetJPKISignCACert() (*x509.Certificate, error) { 262 | return GetJPKICert("00 02", "", "") 263 | } 264 | 265 | /* 266 | func CmsSignJPKISignOld(pin string, in string, out string) error { 267 | rawContent, err := ioutil.ReadFile(in) 268 | if err != nil { 269 | return err 270 | } 271 | 272 | toBeSigned, err := pkcs7.NewSignedData(rawContent) 273 | if err != nil { 274 | return err 275 | } 276 | 277 | // 署名用証明書の取得 278 | cert, err := GetJPKISignCert(pin) 279 | if err != nil { 280 | return err 281 | } 282 | attrs, hashed, err := toBeSigned.HashAttributes(crypto.SHA1, pkcs7.SignerInfoConfig{}) 283 | if err != nil { 284 | return err 285 | } 286 | 287 | ias, err := pkcs7.Cert2issuerAndSerial(cert) 288 | if err != nil { 289 | return err 290 | } 291 | 292 | reader, err := NewReader(OptionDebug) 293 | if err != nil { 294 | return err 295 | } 296 | defer reader.Finalize() 297 | err = reader.Connect() 298 | if err != nil { 299 | return err 300 | } 301 | 302 | reader.SelectJPKIAP() 303 | reader.SelectEF("00 1B") // IEF for SIGN 304 | err = reader.Verify(pin) 305 | if err != nil { 306 | return err 307 | } 308 | 309 | reader.SelectEF("00 1A") // Select SIGN EF 310 | digestInfo := makeDigestInfo(hashed) 311 | 312 | signature, err := reader.Signature(digestInfo) 313 | if err != nil { 314 | return err 315 | } 316 | 317 | oidDigestAlgorithmSHA1 := asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} 318 | oidEncryptionAlgorithmRSA := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} 319 | signerInfo := pkcs7.SignerInfo{ 320 | AuthenticatedAttributes: attrs, 321 | DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1}, 322 | DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidEncryptionAlgorithmRSA}, 323 | IssuerAndSerialNumber: ias, 324 | EncryptedDigest: signature, 325 | Version: 1, 326 | } 327 | toBeSigned.AddSignerInfo(cert, signerInfo) 328 | signed, err := toBeSigned.Finish() 329 | if err != nil { 330 | return err 331 | } 332 | 333 | err = ioutil.WriteFile(out, signed, 0664) 334 | if err != nil { 335 | return err 336 | } 337 | return nil 338 | } 339 | */ 340 | 341 | type JPKISignSigner struct { 342 | pin string 343 | pubkey crypto.PublicKey 344 | } 345 | 346 | func (self JPKISignSigner) Public() crypto.PublicKey { 347 | return self.pubkey 348 | } 349 | 350 | func (self JPKISignSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 351 | digestInfo := makeDigestInfo(opts.HashFunc(), digest) 352 | reader, err := NewReader(OptionDebug) 353 | if err != nil { 354 | return nil, err 355 | } 356 | defer reader.Finalize() 357 | err = reader.Connect() 358 | if err != nil { 359 | return nil, err 360 | } 361 | reader.SelectJPKIAP() 362 | reader.SelectEF("00 1B") // IEF for SIGN 363 | err = reader.Verify(self.pin) 364 | if err != nil { 365 | return nil, err 366 | } 367 | 368 | reader.SelectEF("00 1A") // Select SIGN EF 369 | signature, err = reader.Signature(digestInfo) 370 | if err != nil { 371 | return nil, err 372 | } 373 | return signature, nil 374 | } 375 | 376 | func GetDigestOID(md string) (asn1.ObjectIdentifier, error) { 377 | switch strings.ToUpper(md) { 378 | case "SHA1": 379 | return pkcs7.OIDDigestAlgorithmSHA1, nil 380 | case "SHA256": 381 | return pkcs7.OIDDigestAlgorithmSHA256, nil 382 | case "SHA384": 383 | return pkcs7.OIDDigestAlgorithmSHA384, nil 384 | case "SHA512": 385 | return pkcs7.OIDDigestAlgorithmSHA512, nil 386 | default: 387 | return nil, fmt.Errorf("サポートされていないハッシュアルゴリズムです: %s", md) 388 | } 389 | } 390 | 391 | type CmsSignOpts struct { 392 | Hash string 393 | Form string 394 | Detached bool 395 | } 396 | 397 | type CmsVerifyOpts struct { 398 | Form string 399 | Detached bool 400 | Content string 401 | } 402 | 403 | func CmsSignJPKISign(pin string, in string, out string, opts CmsSignOpts) error { 404 | digest, err := GetDigestOID(opts.Hash) 405 | if err != nil { 406 | return err 407 | } 408 | 409 | content, err := ioutil.ReadFile(in) 410 | if err != nil { 411 | return err 412 | } 413 | 414 | // 署名用証明書の取得 415 | cert, err := GetJPKISignCert(pin) 416 | if err != nil { 417 | return err 418 | } 419 | 420 | privkey := JPKISignSigner{pin, cert.PublicKey} 421 | 422 | toBeSigned, err := pkcs7.NewSignedData(content) 423 | toBeSigned.SetDigestAlgorithm(digest) 424 | err = toBeSigned.AddSigner(cert, privkey, pkcs7.SignerInfoConfig{}) 425 | if err != nil { 426 | return err 427 | } 428 | 429 | if opts.Detached { 430 | toBeSigned.Detach() 431 | } 432 | 433 | signed, err := toBeSigned.Finish() 434 | if err != nil { 435 | return err 436 | } 437 | 438 | if err = writeCms(out, signed, opts.Form); err != nil { 439 | return err 440 | } 441 | 442 | return nil 443 | } 444 | 445 | func writeCms(out string, signed []byte, form string) error { 446 | var file *os.File 447 | var err error 448 | if out == "" { 449 | file = os.Stdout 450 | } else { 451 | file, err = os.OpenFile(out, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 452 | defer file.Close() 453 | if err != nil { 454 | return err 455 | } 456 | } 457 | 458 | switch strings.ToUpper(form) { 459 | case "PEM": 460 | err = pem.Encode(file, &pem.Block{Type: "PKCS7", Bytes: signed}) 461 | if err != nil { 462 | return err 463 | } 464 | 465 | case "DER": 466 | _, err = file.Write(signed) 467 | if err != nil { 468 | return err 469 | } 470 | } 471 | return nil 472 | } 473 | 474 | func readCMSFile(in string, form string) (*pkcs7.PKCS7, error) { 475 | data, err := ioutil.ReadFile(in) 476 | if err != nil { 477 | return nil, err 478 | } 479 | 480 | var signedDer []byte 481 | switch strings.ToUpper(form) { 482 | case "PEM": 483 | block, _ := pem.Decode(data) 484 | signedDer = block.Bytes 485 | case "DER": 486 | signedDer = data 487 | default: 488 | return nil, fmt.Errorf("サポートされていない形式です: %s", form) 489 | } 490 | 491 | p7, err := pkcs7.Parse(signedDer) 492 | if err != nil { 493 | return nil, err 494 | } 495 | return p7, nil 496 | } 497 | 498 | func CmsVerifyJPKISign(in string, opts CmsVerifyOpts) error { 499 | cacert, err := GetJPKISignCACert() 500 | if err != nil { 501 | return err 502 | } 503 | p7, err := readCMSFile(in, opts.Form) 504 | if err != nil { 505 | return err 506 | } 507 | 508 | if opts.Detached { 509 | content, err := ioutil.ReadFile(opts.Content) 510 | if err != nil { 511 | return err 512 | } 513 | p7.Content = content 514 | } 515 | 516 | certPool := x509.NewCertPool() 517 | certPool.AddCert(cacert) 518 | err = p7.VerifyWithChain(certPool) 519 | if err != nil { 520 | return err 521 | } 522 | 523 | return nil 524 | } 525 | 526 | func GetPinStatus() (map[string]int, error) { 527 | reader, err := NewReader(OptionDebug) 528 | if err != nil { 529 | return nil, err 530 | } 531 | defer reader.Finalize() 532 | err = reader.Connect() 533 | if err != nil { 534 | return nil, err 535 | } 536 | 537 | status := map[string]int{} 538 | 539 | jpkiAP, err := reader.SelectJPKIAP() 540 | if err != nil { 541 | return nil, err 542 | } 543 | status["jpki_auth"], err = jpkiAP.LookupAuthPin() 544 | status["jpki_sign"], err = jpkiAP.LookupSignPin() 545 | 546 | token, err := jpkiAP.ReadToken() 547 | if err != nil { 548 | return nil, err 549 | } 550 | 551 | if token == "JPKIAPGPSETOKEN" { 552 | // スマホJPKI 553 | return status, nil 554 | } 555 | 556 | visualAP, err := reader.SelectVisualAP() 557 | if err != nil { 558 | return nil, err 559 | } 560 | status["visual_pin_a"], err = visualAP.LookupPinA() 561 | status["visual_pin_b"], err = visualAP.LookupPinB() 562 | 563 | textAP, err := reader.SelectTextAP() 564 | if err != nil { 565 | return nil, err 566 | } 567 | status["text_pin"], err = textAP.LookupPin() 568 | status["text_pin_a"], err = textAP.LookupPinA() 569 | status["text_pin_b"], err = textAP.LookupPinB() 570 | 571 | return status, nil 572 | } 573 | -------------------------------------------------------------------------------- /libmyna/common.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "crypto" 5 | "crypto/x509/pkix" 6 | "encoding/asn1" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | /* 12 | func Ready() (*Reader, error) { 13 | reader, err := NewReader() 14 | if err != nil { 15 | return nil, err 16 | } 17 | reader.SetDebug(Debug) 18 | err = reader.Connect() 19 | if err != nil { 20 | return nil, err 21 | } 22 | return reader, nil 23 | } 24 | */ 25 | 26 | var digestInfoPrefix = map[crypto.Hash][]byte{ 27 | crypto.SHA1: { 28 | 0x30, 0x21, // SEQUENCE { 29 | 0x30, 0x09, // SEQUENCE { 30 | 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, // SHA1 OID 31 | 0x05, 0x00, // NULL } 32 | 0x04, 0x14, // OCTET STRING } 33 | }, 34 | crypto.SHA256: { 35 | 0x30, 0x31, // SEQUENCE { 36 | 0x30, 0x0d, // SEQUENCE { 37 | 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, // SHA256 OID 38 | 0x05, 0x00, // NULL } 39 | 0x04, 0x20, // OCTET STRING } 40 | }, 41 | crypto.SHA384: { 42 | 0x30, 0x41, // SEQUENCE { 43 | 0x30, 0x0d, // SEQUENCE { 44 | 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, // SHA384 OID 45 | 0x05, 0x00, // NULL } 46 | 0x04, 0x30, // OCTET STRING } 47 | }, 48 | crypto.SHA512: { 49 | 0x30, 0x51, // SEQUENCE { 50 | 0x30, 0x0d, // SEQUENCE { 51 | 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, // SHA512 OID 52 | 0x05, 0x00, // NULL } 53 | 0x04, 0x40, // OCTET STRING } 54 | }, 55 | } 56 | 57 | func makeDigestInfo(hashid crypto.Hash, digest []byte) []byte { 58 | prefix := digestInfoPrefix[hashid] 59 | return append(prefix, digest...) 60 | } 61 | 62 | type ContentInfo struct { 63 | ContentType asn1.ObjectIdentifier 64 | Content asn1.RawValue `asn1:"explicit,optional,tag:0"` 65 | } 66 | 67 | var oid2str = map[string]string{ 68 | "2.5.4.3": "CN", 69 | "2.5.4.6": "C", 70 | "2.5.4.7": "L", 71 | "2.5.4.10": "O", 72 | "2.5.4.11": "OU", 73 | } 74 | 75 | func Name2String(name pkix.Name) string { 76 | var dn []string 77 | for _, rdns := range name.ToRDNSequence() { 78 | for _, rdn := range rdns { 79 | value := rdn.Value.(string) 80 | if key, ok := oid2str[rdn.Type.String()]; ok { 81 | dn = append(dn, fmt.Sprintf("%s=%s", key, value)) 82 | } else { 83 | dn = append(dn, fmt.Sprintf("%s=%s", rdn.Type.String(), value)) 84 | } 85 | } 86 | } 87 | return strings.Join(dn, "/") 88 | } 89 | -------------------------------------------------------------------------------- /libmyna/errors.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type APDUError struct { 8 | sw1 uint8 9 | sw2 uint8 10 | } 11 | 12 | func NewAPDUError(sw1 uint8, sw2 uint8) error { 13 | return &APDUError{sw1, sw2} 14 | } 15 | 16 | func (self *APDUError) Error() string { 17 | return fmt.Sprintf("APDU Error SW1=%02X SW2=%02X", self.sw1, self.sw2) 18 | } 19 | -------------------------------------------------------------------------------- /libmyna/jpki_ap.go: -------------------------------------------------------------------------------- 1 | // JPKIAP Operation API 2 | 3 | package libmyna 4 | 5 | import ( 6 | "bytes" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "errors" 10 | "fmt" 11 | "github.com/jpki/myna/asn1" 12 | ) 13 | 14 | type JPKIAP struct { 15 | reader *Reader 16 | } 17 | 18 | func (self *JPKIAP) ReadToken() (string, error) { 19 | err := self.reader.SelectEF("00 06") // トークン情報EF 20 | if err != nil { 21 | return "", err 22 | } 23 | 24 | data := self.reader.ReadBinary(0x20) 25 | token := string(bytes.TrimRight(data, " ")) 26 | return token, nil 27 | } 28 | 29 | func (self *JPKIAP) LookupAuthPin() (int, error) { 30 | err := self.reader.SelectEF("00 18") // JPKI認証用PIN 31 | if err != nil { 32 | return 0, err 33 | } 34 | count := self.reader.LookupPin() 35 | return count, nil 36 | } 37 | 38 | func (self *JPKIAP) VerifyAuthPin(pin string) error { 39 | err := self.reader.SelectEF("00 18") // JPKI認証用PIN 40 | if err != nil { 41 | return err 42 | } 43 | err = self.reader.Verify(pin) 44 | if err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | func (self *JPKIAP) LookupSignPin() (int, error) { 51 | err := self.reader.SelectEF("00 1B") // JPKI署名用PIN 52 | if err != nil { 53 | return 0, err 54 | } 55 | count := self.reader.LookupPin() 56 | return count, nil 57 | } 58 | 59 | func (self *JPKIAP) VerifySignPin(pin string) error { 60 | err := self.reader.SelectEF("00 1B") // JPKI署名用PIN 61 | if err != nil { 62 | return err 63 | } 64 | err = self.reader.Verify(pin) 65 | if err != nil { 66 | return err 67 | } 68 | return nil 69 | } 70 | 71 | func (self *JPKIAP) ReadCertificate(efid string) (*x509.Certificate, error) { 72 | err := self.reader.SelectEF(efid) 73 | data := self.reader.ReadBinary(7) 74 | if len(data) != 7 { 75 | return nil, errors.New("ReadBinary: invalid length") 76 | } 77 | 78 | parser := ASN1PartialParser{} 79 | err = parser.Parse(data) 80 | if err != nil { 81 | return nil, err 82 | } 83 | data = self.reader.ReadBinary(parser.GetSize()) 84 | cert, err := x509.ParseCertificate(data) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return cert, nil 89 | } 90 | 91 | func (self *JPKIAP) ReadSignCert() (*x509.Certificate, error) { 92 | return self.ReadCertificate("00 01") 93 | } 94 | 95 | func (self *JPKIAP) ReadSignCACert() (*x509.Certificate, error) { 96 | return self.ReadCertificate("00 02") 97 | } 98 | 99 | func (self *JPKIAP) ReadAuthCert() (*x509.Certificate, error) { 100 | return self.ReadCertificate("00 0A") 101 | } 102 | 103 | func (self *JPKIAP) ReadAuthCACert() (*x509.Certificate, error) { 104 | return self.ReadCertificate("00 0B") 105 | } 106 | 107 | type JPKICertificate struct { 108 | *x509.Certificate 109 | } 110 | 111 | var oidExtensionSubjectAltName = []int{2, 5, 29, 17} 112 | var oidJPKICertificateName = []int{1, 2, 392, 200149, 8, 5, 5, 1} 113 | var oidJPKICertificateNameAlt = []int{1, 2, 392, 200149, 8, 5, 5, 2} 114 | var oidJPKICertificateSex = []int{1, 2, 392, 200149, 8, 5, 5, 3} 115 | var oidJPKICertificateBirth = []int{1, 2, 392, 200149, 8, 5, 5, 4} 116 | var oidJPKICertificateAddr = []int{1, 2, 392, 200149, 8, 5, 5, 5} 117 | var oidJPKICertificateAddrAlt = []int{1, 2, 392, 200149, 8, 5, 5, 6} 118 | 119 | func (self *JPKICertificate) GetSubjectAltNames() *pkix.Extension { 120 | for _, ext := range self.Extensions { 121 | if ext.Id.Equal(oidExtensionSubjectAltName) { 122 | return &ext 123 | } 124 | } 125 | return nil 126 | } 127 | 128 | type JPKICertificateAttr struct { 129 | Oid asn1.ObjectIdentifier 130 | Values JPKICertificateAttrValues `asn1:"tag:0"` 131 | } 132 | 133 | type JPKICertificateAttrValues struct { 134 | Value string 135 | } 136 | 137 | type JPKICertificateAttrs struct { 138 | Name string 139 | NameAlt string 140 | Sex string 141 | Birth string 142 | Addr string 143 | AddrAlt string 144 | } 145 | 146 | func (self *JPKICertificate) GetAttributes() (*JPKICertificateAttrs, error) { 147 | attrs := JPKICertificateAttrs{} 148 | san := self.GetSubjectAltNames() 149 | if san == nil { 150 | return nil, nil 151 | } 152 | var seq asn1.RawValue 153 | _, err := asn1.Unmarshal(san.Value, &seq) 154 | if err != nil { 155 | return nil, err 156 | } 157 | 158 | rest := seq.Bytes 159 | for len(rest) > 0 { 160 | var v JPKICertificateAttr 161 | rest, err = asn1.UnmarshalWithParams(rest, &v, "tag:0") 162 | if err != nil { 163 | return nil, err 164 | } 165 | if v.Oid == nil { 166 | return nil, nil 167 | } 168 | if v.Oid.Equal(oidJPKICertificateName) { 169 | attrs.Name = v.Values.Value 170 | } else if v.Oid.Equal(oidJPKICertificateNameAlt) { 171 | attrs.NameAlt = v.Values.Value 172 | } else if v.Oid.Equal(oidJPKICertificateSex) { 173 | attrs.Sex = v.Values.Value 174 | } else if v.Oid.Equal(oidJPKICertificateBirth) { 175 | attrs.Birth = v.Values.Value 176 | } else if v.Oid.Equal(oidJPKICertificateAddr) { 177 | attrs.Addr = v.Values.Value 178 | } else if v.Oid.Equal(oidJPKICertificateAddrAlt) { 179 | attrs.AddrAlt = v.Values.Value 180 | } 181 | } 182 | return &attrs, nil 183 | } 184 | 185 | func (self *JPKICertificate) ToString() string { 186 | var ret string 187 | ret += fmt.Sprintf("SerialNumber: %s\n", self.SerialNumber) 188 | ret += fmt.Sprintf("Subject: %s\n", Name2String(self.Subject)) 189 | ret += fmt.Sprintf("Issuer: %s\n", Name2String(self.Issuer)) 190 | ret += fmt.Sprintf("NotBefore: %s\n", self.NotBefore) 191 | ret += fmt.Sprintf("NotAfter: %s\n", self.NotAfter) 192 | ret += fmt.Sprintf("KeyUsage: %v\n", self.KeyUsage) 193 | attrs, _ := self.GetAttributes() 194 | if attrs != nil { 195 | ret += fmt.Sprintf("Name: %s\n", attrs.Name) 196 | ret += fmt.Sprintf("Sex: %s\n", attrs.Sex) 197 | ret += fmt.Sprintf("Birth: %s\n", attrs.Birth) 198 | ret += fmt.Sprintf("Addr: %s\n", attrs.Addr) 199 | } 200 | return ret 201 | } 202 | -------------------------------------------------------------------------------- /libmyna/reader.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/ebfe/scard" 10 | ) 11 | 12 | type Reader struct { 13 | ctx *scard.Context 14 | name string 15 | card *scard.Card 16 | debug bool 17 | } 18 | 19 | func Debug(d bool) func(*Reader) { 20 | return func(r *Reader) { 21 | r.debug = d 22 | } 23 | } 24 | 25 | var OptionDebug = Debug(false) 26 | 27 | func NewReader(opts ...func(*Reader)) (*Reader, error) { 28 | ctx, err := scard.EstablishContext() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | readers, err := ctx.ListReaders() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | if len(readers) == 0 { 39 | return nil, fmt.Errorf("リーダーが見つかりません") 40 | } 41 | 42 | if len(readers) >= 2 { 43 | fmt.Fprintf(os.Stderr, 44 | "警告: 複数のリーダーが見つかりました。最初のものを使います\n") 45 | } 46 | 47 | reader := new(Reader) 48 | reader.ctx = ctx 49 | reader.name = readers[0] 50 | reader.card = nil 51 | for _, opt := range opts { 52 | opt(reader) 53 | } 54 | return reader, nil 55 | } 56 | 57 | func (self *Reader) SetDebug(debug bool) { 58 | self.debug = debug 59 | } 60 | 61 | func (self *Reader) Finalize() { 62 | self.ctx.Release() 63 | } 64 | 65 | func (self *Reader) GetCard() *scard.Card { 66 | card, _ := self.ctx.Connect( 67 | self.name, scard.ShareExclusive, scard.ProtocolAny) 68 | self.card = card 69 | return card 70 | } 71 | 72 | func (self *Reader) Connect() error { 73 | rs := make([]scard.ReaderState, 1) 74 | rs[0].Reader = self.name 75 | rs[0].CurrentState = scard.StateUnaware 76 | 77 | for true { 78 | err := self.ctx.GetStatusChange(rs, -1) 79 | if err != nil { 80 | return err 81 | } 82 | rs[0].CurrentState = rs[0].EventState 83 | if rs[0].EventState&scard.StatePresent != scard.StatePresent { 84 | continue 85 | } 86 | card, err := self.ctx.Connect( 87 | self.name, scard.ShareExclusive, scard.ProtocolAny) 88 | if err != nil { 89 | return err 90 | } else { 91 | self.card = card 92 | break 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func (self *Reader) SelectVisualAP() (*VisualAP, error) { 99 | err := self.SelectDF("D3921000310001010402") 100 | ap := VisualAP{self} 101 | return &ap, err 102 | } 103 | 104 | func (self *Reader) SelectTextAP() (*TextAP, error) { 105 | err := self.SelectDF("D3921000310001010408") 106 | ap := TextAP{self} 107 | return &ap, err 108 | } 109 | 110 | func (self *Reader) SelectJPKIAP() (*JPKIAP, error) { 111 | err := self.SelectDF("D392F000260100000001") 112 | ap := JPKIAP{self} 113 | return &ap, err 114 | } 115 | 116 | func (self *Reader) SelectDF(id string) error { 117 | if self.debug { 118 | fmt.Fprintf(os.Stderr, "# Select DF\n") 119 | } 120 | bid := ToBytes(id) 121 | apdu := NewAPDUCase3(0x00, 0xA4, 0x04, 0x0C, bid) 122 | sw1, sw2, _ := self.Trans(apdu) 123 | if sw1 == 0x90 && sw2 == 0x00 { 124 | return nil 125 | } else { 126 | return NewAPDUError(sw1, sw2) 127 | } 128 | } 129 | 130 | func (self *Reader) SelectEF(id string) error { 131 | if self.debug { 132 | fmt.Fprintf(os.Stderr, "# Select EF\n") 133 | } 134 | bid := ToBytes(id) 135 | apdu := NewAPDUCase3(0x00, 0xA4, 0x02, 0x0C, bid) 136 | sw1, sw2, _ := self.Trans(apdu) 137 | if sw1 == 0x90 && sw2 == 0x00 { 138 | return nil 139 | } else { 140 | return NewAPDUError(sw1, sw2) 141 | } 142 | } 143 | 144 | func (self *Reader) LookupPin() int { 145 | apdu := NewAPDUCase1(0x00, 0x20, 0x00, 0x80) 146 | if self.debug { 147 | fmt.Fprintf(os.Stderr, "# Lookup PIN\n") 148 | } 149 | sw1, sw2, _ := self.Trans(apdu) 150 | if sw1 == 0x63 { 151 | return int(sw2 & 0x0F) 152 | } else { 153 | return -1 154 | } 155 | } 156 | 157 | func (self *Reader) Verify(pin string) error { 158 | if pin == "" { 159 | return errors.New("PINが空です") 160 | } 161 | if self.debug { 162 | fmt.Fprintf(os.Stderr, "# Verify PIN\n") 163 | } 164 | bpin := []byte(pin) 165 | apdu := NewAPDUCase3(0x00, 0x20, 0x00, 0x80, bpin) 166 | sw1, sw2, _ := self.Trans(apdu) 167 | if sw1 == 0x90 && sw2 == 0x00 { 168 | return nil 169 | } else if sw1 == 0x63 { 170 | counter := int(sw2 & 0x0F) 171 | if counter == 0 { 172 | return errors.New("暗証番号が間違っています。ブロックされました") 173 | } 174 | return fmt.Errorf("暗証番号が間違っています。のこり%d回", counter) 175 | } else if sw1 == 0x69 && sw2 == 0x84 { 176 | return errors.New("暗証番号がブロックされています。") 177 | } else { 178 | return fmt.Errorf("暗証番号が間違っています SW1=%02X SW2=%02X", 179 | sw1, sw2) 180 | } 181 | } 182 | 183 | func (self *Reader) ChangePin(pin string) bool { 184 | if self.debug { 185 | fmt.Fprintf(os.Stderr, "# Change PIN\n") 186 | } 187 | bpin := []byte(pin) 188 | apdu := NewAPDUCase3(0x00, 0x24, 0x01, 0x80, bpin) 189 | sw1, sw2, _ := self.Trans(apdu) 190 | if sw1 == 0x90 && sw2 == 0x00 { 191 | return true 192 | } else { 193 | return false 194 | } 195 | } 196 | 197 | func dumpBinary(bin []byte) { 198 | for i := 0; i < len(bin); i++ { 199 | if i%0x10 == 0 { 200 | fmt.Fprintf(os.Stderr, ">") 201 | } 202 | fmt.Fprintf(os.Stderr, " %02X", bin[i]) 203 | if i%0x10 == 0x0f { 204 | fmt.Fprintln(os.Stderr) 205 | } 206 | } 207 | fmt.Fprintln(os.Stderr) 208 | } 209 | 210 | func (self *Reader) Trans(apdu *APDU) (uint8, uint8, []byte) { 211 | card := self.card 212 | cmd := apdu.cmd 213 | if self.debug { 214 | if len(cmd) > 4 && cmd[0] == 0x00 && cmd[1] == 0x20 { 215 | len := int(cmd[4]) 216 | mask := strings.Repeat(" XX", len) 217 | fmt.Fprintf(os.Stderr, "< % X XX%s\n", cmd[:4], mask) 218 | } else { 219 | fmt.Fprintf(os.Stderr, "< % X\n", cmd) 220 | } 221 | } 222 | res, err := card.Transmit(cmd) 223 | if err != nil { 224 | fmt.Fprintf(os.Stderr, "err: %s\n", err) 225 | return 0, 0, nil 226 | } 227 | 228 | if self.debug { 229 | dumpBinary(res) 230 | } 231 | 232 | l := len(res) 233 | if l == 2 { 234 | return res[0], res[1], nil 235 | } else if l > 2 { 236 | return res[l-2], res[l-1], res[:l-2] 237 | } 238 | return 0, 0, nil 239 | } 240 | 241 | func (self *Reader) ReadBinary(size uint16) []byte { 242 | if self.debug { 243 | fmt.Fprintf(os.Stderr, "# Read Binary\n") 244 | } 245 | 246 | var l uint8 247 | var pos uint16 248 | pos = 0 249 | var res []byte 250 | 251 | for pos < size { 252 | if size-pos > 0xFF { 253 | l = 0 254 | } else { 255 | l = uint8(size - pos) 256 | } 257 | apdu := NewAPDUCase2(0x00, 0xB0, uint8(pos>>8&0xFF), uint8(pos&0xFF), l) 258 | sw1, sw2, data := self.Trans(apdu) 259 | if sw1 != 0x90 || sw2 != 0x00 { 260 | return nil 261 | } 262 | res = append(res, data...) 263 | pos += uint16(len(data)) 264 | } 265 | return res 266 | } 267 | 268 | func (self *Reader) Signature(data []byte) ([]byte, error) { 269 | if self.debug { 270 | fmt.Fprintf(os.Stderr, "# Signature\n") 271 | } 272 | 273 | apdu := NewAPDUCase4(0x80, 0x2A, 0x00, 0x80, data, 0) 274 | sw1, sw2, res := self.Trans(apdu) 275 | if sw1 == 0x90 && sw2 == 0x00 { 276 | return res, nil 277 | } else { 278 | return nil, fmt.Errorf("署名エラー(%0X, %0X)", sw1, sw2) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /libmyna/text_ap.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/jpki/myna/asn1" 7 | "strconv" 8 | ) 9 | 10 | type TextAP struct { 11 | reader *Reader 12 | } 13 | 14 | type TextAttrs struct { 15 | Header []byte `asn1:"private,tag:33"` 16 | Name string `asn1:"private,tag:34,utf8"` 17 | Address string `asn1:"private,tag:35,utf8"` 18 | Birth string `asn1:"private,tag:36"` 19 | Sex string `asn1:"private,tag:37"` 20 | } 21 | 22 | type TextSignature struct { 23 | MyNumDigest []byte `asn1:"private,tag:49"` 24 | AttrsDigest []byte `asn1:"private,tag:50"` 25 | Signature []byte `asn1:"private,tag:51"` 26 | } 27 | 28 | type TextCertificate struct { 29 | Raw []byte `asn1:"application,tag:78"` 30 | } 31 | 32 | type TextBasicInfo struct { 33 | APInfo []byte `asn1:"private,tag:65"` 34 | KeyID []byte `asn1:"private,tag:66"` 35 | } 36 | 37 | func (self *TextAP) LookupPin() (int, error) { 38 | err := self.reader.SelectEF("0011") // 券面事項入力補助用PIN 39 | if err != nil { 40 | return 0, err 41 | } 42 | count := self.reader.LookupPin() 43 | return count, nil 44 | } 45 | 46 | func (self *TextAP) VerifyPin(pin string) error { 47 | err := self.reader.SelectEF("0011") 48 | if err != nil { 49 | return err 50 | } 51 | err = self.reader.Verify(pin) 52 | return err 53 | } 54 | 55 | func (self *TextAP) LookupPinA() (int, error) { 56 | err := self.reader.SelectEF("0014") 57 | if err != nil { 58 | return 0, err 59 | } 60 | count := self.reader.LookupPin() 61 | return count, nil 62 | } 63 | 64 | func (self *TextAP) VerifyPinA(pin string) error { 65 | err := self.reader.SelectEF("0014") 66 | if err != nil { 67 | return err 68 | } 69 | err = self.reader.Verify(pin) 70 | return err 71 | } 72 | 73 | func (self *TextAP) LookupPinB() (int, error) { 74 | err := self.reader.SelectEF("0015") 75 | if err != nil { 76 | return 0, err 77 | } 78 | count := self.reader.LookupPin() 79 | return count, nil 80 | } 81 | 82 | func (self *TextAP) VerifyPinB(pin string) error { 83 | err := self.reader.SelectEF("0015") 84 | if err != nil { 85 | return err 86 | } 87 | err = self.reader.Verify(pin) 88 | return err 89 | } 90 | 91 | func (self *TextAP) ReadMyNumber() (string, error) { 92 | err := self.reader.SelectEF("0001") 93 | if err != nil { 94 | return "", err 95 | } 96 | data := self.reader.ReadBinary(17) 97 | var mynumber asn1.RawValue 98 | _, err = asn1.UnmarshalWithParams(data, &mynumber, "private,tag:16") 99 | if err != nil { 100 | return "", err 101 | } 102 | return string(mynumber.Bytes), nil 103 | } 104 | 105 | func (self *TextAP) ReadAttributes() (*TextAttrs, error) { 106 | err := self.reader.SelectEF("0002") 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | data := self.reader.ReadBinary(7) 112 | if len(data) != 7 { 113 | return nil, errors.New("Error at ReadBinary()") 114 | } 115 | 116 | parser := ASN1PartialParser{} 117 | err = parser.Parse(data) 118 | if err != nil { 119 | return nil, err 120 | } 121 | data = self.reader.ReadBinary(parser.GetSize()) 122 | var attrs TextAttrs 123 | _, err = asn1.UnmarshalWithParams(data, &attrs, "private,tag:32") 124 | if err != nil { 125 | return nil, err 126 | } 127 | return &attrs, nil 128 | } 129 | 130 | func (self *TextAP) ReadSignature() (*TextSignature, error) { 131 | err := self.reader.SelectEF("0003") 132 | if err != nil { 133 | return nil, err 134 | } 135 | data := self.reader.ReadBinary(336) 136 | if len(data) != 336 { 137 | return nil, errors.New("Error at ReadBinary()") 138 | } 139 | var signature TextSignature 140 | _, err = asn1.UnmarshalWithParams(data, &signature, "private,tag:48") 141 | if err != nil { 142 | return nil, err 143 | } 144 | return &signature, nil 145 | } 146 | 147 | func (self *TextAP) ReadCertificate() (*TextCertificate, error) { 148 | err := self.reader.SelectEF("0004") 149 | if err != nil { 150 | return nil, err 151 | } 152 | data := self.reader.ReadBinary(568) 153 | if len(data) != 568 { 154 | return nil, errors.New("Error at ReadBinary()") 155 | } 156 | var certificate TextCertificate 157 | _, err = asn1.UnmarshalWithParams(data, &certificate, "application,tag:33") 158 | if err != nil { 159 | return nil, err 160 | } 161 | return &certificate, nil 162 | } 163 | 164 | func (self *TextAP) ReadBasicInfo() (*TextBasicInfo, error) { 165 | err := self.reader.SelectEF("0005") 166 | if err != nil { 167 | return nil, err 168 | } 169 | data := self.reader.ReadBinary(256) 170 | if len(data) != 256 { 171 | return nil, errors.New("Error at ReadBinary()") 172 | } 173 | var basicInfo TextBasicInfo 174 | _, err = asn1.UnmarshalWithParams(data, &basicInfo, "private,tag:64") 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | if len(basicInfo.APInfo) != 4 { 180 | return nil, errors.New("invalid APInfo length") 181 | } 182 | if len(basicInfo.KeyID) != 16 { 183 | return nil, errors.New("invalid KeyID length") 184 | } 185 | return &basicInfo, nil 186 | } 187 | 188 | // ヘッダーをHEX文字列に変換 189 | func (self *TextAttrs) HeaderString() string { 190 | return fmt.Sprintf("% X", self.Header) 191 | } 192 | 193 | // ISO5218コードから日本語文字列に変換 194 | func (self *TextAttrs) SexString() string { 195 | n, err := strconv.Atoi(self.Sex) 196 | if err != nil { 197 | return "エラー" 198 | } 199 | switch n { 200 | case 1: 201 | return "男性" 202 | case 2: 203 | return "女性" 204 | case 9: 205 | return "適用不能" 206 | default: 207 | return "不明" 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /libmyna/utils.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | func ToBytes(s string) []byte { 10 | b, _ := hex.DecodeString(strings.Replace(s, " ", "", -1)) 11 | return b 12 | } 13 | 14 | func ToHexString(b []byte) string { 15 | s := hex.EncodeToString(b) 16 | return s 17 | } 18 | 19 | type ASN1PartialParser struct { 20 | offset uint16 21 | length uint16 22 | } 23 | 24 | func (self *ASN1PartialParser) GetOffset() uint16 { 25 | return self.offset 26 | } 27 | 28 | func (self *ASN1PartialParser) GetSize() uint16 { 29 | return self.offset + self.length 30 | } 31 | 32 | func (self *ASN1PartialParser) parseTag(data []byte) error { 33 | var tagsize uint16 = 1 34 | if len(data) < 2 { 35 | return errors.New("few data size") 36 | } 37 | if data[0]&0x1f == 0x1f { 38 | tagsize++ 39 | if len(data) < 2 || data[1]&0x80 != 0 { 40 | return errors.New("unexpected tag size") 41 | } 42 | } 43 | self.offset = tagsize 44 | return nil 45 | } 46 | 47 | func (self *ASN1PartialParser) parseLength(data []byte) error { 48 | if int(self.offset) >= len(data) { 49 | return errors.New("few data size") 50 | } 51 | b := data[self.offset] 52 | self.offset++ 53 | if b&0x80 == 0 { 54 | self.length = uint16(b) 55 | } else { 56 | lol := int(b & 0x7f) 57 | for i := 0; i < lol; i++ { 58 | if int(self.offset) >= len(data) { 59 | return errors.New("truncated tag or length") 60 | } 61 | b = data[self.offset] 62 | self.offset++ 63 | self.length <<= 8 64 | self.length |= uint16(int(b)) 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | func (self *ASN1PartialParser) Parse(data []byte) error { 71 | err := self.parseTag(data) 72 | if err != nil { 73 | return err 74 | } 75 | err = self.parseLength(data) 76 | if err != nil { 77 | return err 78 | } 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /libmyna/validate.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | ) 7 | 8 | func Validate4DigitPin(pin string) error { 9 | match, _ := regexp.MatchString("^\\d{4}$", pin) 10 | if !match { 11 | return errors.New("暗証番号(4桁)を入力してください。") 12 | } 13 | return nil 14 | } 15 | 16 | func ValidateJPKISignPassword(pass string) error { 17 | if len(pass) < 4 || 16 < len(pass) { 18 | return errors.New("パスワードの長さが正しくありません") 19 | } 20 | match, _ := regexp.MatchString("^[A-Z0-9]+$", pass) 21 | if !match { 22 | return errors.New("パスワードの文字種が不正です") 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /libmyna/version.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | var Version = "0.5.1" 4 | -------------------------------------------------------------------------------- /libmyna/visual_ap.go: -------------------------------------------------------------------------------- 1 | package libmyna 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jpki/myna/asn1" 7 | ) 8 | 9 | type VisualAP struct { 10 | reader *Reader 11 | } 12 | 13 | type VisualInfo struct { 14 | Header []byte `asn1:"private,tag:33"` 15 | Birth string `asn1:"private,tag:34"` 16 | Sex string `asn1:"private,tag:35"` 17 | PublicKey []byte `asn1:"private,tag:36"` 18 | Name []byte `asn1:"private,tag:37"` 19 | Addr []byte `asn1:"private,tag:38"` 20 | Photo []byte `asn1:"private,tag:39"` 21 | Signature []byte `asn1:"private,tag:40"` 22 | Expire string `asn1:"private,tag:41"` 23 | Code []byte `asn1:"private,tag:42"` 24 | } 25 | 26 | func (self *VisualAP) LookupPinA() (int, error) { 27 | err := self.reader.SelectEF("0013") 28 | if err != nil { 29 | return 0, err 30 | } 31 | count := self.reader.LookupPin() 32 | return count, nil 33 | } 34 | 35 | func (self *VisualAP) VerifyPinA(pin string) error { 36 | err := self.reader.SelectEF("0013") 37 | if err != nil { 38 | return err 39 | } 40 | err = self.reader.Verify(pin) 41 | return err 42 | } 43 | 44 | func (self *VisualAP) LookupPinB() (int, error) { 45 | err := self.reader.SelectEF("0012") 46 | if err != nil { 47 | return 0, err 48 | } 49 | count := self.reader.LookupPin() 50 | return count, nil 51 | } 52 | 53 | func (self *VisualAP) VerifyPinB(pin string) error { 54 | err := self.reader.SelectEF("0012") 55 | if err != nil { 56 | return err 57 | } 58 | err = self.reader.Verify(pin) 59 | return err 60 | } 61 | 62 | func (self *VisualAP) GetVisualInfo() (*VisualInfo, error) { 63 | err := self.reader.SelectEF("0002") 64 | if err != nil { 65 | return nil, err 66 | } 67 | data := self.reader.ReadBinary(7) 68 | if len(data) != 7 { 69 | return nil, errors.New("Error at ReadBinary()") 70 | } 71 | 72 | parser := ASN1PartialParser{} 73 | err = parser.Parse(data) 74 | if err != nil { 75 | return nil, err 76 | } 77 | data = self.reader.ReadBinary(parser.GetSize()) 78 | 79 | var front VisualInfo 80 | _, err = asn1.UnmarshalWithParams(data, &front, "private,tag:32") 81 | if err != nil { 82 | return nil, err 83 | } 84 | return &front, nil 85 | } 86 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jpki/myna/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /myna.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run -tags tool *.go "$@" 4 | -------------------------------------------------------------------------------- /mynaqt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpki/myna/55ab7a81cca6130fd1cbb8def74ec6b9d598c20e/mynaqt.png --------------------------------------------------------------------------------