krill 0.16.0

Resource Public Key Infrastructure (RPKI) daemon
Documentation
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
//! Autonomous System Provider Authorization (ASPA).

use std::fmt;
use std::{collections::HashMap, fmt::Debug};
use rpki::{uri, rrdp};
use rpki::ca::idexchange::CaHandle;
use rpki::ca::publication::Base64;
use rpki::repository::aspa::{Aspa, AspaBuilder};
use rpki::repository::resources::ResourceSet;
use rpki::repository::sigobj::SignedObjectBuilder;
use rpki::repository::x509::{Serial, Time, Validity};
use serde::{Deserialize, Serialize};
use crate::api::aspa::{
    AspaDefinition, AspaDefinitionUpdates, AspaProvidersUpdate, CustomerAsn
};
use crate::api::ca::ObjectName;
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use crate::config::{Config, IssuanceTimingConfig};
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;


//------------ AspaDefinitions -----------------------------------------------

/// All ASPA definitions for a CA.
///
/// An [`AspaDefinition`] describes the intended authorization to be published
/// by a CA for a customer ASN number. There can be at most one definition per
/// customer ASN. The customer ASN will be held by a single resource class 
/// only, but at least in theory the CA could issue ASPA objects in each
/// resource class that holds the ASN.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaDefinitions {
    /// The definitions for each customer ASN.
    attestations: HashMap<CustomerAsn, AspaDefinition>,
}

impl AspaDefinitions {
    /// Adds or replaces a definition.
    pub fn add_or_replace(&mut self, aspa_def: AspaDefinition) {
        self.attestations.insert(aspa_def.customer, aspa_def);
    }

    /// Removes an definition for the given customer ASN.
    pub fn remove(&mut self, customer: CustomerAsn) {
        self.attestations.remove(&customer);
    }

    /// Applies an ASPA definitions update.
    ///
    /// Assumes that the update was verified beforehand.
    pub fn apply_update(
        &mut self,
        customer: CustomerAsn,
        update: &AspaProvidersUpdate,
    ) {
        if let Some(current) = self.attestations.get_mut(&customer) {
            current.apply_update(update);

            // If there are no remaining providers for this AspaDefinition,
            // then remove it so that its ASPA object will also be
            // removed.
            if current.providers.is_empty() {
                self.attestations.remove(&customer);
            }
        }
        else {
            // There was no AspaDefinition. So create an empty definition,
            // apply the update and then add it.
            let mut def = AspaDefinition { customer, providers: vec![] };
            def.apply_update(update);

            self.attestations.insert(customer, def);
        }
    }

    /// Returns an iterator over all ASPA definitions.
    pub fn iter(&self) -> impl Iterator<Item = &AspaDefinition> {
        self.attestations.values()
    }

    /// Proceses updates and returns events leading to the updated definitions.
    ///
    /// Returns an error if the update cannot be applied cleanly.
    pub fn process_updates(
        &self,
        handle: &CaHandle,
        all_resources: &ResourceSet,
        updates: AspaDefinitionUpdates,
    ) -> KrillResult<(Self, Vec<CertAuthEvent>)> {
        let mut events = Vec::new();

        // Keep track of a copy of the AspaDefinitions so we can use to update
        // ASPA objects
        let mut all_aspas = self.clone();

        for customer in updates.remove {
            if !all_aspas.has(customer) {
                return Err(Error::AspaCustomerUnknown(
                    handle.clone(),
                    customer,
                ));
            }
            events.push(CertAuthEvent::AspaConfigRemoved { customer });
            all_aspas.remove(customer);
        }

        for aspa_config in updates.add_or_replace {
            let customer = aspa_config.customer;
            if aspa_config.providers.is_empty() {
                return Err(Error::AspaProvidersEmpty(
                    handle.clone(),
                    customer,
                ));
            }

            if aspa_config.customer_used_as_provider() {
                return Err(Error::AspaCustomerAsProvider(
                    handle.clone(),
                    customer,
                ));
            }

            if aspa_config.contains_duplicate_providers() {
                return Err(Error::AspaProvidersDuplicates(
                    handle.clone(),
                    customer,
                ));
            }

            if !all_resources.contains_asn(customer) {
                return Err(Error::AspaCustomerAsNotEntitled(
                    handle.clone(),
                    customer,
                ));
            }

            // Update the aspas copy so we can update ASPA objects for the
            // events
            all_aspas.add_or_replace(aspa_config.clone());

            match self.get(customer) {
                None => {
                    events.push(
                        CertAuthEvent::AspaConfigAdded { aspa_config }
                    )
                }
                Some(existing) => {
                    // Determine the update from existing to (new) aspa_config
                    let added = aspa_config
                        .providers
                        .iter()
                        .filter(|new_provider| {
                            !existing.providers.contains(new_provider)
                        })
                        .copied()
                        .collect();

                    let removed = existing
                        .providers
                        .iter()
                        .filter(|existing| {
                            !aspa_config.providers.contains(existing)
                        })
                        .copied()
                        .collect();

                    let update = AspaProvidersUpdate { added, removed };

                    if !update.is_empty() {
                        events.push(CertAuthEvent::AspaConfigUpdated {
                            customer, update
                        })
                    }
                }
            }
        }

        Ok((all_aspas, events))
    }
}

