Skip to content

Commit 303a134

Browse files
committed
Preserve security key signature blobs
1 parent 708239e commit 303a134

5 files changed

Lines changed: 244 additions & 5 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
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;
@@ -151,8 +152,12 @@ protected void process(int cmd, Buffer req, Buffer rep) throws Exception {
151152
String algo = result.getKey();
152153
byte[] signature = result.getValue();
153154
Buffer sig = new ByteArrayBuffer(algo.length() + signature.length + Long.SIZE, false);
154-
sig.putString(algo);
155-
sig.putBytes(signature);
155+
if (signingKey instanceof SecurityKeyPublicKey<?>) {
156+
sig.putRawBytes(signature);
157+
} else {
158+
sig.putString(algo);
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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
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;
@@ -175,7 +176,11 @@ public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, Str
175176
} else {
176177
Buffer buf = new ByteArrayBuffer(signature);
177178
String algorithm = buf.getString();
178-
signature = buf.getBytes();
179+
if (key instanceof SecurityKeyPublicKey<?>) {
180+
signature = signature.clone();
181+
} else {
182+
signature = buf.getBytes();
183+
}
179184
if (debugEnabled) {
180185
log.debug("sign({}/{})[{}] {}: {}",
181186
algo, keyType, KeyUtils.getFingerPrint(key), algorithm, BufferUtils.toHex(':', signature));

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

Lines changed: 7 additions & 2 deletions
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;
@@ -465,8 +466,12 @@ protected byte[] appendSignature(
465466
}
466467

467468
bs.clear();
468-
bs.putString(signatureAlgo);
469-
bs.putBytes(sig);
469+
if (key instanceof SecurityKeyPublicKey<?>) {
470+
bs.putRawBytes(sig);
471+
} else {
472+
bs.putString(signatureAlgo);
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: 98 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,49 @@ 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(key, 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(key, rawSignature, flags, counter, result.getValue());
118+
}
119+
120+
private static byte[] createSkSignature(SkEcdsaPublicKey key, byte[] rawSignature, byte flags, long counter) {
121+
ByteArrayBuffer buffer = new ByteArrayBuffer();
122+
buffer.putString(key.getKeyType());
123+
buffer.putBytes(rawSignature);
124+
buffer.putByte(flags);
125+
buffer.putUInt(counter);
126+
return buffer.getCompactData();
127+
}
128+
129+
private static void assertSkSignature(
130+
SkEcdsaPublicKey key, byte[] rawSignature, byte flags, long counter, byte[] signature) {
131+
ByteArrayBuffer buffer = new ByteArrayBuffer(signature);
132+
assertEquals(key.getKeyType(), buffer.getString());
133+
assertArrayEquals(rawSignature, buffer.getBytes());
134+
assertEquals(flags, buffer.getByte());
135+
assertEquals(counter, buffer.getUInt());
136+
assertEquals(0, buffer.available());
137+
}
138+
86139
private static class Server extends AbstractAgentClient {
87140

88141
private Client client;
@@ -132,4 +185,49 @@ void setResult(byte[] data) {
132185
result = data;
133186
}
134187
}
188+
189+
private static class StaticSignatureAgent implements SshAgent {
190+
private final PublicKey key;
191+
private final byte[] signature;
192+
193+
StaticSignatureAgent(PublicKey key, byte[] signature) {
194+
this.key = key;
195+
this.signature = signature.clone();
196+
}
197+
198+
@Override
199+
public Iterable<? extends Map.Entry<PublicKey, String>> getIdentities() {
200+
return Collections.singletonList(new SimpleImmutableEntry<>(key, "security key"));
201+
}
202+
203+
@Override
204+
public Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, String algo, byte[] data) {
205+
return new SimpleImmutableEntry<>(KeyUtils.getKeyType(key), signature.clone());
206+
}
207+
208+
@Override
209+
public void addIdentity(KeyPair key, String comment, SshAgentKeyConstraint... constraints) {
210+
throw new UnsupportedOperationException();
211+
}
212+
213+
@Override
214+
public void removeIdentity(PublicKey key) {
215+
throw new UnsupportedOperationException();
216+
}
217+
218+
@Override
219+
public void removeAllIdentities() {
220+
throw new UnsupportedOperationException();
221+
}
222+
223+
@Override
224+
public boolean isOpen() {
225+
return true;
226+
}
227+
228+
@Override
229+
public void close() {
230+
// Nothing to close.
231+
}
232+
}
135233
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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(sk, 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(SkEcdsaPublicKey key, byte[] rawSignature, byte flags, long counter) {
74+
ByteArrayBuffer buffer = new ByteArrayBuffer();
75+
buffer.putString(key.getKeyType());
76+
buffer.putBytes(rawSignature);
77+
buffer.putByte(flags);
78+
buffer.putUInt(counter);
79+
return buffer.getCompactData();
80+
}
81+
82+
private static void assertSkSignature(
83+
SkEcdsaPublicKey key, byte[] rawSignature, byte flags, long counter, byte[] signature) {
84+
ByteArrayBuffer buffer = new ByteArrayBuffer(signature);
85+
assertEquals(key.getKeyType(), buffer.getString());
86+
assertArrayEquals(rawSignature, buffer.getBytes());
87+
assertEquals(flags, buffer.getByte());
88+
assertEquals(counter, buffer.getUInt());
89+
assertEquals(0, buffer.available());
90+
}
91+
92+
private static class ExposedUserAuthPublicKey extends UserAuthPublicKey {
93+
ExposedUserAuthPublicKey(PublicKeyIdentity identity) {
94+
super(Collections.emptyList());
95+
current = identity;
96+
}
97+
98+
byte[] appendSignature(
99+
ClientSession session, String service, String name, String username, String algo, SkEcdsaPublicKey key)
100+
throws Exception {
101+
ByteArrayBuffer buffer = new ByteArrayBuffer();
102+
super.appendSignature(session, service, name, username, algo, key, null, buffer);
103+
return buffer.getBytes();
104+
}
105+
}
106+
107+
private static class StaticSignatureIdentity implements PublicKeyIdentity {
108+
private final SkEcdsaPublicKey key;
109+
private final byte[] signature;
110+
111+
StaticSignatureIdentity(SkEcdsaPublicKey key, byte[] signature) {
112+
this.key = key;
113+
this.signature = signature.clone();
114+
}
115+
116+
@Override
117+
public KeyPair getKeyIdentity() {
118+
return new KeyPair(key, null);
119+
}
120+
121+
@Override
122+
public Map.Entry<String, byte[]> sign(SessionContext session, String algo, byte[] data) {
123+
return new SimpleImmutableEntry<>(key.getKeyType(), signature.clone());
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)