public override DSAParameters ExportParameters(bool includePrivateParameters) { byte[] dsaBlob = ExportKeyBlob(includePrivateParameters); KeyBlobMagicNumber magic = (KeyBlobMagicNumber)BitConverter.ToInt32(dsaBlob, 0); // Check the magic value in the key blob header. If the blob does not have the required magic, // then throw a CryptographicException. CheckMagicValueOfKey(magic, includePrivateParameters); unsafe { DSAParameters dsaParams = default; fixed(byte *pDsaBlob = dsaBlob) { int offset; if (magic == KeyBlobMagicNumber.BCRYPT_DSA_PUBLIC_MAGIC || magic == KeyBlobMagicNumber.BCRYPT_DSA_PRIVATE_MAGIC) { if (dsaBlob.Length < sizeof(BCRYPT_DSA_KEY_BLOB)) { throw ErrorCode.E_FAIL.ToCryptographicException(); } BCRYPT_DSA_KEY_BLOB *pBcryptBlob = (BCRYPT_DSA_KEY_BLOB *)pDsaBlob; // We now have a buffer laid out as follows: // BCRYPT_DSA_KEY_BLOB header // byte[cbKey] P // byte[cbKey] G // byte[cbKey] Y // -- Private only -- // byte[Sha1HashOutputSize] X offset = sizeof(KeyBlobMagicNumber) + sizeof(int); // skip Magic and cbKey // Read out a (V1) BCRYPT_DSA_KEY_BLOB structure. dsaParams.Counter = BinaryPrimitives.ReadInt32BigEndian(Interop.BCrypt.Consume(dsaBlob, ref offset, 4)); dsaParams.Seed = Interop.BCrypt.Consume(dsaBlob, ref offset, Sha1HashOutputSize); dsaParams.Q = Interop.BCrypt.Consume(dsaBlob, ref offset, Sha1HashOutputSize); Debug.Assert(offset == sizeof(BCRYPT_DSA_KEY_BLOB), $"Expected offset = sizeof(BCRYPT_DSA_KEY_BLOB), got {offset} != {sizeof(BCRYPT_DSA_KEY_BLOB)}"); dsaParams.P = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); dsaParams.G = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); dsaParams.Y = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); if (includePrivateParameters) { dsaParams.X = Interop.BCrypt.Consume(dsaBlob, ref offset, Sha1HashOutputSize); } } else { Debug.Assert(magic == KeyBlobMagicNumber.BCRYPT_DSA_PUBLIC_MAGIC_V2 || magic == KeyBlobMagicNumber.BCRYPT_DSA_PRIVATE_MAGIC_V2); if (dsaBlob.Length < sizeof(BCRYPT_DSA_KEY_BLOB_V2)) { throw ErrorCode.E_FAIL.ToCryptographicException(); } BCRYPT_DSA_KEY_BLOB_V2 *pBcryptBlob = (BCRYPT_DSA_KEY_BLOB_V2 *)pDsaBlob; // We now have a buffer laid out as follows: // BCRYPT_DSA_KEY_BLOB_V2 header // byte[cbSeedLength] Seed // byte[cbGroupSize] Q // byte[cbKey] P // byte[cbKey] G // byte[cbKey] Y // -- Private only -- // byte[cbGroupSize] X offset = sizeof(BCRYPT_DSA_KEY_BLOB_V2) - 4; //skip to Count[4] // Read out a BCRYPT_DSA_KEY_BLOB_V2 structure. dsaParams.Counter = BinaryPrimitives.ReadInt32BigEndian(Interop.BCrypt.Consume(dsaBlob, ref offset, 4)); Debug.Assert(offset == sizeof(BCRYPT_DSA_KEY_BLOB_V2), $"Expected offset = sizeof(BCRYPT_DSA_KEY_BLOB_V2), got {offset} != {sizeof(BCRYPT_DSA_KEY_BLOB_V2)}"); dsaParams.Seed = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbSeedLength); dsaParams.Q = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbGroupSize); dsaParams.P = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); dsaParams.G = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); dsaParams.Y = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbKey); if (includePrivateParameters) { dsaParams.X = Interop.BCrypt.Consume(dsaBlob, ref offset, pBcryptBlob->cbGroupSize); } } // If no counter/seed information is present, normalize Counter and Seed to 0/null to maintain parity with the CAPI version of DSA. if (dsaParams.Counter == -1) { dsaParams.Counter = 0; dsaParams.Seed = null; } Debug.Assert(offset == dsaBlob.Length, $"Expected offset = dsaBlob.Length, got {offset} != {dsaBlob.Length}"); return(dsaParams); } } }
private static unsafe void GenerateV1DsaBlob(out byte[] blob, DSAParameters parameters, int cbKey, bool includePrivate) { // We need to build a key blob structured as follows: // // BCRYPT_DSA_KEY_BLOB header // byte[cbKey] P // byte[cbKey] G // byte[cbKey] Y // -- Private only -- // byte[Sha1HashOutputSize] X int blobSize = sizeof(BCRYPT_DSA_KEY_BLOB) + cbKey + cbKey + cbKey; if (includePrivate) { blobSize += Sha1HashOutputSize; } blob = new byte[blobSize]; fixed(byte *pDsaBlob = &blob[0]) { // Build the header BCRYPT_DSA_KEY_BLOB *pBcryptBlob = (BCRYPT_DSA_KEY_BLOB *)pDsaBlob; pBcryptBlob->Magic = includePrivate ? KeyBlobMagicNumber.BCRYPT_DSA_PRIVATE_MAGIC : KeyBlobMagicNumber.BCRYPT_DSA_PUBLIC_MAGIC; pBcryptBlob->cbKey = cbKey; int offset = sizeof(KeyBlobMagicNumber) + sizeof(int); // skip Magic and cbKey if (parameters.Seed != null) { // The Seed length is hardcoded into BCRYPT_DSA_KEY_BLOB, so check it now we can give a nicer error message. if (parameters.Seed.Length != Sha1HashOutputSize) { throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_SeedRestriction_ShortKey); } Interop.BCrypt.EmitBigEndian(blob, ref offset, parameters.Counter); Interop.BCrypt.Emit(blob, ref offset, parameters.Seed); } else { // If Seed is not present, back fill both counter and seed with 0xff. Do not use parameters.Counter as CNG is more strict than CAPI and will reject // anything other than 0xffffffff. That could complicate efforts to switch usage of DSACryptoServiceProvider to DSACng. Interop.BCrypt.EmitByte(blob, ref offset, 0xff, Sha1HashOutputSize + sizeof(int)); } // The Q length is hardcoded into BCRYPT_DSA_KEY_BLOB, so check it now we can give a nicer error message. if (parameters.Q !.Length != Sha1HashOutputSize) { throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_QRestriction_ShortKey); } Interop.BCrypt.Emit(blob, ref offset, parameters.Q); Debug.Assert(offset == sizeof(BCRYPT_DSA_KEY_BLOB), $"Expected offset = sizeof(BCRYPT_DSA_KEY_BLOB), got {offset} != {sizeof(BCRYPT_DSA_KEY_BLOB)}"); Interop.BCrypt.Emit(blob, ref offset, parameters.P !); Interop.BCrypt.Emit(blob, ref offset, parameters.G !); Interop.BCrypt.Emit(blob, ref offset, parameters.Y !); if (includePrivate) { Interop.BCrypt.Emit(blob, ref offset, parameters.X !); } Debug.Assert(offset == blobSize, $"Expected offset = blobSize, got {offset} != {blobSize}"); } }