/// # Set operations
impl AspaDefinitions {
    pub fn get(&self, customer: CustomerAsn) -> Option<&AspaDefinition> {
        self.attestations.get(&customer)
    }

    pub fn has(&self, customer: CustomerAsn) -> bool {
        self.attestations.contains_key(&customer)
    }

    pub fn is_empty(&self) -> bool {
        self.attestations.is_empty()
    }
}


//------------ AspaObjects ---------------------------------------------------

/// All ASPA objects held by a single resource class of a CA.
///
/// Each ASPA object is described by an [`AspaInfo`]. There can at most by
/// one ASPA object per customer ASN.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaObjects(HashMap<CustomerAsn, AspaInfo>);

impl AspaObjects {
    /// Returns whether there are no ASPA objects.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Returns the updates to the ASA objects based on configuration.
    ///
    /// The method takes all ASPA definitions for the CA but will only act
    /// on definitions for the resource class indicated by `certified_key`.
    ///
    /// It issues new ASPA objects for customer ASNs for which there are no
    /// objects yet and for those for which the definition has changed. It
    /// removes ASPA objects for those customer ASNs for which there are no
    /// longer any definitions.
    pub fn create_updates(
        &self,
        all_aspa_defs: &AspaDefinitions,
        certified_key: &CertifiedKey,
        config: &Config,
        signer: &KrillSigner,
    ) -> KrillResult<AspaObjectsUpdates> {
        let mut object_updates = AspaObjectsUpdates::default();
        let resources = &certified_key.incoming_cert().resources;

        // Issue new and updated ASPAs for definitions relevant to the
        // resources in scope
        for relevant_aspa in all_aspa_defs
            .iter()
            .filter(|aspa| resources.contains_asn(aspa.customer))
        {
            let need_to_issue = self
                .0
                .get(&relevant_aspa.customer)
                .map(|existing| existing.definition != *relevant_aspa)
                .unwrap_or(true);

            if need_to_issue {
                let aspa_info = self.make_aspa(
                    relevant_aspa.clone(),
                    certified_key,
                    &config.issuance_timing,
                    signer,
                )?;
                object_updates.updated.push(aspa_info);
            }
        }

        // Check if any currently held ASPA object needs to be removed
        for &customer in self.0.keys() {
            if !all_aspa_defs.has(customer)
                || !resources.contains_asn(customer)
            {
                // definition was removed, or it's overclaiming
                object_updates.removed.push(customer);
            }
        }

        Ok(object_updates)
    }

    /// Returns the ASPA objects that need to be renewed.
    ///
    /// If the renew_threshold is specified, then only objects which will
    /// expire before that time will be renewed. Otherwise, all 
    pub fn create_renewal(
        &self,
        certified_key: &CertifiedKey,
        renew_threshold: Option<Time>,
        issuance_timing: &IssuanceTimingConfig,
        signer: &KrillSigner,
    ) -> KrillResult<AspaObjectsUpdates> {
        let mut updates = AspaObjectsUpdates::default();

        for aspa in self.0.values() {
            let renew = renew_threshold
                .map(|threshold| aspa.expires() < threshold)
                .unwrap_or(true); // always renew if no threshold is specified

            if renew {
                let aspa_definition = aspa.definition.clone();

                let new_aspa = self.make_aspa(
                    aspa_definition,
                    certified_key,
                    issuance_timing,
                    signer,
                )?;
                updates.updated.push(new_aspa);
            }
        }

        Ok(updates)
    }

