Skip to content

Commit 4de5dfb

Browse files
committed
Rename SXprUtilities to SXprReader
Add SXprWriter to help writing canonical S expression Fix decoding AES-OCB encrypted S expression keys
1 parent 1ddeba6 commit 4de5dfb

File tree

4 files changed

+151
-78
lines changed

4 files changed

+151
-78
lines changed

crypto/src/openpgp/PgpSecretKey.cs

Lines changed: 95 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Diagnostics;
44
using System.IO;
5+
using crypto.openpgp;
56
using Org.BouncyCastle.Asn1;
67
using Org.BouncyCastle.Asn1.Misc;
78
using Org.BouncyCastle.Asn1.X9;
@@ -1116,60 +1117,6 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[]
11161117
return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey);
11171118
}
11181119

1119-
internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
1120-
{
1121-
SXprUtilities reader = new SXprUtilities(inputStream);
1122-
1123-
reader.SkipOpenParenthesis();
1124-
1125-
string type = reader.ReadString();
1126-
if (type.Equals("protected-private-key"))
1127-
{
1128-
reader.SkipOpenParenthesis();
1129-
1130-
string curveName;
1131-
1132-
string keyType = reader.ReadString();
1133-
if (keyType.Equals("ecc"))
1134-
{
1135-
reader.SkipOpenParenthesis();
1136-
1137-
string curveID = reader.ReadString();
1138-
curveName = reader.ReadString();
1139-
1140-
reader.SkipCloseParenthesis();
1141-
}
1142-
else
1143-
{
1144-
throw new PgpException("no curve details found");
1145-
}
1146-
1147-
byte[] qVal;
1148-
1149-
reader.SkipOpenParenthesis();
1150-
1151-
type = reader.ReadString();
1152-
if (type.Equals("q"))
1153-
{
1154-
qVal = reader.ReadBytes();
1155-
}
1156-
else
1157-
{
1158-
throw new PgpException("no q value found");
1159-
}
1160-
1161-
reader.SkipCloseParenthesis();
1162-
1163-
byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
1164-
// TODO: check SHA-1 hash.
1165-
1166-
return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
1167-
new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey);
1168-
}
1169-
1170-
throw new PgpException("unknown key type found");
1171-
}
1172-
11731120
/// <summary>
11741121
/// Parse a secret key from one of the GPG S expression keys.
11751122
/// </summary>
@@ -1179,7 +1126,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
11791126
/// </remarks>
11801127
public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
11811128
{
1182-
return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, false), true);
1129+
return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, null);
11831130
}
11841131

11851132
/// <summary>
@@ -1190,7 +1137,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa
11901137
/// </remarks>
11911138
public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase)
11921139
{
1193-
return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, true), true);
1140+
return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, null);
11941141
}
11951142

11961143
/// <summary>
@@ -1201,14 +1148,16 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[
12011148
/// </remarks>
12021149
public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase)
12031150
{
1204-
return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), rawPassPhrase, false);
1151+
return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, null);
12051152
}
12061153

12071154
/// <summary>
12081155
/// Parse a secret key from one of the GPG S expression keys.
12091156
/// </summary>
1210-
internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase)
1157+
internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
12111158
{
1159+
SXprReader reader = new SXprReader(inputStream);
1160+
12121161
reader.SkipOpenParenthesis();
12131162

12141163
string type = reader.ReadString();
@@ -1272,28 +1221,87 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byt
12721221
throw new PgpException("no q value found");
12731222
}
12741223

1275-
PublicKeyPacket pubPacket = new PublicKeyPacket(
1276-
flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
1277-
new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal)));
1224+
if (pubKey == null)
1225+
{
1226+
PublicKeyPacket pubPacket = new PublicKeyPacket(
1227+
flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
1228+
new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal)));
1229+
pubKey = new PgpPublicKey(pubPacket);
1230+
}
12781231

12791232
reader.SkipCloseParenthesis();
12801233

1281-
byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
1282-
// TODO: check SHA-1 hash.
1234+
byte[] dValue = GetDValue(reader, pubKey.PublicKeyPacket, rawPassPhrase, clearPassPhrase, curveName);
12831235

1284-
return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
1285-
new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket));
1236+
return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
1237+
new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey);
12861238
}
12871239

