1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use super::CoseDecodeError;
use base64::Engine;
use serde::{de::DeserializeOwned, Serialize};
use ssi_claims_core::{ClaimsValidity, SignatureError, ValidateClaims};
use ssi_cose::{CosePayload, CoseSign1Bytes, CoseSigner, DecodedCoseSign1, ValidateCoseHeader};
use ssi_json_ld::{iref::Uri, syntax::Context};
use ssi_vc::{
    enveloped::{EnvelopedVerifiableCredential, EnvelopedVerifiablePresentation},
    v2::{syntax::JsonPresentation, Presentation, PresentationTypes},
    MaybeIdentified,
};
use std::borrow::Cow;

/// Payload of a COSE-secured Verifiable Presentation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CoseVp<T = JsonPresentation<EnvelopedVerifiableCredential>>(pub T);

impl<T: Serialize> CosePayload for CoseVp<T> {
    fn typ(&self) -> Option<ssi_cose::CosePayloadType> {
        Some(ssi_cose::CosePayloadType::Text(
            "application/vp-ld+cose".to_owned(),
        ))
    }

    fn content_type(&self) -> Option<ssi_cose::ContentType> {
        Some(ssi_cose::ContentType::Text("application/vp".to_owned()))
    }

    fn payload_bytes(&self) -> Cow<[u8]> {
        Cow::Owned(serde_json::to_vec(&self.0).unwrap())
    }
}

impl<E, T> ValidateCoseHeader<E> for CoseVp<T> {
    fn validate_cose_headers(
        &self,
        _params: &E,
        _protected: &ssi_cose::ProtectedHeader,
        _unprotected: &ssi_cose::Header,
    ) -> ClaimsValidity {
        Ok(())
    }
}

impl<T: Serialize> CoseVp<T> {
    /// Sign a COSE VP into an enveloped verifiable presentation.
    pub async fn sign_into_enveloped(
        &self,
        signer: &impl CoseSigner,
    ) -> Result<EnvelopedVerifiablePresentation, SignatureError> {
        let cose = CosePayload::sign(self, signer, true).await?;
        let base64_cose = base64::prelude::BASE64_STANDARD.encode(&cose);
        Ok(EnvelopedVerifiablePresentation {
            context: Context::iri_ref(ssi_vc::v2::CREDENTIALS_V2_CONTEXT_IRI.to_owned().into()),
            id: format!("data:application/vp-ld+cose;base64,{base64_cose}")
                .parse()
                .unwrap(),
        })
    }
}

impl<T: DeserializeOwned> CoseVp<T> {
    /// Decode a JOSE VP.
    pub fn decode(
        cose: &CoseSign1Bytes,
        tagged: bool,
    ) -> Result<DecodedCoseSign1<Self>, CoseDecodeError> {
        cose.decode(tagged)?
            .try_map(|_, payload| serde_json::from_slice(payload).map(Self))
            .map_err(Into::into)
    }
}

impl CoseVp {
    /// Decode a JOSE VP with an arbitrary presentation type.
    pub fn decode_any(
        jws: &CoseSign1Bytes,
        tagged: bool,
    ) -> Result<DecodedCoseSign1<Self>, CoseDecodeError> {
        Self::decode(jws, tagged)
    }
}

impl<T: MaybeIdentified> MaybeIdentified for CoseVp<T> {
    fn id(&self) -> Option<&ssi_json_ld::iref::Uri> {
        self.0.id()
    }
}

impl<T: Presentation> Presentation for CoseVp<T> {
    type Credential = T::Credential;
    type Holder = T::Holder;

    fn id(&self) -> Option<&Uri> {
        Presentation::id(&self.0)
    }

    fn additional_types(&self) -> &[String] {
        self.0.additional_types()
    }

    fn types(&self) -> PresentationTypes {
        self.0.types()
    }

    fn verifiable_credentials(&self) -> &[Self::Credential] {
        self.0.verifiable_credentials()
    }

    fn holders(&self) -> &[Self::Holder] {
        self.0.holders()
    }
}

impl<E, P, T: ValidateClaims<E, P>> ValidateClaims<E, P> for CoseVp<T> {
    fn validate_claims(&self, environment: &E, proof: &P) -> ClaimsValidity {
        self.0.validate_claims(environment, proof)
    }
}

