Switch to ciborium since serde_cbor is unmaintained
This commit is contained in:
parent
9dd58d5769
commit
0189e4b5cb
|
@ -32,6 +32,33 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.0"
|
||||
|
@ -47,10 +74,9 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"base45",
|
||||
"ciborium",
|
||||
"flate2",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -110,15 +136,8 @@ name = "serde"
|
|||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -9,7 +9,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.41"
|
||||
base45 = "3.0.0"
|
||||
ciborium = "0.2.0"
|
||||
flate2 = "1.0.20"
|
||||
serde = "1.0.126"
|
||||
serde_cbor = "0.11.1"
|
||||
serde_derive = "1.0.126"
|
||||
|
|
152
src/eudcc.rs
152
src/eudcc.rs
|
@ -1,18 +1,24 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use base45;
|
||||
use ciborium::{de::from_reader, value::Value};
|
||||
use flate2::read::ZlibDecoder;
|
||||
use serde_cbor::value::from_value;
|
||||
use serde_cbor::{from_slice, Value};
|
||||
use serde_derive::Deserialize;
|
||||
use serde::de::{self, Deserializer, MapAccess, Visitor};
|
||||
use serde::Deserialize;
|
||||
|
||||
const CLAIM_KEY_DCCV1: usize = 1; // EU Digital Covid Certificate v1
|
||||
const CLAIM_KEY_EXPIRETION_TIME: i16 = 4;
|
||||
const CLAIM_KEY_HCERT: i16 = -260;
|
||||
const CLAIM_KEY_ISSUED_AT: i16 = 6;
|
||||
const CLAIM_KEY_ISSUER: i16 = 1;
|
||||
const COSE_SIGN1_TAG: u64 = 18;
|
||||
const HC1_FIELD: &str = "HC1:";
|
||||
const HCERT_CLAIM_KEY: i128 = -260;
|
||||
const DCC: i128 = 1;
|
||||
const PAYLOAD_POSITION: usize = 2;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
struct VaccineRecord {
|
||||
tg: String,
|
||||
vp: String,
|
||||
|
@ -26,7 +32,7 @@ struct VaccineRecord {
|
|||
ci: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
struct RecoveryRecord {
|
||||
tg: String,
|
||||
fr: String,
|
||||
|
@ -37,7 +43,7 @@ struct RecoveryRecord {
|
|||
ci: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
struct TestRecord {
|
||||
tg: String,
|
||||
tt: String,
|
||||
|
@ -55,7 +61,7 @@ struct TestRecord {
|
|||
ci: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
struct Name {
|
||||
#[serde(rename = "fn")]
|
||||
fn_: String,
|
||||
|
@ -64,7 +70,7 @@ struct Name {
|
|||
gnt: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct Certificate {
|
||||
ver: String,
|
||||
nam: Name,
|
||||
|
@ -77,6 +83,98 @@ pub struct Certificate {
|
|||
t: Vec<TestRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Payload {
|
||||
pub expires_at: u64,
|
||||
pub issued_at: u64,
|
||||
pub issuer: String,
|
||||
pub certs: HashMap<usize, Certificate>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Payload {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct PayloadVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PayloadVisitor {
|
||||
type Value = Payload;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("struct Payload")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Payload, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut issued_at = None;
|
||||
let mut issuer = None;
|
||||
let mut expires_at = None;
|
||||
let mut certs = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
CLAIM_KEY_ISSUER => {
|
||||
if issuer.is_some() {
|
||||
return Err(de::Error::duplicate_field(
|
||||
"issuer",
|
||||
));
|
||||
}
|
||||
issuer = Some(map.next_value()?);
|
||||
}
|
||||
CLAIM_KEY_ISSUED_AT => {
|
||||
if issued_at.is_some() {
|
||||
return Err(de::Error::duplicate_field(
|
||||
"issued_at",
|
||||
));
|
||||
}
|
||||
issued_at = Some(map.next_value()?);
|
||||
}
|
||||
CLAIM_KEY_EXPIRETION_TIME => {
|
||||
if expires_at.is_some() {
|
||||
return Err(de::Error::duplicate_field(
|
||||
"expires_at",
|
||||
));
|
||||
}
|
||||
expires_at = Some(map.next_value()?);
|
||||
}
|
||||
CLAIM_KEY_HCERT => {
|
||||
if certs.is_some() {
|
||||
return Err(de::Error::duplicate_field(
|
||||
"certs",
|
||||
));
|
||||
}
|
||||
certs = Some(map.next_value()?);
|
||||
}
|
||||
_ => {
|
||||
// Ignore the rest.
|
||||
}
|
||||
}
|
||||
}
|
||||
let issuer =
|
||||
issuer.ok_or_else(|| de::Error::missing_field("issuer"))?;
|
||||
let issued_at = issued_at
|
||||
.ok_or_else(|| de::Error::missing_field("issued_at"))?;
|
||||
let expires_at = expires_at
|
||||
.ok_or_else(|| de::Error::missing_field("expire_at"))?;
|
||||
let certs =
|
||||
certs.ok_or_else(|| de::Error::missing_field("certs"))?;
|
||||
Ok(Payload {
|
||||
issuer,
|
||||
issued_at,
|
||||
expires_at,
|
||||
certs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &'static [&'static str] = &["issuer", "issued_at"];
|
||||
deserializer.deserialize_struct("Payload", FIELDS, PayloadVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(data: String) -> Result<Certificate> {
|
||||
let data = data.trim_end().strip_prefix(HC1_FIELD);
|
||||
|
||||
|
@ -91,25 +189,23 @@ pub fn decode(data: String) -> Result<Certificate> {
|
|||
let mut cbor_data = Vec::new();
|
||||
zlibdecoder.read_to_end(&mut cbor_data)?;
|
||||
|
||||
let (_header1, _header2, payload, _signature): (
|
||||
&[u8],
|
||||
BTreeMap<String, Value>,
|
||||
&[u8],
|
||||
&[u8],
|
||||
) = from_slice(&cbor_data)?;
|
||||
let payload: Value = from_slice(&payload)?;
|
||||
|
||||
if let Value::Map(m) = payload {
|
||||
if let Some(health_certificate) =
|
||||
m.get(&Value::Integer(HCERT_CLAIM_KEY))
|
||||
{
|
||||
if let Value::Map(m) = health_certificate {
|
||||
if let Some(eudccv1) = m.get(&Value::Integer(DCC)) {
|
||||
let cert: Certificate = from_value(eudccv1.clone())?;
|
||||
return Ok(cert);
|
||||
}
|
||||
if let Value::Tag(COSE_SIGN1_TAG, content) =
|
||||
ciborium::de::from_reader(&cbor_data[..])?
|
||||
{
|
||||
if let Value::Array(arr) = *content {
|
||||
// We have 4 part of a CBOR Web Token:
|
||||
// 1. protected header;
|
||||
// 2. unprotected header;
|
||||
// 3. payload;
|
||||
// 4. signature.
|
||||
if let Value::Bytes(p) = &arr[PAYLOAD_POSITION] {
|
||||
let p: Payload = from_reader(&p[..])?;
|
||||
let cert = p.certs[&CLAIM_KEY_DCCV1].clone();
|
||||
return Ok(cert);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!("Not a COSE Single Signer Data Object Tag!")
|
||||
}
|
||||
|
||||
bail!("Can't decode the EU Digital COVID Certificate payload!");
|
||||
|
|
Loading…
Reference in New Issue