diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2022-09-12 12:04:41 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2022-09-12 12:04:41 +0700 |
commit | 611e75935efc4266b9a66b25cff68c66f46a2bd2 (patch) | |
tree | a1b1b901bde0ac62ce0c6ba820cadc1707af53c0 /crypto | |
parent | Improve span-based GCM code (diff) | |
download | BouncyCastle.NET-ed25519-611e75935efc4266b9a66b25cff68c66f46a2bd2.tar.xz |
Add basic support for JKS keystores
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/src/security/JksStore.cs | 610 | ||||
-rw-r--r-- | crypto/test/src/security/test/JksStoreTest.cs | 181 |
2 files changed, 791 insertions, 0 deletions
diff --git a/crypto/src/security/JksStore.cs b/crypto/src/security/JksStore.cs new file mode 100644 index 000000000..9b4269278 --- /dev/null +++ b/crypto/src/security/JksStore.cs @@ -0,0 +1,610 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.Utilities.IO; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Security +{ + public class JksStore + { + private static readonly int Magic = unchecked((int)0xFEEDFEED); + + private static readonly AlgorithmIdentifier JksObfuscationAlg = new AlgorithmIdentifier( + new DerObjectIdentifier("1.3.6.1.4.1.42.2.17.1.1"), DerNull.Instance); + + private readonly Dictionary<string, JksTrustedCertEntry> m_certificateEntries = + new Dictionary<string, JksTrustedCertEntry>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<string, JksKeyEntry> m_keyEntries = + new Dictionary<string, JksKeyEntry>(StringComparer.OrdinalIgnoreCase); + + public JksStore() + { + } + + /// <exception cref="IOException"/> + public bool Probe(Stream stream) + { + using (var br = new BinaryReader(stream)) + try + { + return Magic == ReadInt32(br); + } + catch (EndOfStreamException) + { + return false; + } + } + + /// <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 (!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.ConstantTimeAreEqual(20, encryptedData, pkcs8Len + 20, checksum, 0)) + throw new IOException("cannot recover key"); + + return PrivateKeyFactory.CreateKey(pkcs8Key); + } + + private byte[] GetKeyChecksum(IDigest digest, char[] password, byte[] pkcs8Key) + { + AddPassword(digest, password); + + return DigestUtilities.DoFinal(digest, pkcs8Key); + } + + private byte[] CalculateKeyStream(IDigest digest, char[] password, byte[] salt, int count) + { + byte[] keyStream = new byte[count]; + byte[] hash = Arrays.CopyOf(salt, 20); + + int index = 0; + while (index < count) + { + AddPassword(digest, password); + + digest.BlockUpdate(hash, 0, hash.Length); + digest.DoFinal(hash, 0); + + int length = System.Math.Min(hash.Length, keyStream.Length - index); + Array.Copy(hash, 0, keyStream, index, length); + index += length; + } + + return keyStream; + } + + public X509Certificate[] GetCertificateChain(string alias) + { + if (m_keyEntries.TryGetValue(alias, out var keyEntry)) + return CloneChain(keyEntry.chain); + + return null; + } + + public X509Certificate GetCertificate(string alias) + { + if (m_certificateEntries.TryGetValue(alias, out var certEntry)) + return certEntry.cert; + + if (m_keyEntries.TryGetValue(alias, out var keyEntry)) + return keyEntry.chain?[0]; + + return null; + } + + public DateTime? GetCreationDate(string alias) + { + if (m_certificateEntries.TryGetValue(alias, out var certEntry)) + return certEntry.date; + + if (m_keyEntries.TryGetValue(alias, out var keyEntry)) + return keyEntry.date; + + return null; + } + + /// <exception cref="IOException"/> + public void SetKeyEntry(string alias, AsymmetricKeyParameter key, char[] password, X509Certificate[] chain) + { + 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 = new SecureRandom(); + 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); + } + } + + /// <exception cref="IOException"/> + public void SetKeyEntry(string alias, byte[] key, X509Certificate[] chain) + { + alias = ConvertAlias(alias); + + if (ContainsAlias(alias)) + throw new IOException("alias [" + alias + "] already in use"); + + m_keyEntries.Add(alias, new JksKeyEntry(DateTime.UtcNow, key, CloneChain(chain))); + } + + /// <exception cref="IOException"/> + public void SetCertificateEntry(string alias, X509Certificate cert) + { + alias = ConvertAlias(alias); + + if (ContainsAlias(alias)) + throw new IOException("alias [" + alias + "] already in use"); + + m_certificateEntries.Add(alias, new JksTrustedCertEntry(DateTime.UtcNow, cert)); + } + + public void DeleteEntry(string alias) + { + if (!m_keyEntries.Remove(alias)) + { + m_certificateEntries.Remove(alias); + } + } + + public IEnumerable<string> Aliases + { + get + { + var aliases = new HashSet<string>(m_certificateEntries.Keys); + aliases.UnionWith(m_keyEntries.Keys); + // FIXME + //return CollectionUtilities.Proxy(aliases); + return aliases; + } + } + + public bool ContainsAlias(string alias) + { + return IsCertificateEntry(alias) || IsKeyEntry(alias); + } + + public int Count + { + get { return m_certificateEntries.Count + m_keyEntries.Count; } + } + + public bool IsKeyEntry(string alias) + { + return m_keyEntries.ContainsKey(alias); + } + + public bool IsCertificateEntry(string alias) + { + return m_certificateEntries.ContainsKey(alias); + } + + public string GetCertificateAlias(X509Certificate cert) + { + foreach (var entry in m_certificateEntries) + { + if (entry.Value.cert.Equals(cert)) + return entry.Key; + } + return null; + } + + /// <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)); + + IDigest checksumDigest = CreateChecksumDigest(password); + BinaryWriter bw = new BinaryWriter(new DigestStream(stream, null, checksumDigest)); + + WriteInt32(bw, Magic); + WriteInt32(bw, 2); + + WriteInt32(bw, Count); + + foreach (var entry in m_keyEntries) + { + string alias = entry.Key; + JksKeyEntry keyEntry = entry.Value; + + WriteInt32(bw, 1); + WriteUtf(bw, alias); + WriteDateTime(bw, keyEntry.date); + WriteBufferWithLength(bw, keyEntry.keyData.GetEncoded()); + + X509Certificate[] chain = keyEntry.chain; + int chainLength = chain == null ? 0 : chain.Length; + WriteInt32(bw, chainLength); + for (int i = 0; i < chainLength; ++i) + { + WriteTypedCertificate(bw, chain[i]); + } + } + + foreach (var entry in m_certificateEntries) + { + string alias = entry.Key; + JksTrustedCertEntry certEntry = entry.Value; + + WriteInt32(bw, 2); + WriteUtf(bw, alias); + WriteDateTime(bw, certEntry.date); + WriteTypedCertificate(bw, certEntry.cert); + } + + byte[] checksum = DigestUtilities.DoFinal(checksumDigest); + bw.Write(checksum); + bw.Flush(); + } + + /// <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)) + { + BinaryReader dIn = new BinaryReader(storeStream); + + int magic = ReadInt32(dIn); + int storeVersion = ReadInt32(dIn); + + if (!(magic == Magic && (storeVersion == 1 || storeVersion == 2))) + throw new IOException("Invalid keystore format"); + + int numEntries = ReadInt32(dIn); + + for (int t = 0; t < numEntries; t++) + { + int tag = ReadInt32(dIn); + + switch (tag) + { + case 1: // keys + { + string alias = ReadUtf(dIn); + DateTime date = ReadDateTime(dIn); + + // encrypted key data + byte[] keyData = ReadBufferWithLength(dIn); + + // certificate chain + int chainLength = ReadInt32(dIn); + X509Certificate[] chain = null; + if (chainLength > 0) + { + var certs = new List<X509Certificate>(System.Math.Min(10, chainLength)); + for (int certNo = 0; certNo != chainLength; certNo++) + { + certs.Add(ReadTypedCertificate(dIn, storeVersion)); + } + chain = certs.ToArray(); + } + m_keyEntries.Add(alias, new JksKeyEntry(date, keyData, chain)); + break; + } + case 2: // certificate + { + string alias = ReadUtf(dIn); + DateTime date = ReadDateTime(dIn); + + X509Certificate cert = ReadTypedCertificate(dIn, storeVersion); + + 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"); + } + } + + /* + * Validate password takes the checksum of the store and will either. + * 1. If password is null, load the store into memory, return the result. + * 2. If password is not null, load the store into memory, test the checksum, and if successful return + * a new input stream instance of the store. + * 3. Fail if there is a password and an invalid checksum. + * + * @param inputStream The input stream. + * @param password the password. + * @return Either the passed in input stream or a new input stream. + */ + /// <exception cref="IOException"/> + private ErasableByteStream ValidateStream(Stream inputStream, char[] password) + { + byte[] rawStore = Streams.ReadAll(inputStream); + int checksumPos = rawStore.Length - 20; + + if (password != null) + { + byte[] checksum = CalculateChecksum(password, rawStore, 0, checksumPos); + + if (!Arrays.ConstantTimeAreEqual(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); + } + + private static void AddPassword(IDigest digest, char[] password) + { + // Encoding.BigEndianUnicode + for (int i = 0; i < password.Length; ++i) + { + digest.Update((byte)(password[i] >> 8)); + digest.Update((byte)password[i]); + } + } + + 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); + } + + private static X509Certificate[] CloneChain(X509Certificate[] chain) + { + return (X509Certificate[])chain?.Clone(); + } + + private static string ConvertAlias(string alias) + { + return alias.ToLowerInvariant(); + } + + private static IDigest CreateChecksumDigest(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, 0, prefix.Length); + return digest; + } + + private static byte[] ReadBufferWithLength(BinaryReader br) + { + int length = ReadInt32(br); + return br.ReadBytes(length); + } + + private static DateTime ReadDateTime(BinaryReader br) + { + DateTime unixMs = DateTimeUtilities.UnixMsToDateTime(Longs.ReverseBytes(br.ReadInt64())); + DateTime utc = new DateTime(unixMs.Ticks, DateTimeKind.Utc); + return utc; + } + + private static short ReadInt16(BinaryReader br) + { + short n = br.ReadInt16(); + n = (short)(((n & 0xFF) << 8) | ((n >> 8) & 0xFF)); + return n; + } + + private static int ReadInt32(BinaryReader br) + { + return Integers.ReverseBytes(br.ReadInt32()); + } + + private static X509Certificate ReadTypedCertificate(BinaryReader br, int storeVersion) + { + if (storeVersion == 2) + { + string certFormat = ReadUtf(br); + if ("X.509" != certFormat) + throw new IOException("Unsupported certificate format: " + certFormat); + } + + byte[] certData = ReadBufferWithLength(br); + try + { + return new X509Certificate(certData); + } + finally + { + Array.Clear(certData, 0, certData.Length); + } + } + + private static string ReadUtf(BinaryReader br) + { + short length = ReadInt16(br); + byte[] utfBytes = br.ReadBytes(length); + + /* + * FIXME JKS actually uses a "modified UTF-8" format. For the moment we will just support single-byte + * encodings that aren't null bytes. + */ + for (int i = 0; i < utfBytes.Length; ++i) + { + byte utfByte = utfBytes[i]; + if (utfByte == 0 || (utfByte & 0x80) != 0) + throw new NotSupportedException("Currently missing support for modified UTF-8 encoding in JKS"); + } + + return Encoding.UTF8.GetString(utfBytes); + } + + private static void WriteBufferWithLength(BinaryWriter bw, byte[] buffer) + { + WriteInt32(bw, buffer.Length); + bw.Write(buffer); + } + + private static void WriteDateTime(BinaryWriter bw, DateTime dateTime) + { + bw.Write(Longs.ReverseBytes(DateTimeUtilities.DateTimeToUnixMs(dateTime.ToUniversalTime()))); + } + + private static void WriteInt16(BinaryWriter bw, short n) + { + n = (short)(((n & 0xFF) << 8) | ((n >> 8) & 0xFF)); + bw.Write(n); + } + + private static void WriteInt32(BinaryWriter bw, int n) + { + bw.Write(Integers.ReverseBytes(n)); + } + + private static void WriteTypedCertificate(BinaryWriter bw, X509Certificate cert) + { + WriteUtf(bw, "X.509"); + WriteBufferWithLength(bw, cert.GetEncoded()); + } + + private static void WriteUtf(BinaryWriter bw, string s) + { + byte[] utfBytes = Encoding.UTF8.GetBytes(s); + + /* + * FIXME JKS actually uses a "modified UTF-8" format. For the moment we will just support single-byte + * encodings that aren't null bytes. + */ + for (int i = 0; i < utfBytes.Length; ++i) + { + byte utfByte = utfBytes[i]; + if (utfByte == 0 || (utfByte & 0x80) != 0) + throw new NotSupportedException("Currently missing support for modified UTF-8 encoding in JKS"); + } + + WriteInt16(bw, Convert.ToInt16(utfBytes.Length)); + bw.Write(utfBytes); + } + + /** + * JksTrustedCertEntry is a internal container for the certificate entry. + */ + private sealed class JksTrustedCertEntry + { + internal readonly DateTime date; + internal readonly X509Certificate cert; + + internal JksTrustedCertEntry(DateTime date, X509Certificate cert) + { + this.date = date; + this.cert = cert; + } + } + + private sealed class JksKeyEntry + { + internal readonly DateTime date; + internal readonly EncryptedPrivateKeyInfo keyData; + internal readonly X509Certificate[] chain; + + internal JksKeyEntry(DateTime date, byte[] keyData, X509Certificate[] chain) + { + this.date = date; + this.keyData = EncryptedPrivateKeyInfo.GetInstance(Asn1Sequence.GetInstance(keyData)); + this.chain = chain; + } + } + + private sealed class ErasableByteStream + : MemoryStream + { + internal ErasableByteStream(byte[] buffer, int index, int count) + : base(buffer, index, count, false, true) + { + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Position = 0L; + + byte[] rawStore = GetBuffer(); + Array.Clear(rawStore, 0, rawStore.Length); + } + base.Dispose(disposing); + } + } + } +} diff --git a/crypto/test/src/security/test/JksStoreTest.cs b/crypto/test/src/security/test/JksStoreTest.cs new file mode 100644 index 000000000..335786f5e --- /dev/null +++ b/crypto/test/src/security/test/JksStoreTest.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Security.Tests +{ + [TestFixture] + public class JksStoreTest + { + private static readonly byte[] Test1 = Base64.Decode( + "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"); + + private static readonly byte[] Test2 = Base64.Decode( + "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" + + "4fNHHstpt64C"); + + [Test] + public void TestJks() + { + JksStore ks = new JksStore(); + + ks.Load(new MemoryStream(Test1, false), "fredfred".ToCharArray()); + + ks.GetKey("rsa", "samsam".ToCharArray()); + + IAsymmetricCipherKeyPairGenerator kpGen = new RsaKeyPairGenerator(); + kpGen.Init(new RsaKeyGenerationParameters(new BigInteger("10001", 16), new SecureRandom(), 1024, 100)); + + AsymmetricCipherKeyPair kp = kpGen.GenerateKeyPair(); + + ks.SetKeyEntry("fred", kp.Private, "bobbob".ToCharArray(), ks.GetCertificateChain("rsa")); + + ks.GetKey("fred", "bobbob".ToCharArray()); + + MemoryStream bOut = new MemoryStream(); + + ks.Save(bOut, "billbill".ToCharArray()); + + ks = new JksStore(); + + ks.Load(new MemoryStream(Test2, false), "samsam".ToCharArray()); + + AsymmetricKeyParameter privKey = ks.GetKey("rsa", "samsam".ToCharArray()); + + ks = new JksStore(); + + try + { + ks.Load(new MemoryStream(bOut.ToArray()), "wrong".ToCharArray()); + Assert.Fail("Exception expected for Load() with wrong password"); + } + catch (Exception) + { + // Expected + } + + ks.Load(new MemoryStream(bOut.ToArray()), "billbill".ToCharArray()); + + privKey = ks.GetKey("rsa", "samsam".ToCharArray()); + + privKey = ks.GetKey("fred", "bobbob".ToCharArray()); + + Assert.IsNull(ks.GetCertificate("george")); + Assert.IsNull(ks.GetCertificateChain("george")); + Assert.IsNull(ks.GetKey("george", "ignored".ToCharArray())); + + try + { + privKey = ks.GetKey("fred", "wrong".ToCharArray()); + Assert.Fail("Exception expected for GetKey() with wrong password"); + } + catch (Exception) + { + // Expected + } + } + } +} |