From 6441531292e92a5597d701bd28de7ec3f9e612ab Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 16 Dec 2020 10:11:22 +0100 Subject: [PATCH] Migrate to new S Expression code from https://github.com/bcgit/bc-csharp/pull/282 --- .../BouncyCastle/Bcpg/OpenPgp/PgpSecretKey.cs | 239 +++++++++------ .../BouncyCastle/Bcpg/OpenPgp/SXprReader.cs | 276 ++++++++++++++++++ .../Bcpg/OpenPgp/SXprUtilities.cs | 102 ------- .../BouncyCastle/Bcpg/OpenPgp/SXprWriter.cs | 50 ++++ 4 files changed, 473 insertions(+), 194 deletions(-) create mode 100644 src/Org/BouncyCastle/Bcpg/OpenPgp/SXprReader.cs delete mode 100644 src/Org/BouncyCastle/Bcpg/OpenPgp/SXprUtilities.cs create mode 100644 src/Org/BouncyCastle/Bcpg/OpenPgp/SXprWriter.cs diff --git a/src/Org/BouncyCastle/Bcpg/OpenPgp/PgpSecretKey.cs b/src/Org/BouncyCastle/Bcpg/OpenPgp/PgpSecretKey.cs index d43c363..915d95b 100644 --- a/src/Org/BouncyCastle/Bcpg/OpenPgp/PgpSecretKey.cs +++ b/src/Org/BouncyCastle/Bcpg/OpenPgp/PgpSecretKey.cs @@ -1113,58 +1113,6 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey); } - internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) - { - SXprUtilities.SkipOpenParenthesis(inputStream); - - string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - if (type.Equals("protected-private-key")) - { - SXprUtilities.SkipOpenParenthesis(inputStream); - - string curveName; - - string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - if (keyType.Equals("ecc")) - { - SXprUtilities.SkipOpenParenthesis(inputStream); - - string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - - SXprUtilities.SkipCloseParenthesis(inputStream); - } - else - { - throw new PgpException("no curve details found"); - } - - byte[] qVal; - - SXprUtilities.SkipOpenParenthesis(inputStream); - - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - if (type.Equals("q")) - { - qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); - } - else - { - throw new PgpException("no q value found"); - } - - SXprUtilities.SkipCloseParenthesis(inputStream); - - byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); - // TODO: check SHA-1 hash. - - return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, - new ECSecretBcpgKey(new MPInteger(dValue)).GetEncoded()), pubKey); - } - - throw new PgpException("unknown key type found"); - } - /// /// Parse a secret key from one of the GPG S expression keys. /// @@ -1174,7 +1122,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ /// public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true); + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, null); } /// @@ -1185,7 +1133,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa /// public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true); + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, null); } /// @@ -1196,31 +1144,33 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[ /// public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase) { - return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false); + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, null); } /// /// Parse a secret key from one of the GPG S expression keys. /// - internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase) + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) { - SXprUtilities.SkipOpenParenthesis(inputStream); + SXprReader reader = new SXprReader(inputStream); - string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + reader.SkipOpenParenthesis(); + + string type = reader.ReadString(); if (type.Equals("protected-private-key")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); string curveName; Oid curveOid; - string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string keyType = reader.ReadString(); if (keyType.Equals("ecc")) { - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); - curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + string curveID = reader.ReadString(); + curveName = reader.ReadString(); switch (curveName) { @@ -1230,12 +1180,13 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ case "brainpoolP256r1": curveOid = new Oid("1.3.36.3.3.2.8.1.1.7"); break; case "brainpoolP384r1": curveOid = new Oid("1.3.36.3.3.2.8.1.1.11"); break; case "brainpoolP512r1": curveOid = new Oid("1.3.36.3.3.2.8.1.1.13"); break; - // FIXME: curve25519 + case "Curve25519": curveOid = new Oid("1.3.6.1.4.1.3029.1.5.1"); break; + case "Ed25519": curveOid = new Oid("1.3.6.1.4.1.11591.15.1"); break; default: throw new PgpException("unknown curve algorithm"); } - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); } else { @@ -1243,79 +1194,183 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[ } byte[] qVal; + string flags = null; - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + type = reader.ReadString(); + if (type == "flags") + { + // Skip over flags + flags = reader.ReadString(); + reader.SkipCloseParenthesis(); + reader.SkipOpenParenthesis(); + type = reader.ReadString(); + } if (type.Equals("q")) { - qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + qVal = reader.ReadBytes(); } else { throw new PgpException("no q value found"); } - PublicKeyPacket pubPacket = new PublicKeyPacket(PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, - new ECDsaPublicBcpgKey(curveOid, new MPInteger(qVal))); + if (pubKey == null) + { + PublicKeyPacket pubPacket = new PublicKeyPacket( + flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow, + new ECDsaPublicBcpgKey(curveOid, new MPInteger(qVal))); + pubKey = new PgpPublicKey(pubPacket); + } - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipCloseParenthesis(); - byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); - // TODO: check SHA-1 hash. + byte[] dValue = GetDValue(reader, pubKey.PublicKeyPacket, rawPassPhrase, clearPassPhrase, curveName); - return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null, - new ECSecretBcpgKey(new MPInteger(dValue)).GetEncoded()), new PgpPublicKey(pubPacket)); + return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, + new ECSecretBcpgKey(new MPInteger(dValue)).GetEncoded()), pubKey); } throw new PgpException("unknown key type found"); } - private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) + private static void WriteSExprPublicKey(SXprWriter writer, PublicKeyPacket pubPacket, string curveName, string protectedAt) + { + writer.StartList(); + switch (pubPacket.Algorithm) + { + case PublicKeyAlgorithmTag.ECDsa: + case PublicKeyAlgorithmTag.EdDsa: + writer.WriteString("ecc"); + writer.StartList(); + writer.WriteString("curve"); + writer.WriteString(curveName); + writer.EndList(); + if (pubPacket.Algorithm == PublicKeyAlgorithmTag.EdDsa) + { + writer.StartList(); + writer.WriteString("flags"); + writer.WriteString("eddsa"); + writer.EndList(); + } + writer.StartList(); + writer.WriteString("q"); + writer.WriteBytes(((ECDsaPublicBcpgKey)pubPacket.Key).EncodedPoint.Value); + writer.EndList(); + break; + + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaSign: + case PublicKeyAlgorithmTag.RsaGeneral: + RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)pubPacket.Key; + writer.WriteString("rsa"); + writer.StartList(); + writer.WriteString("n"); + writer.WriteBytes(rsaK.Modulus.Value); + writer.EndList(); + writer.StartList(); + writer.WriteString("e"); + writer.WriteBytes(rsaK.PublicExponent.Value); + writer.EndList(); + break; + + // TODO: DSA, etc. + default: + throw new PgpException("unsupported algorithm in S expression"); + } + + if (protectedAt != null) + { + writer.StartList(); + writer.WriteString("protected-at"); + writer.WriteString(protectedAt); + writer.EndList(); + } + writer.EndList(); + } + + private static byte[] GetDValue(SXprReader reader, PublicKeyPacket publicKey, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) { string type; - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipOpenParenthesis(); string protection; + string protectedAt = null; S2k s2k; byte[] iv; byte[] secKeyData; - type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + type = reader.ReadString(); if (type.Equals("protected")) { - protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte()); + protection = reader.ReadString(); + + reader.SkipOpenParenthesis(); + + s2k = reader.ParseS2k(); + + iv = reader.ReadBytes(); - SXprUtilities.SkipOpenParenthesis(inputStream); + reader.SkipCloseParenthesis(); - s2k = SXprUtilities.ParseS2k(inputStream); + secKeyData = reader.ReadBytes(); - iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + reader.SkipCloseParenthesis(); - SXprUtilities.SkipCloseParenthesis(inputStream); + reader.SkipOpenParenthesis(); - secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte()); + if (reader.ReadString().Equals("protected-at")) + { + protectedAt = reader.ReadString(); + } } else { throw new PgpException("protected block not found"); } - // TODO: recognise other algorithms - byte[] key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + byte[] data; + byte[] key; + + switch (protection) + { + case "openpgp-s2k3-sha1-aes256-cbc": + case "openpgp-s2k3-sha1-aes-cbc": + SymmetricKeyAlgorithmTag symmAlg = + protection.Equals("openpgp-s2k3-sha1-aes256-cbc") ? SymmetricKeyAlgorithmTag.Aes256 : SymmetricKeyAlgorithmTag.Aes128; + key = PgpUtilities.DoMakeKeyFromPassPhrase(symmAlg, s2k, rawPassPhrase, clearPassPhrase); + data = RecoverKeyData(symmAlg, CipherMode.CBC, key, iv, secKeyData, 0, secKeyData.Length); + // TODO: check SHA-1 hash. + break; - byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, CipherMode.CBC, key, iv, secKeyData, 0, secKeyData.Length); + case "openpgp-s2k3-ocb-aes": + MemoryStream aad = new MemoryStream(); + WriteSExprPublicKey(new SXprWriter(aad), publicKey, curveName, protectedAt); + key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); + /*IBufferedCipher c = CipherUtilities.GetCipher("AES/OCB"); + c.Init(false, new AeadParameters(key, 128, iv, aad.ToArray())); + data = c.DoFinal(secKeyData, 0, secKeyData.Length);*/ + // TODO: AES/OCB support + throw new NotImplementedException(); + break; + + case "openpgp-native": + default: + throw new PgpException(protection + " key format is not supported yet"); + } // // parse the secret key S-expr // Stream keyIn = new MemoryStream(data, false); - SXprUtilities.SkipOpenParenthesis(keyIn); - SXprUtilities.SkipOpenParenthesis(keyIn); - SXprUtilities.SkipOpenParenthesis(keyIn); - String name = SXprUtilities.ReadString(keyIn, keyIn.ReadByte()); - return SXprUtilities.ReadBytes(keyIn, keyIn.ReadByte()); + reader = new SXprReader(keyIn); + reader.SkipOpenParenthesis(); + reader.SkipOpenParenthesis(); + reader.SkipOpenParenthesis(); + String name = reader.ReadString(); + return reader.ReadBytes(); } } } diff --git a/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprReader.cs b/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprReader.cs new file mode 100644 index 0000000..6faf14c --- /dev/null +++ b/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprReader.cs @@ -0,0 +1,276 @@ +using System; +using System.IO; +using System.Text; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// + /// Reader for S-expression keys. This class will move when it finds a better home! + /// + /// + /// Format documented here: + /// http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master + /// http://people.csail.mit.edu/rivest/Sexp.txt + /// + class SXprReader + { + Stream stream; + int peekedByte; + + public SXprReader(Stream stream) + { + this.stream = stream; + this.peekedByte = -1; + } + + private int ReadByte() + { + if (this.peekedByte > 0) + { + int pb = this.peekedByte; + this.peekedByte = 0; + return pb; + } + return stream.ReadByte(); + } + + private void UnreadByte(int pb) + { + this.peekedByte = pb; + } + + private int ReadLength() + { + int ch; + int len = 0; + + while ((ch = ReadByte()) >= 0 && ch >= '0' && ch <= '9') + { + len = len * 10 + (ch - '0'); + } + UnreadByte(ch); + + return len; + } + + public string ReadString() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + ch = ReadByte(); + if (ch == ':') + { + char[] chars = new char[len]; + + for (int i = 0; i != chars.Length; i++) + { + chars[i] = (char)ReadByte(); + } + + return new string(chars); + } + else if (ch == '"') + { + return ReadQuotedString(len); + } + throw new IOException("unsupported encoding"); + } + else if (ch == '"') + { + return ReadQuotedString(0); + } + else if (ch == '{' || ch == '|' || ch == '#') + { + // TODO: Unsupported encoding + throw new IOException("unsupported encoding"); + } + else + { + StringBuilder sb = new StringBuilder(); + while (IsTokenChar(ch)) + { + sb.Append((char)ch); + ch = (char)ReadByte(); + } + UnreadByte(ch); + return sb.ToString(); + } + } + + private string ReadQuotedString(int length) + { + StringBuilder sb = new StringBuilder(length); + int ch; + bool skipNewLine = false; + do + { + ch = ReadByte(); + if ((ch == '\n' || ch == '\r') && skipNewLine) + { + skipNewLine = false; + } + else if (ch == '\\') + { + ch = (char)ReadByte(); + switch (ch) + { + case 'b': sb.Append('\b'); break; + case 't': sb.Append('\t'); break; + case 'v': sb.Append('\v'); break; + case 'n': sb.Append('\n'); break; + case 'r': sb.Append('\r'); break; + case 'f': sb.Append('\f'); break; + case '"': sb.Append('"'); break; + case '\'': sb.Append('\''); break; + case '\r': + case '\n': + skipNewLine = true; + break; + default: + // TODO: Octal value, hexadecimal value + throw new IOException("unsupported encoding"); + } + } + else if (ch != '"' && ch >= 0) + { + skipNewLine = false; + sb.Append((char)ch); + } + } + while (ch != '"' && ch > 0); + return sb.ToString(); + } + + private static bool IsTokenChar(int ch) + { + return + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || + ch == '/' || ch == '_' || + ch == ':' || ch == '*' || + ch == '+' || ch == '='; + } + + public byte[] ReadBytes() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + + if (ReadByte() != ':') + throw new IOException("unsupported encoding"); + + byte[] data = new byte[len]; + + Streams.ReadFully(stream, data); + + return data; + } + else if (ch == '#') + { + MemoryStream bytes = new MemoryStream(); + do + { + ch = ReadByte(); + if (ch == '#') + break; + int digit0 = HexToNumber(ch); + if (digit0 < 0) + throw new IOException("invalid hex encoding"); + ch = ReadByte(); + int digit1 = HexToNumber(ch); + if (digit1 < 0) + throw new IOException("invalid hex encoding"); + bytes.WriteByte((byte)((digit0 << 8) + digit1)); + } + while (ch != '#' && ch >= 0); + return bytes.ToArray(); + } + + throw new IOException("unsupported encoding"); + } + + private static int HexToNumber(int c) + { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 0xa; + if (c >= 'A' && c <= 'F') + return c - 'A' + 0xa; + return -1; + } + + public S2k ParseS2k() + { + SkipOpenParenthesis(); + + string alg = ReadString(); + byte[] iv = ReadBytes(); + long iterationCount = Int64.Parse(ReadString()); + + SkipCloseParenthesis(); + + // we have to return the actual iteration count provided. + return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); + } + + public void SkipWhitespace() + { + int ch = ReadByte(); + while (ch == ' ' || ch == '\r' || ch == '\n') + { + ch = ReadByte(); + } + UnreadByte(ch); + } + + public void SkipOpenParenthesis() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch != '(') + throw new IOException("unknown character encountered"); + } + + public void SkipCloseParenthesis() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch != ')') + throw new IOException("unknown character encountered"); + } + + private class MyS2k : S2k + { + private readonly long mIterationCount64; + + internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64) + : base(algorithm, iv, (int)iterationCount64) + { + this.mIterationCount64 = iterationCount64; + } + + public override long IterationCount + { + get { return mIterationCount64; } + } + } + } +} diff --git a/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprUtilities.cs b/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprUtilities.cs deleted file mode 100644 index 68ff373..0000000 --- a/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprUtilities.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.IO; - -using Org.BouncyCastle.Utilities.IO; - -namespace Org.BouncyCastle.Bcpg.OpenPgp -{ - /** - * Utility functions for looking a S-expression keys. This class will move when it finds a better home! - *

