1 //! Implementation of asymmetric cryptography using Windows CNG API. 2 #![allow(unused_variables)] 3 4 use std::time::SystemTime; 5 use std::convert::TryInto; 6 7 use crate::{Error, Result}; 8 9 use crate::crypto::asymmetric::{Decryptor, KeyPair, Signer}; 10 use crate::crypto::mem::Protected; 11 use crate::crypto::mpi; 12 use crate::crypto::SessionKey; 13 use crate::packet::key::{Key4, SecretParts}; 14 use crate::packet::{self, key, Key}; 15 use crate::types::{PublicKeyAlgorithm, SymmetricAlgorithm}; 16 use crate::types::{Curve, HashAlgorithm}; 17 18 use num_bigint_dig::{traits::ModInverse, BigInt, BigUint}; 19 use win_crypto_ng as cng; 20 21 const CURVE25519_SIZE: usize = 32; 22 23 impl Signer for KeyPair { public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>24 fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { 25 KeyPair::public(self) 26 } 27 sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature>28 fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { 29 use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; 30 use cng::asymmetric::{AsymmetricKey, Private, Rsa}; 31 use cng::asymmetric::signature::{Signer, SignaturePadding}; 32 use cng::key_blob::RsaKeyPrivatePayload; 33 use cng::key_blob::EccKeyPrivatePayload; 34 use cng::asymmetric::ecc::NamedCurve; 35 36 #[allow(deprecated)] 37 self.secret().map(|secret| { 38 Ok(match (self.public().pk_algo(), self.public().mpis(), secret) { 39 (PublicKeyAlgorithm::RSAEncryptSign, 40 &mpi::PublicKey::RSA { ref e, ref n }, 41 &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) | 42 (PublicKeyAlgorithm::RSASign, 43 &mpi::PublicKey::RSA { ref e, ref n }, 44 &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) => { 45 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; 46 let key = AsymmetricKey::<Rsa, Private>::import_from_parts( 47 &provider, 48 &RsaKeyPrivatePayload { 49 modulus: n.value(), 50 pub_exp: e.value(), 51 prime1: p.value(), 52 prime2: q.value(), 53 } 54 )?; 55 56 // As described in [Section 5.2.2 and 5.2.3 of RFC 4880], 57 // to verify the signature, we need to encode the 58 // signature data in a PKCS1-v1.5 packet. 59 // 60 // [Section 5.2.2 and 5.2.3 of RFC 4880]: 61 // https://tools.ietf.org/html/rfc4880#section-5.2.2 62 let hash = hash_algo.try_into()?; 63 let padding = SignaturePadding::pkcs1(hash); 64 let sig = key.sign(digest, Some(padding))?; 65 66 mpi::Signature::RSA { s: mpi::MPI::new(&*sig) } 67 }, 68 (PublicKeyAlgorithm::ECDSA, 69 mpi::PublicKey::ECDSA { curve, q }, 70 mpi::SecretKeyMaterial::ECDSA { scalar }) => 71 { 72 let (x, y) = q.decode_point(curve)?; 73 74 // It's expected for the private key to be exactly 32/48/66 75 // (respective curve field size) bytes long but OpenPGP 76 // allows leading zeros to be stripped. 77 // Padding has to be unconditional; otherwise we have a 78 // secret-dependent branch. 79 let curve_bits = curve.bits().ok_or_else(|| 80 Error::UnsupportedEllipticCurve(curve.clone()) 81 )?; 82 let curve_bytes = (curve_bits + 7) / 8; 83 let mut secret = Protected::from(vec![0u8; curve_bytes]); 84 let missing = curve_bytes.saturating_sub(scalar.value().len()); 85 secret.as_mut()[missing..].copy_from_slice(scalar.value()); 86 87 use cng::asymmetric::{ecc::{NistP256, NistP384, NistP521}, Ecdsa}; 88 89 // TODO: Improve CNG public API 90 let sig = match curve { 91 Curve::NistP256 => { 92 let provider = AsymmetricAlgorithm::open( 93 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP256) 94 )?; 95 let key = AsymmetricKey::<Ecdsa<NistP256>, Private>::import_from_parts( 96 &provider, 97 &EccKeyPrivatePayload { x, y, d: &secret } 98 )?; 99 key.sign(digest, None)? 100 }, 101 Curve::NistP384 => { 102 let provider = AsymmetricAlgorithm::open( 103 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP384) 104 )?; 105 let key = AsymmetricKey::<Ecdsa<NistP384>, Private>::import_from_parts( 106 &provider, 107 &EccKeyPrivatePayload { x, y, d: &secret } 108 )?; 109 key.sign(digest, None)? 110 }, 111 Curve::NistP521 => { 112 let provider = AsymmetricAlgorithm::open( 113 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP521) 114 )?; 115 let key = AsymmetricKey::<Ecdsa<NistP521>, Private>::import_from_parts( 116 &provider, 117 &EccKeyPrivatePayload { x, y, d: &secret } 118 )?; 119 key.sign(digest, None)? 120 }, 121 _ => return Err( 122 Error::UnsupportedEllipticCurve(curve.clone()).into()), 123 }; 124 125 // CNG outputs a P1363 formatted signature - r || s 126 let (r, s) = sig.split_at(sig.len() / 2); 127 mpi::Signature::ECDSA { 128 r: mpi::MPI::new(r), 129 s: mpi::MPI::new(s), 130 } 131 }, 132 ( 133 PublicKeyAlgorithm::EdDSA, 134 mpi::PublicKey::EdDSA { curve, q }, 135 mpi::SecretKeyMaterial::EdDSA { scalar }, 136 ) => { 137 // CNG doesn't support EdDSA, use ed25519-dalek instead 138 use ed25519_dalek::{Keypair, Signer}; 139 use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; 140 141 let (public, ..) = q.decode_point(&Curve::Ed25519)?; 142 143 // It's expected for the private key to be exactly 144 // SECRET_KEY_LENGTH bytes long but OpenPGP allows leading 145 // zeros to be stripped. 146 // Padding has to be unconditional; otherwise we have a 147 // secret-dependent branch. 148 let missing = SECRET_KEY_LENGTH.saturating_sub(scalar.value().len()); 149 let mut keypair = Protected::from( 150 vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] 151 ); 152 keypair.as_mut()[missing..SECRET_KEY_LENGTH].copy_from_slice(scalar.value()); 153 keypair.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(&public); 154 let pair = Keypair::from_bytes(&keypair).unwrap(); 155 156 let sig = pair.sign(digest).to_bytes(); 157 158 // https://tools.ietf.org/html/rfc8032#section-5.1.6 159 let (r, s) = sig.split_at(sig.len() / 2); 160 mpi::Signature::EdDSA { 161 r: mpi::MPI::new(r), 162 s: mpi::MPI::new(s), 163 } 164 }, 165 (PublicKeyAlgorithm::DSA, 166 mpi:: PublicKey::DSA { y, p, q, g }, 167 mpi::SecretKeyMaterial::DSA { x }, 168 ) => { 169 use win_crypto_ng::key_blob::{DsaKeyPrivateV2Payload, DsaKeyPrivateV2Blob}; 170 use win_crypto_ng::key_blob::{DsaKeyPrivatePayload, DsaKeyPrivateBlob}; 171 use win_crypto_ng::asymmetric::{Dsa, DsaPrivateBlob}; 172 use win_crypto_ng::helpers::Blob; 173 174 if y.value().len() > 3072 / 8 { 175 return Err(Error::InvalidOperation( 176 "DSA keys are supported up to 3072-bits".to_string()).into() 177 ); 178 } 179 180 enum Version { V1, V2 } 181 // 1024-bit DSA keys are handled differently 182 let version = if y.value().len() <= 128 { Version::V1 } else { Version::V2 }; 183 184 let blob: DsaPrivateBlob = match version { 185 Version::V1 => { 186 let mut group = [0; 20]; 187 assert!(q.value().len() >= 20); 188 &mut group[..q.value().len()].copy_from_slice(q.value()); 189 190 DsaPrivateBlob::V1(Blob::<DsaKeyPrivateBlob>::clone_from_parts( 191 &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB { 192 dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC, 193 cbKey: y.value().len() as u32, 194 Count: [0; 4], // unused 195 Seed: [0; 20], // unused 196 q: group, 197 }, 198 &DsaKeyPrivatePayload { 199 modulus: p.value(), 200 generator: g.value(), 201 public: y.value(), 202 priv_exp: x.value(), 203 }, 204 )) 205 }, 206 Version::V2 => { 207 // https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L276-L282 208 let hash = match q.value().len() { 209 20 => 0, 210 32 => 1, 211 64 => 2, 212 _ => return Err(Error::InvalidOperation( 213 "CNG accepts DSA q with length of either length of 20, 32 or 64".into()) 214 .into()), 215 }; 216 217 // We don't use counter/seed values so set them to 0. 218 // CNG pre-checks that the seed is at least |Q| long, 219 // so we can't use an empty buffer here. 220 let (count, seed) = ([0x0; 4], vec![0x0; q.value().len()]); 221 222 DsaPrivateBlob::V2(Blob::<DsaKeyPrivateV2Blob>::clone_from_parts( 223 &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB_V2 { 224 dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PRIVATE_MAGIC_V2, 225 Count: count, 226 // Size of the prime number q. 227 // Currently, if the key is less than 128 228 // bits, q is 20 bytes long. 229 // If the key exceeds 256 bits, q is 32 bytes long. 230 cbGroupSize: std::cmp::min(q.value().len(), 32) as u32, 231 cbKey: y.value().len() as u32, 232 cbSeedLength: seed.len() as u32, 233 hashAlgorithm: hash, 234 standardVersion: 1, // FIPS 186-3 235 236 }, 237 &DsaKeyPrivateV2Payload { 238 seed: &seed, 239 group: q.value(), 240 modulus: p.value(), 241 generator: g.value(), 242 public: y.value(), 243 priv_exp: x.value(), 244 }, 245 )) 246 }, 247 }; 248 249 use win_crypto_ng::asymmetric::{Import}; 250 251 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Dsa)?; 252 let pair = AsymmetricKey::<Dsa, Private>::import( 253 Dsa, 254 &provider, 255 blob 256 )?; 257 258 // CNG accepts only hash and Q of equal length. Either trim the 259 // digest or pad it with zeroes (since it's treated as a 260 // big-endian number). 261 // See https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs#L148. 262 let mut _digest = vec![]; 263 let digest = match std::cmp::Ord::cmp(&q.value().len(), &digest.len()) { 264 std::cmp::Ordering::Equal => digest, 265 std::cmp::Ordering::Less => &digest[..q.value().len()], 266 std::cmp::Ordering::Greater => { 267 let pad = vec![0; q.value().len() - digest.len()]; 268 _digest = [pad.as_ref(), digest].concat(); 269 &_digest 270 } 271 }; 272 assert_eq!(q.value().len(), digest.len()); 273 274 let sig = pair.sign(digest, None)?; 275 276 // https://tools.ietf.org/html/rfc8032#section-5.1.6 277 let (r, s) = sig.split_at(sig.len() / 2); 278 mpi::Signature::DSA { 279 r: mpi::MPI::new(r), 280 s: mpi::MPI::new(s), 281 } 282 }, 283 (pk_algo, _, _) => Err(Error::InvalidOperation(format!( 284 "unsupported combination of algorithm {:?}, key {:?}, \ 285 and secret key {:?}", 286 pk_algo, self.public(), self.secret())))?, 287 }) 288 }) 289 } 290 } 291 292 impl Decryptor for KeyPair { public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>293 fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { 294 KeyPair::public(self) 295 } 296 297 /// Creates a signature over the `digest` produced by `hash_algo`. decrypt( &mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey>298 fn decrypt( 299 &mut self, 300 ciphertext: &mpi::Ciphertext, 301 plaintext_len: Option<usize>, 302 ) -> Result<SessionKey> { 303 use crate::PublicKeyAlgorithm::*; 304 305 self.secret().map( 306 |secret| Ok(match (self.public().mpis(), secret, ciphertext) 307 { 308 (mpi::PublicKey::RSA { ref e, ref n }, 309 mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }, 310 mpi::Ciphertext::RSA { ref c }) => { 311 use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; 312 use cng::asymmetric::{AsymmetricKey, Private, Rsa}; 313 use cng::asymmetric::EncryptionPadding; 314 use cng::key_blob::RsaKeyPrivatePayload; 315 316 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; 317 let key = AsymmetricKey::<Rsa, Private>::import_from_parts( 318 &provider, 319 &RsaKeyPrivatePayload { 320 modulus: n.value(), 321 pub_exp: e.value(), 322 prime1: p.value(), 323 prime2: q.value(), 324 } 325 )?; 326 327 let decrypted = key.decrypt(Some(EncryptionPadding::Pkcs1), c.value())?; 328 329 SessionKey::from(decrypted) 330 } 331 332 (mpi::PublicKey::ElGamal { .. }, 333 mpi::SecretKeyMaterial::ElGamal { .. }, 334 mpi::Ciphertext::ElGamal { .. }) => 335 return Err( 336 Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()), 337 338 (mpi::PublicKey::ECDH{ .. }, 339 mpi::SecretKeyMaterial::ECDH { .. }, 340 mpi::Ciphertext::ECDH { .. }) => 341 crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, 342 343 (public, secret, ciphertext) => 344 return Err(Error::InvalidOperation(format!( 345 "unsupported combination of key pair {:?}/{:?} \ 346 and ciphertext {:?}", 347 public, secret, ciphertext)).into()), 348 })) 349 } 350 } 351 352 impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { 353 /// Encrypts the given data with this key. encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext>354 pub fn encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { 355 use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; 356 use cng::asymmetric::{AsymmetricKey, Public, Rsa}; 357 use cng::key_blob::RsaKeyPublicPayload; 358 359 use PublicKeyAlgorithm::*; 360 361 #[allow(deprecated)] 362 match self.pk_algo() { 363 RSAEncryptSign | RSAEncrypt => { 364 // Extract the public recipient. 365 match self.mpis() { 366 mpi::PublicKey::RSA { e, n } => { 367 // The ciphertext has the length of the modulus. 368 let ciphertext_len = n.value().len(); 369 if data.len() + 11 > ciphertext_len { 370 return Err(Error::InvalidArgument( 371 "Plaintext data too large".into()).into()); 372 } 373 374 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; 375 let key = AsymmetricKey::<Rsa, Public>::import_from_parts( 376 &provider, 377 &RsaKeyPublicPayload { 378 modulus: n.value(), 379 pub_exp: e.value(), 380 } 381 )?; 382 383 let padding = win_crypto_ng::asymmetric::EncryptionPadding::Pkcs1; 384 let ciphertext = key.encrypt(Some(padding), data)?; 385 386 Ok(mpi::Ciphertext::RSA { 387 c: mpi::MPI::new(ciphertext.as_ref()), 388 }) 389 }, 390 pk => { 391 Err(Error::MalformedPacket( 392 format!( 393 "Key: Expected RSA public key, got {:?}", 394 pk)).into()) 395 }, 396 } 397 }, 398 ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), 399 algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()), 400 } 401 } 402 403 /// Verifies the given signature. verify(&self, sig: &packet::Signature, digest: &[u8]) -> Result<()>404 pub fn verify(&self, sig: &packet::Signature, digest: &[u8]) -> Result<()> { 405 use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; 406 use cng::asymmetric::{AsymmetricKey, Public, Rsa}; 407 use cng::asymmetric::ecc::NamedCurve; 408 use cng::asymmetric::signature::{Verifier, SignaturePadding}; 409 use cng::key_blob::RsaKeyPublicPayload; 410 411 use PublicKeyAlgorithm::*; 412 413 #[allow(deprecated)] 414 let ok = match (sig.pk_algo(), self.mpis(), sig.mpis()) { 415 (RSASign, mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) | 416 (RSAEncryptSign, mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { 417 // CNG accepts only full-size signatures. Since for RSA it's a 418 // big-endian number, just left-pad with zeroes as necessary. 419 let sig_diff = n.value().len().saturating_sub(s.value().len()); 420 let mut _s: Vec<u8> = vec![]; 421 let s = if sig_diff > 0 { 422 _s = [&vec![0u8; sig_diff], s.value()].concat(); 423 &_s 424 } else { 425 s.value() 426 }; 427 428 // CNG supports RSA keys that are at least 512 bit long. 429 // Since it just checks the MPI length rather than data itself, 430 // just pad it with zeroes as necessary. 431 let mut _n: Vec<u8> = vec![]; 432 let missing_size = 512usize.saturating_sub(n.value().len()); 433 let n = if missing_size > 0 { 434 _n = [&vec![0u8; missing_size], n.value()].concat(); 435 &_n 436 } else { 437 n.value() 438 }; 439 440 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; 441 let key = AsymmetricKey::<Rsa, Public>::import_from_parts( 442 &provider, 443 &RsaKeyPublicPayload { 444 modulus: n, 445 pub_exp: e.value(), 446 } 447 )?; 448 449 // As described in [Section 5.2.2 and 5.2.3 of RFC 4880], 450 // to verify the signature, we need to encode the 451 // signature data in a PKCS1-v1.5 packet. 452 // 453 // [Section 5.2.2 and 5.2.3 of RFC 4880]: 454 // https://tools.ietf.org/html/rfc4880#section-5.2.2 455 let hash = sig.hash_algo().try_into()?; 456 let padding = SignaturePadding::pkcs1(hash); 457 458 key.verify(digest, s, Some(padding)).map(|_| true)? 459 }, 460 (DSA, mpi:: PublicKey::DSA { y, p, q, g }, mpi::Signature::DSA { r, s }) => { 461 use win_crypto_ng::key_blob::{DsaKeyPublicPayload, DsaKeyPublicBlob}; 462 use win_crypto_ng::key_blob::{DsaKeyPublicV2Payload, DsaKeyPublicV2Blob}; 463 use win_crypto_ng::asymmetric::{Dsa, DsaPublicBlob}; 464 use win_crypto_ng::helpers::Blob; 465 466 if y.value().len() > 3072 / 8 { 467 return Err(Error::InvalidOperation( 468 "DSA keys are supported up to 3072-bits".to_string()).into() 469 ); 470 } 471 472 // CNG expects full-sized signatures 473 let field_sz = q.value().len(); 474 let r = [&vec![0u8; field_sz - r.value().len()], r.value()].concat(); 475 let s = [&vec![0u8; field_sz - s.value().len()], s.value()].concat(); 476 let signature = [r, s].concat(); 477 478 enum Version { V1, V2 } 479 // 1024-bit DSA keys are handled differently 480 let version = if y.value().len() <= 128 { Version::V1 } else { Version::V2 }; 481 482 let blob: DsaPublicBlob = match version { 483 Version::V1 => { 484 let mut group = [0; 20]; 485 assert!(q.value().len() >= 20); 486 &mut group[..q.value().len()].copy_from_slice(q.value()); 487 488 DsaPublicBlob::V1(Blob::<DsaKeyPublicBlob>::clone_from_parts( 489 &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB { 490 dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC, 491 cbKey: y.value().len() as u32, 492 Count: [0; 4], // unused 493 Seed: [0; 20], // unused 494 q: group, 495 }, 496 &DsaKeyPublicPayload { 497 modulus: p.value(), 498 generator: g.value(), 499 public: y.value(), 500 }, 501 )) 502 }, 503 Version::V2 => { 504 // https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L276-L282 505 let hash = match q.value().len() { 506 20 => 0, 507 32 => 1, 508 64 => 2, 509 _ => return Err(Error::InvalidOperation( 510 "CNG accepts DSA q with length of either length of 20, 32 or 64".into()) 511 .into()), 512 }; 513 514 // We don't use counter/seed values so set them to 0. 515 // CNG pre-checks that the seed is at least |Q| long, 516 // so we can't use an empty buffer here. 517 let (count, seed) = ([0x0; 4], vec![0x0; q.value().len()]); 518 519 DsaPublicBlob::V2(Blob::<DsaKeyPublicV2Blob>::clone_from_parts( 520 &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB_V2 { 521 dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC_V2, 522 Count: count, 523 // Size of the prime number q . 524 // Currently, if the key is less than 128 525 // bits, q is 20 bytes long. 526 // If the key exceeds 256 bits, q is 32 bytes long. 527 cbGroupSize: q.value().len() as u32, 528 cbKey: y.value().len() as u32, 529 // https://csrc.nist.gov/csrc/media/publications/fips/186/3/archive/2009-06-25/documents/fips_186-3.pdf 530 // Length of the seed used to generate the 531 // prime number q. 532 cbSeedLength: seed.len() as u32, 533 hashAlgorithm: hash, 534 standardVersion: 1, // FIPS 186-3 535 536 }, 537 &DsaKeyPublicV2Payload { 538 seed: &seed, 539 group: q.value(), 540 modulus: p.value(), 541 generator: g.value(), 542 public: y.value(), 543 }, 544 )) 545 }, 546 }; 547 548 use win_crypto_ng::asymmetric::Import; 549 let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Dsa)?; 550 let key = AsymmetricKey::<Dsa, Public>::import( 551 Dsa, 552 &provider, 553 blob 554 )?; 555 556 // CNG accepts only hash and Q of equal length. Either trim the 557 // digest or pad it with zeroes (since it's treated as a 558 // big-endian number). 559 // See https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs#L148. 560 let mut _digest = vec![]; 561 let digest = match std::cmp::Ord::cmp(&q.value().len(), &digest.len()) { 562 std::cmp::Ordering::Equal => digest, 563 std::cmp::Ordering::Less => &digest[..q.value().len()], 564 std::cmp::Ordering::Greater => { 565 let pad = vec![0; q.value().len() - digest.len()]; 566 _digest = [pad.as_ref(), digest].concat(); 567 &_digest 568 } 569 }; 570 571 key.verify(digest, &signature, None).map(|_| true)? 572 }, 573 (ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { s, r }) => 574 { 575 let (x, y) = q.decode_point(curve)?; 576 // CNG expects full-sized signatures 577 let field_sz = x.len(); 578 let r = [&vec![0u8; field_sz - r.value().len()], r.value()].concat(); 579 let s = [&vec![0u8; field_sz - s.value().len()], s.value()].concat(); 580 let signature = [r, s].concat(); 581 582 use cng::key_blob::EccKeyPublicPayload; 583 use cng::asymmetric::{ecc::{NistP256, NistP384, NistP521}, Ecdsa}; 584 585 // TODO: Improve CNG public API 586 match curve { 587 Curve::NistP256 => { 588 let provider = AsymmetricAlgorithm::open( 589 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP256) 590 )?; 591 let key = AsymmetricKey::<Ecdsa<NistP256>, Public>::import_from_parts( 592 &provider, 593 &EccKeyPublicPayload { x, y } 594 )?; 595 key.verify(digest, &signature, None).map(|_| true)? 596 }, 597 Curve::NistP384 => { 598 let provider = AsymmetricAlgorithm::open( 599 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP384) 600 )?; 601 let key = AsymmetricKey::<Ecdsa<NistP384>, Public>::import_from_parts( 602 &provider, 603 &EccKeyPublicPayload { x, y } 604 )?; 605 key.verify(digest, &signature, None).map(|_| true)? 606 }, 607 Curve::NistP521 => { 608 let provider = AsymmetricAlgorithm::open( 609 AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP521) 610 )?; 611 let key = AsymmetricKey::<Ecdsa<NistP521>, Public>::import_from_parts( 612 &provider, 613 &EccKeyPublicPayload { x, y } 614 )?; 615 key.verify(digest, &signature, None).map(|_| true)? 616 }, 617 _ => return Err( 618 Error::UnsupportedEllipticCurve(curve.clone()).into()), 619 } 620 }, 621 (EdDSA, mpi::PublicKey::EdDSA { curve, q }, mpi::Signature::EdDSA { r, s }) => { 622 // CNG doesn't support EdDSA, use ed25519-dalek instead 623 use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH}; 624 use ed25519_dalek::{Verifier}; 625 626 let (public, ..) = q.decode_point(&Curve::Ed25519)?; 627 assert_eq!(public.len(), 32); 628 629 let key = PublicKey::from_bytes(public).map_err(|e| { 630 Error::InvalidKey(e.to_string()) 631 })?; 632 633 // ed25519 expects full-sized signatures but OpenPGP allows 634 // for stripped leading zeroes, pad each part with zeroes. 635 let (r, s) = (r.value(), s.value()); 636 let signature = [ 637 [&vec![0u8; (SIGNATURE_LENGTH / 2) - r.len()], r].concat(), 638 [&vec![0u8; (SIGNATURE_LENGTH / 2) - s.len()], s].concat(), 639 ].concat(); 640 assert_eq!(signature.len(), SIGNATURE_LENGTH); 641 let mut sig_bytes = [0u8; 64]; 642 &mut sig_bytes[..].copy_from_slice(&*signature); 643 644 let signature = Signature::from(sig_bytes); 645 646 key.verify(digest, &signature) 647 .map(|_| true) 648 .map_err(|e| Error::BadSignature(e.to_string()))? 649 }, 650 _ => return Err(Error::MalformedPacket(format!( 651 "unsupported combination of algorithm {}, key {} and \ 652 signature {:?}.", 653 sig.pk_algo(), self.pk_algo(), sig.mpis())).into()), 654 }; 655 656 if ok { 657 Ok(()) 658 } else { 659 Err(Error::ManipulatedMessage.into()) 660 } 661 } 662 } 663 664 impl<R> Key4<SecretParts, R> 665 where 666 R: key::KeyRole, 667 { 668 /// Creates a new OpenPGP secret key packet for an existing X25519 key. 669 /// 670 /// The ECDH key will use hash algorithm `hash` and symmetric 671 /// algorithm `sym`. If one or both are `None` secure defaults 672 /// will be used. The key will have its creation date set to 673 /// `ctime` or the current time if `None` is given. import_secret_cv25519<H, S, T>( private_key: &[u8], hash: H, sym: S, ctime: T, ) -> Result<Self> where H: Into<Option<HashAlgorithm>>, S: Into<Option<SymmetricAlgorithm>>, T: Into<Option<SystemTime>>,674 pub fn import_secret_cv25519<H, S, T>( 675 private_key: &[u8], 676 hash: H, 677 sym: S, 678 ctime: T, 679 ) -> Result<Self> 680 where 681 H: Into<Option<HashAlgorithm>>, 682 S: Into<Option<SymmetricAlgorithm>>, 683 T: Into<Option<SystemTime>>, 684 { 685 use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId, Ecdh, Private}; 686 use cng::asymmetric::{AsymmetricKey, Export}; 687 use cng::asymmetric::ecc::{Curve25519, NamedCurve}; 688 689 let provider = AsymmetricAlgorithm::open( 690 AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519) 691 )?; 692 let key = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( 693 &provider, 694 private_key 695 )?; 696 let blob = key.export()?; 697 698 // Mark MPI as compressed point with 0x40 prefix. See 699 // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. 700 let mut public = [0u8; 1 + CURVE25519_SIZE]; 701 public[0] = 0x40; 702 &mut public[1..].copy_from_slice(blob.x()); 703 704 // Reverse the scalar. See 705 // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. 706 let mut private = blob.d().to_vec(); 707 private.reverse(); 708 709 Self::with_secret( 710 ctime.into().unwrap_or_else(SystemTime::now), 711 PublicKeyAlgorithm::ECDH, 712 mpi::PublicKey::ECDH { 713 curve: Curve::Cv25519, 714 hash: hash.into().unwrap_or(HashAlgorithm::SHA512), 715 sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256), 716 q: mpi::MPI::new(&public), 717 }, 718 mpi::SecretKeyMaterial::ECDH { scalar: private.into() }.into() 719 ) 720 } 721 722 /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. 723 /// 724 /// The key will have it's creation date set to `ctime` or the current time 725 /// if `None` is given. import_secret_ed25519<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>>,726 pub fn import_secret_ed25519<T>(private_key: &[u8], ctime: T) -> Result<Self> 727 where 728 T: Into<Option<SystemTime>>, 729 { 730 // CNG doesn't support EdDSA, use ed25519-dalek instead 731 use ed25519_dalek::{PublicKey, SecretKey}; 732 733 let private = SecretKey::from_bytes(private_key).map_err(|e| { 734 Error::InvalidKey(e.to_string()) 735 })?; 736 737 // Mark MPI as compressed point with 0x40 prefix. See 738 // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. 739 let mut public = [0u8; 1 + CURVE25519_SIZE]; 740 public[0] = 0x40; 741 &mut public[1..].copy_from_slice(Into::<PublicKey>::into(&private).as_bytes()); 742 743 Self::with_secret( 744 ctime.into().unwrap_or_else(SystemTime::now), 745 PublicKeyAlgorithm::EdDSA, 746 mpi::PublicKey::EdDSA { 747 curve: Curve::Ed25519, 748 q: mpi::MPI::new(&public) 749 }, 750 mpi::SecretKeyMaterial::EdDSA { 751 scalar: mpi::MPI::new(&private_key).into(), 752 }.into() 753 ) 754 } 755 756 /// Creates a new OpenPGP public key packet for an existing RSA key. 757 /// 758 /// The RSA key will use public exponent `e` and modulo `n`. The key will 759 /// have it's creation date set to `ctime` or the current time if `None` 760 /// is given. import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>>,761 pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> 762 where 763 T: Into<Option<SystemTime>>, 764 { 765 // RFC 4880: `p < q` 766 let (p, q) = if p < q { (p, q) } else { (q, p) }; 767 768 // CNG can't compute the public key from the private one, so do it ourselves 769 let big_p = BigUint::from_bytes_be(p); 770 let big_q = BigUint::from_bytes_be(q); 771 let n = big_p.clone() * big_q.clone(); 772 773 let big_d = BigUint::from_bytes_be(d); 774 let big_phi = (big_p.clone() - 1u32) * (big_q.clone() - 1u32); 775 let e = big_d.mod_inverse(big_phi) // e ≡ d⁻¹ (mod ) 776 .and_then(|x: BigInt| x.to_biguint()) 777 .ok_or_else(|| Error::MalformedMPI("RSA: `d` and `(p-1)(q-1)` aren't coprime".into()))?; 778 779 let u: BigUint = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q) 780 .and_then(|x: BigInt| x.to_biguint()) 781 .ok_or_else(|| Error::MalformedMPI("RSA: `p` and `q` aren't coprime".into()))?; 782 783 Self::with_secret( 784 ctime.into().unwrap_or_else(SystemTime::now), 785 PublicKeyAlgorithm::RSAEncryptSign, 786 mpi::PublicKey::RSA { 787 e: mpi::MPI::new(&e.to_bytes_be()), 788 n: mpi::MPI::new(&n.to_bytes_be()), 789 }, 790 mpi::SecretKeyMaterial::RSA { 791 d: mpi::MPI::new(d).into(), 792 p: mpi::MPI::new(p).into(), 793 q: mpi::MPI::new(q).into(), 794 u: mpi::MPI::new(&u.to_bytes_be()).into(), 795 }.into() 796 ) 797 } 798 799 /// Generates a new RSA key with a public modulos of size `bits`. generate_rsa(bits: usize) -> Result<Self>800 pub fn generate_rsa(bits: usize) -> Result<Self> { 801 use win_crypto_ng::asymmetric::{AsymmetricKey, Rsa}; 802 803 let blob = AsymmetricKey::builder(Rsa) 804 .key_bits(bits as u32) 805 .build()? 806 .export_full()?; 807 808 let public = mpi::PublicKey::RSA { 809 e: mpi::MPI::new(blob.pub_exp()).into(), 810 n: mpi::MPI::new(blob.modulus()).into(), 811 }; 812 813 let p = mpi::MPI::new(blob.prime1()); 814 let q = mpi::MPI::new(blob.prime2()); 815 // RSA prime generation in CNG returns them in arbitrary order but 816 // RFC 4880 expects `p < q` 817 let (p, q) = if p < q { (p, q) } else { (q, p) }; 818 // CNG `coeff` is `prime1`^-1 mod `prime2` so adjust for possible p,q reorder 819 let big_p = BigUint::from_bytes_be(p.value()); 820 let big_q = BigUint::from_bytes_be(q.value()); 821 let u = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q) 822 .and_then(|x: BigInt| x.to_biguint()) 823 .expect("CNG to generate a valid RSA key (where p, q are coprime)"); 824 825 let private = mpi::SecretKeyMaterial::RSA { 826 p: p.into(), 827 q: q.into(), 828 d: mpi::MPI::new(blob.priv_exp()).into(), 829 u: mpi::MPI::new(&u.to_bytes_be()).into(), 830 }; 831 832 Self::with_secret( 833 SystemTime::now(), 834 PublicKeyAlgorithm::RSAEncryptSign, 835 public, 836 private.into() 837 ) 838 } 839 840 /// Generates a new ECC key over `curve`. 841 /// 842 /// If `for_signing` is false a ECDH key, if it's true either a 843 /// EdDSA or ECDSA key is generated. Giving `for_signing == true` 844 /// and `curve == Cv25519` will produce an error. Similar for 845 /// `for_signing == false` and `curve == Ed25519`. 846 /// signing/encryption generate_ecc(for_signing: bool, curve: Curve) -> Result<Self>847 pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { 848 use crate::PublicKeyAlgorithm::*; 849 850 use cng::asymmetric::{ecc, Export}; 851 use cng::asymmetric::{AsymmetricKey, AsymmetricAlgorithmId, Ecdh}; 852 853 let (algo, public, private) = match (curve.clone(), for_signing) { 854 (Curve::NistP256, ..) | (Curve::NistP384, ..) | (Curve::NistP521, ..) => { 855 let (cng_curve, hash) = match curve { 856 Curve::NistP256 => (ecc::NamedCurve::NistP256, HashAlgorithm::SHA256), 857 Curve::NistP384 => (ecc::NamedCurve::NistP384, HashAlgorithm::SHA384), 858 Curve::NistP521 => (ecc::NamedCurve::NistP521, HashAlgorithm::SHA512), 859 _ => unreachable!() 860 }; 861 862 let ecc_algo = if for_signing { 863 AsymmetricAlgorithmId::Ecdsa(cng_curve) 864 } else { 865 AsymmetricAlgorithmId::Ecdh(cng_curve) 866 }; 867 868 let blob = AsymmetricKey::builder(ecc_algo).build()?.export()?; 869 let blob = match blob.try_into::<cng::key_blob::EccKeyPrivateBlob>() { 870 Ok(blob) => blob, 871 // Dynamic algorithm specified is either ECDSA or ECDH so 872 // exported blob should be of appropriate type 873 Err(..) => unreachable!() 874 }; 875 let field_sz = cng_curve.key_bits() as usize; 876 877 let q = mpi::MPI::new_point(blob.x(), blob.y(), field_sz); 878 let scalar = mpi::MPI::new(blob.d()); 879 880 if for_signing { 881 ( 882 ECDSA, 883 mpi::PublicKey::ECDSA { curve, q }, 884 mpi::SecretKeyMaterial::ECDSA { scalar: scalar.into() }, 885 ) 886 } else { 887 let sym = SymmetricAlgorithm::AES256; 888 ( 889 ECDH, 890 mpi::PublicKey::ECDH { curve, q, hash, sym }, 891 mpi::SecretKeyMaterial::ECDH { scalar: scalar.into() }, 892 ) 893 } 894 }, 895 (Curve::Cv25519, false) => { 896 let blob = AsymmetricKey::builder(Ecdh(ecc::Curve25519)).build()?.export()?; 897 898 // Mark MPI as compressed point with 0x40 prefix. See 899 // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. 900 let mut public = [0u8; 1 + CURVE25519_SIZE]; 901 public[0] = 0x40; 902 &mut public[1..].copy_from_slice(blob.x()); 903 904 // Reverse the scalar. See 905 // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. 906 let mut private: Protected = blob.d().into(); 907 private.reverse(); 908 909 ( 910 ECDH, 911 mpi::PublicKey::ECDH { 912 curve, 913 q: mpi::MPI::new(&public), 914 hash: HashAlgorithm::SHA256, 915 sym: SymmetricAlgorithm::AES256, 916 }, 917 mpi::SecretKeyMaterial::ECDH { scalar: private.into() } 918 ) 919 }, 920 (Curve::Ed25519, true) => { 921 // CNG doesn't support EdDSA, use ed25519-dalek instead 922 use ed25519_dalek::Keypair; 923 924 let mut rng = cng::random::RandomNumberGenerator::system_preferred(); 925 let Keypair { public, secret } = Keypair::generate(&mut rng); 926 927 let secret: Protected = secret.as_bytes().as_ref().into(); 928 929 // Mark MPI as compressed point with 0x40 prefix. See 930 // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. 931 let mut compressed_public = [0u8; 1 + CURVE25519_SIZE]; 932 compressed_public[0] = 0x40; 933 &mut compressed_public[1..].copy_from_slice(public.as_bytes()); 934 935 ( 936 EdDSA, 937 mpi::PublicKey::EdDSA { curve, q: mpi::MPI::new(&compressed_public) }, 938 mpi::SecretKeyMaterial::EdDSA { scalar: secret.into() }, 939 ) 940 }, 941 // TODO: Support Brainpool curves 942 (curve, ..) => { 943 return Err(Error::UnsupportedEllipticCurve(curve).into()); 944 } 945 }; 946 947 Self::with_secret(SystemTime::now(), algo, public, private.into()) 948 } 949 } 950