Skip to content

Commit

Permalink
Adopt MultiAlgorithmSigner proposal from golang/go#52132
Browse files Browse the repository at this point in the history
  • Loading branch information
dombenson committed Aug 9, 2023
1 parent b4ddeed commit 130ec71
Show file tree
Hide file tree
Showing 7 changed files with 1,525 additions and 12 deletions.
45 changes: 43 additions & 2 deletions ssh/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,36 @@ func (s *algorithmOpenSSHCertSigner) SignWithAlgorithm(rand io.Reader, data []by
return s.algorithmSigner.SignWithAlgorithm(rand, data, algorithm)
}

type multiAlgorithmSSHCertSigner struct {
AlgorithmSigner
supportedAlgorithms []string
}

func (s *multiAlgorithmSSHCertSigner) Algorithms() []string {
return s.supportedAlgorithms
}

// NewSignerWithAlgorithms returns a signer restricted to the specified algorithms.
// The algorithms must be set in preference order.
// An error is returned if the specified algorithms are incompatible with the
// public key type.
func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (MultiAlgorithmSigner, error) {
if len(algorithms) == 0 {
return nil, errors.New("ssh: please specify at least one valid signing algorithms")
}
supportedAlgos := algorithmsForKeyFormat(signer.PublicKey().Type())
for _, algo := range algorithms {
if !contains(supportedAlgos, algo) {
return nil, fmt.Errorf("ssh: algorithm %s is not supported for key type %s",
algo, signer.PublicKey().Type())
}
}
return &multiAlgorithmSSHCertSigner{
AlgorithmSigner: signer,
supportedAlgorithms: algorithms,
}, nil
}

const sourceAddressCriticalOption = "source-address"

// CertChecker does the work of verifying a certificate. Its methods
Expand Down Expand Up @@ -433,15 +463,26 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {

// SignCert signs the certificate with an authority, setting the Nonce,
// SignatureKey, and Signature fields.
// If the authority implements the MultiAlgorithmSigner interface the
// first algorithm in the list is used. This is useful if you want to sign
// with a specific algorithm.
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
c.Nonce = make([]byte, 32)
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
return err
}
c.SignatureKey = authority.PublicKey()

// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
if v, ok := authority.(MultiAlgorithmSigner); ok {
// use the first algorithm in the list
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), v.Algorithms()[0])
if err != nil {
return err
}
c.Signature = sig
return nil
} else if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA {
// Default to KeyAlgoRSASHA512 for ssh-rsa signers.
sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), KeyAlgoRSASHA512)
if err != nil {
return err
Expand Down
30 changes: 30 additions & 0 deletions ssh/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ func (s *legacyRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error)
}

func TestCertTypes(t *testing.T) {
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
if !ok {
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
}
multiAlgoSigner, err := NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoRSASHA256})
if err != nil {
t.Fatalf("unable to create multi algorithm signer: %v", err)
}
var testVars = []struct {
name string
signer Signer
Expand All @@ -252,6 +260,7 @@ func TestCertTypes(t *testing.T) {
{CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
{CertAlgoED25519v01, testSigners["ed25519"], ""},
{CertAlgoRSAv01, testSigners["rsa"], KeyAlgoRSASHA512},
{CertAlgoRSAv01, multiAlgoSigner, KeyAlgoRSASHA256},
{"legacyRSASigner", &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
{CertAlgoDSAv01, testSigners["dsa"], ""},
}
Expand Down Expand Up @@ -318,3 +327,24 @@ func TestCertTypes(t *testing.T) {
})
}
}

func TestNewSignerWithAlgos(t *testing.T) {
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
if !ok {
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
}
_, err := NewSignerWithAlgorithms(algorithSigner, nil)
if err == nil {
t.Error("signer with algos created with no algorithms")
}

_, err = NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoED25519})
if err == nil {
t.Error("signer with algos created with invalid algorithms")
}

_, err = NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
if err != nil {
t.Errorf("unable to create signer with valid algorithms: %v", err)
}
}
21 changes: 11 additions & 10 deletions ssh/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,20 +461,21 @@ func (t *handshakeTransport) sendKexInit() error {
isServer := len(t.hostKeys) > 0
if isServer {
for _, k := range t.hostKeys {
// If k is an AlgorithmSigner, presume it supports all signature algorithms
// associated with the key format. (Ideally AlgorithmSigner would have a
// method to advertise supported algorithms, but it doesn't. This means that
// adding support for a new algorithm is a breaking change, as we will
// immediately negotiate it even if existing implementations don't support
// it. If that ever happens, we'll have to figure something out.)
// If k is a MultiAlgorithmSigner, we restrict the signature algorithms.
// If k is a AlgorithmSigner, presume it supports all signature algorithms
// associated with the key format.
// If k is not an AlgorithmSigner, we can only assume it only supports the
// algorithms that matches the key format. (This means that Sign can't pick
// a different default.)
keyFormat := k.PublicKey().Type()
if _, ok := k.(AlgorithmSigner); ok {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...)
if signer, ok := k.(MultiAlgorithmSigner); ok {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, signer.Algorithms()...)
} else {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
keyFormat := k.PublicKey().Type()
if _, ok := k.(AlgorithmSigner); ok {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...)
} else {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat)
}
}
}
} else {
Expand Down
Loading

0 comments on commit 130ec71

Please sign in to comment.