using System;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Asn1.Iana;
using Org.BouncyCastle.Asn1.Oiw;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Crmf
{
internal class PKMacStreamCalculator
: IStreamCalculator
{
private readonly MacSink _stream;
public PKMacStreamCalculator(IMac mac)
{
_stream = new MacSink(mac);
}
public Stream Stream
{
get { return _stream; }
}
public object GetResult()
{
return new DefaultPKMacResult(_stream.Mac);
}
}
internal class PKMacFactory
: IMacFactory
{
protected readonly PbmParameter parameters;
private readonly byte[] key;
public PKMacFactory(byte[] key, PbmParameter parameters)
{
this.key = Arrays.Clone(key);
this.parameters = parameters;
}
public virtual object AlgorithmDetails
{
get { return new AlgorithmIdentifier(CmpObjectIdentifiers.passwordBasedMac, parameters); }
}
public virtual IStreamCalculator CreateCalculator()
{
IMac mac = MacUtilities.GetMac(parameters.Mac.Algorithm);
mac.Init(new KeyParameter(key));
return new PKMacStreamCalculator(mac);
}
}
internal class DefaultPKMacResult
: IBlockResult
{
private readonly IMac mac;
public DefaultPKMacResult(IMac mac)
{
this.mac = mac;
}
public byte[] Collect()
{
byte[] res = new byte[mac.GetMacSize()];
mac.DoFinal(res, 0);
return res;
}
public int Collect(byte[] sig, int sigOff)
{
byte[] signature = Collect();
signature.CopyTo(sig, sigOff);
return signature.Length;
}
}
public class PKMacBuilder
{
private AlgorithmIdentifier owf;
private AlgorithmIdentifier mac;
private IPKMacPrimitivesProvider provider;
private SecureRandom random;
private PbmParameter parameters;
private int iterationCount;
private int saltLength;
private byte[] salt;
private int maxIterations;
///
/// Default, IterationCount = 1000, OIW=IdSha1, Mac=HmacSHA1
///
public PKMacBuilder() :
this(new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1), 1000, new AlgorithmIdentifier(IanaObjectIdentifiers.HmacSha1, DerNull.Instance), new DefaultPKMacPrimitivesProvider())
{
}
///
/// Defaults with IPKMacPrimitivesProvider
///
///
public PKMacBuilder(IPKMacPrimitivesProvider provider) :
this(new AlgorithmIdentifier(OiwObjectIdentifiers.IdSha1), 1000, new AlgorithmIdentifier(IanaObjectIdentifiers.HmacSha1, DerNull.Instance), provider)
{
}
///
/// Create.
///
/// The Mac provider
/// Digest Algorithm Id
/// Mac Algorithm Id
public PKMacBuilder(IPKMacPrimitivesProvider provider, AlgorithmIdentifier digestAlgorithmIdentifier, AlgorithmIdentifier macAlgorithmIdentifier) :
this(digestAlgorithmIdentifier, 1000, macAlgorithmIdentifier, provider)
{
}
///
/// Create a PKMAC builder enforcing a ceiling on the maximum iteration count.
///
/// supporting calculator
/// max allowable value for iteration count.
public PKMacBuilder(IPKMacPrimitivesProvider provider, int maxIterations)
{
this.provider = provider;
this.maxIterations = maxIterations;
}
private PKMacBuilder(AlgorithmIdentifier digestAlgorithmIdentifier, int iterationCount, AlgorithmIdentifier macAlgorithmIdentifier, IPKMacPrimitivesProvider provider)
{
this.iterationCount = iterationCount;
this.mac = macAlgorithmIdentifier;
this.owf = digestAlgorithmIdentifier;
this.provider = provider;
}
/**
* Set the salt length in octets.
*
* @param saltLength length in octets of the salt to be generated.
* @return the generator
*/
public PKMacBuilder SetSaltLength(int saltLength)
{
if (saltLength < 8)
throw new ArgumentException("salt length must be at least 8 bytes");
this.saltLength = saltLength;
return this;
}
///
/// Set the iteration count.
///
/// the iteration count.
/// this
/// if iteration count is less than 100
public PKMacBuilder SetIterationCount(int iterationCount)
{
if (iterationCount < 100)
throw new ArgumentException("iteration count must be at least 100");
CheckIterationCountCeiling(iterationCount);
this.iterationCount = iterationCount;
return this;
}
///
/// Set PbmParameters
///
/// The parameters.
/// this
public PKMacBuilder SetParameters(PbmParameter parameters)
{
CheckIterationCountCeiling(parameters.IterationCount.Value.IntValue);
this.parameters = parameters;
return this;
}
///
/// The Secure random
///
/// The random.
/// this
public PKMacBuilder SetSecureRandom(SecureRandom random)
{
this.random = random;
return this;
}
///
/// Build an IMacFactory.
///
/// The password.
/// IMacFactory
public IMacFactory Build(char[] password)
{
if (parameters != null)
return GenCalculator(parameters, password);
byte[] salt = new byte[saltLength];
if (random == null)
{
this.random = new SecureRandom();
}
random.NextBytes(salt);
return GenCalculator(new PbmParameter(salt, owf, iterationCount, mac), password);
}
private void CheckIterationCountCeiling(int iterationCount)
{
if (maxIterations > 0 && iterationCount > maxIterations)
throw new ArgumentException("iteration count exceeds limit (" + iterationCount + " > " + maxIterations + ")");
}
private IMacFactory GenCalculator(PbmParameter parameters, char[] password)
{
// From RFC 4211
//
// 1. Generate a random salt value S
//
// 2. Append the salt to the pw. K = pw || salt.
//
// 3. Hash the value of K. K = HASH(K)
//
// 4. Iter = Iter - 1. If Iter is greater than zero. Goto step 3.
//
// 5. Compute an HMAC as documented in [HMAC].
//
// MAC = HASH( K XOR opad, HASH( K XOR ipad, data) )
//
// Where opad and ipad are defined in [HMAC].
byte[] pw = Strings.ToUtf8ByteArray(password);
byte[] salt = parameters.Salt.GetOctets();
byte[] K = new byte[pw.Length + salt.Length];
Array.Copy(pw, 0, K, 0, pw.Length);
Array.Copy(salt, 0, K, pw.Length, salt.Length);
IDigest digest = provider.CreateDigest(parameters.Owf);
int iter = parameters.IterationCount.Value.IntValue;
digest.BlockUpdate(K, 0, K.Length);
K = new byte[digest.GetDigestSize()];
digest.DoFinal(K, 0);
while (--iter > 0)
{
digest.BlockUpdate(K, 0, K.Length);
digest.DoFinal(K, 0);
}
byte[] key = K;
return new PKMacFactory(key, parameters);
}
}
}