//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.ApplicationBlock.Provider {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
public abstract class AnonymousIdentifierProviderBase {
private int newSaltLength = 20;
///
/// Initializes a new instance of the class.
///
/// The base URI on which to append the anonymous part.
public AnonymousIdentifierProviderBase(Uri baseIdentifier) {
if (baseIdentifier == null) {
throw new ArgumentNullException("baseIdentifier");
}
this.Hasher = HashAlgorithm.Create("SHA256");
this.Encoder = Encoding.UTF8;
this.BaseIdentifier = baseIdentifier;
}
public Uri BaseIdentifier { get; private set; }
protected HashAlgorithm Hasher { get; private set; }
protected Encoding Encoder { get; private set; }
protected int NewSaltLength {
get {
return this.newSaltLength;
}
set {
if (value <= 0) {
throw new ArgumentOutOfRangeException("value");
}
this.newSaltLength = value;
}
}
#region IAnonymousIdentifierProvider Members
public Uri GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) {
byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier);
string valueToHash = localIdentifier + "#" + (relyingPartyRealm ?? string.Empty);
byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash);
byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length];
valueAsBytes.CopyTo(bytesToHash, 0);
salt.CopyTo(bytesToHash, valueAsBytes.Length);
byte[] hash = this.Hasher.ComputeHash(bytesToHash);
string base64Hash = Convert.ToBase64String(hash);
Uri anonymousIdentifier = this.AppendIdentifiers(this.BaseIdentifier, base64Hash);
return anonymousIdentifier;
}
#endregion
protected virtual byte[] GetNewSalt() {
// We COULD use a crypto random function, but for a salt it seems overkill.
return Util.GetNonCryptoRandomData(this.NewSaltLength);
}
protected Uri AppendIdentifiers(Uri baseIdentifier, string uriHash) {
if (baseIdentifier == null) {
throw new ArgumentNullException("baseIdentifier");
}
if (String.IsNullOrEmpty(uriHash)) {
throw new ArgumentNullException("uriHash");
}
if (string.IsNullOrEmpty(baseIdentifier.Query)) {
// The uriHash will appear on the path itself.
string pathEncoded = Uri.EscapeUriString(uriHash.Replace('/', '_'));
return new Uri(baseIdentifier, pathEncoded);
} else {
// The uriHash will appear on the query string.
string dataEncoded = Uri.EscapeDataString(uriHash);
return new Uri(baseIdentifier + dataEncoded);
}
}
///
/// Gets the salt to use for generating an anonymous identifier for a given OP local identifier.
///
/// The OP local identifier.
/// The salt to use in the hash.
///
/// It is important that this method always return the same value for a given
/// .
/// New salts can be generated for local identifiers without previously assigned salt
/// values by calling or by a custom method.
///
protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier);
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
protected void ObjectInvariant() {
Contract.Invariant(this.Hasher != null);
Contract.Invariant(this.Encoder != null);
Contract.Invariant(this.BaseIdentifier != null);
Contract.Invariant(this.NewHashLength > 0);
}
#endif
}
}