Skip to content

Commit f463789

Browse files
Preserve security key signature blobs
1 parent 708239e commit f463789

5 files changed

Lines changed: 259 additions & 3 deletions

File tree

sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.sshd.agent.SshAgent;
3030
import org.apache.sshd.agent.SshAgentConstants;
3131
import org.apache.sshd.common.config.keys.KeyUtils;
32+
import org.apache.sshd.common.config.keys.u2f.SecurityKeyPublicKey;
3233
import org.apache.sshd.common.keyprovider.KeyPairProvider;
3334
import org.apache.sshd.common.util.ValidateUtils;
3435
import org.apache.sshd.common.util.buffer.Buffer;
@@ -152,7 +153,11 @@ protected void process(int cmd, Buffer req, Buffer rep) throws Exception {
152153
byte[] signature = result.getValue();
153154
Buffer sig = new ByteArrayBuffer(algo.length() + signature.length + Long.SIZE, false);
154155
sig.putString(algo);
155-
sig.putBytes(signature);
156+
if (signingKey instanceof SecurityKeyPublicKey<?>) {
157+
sig.putRawBytes(signature);
158+
} else {
159+
sig.putBytes(signature);
160+
}
156161
rep.putByte(SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE);
157162
rep.putBytes(sig.array(), sig.rpos(), sig.available());
158163
break;

sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentProxy.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@
3232
import org.apache.sshd.agent.SshAgentKeyConstraint;
3333
import org.apache.sshd.common.SshException;
3434
import org.apache.sshd.common.config.keys.KeyUtils;
35+
import org.apache.sshd.common.config.keys.u2f.SecurityKeyPublicKey;
3536
import org.apache.sshd.common.session.SessionContext;
3637
import org.apache.sshd.common.util.GenericUtils;
3738
import org.apache.sshd.common.util.buffer.Buffer;
39+
import org.apache.sshd.common.util.buffer.BufferException;
3840
import org.apache.sshd.common.util.buffer.BufferUtils;
3941
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
4042
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -175,7 +177,14 @@ public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, Str
175177
} else {
176178
Buffer buf = new ByteArrayBuffer(signature);
177179
String algorithm = buf.getString();
178-
signature = buf.getBytes();
180+
if (key instanceof SecurityKeyPublicKey<?>) {
181+
signature = getSecurityKeySignature(buf);
182+
} else {
183+
signature = buf.getBytes();
184+
if (buf.available() > 0) {
185+
throw new SshException("Trailing garbage in signing response, got " + buf.available() + " extra bytes");
186+
}
187+
}
179188
if (debugEnabled) {
180189
log.debug("sign({}/{})[{}] {}: {}",
181190
algo, keyType, KeyUtils.getFingerPrint(key), algorithm, BufferUtils.toHex(':', signature));
@@ -184,6 +193,23 @@ public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, Str
184193
}
185194
}
186195

196+
private static byte[] getSecurityKeySignature(Buffer buf) throws SshException {
197+
byte[] signature = buf.getCompactData();
198+
Buffer verifier = new ByteArrayBuffer(signature);
199+
try {
200+
verifier.getBytes();
201+
verifier.getByte();
202+
verifier.getUInt();
203+
} catch (BufferException e) {
204+
throw new SshException("Signing response too short", e);
205+
}
206+
if (verifier.available() > 0) {
207+
throw new SshException(
208+
"Trailing garbage in signing response, got " + verifier.available() + " extra bytes");
209+
}
210+
return signature;
211+
}
212+
187213
@Override
188214
public void addIdentity(KeyPair kp, String comment, SshAgentKeyConstraint... constraints) throws IOException {
189215
byte cmd = SshAgentConstants.SSH2_AGENTC_ADD_IDENTITY;

sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.apache.sshd.common.SshConstants;
4242
import org.apache.sshd.common.config.keys.KeyUtils;
4343
import org.apache.sshd.common.config.keys.OpenSshCertificate;
44+
import org.apache.sshd.common.config.keys.u2f.SecurityKeyPublicKey;
4445
import org.apache.sshd.common.kex.extension.DefaultClientKexExtensionHandler;
4546
import org.apache.sshd.common.kex.extension.parser.HostBoundPubkeyAuthentication;
4647
import org.apache.sshd.common.signature.Signature;
@@ -466,7 +467,11 @@ protected byte[] appendSignature(
466467

467468
bs.clear();
468469
bs.putString(signatureAlgo);
469-
bs.putBytes(sig);
470+
if (key instanceof SecurityKeyPublicKey<?>) {
471+
bs.putRawBytes(sig);
472+
} else {
473+
bs.putBytes(sig);
474+
}
470475
buffer.putBytes(bs.array(), bs.rpos(), bs.available());
471476
return sig;
472477
}

sshd-core/src/test/java/org/apache/sshd/agent/AgentUnitTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@
2020

2121
import java.io.IOException;
2222
import java.security.KeyPair;
23+
import java.security.KeyPairGenerator;
24+
import java.security.PublicKey;
25+
import java.security.interfaces.ECPublicKey;
26+
import java.util.AbstractMap.SimpleImmutableEntry;
2327
import java.util.Arrays;
28+
import java.util.Collections;
2429
import java.util.List;
2530
import java.util.Map;
2631

2732
import org.apache.sshd.agent.common.AbstractAgentClient;
2833
import org.apache.sshd.agent.common.AbstractAgentProxy;
2934
import org.apache.sshd.agent.local.AgentImpl;
3035
import org.apache.sshd.common.config.keys.KeyUtils;
36+
import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
3137
import org.apache.sshd.common.keyprovider.KeyPairProvider;
38+
import org.apache.sshd.common.session.SessionContext;
3239
import org.apache.sshd.common.signature.BuiltinSignatures;
3340
import org.apache.sshd.common.signature.Signature;
41+
import org.apache.sshd.common.util.ValidateUtils;
3442
import org.apache.sshd.common.util.buffer.Buffer;
3543
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
3644
import org.apache.sshd.common.util.security.SecurityUtils;
3745
import org.apache.sshd.util.test.BaseTestSupport;
3846
import org.junit.jupiter.api.Tag;
47+
import org.junit.jupiter.api.Test;
3948
import org.junit.jupiter.params.ParameterizedTest;
4049
import org.junit.jupiter.params.provider.MethodSource;
4150

51+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4252
import static org.junit.jupiter.api.Assertions.assertEquals;
4353
import static org.junit.jupiter.api.Assertions.assertTrue;
4454

@@ -83,6 +93,46 @@ public void rsaSignature(String algorithm, BuiltinSignatures factory) throws Exc
8393
assertTrue(verifier.verify(null, signature), "Signature should validate");
8494
}
8595

