Skip to content

Commit 0e7c268

Browse files
committed
Merge branch 'anhvo/PortOpenSshUtil'
2 parents d80d39b + daa52b5 commit 0e7c268

File tree

7 files changed

+1289
-0
lines changed

7 files changed

+1289
-0
lines changed

crypto/src/crypto/parameters/ECKeyParameters.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ protected ECKeyParameters(
4040

4141
this.algorithm = VerifyAlgorithmName(algorithm);
4242
this.parameters = parameters;
43+
this.publicKeyParamSet = (parameters as ECNamedDomainParameters)?.Name;
4344
}
4445

4546
protected ECKeyParameters(
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
using System;
2+
using System.Text;
3+
4+
using Org.BouncyCastle.Asn1;
5+
using Org.BouncyCastle.Asn1.Pkcs;
6+
using Org.BouncyCastle.Asn1.Sec;
7+
using Org.BouncyCastle.Asn1.X9;
8+
using Org.BouncyCastle.Crypto.Parameters;
9+
using Org.BouncyCastle.Math;
10+
using Org.BouncyCastle.Pkcs;
11+
using Org.BouncyCastle.Utilities;
12+
13+
namespace Org.BouncyCastle.Crypto.Utilities
14+
{
15+
public static class OpenSshPrivateKeyUtilities
16+
{
17+
/// <summary>Magic value for proprietary OpenSSH private key.</summary>
18+
/// <remarks>C string so null terminated.</remarks>
19+
private static readonly byte[] AUTH_MAGIC = Encoding.ASCII.GetBytes("openssh-key-v1\0");
20+
21+
/**
22+
* Encode a cipher parameters into an OpenSSH private key.
23+
* This does not add headers like ----BEGIN RSA PRIVATE KEY----
24+
*
25+
* @param parameters the cipher parameters.
26+
* @return a byte array
27+
*/
28+
public static byte[] EncodePrivateKey(AsymmetricKeyParameter parameters)
29+
{
30+
if (parameters == null)
31+
throw new ArgumentNullException(nameof(parameters));
32+
33+
if (parameters is RsaPrivateCrtKeyParameters || parameters is ECPrivateKeyParameters)
34+
{
35+
PrivateKeyInfo pInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(parameters);
36+
return pInfo.ParsePrivateKey().GetEncoded();
37+
}
38+
else if (parameters is DsaPrivateKeyParameters dsaPrivateKey)
39+
{
40+
DsaParameters dsaparameters = dsaPrivateKey.Parameters;
41+
42+
Asn1EncodableVector vec = new Asn1EncodableVector
43+
{
44+
new DerInteger(0),
45+
new DerInteger(dsaparameters.P),
46+
new DerInteger(dsaparameters.Q),
47+
new DerInteger(dsaparameters.G)
48+
};
49+
50+
// public key = g.modPow(x, p);
51+
BigInteger pubKey = dsaparameters.P.ModPow(dsaPrivateKey.X, dsaparameters.P);
52+
vec.Add(new DerInteger(pubKey));
53+
vec.Add(new DerInteger(dsaPrivateKey.X));
54+
try
55+
{
56+
return new DerSequence(vec).GetEncoded();
57+
}
58+
catch (Exception ex)
59+
{
60+
throw new InvalidOperationException("unable to encode DSAPrivateKeyParameters " + ex.Message);
61+
}
62+
}
63+
else if (parameters is Ed25519PrivateKeyParameters ed25519PrivateKey)
64+
{
65+
Ed25519PublicKeyParameters publicKeyParameters = ed25519PrivateKey.GeneratePublicKey();
66+
67+
SshBuilder builder = new SshBuilder();
68+
builder.WriteBytes(AUTH_MAGIC);
69+
builder.WriteStringAscii("none"); // cipher name
70+
builder.WriteStringAscii("none"); // KDF name
71+
builder.WriteStringAscii(""); // KDF options
72+
73+
builder.U32(1); // Number of keys
74+
75+
{
76+
byte[] pkEncoded = OpenSshPublicKeyUtilities.EncodePublicKey(publicKeyParameters);
77+
builder.WriteBlock(pkEncoded);
78+
}
79+
80+
{
81+
SshBuilder pkBuild = new SshBuilder();
82+
83+
int checkint = CryptoServicesRegistrar.GetSecureRandom().NextInt();
84+
pkBuild.U32((uint)checkint);
85+
pkBuild.U32((uint)checkint);
86+
87+
pkBuild.WriteStringAscii("ssh-ed25519");
88+
89+
// Public key (as part of private key pair)
90+
byte[] pubKeyEncoded = publicKeyParameters.GetEncoded();
91+
pkBuild.WriteBlock(pubKeyEncoded);
92+
93+
// The private key in SSH is 64 bytes long and is the concatenation of the private and the public keys
94+
pkBuild.WriteBlock(Arrays.Concatenate(ed25519PrivateKey.GetEncoded(), pubKeyEncoded));
95+
96+
// Comment for this private key (empty)
97+
pkBuild.WriteStringUtf8("");
98+
99+
builder.WriteBlock(pkBuild.GetPaddedBytes());
100+
}
101+
102+
return builder.GetBytes();
103+
}
104+
105+
throw new ArgumentException("unable to convert " + Platform.GetTypeName(parameters) + " to openssh private key");
106+
}
107+
108+
/**
109+
* Parse a private key.
110+
* <p/>
111+
* This method accepts the body of the OpenSSH private key.
112+
* The easiest way to extract the body is to use PemReader, for example:
113+
* <p/>
114+
* byte[] blob = new PemReader([reader]).readPemObject().getContent();
115+
* CipherParameters params = parsePrivateKeyBlob(blob);
116+
*
117+
* @param blob The key.
118+
* @return A cipher parameters instance.
119+
*/
120+
public static AsymmetricKeyParameter ParsePrivateKeyBlob(byte[] blob)
121+
{
122+
AsymmetricKeyParameter result = null;
123+
124+
if (blob[0] == 0x30)
125+
{
126+
Asn1Sequence sequence = Asn1Sequence.GetInstance(blob);
127+
128+
if (sequence.Count == 6)
129+
{
130+
if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
131+
{
132+
// length of 6 and all Integers -- DSA
133+
result = new DsaPrivateKeyParameters(
134+
((DerInteger)sequence[5]).PositiveValue,
135+
new DsaParameters(
136+
((DerInteger)sequence[1]).PositiveValue,
137+
((DerInteger)sequence[2]).PositiveValue,
138+
((DerInteger)sequence[3]).PositiveValue)
139+
);
140+
}
141+
}
142+
else if (sequence.Count == 9)
143+
{
144+
if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
145+
{
146+
// length of 8 and all Integers -- RSA
147+
RsaPrivateKeyStructure rsaPrivateKey = RsaPrivateKeyStructure.GetInstance(sequence);
148+
149+
result = new RsaPrivateCrtKeyParameters(
150+
rsaPrivateKey.Modulus,
151+
rsaPrivateKey.PublicExponent,
152+
rsaPrivateKey.PrivateExponent,
153+
rsaPrivateKey.Prime1,
154+
rsaPrivateKey.Prime2,
155+
rsaPrivateKey.Exponent1,
156+
rsaPrivateKey.Exponent2,
157+
rsaPrivateKey.Coefficient);
158+
}
159+
}
160+
else if (sequence.Count == 4)
161+
{
162+
if (sequence[3] is Asn1TaggedObject && sequence[2] is Asn1TaggedObject)
163+
{
164+
ECPrivateKeyStructure ecPrivateKey = ECPrivateKeyStructure.GetInstance(sequence);
165+
DerObjectIdentifier curveOID = DerObjectIdentifier.GetInstance(ecPrivateKey.GetParameters());
166+
X9ECParameters x9Params = ECNamedCurveTable.GetByOid(curveOID);
167+
result = new ECPrivateKeyParameters(
168+
ecPrivateKey.GetKey(),
169+
new ECNamedDomainParameters(
170+
curveOID,
171+
x9Params));
172+
}
173+
}
174+
}
175+
else
176+
{
177+
SshBuffer kIn = new SshBuffer(AUTH_MAGIC, blob);
178+
179+
string cipherName = kIn.ReadStringAscii();
180+
if (!"none".Equals(cipherName))
181+
throw new InvalidOperationException("encrypted keys not supported");
182+
183+
// KDF name
184+
kIn.SkipBlock();
185+
186+
// KDF options
187+
kIn.SkipBlock();
188+
189+
int publicKeyCount = kIn.ReadU32();
190+
if (publicKeyCount != 1)
191+
throw new InvalidOperationException("multiple keys not supported");
192+
193+
// Burn off public key.
194+
OpenSshPublicKeyUtilities.ParsePublicKey(kIn.ReadBlock());
195+
196+
byte[] privateKeyBlock = kIn.ReadPaddedBlock();
197+
198+
if (kIn.HasRemaining())
199+
throw new InvalidOperationException("decoded key has trailing data");
200+
201+
SshBuffer pkIn = new SshBuffer(privateKeyBlock);
202+
int check1 = pkIn.ReadU32();
203+
int check2 = pkIn.ReadU32();
204+
205+
if (check1 != check2)
206+
throw new InvalidOperationException("private key check values are not the same");
207+
208+
string keyType = pkIn.ReadStringAscii();
209+
210+
if ("ssh-ed25519".Equals(keyType))
211+
{
212+
// Public key
213+
pkIn.SkipBlock();
214+
215+
// Private key value..
216+
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
217+
ReadOnlySpan<byte> edPrivateKey = pkIn.ReadBlockSpan();
218+
#else
219+
byte[] edPrivateKey = pkIn.ReadBlock();
220+
#endif
221+
222+
if (edPrivateKey.Length != Ed25519PrivateKeyParameters.KeySize + Ed25519PublicKeyParameters.KeySize)
223+
throw new InvalidOperationException("private key value of wrong length");
224+
225+
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
226+
result = new Ed25519PrivateKeyParameters(edPrivateKey[..Ed25519PrivateKeyParameters.KeySize]);
227+
#else
228+
result = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
229+
#endif
230+
}
231+
else if (keyType.StartsWith("ecdsa"))
232+
{
233+
var curveName = pkIn.ReadStringAscii();
234+
235+
var oid = SshNamedCurves.GetOid(curveName) ??
236+
throw new InvalidOperationException("OID not found for: " + keyType);
237+
238+
var x9ECParameters = SshNamedCurves.GetByOid(oid) ??
239+
throw new InvalidOperationException("Curve not found for: " + oid);
240+
241+
// Skip public key.
242+
pkIn.SkipBlock();
243+
244+
var d = pkIn.ReadMpintPositive();
245+
246+
result = new ECPrivateKeyParameters(d, new ECNamedDomainParameters(oid, x9ECParameters));
247+
}
248+
else if (keyType.StartsWith("ssh-rsa"))
249+
{
250+
BigInteger modulus = pkIn.ReadMpintPositive();
251+
BigInteger pubExp = pkIn.ReadMpintPositive();
252+
BigInteger privExp = pkIn.ReadMpintPositive();
253+
BigInteger coef = pkIn.ReadMpintPositive();
254+
BigInteger p = pkIn.ReadMpintPositive();
255+
BigInteger q = pkIn.ReadMpintPositive();
256+
257+
BigInteger pSub1 = p.Subtract(BigIntegers.One);
258+
BigInteger qSub1 = q.Subtract(BigIntegers.One);
259+
BigInteger dP = privExp.Remainder(pSub1);
260+
BigInteger dQ = privExp.Remainder(qSub1);
261+
262+
result = new RsaPrivateCrtKeyParameters(modulus, pubExp, privExp, p, q, dP, dQ, coef);
263+
}
264+
265+
// Comment for private key
266+
pkIn.SkipBlock();
267+
268+
if (pkIn.HasRemaining())
269+
throw new ArgumentException("private key block has trailing data");
270+
}
271+
272+
return result ?? throw new ArgumentException("unable to parse key"); ;
273+
}
274+
275+
/**
276+
* allIntegers returns true if the sequence holds only DerInteger types.
277+
**/
278+
private static bool AllIntegers(Asn1Sequence sequence)
279+
{
280+
for (int t = 0; t < sequence.Count; t++)
281+
{
282+
if (!(sequence[t] is DerInteger))
283+
return false;
284+
}
285+
return true;
286+
}
287+
}
288+
}

0 commit comments

Comments
 (0)