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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use core::str::FromStr;

use sqlx::*;
use tracing::debug;

use ockam_core::async_trait;
use ockam_core::compat::string::{String, ToString};
use ockam_core::compat::sync::Arc;
use ockam_core::compat::vec::Vec;
use ockam_core::errcode::{Kind, Origin};
use ockam_core::Result;
use ockam_node::database::{FromSqlxError, SqlxDatabase, SqlxType, ToSqlxType, ToVoid};

use crate::identity::IdentityConstants;
use crate::models::{Identifier, PurposeKeyAttestation};
use crate::purpose_keys::storage::PurposeKeysRepository;
use crate::Purpose;

/// Storage for own [`super::super::super::purpose_key::PurposeKey`]s
#[derive(Clone)]
pub struct PurposeKeysSqlxDatabase {
    database: Arc<SqlxDatabase>,
}

impl PurposeKeysSqlxDatabase {
    /// Create a new database for purpose keys
    pub fn new(database: Arc<SqlxDatabase>) -> Self {
        debug!("create a repository for purpose keys");
        Self { database }
    }

    /// Create a new in-memory database for purpose keys
    pub async fn create() -> Result<Arc<Self>> {
        Ok(Arc::new(Self::new(
            SqlxDatabase::in_memory("purpose keys").await?,
        )))
    }
}

#[async_trait]
impl PurposeKeysRepository for PurposeKeysSqlxDatabase {
    async fn set_purpose_key(
        &self,
        subject: &Identifier,
        purpose: Purpose,
        purpose_key_attestation: &PurposeKeyAttestation,
    ) -> Result<()> {
        let query = query("INSERT OR REPLACE INTO purpose_key VALUES (?, ?, ?)")
            .bind(subject.to_sql())
            .bind(purpose.to_sql())
            .bind(minicbor::to_vec(purpose_key_attestation)?.to_sql());
        query.execute(&self.database.pool).await.void()
    }

    async fn delete_purpose_key(&self, subject: &Identifier, purpose: Purpose) -> Result<()> {
        let query = query("DELETE FROM purpose_key WHERE identifier = ? and purpose = ?")
            .bind(subject.to_sql())
            .bind(purpose.to_sql());
        query.execute(&self.database.pool).await.void()
    }

    async fn get_purpose_key(
        &self,
        identifier: &Identifier,
        purpose: Purpose,
    ) -> Result<Option<PurposeKeyAttestation>> {
        let query = query_as("SELECT identifier, purpose, purpose_key_attestation FROM purpose_key WHERE identifier=$1 and purpose=$2")
            .bind(identifier.to_sql())
            .bind(purpose.to_sql());
        let row: Option<PurposeKeyRow> = query
            .fetch_optional(&self.database.pool)
            .await
            .into_core()?;
        Ok(row.map(|r| r.purpose_key_attestation()).transpose()?)
    }
}

// Database serialization / deserialization

#[derive(FromRow)]
pub(crate) struct PurposeKeyRow {
    // The identifier who is using this key
    identifier: String,
    // Purpose of the key (signing, encrypting, etc...)
    purpose: String,
    // Attestation that this key is valid
    purpose_key_attestation: Vec<u8>,
}

impl PurposeKeyRow {
    #[allow(dead_code)]
    pub(crate) fn identifier(&self) -> Result<Identifier> {
        Identifier::from_str(&self.identifier)
    }

    #[allow(dead_code)]
    pub(crate) fn purpose(&self) -> Result<Purpose> {
        match self.purpose.as_str() {
            IdentityConstants::SECURE_CHANNEL_PURPOSE_KEY => Ok(Purpose::SecureChannel),
            IdentityConstants::CREDENTIALS_PURPOSE_KEY => Ok(Purpose::Credentials),
            _ => Err(ockam_core::Error::new(
                Origin::Api,
                Kind::Serialization,
                format!("unknown purpose {}", self.purpose),
            )),
        }
    }

    pub(crate) fn purpose_key_attestation(&self) -> Result<PurposeKeyAttestation> {
        Ok(minicbor::decode(self.purpose_key_attestation.as_slice())?)
    }
}

impl ToSqlxType for Purpose {
    fn to_sql(&self) -> SqlxType {
        match self {
            Purpose::SecureChannel => {
                SqlxType::Text(IdentityConstants::SECURE_CHANNEL_PURPOSE_KEY.to_string())
            }
            Purpose::Credentials => {
                SqlxType::Text(IdentityConstants::CREDENTIALS_PURPOSE_KEY.to_string())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::identities;
    use crate::models::PurposeKeyAttestationSignature;
    use ockam_vault::ECDSASHA256CurveP256Signature;

    #[tokio::test]
    async fn test_purpose_keys_repository() -> Result<()> {
        let repository = create_repository().await?;

        // A purpose key can be stored and retrieved, given the owning identifier and purpose type
        let identity1 = create_identity().await?;
        let attestation1 = PurposeKeyAttestation {
            data: vec![1, 2, 3],
            signature: PurposeKeyAttestationSignature::ECDSASHA256CurveP256(
                ECDSASHA256CurveP256Signature([1; 64]),
            ),
        };
        repository
            .set_purpose_key(&identity1, Purpose::Credentials, &attestation1)
            .await?;

        let result = repository
            .get_purpose_key(&identity1, Purpose::Credentials)
            .await?;
        assert_eq!(result, Some(attestation1));

        // the attestation can be updated
        let attestation2 = PurposeKeyAttestation {
            data: vec![4, 5, 6],
            signature: PurposeKeyAttestationSignature::ECDSASHA256CurveP256(
                ECDSASHA256CurveP256Signature([1; 64]),
            ),
        };
        repository
            .set_purpose_key(&identity1, Purpose::Credentials, &attestation2)
            .await?;

        let result = repository
            .get_purpose_key(&identity1, Purpose::Credentials)
            .await?;
        assert_eq!(result, Some(attestation2));

        Ok(())
    }

    /// HELPERS
    async fn create_repository() -> Result<Arc<dyn PurposeKeysRepository>> {
        Ok(PurposeKeysSqlxDatabase::create().await?)
    }

    async fn create_identity() -> Result<Identifier> {
        let identities = identities().await?;
        identities.identities_creation().create_identity().await
    }
}