diff --git a/crypto/src/cmp/ProtectedPkiMessage.cs b/crypto/src/cmp/ProtectedPkiMessage.cs
index 8c9a4b152..df4c45143 100644
--- a/crypto/src/cmp/ProtectedPkiMessage.cs
+++ b/crypto/src/cmp/ProtectedPkiMessage.cs
@@ -119,6 +119,22 @@ namespace Org.BouncyCastle.Cmp
return Arrays.FixedTimeEquals(result.Collect(), m_pkiMessage.Protection.GetBytes());
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual bool Verify(PKMacBuilder pkMacBuilder, ReadOnlySpan<char> password)
+ {
+ if (!CmpObjectIdentifiers.passwordBasedMac.Equals(m_pkiMessage.Header.ProtectionAlg.Algorithm))
+ throw new InvalidOperationException("protection algorithm is not mac based");
+
+ PbmParameter parameter = PbmParameter.GetInstance(m_pkiMessage.Header.ProtectionAlg.Parameters);
+
+ pkMacBuilder.SetParameters(parameter);
+
+ IBlockResult result = Process(pkMacBuilder.Build(password).CreateCalculator());
+
+ return Arrays.FixedTimeEquals(result.Collect(), m_pkiMessage.Protection.GetBytes());
+ }
+#endif
+
private TResult Process<TResult>(IStreamCalculator<TResult> streamCalculator)
{
Asn1EncodableVector avec = new Asn1EncodableVector();
diff --git a/crypto/src/cms/CMSPBEKey.cs b/crypto/src/cms/CMSPBEKey.cs
index 78360c2cd..4b3e542ee 100644
--- a/crypto/src/cms/CMSPBEKey.cs
+++ b/crypto/src/cms/CMSPBEKey.cs
@@ -45,7 +45,29 @@ namespace Org.BouncyCastle.Cms
this.iterationCount = kdfParams.IterationCount.IntValue;
}
- ~CmsPbeKey()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public CmsPbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+ {
+ this.password = password.ToArray();
+ this.salt = salt.ToArray();
+ this.iterationCount = iterationCount;
+ }
+
+ public CmsPbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+ {
+ if (!keyDerivationAlgorithm.Algorithm.Equals(PkcsObjectIdentifiers.IdPbkdf2))
+ throw new ArgumentException("Unsupported key derivation algorithm: "
+ + keyDerivationAlgorithm.Algorithm);
+
+ Pbkdf2Params kdfParams = Pbkdf2Params.GetInstance(keyDerivationAlgorithm.Parameters.ToAsn1Object());
+
+ this.password = password.ToArray();
+ this.salt = kdfParams.GetSalt();
+ this.iterationCount = kdfParams.IterationCount.IntValue;
+ }
+#endif
+
+ ~CmsPbeKey()
{
Array.Clear(this.password, 0, this.password.Length);
}
diff --git a/crypto/src/cms/PKCS5Scheme2PBEKey.cs b/crypto/src/cms/PKCS5Scheme2PBEKey.cs
index 6606d5c45..78238292d 100644
--- a/crypto/src/cms/PKCS5Scheme2PBEKey.cs
+++ b/crypto/src/cms/PKCS5Scheme2PBEKey.cs
@@ -29,7 +29,19 @@ namespace Org.BouncyCastle.Cms
{
}
- internal override KeyParameter GetEncoded(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public Pkcs5Scheme2PbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+ : base(password, salt, iterationCount)
+ {
+ }
+
+ public Pkcs5Scheme2PbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+ : base(password, keyDerivationAlgorithm)
+ {
+ }
+#endif
+
+ internal override KeyParameter GetEncoded(
string algorithmOid)
{
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
diff --git a/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs b/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
index e2a09b760..68eff7b44 100644
--- a/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
+++ b/crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
@@ -29,7 +29,19 @@ namespace Org.BouncyCastle.Cms
{
}
- internal override KeyParameter GetEncoded(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public Pkcs5Scheme2Utf8PbeKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, int iterationCount)
+ : base(password, salt, iterationCount)
+ {
+ }
+
+ public Pkcs5Scheme2Utf8PbeKey(ReadOnlySpan<char> password, AlgorithmIdentifier keyDerivationAlgorithm)
+ : base(password, keyDerivationAlgorithm)
+ {
+ }
+#endif
+
+ internal override KeyParameter GetEncoded(
string algorithmOid)
{
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
diff --git a/crypto/src/crmf/CertificateRequestMessageBuilder.cs b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
index 38e95dfe7..363bfd136 100644
--- a/crypto/src/crmf/CertificateRequestMessageBuilder.cs
+++ b/crypto/src/crmf/CertificateRequestMessageBuilder.cs
@@ -163,8 +163,14 @@ namespace Org.BouncyCastle.Crmf
return this;
}
+ [Obsolete("Use 'SetAuthInfoPKMacBuilder' instead")]
public CertificateRequestMessageBuilder SetAuthInfoPKMAC(PKMacBuilder pkmacFactory, char[] password)
{
+ return SetAuthInfoPKMacBuilder(pkmacFactory, password);
+ }
+
+ public CertificateRequestMessageBuilder SetAuthInfoPKMacBuilder(PKMacBuilder pkmacFactory, char[] password)
+ {
this._pkMacBuilder = pkmacFactory;
this._password = password;
diff --git a/crypto/src/crmf/PKMacBuilder.cs b/crypto/src/crmf/PKMacBuilder.cs
index 7261a9daf..6db80325d 100644
--- a/crypto/src/crmf/PKMacBuilder.cs
+++ b/crypto/src/crmf/PKMacBuilder.cs
@@ -217,17 +217,31 @@ namespace Org.BouncyCastle.Crmf
/// <returns>IMacFactory</returns>
public IMacFactory Build(char[] password)
{
- if (parameters != null)
- return GenCalculator(parameters, password);
-
- byte[] salt = new byte[saltLength];
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return Build(password.AsSpan());
+#else
+ PbmParameter pbmParameter = parameters;
+ if (pbmParameter == null)
+ {
+ pbmParameter = GenParameters();
+ }
- this.random = CryptoServicesRegistrar.GetSecureRandom(random);
+ return GenCalculator(pbmParameter, password);
+#endif
+ }
- random.NextBytes(salt);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public IMacFactory Build(ReadOnlySpan<char> password)
+ {
+ PbmParameter pbmParameter = parameters;
+ if (pbmParameter == null)
+ {
+ pbmParameter = GenParameters();
+ }
- return GenCalculator(new PbmParameter(salt, owf, iterationCount, mac), password);
+ return GenCalculator(pbmParameter, password);
}
+#endif
private void CheckIterationCountCeiling(int iterationCount)
{
@@ -235,8 +249,20 @@ namespace Org.BouncyCastle.Crmf
throw new ArgumentException("iteration count exceeds limit (" + iterationCount + " > " + maxIterations + ")");
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private IMacFactory GenCalculator(PbmParameter parameters, ReadOnlySpan<char> password)
+ {
+ return GenCalculator(parameters, Strings.ToUtf8ByteArray(password));
+ }
+#else
private IMacFactory GenCalculator(PbmParameter parameters, char[] password)
{
+ return GenCalculator(parameters, Strings.ToUtf8ByteArray(password));
+ }
+#endif
+
+ private IMacFactory GenCalculator(PbmParameter parameters, byte[] pw)
+ {
// From RFC 4211
//
// 1. Generate a random salt value S
@@ -252,7 +278,6 @@ namespace Org.BouncyCastle.Crmf
// 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];
@@ -280,5 +305,12 @@ namespace Org.BouncyCastle.Crmf
return new PKMacFactory(key, parameters);
}
+
+ private PbmParameter GenParameters()
+ {
+ byte[] salt = SecureRandom.GetNextBytes(CryptoServicesRegistrar.GetSecureRandom(random), saltLength);
+
+ return new PbmParameter(salt, owf, iterationCount, mac);
+ }
}
}
diff --git a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
index 4cd568e81..4530b18b8 100644
--- a/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
+++ b/crypto/src/crmf/ProofOfPossessionSigningKeyBuilder.cs
@@ -38,27 +38,22 @@ namespace Org.BouncyCastle.Crmf
{
IMacFactory fact = generator.Build(password);
- byte[] d = _pubKeyInfo.GetDerEncoded();
-
- IStreamCalculator<IBlockResult> calc = fact.CreateCalculator();
- using (var stream = calc.Stream)
- {
- stream.Write(d, 0, d.Length);
- }
+ return ImplSetPublicKeyMac(fact);
+ }
- this._publicKeyMAC = new PKMacValue(
- (AlgorithmIdentifier)fact.AlgorithmDetails,
- new DerBitString(calc.GetResult().Collect()));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public ProofOfPossessionSigningKeyBuilder SetPublicKeyMac(PKMacBuilder generator, ReadOnlySpan<char> password)
+ {
+ IMacFactory fact = generator.Build(password);
- return this;
+ return ImplSetPublicKeyMac(fact);
}
+#endif
public PopoSigningKey Build(ISignatureFactory signer)
{
if (_name != null && _publicKeyMAC != null)
- {
throw new InvalidOperationException("name and publicKeyMAC cannot both be set.");
- }
PopoSigningKeyInput popo;
@@ -86,5 +81,22 @@ namespace Org.BouncyCastle.Crmf
return new PopoSigningKey(popo, (AlgorithmIdentifier)signer.AlgorithmDetails, new DerBitString(signature));
}
+
+ private ProofOfPossessionSigningKeyBuilder ImplSetPublicKeyMac(IMacFactory fact)
+ {
+ byte[] d = _pubKeyInfo.GetDerEncoded();
+
+ IStreamCalculator<IBlockResult> calc = fact.CreateCalculator();
+ using (var stream = calc.Stream)
+ {
+ stream.Write(d, 0, d.Length);
+ }
+
+ this._publicKeyMAC = new PKMacValue(
+ (AlgorithmIdentifier)fact.AlgorithmDetails,
+ new DerBitString(calc.GetResult().Collect()));
+
+ return this;
+ }
}
}
diff --git a/crypto/src/crypto/PbeParametersGenerator.cs b/crypto/src/crypto/PbeParametersGenerator.cs
index 86927d230..8c3ac30a8 100644
--- a/crypto/src/crypto/PbeParametersGenerator.cs
+++ b/crypto/src/crypto/PbeParametersGenerator.cs
@@ -44,6 +44,15 @@ namespace Org.BouncyCastle.Crypto
this.mIterationCount = iterationCount;
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public virtual void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterationCount)
+ {
+ this.mPassword = password.ToArray();
+ this.mSalt = salt.ToArray();
+ this.mIterationCount = iterationCount;
+ }
+#endif
+
public virtual byte[] Password
{
get { return Arrays.Clone(mPassword); }
@@ -105,9 +114,21 @@ namespace Org.BouncyCastle.Crypto
if (password == null)
return new byte[0];
- return Encoding.UTF8.GetBytes(password);
+ return Strings.ToUtf8ByteArray(password);
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public static byte[] Pkcs5PasswordToBytes(ReadOnlySpan<char> password)
+ {
+ return Strings.ToByteArray(password);
+ }
+
+ public static byte[] Pkcs5PasswordToUtf8Bytes(ReadOnlySpan<char> password)
+ {
+ return Strings.ToUtf8ByteArray(password);
+ }
+#endif
+
/**
* converts a password to a byte array according to the scheme in
* Pkcs12 (unicode, big endian, 2 zero pad bytes at the end).
@@ -137,5 +158,25 @@ namespace Org.BouncyCastle.Crypto
return bytes;
}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public static byte[] Pkcs12PasswordToBytes(ReadOnlySpan<char> password)
+ {
+ return Pkcs12PasswordToBytes(password, false);
+ }
+
+ public static byte[] Pkcs12PasswordToBytes(ReadOnlySpan<char> password, bool wrongPkcs12Zero)
+ {
+ if (password.IsEmpty)
+ return new byte[wrongPkcs12Zero ? 2 : 0];
+
+ // +1 for extra 2 pad bytes.
+ byte[] bytes = new byte[(password.Length + 1) * 2];
+
+ Encoding.BigEndianUnicode.GetBytes(password, bytes);
+
+ return bytes;
+ }
+#endif
}
}
diff --git a/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs b/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
index 448cd1920..9bdb82317 100644
--- a/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
+++ b/crypto/src/crypto/generators/OpenSSLPBEParametersGenerator.cs
@@ -58,10 +58,23 @@ namespace Org.BouncyCastle.Crypto.Generators
base.Init(password, salt, 1);
}
- /**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public override void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterationCount)
+ {
+ // Ignore the provided iterationCount
+ base.Init(password, salt, 1);
+ }
+
+ public virtual void Init(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt)
+ {
+ base.Init(password, salt, 1);
+ }
+#endif
+
+ /**
* the derived key function, the ith hash of the password and the salt.
*/
- private byte[] GenerateDerivedKey(
+ private byte[] GenerateDerivedKey(
int bytesNeeded)
{
byte[] buf = new byte[digest.GetDigestSize()];
diff --git a/crypto/src/openssl/MiscPemGenerator.cs b/crypto/src/openssl/MiscPemGenerator.cs
index ada0b84ed..0e918f793 100644
--- a/crypto/src/openssl/MiscPemGenerator.cs
+++ b/crypto/src/openssl/MiscPemGenerator.cs
@@ -128,20 +128,62 @@ namespace Org.BouncyCastle.OpenSsl
return new PemObject(type, encoding);
}
-// private string GetHexEncoded(byte[] bytes)
-// {
-// bytes = Hex.Encode(bytes);
-//
-// char[] chars = new char[bytes.Length];
-//
-// for (int i = 0; i != bytes.Length; i++)
-// {
-// chars[i] = (char)bytes[i];
-// }
-//
-// return new string(chars);
-// }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private static PemObject CreatePemObject(object obj, string algorithm, ReadOnlySpan<char> password,
+ SecureRandom random)
+ {
+ if (obj == null)
+ throw new ArgumentNullException("obj");
+ if (algorithm == null)
+ throw new ArgumentNullException("algorithm");
+ if (random == null)
+ throw new ArgumentNullException("random");
+
+ if (obj is AsymmetricCipherKeyPair keyPair)
+ {
+ return CreatePemObject(keyPair.Private, algorithm, password, random);
+ }
+
+ string type = null;
+ byte[] keyData = null;
+ if (obj is AsymmetricKeyParameter akp)
+ {
+ if (akp.IsPrivate)
+ {
+ keyData = EncodePrivateKey(akp, out type);
+ }
+ }
+
+ if (type == null || keyData == null)
+ {
+ // TODO Support other types?
+ throw new PemGenerationException("Object type not supported: " + Platform.GetTypeName(obj));
+ }
+
+
+ string dekAlgName = algorithm.ToUpperInvariant();
+
+ // Note: For backward compatibility
+ if (dekAlgName == "DESEDE")
+ {
+ dekAlgName = "DES-EDE3-CBC";
+ }
+
+ int ivLength = Platform.StartsWith(dekAlgName, "AES-") ? 16 : 8;
+
+ byte[] iv = new byte[ivLength];
+ random.NextBytes(iv);
+
+ byte[] encData = PemUtilities.Crypt(true, keyData, password, dekAlgName, iv);
+
+ var headers = new List<PemHeader>(2);
+ headers.Add(new PemHeader("Proc-Type", "4,ENCRYPTED"));
+ headers.Add(new PemHeader("DEK-Info", dekAlgName + "," + Hex.ToHexString(iv).ToUpperInvariant()));
+
+ return new PemObject(type, headers, encData);
+ }
+#else
private static PemObject CreatePemObject(
object obj,
string algorithm,
@@ -201,6 +243,7 @@ namespace Org.BouncyCastle.OpenSsl
return new PemObject(type, headers, encData);
}
+#endif
private static byte[] EncodePrivateKey(
AsymmetricKeyParameter akp,
diff --git a/crypto/src/openssl/PEMUtilities.cs b/crypto/src/openssl/PEMUtilities.cs
index 332768083..4ff340b12 100644
--- a/crypto/src/openssl/PEMUtilities.cs
+++ b/crypto/src/openssl/PEMUtilities.cs
@@ -50,6 +50,84 @@ namespace Org.BouncyCastle.OpenSsl
throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ internal static byte[] Crypt(bool encrypt, ReadOnlySpan<byte> bytes, ReadOnlySpan<char> password,
+ string dekAlgName, ReadOnlySpan<byte> iv)
+ {
+ PemBaseAlg baseAlg;
+ PemMode mode;
+ ParseDekAlgName(dekAlgName, out baseAlg, out mode);
+
+ string padding;
+ switch (mode)
+ {
+ case PemMode.CBC:
+ case PemMode.ECB:
+ padding = "PKCS5Padding";
+ break;
+ case PemMode.CFB:
+ case PemMode.OFB:
+ padding = "NoPadding";
+ break;
+ default:
+ throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
+ }
+
+ string algorithm;
+
+ ReadOnlySpan<byte> salt = iv;
+ switch (baseAlg)
+ {
+ case PemBaseAlg.AES_128:
+ case PemBaseAlg.AES_192:
+ case PemBaseAlg.AES_256:
+ algorithm = "AES";
+ if (salt.Length > 8)
+ {
+ salt = iv[..8].ToArray();
+ }
+ break;
+ case PemBaseAlg.BF:
+ algorithm = "BLOWFISH";
+ break;
+ case PemBaseAlg.DES:
+ algorithm = "DES";
+ break;
+ case PemBaseAlg.DES_EDE:
+ case PemBaseAlg.DES_EDE3:
+ algorithm = "DESede";
+ break;
+ case PemBaseAlg.RC2:
+ case PemBaseAlg.RC2_40:
+ case PemBaseAlg.RC2_64:
+ algorithm = "RC2";
+ break;
+ default:
+ throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName);
+ }
+
+ string cipherName = algorithm + "/" + mode + "/" + padding;
+ IBufferedCipher cipher = CipherUtilities.GetCipher(cipherName);
+
+ ICipherParameters cParams = GetCipherParameters(password, baseAlg, salt);
+
+ if (mode != PemMode.ECB)
+ {
+ cParams = new ParametersWithIV(cParams, iv);
+ }
+
+ cipher.Init(encrypt, cParams);
+
+ int outputSize = cipher.GetOutputSize(bytes.Length);
+ byte[] output = new byte[outputSize];
+ int length = cipher.DoFinal(bytes, output);
+ if (length < outputSize)
+ {
+ output = Arrays.CopyOfRange(output, 0, length);
+ }
+ return output;
+ }
+#else
internal static byte[] Crypt(
bool encrypt,
byte[] bytes,
@@ -124,7 +202,37 @@ namespace Org.BouncyCastle.OpenSsl
return cipher.DoFinal(bytes);
}
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private static ICipherParameters GetCipherParameters(ReadOnlySpan<char> password, PemBaseAlg baseAlg,
+ ReadOnlySpan<byte> salt)
+ {
+ string algorithm;
+ int keyBits;
+ switch (baseAlg)
+ {
+ case PemBaseAlg.AES_128: keyBits = 128; algorithm = "AES128"; break;
+ case PemBaseAlg.AES_192: keyBits = 192; algorithm = "AES192"; break;
+ case PemBaseAlg.AES_256: keyBits = 256; algorithm = "AES256"; break;
+ case PemBaseAlg.BF: keyBits = 128; algorithm = "BLOWFISH"; break;
+ case PemBaseAlg.DES: keyBits = 64; algorithm = "DES"; break;
+ case PemBaseAlg.DES_EDE: keyBits = 128; algorithm = "DESEDE"; break;
+ case PemBaseAlg.DES_EDE3: keyBits = 192; algorithm = "DESEDE3"; break;
+ case PemBaseAlg.RC2: keyBits = 128; algorithm = "RC2"; break;
+ case PemBaseAlg.RC2_40: keyBits = 40; algorithm = "RC2"; break;
+ case PemBaseAlg.RC2_64: keyBits = 64; algorithm = "RC2"; break;
+ default:
+ return null;
+ }
+
+ OpenSslPbeParametersGenerator pGen = new OpenSslPbeParametersGenerator();
+
+ pGen.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password), salt);
+ return pGen.GenerateDerivedParameters(algorithm, keyBits);
+ }
+#else
private static ICipherParameters GetCipherParameters(
char[] password,
PemBaseAlg baseAlg,
@@ -154,5 +262,6 @@ namespace Org.BouncyCastle.OpenSsl
return pGen.GenerateDerivedParameters(algorithm, keyBits);
}
- }
+#endif
+ }
}
diff --git a/crypto/src/security/JksStore.cs b/crypto/src/security/JksStore.cs
index c5ef92a70..4df0b39db 100644
--- a/crypto/src/security/JksStore.cs
+++ b/crypto/src/security/JksStore.cs
@@ -50,11 +50,15 @@ namespace Org.BouncyCastle.Security
/// <exception cref="IOException"/>
public AsymmetricKeyParameter GetKey(string alias, char[] password)
{
- if (alias == null)
- throw new ArgumentNullException(nameof(alias));
if (password == null)
throw new ArgumentNullException(nameof(password));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ return GetKey(alias, password.AsSpan());
+#else
+ if (alias == null)
+ throw new ArgumentNullException(nameof(alias));
+
if (!m_keyEntries.TryGetValue(alias, out JksKeyEntry keyEntry))
return null;
@@ -84,15 +88,89 @@ namespace Org.BouncyCastle.Security
throw new IOException("cannot recover key");
return PrivateKeyFactory.CreateKey(pkcs8Key);
+#endif
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ /// <exception cref="IOException"/>
+ public AsymmetricKeyParameter GetKey(string alias, ReadOnlySpan<char> password)
+ {
+ if (alias == null)
+ throw new ArgumentNullException(nameof(alias));
+
+ if (!m_keyEntries.TryGetValue(alias, out JksKeyEntry keyEntry))
+ return null;
+
+ if (!JksObfuscationAlg.Equals(keyEntry.keyData.EncryptionAlgorithm))
+ throw new IOException("unknown encryption algorithm");
+
+ byte[] encryptedData = keyEntry.keyData.GetEncryptedData();
+
+ // key length is encryptedData - salt - checksum
+ int pkcs8Len = encryptedData.Length - 40;
+
+ IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+ // key decryption
+ byte[] keyStream = CalculateKeyStream(digest, password, encryptedData, pkcs8Len);
+ byte[] pkcs8Key = new byte[pkcs8Len];
+ for (int i = 0; i < pkcs8Len; ++i)
+ {
+ pkcs8Key[i] = (byte)(encryptedData[20 + i] ^ keyStream[i]);
+ }
+ Array.Clear(keyStream, 0, keyStream.Length);
+
+ // integrity check
+ byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+
+ if (!Arrays.FixedTimeEquals(20, encryptedData, pkcs8Len + 20, checksum, 0))
+ throw new IOException("cannot recover key");
+
+ return PrivateKeyFactory.CreateKey(pkcs8Key);
+ }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private byte[] GetKeyChecksum(IDigest digest, ReadOnlySpan<char> password, ReadOnlySpan<byte> pkcs8Key)
+ {
+ AddPassword(digest, password);
+
+ return DigestUtilities.DoFinal(digest, pkcs8Key);
+ }
+#else
private byte[] GetKeyChecksum(IDigest digest, char[] password, byte[] pkcs8Key)
{
AddPassword(digest, password);
return DigestUtilities.DoFinal(digest, pkcs8Key);
}
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private byte[] CalculateKeyStream(IDigest digest, ReadOnlySpan<char> password, ReadOnlySpan<byte> salt,
+ int count)
+ {
+ byte[] keyStream = new byte[count];
+ Span<byte> hash = stackalloc byte[20];
+ hash.CopyFrom(salt);
+
+ int index = 0;
+ while (index < count)
+ {
+ AddPassword(digest, password);
+
+ digest.BlockUpdate(hash);
+ digest.DoFinal(hash);
+
+ int length = System.Math.Min(hash.Length, keyStream.Length - index);
+ keyStream.AsSpan(index, length).CopyFrom(hash);
+ index += length;
+ }
+
+ return keyStream;
+ }
+#else
private byte[] CalculateKeyStream(IDigest digest, char[] password, byte[] salt, int count)
{
byte[] keyStream = new byte[count];
@@ -113,6 +191,7 @@ namespace Org.BouncyCastle.Security
return keyStream;
}
+#endif
public X509Certificate[] GetCertificateChain(string alias)
{
@@ -128,7 +207,10 @@ namespace Org.BouncyCastle.Security
return certEntry.cert;
if (m_keyEntries.TryGetValue(alias, out var keyEntry))
- return keyEntry.chain?[0];
+ {
+ var chain = keyEntry.chain;
+ return chain == null || chain.Length == 0 ? null : chain[0];
+ }
return null;
}
@@ -147,6 +229,52 @@ namespace Org.BouncyCastle.Security
/// <exception cref="IOException"/>
public void SetKeyEntry(string alias, AsymmetricKeyParameter key, char[] password, X509Certificate[] chain)
{
+ if (password == null)
+ throw new ArgumentNullException(nameof(password));
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ SetKeyEntry(alias, key, password.AsSpan(), chain);
+#else
+ alias = ConvertAlias(alias);
+
+ if (ContainsAlias(alias))
+ throw new IOException("alias [" + alias + "] already in use");
+
+ byte[] pkcs8Key = PrivateKeyInfoFactory.CreatePrivateKeyInfo(key).GetEncoded();
+ byte[] protectedKey = new byte[pkcs8Key.Length + 40];
+
+ SecureRandom rnd = CryptoServicesRegistrar.GetSecureRandom();
+ rnd.NextBytes(protectedKey, 0, 20);
+
+ IDigest digest = DigestUtilities.GetDigest("SHA-1");
+
+ byte[] checksum = GetKeyChecksum(digest, password, pkcs8Key);
+ Array.Copy(checksum, 0, protectedKey, 20 + pkcs8Key.Length, 20);
+
+ byte[] keyStream = CalculateKeyStream(digest, password, protectedKey, pkcs8Key.Length);
+ for (int i = 0; i != keyStream.Length; i++)
+ {
+ protectedKey[20 + i] = (byte)(pkcs8Key[i] ^ keyStream[i]);
+ }
+ Array.Clear(keyStream, 0, keyStream.Length);
+
+ try
+ {
+ var epki = new EncryptedPrivateKeyInfo(JksObfuscationAlg, protectedKey);
+ m_keyEntries.Add(alias, new JksKeyEntry(DateTime.UtcNow, epki.GetEncoded(), CloneChain(chain)));
+ }
+ catch (Exception e)
+ {
+ throw new IOException("unable to encode encrypted private key", e);
+ }
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ /// <exception cref="IOException"/>
+ public void SetKeyEntry(string alias, AsymmetricKeyParameter key, ReadOnlySpan<char> password,
+ X509Certificate[] chain)
+ {
alias = ConvertAlias(alias);
if (ContainsAlias(alias))
@@ -180,6 +308,7 @@ namespace Org.BouncyCastle.Security
throw new IOException("unable to encode encrypted private key", e);
}
}
+#endif
/// <exception cref="IOException"/>
public void SetKeyEntry(string alias, byte[] key, X509Certificate[] chain)
@@ -254,12 +383,36 @@ namespace Org.BouncyCastle.Security
/// <exception cref="IOException"/>
public void Save(Stream stream, char[] password)
{
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
if (password == null)
throw new ArgumentNullException(nameof(password));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ Save(stream, password.AsSpan());
+#else
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
IDigest checksumDigest = CreateChecksumDigest(password);
+
+ SaveStream(stream, checksumDigest);
+#endif
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ /// <exception cref="IOException"/>
+ public void Save(Stream stream, ReadOnlySpan<char> password)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
+ IDigest checksumDigest = CreateChecksumDigest(password);
+
+ SaveStream(stream, checksumDigest);
+ }
+#endif
+
+ private void SaveStream(Stream stream, IDigest checksumDigest)
+ {
BinaryWriter bw = new BinaryWriter(new DigestStream(stream, null, checksumDigest));
BinaryWriters.WriteInt32BigEndian(bw, Magic);
@@ -286,7 +439,7 @@ namespace Org.BouncyCastle.Security
}
}
- foreach (var entry in m_certificateEntries)
+ foreach (var entry in m_certificateEntries)
{
string alias = entry.Key;
JksTrustedCertEntry certEntry = entry.Value;
@@ -302,74 +455,101 @@ namespace Org.BouncyCastle.Security
bw.Flush();
}
+ /// <remarks>WARNING: If <paramref name="password"/> is <c>null</c>, no integrity check is performed.</remarks>
/// <exception cref="IOException"/>
public void Load(Stream stream, char[] password)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
- m_certificateEntries.Clear();
- m_keyEntries.Clear();
+ using (var storeStream = ValidateStream(stream, password))
+ {
+ LoadStream(storeStream);
+ }
+ }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ /// <exception cref="IOException"/>
+ public void Load(Stream stream, ReadOnlySpan<char> password)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
using (var storeStream = ValidateStream(stream, password))
{
- BinaryReader br = new BinaryReader(storeStream);
+ LoadStream(storeStream);
+ }
+ }
+#endif
- int magic = BinaryReaders.ReadInt32BigEndian(br);
- int storeVersion = BinaryReaders.ReadInt32BigEndian(br);
+ /// <summary>Load without any integrity check.</summary>
+ /// <exception cref="IOException"/>
+ public void LoadUnchecked(Stream stream)
+ {
+ Load(stream, null);
+ }
+
+ private void LoadStream(ErasableByteStream storeStream)
+ {
+ m_certificateEntries.Clear();
+ m_keyEntries.Clear();
- if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2)))
- throw new IOException("Invalid keystore format");
+ BinaryReader br = new BinaryReader(storeStream);
- int numEntries = BinaryReaders.ReadInt32BigEndian(br);
+ int magic = BinaryReaders.ReadInt32BigEndian(br);
+ int storeVersion = BinaryReaders.ReadInt32BigEndian(br);
- for (int t = 0; t < numEntries; t++)
- {
- int tag = BinaryReaders.ReadInt32BigEndian(br);
+ if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2)))
+ throw new IOException("Invalid keystore format");
- switch (tag)
- {
- case 1: // keys
- {
- string alias = ReadUtf(br);
- DateTime date = ReadDateTime(br);
+ int numEntries = BinaryReaders.ReadInt32BigEndian(br);
+
+ for (int t = 0; t < numEntries; t++)
+ {
+ int tag = BinaryReaders.ReadInt32BigEndian(br);
- // encrypted key data
- byte[] keyData = ReadBufferWithInt32Length(br);
+ switch (tag)
+ {
+ case 1: // keys
+ {
+ string alias = ReadUtf(br);
+ DateTime date = ReadDateTime(br);
- // certificate chain
- int chainLength = BinaryReaders.ReadInt32BigEndian(br);
- X509Certificate[] chain = null;
- if (chainLength > 0)
+ // encrypted key data
+ byte[] keyData = ReadBufferWithInt32Length(br);
+
+ // certificate chain
+ int chainLength = BinaryReaders.ReadInt32BigEndian(br);
+ X509Certificate[] chain = null;
+ if (chainLength > 0)
+ {
+ var certs = new List<X509Certificate>(System.Math.Min(10, chainLength));
+ for (int certNo = 0; certNo != chainLength; certNo++)
{
- var certs = new List<X509Certificate>(System.Math.Min(10, chainLength));
- for (int certNo = 0; certNo != chainLength; certNo++)
- {
- certs.Add(ReadTypedCertificate(br, storeVersion));
- }
- chain = certs.ToArray();
+ certs.Add(ReadTypedCertificate(br, storeVersion));
}
- m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain));
- break;
+ chain = certs.ToArray();
}
- case 2: // certificate
- {
- string alias = ReadUtf(br);
- DateTime date = ReadDateTime(br);
+ m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain));
+ break;
+ }
+ case 2: // certificate
+ {
+ string alias = ReadUtf(br);
+ DateTime date = ReadDateTime(br);
- X509Certificate cert = ReadTypedCertificate(br, storeVersion);
+ X509Certificate cert = ReadTypedCertificate(br, storeVersion);
- m_certificateEntries.Add(alias, new JksTrustedCertEntry(date, cert));
- break;
- }
- default:
- throw new IOException("unable to discern entry type");
- }
+ m_certificateEntries.Add(alias, new JksTrustedCertEntry(date, cert));
+ break;
+ }
+ default:
+ throw new IOException("unable to discern entry type");
}
-
- if (storeStream.Position != storeStream.Length)
- throw new IOException("password incorrect or store tampered with");
}
+
+ if (storeStream.Position != storeStream.Length)
+ throw new IOException("password incorrect or store tampered with");
}
/*
@@ -391,7 +571,11 @@ namespace Org.BouncyCastle.Security
if (password != null)
{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ byte[] checksum = CalculateChecksum(password, rawStore.AsSpan(0, checksumPos));
+#else
byte[] checksum = CalculateChecksum(password, rawStore, 0, checksumPos);
+#endif
if (!Arrays.FixedTimeEquals(20, checksum, 0, rawStore, checksumPos))
{
@@ -403,6 +587,36 @@ namespace Org.BouncyCastle.Security
return new ErasableByteStream(rawStore, 0, checksumPos);
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ /// <exception cref="IOException"/>
+ private ErasableByteStream ValidateStream(Stream inputStream, ReadOnlySpan<char> password)
+ {
+ byte[] rawStore = Streams.ReadAll(inputStream);
+ int checksumPos = rawStore.Length - 20;
+
+ byte[] checksum = CalculateChecksum(password, rawStore.AsSpan(0, checksumPos));
+
+ if (!Arrays.FixedTimeEquals(20, checksum, 0, rawStore, checksumPos))
+ {
+ Array.Clear(rawStore, 0, rawStore.Length);
+ throw new IOException("password incorrect or store tampered with");
+ }
+
+ return new ErasableByteStream(rawStore, 0, checksumPos);
+ }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private static void AddPassword(IDigest digest, ReadOnlySpan<char> password)
+ {
+ // Encoding.BigEndianUnicode
+ for (int i = 0; i < password.Length; ++i)
+ {
+ digest.Update((byte)(password[i] >> 8));
+ digest.Update((byte)password[i]);
+ }
+ }
+#else
private static void AddPassword(IDigest digest, char[] password)
{
// Encoding.BigEndianUnicode
@@ -412,13 +626,23 @@ namespace Org.BouncyCastle.Security
digest.Update((byte)password[i]);
}
}
+#endif
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private static byte[] CalculateChecksum(ReadOnlySpan<char> password, ReadOnlySpan<byte> buffer)
+ {
+ IDigest checksumDigest = CreateChecksumDigest(password);
+ checksumDigest.BlockUpdate(buffer);
+ return DigestUtilities.DoFinal(checksumDigest);
+ }
+#else
private static byte[] CalculateChecksum(char[] password, byte[] buffer, int offset, int length)
{
IDigest checksumDigest = CreateChecksumDigest(password);
checksumDigest.BlockUpdate(buffer, offset, length);
return DigestUtilities.DoFinal(checksumDigest);
}
+#endif
private static X509Certificate[] CloneChain(X509Certificate[] chain)
{
@@ -430,6 +654,22 @@ namespace Org.BouncyCastle.Security
return alias.ToLowerInvariant();
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ private static IDigest CreateChecksumDigest(ReadOnlySpan<char> password)
+ {
+ IDigest digest = DigestUtilities.GetDigest("SHA-1");
+ AddPassword(digest, password);
+
+ //
+ // This "Mighty Aphrodite" string goes all the way back to the
+ // first java betas in the mid 90's, why who knows? But see
+ // https://cryptosense.com/mighty-aphrodite-dark-secrets-of-the-java-keystore/
+ //
+ byte[] prefix = Encoding.UTF8.GetBytes("Mighty Aphrodite");
+ digest.BlockUpdate(prefix);
+ return digest;
+ }
+#else
private static IDigest CreateChecksumDigest(char[] password)
{
IDigest digest = DigestUtilities.GetDigest("SHA-1");
@@ -444,6 +684,7 @@ namespace Org.BouncyCastle.Security
digest.BlockUpdate(prefix, 0, prefix.Length);
return digest;
}
+#endif
private static byte[] ReadBufferWithInt16Length(BinaryReader br)
{
diff --git a/crypto/src/util/Strings.cs b/crypto/src/util/Strings.cs
index 12eafd21e..29a95a07e 100644
--- a/crypto/src/util/Strings.cs
+++ b/crypto/src/util/Strings.cs
@@ -46,6 +46,18 @@ namespace Org.BouncyCastle.Utilities
return bs;
}
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public static byte[] ToByteArray(ReadOnlySpan<char> cs)
+ {
+ byte[] bs = new byte[cs.Length];
+ for (int i = 0; i < bs.Length; ++i)
+ {
+ bs[i] = Convert.ToByte(cs[i]);
+ }
+ return bs;
+ }
+#endif
+
public static string FromAsciiByteArray(byte[] bytes)
{
return Encoding.ASCII.GetString(bytes);
@@ -75,5 +87,15 @@ namespace Org.BouncyCastle.Utilities
{
return Encoding.UTF8.GetBytes(s);
}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+ public static byte[] ToUtf8ByteArray(ReadOnlySpan<char> cs)
+ {
+ int count = Encoding.UTF8.GetByteCount(cs);
+ byte[] bytes = new byte[count];
+ Encoding.UTF8.GetBytes(cs, bytes);
+ return bytes;
+ }
+#endif
}
}
|