1#[cfg(not(feature = "sys-rng"))]
4use blind_rsa_signatures::DefaultRng;
5use blind_rsa_signatures::{
6 pbrsa::PartiallyBlindKeyPairSha384PSSDeterministic,
7 reexports::rand::{
8 self,
9 rand_core::{Infallible, TryCryptoRng, TryRng, UnwrapErr},
10 rngs::SysRng,
11 },
12};
13use serde_json::Value;
14
15use crate::{
16 canonicalize_pbrsa_info, CredentialsError, IssuanceRequest, IssuanceResponse, Issuer,
17 IssuerAuthority, IssuerId, IssuerSecretKeys, ProtocolV1, Revocation, RevocationLocation,
18 RevocationProof, SchnorrSignatureProof, SignedCredential, SignedRevocation,
19};
20
21pub const ISSUER_MODULUS_BITS: usize = 2048;
22const KEYGEN_PROGRESS_RANDOM_DRAWS: u64 = 100_000;
23
24fn default_pbrsa_rng() -> impl TryCryptoRng<Error = Infallible> {
25 #[cfg(feature = "sys-rng")]
26 {
27 UnwrapErr(SysRng)
28 }
29
30 #[cfg(not(feature = "sys-rng"))]
31 {
32 DefaultRng
33 }
34}
35
36struct KeygenProgressRng<R> {
37 inner: R,
38 rng_strategy: &'static str,
39 random_draws: u64,
40 next_progress_log_at: u64,
41}
42
43impl<R> KeygenProgressRng<R> {
44 fn new(inner: R, rng_strategy: &'static str) -> Self {
45 tracing::info!(rng_strategy, "issuer RSA keygen started");
46 Self {
47 inner,
48 rng_strategy,
49 random_draws: 0,
50 next_progress_log_at: KEYGEN_PROGRESS_RANDOM_DRAWS,
51 }
52 }
53
54 fn random_draws(&self) -> u64 {
55 self.random_draws
56 }
57
58 fn record_random_draw(&mut self) {
59 self.random_draws += 1;
60 if self.random_draws >= self.next_progress_log_at {
61 tracing::info!(
62 rng_strategy = self.rng_strategy,
63 random_draws = self.random_draws,
64 "issuer RSA keygen still running"
65 );
66 self.next_progress_log_at += KEYGEN_PROGRESS_RANDOM_DRAWS;
67 }
68 }
69}
70
71impl<R: TryRng> TryRng for KeygenProgressRng<R> {
72 type Error = R::Error;
73
74 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
75 let value = self.inner.try_next_u32()?;
76 self.record_random_draw();
77 Ok(value)
78 }
79
80 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
81 let value = self.inner.try_next_u64()?;
82 self.record_random_draw();
83 Ok(value)
84 }
85
86 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
87 self.inner.try_fill_bytes(dst)?;
88 self.record_random_draw();
89 Ok(())
90 }
91}
92
93impl<R: TryCryptoRng> TryCryptoRng for KeygenProgressRng<R> {}
94
95#[derive(Clone)]
97pub struct IssuerContext {
98 identity_keys: nostr::Keys,
99 key_pair: PartiallyBlindKeyPairSha384PSSDeterministic,
100}
101
102impl IssuerContext {
103 #[cfg(not(feature = "sys-rng"))]
105 pub fn generate() -> Result<Self, CredentialsError> {
106 Self::generate_with_thread_rng()
107 }
108
109 #[cfg(feature = "sys-rng")]
111 pub fn generate() -> Result<Self, CredentialsError> {
112 Self::generate_with_system_rng()
113 }
114
115 pub fn generate_with_thread_rng() -> Result<Self, CredentialsError> {
117 Self::generate_with_rng_source("thread_rng", rand::rng())
118 }
119
120 pub fn generate_with_system_rng() -> Result<Self, CredentialsError> {
122 Self::generate_with_rng_source("system_rng", UnwrapErr(SysRng))
123 }
124
125 fn generate_with_rng_source<R>(
126 rng_strategy: &'static str,
127 rng: R,
128 ) -> Result<Self, CredentialsError>
129 where
130 R: TryCryptoRng<Error = Infallible>,
131 {
132 let span = tracing::info_span!(
133 "issuer_rsa_keygen",
134 modulus_bits = ISSUER_MODULUS_BITS,
135 rng_strategy
136 );
137 let _span_guard = span.enter();
138 let mut rng = KeygenProgressRng::new(rng, rng_strategy);
139 let generated = Self::generate_with_rng(nostr::Keys::generate(), &mut rng);
140 match &generated {
141 Ok(_) => tracing::info!(
142 rng_strategy,
143 random_draws = rng.random_draws(),
144 "issuer RSA keygen finished"
145 ),
146 Err(error) => tracing::warn!(
147 rng_strategy,
148 random_draws = rng.random_draws(),
149 %error,
150 "issuer RSA keygen failed"
151 ),
152 }
153 generated
154 }
155
156 pub(crate) fn generate_with_rng(
157 identity_keys: nostr::Keys,
158 rng: &mut (impl blind_rsa_signatures::reexports::rsa::rand_core::CryptoRng + ?Sized),
159 ) -> Result<Self, CredentialsError> {
160 Ok(Self {
161 identity_keys,
162 key_pair: PartiallyBlindKeyPairSha384PSSDeterministic::generate(
163 rng,
164 ISSUER_MODULUS_BITS,
165 )?,
166 })
167 }
168
169 pub fn issuer_authority(
174 &self,
175 revocation: Vec<RevocationLocation>,
176 ) -> Result<IssuerAuthority, CredentialsError> {
177 self.issuer_authority_with_rng(revocation, &mut nostr::secp256k1::rand::rngs::OsRng)
178 }
179
180 pub(crate) fn issuer_authority_with_rng(
181 &self,
182 revocation: Vec<RevocationLocation>,
183 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
184 ) -> Result<IssuerAuthority, CredentialsError> {
185 let issuer = Issuer {
186 issuer_id_pubkey: self.issuer_id(),
187 issuance_key: self.key_pair.pk.clone(),
188 revocation,
189 };
190 let signature = self.sign_identity_digest_with_rng(issuer.digest()?, rng);
191
192 Ok(IssuerAuthority {
193 version: ProtocolV1,
194 issuer,
195 proof: SchnorrSignatureProof { signature },
196 })
197 }
198
199 fn issuer_id(&self) -> IssuerId {
200 IssuerId(self.identity_keys.public_key())
201 }
202
203 pub fn export_secret_key(&self) -> Result<IssuerSecretKeys, CredentialsError> {
205 Ok(IssuerSecretKeys {
206 issuer_id_secret_key: self.identity_keys.secret_key().to_secret_hex(),
207 issuance_secret_key: self.key_pair.sk.to_der()?,
208 })
209 }
210
211 pub fn import_secret_key(secret_key: &IssuerSecretKeys) -> Result<Self, CredentialsError> {
213 let identity_keys = nostr::Keys::parse(&secret_key.issuer_id_secret_key)?;
214 let secret_key =
215 blind_rsa_signatures::pbrsa::PartiallyBlindSecretKeySha384PSSDeterministic::from_der(
216 &secret_key.issuance_secret_key,
217 )?;
218 let public_key = secret_key.public_key()?;
219 Ok(Self {
220 identity_keys,
221 key_pair: PartiallyBlindKeyPairSha384PSSDeterministic {
222 pk: public_key,
223 sk: secret_key,
224 },
225 })
226 }
227
228 pub fn issue_credential(
230 &self,
231 info: Value,
232 request: &IssuanceRequest,
233 ) -> Result<IssuanceResponse, CredentialsError> {
234 self.issue_credential_with_rng(info, request, &mut default_pbrsa_rng())
235 }
236
237 pub(crate) fn issue_credential_with_rng(
238 &self,
239 info: Value,
240 request: &IssuanceRequest,
241 rng: &mut (impl blind_rsa_signatures::reexports::rsa::rand_core::TryCryptoRng + ?Sized),
242 ) -> Result<IssuanceResponse, CredentialsError> {
243 let issuer_id = self.issuer_id();
244 let metadata = canonicalize_pbrsa_info(ProtocolV1, &issuer_id, &info)?;
245 let secret_key = self.key_pair.derive_secret_key_for_metadata(&metadata)?;
246 Ok(IssuanceResponse {
247 version: ProtocolV1,
248 issuer_id,
249 info,
250 blind_signature: secret_key.blind_sign_with_rng(rng, &request.blinded_message)?,
251 })
252 }
253
254 pub fn revoke_credential(
260 &self,
261 credential: &SignedCredential,
262 ) -> Result<SignedRevocation, CredentialsError> {
263 self.revoke_credential_with_rng(credential, &mut nostr::secp256k1::rand::rngs::OsRng)
264 }
265
266 pub(crate) fn revoke_credential_with_rng(
267 &self,
268 credential: &SignedCredential,
269 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
270 ) -> Result<SignedRevocation, CredentialsError> {
271 let issuer_id = self.issuer_id();
272 if credential.credential.issuer_id_pubkey != issuer_id {
273 return Err(CredentialsError::IssuerIdMismatch);
274 }
275
276 let revocation = Revocation {
277 credential_digest: crate::CredentialDigest(credential.credential.digest()?),
278 };
279
280 let signature = self.sign_identity_digest_with_rng(revocation.digest()?, rng);
281
282 Ok(SignedRevocation {
283 version: ProtocolV1,
284 revocation,
285 proof: RevocationProof {
286 issuer_id_pubkey: issuer_id,
287 signature,
288 },
289 })
290 }
291
292 fn sign_identity_digest_with_rng(
293 &self,
294 digest: sha2::digest::Output<sha2::Sha256>,
295 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
296 ) -> nostr::secp256k1::schnorr::Signature {
297 self.identity_keys.sign_schnorr_with_ctx(
298 nostr::SECP256K1,
299 &nostr::secp256k1::Message::from_digest(digest.into()),
300 rng,
301 )
302 }
303}