| File: System\Security\Cryptography\CapiHelper.Shared.cs | Web Access |
| Project: src\runtime\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO; using Internal.Cryptography; namespace System.Security.Cryptography { internal static partial class CapiHelper { internal const int S_OK = 0; internal const int E_FAIL = unchecked((int)0x80004005); //Leaving these constants same as they are defined in Windows internal const int ALG_TYPE_DSS = (1 << 9); internal const int ALG_TYPE_RSA = (2 << 9); internal const int ALG_TYPE_BLOCK = (3 << 9); internal const int ALG_CLASS_SIGNATURE = (1 << 13); internal const int ALG_CLASS_DATA_ENCRYPT = (3 << 13); internal const int ALG_CLASS_KEY_EXCHANGE = (5 << 13); internal const int CALG_RSA_SIGN = (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | 0); internal const int CALG_DSS_SIGN = (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | 0); internal const int CALG_RSA_KEYX = (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RSA | 0); internal const int ALG_CLASS_HASH = (4 << 13); internal const int ALG_TYPE_ANY = (0); internal const int SIMPLEBLOB = 0x1; internal const int PUBLICKEYBLOB = 0x6; internal const int PRIVATEKEYBLOB = 0x7; internal const int PLAINTEXTKEYBLOB = 0x8; internal const byte BLOBHEADER_CURRENT_BVERSION = 0x2; internal const int CRYPT_BLOB_VER3 = 0x00000080; // export version 3 of a blob type internal const int RSA_PUB_MAGIC = 0x31415352; internal const int RSA_PRIV_MAGIC = 0x32415352; // Provider type to use by default for RSA operations. We want to use RSA-AES CSP // since it enables access to SHA-2 operations. All currently supported OSes support RSA-AES. internal const int DefaultRsaProviderType = (int)ProviderType.PROV_RSA_AES; internal enum CryptKeyError : uint { NTE_NO_KEY = 0x8009000D, // Key does not exist. NTE_BAD_KEYSET = 0x80090016, // Keyset does not exist. NTE_KEYSET_NOT_DEF = 0x80090019, // The keyset is not defined. NTE_BAD_KEY_STATE = 0x8009000B, NTE_BAD_KEYSET_PARAM = 0x8009001F, NTE_KEYSET_ENTRY_BAD = 0x8009001A, NTE_FILENOTFOUND = 0x80070002, NTE_BAD_KEY = 0x80090003, NTE_BAD_FLAGS = 0x80090009, NTE_BAD_DATA = 0x80090005, NTE_BAD_HASH = 0x80090003, } /// <summary> /// dwProvType is fourth parameter in the CryptAcquireContext and it uses following /// </summary> internal enum ProviderType : int { PROV_RSA_FULL = 1, PROV_DSS_DH = 13, PROV_RSA_AES = 24 } /// <summary> /// Helper for RsaCryptoServiceProvider.ImportParameters() /// </summary> internal static byte[] ToKeyBlob(this RSAParameters rsaParameters) { // Validate the RSA structure first. if (rsaParameters.Modulus == null) throw GetBadDataException(); if (rsaParameters.Exponent == null || rsaParameters.Exponent.Length > 4) throw GetBadDataException(); int modulusLength = rsaParameters.Modulus.Length; int halfModulusLength = (modulusLength + 1) / 2; // We assume that if P != null, then so are Q, DP, DQ, InverseQ and D if (rsaParameters.P != null) { if (rsaParameters.P.Length != halfModulusLength) throw GetBadDataException(); if (rsaParameters.Q == null || rsaParameters.Q.Length != halfModulusLength) throw GetBadDataException(); if (rsaParameters.DP == null || rsaParameters.DP.Length != halfModulusLength) throw GetBadDataException(); if (rsaParameters.DQ == null || rsaParameters.DQ.Length != halfModulusLength) throw GetBadDataException(); if (rsaParameters.InverseQ == null || rsaParameters.InverseQ.Length != halfModulusLength) throw GetBadDataException(); if (rsaParameters.D == null || rsaParameters.D.Length != modulusLength) throw GetBadDataException(); } bool isPrivate = (rsaParameters.P != null && rsaParameters.P.Length != 0); MemoryStream ms = new MemoryStream(); BinaryWriter bw = new BinaryWriter(ms); // Write out the BLOBHEADER bw.Write((byte)(isPrivate ? PRIVATEKEYBLOB : PUBLICKEYBLOB)); // BLOBHEADER.bType bw.Write((byte)(BLOBHEADER_CURRENT_BVERSION)); // BLOBHEADER.bVersion bw.Write((ushort)0); // BLOBHEADER.wReserved bw.Write((uint)CapiHelper.CALG_RSA_KEYX); // BLOBHEADER.aiKeyAlg // Write the RSAPubKey header bw.Write((int)(isPrivate ? RSA_PRIV_MAGIC : RSA_PUB_MAGIC)); // RSAPubKey.magic bw.Write((uint)(modulusLength * 8)); // RSAPubKey.bitLen uint expAsDword = 0; for (int i = 0; i < rsaParameters.Exponent.Length; i++) { expAsDword <<= 8; expAsDword |= rsaParameters.Exponent[i]; } bw.Write((uint)expAsDword); // RSAPubKey.pubExp bw.WriteReversed(rsaParameters.Modulus); // Copy over the modulus for both public and private if (isPrivate) { bw.WriteReversed(rsaParameters.P!); bw.WriteReversed(rsaParameters.Q!); bw.WriteReversed(rsaParameters.DP!); bw.WriteReversed(rsaParameters.DQ!); bw.WriteReversed(rsaParameters.InverseQ!); bw.WriteReversed(rsaParameters.D!); } bw.Flush(); byte[] key = ms.ToArray(); return key; } /// <summary> /// Write out a byte array in reverse order. /// </summary> private static void WriteReversed(this BinaryWriter bw, byte[] bytes) { byte[] reversedBytes = bytes.CloneByteArray(); Array.Reverse(reversedBytes); bw.Write(reversedBytes); return; } /// <summary> /// Helper for RsaCryptoServiceProvider.ExportParameters() /// </summary> internal static RSAParameters ToRSAParameters(this byte[] cspBlob, bool includePrivateParameters) { try { BinaryReader br = new BinaryReader(new MemoryStream(cspBlob)); br.ReadByte(); // BLOBHEADER.bType: Expected to be 0x6 (PUBLICKEYBLOB) or 0x7 (PRIVATEKEYBLOB), though there's no check for backward compat reasons. br.ReadByte(); // BLOBHEADER.bVersion: Expected to be 0x2, though there's no check for backward compat reasons. br.ReadUInt16(); // BLOBHEADER.wReserved int algId = br.ReadInt32(); // BLOBHEADER.aiKeyAlg if (algId != CALG_RSA_KEYX && algId != CALG_RSA_SIGN) throw new PlatformNotSupportedException(); // The FCall this code was ported from supports other algid's but we're only porting what we use. br.ReadInt32(); // RSAPubKey.magic: Expected to be 0x31415352 ('RSA1') or 0x32415352 ('RSA2') int bitLen = br.ReadInt32(); // RSAPubKey.bitLen int modulusLength = bitLen / 8; int halfModulusLength = (modulusLength + 1) / 2; uint expAsDword = br.ReadUInt32(); RSAParameters rsaParameters = default; rsaParameters.Exponent = ExponentAsBytes(expAsDword); rsaParameters.Modulus = br.ReadReversed(modulusLength); if (includePrivateParameters) { rsaParameters.P = br.ReadReversed(halfModulusLength); rsaParameters.Q = br.ReadReversed(halfModulusLength); rsaParameters.DP = br.ReadReversed(halfModulusLength); rsaParameters.DQ = br.ReadReversed(halfModulusLength); rsaParameters.InverseQ = br.ReadReversed(halfModulusLength); rsaParameters.D = br.ReadReversed(modulusLength); } return rsaParameters; } catch (EndOfStreamException) { // For compat reasons, we throw an E_FAIL CrytoException if CAPI returns a smaller blob than expected. // For compat reasons, we ignore the extra bits if the CAPI returns a larger blob than expected. throw GetEFailException(); } } internal static byte GetKeyBlobHeaderVersion(byte[] cspBlob) { if (cspBlob.Length < 8) throw new EndOfStreamException(); // Simulate same exception as ReadKeyBlobHeader would throw // Second byte is BLOBHEADER.bVersion: Expected to be 0x2 or 0x3 for DSA. return cspBlob[1]; } /// <summary> /// Helper for converting a UInt32 exponent to bytes. /// </summary> private static byte[] ExponentAsBytes(uint exponent) { if (exponent <= 0xFF) { return new[] { (byte)exponent }; } else if (exponent <= 0xFFFF) { unchecked { return new[] { (byte)(exponent >> 8), (byte)(exponent) }; } } else if (exponent <= 0xFFFFFF) { unchecked { return new[] { (byte)(exponent >> 16), (byte)(exponent >> 8), (byte)(exponent) }; } } else { return new[] { (byte)(exponent >> 24), (byte)(exponent >> 16), (byte)(exponent >> 8), (byte)(exponent) }; } } /// <summary> /// Read in a byte array in reverse order. /// </summary> private static byte[] ReadReversed(this BinaryReader br, int count) { byte[] data = br.ReadBytes(count); Array.Reverse(data); return data; } internal static byte[]? TrimLargeIV(byte[]? currentIV, int blockSizeInBits) { int blockSizeBytes = checked((blockSizeInBits + 7) / 8); if (currentIV?.Length > blockSizeBytes) { byte[] tmp = new byte[blockSizeBytes]; Buffer.BlockCopy(currentIV, 0, tmp, 0, tmp.Length); return tmp; } return currentIV; } } }