Skip to content

Commit 3375612

Browse files
imirkingopherbot
authored andcommitted
ssh: add support for unpadded RSA signatures
The original SSH RFC 4253 explicitly disallows padding. This applies to ssh-rsa signatures. The updated SSH RFC 8332 which defines the SHA2 RSA signature variants explicitly calls out the existence of signers who produce short signatures and specifies that verifiers may allow this behavior. In practice, PuTTY 0.81 and prior versions, as well as SSH.NET prior to 2024.1.0 always generated short signatures. Furthermore, PuTTY is embedded in other software like WinSCP and FileZilla, which are updated on their own schedules as well. This leads to occasional unexplained login errors, when using RSA keys. OpenSSH server allows these short signatures for all RSA algorithms. Fixes golang/go#68286 Change-Id: Ia60ece21bf9c111c490fac0c066443ed5ff7dd29 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/598534 Reviewed-by: Nicola Murino <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Nicola Murino <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent bb80217 commit 3375612

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

ssh/keys.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,49 @@ func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
488488
h := hash.New()
489489
h.Write(data)
490490
digest := h.Sum(nil)
491-
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), hash, digest, sig.Blob)
491+
492+
// Signatures in PKCS1v15 must match the key's modulus in
493+
// length. However with SSH, some signers provide RSA
494+
// signatures which are missing the MSB 0's of the bignum
495+
// represented. With ssh-rsa signatures, this is encouraged by
496+
// the spec (even though e.g. OpenSSH will give the full
497+
// length unconditionally). With rsa-sha2-* signatures, the
498+
// verifier is allowed to support these, even though they are
499+
// out of spec. See RFC 4253 Section 6.6 for ssh-rsa and RFC
500+
// 8332 Section 3 for rsa-sha2-* details.
501+
//
502+
// In practice:
503+
// * OpenSSH always allows "short" signatures:
504+
// https://github.com/openssh/openssh-portable/blob/V_9_8_P1/ssh-rsa.c#L526
505+
// but always generates padded signatures:
506+
// https://github.com/openssh/openssh-portable/blob/V_9_8_P1/ssh-rsa.c#L439
507+
//
508+
// * PuTTY versions 0.81 and earlier will generate short
509+
// signatures for all RSA signature variants. Note that
510+
// PuTTY is embedded in other software, such as WinSCP and
511+
// FileZilla. At the time of writing, a patch has been
512+
// applied to PuTTY to generate padded signatures for
513+
// rsa-sha2-*, but not yet released:
514+
// https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=a5bcf3d384e1bf15a51a6923c3724cbbee022d8e
515+
//
516+
// * SSH.NET versions 2024.0.0 and earlier will generate short
517+
// signatures for all RSA signature variants, fixed in 2024.1.0:
518+
// https://github.com/sshnet/SSH.NET/releases/tag/2024.1.0
519+
//
520+
// As a result, we pad these up to the key size by inserting
521+
// leading 0's.
522+
//
523+
// Note that support for short signatures with rsa-sha2-* may
524+
// be removed in the future due to such signatures not being
525+
// allowed by the spec.
526+
blob := sig.Blob
527+
keySize := (*rsa.PublicKey)(r).Size()
528+
if len(blob) < keySize {
529+
padded := make([]byte, keySize)
530+
copy(padded[keySize-len(blob):], blob)
531+
blob = padded
532+
}
533+
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), hash, digest, blob)
492534
}
493535

494536
func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {

ssh/keys_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,44 @@ func TestKeySignWithAlgorithmVerify(t *testing.T) {
154154
}
155155
}
156156

157+
func TestKeySignWithShortSignature(t *testing.T) {
158+
signer := testSigners["rsa"].(AlgorithmSigner)
159+
pub := signer.PublicKey()
160+
// Note: data obtained by empirically trying until a result
161+
// starting with 0 appeared
162+
tests := []struct {
163+
algorithm string
164+
data []byte
165+
}{
166+
{
167+
algorithm: KeyAlgoRSA,
168+
data: []byte("sign me92"),
169+
},
170+
{
171+
algorithm: KeyAlgoRSASHA256,
172+
data: []byte("sign me294"),
173+
},
174+
{
175+
algorithm: KeyAlgoRSASHA512,
176+
data: []byte("sign me60"),
177+
},
178+
}
179+
180+
for _, tt := range tests {
181+
sig, err := signer.SignWithAlgorithm(rand.Reader, tt.data, tt.algorithm)
182+
if err != nil {
183+
t.Fatalf("Sign(%T): %v", signer, err)
184+
}
185+
if sig.Blob[0] != 0 {
186+
t.Errorf("%s: Expected signature with a leading 0", tt.algorithm)
187+
}
188+
sig.Blob = sig.Blob[1:]
189+
if err := pub.Verify(tt.data, sig); err != nil {
190+
t.Errorf("publicKey.Verify(%s): %v", tt.algorithm, err)
191+
}
192+
}
193+
}
194+
157195
func TestParseRSAPrivateKey(t *testing.T) {
158196
key := testPrivateKeys["rsa"]
159197

0 commit comments

Comments
 (0)