1 // ==++== 2 // 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // 5 // ==--== 6 7 using System; 8 using System.Diagnostics; 9 using System.IO; 10 using System.Security; 11 using System.Security.Permissions; 12 using System.Diagnostics.Contracts; 13 using Microsoft.Win32.SafeHandles; 14 15 namespace System.Security.Cryptography { 16 /// <summary> 17 /// Wrapper for NCrypt's implementation of elliptic curve DSA 18 /// </summary> 19 [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] 20 public sealed partial class ECDsaCng : ECDsa { 21 #if MONO ECDsaCng()22 public ECDsaCng() : this(521) { 23 } 24 ECDsaCng(int keySize)25 public ECDsaCng(int keySize) { 26 throw new NotImplementedException (); 27 } 28 29 [SecuritySafeCritical] ECDsaCng(CngKey key)30 public ECDsaCng(CngKey key) { 31 throw new NotImplementedException (); 32 } 33 ECDsaCng(ECCurve curve)34 public ECDsaCng(ECCurve curve) { 35 throw new NotImplementedException (); 36 } 37 38 public CngAlgorithm HashAlgorithm { get; set; } 39 40 public CngKey Key { 41 get { 42 throw new NotImplementedException (); 43 } 44 45 private set { 46 throw new NotImplementedException (); 47 } 48 } 49 SignHash(byte[] hash)50 public override byte[] SignHash(byte[] hash) { 51 throw new NotImplementedException(); 52 } 53 VerifyHash(byte[] hash, byte[] signature)54 public override bool VerifyHash(byte[] hash, byte[] signature) { 55 throw new NotImplementedException(); 56 } 57 FromXmlString(string xml, ECKeyXmlFormat format)58 public void FromXmlString (string xml, ECKeyXmlFormat format) { 59 throw new NotImplementedException(); 60 } 61 SignData(byte[] data)62 public byte[] SignData (byte[] data) { 63 throw new NotImplementedException(); 64 } 65 SignData(System.IO.Stream data)66 public byte[] SignData (System.IO.Stream data) { 67 throw new NotImplementedException(); 68 } 69 SignData(byte[] data, int offset, int count)70 public byte[] SignData (byte[] data, int offset, int count) { 71 throw new NotImplementedException(); 72 } 73 ToXmlString(ECKeyXmlFormat format)74 public string ToXmlString (ECKeyXmlFormat format) { 75 throw new NotImplementedException(); 76 } 77 VerifyData(byte[] data, byte[] signature)78 public bool VerifyData (byte[] data, byte[] signature) { 79 throw new NotImplementedException(); 80 } 81 VerifyData(System.IO.Stream data, byte[] signature)82 public bool VerifyData (System.IO.Stream data, byte[] signature) { 83 throw new NotImplementedException(); 84 } 85 VerifyData(byte[] data, int offset, int count, byte[] signature)86 public bool VerifyData (byte[] data, int offset, int count, byte[] signature) { 87 throw new NotImplementedException(); 88 } 89 #else 90 private static KeySizes[] s_legalKeySizes = new KeySizes[] { new KeySizes(256, 384, 128), new KeySizes(521, 521, 0) }; 91 92 private CngKey m_key; 93 private CngAlgorithm m_hashAlgorithm = CngAlgorithm.Sha256; 94 95 // 96 // Constructors 97 // 98 99 public ECDsaCng() : this(521) { 100 Contract.Ensures(LegalKeySizesValue != null); 101 } 102 103 public ECDsaCng(int keySize) { 104 Contract.Ensures(LegalKeySizesValue != null); 105 106 if (!NCryptNative.NCryptSupported) { 107 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported)); 108 } 109 110 LegalKeySizesValue = s_legalKeySizes; 111 KeySize = keySize; 112 } 113 114 public ECDsaCng(ECCurve curve) { 115 // GenerateKey will already do all of the validation we need. 116 GenerateKey(curve); 117 } 118 119 [SecuritySafeCritical] 120 public ECDsaCng(CngKey key) { 121 Contract.Ensures(LegalKeySizesValue != null); 122 Contract.Ensures(m_key != null && IsEccAlgorithmGroup(m_key.AlgorithmGroup)); 123 124 if (key == null) { 125 throw new ArgumentNullException("key"); 126 } 127 if (!IsEccAlgorithmGroup(key.AlgorithmGroup)) { 128 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDsaRequiresECDsaKey), "key"); 129 } 130 131 if (!NCryptNative.NCryptSupported) { 132 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported)); 133 } 134 135 LegalKeySizesValue = s_legalKeySizes; 136 137 // Make a copy of the key so that we continue to work if it gets disposed before this algorithm 138 // 139 // This requires an assert for UnmanagedCode since we'll need to access the raw handles of the key 140 // and the handle constructor of CngKey. The assert is safe since ECDsaCng will never expose the 141 // key handles to calling code (without first demanding UnmanagedCode via the Handle property of 142 // CngKey). 143 // 144 // We also need to dispose of the key handle since CngKey.Handle returns a duplicate 145 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); 146 using (SafeNCryptKeyHandle keyHandle = key.Handle) { 147 Key = CngKey.Open(keyHandle, key.IsEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None); 148 } 149 CodeAccessPermission.RevertAssert(); 150 151 // Our LegalKeySizes value stores the values that we encoded as being the correct 152 // legal key size limitations for this algorithm, as documented on MSDN. 153 // 154 // But on a new OS version we might not question if our limit is accurate, or MSDN 155 // could have been innacurate to start with. 156 // 157 // Since the key is already loaded, we know that Windows thought it to be valid; 158 // therefore we should set KeySizeValue directly to bypass the LegalKeySizes conformance 159 // check. 160 // 161 // For RSA there are known cases where this change matters. RSACryptoServiceProvider can 162 // create a 384-bit RSA key, which we consider too small to be legal. It can also create 163 // a 1032-bit RSA key, which we consider illegal because it doesn't match our 64-bit 164 // alignment requirement. (In both cases Windows loads it just fine) 165 KeySizeValue = m_key.KeySize; 166 } 167 168 /// <summary> 169 /// Hash algorithm to use when generating a signature over arbitrary data 170 /// </summary> 171 public CngAlgorithm HashAlgorithm { 172 get { 173 Contract.Ensures(Contract.Result<CngAlgorithm>() != null); 174 return m_hashAlgorithm; 175 } 176 177 set { 178 Contract.Ensures(m_hashAlgorithm != null); 179 180 if (value == null) { 181 throw new ArgumentNullException("value"); 182 } 183 184 m_hashAlgorithm = value; 185 } 186 } 187 188 /// <summary> 189 /// Key to use for signing 190 /// </summary> 191 public CngKey Key { 192 get { 193 Contract.Ensures(Contract.Result<CngKey>() != null); 194 Contract.Ensures(IsEccAlgorithmGroup(Contract.Result<CngKey>().AlgorithmGroup)); 195 Contract.Ensures(m_key != null && IsEccAlgorithmGroup(m_key.AlgorithmGroup)); 196 197 // If the size of the key no longer matches our stored value, then we need to replace it with 198 // a new key of the correct size. 199 if (m_key != null && m_key.KeySize != KeySize) { 200 m_key.Dispose(); 201 m_key = null; 202 } 203 204 if (m_key == null) { 205 // Map the current key size to a CNG algorithm name 206 CngAlgorithm algorithm = null; 207 switch (KeySize) { 208 case 256: 209 algorithm = CngAlgorithm.ECDsaP256; 210 break; 211 212 case 384: 213 algorithm = CngAlgorithm.ECDsaP384; 214 break; 215 216 case 521: 217 algorithm = CngAlgorithm.ECDsaP521; 218 break; 219 220 default: 221 Debug.Assert(false, "Illegal key size set"); 222 break; 223 } 224 225 m_key = CngKey.Create(algorithm); 226 } 227 228 return m_key; 229 } 230 231 private set { 232 Contract.Requires(value != null); 233 Contract.Ensures(m_key != null && IsEccAlgorithmGroup(m_key.AlgorithmGroup)); 234 235 if (!IsEccAlgorithmGroup(value.AlgorithmGroup)) { 236 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDsaRequiresECDsaKey)); 237 } 238 239 if (m_key != null) { 240 m_key.Dispose(); 241 } 242 243 // 244 // We do not duplicate the handle because the only time the user has access to the key itself 245 // to dispose underneath us is when they construct via the CngKey constructor, which does a 246 // copy. Otherwise all key lifetimes are controlled directly by the ECDsaCng class. 247 // 248 249 m_key = value; 250 251 // Our LegalKeySizes value stores the values that we encoded as being the correct 252 // legal key size limitations for this algorithm, as documented on MSDN. 253 // 254 // But on a new OS version we might not question if our limit is accurate, or MSDN 255 // could have been innacurate to start with. 256 // 257 // Since the key is already loaded, we know that Windows thought it to be valid; 258 // therefore we should set KeySizeValue directly to bypass the LegalKeySizes conformance 259 // check. 260 // 261 // For RSA there are known cases where this change matters. RSACryptoServiceProvider can 262 // create a 384-bit RSA key, which we consider too small to be legal. It can also create 263 // a 1032-bit RSA key, which we consider illegal because it doesn't match our 64-bit 264 // alignment requirement. (In both cases Windows loads it just fine) 265 KeySizeValue = m_key.KeySize; 266 } 267 } 268 269 /// <summary> 270 /// Clean up the algorithm 271 /// </summary> 272 protected override void Dispose(bool disposing) { 273 try { 274 if (m_key != null) { 275 m_key.Dispose(); 276 } 277 } 278 finally { 279 base.Dispose(disposing); 280 } 281 } 282 283 // 284 // XML Import 285 // 286 // #ECCXMLFormat 287 // 288 // There is currently not a standard XML format for ECC keys, so we will not implement the default 289 // To/FromXmlString so that we're not tied to one format when a standard one does exist. Instead we'll 290 // use an overload which allows the user to specify the format they'd like to serialize into. 291 // 292 // See code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about 293 // the currently supported format. 294 // 295 296 public override void FromXmlString(string xmlString) { 297 throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired)); 298 } 299 300 public void FromXmlString(string xml, ECKeyXmlFormat format) { 301 if (xml == null) { 302 throw new ArgumentNullException("xml"); 303 } 304 if (format != ECKeyXmlFormat.Rfc4050) { 305 throw new ArgumentOutOfRangeException("format"); 306 } 307 308 bool isEcdh; 309 ECParameters parameters = Rfc4050KeyFormatter.FromXml(xml, out isEcdh); 310 311 // .NET 4.6.2 allowed ECDsaCng to wrap ECDH keys because of interop with non-Windows PFX files. 312 // As a result XML marked as ECDiffieHellman loaded just fine, so no check should be done on the 313 // key type. 314 ImportParameters(parameters); 315 } 316 317 // 318 // Signature generation 319 // 320 321 public byte[] SignData(byte[] data) { 322 Contract.Ensures(Contract.Result<byte[]>() != null); 323 324 if (data == null) { 325 throw new ArgumentNullException("data"); 326 } 327 328 return SignData(data, 0, data.Length); 329 } 330 331 [SecuritySafeCritical] 332 public byte[] SignData(byte[] data, int offset, int count) { 333 Contract.Ensures(Contract.Result<byte[]>() != null); 334 335 if (data == null) { 336 throw new ArgumentNullException("data"); 337 } 338 if (offset < 0 || offset > data.Length) { 339 throw new ArgumentOutOfRangeException("offset"); 340 } 341 if (count < 0 || count > data.Length - offset) { 342 throw new ArgumentOutOfRangeException("count"); 343 } 344 345 using (BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(HashAlgorithm, BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 346 hashAlgorithm.HashCore(data, offset, count); 347 byte[] hashValue = hashAlgorithm.HashFinal(); 348 349 return SignHash(hashValue); 350 } 351 } 352 353 [SecuritySafeCritical] 354 public byte[] SignData(Stream data) { 355 Contract.Ensures(Contract.Result<byte[]>() != null); 356 357 if (data == null) { 358 throw new ArgumentNullException("data"); 359 } 360 361 using (BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(HashAlgorithm, BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 362 hashAlgorithm.HashStream(data); 363 byte[] hashValue = hashAlgorithm.HashFinal(); 364 365 return SignHash(hashValue); 366 } 367 } 368 369 [SecuritySafeCritical] 370 public override byte[] SignHash(byte[] hash) { 371 if (hash == null) { 372 throw new ArgumentNullException("hash"); 373 } 374 375 // Make sure we're allowed to sign using this key 376 KeyContainerPermission permission = Key.BuildKeyContainerPermission(KeyContainerPermissionFlags.Sign); 377 if (permission != null) { 378 permission.Demand(); 379 } 380 381 // Now that know we have permission to use this key for signing, pull the key value out, which 382 // will require unmanaged code permission 383 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); 384 385 // This looks odd, but the key handle is actually a duplicate so we need to dispose it 386 using (SafeNCryptKeyHandle keyHandle = Key.Handle) { 387 CodeAccessPermission.RevertAssert(); 388 389 return NCryptNative.SignHash(keyHandle, hash); 390 } 391 } 392 393 // 394 // XML Export 395 // 396 // See code:System.Security.Cryptography.ECDsaCng#ECCXMLFormat and 397 // code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about 398 // XML serialization of elliptic curve keys 399 // 400 401 public override string ToXmlString(bool includePrivateParameters) { 402 throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired)); 403 } 404 405 public string ToXmlString(ECKeyXmlFormat format) { 406 Contract.Ensures(Contract.Result<string>() != null); 407 408 if (format != ECKeyXmlFormat.Rfc4050) { 409 throw new ArgumentOutOfRangeException("format"); 410 } 411 412 ECParameters ecParams = ExportParameters(false); 413 return Rfc4050KeyFormatter.ToXml(ecParams, isEcdh: false); 414 } 415 416 // 417 // Signature verification 418 // 419 420 public bool VerifyData(byte[] data, byte[] signature) { 421 if (data == null) { 422 throw new ArgumentNullException("data"); 423 } 424 425 return VerifyData(data, 0, data.Length, signature); 426 } 427 428 [SecuritySafeCritical] 429 public bool VerifyData(byte[] data, int offset, int count, byte[] signature) { 430 if (data == null) { 431 throw new ArgumentNullException("data"); 432 } 433 if (offset < 0 || offset > data.Length) { 434 throw new ArgumentOutOfRangeException("offset"); 435 } 436 if (count < 0 || count > data.Length - offset) { 437 throw new ArgumentOutOfRangeException("count"); 438 } 439 if (signature == null) { 440 throw new ArgumentNullException("signature"); 441 } 442 443 using (BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(HashAlgorithm, BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 444 hashAlgorithm.HashCore(data, offset, count); 445 byte[] hashValue = hashAlgorithm.HashFinal(); 446 447 return VerifyHash(hashValue, signature); 448 } 449 } 450 451 [SecuritySafeCritical] 452 public bool VerifyData(Stream data, byte[] signature) { 453 if (data == null) { 454 throw new ArgumentNullException("data"); 455 } 456 if (signature == null) { 457 throw new ArgumentNullException("signature"); 458 } 459 460 using (BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(HashAlgorithm, BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 461 hashAlgorithm.HashStream(data); 462 byte[] hashValue = hashAlgorithm.HashFinal(); 463 464 return VerifyHash(hashValue, signature); 465 } 466 } 467 468 [SecuritySafeCritical] 469 public override bool VerifyHash(byte[] hash, byte[] signature) { 470 if (hash == null) { 471 throw new ArgumentNullException("hash"); 472 } 473 if (signature == null) { 474 throw new ArgumentNullException("signature"); 475 } 476 477 // We need to get the raw key handle to verify the signature. Asserting here is safe since verifiation 478 // is not a protected operation, and we do not expose the handle to the user code. 479 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); 480 481 // This looks odd, but Key.Handle is really a duplicate so we need to dispose it 482 using (SafeNCryptKeyHandle keyHandle = Key.Handle) { 483 CodeAccessPermission.RevertAssert(); 484 485 return NCryptNative.VerifySignature(keyHandle, hash, signature); 486 } 487 } 488 489 public override void GenerateKey(ECCurve curve) { 490 curve.Validate(); 491 492 if (m_key != null) { 493 m_key.Dispose(); 494 m_key = null; 495 } 496 497 CngKey newKey = CngKey.Create(curve, name => CngKey.EcdsaCurveNameToAlgorithm(name)); 498 m_key = newKey; 499 KeySizeValue = newKey.KeySize; 500 } 501 502 /// <summary> 503 /// Helper property to get the NCrypt key handle 504 /// </summary> 505 private SafeNCryptKeyHandle KeyHandle { 506 [SecuritySafeCritical] 507 get { return Key.Handle; } 508 } 509 510 protected override byte[] HashData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) { 511 // we're sealed and the base should have checked this before calling us 512 Debug.Assert(data != null); 513 Debug.Assert(offset >= 0 && offset <= data.Length); 514 Debug.Assert(count >= 0 && count <= data.Length - offset); 515 Debug.Assert(!String.IsNullOrEmpty(hashAlgorithm.Name)); 516 517 using (BCryptHashAlgorithm hasher = new BCryptHashAlgorithm(new CngAlgorithm(hashAlgorithm.Name), BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 518 hasher.HashCore(data, offset, count); 519 return hasher.HashFinal(); 520 } 521 } 522 523 protected override byte[] HashData(Stream data, HashAlgorithmName hashAlgorithm) { 524 // we're sealed and the base should have checked this before calling us 525 Debug.Assert(data != null); 526 Debug.Assert(!String.IsNullOrEmpty(hashAlgorithm.Name)); 527 528 using (BCryptHashAlgorithm hasher = new BCryptHashAlgorithm(new CngAlgorithm(hashAlgorithm.Name), BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) { 529 hasher.HashStream(data); 530 return hasher.HashFinal(); 531 } 532 } 533 534 private static bool IsEccAlgorithmGroup(CngAlgorithmGroup algorithmGroup) 535 { 536 // Sometimes, when reading from certificates, ECDSA keys get identified as ECDH. 537 // Windows allows the ECDH keys to perform both key exchange (ECDH) and signing (ECDSA), 538 // so either value is acceptable for the ECDSA wrapper object. 539 // 540 // It is worth noting, however, that ECDSA-identified keys cannot be used for key exchange (ECDH) in CNG. 541 return algorithmGroup == CngAlgorithmGroup.ECDsa || algorithmGroup == CngAlgorithmGroup.ECDiffieHellman; 542 } 543 #endif 544 } 545 } 546