12881240
throw new PgpException("unknown key type found");
12891241
}
12901242

1291-
private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
1243+
private static void WriteSExprPublicKey(SXprWriter writer, PublicKeyPacket pubPacket, string curveName, string protectedAt)
1244+
{
1245+
writer.StartList();
1246+
switch (pubPacket.Algorithm)
1247+
{
1248+
case PublicKeyAlgorithmTag.ECDsa:
1249+
case PublicKeyAlgorithmTag.EdDsa:
1250+
writer.WriteString("ecc");
1251+
writer.StartList();
1252+
writer.WriteString("curve");
1253+
writer.WriteString(curveName);
1254+
writer.EndList();
1255+
if (pubPacket.Algorithm == PublicKeyAlgorithmTag.EdDsa)
1256+
{
1257+
writer.StartList();
1258+
writer.WriteString("flags");
1259+
writer.WriteString("eddsa");
1260+
writer.EndList();
1261+
}
1262+
writer.StartList();
1263+
writer.WriteString("q");
1264+
writer.WriteBytes(((ECDsaPublicBcpgKey)pubPacket.Key).EncodedPoint.ToByteArrayUnsigned());
1265+
writer.EndList();
1266+
break;
1267+
1268+
case PublicKeyAlgorithmTag.RsaEncrypt:
1269+
case PublicKeyAlgorithmTag.RsaSign:
1270+
case PublicKeyAlgorithmTag.RsaGeneral:
1271+
RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)pubPacket.Key;
1272+
writer.WriteString("rsa");
1273+
writer.StartList();
1274+
writer.WriteString("n");
1275+
writer.WriteBytes(rsaK.Modulus.ToByteArrayUnsigned());
1276+
writer.EndList();
1277+
writer.StartList();
1278+
writer.WriteString("e");
1279+
writer.WriteBytes(rsaK.PublicExponent.ToByteArrayUnsigned());
1280+
writer.EndList();
1281+
break;
1282+
1283+
// TODO: DSA, etc.
1284+
default:
1285+
throw new PgpException("unsupported algorithm in S expression");
1286+
}
1287+
1288+
if (protectedAt != null)
1289+
{
1290+
writer.StartList();
1291+
writer.WriteString("protected-at");
1292+
writer.WriteString(protectedAt);
1293+
writer.EndList();
1294+
}
1295+
writer.EndList();
1296+
}
1297+
1298+
private static byte[] GetDValue(SXprReader reader, PublicKeyPacket publicKey, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
12921299
{
12931300
string type;
12941301
reader.SkipOpenParenthesis();
12951302

12961303
string protection;
1304+
string protectedAt = null;
12971305
S2k s2k;
12981306
byte[] iv;
12991307
byte[] secKeyData;
@@ -1312,26 +1320,42 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool
13121320
reader.SkipCloseParenthesis();
13131321

13141322
secKeyData = reader.ReadBytes();
1323+
1324+
reader.SkipCloseParenthesis();
1325+
1326+
reader.SkipOpenParenthesis();
1327+
1328+
if (reader.ReadString().Equals("protected-at"))
1329+
{
1330+
protectedAt = reader.ReadString();
1331+
}
13151332
}
13161333
else
13171334
{
13181335
throw new PgpException("protected block not found");
13191336
}
13201337

1321-
// Valid values of protection: openpgp-s2k3-sha1-aes-cbc, openpgp-s2k3-ocb-aes, openpgp-native
13221338
byte[] data;
13231339
KeyParameter key;
13241340

13251341
switch (protection)
13261342
{
1343+
case "openpgp-s2k3-sha1-aes256-cbc":
13271344
case "openpgp-s2k3-sha1-aes-cbc":
1328-
key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
1329-
data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
1345+
SymmetricKeyAlgorithmTag symmAlg =
1346+
protection.Equals("openpgp-s2k3-sha1-aes256-cbc") ? SymmetricKeyAlgorithmTag.Aes256 : SymmetricKeyAlgorithmTag.Aes128;
1347+
key = PgpUtilities.DoMakeKeyFromPassPhrase(symmAlg, s2k, rawPassPhrase, clearPassPhrase);
1348+
data = RecoverKeyData(symmAlg, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
1349+
// TODO: check SHA-1 hash.
13301350
break;
13311351

13321352
case "openpgp-s2k3-ocb-aes":
1353+
MemoryStream aad = new MemoryStream();
1354+
WriteSExprPublicKey(new SXprWriter(aad), publicKey, curveName, protectedAt);
13331355
key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
1334-
data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/OCB/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
1356+
IBufferedCipher c = CipherUtilities.GetCipher("AES/OCB");
1357+
c.Init(false, new AeadParameters(key, 128, iv, aad.ToArray()));
1358+
data = c.DoFinal(secKeyData, 0, secKeyData.Length);
13351359
break;
13361360

13371361
case "openpgp-native":
@@ -1344,7 +1368,7 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool
13441368
//
13451369
Stream keyIn = new MemoryStream(data, false);
13461370

1347-
reader = new SXprUtilities(keyIn);
1371+
reader = new SXprReader(keyIn);
13481372
reader.SkipOpenParenthesis();
13491373
reader.SkipOpenParenthesis();
13501374
reader.SkipOpenParenthesis();

crypto/src/openpgp/SXprUtilities.cs renamed to crypto/src/openpgp/SXprReader.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77
namespace Org.BouncyCastle.Bcpg.OpenPgp
88
{
99
/**
10-
* Utility functions for looking a S-expression keys. This class will move when it finds a better home!
10+
* Reader for S-expression keys. This class will move when it finds a better home!
1111
* <p>
1212
* Format documented here:
1313
* http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
1414
* http://people.csail.mit.edu/rivest/Sexp.txt
1515
* </p>
1616
*/
17-
class SXprUtilities
17+
class SXprReader
1818
{
1919
Stream stream;
2020
int peekedByte;
2121

22-
internal SXprUtilities(Stream stream)
22+
public SXprReader(Stream stream)
2323
{
2424
this.stream = stream;
2525
this.peekedByte = -1;
@@ -211,7 +211,7 @@ public S2k ParseS2k()
211211
return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount);
212212
}
213213

214-
private void SkipWhitespace()
214+
public void SkipWhitespace()
215215
{
216216
int ch = ReadByte();
217217
while (ch == ' ' || ch == '\r' || ch == '\n')

crypto/src/openpgp/SXprWriter.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.IO;
3+
using System.Text;
4+
5+
namespace crypto.openpgp
6+
{
7+
/**
8+
* Writer for S-expression keys
9+
* <p>
10+
* Format documented here:
11+
* http://people.csail.mit.edu/rivest/Sexp.txt
12+
*
13+
* Only canonical S expression format is used.
14+
* </p>
15+
*/
16+
class SXprWriter
17+
{
18+
Stream output;
19+
20+
public SXprWriter(Stream output)
21+
{
22+
this.output = output;
23+
}
24+
25+
public void StartList()
26+
{
27+
output.WriteByte((byte)'(');
28+
}
29+
30+
public void EndList()
31+
{
32+
output.WriteByte((byte)')');
33+
}
34+
35+
public void WriteString(string s)
36+
{
37+
byte[] stringBytes = Encoding.UTF8.GetBytes(s);
38+
byte[] lengthBytes = Encoding.UTF8.GetBytes(stringBytes.Length + ":");
39+
output.Write(lengthBytes, 0, lengthBytes.Length);
40+
output.Write(stringBytes, 0, stringBytes.Length);
41+
}
42+
43+
public void WriteBytes(byte[] b)
44+
{
45+
byte[] lengthBytes = Encoding.UTF8.GetBytes(b.Length + ":");
46+
output.Write(lengthBytes, 0, lengthBytes.Length);
47+
output.Write(b, 0, b.Length);
48+
}
49+
}
50+
}

crypto/test/src/openpgp/test/PgpEdDsaTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,7 @@ public override void PerformTest()
184184
//
185185
// sExpr
186186
//
187-
// TODO: Fails the OCB MAC check when decoding key but works otherwise
188-
/*byte[] msg = Encoding.ASCII.GetBytes("hello world!");
187+
byte[] msg = Encoding.ASCII.GetBytes("hello world!");
189188

190189
PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray());
191190

@@ -200,7 +199,7 @@ public override void PerformTest()
200199
if (!sig.Verify())
201200
{
202201
Fail("signature failed to verify!");
203-
}*/
202+
}
204203
}
205204

206205
private static object First(IEnumerable e)

0 commit comments

Comments
 (0)