//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
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;
///
/// Diffie-Hellman encryption methods used by both the relying party and provider.
///
internal class DiffieHellmanUtilities {
///
/// An array of known Diffie Hellman sessions, sorted by decreasing hash size.
///
private static DHSha[] diffieHellmanSessionTypes = CreateSessionTypes();
///
/// Finds the hashing algorithm to use given an openid.session_type value.
///
/// The protocol version of the message that named the session_type to be used.
/// The value of the openid.session_type parameter.
/// The hashing algorithm to use.
/// Thrown if no match could be found for the given .
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;
}
///
/// Looks up the value to be used for the openid.session_type parameter.
///
/// The protocol version that is to be used.
/// The hash size (in bits) that the DH session must have.
/// The value to be used for the openid.session_type parameter, or null if no match was found.
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;
}
///
/// Encrypts/decrypts a shared secret.
///
/// The hashing algorithm that is agreed by both parties to use as part of the secret exchange.
///
/// 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.
///
/// The public key of the remote party.
/// The secret to encode, or the encoded secret. Whichever one is given will generate the opposite in the return value.
///
/// The encrypted version of the secret if the secret itself was given in .
/// The secret itself if the encrypted version of the secret was given in .
///
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;
}
///
/// Ensures that the big integer represented by a given series of bytes
/// is a positive integer.
///
/// The bytes that make up the big integer.
///
/// A byte array (possibly new if a change was required) whose
/// integer is guaranteed to be positive.
///
///
/// This is to be consistent with OpenID spec section 4.2.
///
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;
}
///
/// Returns the value used to initialize the static field storing DH session types.
///
/// A non-null, non-empty array.
/// >
/// 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.
///
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),
};
}
///
/// Provides access to a Diffie-Hellman session algorithm and its name.
///
private class DHSha {
///
/// Initializes a new instance of the class.
///
/// The hashing algorithm used in this particular Diffie-Hellman session type.
/// A function that will return the value of the openid.session_type parameter for a given version of OpenID.
public DHSha(HashAlgorithm algorithm, Func getName) {
Requires.NotNull(algorithm, "algorithm");
Requires.NotNull(getName, "getName");
this.GetName = getName;
this.Algorithm = algorithm;
}
///
/// Gets the function that will return the value of the openid.session_type parameter for a given version of OpenID.
///
internal Func GetName { get; private set; }
///
/// Gets the hashing algorithm used in this particular Diffie-Hellman session type
///
internal HashAlgorithm Algorithm { get; private set; }
}
}
}