ic_agent/identity/
basic.rs

1use crate::{agent::EnvelopeContent, export::Principal, Identity, Signature};
2
3#[cfg(feature = "pem")]
4use crate::identity::error::PemError;
5
6use ic_ed25519::PrivateKey;
7
8use std::fmt;
9
10use super::Delegation;
11
12/// A cryptographic identity which signs using an Ed25519 key pair.
13///
14/// The caller will be represented via [`Principal::self_authenticating`], which contains the SHA-224 hash of the public key.
15pub struct BasicIdentity {
16    private_key: KeyCompat,
17    der_encoded_public_key: Vec<u8>,
18}
19
20impl fmt::Debug for BasicIdentity {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        f.debug_struct("BasicIdentity")
23            .field("der_encoded_public_key", &self.der_encoded_public_key)
24            .finish_non_exhaustive()
25    }
26}
27
28impl BasicIdentity {
29    /// Create a `BasicIdentity` from reading a PEM file at the path.
30    #[cfg(feature = "pem")]
31    pub fn from_pem_file<P: AsRef<std::path::Path>>(file_path: P) -> Result<Self, PemError> {
32        Self::from_pem(std::fs::File::open(file_path)?)
33    }
34
35    /// Create a `BasicIdentity` from reading a PEM File from a Reader.
36    #[cfg(feature = "pem")]
37    pub fn from_pem<R: std::io::Read>(pem_reader: R) -> Result<Self, PemError> {
38        use der::{asn1::OctetString, Decode, ErrorKind, SliceReader, Tag, TagNumber};
39        use pkcs8::PrivateKeyInfo;
40
41        let bytes: Vec<u8> = pem_reader.bytes().collect::<Result<_, _>>()?;
42        let pem = pem::parse(bytes)?;
43        let pki_res = PrivateKeyInfo::decode(&mut SliceReader::new(pem.contents())?);
44        let mut truncated;
45        let pki = match pki_res {
46            Ok(pki) => pki,
47            Err(e) => {
48                if e.kind()
49                    == (ErrorKind::Noncanonical {
50                        tag: Tag::ContextSpecific {
51                            constructed: true,
52                            number: TagNumber::new(1),
53                        },
54                    })
55                {
56                    // Very old versions of dfx generated nonconforming containers. They can only be imported if the extra data is removed.
57                    truncated = pem.into_contents();
58                    if truncated[48..52] != *b"\xA1\x23\x03\x21" {
59                        return Err(e.into());
60                    }
61                    // hatchet surgery
62                    truncated.truncate(48);
63                    truncated[1] = 46;
64                    truncated[4] = 0;
65                    PrivateKeyInfo::decode(&mut SliceReader::new(&truncated)?).map_err(|_| e)?
66                } else {
67                    return Err(e.into());
68                }
69            }
70        };
71        let decoded_key = OctetString::from_der(pki.private_key)?; // ed25519 uses an octet string within another octet string
72        let key_len = decoded_key.as_bytes().len();
73        if key_len != 32 {
74            Err(PemError::InvalidPrivateKey(format!(
75                "Ed25519 expects a 32 octets private key, but got {key_len} octets",
76            )))
77        } else {
78            let raw_key: [u8; 32] = decoded_key.as_bytes().try_into().unwrap();
79            Ok(Self::from_raw_key(&raw_key))
80        }
81    }
82
83    /// Create a `BasicIdentity` from a raw 32-byte private key as described in RFC 8032 5.1.5.
84    pub fn from_raw_key(key: &[u8; 32]) -> Self {
85        let private_key = PrivateKey::deserialize_raw_32(key);
86        let public_key = private_key.public_key();
87        let der_encoded_public_key = public_key.serialize_rfc8410_der();
88        Self {
89            private_key: KeyCompat::Standard(private_key),
90            der_encoded_public_key,
91        }
92    }
93
94    /// Create a `BasicIdentity` from a `SigningKey` from `ed25519-consensus`.
95    ///
96    /// # Note
97    ///
98    /// This constructor is kept for backwards compatibility.
99    /// The signing won't use `ed25519-consensus` anymore.
100    #[deprecated(since = "0.41.0", note = "use BasicIdentity::from_raw_key instead")]
101    pub fn from_signing_key(key: ed25519_consensus::SigningKey) -> Self {
102        let raw_key = key.to_bytes();
103        Self::from_raw_key(&raw_key)
104    }
105
106    /// Create a `BasicIdentity` from an `Ed25519KeyPair` from `ring`.
107    #[cfg(feature = "ring")]
108    pub fn from_key_pair(key_pair: ring::signature::Ed25519KeyPair) -> Self {
109        use ic_ed25519::PublicKey;
110        use ring::signature::KeyPair;
111        let raw_public_key = key_pair.public_key().as_ref().to_vec();
112        // Unwrap safe: we trust that the public key is valid, as it comes from a valid key pair.
113        let public_key = PublicKey::deserialize_raw(&raw_public_key).unwrap();
114        let der_encoded_public_key = public_key.serialize_rfc8410_der();
115        Self {
116            private_key: KeyCompat::Ring(key_pair),
117            der_encoded_public_key,
118        }
119    }
120}
121
122enum KeyCompat {
123    /// ic_ed25519::PrivateKey
124    Standard(PrivateKey),
125    #[cfg(feature = "ring")]
126    Ring(ring::signature::Ed25519KeyPair),
127}
128
129impl KeyCompat {
130    fn sign(&self, payload: &[u8]) -> Vec<u8> {
131        match self {
132            Self::Standard(k) => k.sign_message(payload).to_vec(),
133            #[cfg(feature = "ring")]
134            Self::Ring(k) => k.sign(payload).as_ref().to_vec(),
135        }
136    }
137}
138
139impl Identity for BasicIdentity {
140    fn sender(&self) -> Result<Principal, String> {
141        Ok(Principal::self_authenticating(&self.der_encoded_public_key))
142    }
143
144    fn public_key(&self) -> Option<Vec<u8>> {
145        Some(self.der_encoded_public_key.clone())
146    }
147
148    fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
149        self.sign_arbitrary(&content.to_request_id().signable())
150    }
151
152    fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
153        self.sign_arbitrary(&content.signable())
154    }
155
156    fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
157        let signature = self.private_key.sign(content);
158        Ok(Signature {
159            signature: Some(signature),
160            public_key: self.public_key(),
161            delegations: None,
162        })
163    }
164}