Skip to main content

gix_object/signature/
mod.rs

1//! Discover, sign, and verify Git object signatures.
2
3use std::ops::Range;
4
5use bstr::{BStr, BString, ByteSlice};
6
7/// Object signing with external-program options.
8#[cfg(feature = "signature")]
9pub mod sign;
10/// Object signature verification with external-program options.
11#[cfg(feature = "signature")]
12pub mod verify;
13
14/// A borrowed armored signature and its detected format.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct SignatureRef<'a> {
17    /// The signature format detected from the armor marker.
18    pub format: Format,
19    /// The complete armored signature, from its marker through end of object.
20    pub data: &'a BStr,
21}
22
23/// Exact object bytes covered by a [signature](SignatureRef).
24#[derive(PartialEq, Eq, Debug, Hash, Clone)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct SignedData<'a> {
27    data: &'a [u8],
28    excluded: Range<usize>,
29}
30
31impl<'a> SignedData<'a> {
32    pub(crate) fn new(data: &'a [u8], excluded: Range<usize>) -> Self {
33        SignedData { data, excluded }
34    }
35
36    pub(crate) fn segments(&self) -> [&[u8]; 2] {
37        [&self.data[..self.excluded.start], &self.data[self.excluded.end..]]
38    }
39
40    /// Return an exact copy of the bytes covered by the signature.
41    pub fn to_bstring(&self) -> BString {
42        let [before, after] = self.segments();
43        let mut out = BString::from(before);
44        out.extend_from_slice(after);
45        out
46    }
47}
48
49impl From<SignedData<'_>> for BString {
50    fn from(value: SignedData<'_>) -> Self {
51        value.to_bstring()
52    }
53}
54
55/// Find the last supported armor marker at a line boundary, matching Git's `parse_signed_buffer()`.
56pub(crate) fn find(data: &[u8]) -> Option<(usize, Format)> {
57    let mut found = None;
58    let mut offset = 0;
59    while offset < data.len() {
60        if let Some(format) = Format::from_signature(&data[offset..]) {
61            found = Some((offset, format));
62        }
63        offset = data[offset..]
64            .find_byte(b'\n')
65            .map_or(data.len(), |newline| offset + newline + 1);
66    }
67    found
68}
69
70/// A Git-supported signature format.
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum Format {
73    /// An OpenPGP signature made with `gpg` by default.
74    OpenPgp,
75    /// An X.509 signature made with `gpgsm` by default.
76    X509,
77    /// An SSH signature made with `ssh-keygen` by default.
78    Ssh,
79}
80
81impl Format {
82    /// Detect the format from the signature's armor header, or return `None` if it is unsupported.
83    pub fn from_signature(signature: &[u8]) -> Option<Self> {
84        if signature.starts_with(b"-----BEGIN PGP SIGNATURE-----")
85            || signature.starts_with(b"-----BEGIN PGP MESSAGE-----")
86        {
87            Some(Format::OpenPgp)
88        } else if signature.starts_with(b"-----BEGIN SIGNED MESSAGE-----") {
89            Some(Format::X509)
90        } else if signature.starts_with(b"-----BEGIN SSH SIGNATURE-----") {
91            Some(Format::Ssh)
92        } else {
93            None
94        }
95    }
96}