    /// Creates a new signed ASPA object.
    fn make_aspa(
        &self,
        aspa_def: AspaDefinition,
        certified_key: &CertifiedKey,
        issuance_timing: &IssuanceTimingConfig,
        signer: &KrillSigner,
    ) -> KrillResult<AspaInfo> {
        let name = ObjectName::from(&aspa_def);

        let aspa_builder = AspaBuilder::new(
            aspa_def.customer,
            aspa_def.providers.clone(),
        ).map_err(|e| {
            Error::Custom(format!("Cannot use aspa config: {e}"))
        })?;

        let object_builder = {
            let mut object_builder = SignedObjectBuilder::new(
                signer.random_serial()?,
                issuance_timing.new_aspa_validity(),
                certified_key.incoming_cert().crl_uri(),
                certified_key.incoming_cert().uri.clone(),
                certified_key.incoming_cert().uri_for_name(&name),
            );
            object_builder.set_issuer(
                Some(certified_key.incoming_cert().subject.clone()));
            object_builder.set_signing_time(Time::now());

            object_builder
        };

        let aspa = signer.sign_aspa(
            aspa_builder,
            object_builder,
            &certified_key.key_id(),
        )?;
        Ok(AspaInfo::new(aspa_def, aspa))
    }

    /// Applies the updates to the ASPA definitions.
    pub fn apply_updates(&mut self, updates: AspaObjectsUpdates) {
        for aspa_info in updates.updated {
            self.0.insert(aspa_info.customer(), aspa_info);
        }
        for customer in updates.removed {
            self.0.remove(&customer);
        }
    }
}


//------------ AspaInfo ----------------------------------------------------

/// Information about a single ASPA obejct.
///
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaInfo {
    /// The customer ASN and all Provider ASNs
    pub definition: AspaDefinition,

    /// The validity time for this ASPA.
    pub validity: Validity,

    /// The serial number (needed for revocation)
    pub serial: Serial,

    /// The URI where this object is expected to be published
    pub uri: uri::Rsync,

    /// The encoded ASPA object.
    pub base64: Base64,

    /// The RRDP hash of the encoded ASPA object.
    pub hash: rrdp::Hash,
}

impl AspaInfo {
    /// Creates a new value from an ASPA definition and the ASPA.
    pub fn new(definition: AspaDefinition, aspa: Aspa) -> Self {
        let validity = aspa.cert().validity();
        let serial = aspa.cert().serial_number();
        // unwrapping is safe for our own objects
        let uri = aspa.cert().signed_object().unwrap().clone(); 
        let base64 = Base64::from(&aspa);
        let hash = base64.to_hash();

        AspaInfo {
            definition,
            validity,
            serial,
            uri,
            base64,
            hash,
        }
    }

    /// Returns the customer ASN of the ASPA object.
    pub fn customer(&self) -> CustomerAsn {
        self.definition.customer
    }

    /// Returns when the ASPA object expires.
    pub fn expires(&self) -> Time {
        self.validity.not_after()
    }
}


//------------ AspaObjectsUpdates --------------------------------------------

/// The updates to the ASPA objects of a resource class.
//
//  *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaObjectsUpdates {
    /// Newly added or updated ASPA objects.
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    updated: Vec<AspaInfo>,

    /// Customer ASNs of the ASPA objects to be removed.
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    removed: Vec<CustomerAsn>,
}

impl AspaObjectsUpdates {
    /// Returns whether the updates object is empty.
    pub fn is_empty(&self) -> bool {
        self.updated.is_empty() && self.removed.is_empty()
    }

    /// Returns the updated objects.
    pub fn updated(&self) -> &[AspaInfo] {
        &self.updated
    }

    /// Returns the removed objects.
    pub fn removed(&self) -> &[CustomerAsn] {
        &self.removed
    }
}

impl fmt::Display for AspaObjectsUpdates {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if !self.updated.is_empty() {
            write!(f, " updated:")?;
            for upd in &self.updated {
                write!(
                    f, " {}",
                    ObjectName::aspa_from_customer(upd.customer())
                )?;
            }
        }
        if !self.removed.is_empty() {
            write!(f, " removed:")?;
            for rem in &self.removed {
                write!(f,
                    " {}",
                    ObjectName::aspa_from_customer(*rem)
                )?;
            }
        }
        Ok(())
    }
}