summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs
blob: 6f7fc315456de60412bdb76f59ee0929816ba1d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//-----------------------------------------------------------------------
// <copyright file="DiffieHellmanUtilities.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.OpenId {
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Security.Cryptography;
	using System.Text;
	using DotNetOpenAuth.Messaging;
	using Org.Mentalis.Security.Cryptography;
	using Validation;

	/// <summary>
	/// Diffie-Hellman encryption methods used by both the relying party and provider.
	/// </summary>
	internal class DiffieHellmanUtilities {
		/// <summary>
		/// An array of known Diffie Hellman sessions, sorted by decreasing hash size.
		/// </summary>
		private static DHSha[] diffieHellmanSessionTypes = CreateSessionTypes();

		/// <summary>
		/// Finds the hashing algorithm to use given an openid.session_type value.
		/// </summary>
		/// <param name="protocol">The protocol version of the message that named the session_type to be used.</param>
		/// <param name="sessionType">The value of the openid.session_type parameter.</param>
		/// <returns>The hashing algorithm to use.</returns>
		/// <exception cref="ProtocolException">Thrown if no match could be found for the given <paramref name="sessionType"/>.</exception>
		public static HashAlgorithm Lookup(Protocol protocol, string sessionType) {
			Requires.NotNull(protocol, "protocol");
			Requires.NotNull(sessionType, "sessionType");

			// We COULD use just First instead of FirstOrDefault, but we want to throw ProtocolException instead of InvalidOperationException.
			DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => string.Equals(dhsha.GetName(protocol), sessionType, StringComparison.Ordinal));
			ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoSessionTypeFound, sessionType, protocol.Version);
			return match.Algorithm;
		}

		/// <summary>
		/// Looks up the value to be used for the openid.session_type parameter.
		/// </summary>
		/// <param name="protocol">The protocol version that is to be used.</param>
		/// <param name="hashSizeInBits">The hash size (in bits) that the DH session must have.</param>
		/// <returns>The value to be used for the openid.session_type parameter, or null if no match was found.</returns>
		internal static string GetNameForSize(Protocol protocol, int hashSizeInBits) {
			Requires.NotNull(protocol, "protocol");
			DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => dhsha.Algorithm.HashSize == hashSizeInBits);
			return match != null ? match.GetName(protocol) : null;
		}

		/// <summary>
		/// Encrypts/decrypts a shared secret.
		/// </summary>
		/// <param name="hasher">The hashing algorithm that is agreed by both parties to use as part of the secret exchange.</param>
		/// <param name="dh">
		/// If the secret is being encrypted, this is the new Diffie Hellman object to use.
		/// If the secret is being decrypted, this must be the same Diffie Hellman object used to send the original request message.
		/// </param>
		/// <param name="remotePublicKey">The public key of the remote party.</param>
		/// <param name="plainOrEncryptedSecret">The secret to encode, or the encoded secret.  Whichever one is given will generate the opposite in the return value.</param>
		/// <returns>
		/// The encrypted version of the secret if the secret itself was given in <paramref name="remotePublicKey"/>.
		/// The secret itself if the encrypted version of the secret was given in <paramref name="remotePublicKey"/>.
		/// </returns>
		internal static byte[] SHAHashXorSecret(HashAlgorithm hasher, DiffieHellman dh, byte[] remotePublicKey, byte[] plainOrEncryptedSecret) {
			Requires.NotNull(hasher, "hasher");
			Requires.NotNull(dh, "dh");
			Requires.NotNull(remotePublicKey, "remotePublicKey");
			Requires.NotNull(plainOrEncryptedSecret, "plainOrEncryptedSecret");

			byte[] sharedBlock = dh.DecryptKeyExchange(remotePublicKey);
			byte[] sharedBlockHash = hasher.ComputeHash(EnsurePositive(sharedBlock));
			ErrorUtilities.VerifyProtocol(sharedBlockHash.Length == plainOrEncryptedSecret.Length, OpenIdStrings.AssociationSecretHashLengthMismatch, plainOrEncryptedSecret.Length, sharedBlockHash.Length);

			byte[] secret = new byte[plainOrEncryptedSecret.Length];
			for (int i = 0; i < plainOrEncryptedSecret.Length; i++) {
				secret[i] = (byte)(plainOrEncryptedSecret[i] ^ sharedBlockHash[i]);
			}
			return secret;
		}

		/// <summary>
		/// Ensures that the big integer represented by a given series of bytes
		/// is a positive integer.
		/// </summary>
		/// <param name="inputBytes">The bytes that make up the big integer.</param>
		/// <returns>
		/// A byte array (possibly new if a change was required) whose
		/// integer is guaranteed to be positive.
		/// </returns>
		/// <remarks>
		/// This is to be consistent with OpenID spec section 4.2.
		/// </remarks>
		internal static byte[] EnsurePositive(byte[] inputBytes) {
			Requires.NotNull(inputBytes, "inputBytes");
			if (inputBytes.Length == 0) {
				throw new ArgumentException(MessagingStrings.UnexpectedEmptyArray, "inputBytes");
			}

			int i = (int)inputBytes[0];
			if (i > 127) {
				byte[] nowPositive = new byte[inputBytes.Length + 1];
				nowPositive[0] = 0;
				inputBytes.CopyTo(nowPositive, 1);
				return nowPositive;
			}

			return inputBytes;
		}

		/// <summary>
		/// Returns the value used to initialize the static field storing DH session types.
		/// </summary>
		/// <returns>A non-null, non-empty array.</returns>
		/// <remarks>>
		/// This is a method rather than being inlined to the field initializer to try to avoid
		/// the CLR bug that crops up sometimes if we initialize arrays using object initializer syntax.
		/// </remarks>
		private static DHSha[] CreateSessionTypes() {
			return new[] {
				new DHSha(SHA512.Create(), protocol => protocol.Args.SessionType.DH_SHA512),
				new DHSha(SHA384.Create(), protocol => protocol.Args.SessionType.DH_SHA384),
				new DHSha(SHA256.Create(), protocol => protocol.Args.SessionType.DH_SHA256),
				new DHSha(SHA1.Create(), protocol => protocol.Args.SessionType.DH_SHA1),
			};
		}

		/// <summary>
		/// Provides access to a Diffie-Hellman session algorithm and its name.
		/// </summary>
		private class DHSha {
			/// <summary>
			/// Initializes a new instance of the <see cref="DHSha"/> class.
			/// </summary>
			/// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param>
			/// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param>
			public DHSha(HashAlgorithm algorithm, Func<Protocol, string> getName) {
				Requires.NotNull(algorithm, "algorithm");
				Requires.NotNull(getName, "getName");

				this.GetName = getName;
				this.Algorithm = algorithm;
			}

			/// <summary>
			/// Gets the function that will return the value of the openid.session_type parameter for a given version of OpenID.
			/// </summary>
			internal Func<Protocol, string> GetName { get; private set; }

			/// <summary>
			/// Gets the hashing algorithm used in this particular Diffie-Hellman session type
			/// </summary>
			internal HashAlgorithm Algorithm { get; private set; }
		}
	}
}