private bool TryCopyCharacterString( Asn1Tag expectedTag, UniversalTagNumber universalTagNumber, Text.Encoding encoding, Span <char> destination, out int charsWritten) { byte[]? rented = null; // T-REC-X.690-201508 sec 8.23.3, all character strings are encoded as octet strings. ReadOnlySpan <byte> contents = GetOctetStringContents( expectedTag, universalTagNumber, out int bytesRead, ref rented); try { bool copied = TryCopyCharacterString( contents, destination, encoding, out charsWritten); if (copied) { _data = _data.Slice(bytesRead); } return(copied); } finally { if (rented != null) { CryptoPool.Return(rented, contents.Length); } } }
private static byte[]? DecryptContent( ReadOnlyMemory <byte> encryptedContent, byte[] cek, AlgorithmIdentifierAsn contentEncryptionAlgorithm, out Exception?exception) { exception = null; int encryptedContentLength = encryptedContent.Length; byte[]? encryptedContentArray = CryptoPool.Rent(encryptedContentLength); try { encryptedContent.CopyTo(encryptedContentArray); using (SymmetricAlgorithm alg = OpenAlgorithm(contentEncryptionAlgorithm)) using (ICryptoTransform decryptor = alg.CreateDecryptor(cek, alg.IV)) { // If we extend this library to accept additional algorithm providers // then a different array pool needs to be used. Debug.Assert(alg.GetType().Assembly == typeof(Aes).Assembly); return(decryptor.OneShot( encryptedContentArray, 0, encryptedContentLength)); } } catch (CryptographicException e) { exception = e; return(null); } finally { CryptoPool.Return(encryptedContentArray, encryptedContentLength); encryptedContentArray = null; } }
/// <summary> /// Reads the next value as a UTCTime with a specified tag. /// </summary> /// <param name="expectedTag">The tag to check for before reading.</param> /// <param name="twoDigitYearMax"> /// The largest year to represent with this value. /// The default value, 2049, represents the 1950-2049 range for X.509 certificates. /// </param> /// <returns> /// a DateTimeOffset representing the value encoded in the UTCTime. /// </returns> /// <exception cref="CryptographicException"> /// the next value does not have the correct tag --OR-- /// the length encoding is not valid under the current encoding rules --OR-- /// the contents are not valid under the current encoding rules /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is /// <see cref="TagClass.Universal"/>, but /// <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for /// the method /// </exception> /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/> public DateTimeOffset ReadUtcTime(Asn1Tag expectedTag, int twoDigitYearMax = 2049) { // T-REC-X.680-201510 sec 47.3 says it is IMPLICIT VisibleString, which means // that BER is allowed to do complex constructed forms. // The full allowed formats (T-REC-X.680-201510 sec 47.3) // YYMMDDhhmmZ (a, b1, c1) // YYMMDDhhmm+hhmm (a, b1, c2+) // YYMMDDhhmm-hhmm (a, b1, c2-) // YYMMDDhhmmssZ (a, b2, c1) // YYMMDDhhmmss+hhmm (a, b2, c2+) // YYMMDDhhmmss-hhmm (a, b2, c2-) // CER and DER are restricted to YYMMDDhhmmssZ // T-REC-X.690-201510 sec 11.8 byte[] rented = null; // The longest format is 17 bytes. Span <byte> tmpSpace = stackalloc byte[17]; ReadOnlySpan <byte> contents = this.GetOctetStringContents( expectedTag, UniversalTagNumber.UtcTime, out int bytesRead, ref rented, tmpSpace); DateTimeOffset value = this.ParseUtcTime(contents, twoDigitYearMax); if (rented != null) { Debug.Fail($"UtcTime did not fit in tmpSpace ({contents.Length} total)"); CryptoPool.Return(rented, contents.Length); } this._data = this._data.Slice(bytesRead); return(value); }
private static Memory <byte> PseudoRandomPlus(ReadOnlyMemory <byte> key, ReadOnlyMemory <byte> pepper, KerberosCryptoTransformer handler) { // PRF+(protocol key, octet string) -> (octet string) // PRF+(key, shared-info) := pseudo-random(key, 1 || shared-info ) || // pseudo-random(key, 2 || shared-info ) || // pseudo-random(key, 3 || shared-info ) || ... using (var pool = CryptoPool.Rent <byte>(pepper.Length + 1)) { Memory <byte> input = pool.Memory.Slice(0, pepper.Length + 1); int prfSize = handler.BlockSize; int iterations = handler.KeySize / prfSize; if (handler.KeySize % prfSize > 0) { iterations++; } input.Span[0] = 1; pepper.CopyTo(input.Slice(1)); Memory <byte> result = new byte[prfSize * iterations]; for (var i = 0; i < iterations; i++) { handler.PseudoRandomFunction(key, input) .Slice(0, prfSize) .CopyTo(result.Slice(i * prfSize)); input.Span[0]++; } return(result); } }
public override ReadOnlyMemory <byte> Encrypt(ReadOnlyMemory <byte> data, KerberosKey kerberosKey, KeyUsage usage) { var Ke = GetOrDeriveKey(kerberosKey, usage); var confounder = GenerateRandomBytes(ConfounderSize); var concatLength = confounder.Length + data.Length; using (var cleartextPool = CryptoPool.Rent <byte>(concatLength)) { var cleartext = Concat(confounder.Span, data.Span, cleartextPool.Memory.Slice(0, concatLength)); var encrypted = AESCTS.Encrypt( cleartext, Ke, AllZerosInitVector ); var checksum = MakeChecksum(cleartext, kerberosKey, usage, KeyDerivationMode.Ki, ChecksumSize); return(Concat(encrypted.Span, checksum.Span)); } }
private static ArraySegment <byte> RentDynamicBuffer <THandle>(NegativeSizeReadMethod <THandle> method, THandle handle) { int negativeSize = method(handle, null, 0); if (negativeSize > 0) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } int targetSize = -negativeSize; byte[] bytes = CryptoPool.Rent(targetSize); int ret = method(handle, bytes, targetSize); if (ret != 1) { CryptoPool.Return(bytes); throw Interop.Crypto.CreateOpenSslCryptographicException(); } return(new ArraySegment <byte>(bytes, 0, targetSize)); }
/// <summary> /// Purge the ticket cache of the provided Logon Id. Note that the value 0 zero is treated as the current users Logon Id. /// </summary> /// <param name="luid">The Logon Id of the cache to be purged.</param> public unsafe void PurgeTicketCache(long luid = 0) { var purgeRequest = new KERB_PURGE_TKT_CACHE_EX_REQUEST { MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbPurgeTicketCacheExMessage, Flags = 1, LogonId = luid }; var bufferSize = Marshal.SizeOf(typeof(KERB_PURGE_TKT_CACHE_EX_REQUEST)); using (var pool = CryptoPool.Rent <byte>(bufferSize)) { var buffer = pool.Memory.Slice(0, bufferSize); fixed(void *pBuffer = &MemoryMarshal.GetReference(buffer.Span)) { Marshal.StructureToPtr(purgeRequest, (IntPtr)pBuffer, false); this.LsaCallAuthenticationPackage(pBuffer, bufferSize); } } }
private static AsymmetricAlgorithm DecodeDsaPublicKey(byte[] encodedKeyValue, byte[] encodedParameters) { SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn { Algorithm = new AlgorithmIdentifierAsn { Algorithm = Oids.Dsa, Parameters = encodedParameters }, SubjectPublicKey = encodedKeyValue, }; AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); spki.Encode(writer); byte[] rented = CryptoPool.Rent(writer.GetEncodedLength()); if (!writer.TryEncode(rented, out int written)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } DSA dsa = DSA.Create(); IDisposable?toDispose = dsa; try { dsa.ImportSubjectPublicKeyInfo(rented.AsSpan(0, written), out _); toDispose = null; return(dsa); } finally { toDispose?.Dispose(); CryptoPool.Return(rented, written); } }
/* * This is an implementation of SP800-108 KDF in Counter Mode * https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf */ public ReadOnlyMemory <byte> Derive( HashAlgorithmName algName, ReadOnlyMemory <byte> passwordBytes, ReadOnlyMemory <byte> salt, int k, int keySize ) { // K1 = HMAC-SHA-256(key, 0x00000001 | label | 0x00 | k) // length(0x00000001 | 0x00 | k) = 9 var inputLength = salt.Length + 9; using (var inputRented = CryptoPool.Rent <byte>(inputLength)) { var input = inputRented.Memory.Slice(0, inputLength); input.Span[3] = 1; // 0x00000001 salt.CopyTo(input.Slice(4)); // label IHmacAlgorithm hmac; if (algName == HashAlgorithmName.SHA256) { hmac = CryptoPal.Platform.HmacSha256(passwordBytes); } else { hmac = CryptoPal.Platform.HmacSha384(passwordBytes); } BinaryPrimitives.WriteInt32BigEndian(input.Span.Slice(input.Length - 4), k); return(hmac.ComputeHash(input).Slice(0, keySize)); } }
internal static ArraySegment <byte> OpenSslRentEncode <THandle>( GetEncodedSizeFunc <THandle> getSize, EncodeFunc <THandle> encode, THandle handle) where THandle : SafeHandle { int size = getSize(handle); if (size < 1) { throw CreateOpenSslCryptographicException(); } byte[] data = CryptoPool.Rent(size); int size2 = encode(handle, data); if (size2 < 1) { Debug.Fail( $"{nameof(OpenSslEncode)}: {nameof(getSize)} succeeded ({size}) and {nameof(encode)} failed ({size2})"); // Since we don't know what was written, assume it was secret and have the // CryptoPool.Return clear the whole array. // (It doesn't matter much, since we're behind Debug.Fail) CryptoPool.Return(data); // If it ever happens, ensure the error queue gets cleared. // And since it didn't write the data, reporting an exception is good too. throw CreateOpenSslCryptographicException(); } Debug.Assert(size == size2); return(new ArraySegment <byte>(data, 0, size2)); }
public byte[] ExportPrivateKey( ReadOnlySpan <byte> passwordBytes, S2kParameters s2kParameters) { ECParameters ecParameters = new ECParameters(); byte[] secretPart = Array.Empty <byte>(); try { ecParameters = ecdsa.ExportParameters(true); int secretSize = MPInteger.GetMPEncodedLength(ecParameters.D !); secretPart = CryptoPool.Rent(secretSize); MPInteger.TryWriteInteger(ecParameters.D, secretPart, out var _); int encryptedSecretLength = S2kBasedEncryption.GetEncryptedLength(s2kParameters, secretSize); int estimatedLength = 32 /* OID */ + MPInteger.GetMPEncodedLength(ecParameters.Q.X !, ecParameters.Q.Y !) + 1 /* EC Point type */ + encryptedSecretLength; var destination = new byte[estimatedLength]; WriteOpenPgpECParameters(ecParameters, destination, out int bytesWritten); S2kBasedEncryption.EncryptSecretKey(passwordBytes, s2kParameters, secretPart.AsSpan(0, secretSize), destination.AsSpan(bytesWritten)); return(destination.AsSpan(0, bytesWritten + encryptedSecretLength).ToArray()); } finally { CryptoPool.Return(secretPart); if (ecParameters.D != null) { CryptographicOperations.ZeroMemory(ecParameters.D); } } }
private static void Derive( ReadOnlySpan <char> password, HashAlgorithmName hashAlgorithm, int iterationCount, byte id, ReadOnlySpan <byte> salt, Span <byte> destination) { // https://tools.ietf.org/html/rfc7292#appendix-B.2 Debug.Assert(iterationCount >= 1); if (!s_uvLookup.TryGetValue(hashAlgorithm, out Tuple <int, int>?uv)) { throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name); } (int u, int v) = uv; Debug.Assert(v <= 1024); // 1. Construct a string, D (the "diversifier"), by concatenating v/8 copies of ID. int vBytes = v >> 3; Span <byte> D = stackalloc byte[vBytes]; D.Fill(id); // 2. Concatenate copies of the salt together to create a string S of // length v(ceiling(s/ v)) bits(the final copy of the salt may be // truncated to create S). Note that if the salt is the empty // string, then so is S. int SLen = ((salt.Length - 1 + vBytes) / vBytes) * vBytes; // The password is a null-terminated UTF-16BE version of the input. int passLen = checked ((password.Length + 1) * 2); // If password == default then the span represents the null string (as opposed to // an empty string), and the P block should then have size 0 in the next step. if (password == default) { passLen = 0; } // 3. Concatenate copies of the password together to create a string P // of length v(ceiling(p/v)) bits (the final copy of the password // may be truncated to create P). Note that if the password is the // empty string, then so is P. // // (The RFC quote considers the trailing '\0' to be part of the string, // so "empty string" from this RFC means "null string" in C#, and C#'s // "empty string" is not 'empty' in this context.) int PLen = ((passLen - 1 + vBytes) / vBytes) * vBytes; // 4. Set I=S||P to be the concatenation of S and P. int ILen = SLen + PLen; Span <byte> I = stackalloc byte[0]; byte[]? IRented = null; if (ILen <= 1024) { I = stackalloc byte[ILen]; } else { IRented = CryptoPool.Rent(ILen); I = IRented.AsSpan(0, ILen); } IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm); try { CircularCopy(salt, I.Slice(0, SLen)); CircularCopyUtf16BE(password, I.Slice(SLen)); int uBytes = u >> 3; Span <byte> hashBuf = stackalloc byte[uBytes]; Span <byte> bBuf = stackalloc byte[vBytes]; // 5. Set c=ceiling(n/u). // 6. For i=1, 2, ..., c, do the following: // (later we're going to start writing A_i values as output, // they mean "while work remains"). while (true) { // A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I, // H(H(H(... H(D || I)))) hash.AppendData(D); hash.AppendData(I); for (int j = iterationCount; j > 0; j--) { if (!hash.TryGetHashAndReset(hashBuf, out int bytesWritten) || bytesWritten != hashBuf.Length) { Debug.Fail($"Hash output wrote {bytesWritten} bytes when {hashBuf.Length} was expected"); throw new CryptographicException(); } if (j != 1) { hash.AppendData(hashBuf); } } // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom // bit string, A. // // 8. Use the first n bits of A as the output of this entire process. if (hashBuf.Length >= destination.Length) { hashBuf.Slice(0, destination.Length).CopyTo(destination); return; } hashBuf.CopyTo(destination); destination = destination.Slice(hashBuf.Length); // B. Concatenate copies of A_i to create a string B of length v // bits(the final copy of Ai may be truncated to create B). CircularCopy(hashBuf, bBuf); // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit // blocks, where k = ceiling(s / v) + ceiling(p / v), modify I by // setting I_j = (I_j + B + 1) mod 2 ^ v for each j. for (int j = (I.Length / vBytes) - 1; j >= 0; j--) { Span <byte> I_j = I.Slice(j * vBytes, vBytes); AddPlusOne(I_j, bBuf); } } } finally { CryptographicOperations.ZeroMemory(I); if (IRented != null) { CryptoPool.Return(IRented, clearSize: 0); } hash.Dispose(); } }
internal byte[] Encrypt( ReadOnlySpan <char> password, ReadOnlySpan <byte> passwordBytes, PbeParameters pbeParameters) { Debug.Assert(pbeParameters != null); Debug.Assert(pbeParameters.IterationCount >= 1); AsnWriter writer = null; using (AsnWriter contentsWriter = Encode()) { ReadOnlySpan <byte> contentsSpan = contentsWriter.EncodeAsSpan(); PasswordBasedEncryption.InitiateEncryption( pbeParameters, out SymmetricAlgorithm cipher, out string hmacOid, out string encryptionAlgorithmOid, out bool isPkcs12); int cipherBlockBytes = cipher.BlockSize / 8; byte[] encryptedRent = CryptoPool.Rent(contentsSpan.Length + cipherBlockBytes); Span <byte> encryptedSpan = Span <byte> .Empty; Span <byte> iv = stackalloc byte[cipherBlockBytes]; Span <byte> salt = stackalloc byte[16]; RandomNumberGenerator.Fill(salt); try { int written = PasswordBasedEncryption.Encrypt( password, passwordBytes, cipher, isPkcs12, contentsSpan, pbeParameters, salt, encryptedRent, iv); encryptedSpan = encryptedRent.AsSpan(0, written); writer = new AsnWriter(AsnEncodingRules.DER); // EncryptedData writer.PushSequence(); // version // Since we're not writing unprotected attributes, version=0 writer.WriteInteger(0); // encryptedContentInfo { writer.PushSequence(); writer.WriteObjectIdentifier(Oids.Pkcs7Data); PasswordBasedEncryption.WritePbeAlgorithmIdentifier( writer, isPkcs12, encryptionAlgorithmOid, salt, pbeParameters.IterationCount, hmacOid, iv); writer.WriteOctetString( new Asn1Tag(TagClass.ContextSpecific, 0), encryptedSpan); writer.PopSequence(); } writer.PopSequence(); return(writer.Encode()); } finally { CryptographicOperations.ZeroMemory(encryptedSpan); CryptoPool.Return(encryptedRent, clearSize: 0); writer?.Dispose(); } } }
private static unsafe ArraySegment <byte> EncodeAuthSafe( AsnWriter tmpWriter, SafeBagAsn[] keyBags, int keyCount, CertBagAsn[] certBags, AttributeAsn[] certAttrs, int certIdx, ReadOnlySpan <char> passwordSpan) { string?encryptionAlgorithmOid = null; bool certsIsPkcs12Encryption = false; string?certsHmacOid = null; ArraySegment <byte> encodedKeyContents = default; ArraySegment <byte> encodedCertContents = default; try { if (keyCount > 0) { encodedKeyContents = EncodeKeys(tmpWriter, keyBags, keyCount); } Span <byte> salt = stackalloc byte[16]; RandomNumberGenerator.Fill(salt); Span <byte> certContentsIv = stackalloc byte[8]; if (certIdx > 0) { encodedCertContents = EncodeCerts( tmpWriter, certBags, certAttrs, certIdx, salt, passwordSpan, certContentsIv, out certsHmacOid, out encryptionAlgorithmOid, out certsIsPkcs12Encryption); } return(EncodeAuthSafe( tmpWriter, encodedKeyContents, encodedCertContents, certsIsPkcs12Encryption, certsHmacOid !, encryptionAlgorithmOid !, salt, certContentsIv)); } finally { if (encodedCertContents.Array != null) { CryptoPool.Return(encodedCertContents); } if (encodedKeyContents.Array != null) { CryptoPool.Return(encodedKeyContents); } } }
private static byte[]? DecryptContent( ReadOnlyMemory <byte> encryptedContent, byte[] cek, AlgorithmIdentifierAsn contentEncryptionAlgorithm, out Exception?exception) { exception = null; // Windows compat: If the encrypted content is completely empty, even where it does not make sense for the // mode and padding (e.g. CBC + PKCS7), produce an empty plaintext. if (encryptedContent.IsEmpty) { return(Array.Empty <byte>()); } #if NET try { using (SymmetricAlgorithm alg = OpenAlgorithm(contentEncryptionAlgorithm)) { try { alg.Key = cek; } catch (CryptographicException ce) { throw new CryptographicException(SR.Cryptography_Cms_InvalidSymmetricKey, ce); } return(alg.DecryptCbc(encryptedContent.Span, alg.IV)); } } catch (CryptographicException ce) { exception = ce; return(null); } #else int encryptedContentLength = encryptedContent.Length; byte[] encryptedContentArray = CryptoPool.Rent(encryptedContentLength); try { encryptedContent.CopyTo(encryptedContentArray); using (SymmetricAlgorithm alg = OpenAlgorithm(contentEncryptionAlgorithm)) { ICryptoTransform decryptor; try { decryptor = alg.CreateDecryptor(cek, alg.IV); } catch (ArgumentException ae) { // Decrypting or deriving the symmetric key with the wrong key may still succeed // but produce a symmetric key that is not the correct length. throw new CryptographicException(SR.Cryptography_Cms_InvalidSymmetricKey, ae); } using (decryptor) { // If we extend this library to accept additional algorithm providers // then a different array pool needs to be used. Debug.Assert(alg.GetType().Assembly == typeof(Aes).Assembly); return(decryptor.OneShot( encryptedContentArray, 0, encryptedContentLength)); } } } catch (CryptographicException e) { exception = e; return(null); } finally { CryptoPool.Return(encryptedContentArray, encryptedContentLength); } #endif }
/// <summary> /// Create a "NewCredentials" logon session for the current LSA Handle. This does not authenticate the user /// and only uses the credentials provided for outbound calls similar to the /netonly flag for runas.exe. /// /// Note: this will call <see cref="ImpersonateLoggedOnUser(LsaTokenSafeHandle)" /> and set the current /// thread's primary token to the generated NT Token. /// </summary> /// <param name="username">The username to be used. Note leaving this null will use the default value "user". /// Passing an empty string will cause LSA to treat this as an anonymous user.</param> /// <param name="password">The password to be used by LSA for any future outbound ticket requests not already cached.</param> /// <param name="realm">The default realm to be used by LSA for the any outbound ticket requests not already cached.</param> public unsafe void LogonUser(string username = null, string password = null, string realm = null) { if (username == null) { username = DefaultUserName; } if (password == null) { password = string.Empty; } if (realm == null) { realm = string.Empty; } var originName = new LSA_STRING { Buffer = ProcessName, Length = (ushort)(ProcessName.Length * 2), MaximumLength = (ushort)(ProcessName.Length * 2) }; var bufferSize = Marshal.SizeOf(typeof(KERB_INTERACTIVE_LOGON)) + (realm.Length * 2) + (username.Length * 2) + (password.Length * 2); if (this.impersonationContext != null) { this.impersonationContext.Dispose(); this.impersonationContext = null; } LsaBufferSafeHandle profileBuffer = null; using (var pool = CryptoPool.Rent <byte>(bufferSize)) { var buffer = pool.Memory.Slice(0, bufferSize); try { fixed(byte *pBuffer = &MemoryMarshal.GetReference(buffer.Span)) { KERB_INTERACTIVE_LOGON *pLogon = (KERB_INTERACTIVE_LOGON *)pBuffer; pLogon->MessageType = KERB_LOGON_SUBMIT_TYPE.KerbInteractiveLogon; int offset = Marshal.SizeOf(typeof(KERB_INTERACTIVE_LOGON)); SetString(realm, (IntPtr)pLogon, ref pLogon->LogonDomainName, ref offset); SetString(username, (IntPtr)pLogon, ref pLogon->UserName, ref offset); SetString(password, (IntPtr)pLogon, ref pLogon->Password, ref offset); var tokenSource = new TOKEN_SOURCE() { SourceName = Encoding.UTF8.GetBytes("kerb.net") }; int profileLength = 0; int result = LsaLogonUser( this.lsaHandle, ref originName, SECURITY_LOGON_TYPE.NewCredentials, this.negotiateAuthPackage, pLogon, bufferSize, IntPtr.Zero, ref tokenSource, out profileBuffer, ref profileLength, out this.luid, out this.impersonationContext, out IntPtr pQuotas, out int subStatus ); LsaThrowIfError(result); } } finally { profileBuffer?.Dispose(); } } // this call to impersonate will set the current thread token to be the token out of LsaLogonUser // do we need to do anything special if this gets used within an async context? this.impersonationContext.Impersonate(); }
private static void ReadSubIdentifier( ReadOnlySpan <byte> source, out int bytesRead, out long?smallValue, out BigInteger?largeValue) { Debug.Assert(source.Length > 0); // T-REC-X.690-201508 sec 8.19.2 (last sentence) if (source[0] == 0x80) { throw new CryptographicException(SR.Resource("Cryptography_Der_Invalid_Encoding")); } // First, see how long the segment is int end = -1; int idx; for (idx = 0; idx < source.Length; idx++) { // If the high bit isn't set this marks the end of the sub-identifier. bool endOfIdentifier = (source[idx] & 0x80) == 0; if (endOfIdentifier) { end = idx; break; } } if (end < 0) { throw new CryptographicException(SR.Resource("Cryptography_Der_Invalid_Encoding")); } bytesRead = end + 1; long accum = 0; // Fast path, 9 or fewer bytes => fits in a signed long. // (7 semantic bits per byte * 9 bytes = 63 bits, which leaves the sign bit alone) if (bytesRead <= 9) { for (idx = 0; idx < bytesRead; idx++) { byte cur = source[idx]; accum <<= 7; accum |= (byte)(cur & 0x7F); } largeValue = null; smallValue = accum; return; } // Slow path, needs temporary storage. const int SemanticByteCount = 7; const int ContentByteCount = 8; // Every 8 content bytes turns into 7 integer bytes, so scale the count appropriately. // Add one while we're shrunk to account for the needed padding byte or the len%8 discarded bytes. int bytesRequired = ((bytesRead / ContentByteCount) + 1) * SemanticByteCount; byte[] tmpBytes = CryptoPool.Rent(bytesRequired); // Ensure all the bytes are zeroed out for BigInteger's parsing. Array.Clear(tmpBytes, 0, tmpBytes.Length); Span <byte> writeSpan = tmpBytes; Span <byte> accumValueBytes = stackalloc byte[sizeof(long)]; int nextStop = bytesRead; idx = bytesRead - ContentByteCount; while (nextStop > 0) { byte cur = source[idx]; accum <<= 7; accum |= (byte)(cur & 0x7F); idx++; if (idx >= nextStop) { Debug.Assert(idx == nextStop); Debug.Assert(writeSpan.Length >= SemanticByteCount); BinaryPrimitives.WriteInt64LittleEndian(accumValueBytes, accum); Debug.Assert(accumValueBytes[7] == 0); accumValueBytes.Slice(0, SemanticByteCount).CopyTo(writeSpan); writeSpan = writeSpan.Slice(SemanticByteCount); accum = 0; nextStop -= ContentByteCount; idx = Math.Max(0, nextStop - ContentByteCount); } } int bytesWritten = tmpBytes.Length - writeSpan.Length; // Verify our bytesRequired calculation. There should be at most 7 padding bytes. // If the length % 8 is 7 we'll have 0 padding bytes, but the sign bit is still clear. // // 8 content bytes had a sign bit problem, so we gave it a second 7-byte block, 7 remain. // 7 content bytes got a single block but used and wrote 7 bytes, but only 49 of the 56 bits. // 6 content bytes have a padding count of 1. // 1 content byte has a padding count of 6. // 0 content bytes is illegal, but see 8 for the cycle. int paddingByteCount = bytesRequired - bytesWritten; Debug.Assert(paddingByteCount >= 0 && paddingByteCount < sizeof(long)); largeValue = new BigInteger(tmpBytes); smallValue = null; CryptoPool.Return(tmpBytes, bytesWritten); }
private static ArraySegment <byte> EncodeCerts( AsnWriter tmpWriter, CertBagAsn[] certBags, AttributeAsn[] certAttrs, int certCount, Span <byte> salt, ReadOnlySpan <char> passwordSpan, Span <byte> certContentsIv, out string hmacOid, out string encryptionAlgorithmOid, out bool isPkcs12) { Debug.Assert(tmpWriter.GetEncodedLength() == 0); tmpWriter.PushSequence(); PasswordBasedEncryption.InitiateEncryption( s_windowsPbe, out SymmetricAlgorithm cipher, out hmacOid, out encryptionAlgorithmOid, out isPkcs12); using (cipher) { Debug.Assert(certContentsIv.Length * 8 == cipher.BlockSize); for (int i = certCount - 1; i >= 0; --i) { // Manually write the SafeBagAsn // https://tools.ietf.org/html/rfc7292#section-4.2 // // SafeBag ::= SEQUENCE { // bagId BAG-TYPE.&id ({PKCS12BagSet}) // bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), // bagAttributes SET OF PKCS12Attribute OPTIONAL // } tmpWriter.PushSequence(); tmpWriter.WriteObjectIdentifierForCrypto(Oids.Pkcs12CertBag); tmpWriter.PushSequence(s_contextSpecific0); certBags[i].Encode(tmpWriter); tmpWriter.PopSequence(s_contextSpecific0); if (certAttrs[i].AttrType != null) { tmpWriter.PushSetOf(); certAttrs[i].Encode(tmpWriter); tmpWriter.PopSetOf(); } tmpWriter.PopSequence(); } tmpWriter.PopSequence(); // The padding applied will add at most a block to the output, // so ask for contentsSpan.Length + the number of bytes in a cipher block. int cipherBlockBytes = cipher.BlockSize >> 3; int requestedSize = checked (tmpWriter.GetEncodedLength() + cipherBlockBytes); byte[] certContents = CryptoPool.Rent(requestedSize); int encryptedLength = PasswordBasedEncryption.Encrypt( passwordSpan, ReadOnlySpan <byte> .Empty, cipher, isPkcs12, tmpWriter, s_windowsPbe, salt, certContents, certContentsIv); Debug.Assert(encryptedLength <= requestedSize); tmpWriter.Reset(); return(new ArraySegment <byte>(certContents, 0, encryptedLength)); } }
private unsafe void ImportKey(DiffieHellmanKey incoming, ref IntPtr hKey) { DiffieHellmanKey key; string keyType; int dwMagic; int structSize; if (incoming.Type == AsymmetricKeyType.Private) { key = incoming; keyType = BCRYPT_DH_PRIVATE_BLOB; dwMagic = BCRYPT_DH_PRIVATE_MAGIC; structSize = sizeof(BCRYPT_DH_KEY_BLOB_HEADER) + (key.KeyLength * 4); } else { key = (DiffieHellmanKey)PublicKey; keyType = BCRYPT_DH_PUBLIC_BLOB; dwMagic = BCRYPT_DH_PUBLIC_MAGIC; structSize = sizeof(BCRYPT_DH_KEY_BLOB_HEADER) + (key.KeyLength * 3); } Factor = incoming.Factor.ToArray(); using (var rented = CryptoPool.Rent <byte>(structSize)) { rented.Memory.Span.Fill(0); fixed(byte *pbInput = &MemoryMarshal.GetReference(rented.Memory.Span)) { BCRYPT_DH_KEY_BLOB *param = (BCRYPT_DH_KEY_BLOB *)pbInput; param->header.dwMagic = dwMagic; param->header.cbKey = key.KeyLength; key.Modulus.CopyTo( rented.Memory.Slice(sizeof(BCRYPT_DH_KEY_BLOB_HEADER)) ); key.Generator.CopyTo( rented.Memory.Slice(sizeof(BCRYPT_DH_KEY_BLOB_HEADER) + key.Modulus.Length) ); incoming.Public.CopyTo( rented.Memory.Slice(sizeof(BCRYPT_DH_KEY_BLOB_HEADER) + key.Modulus.Length + key.Generator.Length) ); if (incoming.Type == AsymmetricKeyType.Private && incoming.Private.Length > 0) { incoming.Private.CopyTo( rented.Memory.Slice(sizeof(BCRYPT_DH_KEY_BLOB_HEADER) + key.Modulus.Length + key.Generator.Length + key.Public.Length) ); } var status = BCryptImportKeyPair( hAlgorithm, IntPtr.Zero, keyType, ref hKey, pbInput, structSize, 0 ); ThrowIfNotNtSuccess(status); } } }
internal static bool TryDecodePem(ReadOnlySpan <byte> rawData, DerCallback derCallback) { // If the character is a control character that isn't whitespace, then we're probably using a DER encoding // and not using a PEM encoding in ASCII. if (char.IsControl((char)rawData[0]) && !char.IsWhiteSpace((char)rawData[0])) { return(false); } // Look for the PEM marker. This doesn't guarantee it will be a valid PEM since we don't check whether // the marker is at the beginning of line or whether the line is a complete marker. It's just a quick // check to avoid conversion from bytes to characters if the content is DER encoded. if (rawData.IndexOf(pemBegin) < 0) { return(false); } char[] certPem = ArrayPool <char> .Shared.Rent(rawData.Length); byte[]? certBytes = null; try { Encoding.ASCII.GetChars(rawData, certPem); foreach ((ReadOnlySpan <char> contents, PemFields fields) in new PemEnumerator(certPem.AsSpan(0, rawData.Length))) { ReadOnlySpan <char> label = contents[fields.Label]; if (label.SequenceEqual(PemLabels.X509Certificate) || label.SequenceEqual(PemLabels.Pkcs7Certificate)) { certBytes = CryptoPool.Rent(fields.DecodedDataLength); if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], certBytes, out int bytesWritten) || bytesWritten != fields.DecodedDataLength) { Debug.Fail("The contents should have already been validated by the PEM reader."); throw new CryptographicException(SR.Cryptography_X509_NoPemCertificate); } X509ContentType contentType = label.SequenceEqual(PemLabels.X509Certificate) ? X509ContentType.Cert : X509ContentType.Pkcs7; bool cont = derCallback(certBytes.AsSpan(0, bytesWritten), contentType); CryptoPool.Return(certBytes, clearSize: 0); certBytes = null; if (!cont) { return(true); } } } } finally { ArrayPool <char> .Shared.Return(certPem, clearArray : true); if (certBytes != null) { CryptoPool.Return(certBytes, clearSize: 0); } } return(true); }
private static ArraySegment <byte> EncodeAuthSafe( AsnWriter tmpWriter, ReadOnlyMemory <byte> encodedKeyContents, ReadOnlyMemory <byte> encodedCertContents, bool isPkcs12, string hmacOid, string encryptionAlgorithmOid, Span <byte> salt, Span <byte> certContentsIv) { Debug.Assert(tmpWriter.GetEncodedLength() == 0); tmpWriter.PushSequence(); if (!encodedKeyContents.IsEmpty) { tmpWriter.PushSequence(); tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); tmpWriter.PushSequence(s_contextSpecific0); ReadOnlySpan <byte> keyContents = encodedKeyContents.Span; tmpWriter.WriteOctetString(keyContents); tmpWriter.PopSequence(s_contextSpecific0); tmpWriter.PopSequence(); } if (!encodedCertContents.IsEmpty) { tmpWriter.PushSequence(); { tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Encrypted); tmpWriter.PushSequence(s_contextSpecific0); tmpWriter.PushSequence(); { // No unprotected attributes: version 0 data tmpWriter.WriteInteger(0); tmpWriter.PushSequence(); { tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); PasswordBasedEncryption.WritePbeAlgorithmIdentifier( tmpWriter, isPkcs12, encryptionAlgorithmOid, salt, s_windowsPbe.IterationCount, hmacOid, certContentsIv); tmpWriter.WriteOctetString(encodedCertContents.Span, s_contextSpecific0); tmpWriter.PopSequence(); } tmpWriter.PopSequence(); tmpWriter.PopSequence(s_contextSpecific0); } tmpWriter.PopSequence(); } } tmpWriter.PopSequence(); int authSafeLength = tmpWriter.GetEncodedLength(); byte[] authSafe = CryptoPool.Rent(authSafeLength); if (!tmpWriter.TryEncode(authSafe, out authSafeLength)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } tmpWriter.Reset(); return(new ArraySegment <byte>(authSafe, 0, authSafeLength)); }
private byte[] ExportPfx(SafePasswordHandle password) { int certCount = 1; if (_singleCertPal == null) { Debug.Assert(_certs != null); certCount = _certs.Count; } CertBagAsn[] certBags = ArrayPool <CertBagAsn> .Shared.Rent(certCount); SafeBagAsn[] keyBags = ArrayPool <SafeBagAsn> .Shared.Rent(certCount); AttributeAsn[] certAttrs = ArrayPool <AttributeAsn> .Shared.Rent(certCount); certAttrs.AsSpan(0, certCount).Clear(); AsnWriter tmpWriter = new AsnWriter(AsnEncodingRules.DER); ArraySegment <byte> encodedAuthSafe = default; bool gotRef = false; password.DangerousAddRef(ref gotRef); try { ReadOnlySpan <char> passwordSpan = password.DangerousGetSpan(); int keyIdx = 0; int certIdx = 0; if (_singleCertPal != null) { BuildBags( _singleCertPal, passwordSpan, tmpWriter, certBags, certAttrs, keyBags, ref certIdx, ref keyIdx); } else { foreach (X509Certificate2 cert in _certs !) { BuildBags( cert.Pal, passwordSpan, tmpWriter, certBags, certAttrs, keyBags, ref certIdx, ref keyIdx); } } encodedAuthSafe = EncodeAuthSafe( tmpWriter, keyBags, keyIdx, certBags, certAttrs, certIdx, passwordSpan); return(MacAndEncode(tmpWriter, encodedAuthSafe, passwordSpan)); } finally { password.DangerousRelease(); certAttrs.AsSpan(0, certCount).Clear(); certBags.AsSpan(0, certCount).Clear(); keyBags.AsSpan(0, certCount).Clear(); ArrayPool <AttributeAsn> .Shared.Return(certAttrs); ArrayPool <CertBagAsn> .Shared.Return(certBags); ArrayPool <SafeBagAsn> .Shared.Return(keyBags); if (encodedAuthSafe.Array != null) { CryptoPool.Return(encodedAuthSafe); } } }
private static string?GetCdpUrl(SafeX509Handle cert) { ArraySegment <byte> crlDistributionPoints = OpenSslX509CertificateReader.FindFirstExtension(cert, Oids.CrlDistributionPoints); if (crlDistributionPoints.Array == null) { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NoCdpFound(cert); } return(null); } try { AsnValueReader reader = new AsnValueReader(crlDistributionPoints, AsnEncodingRules.DER); AsnValueReader sequenceReader = reader.ReadSequence(); reader.ThrowIfNotEmpty(); while (sequenceReader.HasData) { DistributionPointAsn.Decode(ref sequenceReader, crlDistributionPoints, out DistributionPointAsn distributionPoint); // Only distributionPoint is supported // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup. if (distributionPoint.DistributionPoint.HasValue && distributionPoint.DistributionPoint.Value.FullName != null) { foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName) { if (name.Uri != null) { if (Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri? uri) && uri.Scheme == "http") { return(name.Uri); } else { if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NonHttpCdpEntry(name.Uri); } } } } if (OpenSslX509ChainEventSource.Log.IsEnabled()) { OpenSslX509ChainEventSource.Log.NoMatchingCdpEntry(); } } } } catch (CryptographicException) { // Treat any ASN errors as if the extension was missing. } catch (AsnContentException) { // Treat any ASN errors as if the extension was missing. } finally { // The data came from a certificate, so it's public. CryptoPool.Return(crlDistributionPoints.Array, clearSize: 0); } return(null); }
private static ReadOnlyMemory <byte> DeriveManually(ReadOnlyMemory <byte> salt, int iterations, int keySize, IHmacAlgorithm hmac) { if (RequireNativeImplementation) { throw new CryptographicException("The caller requires the use of the native implementation but the platform doesn't support it."); } // This is here because .NET Standard doesn't include the built-in // ctor so it's possible the particular framework doesn't support it. // It's mostly based off the original Rfc2898DeriveBytes implementation. // // https://github.com/aspnet/DataProtection/blob/9941fb825fcbeefec898093755553679410d8a6b/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs // PBKDF2 is defined in NIST SP800-132, Sec. 5.3. // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf byte[] retVal = new byte[keySize]; int numBytesWritten = 0; int numBytesRemaining = keySize; // For each block index, U_0 := Salt || block_index var saltLength = salt.Length + sizeof(uint); using (var saltWithBlockIndexRented = CryptoPool.Rent <byte>(checked (saltLength))) { var saltWithBlockIndex = saltWithBlockIndexRented.Memory.Slice(0, saltLength); salt.CopyTo(saltWithBlockIndex); for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++) { // write the block index out as big-endian saltWithBlockIndex.Span[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24); saltWithBlockIndex.Span[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16); saltWithBlockIndex.Span[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8); saltWithBlockIndex.Span[saltWithBlockIndex.Length - 1] = (byte)blockIndex; // U_1 = PRF(U_0) = PRF(Salt || block_index) // T_blockIndex = U_1 byte[] u_iter = hmac.ComputeHashArray(saltWithBlockIndex); // this is U_1 byte[] t_blockIndex = u_iter; for (int iter = 1; iter < iterations; iter++) { u_iter = hmac.ComputeHashArray(u_iter); for (int i = 0; i < u_iter.Length; i++) { t_blockIndex[i] ^= u_iter[i]; } // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences). } // At this point, we're done iterating on this block, so copy the transformed block into retVal. int numBytesToCopy = Math.Min(numBytesRemaining, t_blockIndex.Length); Buffer.BlockCopy(t_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy); numBytesWritten += numBytesToCopy; numBytesRemaining -= numBytesToCopy; } // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length return(retVal); } }
// T-REC-X.690-201508 sec 8.19 private void WriteObjectIdentifierCore(Asn1Tag tag, ReadOnlySpan <char> oidValue) { // T-REC-X.690-201508 sec 8.19.4 // The first character is in { 0, 1, 2 }, the second will be a '.', and a third (digit) // will also exist. if (oidValue.Length < 3) { throw new ArgumentException(SR.Argument_InvalidOidValue, nameof(oidValue)); } if (oidValue[1] != '.') { throw new ArgumentException(SR.Argument_InvalidOidValue, nameof(oidValue)); } // The worst case is "1.1.1.1.1", which takes 4 bytes (5 components, with the first two condensed) // Longer numbers get smaller: "2.1.127" is only 2 bytes. (81d (0x51) and 127 (0x7F)) // So length / 2 should prevent any reallocations. byte[] tmp = CryptoPool.Rent(oidValue.Length / 2); int tmpOffset = 0; try { int firstComponent = oidValue[0] switch { '0' => 0, '1' => 1, '2' => 2, _ => throw new ArgumentException(SR.Argument_InvalidOidValue, nameof(oidValue)), }; // The first two components are special: // ITU X.690 8.19.4: // The numerical value of the first subidentifier is derived from the values of the first two // object identifier components in the object identifier value being encoded, using the formula: // (X*40) + Y // where X is the value of the first object identifier component and Y is the value of the // second object identifier component. // NOTE - This packing of the first two object identifier components recognizes that only // three values are allocated from the root node, and at most 39 subsequent values from // nodes reached by X = 0 and X = 1. // skip firstComponent and the trailing . ReadOnlySpan <char> remaining = oidValue.Slice(2); BigInteger subIdentifier = ParseSubIdentifier(ref remaining); subIdentifier += 40 * firstComponent; int localLen = EncodeSubIdentifier(tmp.AsSpan(tmpOffset), ref subIdentifier); tmpOffset += localLen; while (!remaining.IsEmpty) { subIdentifier = ParseSubIdentifier(ref remaining); localLen = EncodeSubIdentifier(tmp.AsSpan(tmpOffset), ref subIdentifier); tmpOffset += localLen; } Debug.Assert(!tag.IsConstructed); WriteTag(tag); WriteLength(tmpOffset); Buffer.BlockCopy(tmp, 0, _buffer, _offset, tmpOffset); _offset += tmpOffset; } finally { CryptoPool.Return(tmp, tmpOffset); } }
public static unsafe ContentInfo?TryDecryptCore( byte[] cek, string contentType, ReadOnlyMemory <byte>?content, AlgorithmIdentifierAsn contentEncryptionAlgorithm, out Exception?exception) { if (content == null) { exception = null; return(new ContentInfo( new Oid(contentType), Array.Empty <byte>())); } byte[]? decrypted = DecryptContent(content.Value, cek, contentEncryptionAlgorithm, out exception); if (exception != null) { return(null); } // Compat: Previous versions of the managed PAL encryptor would wrap the contents in an octet stream // which is not correct and is incompatible with other CMS readers. To maintain compatibility with // existing CMS that have the incorrect wrapping, we attempt to remove it. if (contentType == Oids.Pkcs7Data) { byte[]? tmp = null; try { AsnReader reader = new AsnReader(decrypted, AsnEncodingRules.BER); if (reader.TryReadPrimitiveOctetStringBytes(out ReadOnlyMemory <byte> contents)) { decrypted = contents.ToArray(); } else { tmp = CryptoPool.Rent(decrypted !.Length); if (reader.TryCopyOctetStringBytes(tmp, out int written)) { Span <byte> innerContents = new Span <byte>(tmp, 0, written); decrypted = innerContents.ToArray(); innerContents.Clear(); } else { Debug.Fail("Octet string grew during copy"); // If this happens (which requires decrypted was overwritten, which // shouldn't be possible), just leave decrypted alone. } } } catch (CryptographicException) { } finally { if (tmp != null) { // Already cleared CryptoPool.Return(tmp, clearSize: 0); } } } else { decrypted = GetAsnSequenceWithContentNoValidation(decrypted); } exception = null; return(new ContentInfo( new Oid(contentType), decrypted !)); }
public static CspParameters GetProvParameters(this SafeProvOrNCryptKeyHandle handle) { // A normal key container name is a GUID (~34 bytes ASCII) // The longest standard provider name is 64 bytes (including the \0), // but we shouldn't have a CAPI call with a software CSP. // // In debug builds use a buffer which will need to be resized, but is big // enough to hold the DWORD "can't fail" values. Span <byte> stackSpan = stackalloc byte[ #if DEBUG sizeof(int) #else 64 #endif ]; stackSpan.Clear(); int size = stackSpan.Length; if (!Interop.Advapi32.CryptGetProvParam(handle, CryptProvParam.PP_PROVTYPE, stackSpan, ref size)) { throw Interop.CPError.GetLastWin32Error().ToCryptographicException(); } if (size != sizeof(int)) { Debug.Fail("PP_PROVTYPE writes a DWORD - enum misalignment?"); throw new CryptographicException(); } int provType = MemoryMarshal.Read <int>(stackSpan.Slice(0, size)); size = stackSpan.Length; if (!Interop.Advapi32.CryptGetProvParam(handle, CryptProvParam.PP_KEYSET_TYPE, stackSpan, ref size)) { throw Interop.CPError.GetLastWin32Error().ToCryptographicException(); } if (size != sizeof(int)) { Debug.Fail("PP_KEYSET_TYPE writes a DWORD - enum misalignment?"); throw new CryptographicException(); } int keysetType = MemoryMarshal.Read <int>(stackSpan.Slice(0, size)); // Only CRYPT_MACHINE_KEYSET is described as coming back, but be defensive. CspProviderFlags provFlags = ((CspProviderFlags)keysetType & CspProviderFlags.UseMachineKeyStore) | CspProviderFlags.UseExistingKey; byte[] rented = null; Span <byte> asciiStringBuf = stackSpan; string provName = GetStringProvParam(handle, CryptProvParam.PP_NAME, ref asciiStringBuf, ref rented, 0); int maxClear = provName.Length; string keyName = GetStringProvParam(handle, CryptProvParam.PP_CONTAINER, ref asciiStringBuf, ref rented, maxClear); maxClear = Math.Max(maxClear, keyName.Length); if (rented != null) { CryptoPool.Return(rented, maxClear); } return(new CspParameters(provType) { Flags = provFlags, KeyContainerName = keyName, ProviderName = provName, }); }
internal Interop.Crypto.X509VerifyStatusCode FindChainViaAia( ref List <X509Certificate2> downloadedCerts) { IntPtr lastCert = IntPtr.Zero; SafeX509StoreCtxHandle storeCtx = _storeCtx; Interop.Crypto.X509VerifyStatusCode statusCode = Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; while (!IsCompleteChain(statusCode)) { using (SafeX509Handle currentCert = Interop.Crypto.X509StoreCtxGetCurrentCert(storeCtx)) { IntPtr currentHandle = currentCert.DangerousGetHandle(); // No progress was made, give up. if (currentHandle == lastCert) { break; } lastCert = currentHandle; ArraySegment <byte> authorityInformationAccess = OpenSslX509CertificateReader.FindFirstExtension( currentCert, Oids.AuthorityInformationAccess); if (authorityInformationAccess.Count == 0) { break; } X509Certificate2 downloaded = DownloadCertificate( authorityInformationAccess, ref _remainingDownloadTime); // The AIA record is contained in a public structure, so no need to clear it. CryptoPool.Return(authorityInformationAccess.Array, clearSize: 0); if (downloaded == null) { break; } if (downloadedCerts == null) { downloadedCerts = new List <X509Certificate2>(); } AddToStackAndUpRef(downloaded.Handle, _untrustedLookup); downloadedCerts.Add(downloaded); Interop.Crypto.X509StoreCtxRebuildChain(storeCtx); statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx); } } if (statusCode == Interop.Crypto.X509VerifyStatusCode.X509_V_OK && downloadedCerts != null) { using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx)) { int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack); Span <IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity]; byte[] tempChainRent = null; if (chainSize <= tempChain.Length) { tempChain = tempChain.Slice(0, chainSize); } else { int targetSize = checked (chainSize * IntPtr.Size); tempChainRent = CryptoPool.Rent(targetSize); tempChain = MemoryMarshal.Cast <byte, IntPtr>(tempChainRent.AsSpan(0, targetSize)); } for (int i = 0; i < chainSize; i++) { tempChain[i] = Interop.Crypto.GetX509StackField(chainStack, i); } // In the average case we never made it here. // // Given that we made it here, in the average remaining case // we are doing a one item for which will match in the second position // of an (on-average) 3 item collection. // // The only case where this loop really matters is if downloading the // certificate made an alternate chain better, which may have resulted in // an extra download and made the first one not be involved any longer. In // that case, it's a 2 item for loop matching against a three item set. // // So N*M is well contained. for (int i = downloadedCerts.Count - 1; i >= 0; i--) { X509Certificate2 downloadedCert = downloadedCerts[i]; if (!tempChain.Contains(downloadedCert.Handle)) { downloadedCert.Dispose(); downloadedCerts.RemoveAt(i); } } if (downloadedCerts.Count == 0) { downloadedCerts = null; } if (tempChainRent != null) { CryptoPool.Return(tempChainRent); } } } return(statusCode); }
public void SealWithMac( ReadOnlySpan <char> password, HashAlgorithmName hashAlgorithm, int iterationCount) { if (iterationCount < 1) { throw new ArgumentOutOfRangeException(nameof(iterationCount)); } if (IsSealed) { throw new InvalidOperationException(SR.Cryptography_Pkcs12_PfxIsSealed); } byte[]? rentedAuthSafe = null; Span <byte> authSafeSpan = default; byte[]? rentedMac = null; Span <byte> macSpan = default; Span <byte> salt = stackalloc byte[0]; try { AsnWriter contentsWriter = new AsnWriter(AsnEncodingRules.BER); using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm)) { contentsWriter.PushSequence(); if (_contents != null) { foreach (ContentInfoAsn contentInfo in _contents) { contentInfo.Encode(contentsWriter); } } contentsWriter.PopSequence(); rentedAuthSafe = CryptoPool.Rent(contentsWriter.GetEncodedLength()); if (!contentsWriter.TryEncode(rentedAuthSafe, out int written)) { Debug.Fail("TryEncode failed with a pre-allocated buffer"); throw new InvalidOperationException(); } authSafeSpan = rentedAuthSafe.AsSpan(0, written); // Get an array of the proper size for the hash. byte[] macKey = hasher.GetHashAndReset(); rentedMac = CryptoPool.Rent(macKey.Length); macSpan = rentedMac.AsSpan(0, macKey.Length); // Since the biggest supported hash is SHA-2-512 (64 bytes), the // 128-byte cap here shouldn't ever come into play. Debug.Assert(macKey.Length <= 128); salt = stackalloc byte[Math.Min(macKey.Length, 128)]; RandomNumberGenerator.Fill(salt); Pkcs12Kdf.DeriveMacKey( password, hashAlgorithm, iterationCount, salt, macKey); using (IncrementalHash mac = IncrementalHash.CreateHMAC(hashAlgorithm, macKey)) { mac.AppendData(authSafeSpan); if (!mac.TryGetHashAndReset(macSpan, out int bytesWritten) || bytesWritten != macSpan.Length) { Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} of {macSpan.Length} bytes"); throw new CryptographicException(); } } } // https://tools.ietf.org/html/rfc7292#section-4 // // PFX ::= SEQUENCE { // version INTEGER {v3(3)}(v3,...), // authSafe ContentInfo, // macData MacData OPTIONAL // } AsnWriter writer = new AsnWriter(AsnEncodingRules.BER); { writer.PushSequence(); writer.WriteInteger(3); writer.PushSequence(); { writer.WriteObjectIdentifierForCrypto(Oids.Pkcs7Data); Asn1Tag contextSpecific0 = new Asn1Tag(TagClass.ContextSpecific, 0); writer.PushSequence(contextSpecific0); { writer.WriteOctetString(authSafeSpan); writer.PopSequence(contextSpecific0); } writer.PopSequence(); } // https://tools.ietf.org/html/rfc7292#section-4 // // MacData ::= SEQUENCE { // mac DigestInfo, // macSalt OCTET STRING, // iterations INTEGER DEFAULT 1 // -- Note: The default is for historical reasons and its use is // -- deprecated. // } writer.PushSequence(); { writer.PushSequence(); { writer.PushSequence(); { writer.WriteObjectIdentifierForCrypto(PkcsHelpers.GetOidFromHashAlgorithm(hashAlgorithm)); writer.PopSequence(); } writer.WriteOctetString(macSpan); writer.PopSequence(); } writer.WriteOctetString(salt); if (iterationCount > 1) { writer.WriteInteger(iterationCount); } writer.PopSequence(); } writer.PopSequence(); _sealedData = writer.Encode(); } } finally { CryptographicOperations.ZeroMemory(macSpan); CryptographicOperations.ZeroMemory(authSafeSpan); if (rentedMac != null) { // Already cleared CryptoPool.Return(rentedMac, clearSize: 0); } if (rentedAuthSafe != null) { // Already cleared CryptoPool.Return(rentedAuthSafe, clearSize: 0); } } }
private void Process(ReadOnlySpan <byte> nonce, ReadOnlySpan <byte> input, Span <byte> output, Span <byte> tag, ReadOnlySpan <byte> associatedData, bool outputIsCiphertext) { using var encryptor = aes.CreateEncryptor(); var tmp = CryptoPool.Rent(16); var counter = CryptoPool.Rent(16); var counterEnc = CryptoPool.Rent(16); var nonceMac = CryptoPool.Rent(16); var associatedDataMac = CryptoPool.Rent(16); var ciphertextMac = CryptoPool.Rent(16); try { CryptographicOperations.ZeroMemory(tmp.AsSpan(0, 15)); tmp[15] = 0; // N tag cmac.TransformBlock(tmp, 0, 16, null, 0); cmac.TryComputeHash(nonce, nonceMac, out var _); tmp[15] = 1; // H tag cmac.TransformBlock(tmp, 0, 16, null, 0); cmac.TryComputeHash(associatedData, associatedDataMac, out var _); cmac.Initialize(); tmp[15] = 2; // C tag cmac.TransformBlock(tmp, 0, 16, null, 0); nonceMac.AsSpan().CopyTo(counter); while (input.Length >= 16) { encryptor.TransformBlock(counter, 0, 16, counterEnc, 0); if (outputIsCiphertext) { for (int i = 0; i < 16; i++) { tmp[i] = (byte)(input[i] ^ counterEnc[i]); } cmac.TransformBlock(tmp, 0, 16, null, 0); tmp.AsSpan(0, 16).CopyTo(output); } else { input.Slice(0, 16).CopyTo(tmp); cmac.TransformBlock(tmp, 0, 16, null, 0); for (int i = 0; i < 16; i++) { output[i] = (byte)(input[i] ^ counterEnc[i]); } } byte add = 1; for (int i = 15; i >= 0; i--) { counter[i] += add; add = counter[i] == 0 ? 1 : 0; } input = input.Slice(16); output = output.Slice(16); } if (input.Length > 0) { encryptor.TransformBlock(counter, 0, 16, counterEnc, 0); if (outputIsCiphertext) { for (int i = 0; i < input.Length; i++) { tmp[i] = (byte)(input[i] ^ counterEnc[i]); } cmac.TransformBlock(tmp, 0, input.Length, null, 0); tmp.AsSpan(0, input.Length).CopyTo(output); } else { input.CopyTo(tmp); cmac.TransformBlock(tmp, 0, input.Length, null, 0); for (int i = 0; i < input.Length; i++) { output[i] = (byte)(input[i] ^ counterEnc[i]); } } } cmac.TryComputeHash(Array.Empty <byte>(), ciphertextMac, out var _); for (int i = 0; i < tag.Length; i++) { tag[i] = (byte)(nonceMac[i] ^ associatedDataMac[i] ^ ciphertextMac[i]); } } finally { cmac.Initialize(); CryptoPool.Return(tmp, 16); CryptoPool.Return(counter, 16); CryptoPool.Return(counterEnc, 16); CryptoPool.Return(nonceMac, 16); CryptoPool.Return(associatedDataMac, 16); CryptoPool.Return(ciphertextMac, 16); } }