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
// Copyright 2021 Heiko Schaefer <heiko@schaefer.name>
//
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: GPL-3.0-or-later

use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};

/// Data structure for an OpenPGP Keylist, as specified in
/// https://www.ietf.org/archive/id/draft-mccain-keylist-05.txt
#[derive(Debug, Serialize, Deserialize)]
pub struct Keylist {
    pub metadata: Metadata,
    pub keys: Vec<Key>,
}

impl Keylist {
    /// Create a Keylist with metadata set, but no Keys
    pub fn new(metadata: Metadata) -> Self {
        Self { metadata, keys: Vec::new() }
    }

    /// Transform this Keylist into a SignedKeylist
    pub fn sign<'a>(
        self,
        sign: Box<dyn Fn(&str) -> Result<String> + 'a>,
    ) -> Result<SignedKeylist> {
        let keylist = serde_json::to_string(&self)?;
        let sig = sign(&keylist)?;

        Ok(SignedKeylist { keylist, sig })
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
    // e.g. "https://www.example.com/keylist.json.asc"
    pub signature_uri: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyserver: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Key {
    pub fingerprint: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyserver: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
}

pub struct SignedKeylist {
    pub keylist: String,
    pub sig: String,
}

impl SignedKeylist {
    /// If the SignedKeylist validates (according to `verify_sig`), this
    /// function returns a Keylist (which gives access to the individual
    /// fields of the Keylist).
    ///
    /// `verify_sig` takes a message and a detached signature and checks
    /// if the signature is valid (the certificate that is used to
    /// validate has to be embedded in the Fn - no key store is
    /// used for this test)
    pub fn verify(
        &self,
        verify_sig: Box<dyn FnOnce(&[u8], &str) -> Result<bool>>,
    ) -> Result<Keylist> {
        let verify: Result<bool> =
            verify_sig(self.keylist.as_bytes(), &self.sig);

        if verify.is_ok() && *verify.as_ref().unwrap() {
            Ok(serde_json::from_str(&self.keylist)
                .map_err(|e| anyhow!(e))?)
        } else {
            Err(anyhow!(
                "Signature verification failed {:?}",
                verify
            ))
        }
    }

    /// Retrieve a Keylist from the provided url.
    /// This implicitly also loads the signature_uri from within that Keylist.
    ///
    /// The resulting SignedKeylist needs to be verified as a next step,
    /// before its data can be used.
    pub fn from_url(url: String) -> Result<Self> {
        let keylist = reqwest::blocking::get(&url)?.text()?;

        let kl: Keylist = serde_json::from_str(&keylist)
            .map_err(|e| anyhow!(e))?;

        let sig = reqwest::blocking::get(&kl.metadata.signature_uri)?.text()?;

        Ok(SignedKeylist { keylist, sig })
    }
}