- Seattle Public Library와 King County Library System의 플라스틱 카드를 줄이려는 목표에서, iPhone Wallet 패스의 JSON·이미지·서명 구조를 직접 다루게 됨
- Wallet은 QR code, PDF417, Aztec Code, Code 128만 지원해 도서관 카드의 Codabar를 기본 바코드로 넣을 수 없었고, 실제 스캔용 바코드는 이미지로 우회함
- 패스는 Apple이 신뢰하는 키로 서명해야 했지만, 연 99달러 Apple Developer 멤버십 대신 오프라인 생성이 가능한 iOS 패스 앱에서 인증서와 키를 찾아 사용함
storeCard레이아웃, 해상도별 로고, Codabarstrip.png, 빈PKBarcodeFormatCode128항목을 조합해 화면에는 이미지 바코드를 보여주면서도 iOS의 화면 밝기 증가 동작을 살림- 완성된
.pkpass는 manifest 생성,openssl smime서명, ZIP 패키징으로 만들 수 있었고, 실제 도서관 테스트 전이지만 별도 스캐너는 휴대폰 화면의 바코드를 읽었음
목표: 플라스틱 카드 없이 도서관 바코드 쓰기
- Seattle Public Library와 King County Library System은 대출 계정에 연결된 바코드 인쇄 플라스틱 카드를 발급함
- 2023년 기준 Seattle 도서관의 대부분 기능은 셀프서비스이며, 보통 도서관 바코드를 수동 입력할 수 있음
- 그래도 바코드를 바로 보여주는 편이 훨씬 편리하므로, 거의 쓰지 않는 플라스틱 카드를 지갑에 넣지 않고 iPhone Wallet 앱에 넣는 것이 목표였음
Wallet 패스는 JSON·이미지·서명으로 구성됨
- Wallet 앱은 현재 결제 카드, ID, 키 등을 다루지만, 2012년 Passbook 출시 당시에는 “패스”만 관리했음
- Apple의 패스 문서에 따르면 패스는 이메일이나 웹으로 배포할 수 있는 자체 포함 ZIP 파일이며, 내부에는 JSON과 PNG가 들어감
.pkpass파일의 핵심 구성은 단순함pass.json: 이미지가 아닌 패스 내용을 설명함manifest.json: 다른 파일들의 SHA-1 체크섬 목록signature:manifest.json내용에 대한 S/MIME 서명- 지원되는 이미지 파일들
Codabar 미지원이 첫 번째 장벽
- Wallet 패스가 지원하는 바코드는 네 가지뿐임
- QR code
- PDF417
- Aztec Code
- Code 128
- 도서관 카드는 도서관에서 널리 쓰이는 Codabar 형식이었음
- 도서관 바코드 스캐너가 다른 선형 바코드를 지원할 가능성은 있지만, 모든 지점에서 확실히 동작한다고 볼 수 있는 형식은 Codabar뿐임
- 결국 Wallet의 기본 바코드 기능 대신, 스캔 가능한 Codabar 이미지를 만들어 패스 안에 넣어야 했음
패스 서명 인증서 문제
- Wallet 패스는 암호화 서명이 필요하며, Apple 인증기관 중 하나가 신뢰하는 키로 서명되어야 함
- 항공권의 게이트 변경이나 좌석 변경처럼 벤더가 패스를 자동 업데이트하는 용도라면 서명이 어느 정도 타당함
- Apple 개발자라면 패스 서명 키를 비교적 쉽게 받을 수 있지만, 이 작업을 위해 연 99달러를 지불할 생각은 없었음
기존 패스 생성 앱이 맞지 않았던 이유
- 이미 Apple 개발자인 사람들이 만든 여러 패스 디자인 앱이 있었지만, 필요한 수준의 제어를 제공하지 않았음
-
로고 크기 제어
- Apple은 패스 왼쪽 위 로고에 최대 50 device-independent pixels 높이를 허용함
- 텍스트 오른쪽에 붙는 정사각형 로고는 약 40px 높이가 가장 적당했음
- 개발자는
logo.png,logo@2x.png,logo@3x.png를 각각 제공해야 하지만, 앱들은 보통 단일 로고 선택만 허용하고 스케일 제어를 주지 않음
-
화면 밝기 동작
- 일반 지원 바코드가 있는 패스를 열면 스캐너 대비를 위해 화면이 밝아짐
- 실제로는 동작하지 않는 바코드를 표시하지 않으면서도 iOS가 화면을 밝게 만들려면
pass.json을 직접 다뤄야 했음 - 무료로 내려받을 수 있는 앱들도 Wallet 저장 가능 패스 수를 제한하고 결제를 요구하는 경우가 있었음
오프라인 생성 앱에서 서명 키 찾기
- 여러 무료 시작형 패스 생성 앱을 내려받아, 오프라인에서도 패스 생성이 되는지 확인함
- 비행기 모드와 Wi-Fi 비활성화 상태에서 테스트한 결과, 최소 한 앱은 오프라인으로 패스를 만들 수 있었음
- 해당 앱의 구체적 이름은 키가 폐기되는 것을 피하기 위해 밝히지 않음
- 과정은 예상보다 단순했음
- Apple silicon Mac에서 iOS 앱을 실행할 수 있으므로 Mac에 앱을 다운로드함
- 래핑된 iOS 앱 번들 안에서 눈에 띄는
.p12파일을 찾음 - PKCS#12 파일에는 가져오기 비밀번호가 필요하므로, 메인 바이너리에
strings를 실행해 비밀번호로 보이는 문자열을 찾음
openssl pkcs12로 인증서 정보를 확인하자 issuer가 Apple Worldwide Developer Relations Certification Authority로 나타남- 인증서 체인도 필요하며, 중간 인증서는 앱 번들 안에 있을 가능성이 있고 X.509 확장 필드 안의 URL로도 다운로드할 수 있음
패스 레이아웃 구성
- 패스 스타일은
boarding pass,coupon,event ticket,generic,store card중 하나를 선택해야 함 - 큰 가로 이미지를 넣으려면
strip이미지를 지원하는 레이아웃이 필요함- 후보는
coupon,event ticket,store card - 물리적 도서관 카드와 가장 비슷한 형태로 store card를 선택함
- 후보는
pass.json에는 다음 값들이 들어감passTypeIdentifierteamIdentifierformatVersionserialNumberorganizationNamelogoTextdescriptionstoreCardbackgroundColorforegroundColorsharingProhibited
passTypeIdentifier와teamIdentifier는 Apple 인증서 subject의UID,OU필드와 각각 일치해야 함- 같은
passTypeIdentifier로 만든 각 패스에는 고유한serialNumber가 필요함 - 이미지는 용도별로 준비함
icon.png는 필수지만 패스 자체에는 표시되지 않음logo.png는 왼쪽 위에 표시되는 로고임- 40×40
logo.png, 80×80logo@2x.png, 120×120logo@3x.png를 생성함 logo@3x.png를icon.png로 복사함- 사전 생성한 바코드는
strip.png에 넣음
Codabar 바코드 이미지 만들기
- iOS는
strip.png를 기기 화면의 박스에 맞게 스케일하고 크롭하므로,strip.png는 해상도별로 3종을 만들 필요가 없었음 - 두 도서관 카드는 시작·종료 심볼로 A와 D를 사용함
- 시작·종료 심볼은 바코드 스캐너가 있으면 가장 쉽게 확인할 수 있고, 없으면 Wikipedia의 Codabar 인코딩 표와 눈으로 비교할 수 있음
- 온라인에서 바로 쓸 수 있는 Codabar 생성기는 많지 않았지만, 형식 자체는 직접 구현하기 쉬웠음
- 프로토타입에서는 Rust용 Barcoders 라이브러리로 SVG를 만들고, SVG를 조정한 뒤 PNG로 내보냄
- 최종 레이아웃은 스캔 가능성과 iOS 축소 표시를 고려해 잡음
- 바코드 높이는 시작·종료 심볼을 포함한 전체 심볼 수의 2배 단위로 설정함
- 예를 들어 13자리 바코드 번호는 15개 심볼이므로 높이를 30단위로 만듦
- 시작 전과 종료 후에 각각 15단위 quiet space를 둠
- 바코드 위아래에는 각각 50단위 패딩을 둠
- 각 단위는 8픽셀로 확대해 iOS가 항상 이미지를 축소하도록 함
- 예시 15심볼 바코드에서는 최종 이미지 높이가 1040픽셀이고 바코드 자체 높이는 240픽셀임
- 위 레이아웃의 Codabar BMP를 만들고
sips로 PNG로 변환하는 69줄 셸 스크립트를 작성함 - 스크립트 출력물을
strip.png로 저장하면 패스에 들어갈 바코드 이미지가 준비됨
바코드 번호 표시와 화면 밝기 우회
- 바코드 번호는
secondaryFields로 바코드 아래에 표시함key:numberlabel:CARD NUMBERvalue: 카드 번호
- iOS는 바코드가 있는 패스를 선택하면 스캐너를 돕기 위해 화면을 밝게 만듦
- 이미지로 넣은 Codabar만으로는 iOS가 바코드가 있다고 판단하지 않음
pass.json최상위에 빈 바코드 항목을 지정해 이 동작을 우회함message는 빈 문자열format은PKBarcodeFormatCode128messageEncoding은iso-8859-1
- 이 방식은 패스 하단에 바코드를 표시하지 않으면서도, iPhone이 바코드가 있는 패스처럼 화면을 밝게 만들었음
서명과 패키징
- 모든 파일을 준비한 뒤
manifest.json을 생성해야 함 manifest.json은 파일명을 키로, SHA-1 체크섬을 값으로 갖는 객체임sha1sum,jq를 조합해 PNG 파일들과pass.json의 manifest를 생성함- manifest 서명은
openssl smime명령으로 수행함- signer 인증서
- private key
- Apple WWDR 중간 인증서
- 입력
manifest.json - 출력
signature
openssl smime의-attime옵션으로 원하는 서명 시각을 지정할 수 있음- 옵션 값은 UNIX epoch임
- Apple에서 받았거나 찾아낸 인증서가 만료됐더라도 해당 시각으로 서명할 수 있음
- 마지막으로 다음 파일들을 ZIP으로 묶어
.pkpass를 만듦- PNG 이미지들
pass.jsonmanifest.jsonsignature
테스트 결과와 아쉬운 점
- macOS에는 패스 미리보기 도구가 있어 패스가 유효하고 대체로 제대로 보이는지 확인할 수 있음
- 유효하지 않으면
Console.app에서 오류를 찾을 수 있음 - 미리보기 도구는 100% 정확하지 않지만, iCloud를 통해 iPhone으로 보내는 버튼이 있음
- 완성된 패스는 실제 도서관에서는 아직 테스트하지 않았음
- 별도 바코드 스캐너는 휴대폰 화면의 바코드를 실제 플라스틱 카드와 마찬가지로 읽을 수 있었음
- 다만 화면 밝기를 최대로 올렸을 때 잘 읽힘
- 빈 바코드 우회로 올라가는 밝기보다 더 밝게 설정했음
- 패스 명세가 10년 동안 거의 변하지 않은 점은 좋지만, PNG와 JSON으로 된 무해한 패스를 서명하려고 연 99달러 Apple Developer 멤버십이 필요한 점은 아쉬움
- 일부 패스 기능에는 서명이 필요하다고 볼 수 있지만, 이 작업에서 한 일에는 서명이 필요하지 않아야 한다고 봄
- Apple이 Wallet에 Codabar 지원을 추가하면, 도서관 시스템 전체의 스캐너가 Code 128을 지원하는지 감사하지 않고도 디지털 도서관 카드를 지원할 수 있음