ic_agent/identity/
basic.rs

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
use crate::{agent::EnvelopeContent, export::Principal, Identity, Signature};

#[cfg(feature = "pem")]
use crate::identity::error::PemError;

use ed25519_consensus::SigningKey;
use simple_asn1::{
    oid, to_der,
    ASN1Block::{BitString, ObjectIdentifier, Sequence},
};

use std::fmt;

use super::Delegation;

/// A cryptographic identity which signs using an Ed25519 key pair.
///
/// The caller will be represented via [`Principal::self_authenticating`], which contains the SHA-224 hash of the public key.
pub struct BasicIdentity {
    private_key: KeyCompat,
    der_encoded_public_key: Vec<u8>,
}

impl fmt::Debug for BasicIdentity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BasicIdentity")
            .field("der_encoded_public_key", &self.der_encoded_public_key)
            .finish_non_exhaustive()
    }
}

impl BasicIdentity {
    /// Create a `BasicIdentity` from reading a PEM file at the path.
    #[cfg(feature = "pem")]
    pub fn from_pem_file<P: AsRef<std::path::Path>>(file_path: P) -> Result<Self, PemError> {
        Self::from_pem(std::fs::File::open(file_path)?)
    }

    /// Create a `BasicIdentity` from reading a PEM File from a Reader.
    #[cfg(feature = "pem")]
    pub fn from_pem<R: std::io::Read>(pem_reader: R) -> Result<Self, PemError> {
        use der::{asn1::OctetString, Decode, ErrorKind, SliceReader, Tag, TagNumber};
        use pkcs8::PrivateKeyInfo;

        let bytes: Vec<u8> = pem_reader.bytes().collect::<Result<_, _>>()?;
        let pem = pem::parse(bytes)?;
        let pki_res = PrivateKeyInfo::decode(&mut SliceReader::new(pem.contents())?);
        let mut truncated;
        let pki = match pki_res {
            Ok(pki) => pki,
            Err(e) => {
                if e.kind()
                    == (ErrorKind::Noncanonical {
                        tag: Tag::ContextSpecific {
                            constructed: true,
                            number: TagNumber::new(1),
                        },
                    })
                {
                    // Very old versions of dfx generated nonconforming containers. They can only be imported if the extra data is removed.
                    truncated = pem.into_contents();
                    if truncated[48..52] != *b"\xA1\x23\x03\x21" {
                        return Err(e.into());
                    }
                    // hatchet surgery
                    truncated.truncate(48);
                    truncated[1] = 46;
                    truncated[4] = 0;
                    PrivateKeyInfo::decode(&mut SliceReader::new(&truncated)?).map_err(|_| e)?
                } else {
                    return Err(e.into());
                }
            }
        };
        let decoded_key = OctetString::from_der(pki.private_key)?; // ed25519 uses an octet string within another octet string
        let private_key = SigningKey::try_from(decoded_key.as_bytes())?;
        Ok(BasicIdentity::from_signing_key(private_key))
    }

    /// Create a `BasicIdentity` from a `SigningKey` from `ed25519-consensus`.
    pub fn from_signing_key(key: SigningKey) -> Self {
        let public_key = key.verification_key();
        let der_encoded_public_key = der_encode_public_key(public_key.as_bytes().to_vec());

        Self {
            private_key: KeyCompat::Standard(key),
            der_encoded_public_key,
        }
    }

    /// Create a `BasicIdentity` from an `Ed25519KeyPair` from `ring`.
    #[cfg(feature = "ring")]
    pub fn from_key_pair(key_pair: ring::signature::Ed25519KeyPair) -> Self {
        use ring::signature::KeyPair;
        let der_encoded_public_key = der_encode_public_key(key_pair.public_key().as_ref().to_vec());
        Self {
            private_key: KeyCompat::Ring(key_pair),
            der_encoded_public_key,
        }
    }
}

enum KeyCompat {
    Standard(SigningKey),
    #[cfg(feature = "ring")]
    Ring(ring::signature::Ed25519KeyPair),
}

impl KeyCompat {
    fn sign(&self, payload: &[u8]) -> Vec<u8> {
        match self {
            Self::Standard(k) => k.sign(payload).to_bytes().to_vec(),
            #[cfg(feature = "ring")]
            Self::Ring(k) => k.sign(payload).as_ref().to_vec(),
        }
    }
}

impl Identity for BasicIdentity {
    fn sender(&self) -> Result<Principal, String> {
        Ok(Principal::self_authenticating(&self.der_encoded_public_key))
    }

    fn public_key(&self) -> Option<Vec<u8>> {
        Some(self.der_encoded_public_key.clone())
    }

    fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
        self.sign_arbitrary(&content.to_request_id().signable())
    }

    fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
        self.sign_arbitrary(&content.signable())
    }

    fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
        let signature = self.private_key.sign(content);
        Ok(Signature {
            signature: Some(signature),
            public_key: self.public_key(),
            delegations: None,
        })
    }
}

fn der_encode_public_key(public_key: Vec<u8>) -> Vec<u8> {
    // see Section 4 "SubjectPublicKeyInfo" in https://tools.ietf.org/html/rfc8410

    let id_ed25519 = oid!(1, 3, 101, 112);
    let algorithm = Sequence(0, vec![ObjectIdentifier(0, id_ed25519)]);
    let subject_public_key = BitString(0, public_key.len() * 8, public_key);
    let subject_public_key_info = Sequence(0, vec![algorithm, subject_public_key]);
    to_der(&subject_public_key_info).unwrap()
}