Skip to content

Commit 1c4cacb

Browse files
authored
Merge pull request #21 from github/sk-keys
Support parsing SK-* keys
2 parents 236c8a3 + 595d2d6 commit 1c4cacb

20 files changed

+411
-35
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,6 @@ build-iPhoneSimulator/
5151

5252
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
5353
.rvmrc
54+
55+
# macOS generated files
56+
.DS_Store

lib/ssh_data/certificate.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ class Certificate
1212
TYPE_HOST = 2
1313

1414
# Certificate algorithm identifiers
15-
ALGO_RSA = "[email protected]"
16-
ALGO_DSA = "[email protected]"
17-
ALGO_ECDSA256 = "[email protected]"
18-
ALGO_ECDSA384 = "[email protected]"
19-
ALGO_ECDSA521 = "[email protected]"
20-
ALGO_ED25519 = "[email protected]"
15+
ALGO_RSA = "[email protected]"
16+
ALGO_DSA = "[email protected]"
17+
ALGO_ECDSA256 = "[email protected]"
18+
ALGO_ECDSA384 = "[email protected]"
19+
ALGO_ECDSA521 = "[email protected]"
20+
ALGO_ED25519 = "[email protected]"
21+
ALGO_SKECDSA256 = "[email protected]"
22+
ALGO_SKED25519 = "[email protected]"
2123

2224
ALGOS = [
2325
ALGO_RSA, ALGO_DSA, ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521,
24-
ALGO_ED25519
26+
ALGO_ED25519, ALGO_SKECDSA256, ALGO_SKED25519
2527
]
2628

2729
CRITICAL_OPTION_FORCE_COMMAND = "force-command"

lib/ssh_data/encoding.rb

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,44 @@ module Encoding
6262
[:public_key, :string]
6363
]
6464

65+
# Fields in an SK-ECDSA public key
66+
SKECDSA_KEY_FIELDS = [
67+
[:curve, :string],
68+
[:public_key, :string],
69+
[:application, :string]
70+
]
71+
6572
# Fields in a ED25519 public key
6673
ED25519_KEY_FIELDS = [
6774
[:pk, :string]
6875
]
6976

77+
# Fields in a SK-ED25519 public key
78+
SKED25519_KEY_FIELDS = [
79+
[:pk, :string],
80+
[:application, :string]
81+
]
82+
7083
PUBLIC_KEY_ALGO_BY_CERT_ALGO = {
71-
Certificate::ALGO_RSA => PublicKey::ALGO_RSA,
72-
Certificate::ALGO_DSA => PublicKey::ALGO_DSA,
73-
Certificate::ALGO_ECDSA256 => PublicKey::ALGO_ECDSA256,
74-
Certificate::ALGO_ECDSA384 => PublicKey::ALGO_ECDSA384,
75-
Certificate::ALGO_ECDSA521 => PublicKey::ALGO_ECDSA521,
76-
Certificate::ALGO_ED25519 => PublicKey::ALGO_ED25519,
84+
Certificate::ALGO_RSA => PublicKey::ALGO_RSA,
85+
Certificate::ALGO_DSA => PublicKey::ALGO_DSA,
86+
Certificate::ALGO_ECDSA256 => PublicKey::ALGO_ECDSA256,
87+
Certificate::ALGO_ECDSA384 => PublicKey::ALGO_ECDSA384,
88+
Certificate::ALGO_ECDSA521 => PublicKey::ALGO_ECDSA521,
89+
Certificate::ALGO_ED25519 => PublicKey::ALGO_ED25519,
90+
Certificate::ALGO_SKECDSA256 => PublicKey::ALGO_SKECDSA256,
91+
Certificate::ALGO_SKED25519 => PublicKey::ALGO_SKED25519,
7792
}
7893

7994
CERT_ALGO_BY_PUBLIC_KEY_ALGO = {
80-
PublicKey::ALGO_RSA => Certificate::ALGO_RSA,
81-
PublicKey::ALGO_DSA => Certificate::ALGO_DSA,
82-
PublicKey::ALGO_ECDSA256 => Certificate::ALGO_ECDSA256,
83-
PublicKey::ALGO_ECDSA384 => Certificate::ALGO_ECDSA384,
84-
PublicKey::ALGO_ECDSA521 => Certificate::ALGO_ECDSA521,
85-
PublicKey::ALGO_ED25519 => Certificate::ALGO_ED25519,
95+
PublicKey::ALGO_RSA => Certificate::ALGO_RSA,
96+
PublicKey::ALGO_DSA => Certificate::ALGO_DSA,
97+
PublicKey::ALGO_ECDSA256 => Certificate::ALGO_ECDSA256,
98+
PublicKey::ALGO_ECDSA384 => Certificate::ALGO_ECDSA384,
99+
PublicKey::ALGO_ECDSA521 => Certificate::ALGO_ECDSA521,
100+
PublicKey::ALGO_ED25519 => Certificate::ALGO_ED25519,
101+
PublicKey::ALGO_SKECDSA256 => Certificate::ALGO_SKECDSA256,
102+
PublicKey::ALGO_SKED25519 => Certificate::ALGO_SKED25519,
86103
}
87104