- * Format documented here: - * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master - *

- */ - public sealed class SXprUtilities - { - private SXprUtilities() - { - } - - private static int ReadLength(Stream input, int ch) - { - int len = ch - '0'; - - while ((ch = input.ReadByte()) >= 0 && ch != ':') - { - len = len * 10 + ch - '0'; - } - - return len; - } - - internal static string ReadString(Stream input, int ch) - { - int len = ReadLength(input, ch); - - char[] chars = new char[len]; - - for (int i = 0; i != chars.Length; i++) - { - chars[i] = (char)input.ReadByte(); - } - - return new string(chars); - } - - internal static byte[] ReadBytes(Stream input, int ch) - { - int len = ReadLength(input, ch); - - byte[] data = new byte[len]; - - Streams.ReadFully(input, data); - - return data; - } - - internal static S2k ParseS2k(Stream input) - { - SkipOpenParenthesis(input); - - string alg = ReadString(input, input.ReadByte()); - byte[] iv = ReadBytes(input, input.ReadByte()); - long iterationCount = Int64.Parse(ReadString(input, input.ReadByte())); - - SkipCloseParenthesis(input); - - // we have to return the actual iteration count provided. - return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); - } - - internal static void SkipOpenParenthesis(Stream input) - { - int ch = input.ReadByte(); - if (ch != '(') - throw new IOException("unknown character encountered"); - } - - internal static void SkipCloseParenthesis(Stream input) - { - int ch = input.ReadByte(); - if (ch != ')') - throw new IOException("unknown character encountered"); - } - - private class MyS2k : S2k - { - private readonly long mIterationCount64; - - internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64) - : base(algorithm, iv, (int)iterationCount64) - { - this.mIterationCount64 = iterationCount64; - } - - public override long IterationCount - { - get { return mIterationCount64; } - } - } - } -} diff --git a/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprWriter.cs b/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprWriter.cs new file mode 100644 index 0000000..640c4b6 --- /dev/null +++ b/src/Org/BouncyCastle/Bcpg/OpenPgp/SXprWriter.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Text; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /** + * Writer for S-expression keys + *

+ * Format documented here: + * http://people.csail.mit.edu/rivest/Sexp.txt + * + * Only canonical S expression format is used. + *

+ */ + class SXprWriter + { + Stream output; + + public SXprWriter(Stream output) + { + this.output = output; + } + + public void StartList() + { + output.WriteByte((byte)'('); + } + + public void EndList() + { + output.WriteByte((byte)')'); + } + + public void WriteString(string s) + { + byte[] stringBytes = Encoding.UTF8.GetBytes(s); + byte[] lengthBytes = Encoding.UTF8.GetBytes(stringBytes.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(stringBytes, 0, stringBytes.Length); + } + + public void WriteBytes(byte[] b) + { + byte[] lengthBytes = Encoding.UTF8.GetBytes(b.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(b, 0, b.Length); + } + } +}