Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
meta:
id: restrict_destination_constraint
title: restrict-destination-v00@openssh.com Constraint
endian: be
imports:
- byte_string
doc: |
One hop entry for the restrict-destination-v00@openssh.com agent key constraint.
from_hostname and from_keyspecs are empty for origin-side constraints.
seq:
- id: from_hostname
type: byte_string
doc: Hostname of the previous hop (empty for origin)
- id: num_from_keyspecs
type: u4
doc: Number of from host key specs
- id: from_keyspecs
type: keyspec
repeat: expr
repeat-expr: num_from_keyspecs
doc: Host key specs for the previous hop (empty for origin)
- id: to_username
type: byte_string
doc: Destination username (empty = any user)
- id: to_hostname
type: byte_string
doc: Destination hostname
- id: num_to_hostspecs
type: u4
doc: Number of destination host key specs
- id: to_hostspecs
type: keyspec
repeat: expr
repeat-expr: num_to_hostspecs
doc: Destination host key specs
types:
keyspec:
seq:
- id: keyblob
type: byte_string
doc: Host key blob
- id: is_ca
type: u1
doc: 1 if this is a CA key, 0 if it is a direct host key
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ meta:
- userauth_request_none
- userauth_request_password
- userauth_request_publickey
- userauth_request_publickey_hostbound
doc-ref: RFC 4252 section 5
seq:
- id: user_name
Expand All @@ -26,6 +27,7 @@ seq:
switch-on: method_name.value
cases:
'"publickey"': userauth_request_publickey
'"publickey-hostbound-v00@openssh.com"': userauth_request_publickey_hostbound
'"password"': userauth_request_password
'"hostbased"': userauth_request_hostbased
'"none"': userauth_request_none
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
meta:
id: userauth_publickey_hostbound_signature_data
endian: be
imports:
- byte_string
doc-ref: OpenSSH publickey-hostbound-v00@openssh.com extension
doc: >
The data over which the signature is computed for publickey-hostbound authentication.
Identical to userauth_publickey_signature_data but method_name is
"publickey-hostbound-v00@openssh.com" and server_host_key is appended.
seq:
- id: session_identifier
type: byte_string
doc: Session identifier from key exchange
- id: message_type
contents:
- 50
doc: SSH_MSG_USERAUTH_REQUEST (50)
- id: user_name
type: byte_string
doc: User name
- id: service_name
type: byte_string
doc: Service name
- id: method_name
type: byte_string
doc: Authentication method name ("publickey-hostbound-v00@openssh.com")
- id: has_signature
contents:
- 1
doc: TRUE (1)
- id: public_key_algorithm_name
type: byte_string
doc: Public key algorithm name
- id: public_key_blob
type: byte_string
doc: Public key to be used for authentication
- id: server_host_key
type: byte_string
doc: Server's host key, binding the signature to the intended destination
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
meta:
id: userauth_publickey_signature_data_any
endian: be
imports:
- byte_string
- ascii_string
- utf8_string
doc: >
The data over which the signature is computed for publickey authentication.
This handles both the standard RFC 4252 version and the OpenSSH
publickey-hostbound-v00@openssh.com extension.
seq:
- id: session_identifier
type: byte_string
doc: Session identifier from key exchange
- id: message_type
contents: [50]
doc: SSH_MSG_USERAUTH_REQUEST (50)
- id: user_name
type: utf8_string
doc: User name
- id: service_name
type: ascii_string
doc: Service name
- id: method_name
type: ascii_string
doc: Authentication method name
- id: has_signature
contents: [1]
doc: TRUE (1)
- id: public_key_algorithm_name
type: ascii_string
doc: Public key algorithm name
- id: public_key_blob
type: byte_string
doc: Public key to be used for authentication
- id: server_host_key
type: byte_string
doc: Server's host key, binding the signature to the intended destination (only for hostbound method)
if: method_name.value == "publickey-hostbound-v00@openssh.com"
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
meta:
id: userauth_request_publickey_hostbound
endian: be
imports:
- ascii_string
- byte_string
doc-ref: OpenSSH publickey-hostbound-v00@openssh.com extension
doc: >
Publickey authentication method fields for publickey-hostbound-v00@openssh.com.
Identical to userauth_request_publickey but adds server_host_key before signature.
seq:
- id: has_signature
type: u1
doc: >
FALSE (0) to query if the public key is acceptable for authentication.
TRUE (1) to perform actual authentication with signature.
- id: public_key_algorithm_name
type: ascii_string
doc: Public key algorithm name
- id: public_key_blob
type: byte_string
doc: Public key blob (may contain certificates)
- id: server_host_key
type: byte_string
doc: Server's host key blob, binding the authentication to a specific destination
- id: signature
type: byte_string
doc: >
Signature over session identifier and authentication request including server host key.
Only present when has_signature is TRUE.
if: has_signature != 0
44 changes: 42 additions & 2 deletions sshlib/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,36 @@
package org.connectbot.sshlib {

public final class AgentIdentity {
ctor public AgentIdentity(byte[] publicKeyBlob, java.lang.String comment);
ctor public AgentIdentity(byte[] publicKeyBlob, java.lang.String comment, optional java.util.List<org.connectbot.sshlib.DestinationConstraint>? destinationConstraints);
method public byte[] component1();
method public java.lang.String component2();
method public org.connectbot.sshlib.AgentIdentity copy(optional byte[] publicKeyBlob, optional java.lang.String comment);
method public java.util.List<org.connectbot.sshlib.DestinationConstraint>? component3();
method public org.connectbot.sshlib.AgentIdentity copy(optional byte[] publicKeyBlob, optional java.lang.String comment, optional java.util.List<org.connectbot.sshlib.DestinationConstraint>? destinationConstraints);
method public boolean equals(java.lang.Object? other);
method @InaccessibleFromKotlin public java.lang.String getComment();
method @InaccessibleFromKotlin public java.util.List<org.connectbot.sshlib.DestinationConstraint>? getDestinationConstraints();
method @InaccessibleFromKotlin public byte[] getPublicKeyBlob();
method public int hashCode();
method public java.lang.String toString();
property public String comment;
property public java.util.List<org.connectbot.sshlib.DestinationConstraint>? destinationConstraints;
property public byte[] publicKeyBlob;
}

public final class AgentKeySpec {
ctor public AgentKeySpec(byte[] keyBlob, boolean isCa);
method public byte[] component1();
method public boolean component2();
method public org.connectbot.sshlib.AgentKeySpec copy(optional byte[] keyBlob, optional boolean isCa);
method public boolean equals(java.lang.Object? other);
method @InaccessibleFromKotlin public byte[] getKeyBlob();
method public int hashCode();
method @InaccessibleFromKotlin public boolean isCa();
method public java.lang.String toString();
property public boolean isCa;
property public byte[] keyBlob;
}

public interface AgentProvider {
method public suspend java.lang.Object? getIdentities(kotlin.coroutines.Continuation<? super java.util.List<org.connectbot.sshlib.AgentIdentity>>);
method public suspend java.lang.Object? signData(org.connectbot.sshlib.AgentSigningContext context, kotlin.coroutines.Continuation<? super byte[]?>);
Expand Down Expand Up @@ -196,6 +213,29 @@ package org.connectbot.sshlib {
property public String serverHostKeyAlgorithm;
}

public final class DestinationConstraint {
ctor public DestinationConstraint(java.lang.String fromHostname, java.util.List<org.connectbot.sshlib.AgentKeySpec> fromKeyspecs, java.lang.String toUsername, java.lang.String toHostname, java.util.List<org.connectbot.sshlib.AgentKeySpec> toHostspecs);
method public java.lang.String component1();
method public java.util.List<org.connectbot.sshlib.AgentKeySpec> component2();
method public java.lang.String component3();
method public java.lang.String component4();
method public java.util.List<org.connectbot.sshlib.AgentKeySpec> component5();
method public org.connectbot.sshlib.DestinationConstraint copy(optional java.lang.String fromHostname, optional java.util.List<org.connectbot.sshlib.AgentKeySpec> fromKeyspecs, optional java.lang.String toUsername, optional java.lang.String toHostname, optional java.util.List<org.connectbot.sshlib.AgentKeySpec> toHostspecs);
method public boolean equals(java.lang.Object? other);
method @InaccessibleFromKotlin public java.lang.String getFromHostname();
method @InaccessibleFromKotlin public java.util.List<org.connectbot.sshlib.AgentKeySpec> getFromKeyspecs();
method @InaccessibleFromKotlin public java.lang.String getToHostname();
method @InaccessibleFromKotlin public java.util.List<org.connectbot.sshlib.AgentKeySpec> getToHostspecs();
method @InaccessibleFromKotlin public java.lang.String getToUsername();
method public int hashCode();
method public java.lang.String toString();
property public String fromHostname;
property public java.util.List<org.connectbot.sshlib.AgentKeySpec> fromKeyspecs;
property public String toHostname;
property public java.util.List<org.connectbot.sshlib.AgentKeySpec> toHostspecs;
property public String toUsername;
}

public interface HostKeyVerifier {
method public default suspend java.lang.Object? addKeys(java.util.List<org.connectbot.sshlib.PublicKey> keys, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public default suspend java.lang.Object? removeKeys(java.util.List<org.connectbot.sshlib.PublicKey> keys, kotlin.coroutines.Continuation<? super kotlin.Unit>);
Expand Down
51 changes: 51 additions & 0 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,53 @@

package org.connectbot.sshlib

/**
* A host key specification used in destination constraints.
*
* @param keyBlob Wire-format public key blob
* @param isCa True if this is a CA key that signed the destination's host certificate
*/
data class AgentKeySpec(
val keyBlob: ByteArray,
val isCa: Boolean,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AgentKeySpec
if (!keyBlob.contentEquals(other.keyBlob)) return false
if (isCa != other.isCa) return false
return true
}

override fun hashCode(): Int {
var result = keyBlob.contentHashCode()
result = 31 * result + isCa.hashCode()
return result
}
}

/**
* One hop entry in a destination constraint for a key in the agent.
*
* Describes a single permitted (from → to) hop in a forwarding chain.
* When [fromHostname] and [fromKeyspecs] are empty this is an origin-direct constraint
* (key may be used directly from the machine running the agent).
*
* @param fromHostname Hostname of the previous hop, empty string for the origin machine
* @param fromKeyspecs Host key specs of the previous hop, empty for the origin machine
* @param toUsername Permitted destination username; empty string means any user is allowed
* @param toHostname Destination hostname
* @param toHostspecs Destination host key specs (must be non-empty)
*/
data class DestinationConstraint(
val fromHostname: String,
val fromKeyspecs: List<AgentKeySpec>,
val toUsername: String,
val toHostname: String,
val toHostspecs: List<AgentKeySpec>,
)

/**
* Provider interface for SSH agent forwarding.
*
Expand Down Expand Up @@ -50,23 +97,27 @@ interface AgentProvider {
*
* @param publicKeyBlob Wire-format public key blob
* @param comment Human-readable comment describing the key
* @param destinationConstraints Optional per-hop destination constraints; null means unconstrained
*/
data class AgentIdentity(
val publicKeyBlob: ByteArray,
val comment: String,
val destinationConstraints: List<DestinationConstraint>? = null,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AgentIdentity
if (!publicKeyBlob.contentEquals(other.publicKeyBlob)) return false
if (comment != other.comment) return false
if (destinationConstraints != other.destinationConstraints) return false
return true
}

override fun hashCode(): Int {
var result = publicKeyBlob.contentHashCode()
result = 31 * result + comment.hashCode()
result = 31 * result + destinationConstraints.hashCode()
Comment thread
kruton marked this conversation as resolved.
return result
}
}
Expand Down
Loading
Loading