#[cfg(test)]
mod tests {
    use super::CoseVp;
    use serde_json::json;
    use ssi_claims_core::VerificationParameters;
    use ssi_cose::{key::CoseKeyGenerate, CoseKey, CoseSign1Bytes, CoseSign1BytesBuf};
    use ssi_vc::{enveloped::EnvelopedVerifiableCredential, v2::syntax::JsonPresentation};

    async fn verify(input: &CoseSign1Bytes, key: &CoseKey) {
        let vp = CoseVp::decode_any(input, true).unwrap();
        let params = VerificationParameters::from_resolver(key);
        let result = vp.verify(params).await.unwrap();
        assert_eq!(result, Ok(()))
    }

    #[async_std::test]
    async fn cose_vp_roundtrip() {
        let vp: JsonPresentation<EnvelopedVerifiableCredential> = serde_json::from_value(json!({
            "@context": [
                "https://www.w3.org/ns/credentials/v2",
                "https://www.w3.org/ns/credentials/examples/v2"
            ],
            "type": "VerifiablePresentation",
            "verifiableCredential": [{
                "@context": ["https://www.w3.org/ns/credentials/v2"],
                "type": ["EnvelopedVerifiableCredential"],
                "id": "data:application/vc-ld+jwt,eyJraWQiOiJFeEhrQk1XOWZtYmt2VjI2Nm1ScHVQMnNVWV9OX0VXSU4xbGFwVXpPOHJvIiwiYWxnIjoiRVMzODQifQ.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvdjIiLCJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjIiXSwiaWQiOiJodHRwOi8vdW5pdmVyc2l0eS5leGFtcGxlL2NyZWRlbnRpYWxzLzE4NzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRXhhbXBsZUFsdW1uaUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly91bml2ZXJzaXR5LmV4YW1wbGUvaXNzdWVycy81NjUwNDkiLCJ2YWxpZEZyb20iOiIyMDEwLTAxLTAxVDE5OjIzOjI0WiIsImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2V4YW1wbGUub3JnL2V4YW1wbGVzL2RlZ3JlZS5qc29uIiwidHlwZSI6Ikpzb25TY2hlbWEifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZToxMjMiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19.d2k4O3FytQJf83kLh-HsXuPvh6yeOlhJELVo5TF71gu7elslQyOf2ZItAXrtbXF4Kz9WivNdztOayz4VUQ0Mwa8yCDZkP9B2pH-9S_tcAFxeoeJ6Z4XnFuL_DOfkR1fP"
            }]
        })).unwrap();

        let key = CoseKey::generate_p256();
        let enveloped = CoseVp(vp).sign_into_enveloped(&key).await.unwrap();
        let jws = CoseSign1BytesBuf::new(enveloped.id.decoded_data().unwrap().into_owned());
        verify(&jws, &key).await
    }