96+
@Test
97+
public void securityKeySignatureBlob() throws Exception {
98+
KeyPairGenerator generator = SecurityUtils.getKeyPairGenerator("EC");
99+
generator.initialize(256);
100+
KeyPair pair = generator.generateKeyPair();
101+
ECPublicKey ecPublicKey = ValidateUtils.checkInstanceOf(
102+
pair.getPublic(), ECPublicKey.class, "Expected an ECPublicKey");
103+
SkEcdsaPublicKey key = new SkEcdsaPublicKey("ssh", false, false, ecPublicKey);
104+
105+
byte[] rawSignature = { 1, 2, 3, 4, 5 };
106+
byte flags = 1;
107+
long counter = 42;
108+
byte[] signature = createSkSignature(rawSignature, flags, counter);
109+
110+
SshAgent agent = new StaticSignatureAgent(key, signature);
111+
Server server = new Server(agent);
112+
Client client = new Client(server);
113+
server.setClient(client);
114+
115+
Map.Entry<String, byte[]> result = client.sign(null, key, key.getKeyType(), new byte[] { 'd', 'a', 't', 'a' });
116+
assertEquals(key.getKeyType(), result.getKey(), "Unexpected signature algorithm");
117+
assertSkSignature(rawSignature, flags, counter, result.getValue());
118+
}
119+
120+
private static byte[] createSkSignature(byte[] rawSignature, byte flags, long counter) {
121+
ByteArrayBuffer buffer = new ByteArrayBuffer();
122+
buffer.putBytes(rawSignature);
123+
buffer.putByte(flags);
124+
buffer.putUInt(counter);
125+
return buffer.getCompactData();
126+
}
127+
128+
private static void assertSkSignature(byte[] rawSignature, byte flags, long counter, byte[] signature) {
129+
ByteArrayBuffer buffer = new ByteArrayBuffer(signature);
130+
assertArrayEquals(rawSignature, buffer.getBytes());
131+
assertEquals(flags, buffer.getByte());
132+
assertEquals(counter, buffer.getUInt());
133+
assertEquals(0, buffer.available());
134+
}
135+
86136
private static class Server extends AbstractAgentClient {
87137

88138
private Client client;
@@ -132,4 +182,49 @@ void setResult(byte[] data) {
132182
result = data;
133183
}
134184
}
185+
186+
private static class StaticSignatureAgent implements SshAgent {
187+
private final PublicKey key;
188+
private final byte[] signature;
189+
190+
StaticSignatureAgent(PublicKey key, byte[] signature) {
191+
this.key = key;
192+
this.signature = signature.clone();
193+
}
194+
195+
@Override
196+
public Iterable<? extends Map.Entry<PublicKey, String>> getIdentities() {
197+
return Collections.singletonList(new SimpleImmutableEntry<>(key, "security key"));
198+
}
199+
200+
@Override
201+
public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, String algo, byte[] data) {
202+
return new SimpleImmutableEntry<>(KeyUtils.getKeyType(key), signature.clone());
203+
}
204+
205+
@Override
206+
public void addIdentity(KeyPair key, String comment, SshAgentKeyConstraint... constraints) {
207+
throw new UnsupportedOperationException();
208+
}
209+
210+
@Override
211+
public void removeIdentity(PublicKey key) {
212+
throw new UnsupportedOperationException();
213+
}
214+
215+
@Override
216+
public void removeAllIdentities() {
217+
throw new UnsupportedOperationException();
218+
}
219+
220+
@Override
221+
public boolean isOpen() {
222+
return true;
223+
}
224+
225+
@Override
226+
public void close() {
227+
// Nothing to close.
228+
}
229+
}
135230
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sshd.client.auth.pubkey;
20+
21+
import java.security.KeyPair;
22+
import java.security.KeyPairGenerator;
23+
import java.security.interfaces.ECPublicKey;
24+
import java.util.AbstractMap.SimpleImmutableEntry;
25+
import java.util.Collections;
26+
import java.util.Map;
27+
28+
import org.apache.sshd.client.session.ClientSession;
29+
import org.apache.sshd.common.auth.AbstractUserAuthServiceFactory;
30+
import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
31+
import org.apache.sshd.common.session.SessionContext;
32+
import org.apache.sshd.common.util.ValidateUtils;
33+
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
34+
import org.apache.sshd.common.util.security.SecurityUtils;
35+
import org.apache.sshd.util.test.BaseTestSupport;
36+
import org.junit.jupiter.api.Tag;
37+
import org.junit.jupiter.api.Test;
38+
import org.mockito.Mockito;
39+
40+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
41+
import static org.junit.jupiter.api.Assertions.assertEquals;
42+
43+
/**
44+
* Unit tests for client-side sk-* authentication packet encoding.
45+
*/
46+
@Tag("NoIoTestCase")
47+
public class UserAuthPublicKeySkTest extends BaseTestSupport {
48+
49+
@Test
50+
public void securityKeySignatureBlobIsNotWrapped() throws Exception {
51+
byte[] rawSignature = { 1, 2, 3, 4, 5 };
52+
byte flags = 1;
53+
long counter = 42;
54+
55+
KeyPairGenerator generator = SecurityUtils.getKeyPairGenerator("EC");
56+
generator.initialize(256);
57+
KeyPair pair = generator.generateKeyPair();
58+
ECPublicKey ecPublicKey = ValidateUtils.checkInstanceOf(
59+
pair.getPublic(), ECPublicKey.class, "Expected an ECPublicKey");
60+
SkEcdsaPublicKey sk = new SkEcdsaPublicKey("ssh", false, false, ecPublicKey);
61+
byte[] skSignature = createSkSignature(rawSignature, flags, counter);
62+
63+
ClientSession session = Mockito.mock(ClientSession.class);
64+
Mockito.when(session.getSessionId()).thenReturn(new byte[] { 9, 8, 7, 6 });
65+
66+
ExposedUserAuthPublicKey auth = new ExposedUserAuthPublicKey(new StaticSignatureIdentity(sk, skSignature));
67+
byte[] actual = auth.appendSignature(
68+
session, AbstractUserAuthServiceFactory.DEFAULT_NAME, UserAuthPublicKey.NAME, "testuser", sk.getKeyType(), sk);
69+
70+
assertSkSignature(sk, rawSignature, flags, counter, actual);
71+
}
72+
73+
private static byte[] createSkSignature(byte[] rawSignature, byte flags, long counter) {
74+
ByteArrayBuffer buffer = new ByteArrayBuffer();
75+
buffer.putBytes(rawSignature);
76+
buffer.putByte(flags);
77+
buffer.putUInt(counter);
78+
return buffer.getCompactData();
79+
}
80+
81+
private static void assertSkSignature(
82+
SkEcdsaPublicKey key, byte[] rawSignature, byte flags, long counter, byte[] signature) {
83+
ByteArrayBuffer buffer = new ByteArrayBuffer(signature);
84+
assertEquals(key.getKeyType(), buffer.getString());
85+
assertArrayEquals(rawSignature, buffer.getBytes());
86+
assertEquals(flags, buffer.getByte());
87+
assertEquals(counter, buffer.getUInt());
88+
assertEquals(0, buffer.available());
89+
}
90+
91+
private static class ExposedUserAuthPublicKey extends UserAuthPublicKey {
92+
ExposedUserAuthPublicKey(PublicKeyIdentity identity) {
93+
super(Collections.emptyList());
94+
current = identity;
95+
}
96+
97+
byte[] appendSignature(
98+
ClientSession session, String service, String name, String username, String algo, SkEcdsaPublicKey key)
99+
throws Exception {
100+
ByteArrayBuffer buffer = new ByteArrayBuffer();
101+
super.appendSignature(session, service, name, username, algo, key, null, buffer);
102+
return buffer.getBytes();
103+
}
104+
}
105+
106+
private static class StaticSignatureIdentity implements PublicKeyIdentity {
107+
private final SkEcdsaPublicKey key;
108+
private final byte[] signature;
109+
110+
StaticSignatureIdentity(SkEcdsaPublicKey key, byte[] signature) {
111+
this.key = key;
112+
this.signature = signature.clone();
113+
}
114+
115+
@Override
116+
public KeyPair getKeyIdentity() {
117+
return new KeyPair(key, null);
118+
}
119+
120+
@Override
121+
public Map.Entry<String, byte[]> sign(SessionContext session, String algo, byte[] data) {
122+
return new SimpleImmutableEntry<>(key.getKeyType(), signature.clone());
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)