Skip to main content

ic_auth_verifier/
lib.rs

1//! Verification and signing utilities for IC-Auth.
2//!
3//! The base crate verifies raw signatures for the public key formats used by
4//! Internet Computer identities: Ed25519, ECDSA P-256, ECDSA secp256k1, and IC
5//! canister signatures. Optional features add higher-level protocol surfaces:
6//!
7//! - `envelope`: [`SignedEnvelope`] parsing, verification, HTTP headers, and
8//!   deep-link payload helpers.
9//! - `identity`: `ic-agent` identity helpers for clients and services that
10//!   need to sign envelopes. This feature is intended for native/server
11//!   targets, not canisters.
12//!
13//! # Examples
14//!
15//! ```
16//! use ic_auth_verifier::{Algorithm, sha256, verify_basic_sig};
17//!
18//! let digest = sha256(b"message");
19//! assert_eq!(digest.len(), 32);
20//!
21//! let err = verify_basic_sig(Algorithm::IcCanisterSignature, &[], b"message", &[])
22//!     .unwrap_err();
23//! assert!(err.contains("not supported"));
24//! ```
25
26use k256::ecdsa::signature::hazmat::PrehashVerifier;
27use sha3::Digest;
28
29mod asn1;
30
31#[cfg(feature = "envelope")]
32pub mod envelope;
33
34#[cfg(feature = "envelope")]
35pub mod deeplink;
36
37#[cfg(feature = "envelope")]
38mod ic_signature_verification;
39
40#[cfg(feature = "identity")]
41pub mod identity;
42
43pub use asn1::*;
44pub use ic_canister_sig_creation::CanisterSigPublicKey;
45
46#[cfg(feature = "envelope")]
47pub use ic_signature_verification::*;
48
49#[cfg(feature = "envelope")]
50pub use envelope::*;
51
52#[cfg(feature = "envelope")]
53pub use deeplink::*;
54
55#[cfg(feature = "identity")]
56pub use identity::*;
57
58/// Verifies a raw signature for non-canister public keys.
59///
60/// `public_key` must be the algorithm-specific raw public key bytes returned by
61/// [`user_public_key_from_der`], not the DER SubjectPublicKeyInfo wrapper. For
62/// ECDSA variants the function hashes `msg` with SHA-256 and verifies the
63/// resulting prehash, matching `ic-agent` arbitrary-message signatures.
64///
65/// IC canister signatures require certificate verification and should be
66/// handled with `verify_sig` or `verify_sig_with_rootkey` from the `envelope`
67/// feature instead.
68pub fn verify_basic_sig(
69    algorithm_id: Algorithm,
70    public_key: &[u8],
71    msg: &[u8],
72    signature: &[u8],
73) -> Result<(), String> {
74    match algorithm_id {
75        Algorithm::Ed25519 => {
76            let public_key = public_key
77                .try_into()
78                .map_err(|_| "Ed25519 public key must be 32 bytes long".to_string())?;
79            let key = ed25519_dalek::VerifyingKey::from_bytes(public_key)
80                .map_err(|err| format!("{err:?}"))?;
81            let sig = ed25519_dalek::Signature::from_slice(signature)
82                .map_err(|err| format!("{err:?}"))?;
83            key.verify_strict(msg, &sig)
84                .map_err(|_| "Ed25519 signature verification failed".to_string())
85        }
86        Algorithm::EcdsaP256 => {
87            let key = p256::ecdsa::VerifyingKey::from_sec1_bytes(public_key)
88                .map_err(|err| format!("{err:?}"))?;
89            let sig =
90                p256::ecdsa::Signature::try_from(signature).map_err(|err| format!("{err:?}"))?;
91
92            let msg_hash = sha256(msg);
93            key.verify_prehash(&msg_hash, &sig)
94                .map_err(|_| "ECDSA P256 signature verification failed".to_string())
95        }
96        Algorithm::EcdsaSecp256k1 => {
97            let key = k256::ecdsa::VerifyingKey::from_sec1_bytes(public_key)
98                .map_err(|err| err.to_string())?;
99            let sig =
100                k256::ecdsa::Signature::try_from(signature).map_err(|err| format!("{err:?}"))?;
101
102            let msg_hash = sha256(msg);
103            key.verify_prehash(&msg_hash, &sig)
104                .map_err(|_| "ECDSA Secp256k1 signature verification failed".to_string())
105        }
106        algorithm => Err(format!(
107            "{algorithm:?} is not supported for basic signature verification"
108        )),
109    }
110}
111
112/// Computes SHA-256 for `data`.
113pub fn sha256(data: &[u8]) -> [u8; 32] {
114    let mut hasher = sha2::Sha256::new();
115    hasher.update(data);
116    hasher.finalize().into()
117}
118
119/// Computes SHA3-256 for `data`.
120pub fn sha3_256(data: &[u8]) -> [u8; 32] {
121    let mut hasher = sha3::Sha3_256::new();
122    hasher.update(data);
123    hasher.finalize().into()
124}
125
126/// Computes Keccak-256 for `data`.
127pub fn keccak256(data: &[u8]) -> [u8; 32] {
128    let mut hasher = sha3::Keccak256::new();
129    hasher.update(data);
130    hasher.finalize().into()
131}
132
133#[cfg(feature = "identity")]
134/// Returns `N` cryptographically random bytes.
135pub fn rand_bytes<const N: usize>() -> [u8; N] {
136    use rand::Rng;
137
138    let mut rng = rand::rng();
139    let mut bytes = [0u8; N];
140    rng.fill_bytes(&mut bytes);
141    bytes
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use ic_agent::{
148        Identity,
149        identity::{BasicIdentity, Prime256v1Identity, Secp256k1Identity},
150    };
151    use rand::{Rng, rng};
152
153    const MESSAGE: &[u8] = b"some message";
154
155    fn rand_bytes<const N: usize>() -> [u8; N] {
156        let mut rng = rng();
157        let mut bytes = [0u8; N];
158        rng.fill_bytes(&mut bytes);
159        bytes
160    }
161
162    #[test]
163    fn should_work_with_ed25519() {
164        let sk: [u8; 32] = rand_bytes();
165        let id = BasicIdentity::from_raw_key(&sk);
166        let sig = id.sign_arbitrary(MESSAGE).unwrap();
167        let pk_der = id.public_key().unwrap();
168        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
169        assert_eq!(alg, Algorithm::Ed25519);
170        assert!(verify_basic_sig(alg, &pk, MESSAGE, &sig.signature.unwrap()).is_ok());
171    }
172
173    #[test]
174    fn should_work_with_ecdsa_secp256k1() {
175        let pk_der = hex::decode("3056301006072a8648ce3d020106052b8104000a034200047060f720298ffa0f48d9606abdb013bc82f4ff269f9adc3e7226391af3fad8b30fd6a30deb81d5b4f9e142971085d0ae15b8e222d85af1e17438e630d09b7ef4").unwrap();
176        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
177        assert_eq!(alg, Algorithm::EcdsaSecp256k1);
178        assert!(k256::ecdsa::VerifyingKey::from_sec1_bytes(&pk).is_ok());
179
180        let sk: [u8; 32] = rand_bytes();
181        let sk = k256::ecdsa::SigningKey::from_bytes(&sk.into()).unwrap();
182        let id = Secp256k1Identity::from_private_key(sk.into());
183        let sig = id.sign_arbitrary(MESSAGE).unwrap();
184        let pk_der = id.public_key().unwrap();
185        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
186        assert_eq!(alg, Algorithm::EcdsaSecp256k1);
187        assert!(verify_basic_sig(alg, &pk, MESSAGE, &sig.signature.unwrap()).is_ok());
188    }
189
190    #[test]
191    fn should_work_with_ecdsa_p256() {
192        let pk_der = hex::decode("3059301306072a8648ce3d020106082a8648ce3d03010703420004485c32997ce7c6d38ca82c821185c689d424fac7c9695bb97786c4248aab6428949bcd163e2bcf3eeeac4f200b38fbd053f82c4e1776dc9c6dc8db9b7c35e06f").unwrap();
193        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
194        assert_eq!(alg, Algorithm::EcdsaP256);
195        assert!(p256::ecdsa::VerifyingKey::from_sec1_bytes(&pk).is_ok());
196
197        let sk: [u8; 32] = rand_bytes();
198        let sk = p256::ecdsa::SigningKey::from_bytes(&sk.into()).unwrap();
199        let id = Prime256v1Identity::from_private_key(sk.into());
200        let sig = id.sign_arbitrary(MESSAGE).unwrap();
201        let pk_der = id.public_key().unwrap();
202        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
203        assert_eq!(alg, Algorithm::EcdsaP256);
204        assert!(verify_basic_sig(alg, &pk, MESSAGE, &sig.signature.unwrap()).is_ok());
205    }
206
207    #[test]
208    fn should_work_with_iccsa_pubkey() {
209        let pk_der =
210            hex::decode("301b300c060a2b0601040183b8430102030b007075626c6963206b6579").unwrap();
211        let (alg, _pk) = user_public_key_from_der(&pk_der).unwrap();
212        assert_eq!(alg, Algorithm::IcCanisterSignature);
213    }
214
215    #[test]
216    fn hash_helpers_match_known_vectors() {
217        assert_eq!(
218            hex::encode(sha256(b"abc")),
219            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
220        );
221        assert_eq!(
222            hex::encode(sha3_256(b"abc")),
223            "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
224        );
225        assert_eq!(
226            hex::encode(keccak256(b"abc")),
227            "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"
228        );
229    }
230
231    #[test]
232    fn verify_basic_sig_rejects_invalid_inputs() {
233        assert_eq!(
234            verify_basic_sig(Algorithm::IcCanisterSignature, &[], MESSAGE, &[]).unwrap_err(),
235            "IcCanisterSignature is not supported for basic signature verification"
236        );
237
238        assert_eq!(
239            verify_basic_sig(Algorithm::Ed25519, &[0; 31], MESSAGE, &[]).unwrap_err(),
240            "Ed25519 public key must be 32 bytes long"
241        );
242
243        let id = BasicIdentity::from_raw_key(&[8u8; 32]);
244        let sig = id.sign_arbitrary(MESSAGE).unwrap();
245        let pk_der = id.public_key().unwrap();
246        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
247        assert!(verify_basic_sig(alg.clone(), &pk, MESSAGE, &[]).is_err());
248        assert_eq!(
249            verify_basic_sig(alg, &pk, b"tampered", &sig.signature.unwrap()).unwrap_err(),
250            "Ed25519 signature verification failed"
251        );
252
253        let sk: [u8; 32] = rand_bytes();
254        let sk = p256::ecdsa::SigningKey::from_bytes(&sk.into()).unwrap();
255        let id = Prime256v1Identity::from_private_key(sk.into());
256        let pk_der = id.public_key().unwrap();
257        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
258        assert!(verify_basic_sig(alg, &pk, MESSAGE, &[]).is_err());
259
260        let sk: [u8; 32] = rand_bytes();
261        let sk = k256::ecdsa::SigningKey::from_bytes(&sk.into()).unwrap();
262        let id = Secp256k1Identity::from_private_key(sk.into());
263        let pk_der = id.public_key().unwrap();
264        let (alg, pk) = user_public_key_from_der(&pk_der).unwrap();
265        assert!(verify_basic_sig(alg, &pk, MESSAGE, &[]).is_err());
266
267        assert!(verify_basic_sig(Algorithm::EcdsaP256, &[0], MESSAGE, &[]).is_err());
268        assert!(verify_basic_sig(Algorithm::EcdsaSecp256k1, &[0], MESSAGE, &[]).is_err());
269    }
270}