Skip to content

Commit

Permalink
add support for reading sk keys
Browse files Browse the repository at this point in the history
This adds partial support for "sk" keys that are associated with a
hardware key. We can only read keys but can't use them for signing.

Reference: https://api.libssh.org/rfc/PROTOCOL.u2f
Issue: dlech/KeeAgent#300
  • Loading branch information
dlech committed May 24, 2022
1 parent aaa5ebd commit dd41ac2
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 44 deletions.
6 changes: 4 additions & 2 deletions SshAgentLib/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,8 @@ public void AnswerMessage(Stream messageStream, Process process = null)
{
var publicKeyParams = messageParser.ReadSsh2PublicKeyData(
out var nonce,
out var cert
out var cert,
out var application
);
var privateKeyParams = messageParser.ReadSsh2KeyData(publicKeyParams);
var key = new SshKey(
Expand All @@ -801,7 +802,8 @@ out var cert
privateKeyParams,
"",
nonce,
cert
cert,
application
)
{
Comment = messageParser.ReadString(),
Expand Down
13 changes: 11 additions & 2 deletions SshAgentLib/AgentClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,20 @@ public ICollection<ISshKey> ListKeys(SshVersion aVersion)
var publicKeyParser = new BlobParser(publicKeyBlob);
var publicKeyParams = publicKeyParser.ReadSsh2PublicKeyData(
out var nonce,
out var cert
out var cert,
out var application
);
var comment = replyParser.ReadString();
keyCollection.Add(
new SshKey(SshVersion.SSH2, publicKeyParams, null, comment, nonce, cert)
new SshKey(
SshVersion.SSH2,
publicKeyParams,
null,
comment,
nonce,
cert,
application
)
);
}
break;
Expand Down
69 changes: 63 additions & 6 deletions SshAgentLib/BlobParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,13 @@ OpensshCertificateInfo ReadCertificate()
/// <returns>AsymmetricKeyParameter containing the public key</returns>
public AsymmetricKeyParameter ReadSsh2PublicKeyData(
out byte[] nonce,
out OpensshCertificateInfo cert
out OpensshCertificateInfo cert,
out string application
)
{
nonce = null;
cert = null;
application = null;
var algorithm = KeyFormatIdentifier.Parse(ReadString());

switch (algorithm)
Expand All @@ -189,7 +191,11 @@ out OpensshCertificateInfo cert
// we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob
// is the whole certificate, not the nonce
var certParser = new BlobParser(nonceOrPublicKey);
return certParser.ReadSsh2PublicKeyData(out nonce, out cert);
return certParser.ReadSsh2PublicKeyData(
out nonce,
out cert,
out application
);
}
else
{
Expand Down Expand Up @@ -222,7 +228,11 @@ out OpensshCertificateInfo cert
// we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob
// is the whole certificate, not the nonce
var certParser = new BlobParser(nonceOrPublicKey);
return certParser.ReadSsh2PublicKeyData(out nonce, out cert);
return certParser.ReadSsh2PublicKeyData(
out nonce,
out cert,
out application
);
}
else
{
Expand All @@ -242,10 +252,18 @@ out OpensshCertificateInfo cert
case PublicKeyAlgorithm.EcdsaSha2Nistp256:
case PublicKeyAlgorithm.EcdsaSha2Nistp384:
case PublicKeyAlgorithm.EcdsaSha2Nistp521:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp256:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp384:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp521:
{
var curveName = ReadString();
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

var x9Params = NistNamedCurves.GetByName(curveName.Replace("nistp", "P-"));
var domainParams = new ECDomainParameters(
x9Params.Curve,
Expand All @@ -260,20 +278,33 @@ out OpensshCertificateInfo cert
case PublicKeyAlgorithm.EcdsaSha2Nistp256CertV1:
case PublicKeyAlgorithm.EcdsaSha2Nistp384CertV1:
case PublicKeyAlgorithm.EcdsaSha2Nistp521CertV1:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp256CertV1:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp384CertV1:
case PublicKeyAlgorithm.SkEcdsaSha2Nistp521CertV1:
{
var nonceOrPublicKey = ReadBlob();

if (nonceOrPublicKey.Length != 32)
{
// we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob
// is the whole certificate, not the nonce
var certParser = new BlobParser(nonceOrPublicKey);
return certParser.ReadSsh2PublicKeyData(out nonce, out cert);
return certParser.ReadSsh2PublicKeyData(
out nonce,
out cert,
out application
);
}
else
{
var curveName = ReadString();
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

nonce = nonceOrPublicKey;
cert = ReadCertificate();

Expand All @@ -291,20 +322,28 @@ out OpensshCertificateInfo cert
}

case PublicKeyAlgorithm.SshEd25519:
case PublicKeyAlgorithm.SkSshEd25519:
{
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

return new Ed25519PublicKeyParameters(publicKey);
}

case PublicKeyAlgorithm.SshEd25519CertV1:
case PublicKeyAlgorithm.SkSshEd25519CertV1:
{
var nonceOrPublicKey = ReadBlob();
if (nonceOrPublicKey.Length != 32)
{
// we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob
// is the whole certificate, not the nonce
var certParser = new BlobParser(nonceOrPublicKey);
certParser.ReadSsh2PublicKeyData(out nonce, out cert);
certParser.ReadSsh2PublicKeyData(out nonce, out cert, out application);
var publicKey = ReadBlob();

return new Ed25519PublicKeyParameters(publicKey);
Expand All @@ -313,6 +352,11 @@ out OpensshCertificateInfo cert
{
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

nonce = nonceOrPublicKey;
cert = ReadCertificate();

Expand All @@ -321,20 +365,28 @@ out OpensshCertificateInfo cert
}

case PublicKeyAlgorithm.SshEd448:
case PublicKeyAlgorithm.SkSshEd448:
{
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

return new Ed448PublicKeyParameters(publicKey);
}

case PublicKeyAlgorithm.SshEd448CertV1:
case PublicKeyAlgorithm.SkSshEd448CertV1:
{
var nonceOrPublicKey = ReadBlob();
if (nonceOrPublicKey.Length != 32)
{
// we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob
// is the whole certificate, not the nonce
var certParser = new BlobParser(nonceOrPublicKey);
certParser.ReadSsh2PublicKeyData(out nonce, out cert);
certParser.ReadSsh2PublicKeyData(out nonce, out cert, out application);
var publicKey = ReadBlob();

return new Ed448PublicKeyParameters(publicKey);
Expand All @@ -343,6 +395,11 @@ out OpensshCertificateInfo cert
{
var publicKey = ReadBlob();

if (algorithm.HasApplication())
{
application = ReadString();
}

nonce = nonceOrPublicKey;
cert = ReadCertificate();

Expand Down
3 changes: 2 additions & 1 deletion SshAgentLib/IAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ public static ISshKey AddKeyFromFile(
privateKey.Decrypt(getPassPhraseCallback, progress),
privateKey.PublicKey.Comment,
publicKey.Nonce,
publicKey.Certificate
publicKey.Certificate,
publicKey.Application
);

if (constraints != null)
Expand Down
11 changes: 11 additions & 0 deletions SshAgentLib/ISshKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ public interface ISshKey : IDisposable
/// </summary>
OpensshCertificateInfo Certificate { get; }

/// <summary>
/// Gets the application for keys associated with a hardware key or
/// <c>null</c> for keys that are not associated with a hardware key.
/// </summary>
string Application { get; }

/// <summary>
/// returns true if key does not have private key parameters
/// </summary>
Expand Down Expand Up @@ -217,6 +223,11 @@ public static byte[] GetPublicKeyBlob(this ISshKey key, bool cert = true)
);
}

if (key.Application != null)
{
builder.AddStringBlob(key.Application);
}

if (cert && key.Certificate != null)
{
var principalsBuilder = new BlobBuilder();
Expand Down
7 changes: 6 additions & 1 deletion SshAgentLib/Keys/OpensshPrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,16 @@ public static SshPrivateKey Read(Stream stream)
throw new FormatException("checkint does not match in private key.");
}
var publicKey = privateKeyParser.ReadSsh2PublicKeyData(out var nonce, out var cert);
var publicKey = privateKeyParser.ReadSsh2PublicKeyData(
out var nonce,
out var cert,
out var application
);
var privateKey = privateKeyParser.ReadSsh2KeyData(publicKey);
// var comment = privateKeyParser.ReadString();
// TODO: what to do with comment?
// TODO: should we throw if nonce/cert is not null?
// TODO: do we expect application?
return privateKey;
};
Expand Down
38 changes: 33 additions & 5 deletions SshAgentLib/Keys/SshPublicKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ public string AuthorizedKeysString
{
var builder = new StringBuilder();

builder.Append(GetAlgorithmIdentifier(Parameter, Certificate != null));
builder.Append(
GetAlgorithmIdentifier(Parameter, Certificate != null, Application != null)
);
builder.Append(' ');
builder.Append(Convert.ToBase64String(KeyBlob));

Expand All @@ -81,6 +83,12 @@ public string AuthorizedKeysString

public OpensshCertificateInfo Certificate { get; }

/// <summary>
/// Gets the application for hardware security keys or <c>null</c> if
/// this key is not associated with a hardware key.
/// </summary>
public string Application { get; }

/// <summary>
/// Creates a new public key.
/// </summary>
Expand All @@ -101,9 +109,14 @@ public SshPublicKey(SshVersion version, byte[] keyBlob, string comment = "")
Parameter = parser.ReadSsh1PublicKeyData();
break;
case SshVersion.SSH2:
Parameter = parser.ReadSsh2PublicKeyData(out var nonce, out var certificate);
Parameter = parser.ReadSsh2PublicKeyData(
out var nonce,
out var certificate,
out var application
);
Nonce = nonce;
Certificate = certificate;
Application = application;
break;
default:
throw new ArgumentException("unsupported SSH version", nameof(version));
Expand Down Expand Up @@ -136,8 +149,12 @@ public SshPublicKey WithoutCertificate()

// separate the key from the certificate
var parser = new BlobParser(KeyBlob);
var parameters = parser.ReadSsh2PublicKeyData(out var nonce, out var certificate);
var key = new SshKey(Version, parameters);
var parameters = parser.ReadSsh2PublicKeyData(
out var nonce,
out var certificate,
out var application
);
var key = new SshKey(Version, parameters, null, "", null, null, application);

return new SshPublicKey(Version, key.GetPublicKeyBlob(), Comment);
}
Expand Down Expand Up @@ -176,7 +193,8 @@ public static SshPublicKey Read(Stream stream)

private static string GetAlgorithmIdentifier(
AsymmetricKeyParameter parameters,
bool hasCertificate
bool hasCertificate,
bool hasApplication
)
{
var algorithm = GetBaseAlgorithmIdentifier(parameters);
Expand All @@ -186,6 +204,16 @@ bool hasCertificate
algorithm += "[email protected]";
}

if (hasApplication)
{
algorithm = "sk-" + algorithm;

if (!hasCertificate)
{
algorithm += "@openssh.com";
}
}

return algorithm;
}

Expand Down
Loading

0 comments on commit dd41ac2

Please sign in to comment.