Closed8
ImHexでWebAuthnのAttestationObjectのパースをする
WebAuthnのAttestation Objectをバイナリエディタで見たい
- WebAuthnのサーバー実装を書いている
- クライアントから送られたAttestation Objectのバイナリをサーバーでパースしたい
ImHex
可視化に使うバイナリエディタ https://github.com/WerWolv/ImHex
Attestation Objectのspec
外側がCBORで、内側のauthDataがまたバイナリ
authDataの中身に公開鍵や鍵が保存されている方法などが記録されている
CBORのImHexで使うpatternがなさそうなので自作する必要がありそう?
ImHex パターンランゲージ
パースに使うためのDSL https://docs.werwolv.net/pattern-language/
CBOR部分のパース
以下のパターンでとりあえずパースできた。attestation objectに出てくるものに限っている。
参考
#include <std/mem.pat>
#include <std/core.pat>
#include <std/io.pat>
#define SHORT_COUNT_TINY_FIELD_MAX_NUM 23
enum MajorType : u8 {
PositiveInteger = 0x00,
NegativeInteger = 0x01,
ByteString = 0x02,
UTF8String = 0x03,
Array = 0x04,
Map = 0x05,
SemanticTag = 0x06,
Special = 0x07
};
enum ShortCount : u8 {
};
bitfield Header {
short_count : 5;
MajorType major_type : 3;
};
struct CBOR {
Header header;
if (
header.major_type == MajorType::PositiveInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 tiny_int = header.short_count;
break;
} else if (
header.major_type == MajorType::NegativeInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
s8 tiny_int = -1 - header.short_count;
break;
} else if (
header.major_type == MajorType::Special &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 special = header.short_count;
break;
} else if (
header.major_type == MajorType::UTF8String ||
header.major_type == MajorType::ByteString
) {
u64 size = header.short_count;
if (size > SHORT_COUNT_TINY_FIELD_MAX_NUM) {
u8 extra_bytes = size - SHORT_COUNT_TINY_FIELD_MAX_NUM;
if (extra_bytes == 1) {
u8 extra_size;
size = extra_size;
} else if (extra_bytes == 2) {
u16 extra_size;
size = extra_size;
} else if (extra_bytes == 3) {
u32 extra_size;
size = extra_size;
} else if (extra_bytes == 4) {
u64 extra_size;
size = extra_size;
}
}
u8 end = $ + size;
match (header.major_type) {
(MajorType::UTF8String): char value[while($ < end)];
(MajorType::ByteString): u8 value[while($ < end)];
}
} else if (header.major_type == MajorType::Map
) {
u8 size = header.short_count;
CBOR items[size*2];
}
};
CBOR cbor[while(!std::mem::eof())] @ 0x00;
ちなみにこんな感じ
AuthDataまでのパース
PublicKeyより下はCOSEフォーマットなので、それ以外はパースできた気がする。
#include <std/mem.pat>
#include <std/core.pat>
#include <std/io.pat>
#include <type/guid.pat>
#define SHORT_COUNT_TINY_FIELD_MAX_NUM 23
namespace authData {
bitfield Flags {
bool user_presence : 1;
bool reserved1 : 1;
bool user_verification : 1;
bool backup_eligibility : 1;
bool backup_state : 1;
bool reserved5 : 1;
bool attested_credential_data : 1;
bool extension_data : 1;
};
struct AttestedCredentialData {
type::GUID aaguid;
be u16 credential_id_length;
if (credential_id_length == 16) {
type::GUID credential_id;
} else {
u64 credential_id_end = $ + credential_id_length;
u8 credential_id[while($ < credential_id_end)];
}
u8 credential_public_key[while(!std::mem::eof())];
};
struct Data {
u8 rpIdHash[32];
Flags flags;
u32 signCount;
AttestedCredentialData attested_credential_data;
};
}
enum MajorType : u8 {
PositiveInteger = 0x00,
NegativeInteger = 0x01,
ByteString = 0x02,
UTF8String = 0x03,
Array = 0x04,
Map = 0x05,
SemanticTag = 0x06,
Special = 0x07
};
enum ShortCount : u8 {
};
bitfield Header {
short_count : 5;
MajorType major_type : 3;
};
struct CBOR {
Header header;
if (
header.major_type == MajorType::PositiveInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 tiny_int = header.short_count;
break;
} else if (
header.major_type == MajorType::NegativeInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
s8 tiny_int = -1 - header.short_count;
break;
} else if (
header.major_type == MajorType::Special &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 special = header.short_count;
break;
} else if (
header.major_type == MajorType::UTF8String ||
header.major_type == MajorType::ByteString
) {
u64 size = header.short_count;
if (size > SHORT_COUNT_TINY_FIELD_MAX_NUM) {
u8 extra_bytes = size - SHORT_COUNT_TINY_FIELD_MAX_NUM;
if (extra_bytes == 1) {
u8 extra_size;
size = extra_size;
} else if (extra_bytes == 2) {
be u16 extra_size;
size = extra_size;
} else if (extra_bytes == 3) {
be u32 extra_size;
size = extra_size;
} else if (extra_bytes == 4) {
be u64 extra_size;
size = extra_size;
}
}
u64 end = $ + size;
match (header.major_type) {
(MajorType::UTF8String): char value[while($ < end)];
(MajorType::ByteString): authData::Data auth_data;
}
} else if (header.major_type == MajorType::Map
) {
u8 size = header.short_count;
CBOR items[size*2];
}
};
CBOR cbor[while(!std::mem::eof())] @ 0x00;
attestationObjectのattStmtはfmtがnone
の場合は空。1PasswordとiOSのKeychainで試したが、どちらもnoneが入っていた
authData内のPublicKeyをCOSEとしてパース
COSEのRFC
参考: https://datatracker.ietf.org/doc/html/rfc8152
自動翻訳: https://tex2e.github.io/rfc-translater/html/rfc8152.html
今回のケースではCOSEはCBORのmapで格納されている。mapのキーは負数を含む整数である。手元のauthDataでは、1
,3
,-1
,-2
,-3
のキーが確認できた。これらをラベルと呼ぶ。ラベルのそれぞれの意味は、
ラベル | 意味 | 値の型 | 値の対応(例) |
---|---|---|---|
1 | マップのキーのファミリー | 整数 または 文字列 | 2=EC2 |
3 | アルゴリズム | 整数 または 文字列 | -7=ECDSA w/ SHA-256 |
-1 | 楕円曲線暗号の種別(crv) | 整数 | 1=P-256 |
-2 | 楕円曲線暗号のECポイントのx座標 | バイト列 | - |
-3 | 楕円曲線暗号のECポイントのy座標 | バイト列 | - |
色々整理した結果こんな感じ
#include <std/mem.pat>
#include <std/core.pat>
#include <std/io.pat>
#include <type/guid.pat>
#define SHORT_COUNT_TINY_FIELD_MAX_NUM 23
using CBOR;
namespace authData {
bitfield Flags {
bool user_presence : 1;
bool reserved1 : 1;
bool user_verification : 1;
bool backup_eligibility : 1;
bool backup_state : 1;
bool reserved5 : 1;
bool attested_credential_data : 1;
bool extension_data : 1;
};
struct AttestedCredentialData {
be type::GUID aaguid;
be u16 credential_id_length;
if (credential_id_length == 16) {
be type::GUID credential_id;
} else {
u8 credential_id_end = $ + credential_id_length;
u8 credential_id[while($ < credential_id_end)];
}
CBOR credential_data;
};
struct Data {
u8 rpIdHash[32];
Flags flags;
u32 signCount;
AttestedCredentialData attested_credential_data;
};
}
bool inAuthData;
enum MajorType : u8 {
PositiveInteger = 0x00,
NegativeInteger = 0x01,
ByteString = 0x02,
UTF8String = 0x03,
Array = 0x04,
Map = 0x05,
SemanticTag = 0x06,
Special = 0x07
};
enum ShortCount : u8 {
};
bitfield Header {
short_count : 5;
MajorType major_type : 3;
};
struct CBOR {
Header header;
if (
header.major_type == MajorType::PositiveInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 tiny_int = header.short_count;
} else if (
header.major_type == MajorType::NegativeInteger &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
s8 tiny_int = -1 - header.short_count;
} else if (
header.major_type == MajorType::Special &&
header.short_count <= SHORT_COUNT_TINY_FIELD_MAX_NUM
) {
u8 special = header.short_count;
} else if (
header.major_type == MajorType::UTF8String ||
header.major_type == MajorType::ByteString
) {
u64 size = header.short_count;
if (size > SHORT_COUNT_TINY_FIELD_MAX_NUM) {
u8 extra_bytes = size - SHORT_COUNT_TINY_FIELD_MAX_NUM;
if (extra_bytes == 1) {
u8 extra_size;
size = extra_size;
} else if (extra_bytes == 2) {
be u16 extra_size;
size = extra_size;
} else if (extra_bytes == 3) {
be u32 extra_size;
size = extra_size;
} else if (extra_bytes == 4) {
be u64 extra_size;
size = extra_size;
}
}
u64 end = $ + size;
if (header.major_type == MajorType::UTF8String) {
char value[while($ < end)];
if (value == "authData") {
inAuthData = true;
}
} else if (header.major_type == MajorType::ByteString) {
if (inAuthData) {
inAuthData = false;
authData::Data auth_data;
} else {
u8 value[while($ < end)];
}
}
} else if (header.major_type == MajorType::Map
) {
u8 size = header.short_count;
CBOR items[size*2];
}
}[[format_read("cbor_read")]];
fn cbor_read(CBOR c) {
if (std::core::has_member(c, "tiny_int")) {
return std::format("CBOR:{}={}", major_type_name(c.header.major_type), c.tiny_int);
}
if (std::core::has_member(c, "value")) {
return std::format("CBOR:{}={}", major_type_name(c.header.major_type), c.value);
}
return std::format("CBOR:{}", major_type_name(c.header.major_type));
};
fn major_type_name(MajorType mt) {
match(mt) {
(MajorType::PositiveInteger): return "PositiveInteger";
(MajorType::NegativeInteger): return "Integer";
(MajorType::Special): return "Special";
(MajorType::UTF8String): return "UTF8String";
(MajorType::ByteString): return "ByteString";
(MajorType::Array): return "Array";
(MajorType::Map): return "Map";
(MajorType::SemanticTag): return "SemanticTag";
}
};
CBOR cbor[while(!std::mem::eof())] @ 0x00;
大体パースしたいところまでできた気がする。
このスクラップは2024/01/17にクローズされました