    // NOTE: the example is incorrect because of the invalid data URL.
    // #[test]
    // fn example8() {
    //     let input_hex = "d28444a1013822a05908d67b2240636f6e74657874223a5b2268747470733a2f2f7777772e77332e6f72672f6e732f63726564656e7469616c732f7632222c2268747470733a2f2f7777772e77332e6f72672f6e732f63726564656e7469616c732f6578616d706c65732f7632225d2c2274797065223a2256657269666961626c6550726573656e746174696f6e222c2276657269666961626c6543726564656e7469616c223a5b7b2240636f6e74657874223a2268747470733a2f2f7777772e77332e6f72672f6e732f63726564656e7469616c732f7632222c226964223a22646174613a6170706c69636174696f6e2f76632d6c642b73642d6a77743b65794a68624763694f694a46557a4d344e434973496d74705a434936496c565254563966626c4530557a5a43547a68755554527554303559654842346148526f62336c4f654749314d30785a5a316c364c544a42516e4d694c434a30655841694f694a32634374735a437471633239754b334e6b4c57703364434973496d4e3065534936496e5a774b32786b4b32707a6232346966512e65794a4159323975644756346443493657794a6f64485277637a6f764c336433647935334d793576636d6376626e4d7659334a6c5a47567564476c6862484d76646a49694c434a6f64485277637a6f764c336433647935334d793576636d6376626e4d7659334a6c5a47567564476c6862484d765a586868625842735a584d76646a496958537769646d567961575a7059574a735a554e795a57526c626e5270595777694f6c7437496b426a623235305a586830496a7062496d68306448427a4f693876643364334c6e637a4c6d39795a7939756379396a636d566b5a57353061574673637939324d694973496d68306448427a4f693876643364334c6e637a4c6d39795a7939756379396a636d566b5a573530615746736379396c654746746347786c637939324d694a644c434a7063334e315a5849694f694a6f64485277637a6f764c33567561585a6c636e4e7064486b755a586868625842735a53397063334e315a584a7a4c7a55324e5441304f534973496e5a6862476c6b526e4a7662534936496a49774d5441744d4445744d4446554d546b364d6a4d364d6a52614969776959334a6c5a47567564476c6862464e31596d706c593351694f6e73695957783162573570543259694f6e7369626d46745a534936496b5634595731776247556756573570646d567963326c3065534973496c397a5a43493657794a6f656b394c527a55326344493563314279544746444e554534526e64466455637a5655303564556c5a5531703163553959637a4a6c56474a42496c31394c434a66633251694f6c736957566458566d5644526e6478516d6b34574442715346396a56304e575755313653544e684f48426a5445565952575a6963464e5351566c6e64794a646653776958334e6b496a7062496a4a4a5a6a68686155733452455a7756574a346445633163474d77656c395361464a7a626d3179624746524d45687a63546b3457464e79595773694c434a5565445a345a575a4d565564555a557066595774565546644765484e766255686f62477457566e70664e7a566f61565a3665577079596d567a496c31395853776958334e6b496a7062496a6432616e6c3056564e335a454a304d585135526b746c4f56466653334a49525868465747787254454661547a424b4d304a7064323030646c6b695853776958334e6b583246735a794936496e4e6f595330794e5459694c434a70595851694f6a45334d4459314e6a49344e446b73496d5634634349364d54637a4f4445344e5449304f5377695932356d496a7037496d70336179493665794a7264486b694f694a4651794973496d4e7964694936496c41744d7a6730496977695957786e496a6f6952564d7a4f4451694c434a34496a6f6964577445643155325a7a6c51555652465557685961456779636b525a4e6e644d516c67335548466c556a5a4263476c685648424555586f77636c387464446c3655584e78656d35345a3068456345356f656b5a6c51794973496e6b694f694a4d516e6856596e425664464e474d56564b56545670596e4a49646b70494e6a4255534735594d6b3178613078485a476c7455316c3055475234526c6b784f456468636c64695333465a5630646a556b5a4856453942496e313966512e6b594436335974424e596e4c55547736537a663176735f556733554258685077437971704e6d506e5044613372585a5168514c6442314267616f4f387a67512d6333423431667861584d6e4c485956392d42323075626f53704a5030422d325672653931376551743163534473774447415f5974766e3442537159564242324a7e57794a464d6b4673527a68735932703051564672636c6c49626a6c49626e565249697767496e5235634755694c434169566d567961575a7059574a735a5642795a584e6c626e526864476c7662694a647e577949354e6c64594d44526e656e6f3463565a7a4f565a4c553277775954566e49697767496d6c6b49697767496d6830644841364c793931626d6c325a584a7a615852354c6d5634595731776247557659334a6c5a47567564476c6862484d764d5467334d694a647e57794a61656b553256465661616d74484d5731445758424b4d45686e63306c3349697767496e5235634755694c434262496c5a6c636d6c6d6157466962475644636d566b5a5735306157467349697767496b5634595731776247564262485674626d6c44636d566b5a57353061574673496c31647e5779497451334e73533235475a47465962324a695157737955304a425647523349697767496d6c6b49697767496d52705a44706c654746746347786c4f6d56695a6d56694d5759334d544a6c596d4d325a6a466a4d6a63325a5445795a574d794d534a647e57794a75526d314f576c3949637a423357574e6f4f46646b6554646e51554e5249697767496d6c6b49697767496d52705a44706c654746746347786c4f6d4d794e7a5a6c4d544a6c597a49785a574a6d5a5749785a6a63784d6d5669597a5a6d4d534a64222c2274797065223a22456e76656c6f70656456657269666961626c6543726564656e7469616c227d5d7d5840710a23eba256305aaadd73655219bc38acf04714ce3310823f3ad2288a58f9b40e8764cbe28fe20a60e415e63fba71f352160dd2c812c2dd794cd67f420999fd";
    //     let input = CoseSign1BytesBuf::new(hex::decode(input_hex).unwrap());
    //     let _ = CoseVp::decode_any(&input, true).unwrap();
    // }
}