You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1164 lines
42 KiB
1164 lines
42 KiB
1 year ago
|
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
|
||
|
#pragma warning disable
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.Oiw;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.Pkcs;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.X509;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.Utilities;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Collections;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Encoders;
|
||
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.X509;
|
||
|
|
||
|
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Pkcs
|
||
|
{
|
||
|
public class Pkcs12Store
|
||
|
{
|
||
|
public const string IgnoreUselessPasswordProperty = "BestHTTP.SecureProtocol.Org.BouncyCastle.Pkcs12.IgnoreUselessPassword";
|
||
|
|
||
|
private readonly IgnoresCaseHashtable keys = new IgnoresCaseHashtable();
|
||
|
private readonly IDictionary localIds = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
private readonly IgnoresCaseHashtable certs = new IgnoresCaseHashtable();
|
||
|
private readonly IDictionary chainCerts = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
private readonly IDictionary keyCerts = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
private readonly DerObjectIdentifier keyAlgorithm;
|
||
|
private readonly DerObjectIdentifier keyPrfAlgorithm;
|
||
|
private readonly DerObjectIdentifier certAlgorithm;
|
||
|
private readonly DerObjectIdentifier certPrfAlgorithm;
|
||
|
private readonly bool useDerEncoding;
|
||
|
|
||
|
private AsymmetricKeyEntry unmarkedKeyEntry = null;
|
||
|
|
||
|
private const int MinIterations = 1024;
|
||
|
private const int SaltSize = 20;
|
||
|
|
||
|
private static SubjectKeyIdentifier CreateSubjectKeyID(
|
||
|
AsymmetricKeyParameter pubKey)
|
||
|
{
|
||
|
return new SubjectKeyIdentifier(
|
||
|
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey));
|
||
|
}
|
||
|
|
||
|
internal class CertId
|
||
|
{
|
||
|
private readonly byte[] id;
|
||
|
|
||
|
internal CertId(
|
||
|
AsymmetricKeyParameter pubKey)
|
||
|
{
|
||
|
this.id = CreateSubjectKeyID(pubKey).GetKeyIdentifier();
|
||
|
}
|
||
|
|
||
|
internal CertId(
|
||
|
byte[] id)
|
||
|
{
|
||
|
this.id = id;
|
||
|
}
|
||
|
|
||
|
internal byte[] Id
|
||
|
{
|
||
|
get { return id; }
|
||
|
}
|
||
|
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return Arrays.GetHashCode(id);
|
||
|
}
|
||
|
|
||
|
public override bool Equals(
|
||
|
object obj)
|
||
|
{
|
||
|
if (obj == this)
|
||
|
return true;
|
||
|
|
||
|
CertId other = obj as CertId;
|
||
|
|
||
|
if (other == null)
|
||
|
return false;
|
||
|
|
||
|
return Arrays.AreEqual(id, other.id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal Pkcs12Store(
|
||
|
DerObjectIdentifier keyAlgorithm,
|
||
|
DerObjectIdentifier certAlgorithm,
|
||
|
bool useDerEncoding)
|
||
|
{
|
||
|
this.keyAlgorithm = keyAlgorithm;
|
||
|
this.keyPrfAlgorithm = null;
|
||
|
this.certAlgorithm = certAlgorithm;
|
||
|
this.certPrfAlgorithm = null;
|
||
|
this.useDerEncoding = useDerEncoding;
|
||
|
}
|
||
|
|
||
|
internal Pkcs12Store(
|
||
|
DerObjectIdentifier keyAlgorithm,
|
||
|
DerObjectIdentifier keyPrfAlgorithm,
|
||
|
DerObjectIdentifier certAlgorithm,
|
||
|
DerObjectIdentifier certPrfAlgorithm,
|
||
|
bool useDerEncoding)
|
||
|
{
|
||
|
this.keyAlgorithm = keyAlgorithm;
|
||
|
this.keyPrfAlgorithm = keyPrfAlgorithm;
|
||
|
this.certAlgorithm = certAlgorithm;
|
||
|
this.certPrfAlgorithm = certPrfAlgorithm;
|
||
|
this.useDerEncoding = useDerEncoding;
|
||
|
}
|
||
|
|
||
|
// TODO Consider making obsolete
|
||
|
|
||
|
public Pkcs12Store()
|
||
|
: this(PkcsObjectIdentifiers.PbeWithShaAnd3KeyTripleDesCbc,
|
||
|
PkcsObjectIdentifiers.PbewithShaAnd40BitRC2Cbc, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// TODO Consider making obsolete
|
||
|
|
||
|
public Pkcs12Store(
|
||
|
Stream input,
|
||
|
char[] password)
|
||
|
: this()
|
||
|
{
|
||
|
Load(input, password);
|
||
|
}
|
||
|
|
||
|
protected virtual void LoadKeyBag(PrivateKeyInfo privKeyInfo, Asn1Set bagAttributes)
|
||
|
{
|
||
|
AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(privKeyInfo);
|
||
|
|
||
|
IDictionary attributes = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(privKey, attributes);
|
||
|
|
||
|
string alias = null;
|
||
|
Asn1OctetString localId = null;
|
||
|
|
||
|
if (bagAttributes != null)
|
||
|
{
|
||
|
foreach (Asn1Sequence sq in bagAttributes)
|
||
|
{
|
||
|
DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]);
|
||
|
Asn1Set attrSet = Asn1Set.GetInstance(sq[1]);
|
||
|
Asn1Encodable attr = null;
|
||
|
|
||
|
if (attrSet.Count > 0)
|
||
|
{
|
||
|
// TODO We should be adding all attributes in the set
|
||
|
attr = attrSet[0];
|
||
|
|
||
|
// TODO We might want to "merge" attribute sets with
|
||
|
// the same OID - currently, differing values give an error
|
||
|
if (attributes.Contains(aOid.Id))
|
||
|
{
|
||
|
// OK, but the value has to be the same
|
||
|
if (!attributes[aOid.Id].Equals(attr))
|
||
|
throw new IOException("attempt to add existing attribute with different value");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
attributes.Add(aOid.Id, attr);
|
||
|
}
|
||
|
|
||
|
if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
|
||
|
{
|
||
|
alias = ((DerBmpString)attr).GetString();
|
||
|
// TODO Do these in a separate loop, just collect aliases here
|
||
|
keys[alias] = keyEntry;
|
||
|
}
|
||
|
else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
|
||
|
{
|
||
|
localId = (Asn1OctetString)attr;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (localId != null)
|
||
|
{
|
||
|
string name = Hex.ToHexString(localId.GetOctets());
|
||
|
|
||
|
if (alias == null)
|
||
|
{
|
||
|
keys[name] = keyEntry;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// TODO There may have been more than one alias
|
||
|
localIds[alias] = name;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
unmarkedKeyEntry = keyEntry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo encPrivKeyInfo, Asn1Set bagAttributes,
|
||
|
char[] password, bool wrongPkcs12Zero)
|
||
|
{
|
||
|
if (password != null)
|
||
|
{
|
||
|
PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(
|
||
|
password, wrongPkcs12Zero, encPrivKeyInfo);
|
||
|
|
||
|
LoadKeyBag(privInfo, bagAttributes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Load(
|
||
|
Stream input,
|
||
|
char[] password)
|
||
|
{
|
||
|
if (input == null)
|
||
|
throw new ArgumentNullException("input");
|
||
|
|
||
|
Pfx bag = Pfx.GetInstance(Asn1Object.FromStream(input));
|
||
|
ContentInfo info = bag.AuthSafe;
|
||
|
bool wrongPkcs12Zero = false;
|
||
|
|
||
|
if (bag.MacData != null) // check the mac code
|
||
|
{
|
||
|
if (password == null)
|
||
|
throw new ArgumentNullException("password", "no password supplied when one expected");
|
||
|
|
||
|
MacData mData = bag.MacData;
|
||
|
DigestInfo dInfo = mData.Mac;
|
||
|
AlgorithmIdentifier algId = dInfo.AlgorithmID;
|
||
|
byte[] salt = mData.GetSalt();
|
||
|
int itCount = mData.IterationCount.IntValue;
|
||
|
|
||
|
byte[] data = Asn1OctetString.GetInstance(info.Content).GetOctets();
|
||
|
|
||
|
byte[] mac = CalculatePbeMac(algId.Algorithm, salt, itCount, password, false, data);
|
||
|
byte[] dig = dInfo.GetDigest();
|
||
|
|
||
|
if (!Arrays.ConstantTimeAreEqual(mac, dig))
|
||
|
{
|
||
|
if (password.Length > 0)
|
||
|
throw new IOException("PKCS12 key store MAC invalid - wrong password or corrupted file.");
|
||
|
|
||
|
// Try with incorrect zero length password
|
||
|
mac = CalculatePbeMac(algId.Algorithm, salt, itCount, password, true, data);
|
||
|
|
||
|
if (!Arrays.ConstantTimeAreEqual(mac, dig))
|
||
|
throw new IOException("PKCS12 key store MAC invalid - wrong password or corrupted file.");
|
||
|
|
||
|
wrongPkcs12Zero = true;
|
||
|
}
|
||
|
}
|
||
|
else if (password != null)
|
||
|
{
|
||
|
string ignoreProperty = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.GetEnvironmentVariable(IgnoreUselessPasswordProperty);
|
||
|
bool ignore = ignoreProperty != null && BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.EqualsIgnoreCase("true", ignoreProperty);
|
||
|
|
||
|
if (!ignore)
|
||
|
{
|
||
|
throw new IOException("password supplied for keystore that does not require one");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
keys.Clear();
|
||
|
localIds.Clear();
|
||
|
unmarkedKeyEntry = null;
|
||
|
|
||
|
IList certBags = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
|
||
|
|
||
|
if (info.ContentType.Equals(PkcsObjectIdentifiers.Data))
|
||
|
{
|
||
|
Asn1OctetString content = Asn1OctetString.GetInstance(info.Content);
|
||
|
AuthenticatedSafe authSafe = AuthenticatedSafe.GetInstance(content.GetOctets());
|
||
|
ContentInfo[] cis = authSafe.GetContentInfo();
|
||
|
|
||
|
foreach (ContentInfo ci in cis)
|
||
|
{
|
||
|
DerObjectIdentifier oid = ci.ContentType;
|
||
|
|
||
|
byte[] octets = null;
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Data))
|
||
|
{
|
||
|
octets = Asn1OctetString.GetInstance(ci.Content).GetOctets();
|
||
|
}
|
||
|
else if (oid.Equals(PkcsObjectIdentifiers.EncryptedData))
|
||
|
{
|
||
|
if (password != null)
|
||
|
{
|
||
|
EncryptedData d = EncryptedData.GetInstance(ci.Content);
|
||
|
octets = CryptPbeData(false, d.EncryptionAlgorithm,
|
||
|
password, wrongPkcs12Zero, d.Content.GetOctets());
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// TODO Other data types
|
||
|
}
|
||
|
|
||
|
if (octets != null)
|
||
|
{
|
||
|
Asn1Sequence seq = Asn1Sequence.GetInstance(octets);
|
||
|
|
||
|
foreach (Asn1Sequence subSeq in seq)
|
||
|
{
|
||
|
SafeBag b = SafeBag.GetInstance(subSeq);
|
||
|
|
||
|
if (b.BagID.Equals(PkcsObjectIdentifiers.CertBag))
|
||
|
{
|
||
|
certBags.Add(b);
|
||
|
}
|
||
|
else if (b.BagID.Equals(PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag))
|
||
|
{
|
||
|
LoadPkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo.GetInstance(b.BagValue),
|
||
|
b.BagAttributes, password, wrongPkcs12Zero);
|
||
|
}
|
||
|
else if (b.BagID.Equals(PkcsObjectIdentifiers.KeyBag))
|
||
|
{
|
||
|
LoadKeyBag(PrivateKeyInfo.GetInstance(b.BagValue), b.BagAttributes);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// TODO Other bag types
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
certs.Clear();
|
||
|
chainCerts.Clear();
|
||
|
keyCerts.Clear();
|
||
|
|
||
|
foreach (SafeBag b in certBags)
|
||
|
{
|
||
|
CertBag certBag = CertBag.GetInstance(b.BagValue);
|
||
|
byte[] octets = ((Asn1OctetString)certBag.CertValue).GetOctets();
|
||
|
X509Certificate cert = new X509CertificateParser().ReadCertificate(octets);
|
||
|
|
||
|
//
|
||
|
// set the attributes
|
||
|
//
|
||
|
IDictionary attributes = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
Asn1OctetString localId = null;
|
||
|
string alias = null;
|
||
|
|
||
|
if (b.BagAttributes != null)
|
||
|
{
|
||
|
foreach (Asn1Sequence sq in b.BagAttributes)
|
||
|
{
|
||
|
DerObjectIdentifier aOid = DerObjectIdentifier.GetInstance(sq[0]);
|
||
|
Asn1Set attrSet = Asn1Set.GetInstance(sq[1]);
|
||
|
|
||
|
if (attrSet.Count > 0)
|
||
|
{
|
||
|
// TODO We should be adding all attributes in the set
|
||
|
Asn1Encodable attr = attrSet[0];
|
||
|
|
||
|
// TODO We might want to "merge" attribute sets with
|
||
|
// the same OID - currently, differing values give an error
|
||
|
if (attributes.Contains(aOid.Id))
|
||
|
{
|
||
|
// we've found more than one - one might be incorrect
|
||
|
if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
|
||
|
{
|
||
|
String id = Hex.ToHexString(Asn1OctetString.GetInstance(attr).GetOctets());
|
||
|
if (!(keys[id] != null || localIds[id] != null))
|
||
|
{
|
||
|
continue; // ignore this one - it's not valid
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// OK, but the value has to be the same
|
||
|
if (!attributes[aOid.Id].Equals(attr))
|
||
|
{
|
||
|
throw new IOException("attempt to add existing attribute with different value");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
attributes.Add(aOid.Id, attr);
|
||
|
}
|
||
|
|
||
|
if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName))
|
||
|
{
|
||
|
alias = ((DerBmpString)attr).GetString();
|
||
|
}
|
||
|
else if (aOid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID))
|
||
|
{
|
||
|
localId = (Asn1OctetString)attr;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CertId certId = new CertId(cert.GetPublicKey());
|
||
|
X509CertificateEntry certEntry = new X509CertificateEntry(cert, attributes);
|
||
|
|
||
|
chainCerts[certId] = certEntry;
|
||
|
|
||
|
if (unmarkedKeyEntry != null)
|
||
|
{
|
||
|
if (keyCerts.Count == 0)
|
||
|
{
|
||
|
string name = Hex.ToHexString(certId.Id);
|
||
|
|
||
|
keyCerts[name] = certEntry;
|
||
|
keys[name] = unmarkedKeyEntry;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
keys["unmarked"] = unmarkedKeyEntry;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (localId != null)
|
||
|
{
|
||
|
string name = Hex.ToHexString(localId.GetOctets());
|
||
|
|
||
|
keyCerts[name] = certEntry;
|
||
|
}
|
||
|
|
||
|
if (alias != null)
|
||
|
{
|
||
|
// TODO There may have been more than one alias
|
||
|
certs[alias] = certEntry;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public AsymmetricKeyEntry GetKey(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
return (AsymmetricKeyEntry)keys[alias];
|
||
|
}
|
||
|
|
||
|
public bool IsCertificateEntry(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
return (certs[alias] != null && keys[alias] == null);
|
||
|
}
|
||
|
|
||
|
public bool IsKeyEntry(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
return (keys[alias] != null);
|
||
|
}
|
||
|
|
||
|
private IDictionary GetAliasesTable()
|
||
|
{
|
||
|
IDictionary tab = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
|
||
|
foreach (string key in certs.Keys)
|
||
|
{
|
||
|
tab[key] = "cert";
|
||
|
}
|
||
|
|
||
|
foreach (string a in keys.Keys)
|
||
|
{
|
||
|
if (tab[a] == null)
|
||
|
{
|
||
|
tab[a] = "key";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return tab;
|
||
|
}
|
||
|
|
||
|
public IEnumerable Aliases
|
||
|
{
|
||
|
get { return new EnumerableProxy(GetAliasesTable().Keys); }
|
||
|
}
|
||
|
|
||
|
public bool ContainsAlias(
|
||
|
string alias)
|
||
|
{
|
||
|
return certs[alias] != null || keys[alias] != null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* simply return the cert entry for the private key
|
||
|
*/
|
||
|
public X509CertificateEntry GetCertificate(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
X509CertificateEntry c = (X509CertificateEntry) certs[alias];
|
||
|
|
||
|
//
|
||
|
// look up the key table - and try the local key id
|
||
|
//
|
||
|
if (c == null)
|
||
|
{
|
||
|
string id = (string)localIds[alias];
|
||
|
if (id != null)
|
||
|
{
|
||
|
c = (X509CertificateEntry)keyCerts[id];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
c = (X509CertificateEntry)keyCerts[alias];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
public string GetCertificateAlias(
|
||
|
X509Certificate cert)
|
||
|
{
|
||
|
if (cert == null)
|
||
|
throw new ArgumentNullException("cert");
|
||
|
|
||
|
foreach (DictionaryEntry entry in certs)
|
||
|
{
|
||
|
X509CertificateEntry entryValue = (X509CertificateEntry) entry.Value;
|
||
|
if (entryValue.Certificate.Equals(cert))
|
||
|
{
|
||
|
return (string) entry.Key;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (DictionaryEntry entry in keyCerts)
|
||
|
{
|
||
|
X509CertificateEntry entryValue = (X509CertificateEntry) entry.Value;
|
||
|
if (entryValue.Certificate.Equals(cert))
|
||
|
{
|
||
|
return (string) entry.Key;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public X509CertificateEntry[] GetCertificateChain(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
if (!IsKeyEntry(alias))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
X509CertificateEntry c = GetCertificate(alias);
|
||
|
|
||
|
if (c != null)
|
||
|
{
|
||
|
IList cs = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList();
|
||
|
|
||
|
while (c != null)
|
||
|
{
|
||
|
X509Certificate x509c = c.Certificate;
|
||
|
X509CertificateEntry nextC = null;
|
||
|
|
||
|
Asn1OctetString akiValue = x509c.GetExtensionValue(X509Extensions.AuthorityKeyIdentifier);
|
||
|
if (akiValue != null)
|
||
|
{
|
||
|
AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.GetInstance(akiValue.GetOctets());
|
||
|
|
||
|
byte[] keyID = aki.GetKeyIdentifier();
|
||
|
if (keyID != null)
|
||
|
{
|
||
|
nextC = (X509CertificateEntry)chainCerts[new CertId(keyID)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (nextC == null)
|
||
|
{
|
||
|
//
|
||
|
// no authority key id, try the Issuer DN
|
||
|
//
|
||
|
X509Name i = x509c.IssuerDN;
|
||
|
X509Name s = x509c.SubjectDN;
|
||
|
|
||
|
if (!i.Equivalent(s))
|
||
|
{
|
||
|
foreach (CertId certId in chainCerts.Keys)
|
||
|
{
|
||
|
X509CertificateEntry x509CertEntry = (X509CertificateEntry) chainCerts[certId];
|
||
|
|
||
|
X509Certificate crt = x509CertEntry.Certificate;
|
||
|
|
||
|
X509Name sub = crt.SubjectDN;
|
||
|
if (sub.Equivalent(i))
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
x509c.Verify(crt.GetPublicKey());
|
||
|
|
||
|
nextC = x509CertEntry;
|
||
|
break;
|
||
|
}
|
||
|
catch (InvalidKeyException)
|
||
|
{
|
||
|
// TODO What if it doesn't verify?
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cs.Add(c);
|
||
|
if (nextC != c) // self signed - end of the chain
|
||
|
{
|
||
|
c = nextC;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
c = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
X509CertificateEntry[] result = new X509CertificateEntry[cs.Count];
|
||
|
for (int i = 0; i < cs.Count; ++i)
|
||
|
{
|
||
|
result[i] = (X509CertificateEntry)cs[i];
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public void SetCertificateEntry(
|
||
|
string alias,
|
||
|
X509CertificateEntry certEntry)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
if (certEntry == null)
|
||
|
throw new ArgumentNullException("certEntry");
|
||
|
if (keys[alias] != null)
|
||
|
throw new ArgumentException("There is a key entry with the name " + alias + ".");
|
||
|
|
||
|
certs[alias] = certEntry;
|
||
|
chainCerts[new CertId(certEntry.Certificate.GetPublicKey())] = certEntry;
|
||
|
}
|
||
|
|
||
|
public void SetKeyEntry(
|
||
|
string alias,
|
||
|
AsymmetricKeyEntry keyEntry,
|
||
|
X509CertificateEntry[] chain)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
if (keyEntry == null)
|
||
|
throw new ArgumentNullException("keyEntry");
|
||
|
if (keyEntry.Key.IsPrivate && (chain == null))
|
||
|
throw new ArgumentException("No certificate chain for private key");
|
||
|
|
||
|
if (keys[alias] != null)
|
||
|
{
|
||
|
DeleteEntry(alias);
|
||
|
}
|
||
|
|
||
|
keys[alias] = keyEntry;
|
||
|
certs[alias] = chain[0];
|
||
|
|
||
|
for (int i = 0; i != chain.Length; i++)
|
||
|
{
|
||
|
chainCerts[new CertId(chain[i].Certificate.GetPublicKey())] = chain[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void DeleteEntry(
|
||
|
string alias)
|
||
|
{
|
||
|
if (alias == null)
|
||
|
throw new ArgumentNullException("alias");
|
||
|
|
||
|
AsymmetricKeyEntry k = (AsymmetricKeyEntry)keys[alias];
|
||
|
if (k != null)
|
||
|
{
|
||
|
keys.Remove(alias);
|
||
|
}
|
||
|
|
||
|
X509CertificateEntry c = (X509CertificateEntry)certs[alias];
|
||
|
|
||
|
if (c != null)
|
||
|
{
|
||
|
certs.Remove(alias);
|
||
|
chainCerts.Remove(new CertId(c.Certificate.GetPublicKey()));
|
||
|
}
|
||
|
|
||
|
if (k != null)
|
||
|
{
|
||
|
string id = (string)localIds[alias];
|
||
|
if (id != null)
|
||
|
{
|
||
|
localIds.Remove(alias);
|
||
|
c = (X509CertificateEntry)keyCerts[id];
|
||
|
}
|
||
|
if (c != null)
|
||
|
{
|
||
|
keyCerts.Remove(id);
|
||
|
chainCerts.Remove(new CertId(c.Certificate.GetPublicKey()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (c == null && k == null)
|
||
|
{
|
||
|
throw new ArgumentException("no such entry as " + alias);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsEntryOfType(
|
||
|
string alias,
|
||
|
Type entryType)
|
||
|
{
|
||
|
if (entryType == typeof(X509CertificateEntry))
|
||
|
return IsCertificateEntry(alias);
|
||
|
|
||
|
if (entryType == typeof(AsymmetricKeyEntry))
|
||
|
return IsKeyEntry(alias) && GetCertificate(alias) != null;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
public int Size()
|
||
|
{
|
||
|
return Count;
|
||
|
}
|
||
|
|
||
|
public int Count
|
||
|
{
|
||
|
// TODO Seems a little inefficient
|
||
|
get { return GetAliasesTable().Count; }
|
||
|
}
|
||
|
|
||
|
public void Save(
|
||
|
Stream stream,
|
||
|
char[] password,
|
||
|
SecureRandom random)
|
||
|
{
|
||
|
if (stream == null)
|
||
|
throw new ArgumentNullException("stream");
|
||
|
if (random == null)
|
||
|
throw new ArgumentNullException("random");
|
||
|
|
||
|
//
|
||
|
// handle the keys
|
||
|
//
|
||
|
Asn1EncodableVector keyBags = new Asn1EncodableVector();
|
||
|
foreach (string name in keys.Keys)
|
||
|
{
|
||
|
byte[] kSalt = new byte[SaltSize];
|
||
|
random.NextBytes(kSalt);
|
||
|
|
||
|
AsymmetricKeyEntry privKey = (AsymmetricKeyEntry)keys[name];
|
||
|
|
||
|
DerObjectIdentifier bagOid;
|
||
|
Asn1Encodable bagData;
|
||
|
|
||
|
if (password == null)
|
||
|
{
|
||
|
bagOid = PkcsObjectIdentifiers.KeyBag;
|
||
|
bagData = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privKey.Key);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bagOid = PkcsObjectIdentifiers.Pkcs8ShroudedKeyBag;
|
||
|
if (keyPrfAlgorithm != null)
|
||
|
{
|
||
|
bagData = EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo(
|
||
|
keyAlgorithm, keyPrfAlgorithm, password, kSalt, MinIterations, random, privKey.Key);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bagData = EncryptedPrivateKeyInfoFactory.CreateEncryptedPrivateKeyInfo(
|
||
|
keyAlgorithm, password, kSalt, MinIterations, privKey.Key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Asn1EncodableVector kName = new Asn1EncodableVector();
|
||
|
|
||
|
foreach (string oid in privKey.BagAttributeKeys)
|
||
|
{
|
||
|
Asn1Encodable entry = privKey[oid];
|
||
|
|
||
|
// NB: Ignore any existing FriendlyName
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id))
|
||
|
continue;
|
||
|
|
||
|
kName.Add(
|
||
|
new DerSequence(
|
||
|
new DerObjectIdentifier(oid),
|
||
|
new DerSet(entry)));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// make sure we are using the local alias on store
|
||
|
//
|
||
|
// NB: We always set the FriendlyName based on 'name'
|
||
|
//if (privKey[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null)
|
||
|
{
|
||
|
kName.Add(
|
||
|
new DerSequence(
|
||
|
PkcsObjectIdentifiers.Pkcs9AtFriendlyName,
|
||
|
new DerSet(new DerBmpString(name))));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// make sure we have a local key-id
|
||
|
//
|
||
|
if (privKey[PkcsObjectIdentifiers.Pkcs9AtLocalKeyID] == null)
|
||
|
{
|
||
|
X509CertificateEntry ct = GetCertificate(name);
|
||
|
AsymmetricKeyParameter pubKey = ct.Certificate.GetPublicKey();
|
||
|
SubjectKeyIdentifier subjectKeyID = CreateSubjectKeyID(pubKey);
|
||
|
|
||
|
kName.Add(
|
||
|
new DerSequence(
|
||
|
PkcsObjectIdentifiers.Pkcs9AtLocalKeyID,
|
||
|
new DerSet(subjectKeyID)));
|
||
|
}
|
||
|
|
||
|
keyBags.Add(new SafeBag(bagOid, bagData.ToAsn1Object(), new DerSet(kName)));
|
||
|
}
|
||
|
|
||
|
byte[] keyBagsEncoding = new DerSequence(keyBags).GetDerEncoded();
|
||
|
ContentInfo keysInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(keyBagsEncoding));
|
||
|
|
||
|
//
|
||
|
// certificate processing
|
||
|
//
|
||
|
byte[] cSalt = new byte[SaltSize];
|
||
|
|
||
|
random.NextBytes(cSalt);
|
||
|
|
||
|
Asn1EncodableVector certBags = new Asn1EncodableVector();
|
||
|
Pkcs12PbeParams cParams = new Pkcs12PbeParams(cSalt, MinIterations);
|
||
|
AlgorithmIdentifier cAlgId = new AlgorithmIdentifier(certAlgorithm, cParams.ToAsn1Object());
|
||
|
ISet doneCerts = new HashSet();
|
||
|
|
||
|
foreach (string name in keys.Keys)
|
||
|
{
|
||
|
X509CertificateEntry certEntry = GetCertificate(name);
|
||
|
CertBag cBag = new CertBag(
|
||
|
PkcsObjectIdentifiers.X509Certificate,
|
||
|
new DerOctetString(certEntry.Certificate.GetEncoded()));
|
||
|
|
||
|
Asn1EncodableVector fName = new Asn1EncodableVector();
|
||
|
|
||
|
foreach (string oid in certEntry.BagAttributeKeys)
|
||
|
{
|
||
|
Asn1Encodable entry = certEntry[oid];
|
||
|
|
||
|
// NB: Ignore any existing FriendlyName
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id))
|
||
|
continue;
|
||
|
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
new DerObjectIdentifier(oid),
|
||
|
new DerSet(entry)));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// make sure we are using the local alias on store
|
||
|
//
|
||
|
// NB: We always set the FriendlyName based on 'name'
|
||
|
//if (certEntry[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null)
|
||
|
{
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
PkcsObjectIdentifiers.Pkcs9AtFriendlyName,
|
||
|
new DerSet(new DerBmpString(name))));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// make sure we have a local key-id
|
||
|
//
|
||
|
if (certEntry[PkcsObjectIdentifiers.Pkcs9AtLocalKeyID] == null)
|
||
|
{
|
||
|
AsymmetricKeyParameter pubKey = certEntry.Certificate.GetPublicKey();
|
||
|
SubjectKeyIdentifier subjectKeyID = CreateSubjectKeyID(pubKey);
|
||
|
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
PkcsObjectIdentifiers.Pkcs9AtLocalKeyID,
|
||
|
new DerSet(subjectKeyID)));
|
||
|
}
|
||
|
|
||
|
certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
|
||
|
|
||
|
doneCerts.Add(certEntry.Certificate);
|
||
|
}
|
||
|
|
||
|
foreach (string certId in certs.Keys)
|
||
|
{
|
||
|
X509CertificateEntry cert = (X509CertificateEntry)certs[certId];
|
||
|
|
||
|
if (keys[certId] != null)
|
||
|
continue;
|
||
|
|
||
|
CertBag cBag = new CertBag(
|
||
|
PkcsObjectIdentifiers.X509Certificate,
|
||
|
new DerOctetString(cert.Certificate.GetEncoded()));
|
||
|
|
||
|
Asn1EncodableVector fName = new Asn1EncodableVector();
|
||
|
|
||
|
foreach (string oid in cert.BagAttributeKeys)
|
||
|
{
|
||
|
// a certificate not immediately linked to a key doesn't require
|
||
|
// a localKeyID and will confuse some PKCS12 implementations.
|
||
|
//
|
||
|
// If we find one, we'll prune it out.
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID.Id))
|
||
|
continue;
|
||
|
|
||
|
Asn1Encodable entry = cert[oid];
|
||
|
|
||
|
// NB: Ignore any existing FriendlyName
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtFriendlyName.Id))
|
||
|
continue;
|
||
|
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
new DerObjectIdentifier(oid),
|
||
|
new DerSet(entry)));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// make sure we are using the local alias on store
|
||
|
//
|
||
|
// NB: We always set the FriendlyName based on 'certId'
|
||
|
//if (cert[PkcsObjectIdentifiers.Pkcs9AtFriendlyName] == null)
|
||
|
{
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
PkcsObjectIdentifiers.Pkcs9AtFriendlyName,
|
||
|
new DerSet(new DerBmpString(certId))));
|
||
|
}
|
||
|
|
||
|
certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
|
||
|
|
||
|
doneCerts.Add(cert.Certificate);
|
||
|
}
|
||
|
|
||
|
foreach (CertId certId in chainCerts.Keys)
|
||
|
{
|
||
|
X509CertificateEntry cert = (X509CertificateEntry)chainCerts[certId];
|
||
|
|
||
|
if (doneCerts.Contains(cert.Certificate))
|
||
|
continue;
|
||
|
|
||
|
CertBag cBag = new CertBag(
|
||
|
PkcsObjectIdentifiers.X509Certificate,
|
||
|
new DerOctetString(cert.Certificate.GetEncoded()));
|
||
|
|
||
|
Asn1EncodableVector fName = new Asn1EncodableVector();
|
||
|
|
||
|
foreach (string oid in cert.BagAttributeKeys)
|
||
|
{
|
||
|
// a certificate not immediately linked to a key doesn't require
|
||
|
// a localKeyID and will confuse some PKCS12 implementations.
|
||
|
//
|
||
|
// If we find one, we'll prune it out.
|
||
|
if (oid.Equals(PkcsObjectIdentifiers.Pkcs9AtLocalKeyID.Id))
|
||
|
continue;
|
||
|
|
||
|
fName.Add(
|
||
|
new DerSequence(
|
||
|
new DerObjectIdentifier(oid),
|
||
|
new DerSet(cert[oid])));
|
||
|
}
|
||
|
|
||
|
certBags.Add(new SafeBag(PkcsObjectIdentifiers.CertBag, cBag.ToAsn1Object(), new DerSet(fName)));
|
||
|
}
|
||
|
|
||
|
byte[] certBagsEncoding = new DerSequence(certBags).GetDerEncoded();
|
||
|
|
||
|
ContentInfo certsInfo;
|
||
|
if (password == null || certAlgorithm == null)
|
||
|
{
|
||
|
certsInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(certBagsEncoding));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
byte[] certBytes = CryptPbeData(true, cAlgId, password, false, certBagsEncoding);
|
||
|
EncryptedData cInfo = new EncryptedData(PkcsObjectIdentifiers.Data, cAlgId, new BerOctetString(certBytes));
|
||
|
certsInfo = new ContentInfo(PkcsObjectIdentifiers.EncryptedData, cInfo.ToAsn1Object());
|
||
|
}
|
||
|
|
||
|
ContentInfo[] info = new ContentInfo[]{ keysInfo, certsInfo };
|
||
|
|
||
|
byte[] data = new AuthenticatedSafe(info).GetEncoded(
|
||
|
useDerEncoding ? Asn1Encodable.Der : Asn1Encodable.Ber);
|
||
|
|
||
|
ContentInfo mainInfo = new ContentInfo(PkcsObjectIdentifiers.Data, new BerOctetString(data));
|
||
|
|
||
|
//
|
||
|
// create the mac
|
||
|
//
|
||
|
MacData macData = null;
|
||
|
if (password != null)
|
||
|
{
|
||
|
byte[] mSalt = new byte[20];
|
||
|
random.NextBytes(mSalt);
|
||
|
|
||
|
byte[] mac = CalculatePbeMac(OiwObjectIdentifiers.IdSha1,
|
||
|
mSalt, MinIterations, password, false, data);
|
||
|
|
||
|
AlgorithmIdentifier algId = new AlgorithmIdentifier(
|
||
|
OiwObjectIdentifiers.IdSha1, DerNull.Instance);
|
||
|
DigestInfo dInfo = new DigestInfo(algId, mac);
|
||
|
|
||
|
macData = new MacData(dInfo, mSalt, MinIterations);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// output the Pfx
|
||
|
//
|
||
|
Pfx pfx = new Pfx(mainInfo, macData);
|
||
|
|
||
|
pfx.EncodeTo(stream, useDerEncoding ? Asn1Encodable.Der : Asn1Encodable.Ber);
|
||
|
}
|
||
|
|
||
|
internal static byte[] CalculatePbeMac(
|
||
|
DerObjectIdentifier oid,
|
||
|
byte[] salt,
|
||
|
int itCount,
|
||
|
char[] password,
|
||
|
bool wrongPkcs12Zero,
|
||
|
byte[] data)
|
||
|
{
|
||
|
Asn1Encodable asn1Params = PbeUtilities.GenerateAlgorithmParameters(
|
||
|
oid, salt, itCount);
|
||
|
ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters(
|
||
|
oid, password, wrongPkcs12Zero, asn1Params);
|
||
|
|
||
|
IMac mac = (IMac) PbeUtilities.CreateEngine(oid);
|
||
|
mac.Init(cipherParams);
|
||
|
return MacUtilities.DoFinal(mac, data);
|
||
|
}
|
||
|
|
||
|
private static byte[] CryptPbeData(
|
||
|
bool forEncryption,
|
||
|
AlgorithmIdentifier algId,
|
||
|
char[] password,
|
||
|
bool wrongPkcs12Zero,
|
||
|
byte[] data)
|
||
|
{
|
||
|
IBufferedCipher cipher = PbeUtilities.CreateEngine(algId) as IBufferedCipher;
|
||
|
|
||
|
if (cipher == null)
|
||
|
throw new Exception("Unknown encryption algorithm: " + algId.Algorithm);
|
||
|
|
||
|
if (algId.Algorithm.Equals(PkcsObjectIdentifiers.IdPbeS2))
|
||
|
{
|
||
|
PbeS2Parameters pbeParameters = PbeS2Parameters.GetInstance(algId.Parameters);
|
||
|
ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters(
|
||
|
algId.Algorithm, password, pbeParameters);
|
||
|
cipher.Init(forEncryption, cipherParams);
|
||
|
return cipher.DoFinal(data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Pkcs12PbeParams pbeParameters = Pkcs12PbeParams.GetInstance(algId.Parameters);
|
||
|
ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters(
|
||
|
algId.Algorithm, password, wrongPkcs12Zero, pbeParameters);
|
||
|
cipher.Init(forEncryption, cipherParams);
|
||
|
return cipher.DoFinal(data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class IgnoresCaseHashtable
|
||
|
: IEnumerable
|
||
|
{
|
||
|
private readonly IDictionary orig = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
private readonly IDictionary keys = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable();
|
||
|
|
||
|
public void Clear()
|
||
|
{
|
||
|
orig.Clear();
|
||
|
keys.Clear();
|
||
|
}
|
||
|
|
||
|
public IEnumerator GetEnumerator()
|
||
|
{
|
||
|
return orig.GetEnumerator();
|
||
|
}
|
||
|
|
||
|
public ICollection Keys
|
||
|
{
|
||
|
get { return orig.Keys; }
|
||
|
}
|
||
|
|
||
|
public object Remove(
|
||
|
string alias)
|
||
|
{
|
||
|
string upper = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.ToUpperInvariant(alias);
|
||
|
string k = (string)keys[upper];
|
||
|
|
||
|
if (k == null)
|
||
|
return null;
|
||
|
|
||
|
keys.Remove(upper);
|
||
|
|
||
|
object o = orig[k];
|
||
|
orig.Remove(k);
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
public object this[
|
||
|
string alias]
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
string upper = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.ToUpperInvariant(alias);
|
||
|
string k = (string)keys[upper];
|
||
|
|
||
|
if (k == null)
|
||
|
return null;
|
||
|
|
||
|
return orig[k];
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
string upper = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.ToUpperInvariant(alias);
|
||
|
string k = (string)keys[upper];
|
||
|
if (k != null)
|
||
|
{
|
||
|
orig.Remove(k);
|
||
|
}
|
||
|
keys[upper] = alias;
|
||
|
orig[alias] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ICollection Values
|
||
|
{
|
||
|
get { return orig.Values; }
|
||
|
}
|
||
|
|
||
|
public int Count
|
||
|
{
|
||
|
get { return orig.Count; }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#pragma warning restore
|
||
|
#endif
|