88105
KEY_FIELDS_BY_PUBLIC_KEY_ALGO = {
@@ -92,6 +109,8 @@ module Encoding
92109
PublicKey::ALGO_ECDSA384 => ECDSA_KEY_FIELDS,
93110
PublicKey::ALGO_ECDSA521 => ECDSA_KEY_FIELDS,
94111
PublicKey::ALGO_ED25519 => ED25519_KEY_FIELDS,
112+
PublicKey::ALGO_SKED25519 => SKED25519_KEY_FIELDS,
113+
PublicKey::ALGO_SKECDSA256 => SKECDSA_KEY_FIELDS,
95114
}
96115

97116
KEY_FIELDS_BY_PRIVATE_KEY_ALGO = {

lib/ssh_data/error.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module SSHData
2-
Error = Class.new(StandardError)
3-
DecodeError = Class.new(Error)
4-
VerifyError = Class.new(Error)
5-
AlgorithmError = Class.new(Error)
6-
DecryptError = Class.new(Error)
2+
Error = Class.new(StandardError)
3+
DecodeError = Class.new(Error)
4+
VerifyError = Class.new(Error)
5+
AlgorithmError = Class.new(Error)
6+
DecryptError = Class.new(Error)
7+
UnsupportedError = Class.new(Error)
78
end

lib/ssh_data/public_key.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
module SSHData
22
module PublicKey
33
# Public key algorithm identifiers
4-
ALGO_RSA = "ssh-rsa"
5-
ALGO_DSA = "ssh-dss"
6-
ALGO_ECDSA256 = "ecdsa-sha2-nistp256"
7-
ALGO_ECDSA384 = "ecdsa-sha2-nistp384"
8-
ALGO_ECDSA521 = "ecdsa-sha2-nistp521"
9-
ALGO_ED25519 = "ssh-ed25519"
4+
ALGO_RSA = "ssh-rsa"
5+
ALGO_DSA = "ssh-dss"
6+
ALGO_ECDSA256 = "ecdsa-sha2-nistp256"
7+
ALGO_ECDSA384 = "ecdsa-sha2-nistp384"
8+
ALGO_ECDSA521 = "ecdsa-sha2-nistp521"
9+
ALGO_ED25519 = "ssh-ed25519"
10+
ALGO_SKED25519 = "[email protected]"
11+
ALGO_SKECDSA256 = "[email protected]"
1012

1113
# RSA SHA2 *signature* algorithms used with ALGO_RSA keys.
1214
# https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02
@@ -15,7 +17,7 @@ module PublicKey
1517

1618
ALGOS = [
1719
ALGO_RSA, ALGO_DSA, ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521,
18-
ALGO_ED25519
20+
ALGO_ED25519, ALGO_SKECDSA256, ALGO_SKED25519
1921
]
2022

2123
# Parse an OpenSSH public key in authorized_keys format (see sshd(8) manual
@@ -64,6 +66,10 @@ def self.from_data(data)
6466
ECDSA.new(**data)
6567
when ALGO_ED25519
6668
ED25519.new(**data)
69+
when ALGO_SKED25519
70+
SKED25519.new(**data)
71+
when ALGO_SKECDSA256
72+
SKECDSA.new(**data)
6773
else
6874
raise DecodeError, "unkown algo: #{data[:algo].inspect}"
6975
end
@@ -76,3 +82,5 @@ def self.from_data(data)
7682
require "ssh_data/public_key/dsa"
7783
require "ssh_data/public_key/ecdsa"
7884
require "ssh_data/public_key/ed25519"
85+
require "ssh_data/public_key/sked25519"
86+
require "ssh_data/public_key/skecdsa"

lib/ssh_data/public_key/ecdsa.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,18 @@ def self.ssh_signature(sig)
6565
[Encoding.encode_mpint(r.value), Encoding.encode_mpint(s.value)].join
6666
end
6767

68-
def initialize(algo:, curve:, public_key:)
68+
def self.check_algorithm!(algo, curve)
6969
unless [ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521].include?(algo)
7070
raise DecodeError, "bad algorithm: #{algo.inspect}"
7171
end
7272

7373
unless algo == "ecdsa-sha2-#{curve}"
7474
raise DecodeError, "bad curve: #{curve.inspect}"
7575
end
76+
end
77+
78+
def initialize(algo:, curve:, public_key:)
79+
self.class.check_algorithm!(algo, curve)
7680

7781
@curve = curve
7882
@public_key_bytes = public_key

lib/ssh_data/public_key/ed25519.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ def self.ed25519_gem_required!
1616
raise AlgorithmError, "the ed25519 gem is not loaded" unless enabled?
1717
end
1818

19+
def self.algorithm_identifier
20+
ALGO_ED25519
21+
end
22+
1923
def initialize(algo:, pk:)
20-
unless algo == ALGO_ED25519
24+
unless algo == self.class.algorithm_identifier
2125
raise DecodeError, "bad algorithm: #{algo.inspect}"
2226
end
2327

@@ -40,7 +44,7 @@ def verify(signed_data, signature)
4044
self.class.ed25519_gem_required!
4145

4246
sig_algo, raw_sig, _ = Encoding.decode_signature(signature)
43-
if sig_algo != ALGO_ED25519
47+
if sig_algo != self.class.algorithm_identifier
4448
raise DecodeError, "bad signature algorithm: #{sig_algo.inspect}"
4549
end
4650

lib/ssh_data/public_key/skecdsa.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module SSHData
2+
module PublicKey
3+
class SKECDSA < ECDSA
4+
attr_reader :application
5+
6+
OPENSSL_CURVE_NAME_FOR_CURVE = {
7+
NISTP256 => "prime256v1",
8+
}
9+
10+
def self.check_algorithm!(algo, curve)
11+
unless algo == ALGO_SKECDSA256
12+
raise DecodeError, "bad algorithm: #{algo.inspect}"
13+
end
14+
15+
unless algo == "sk-ecdsa-sha2-#{curve}@openssh.com"
16+
raise DecodeError, "bad curve: #{curve.inspect}"
17+
end
18+
end
19+
20+
def initialize(algo:, curve:, public_key:, application:)
21+
@application = application
22+
super(algo: algo, curve: curve, public_key: public_key)
23+
end
24+
25+
# RFC4253 binary encoding of the public key.
26+
#
27+
# Returns a binary String.
28+
def rfc4253
29+
Encoding.encode_fields(
30+
[:string, algo],
31+
[:string, curve],
32+
[:string, public_key_bytes],
33+
[:string, application],
34+
)
35+
end
36+
37+
def verify(signed_data, signature)
38+
raise UnsupportedError, "SK-ECDSA verification is not supported."
39+
end
40+
41+
def ==(other)
42+
super && other.application == application
43+
end
44+
end
45+
end
46+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module SSHData
2+
module PublicKey
3+
class SKED25519 < ED25519
4+
attr_reader :application
5+
6+
def initialize(algo:, pk:, application:)
7+
@application = application
8+
super(algo: algo, pk: pk)
9+
end
10+
11+
def self.algorithm_identifier
12+
ALGO_SKED25519
13+
end
14+
15+
# RFC4253 binary encoding of the public key.
16+
#
17+
# Returns a binary String.
18+
def rfc4253
19+
Encoding.encode_fields(
20+
[:string, algo],
21+
[:string, pk],
22+
[:string, application],
23+
)
24+
end
25+
26+
def verify(signed_data, signature)
27+
raise UnsupportedError, "SK-Ed25519 verification is not supported."
28+
end
29+
30+
def ==(other)
31+
super && other.application == application
32+
end
33+
end
34+
end
35+
end

spec/certificate_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,22 @@
265265
SSHData::PublicKey::ED25519 # ca key type
266266
]
267267

268+
test_cases << [
269+
:skecdsa_leaf_for_rsa_ca, # name
270+
"skecdsa_leaf_for_rsa_ca-cert.pub", # fixture
271+
SSHData::Certificate::ALGO_SKECDSA256, # algo
272+
SSHData::PublicKey::SKECDSA, # public key type
273+
SSHData::PublicKey::RSA # ca key type
274+
]
275+
276+
test_cases << [
277+
:sked25519_leaf_for_rsa_ca, # name
278+
"sked25519_leaf_for_rsa_ca-cert.pub", # fixture
279+
SSHData::Certificate::ALGO_SKED25519, # algo
280+
SSHData::PublicKey::SKED25519, # public key type
281+
SSHData::PublicKey::RSA # ca key type
282+
]
283+
268284
test_cases.each do |name, fixture_name, algo, public_key_class, ca_key_class|
269285
describe(name) do
270286
let(:openssh) { fixture(fixture_name).strip }

0 commit comments

Comments
 (0)