myna-card
A Rust library for accessing the Japanese Individual Number Card (個人番号カード / My Number Card) over PC/SC.
Status
The transport, APDU and file-access layers are implemented and unit tested, and the files whose
layouts have been worked out are decoded into types rather than handed back as bytes: the 個人番号,
the 基本4情報, the card info record, the card face with its images, and the card-verifiable
certificates. Files whose layout has not been worked out are still reachable, as raw bytes, through
each application's read_ef.
Layers
ap::{common, juki, surface, text, jpki} which application owns which file, and its access rules
sm::SecureSession secure messaging with the 券面入力補助AP (feature `sm`)
mf::MasterFile the master file level: GET DATA objects, and the
files JICSAP defines there (001E, 2F10, 2F11)
card::Card SELECT FILE, READ BINARY, READ RECORD, VERIFY, GET DATA
apdu::{Command, Response, StatusWord} ISO/IEC 7816-4 encoding — no I/O
data::{...} the values the card stores, and the credentials
certificate::Certificate the JPKI X.509 certificates (feature `verify`)
tlv::{ber, simple} the two TLV encodings the card uses
transport::Transmit the link to the card
transport::pcsc PC/SC backend (feature `pcsc`, on by default)
transport::mock scripted in-memory backend (feature `mock`)
Two TLV encodings
Record structured EFs hold simple encoded TLV (JICSAP 4.4.1): a one byte tag, a one or three
byte length, then the value. Transparent EFs hold whatever the application put there, which on
this card is BER — DER certificates in JPKI, BER-TLV objects in the 券面 applications. The two are
not interchangeable: a first length byte of FF introduces a two byte length in the simple
encoding and is not a valid length at all in BER. Use tlv::simple for records and tlv::ber for
transparent files.
Card is generic over the transport, so everything above it can be exercised without a physical
card. transport::mock::MockTransport replays a fixed script of responses and records what was
sent; the unit tests in card.rs and ap/jpki.rs show the pattern.
Usage
use ;
use Sharing;
use ;
// Exclusive because this presents a PIN — see below.
let mut card = connect_any?;
let mut jpki = select?;
// Readable without a password.
let cert = jpki.read_auth_certificate?;
// Needs the four digit authentication PIN.
jpki.verify_auth_pin?;
let signature = jpki.sign_with_auth_key?;
Who else may hold the card
Every connection says whether anything else may hold the card at the same time. There is no default, because the answer follows from what the program is about to do and the wrong one fails quietly in either direction.
Sharing::Shared is the PC/SC default and the right one for reading: several programs can ask a
card what it is at once without disturbing each other.
Signing is different. A successful VERIFY stays in effect until the card leaves the field, so
between presenting a PIN and powering the card down, any other process on the machine can sign with
the key you unlocked — without knowing the PIN. Sharing::Exclusive closes that window:
use ;
let mut card = connect_any?;
Nothing else can hold the card until that connection is dropped, and power_cycle reconnects on the
same terms rather than quietly giving the reservation up. Connecting fails with
pcsc::Error::SharingViolation if something already has the card — the holder keeps it, so this is
a reservation to take when the key needs it and release as soon as you are done. What it locks out
is the card's other legitimate users.
Secure messaging
One application offers it, and only one. The 券面入力補助AP publishes an RSA public key in EF
0006; a terminal encrypts a session key to it with SET SESSION KEY, and afterwards command and
response data travel under AES-128-CBC. Behind the sm feature, which is off by default.
let mut text = select?;
text.verify_pin?; // in the clear — see below
let mut seed = ; // from a CSPRNG, fresh per session
fill_bytes;
let mut session = text.open_secure_session?;
session.verify?; // 照合番号A, encrypted
let my_number = session.read_ef?; // and the file it opens
It cannot protect the PIN. The card will not deliver a session key until the PIN has been
presented in the clear: with nothing presented SET SESSION KEY answers 6982, and presenting
照合番号A instead — which the card accepts — leaves it at 6982. The ordering is closed, so treat
the PIN as exposed to anything listening whether or not a session follows.
What it does protect is what comes after, and one thing in particular: 照合番号A is the 個人番号, so presenting it inside a session keeps the number itself off the interface. The 券面事項確認AP has no equivalent, which is why the 照合番号 that open it are unavoidably in the clear.
There is no integrity, only confidentiality — see "Not implemented yet".
examples/secure_messaging.rs runs the whole sequence against a card.
Signing
CLA 80 INS 2A is the card's own command, not one of the JICSAP five. Its P1 selects one of six
schemes — three padding modes, each in a "you supply the SHA-256" and a "the card hashes it"
variant — modelled as SignatureScheme. All six were established by exercising every P1 value
against a card and checking each result against the certificate's public key.
Sha256DigestInfo is the ordinary choice: hand it the message and the signature verifies as a
standard sha256WithRSAEncryption.
Signature verification
With the verify feature — on by default — signatures are checked rather than just produced.
// JPKI: sign, and check the result against the certificate the key belongs to.
jpki.verify_auth_pin?;
let signature = jpki.sign_with_auth_key_checked?;
let cert = jpki.read_auth_certificate?; // subject, issuer, validity, public key
The 券面 applications take two steps, and both are needed. The data is signed by an issuer key,
certified in EF 0004; the card's own key — carried inside the signed data — signs challenges.
So verifying the record proves the data is authentic and that the key belongs to it, and
challenging the card proves the card is present:
let cert = surface.read_certificate?;
cert.verify?; // the issuer key is certified
let face = surface.read_card_face?;
face.verify?; // the data is authentic
let challenge = surface.card.get_challenge?;
let signature = surface.sign?; // the card's own key, no PIN
Sha256DigestInfo
.verify?; // the card is here
Turn the feature off with default-features = false if you would rather check signatures
elsewhere; the RSA and X.509 dependencies go with it.
JPKI certificates and their roots
Certificate::verify_chain checks the pair the card hands over, EF 000A then EF 000B. Both
came off the same card, so that is an internal consistency check and nothing more.
Certificate::verify_to_root ends at a root the crate carries instead, compiled in from
certs/ rather than read at run time: six published by J-LIS, three generations for each
of the two certificate types.
Four test hierarchy roots are carried as well, and reaching them takes asking. Both entry points
take an Accept, and Accept::ProductionOnly — the setting for any program that verifies real
cardholders — never returns one. A test card is not a person's Individual Number Card.
Note that a distinguished name does not identify a root: all three generations share one. The lookup narrows by name and decides by signature.
Card-verifiable certificates
CardVerifiableCertificate::verify() resolves the CA key from the certificate's 証明者鍵ID using
the table in ca, or take verify_with() to supply one yourself. verify_chain() walks a chain,
resolving a key for the root only and checking each later link against the one above it.
Identifiers are a KeyId, not loose bytes: 6000023/001 prints as such, and comparison uses all
sixteen bytes, so a certificate from one hierarchy never resolves to another's key by accident.
The table holds six keys: the three production 証明者鍵ID and the three matching ones of the test
hierarchy JPKI test cards are issued under. Certificates from either verify without a key being
supplied by hand, and so do the intermediates below them, whose keys travel inside the certificate
above. Any other 証明者鍵ID returns UnknownCertificateAuthority — nothing was checked, which is a
different answer from a bad signature and is reported as one.
The master file level
No elementary file under the MF is readable on this card, but GET DATA answers there. MasterFile
exposes what is: the card identification number, the issuing municipality, the expiry date, and a
chain of card-verifiable certificates that needs a CA key only for its root.
card.transport_mut.power_cycle?; // GET DATA answers only with no application selected
let mut mf = new;
verify_chain?;
Card::contact_atr() returns the card's real contact-interface ATR, checksum verified — a
contactless reader reports one it made up instead. Which state answers varies between cards: some
return it at the MF level and some only with an application selected.
Reading a card
Each credential is optional and is only ever presented to the key it belongs to, so a missing one
skips the files it guards rather than costing a retry. data::verification_code_b builds 照合番号B
from the date of birth, expiry year and security code — note that the card wants the date of birth
as a Japanese era year.
Other examples:
Retry counters
Every failed VERIFY decrements a counter on the card. When a counter reaches zero the key is
blocked and only a municipal office can unblock it. pin_retries() sends a VERIFY with an empty
data field, which JICSAP 6.4.9 (5) defines as reporting the remaining attempts without consuming
one — call it before presenting a value you are not sure about.
Two statuses mean blocked: 63C0 from the attempt that exhausts the counter, and 6984 from
every attempt after that. A key with no retry limit answers 6300 and never reports a number.
Requirements
- A PC/SC stack. On Linux that is
pcscdplus thelibpcsclitedevelopment headers; on macOS and Windows it is part of the OS. - A contactless or contact reader. The card is a Type B contactless card and also works over a contact interface.
Not implemented yet
- Revocation. Chains are checked against published roots, but JPKI publishes revocation as a separate online service and nothing here consults it. Basic constraints and key usage are not checked either.
- The files that stay unidentified: 公的個人認証AP
0009, 券面事項確認AP0006and 券面入力補助AP0008— both sixteenFFbytes — and the trailing 128 bytes of 券面入力補助AP0005. - 券面入力補助AP
0012, a key reference that arrives blocked with zero attempts left. It answers the non-consuming query63C0and a VERIFY6984. No file was found that it opens. - Integrity-protected secure messaging.
CLA=0Cneeds a CCS key, and the one application that offers secure messaging will not accept one: SET SESSION KEY takesA0 { 80 : … }and answers6A80to the forms carrying81. Confidentiality is implemented — seesm— but there is no MAC to be had, so an active attacker on the interface can corrupt a command even though they cannot read one. - Secure messaging on the other four applications, because they do not offer it. 共通カードAP and
券面事項確認AP answer SET SESSION KEY
66F1, 公的個人認証AP and 住基AP answer6982with every credential this crate can present. None answers6D00, so the instruction exists card-wide. - The remaining extended system commands. Their instruction bytes are in
card::ins, but none of them helps read a card. - Answer-to-Reset parsing (JICSAP 3.2). There is nothing to parse on this card: the contact ATR, which the card will hand over through GET DATA, declares zero historical bytes.
License
MIT