Skip to content

Instantly share code, notes, and snippets.

@SwampDragons
Last active June 26, 2020 22:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SwampDragons/8b913208add452f1b50f6f47426ac329 to your computer and use it in GitHub Desktop.
Save SwampDragons/8b913208add452f1b50f6f47426ac329 to your computer and use it in GitHub Desktop.

Steps to Reproduce -- assumes AWS account, but principles apply wherever you try to connect.

Create an AWS instance

If you want to test this against an actual instance, you can do so via AWS:

From the console, select and launch a RHEL 8.2 AMI released by RHEL; RHEL's amazon ID is 309956199498.

A valid ami choice in region us-west-2 is ami-02f147dfb8be58a10

Launch this instance with an AWS-created RSA keys pair.

Copy the instance IP into the instanceIP var on line 12 of rsa_sha2_repro.go. Don't forget to set the port.

Call go run ssh_repro.go. It should connect successfully.

Lock down the instance to make it reject ssh-rsa sha1 algorithms, but still accept the more secure algorithms for ssh-rsa keys:

Manually SSH onto the instance.

Once on the instance, run sudo update-crypto-policies --set FIPS and then sudo reboot to restart the instance.

Now that the policy has been updated to remove the ssh-rsa algorithm, you won't be able to connect via the below code.

Some more details about the code:

When UseAlgoSigner = false, you will be able to connect normally when the instance has not had the crypto policies updated.

However, if you set UseAlgoSigner = true, you won't be able to connect. This is becasue the publicKeyCallback in ssh/client_auth.go incorrectly assigns the algorithm as SigAlgoRSA ("ssh-rsa") instead of SigAlgoRSASHA2256 ("rsa-sha2-256"). This is the key type, but not the algorithm name in this example.

Even if we fix the publicKeyCallback, validateKey will fail for the same reason.

package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"golang.org/x/crypto/ssh"
)
var instanceIP = "" // example "18.237.11.10:22"
var privateKeyPath = "/path/to/rsa/key.pem" // AWS-generated ssh-rsa key. Instance is launched with public half of the keypair already on it.
var UseAlgoSigner = false
func ReadSSHPrivateKeyFile(keyPath string) ([]byte, error) {
var privateKey []byte
privateKey, err := ioutil.ReadFile(keyPath)
if err != nil {
return privateKey, fmt.Errorf("Error on reading SSH private key: %s", err)
}
return privateKey, nil
}
// Adapted from https://github.com/golang/go/issues/36261#issuecomment-573449605
// This implements the crypto AlgorithmSigner interface, allowing us to
// forcibly overwrite the rsa algorithm being used to sign the key.
type sshAlgorithmSigner struct {
algorithm string
signer ssh.AlgorithmSigner
}
func (s *sshAlgorithmSigner) PublicKey() ssh.PublicKey {
return s.signer.PublicKey()
}
func (s *sshAlgorithmSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
return s.signer.SignWithAlgorithm(rand, data, s.algorithm)
}
func NewAlgorithmSignerFromSigner(signer ssh.Signer, algorithm string) (ssh.Signer, error) {
algorithmSigner, ok := signer.(ssh.AlgorithmSigner)
if !ok {
return nil, fmt.Errorf("unable to cast to ssh.AlgorithmSigner")
}
s := sshAlgorithmSigner{
signer: algorithmSigner,
algorithm: algorithm,
}
return &s, nil
}
func createSSHConfig() (*ssh.ClientConfig, error) {
// Load key from file
privateKey, err := ioutil.ReadFile(privateKeyPath)
if err != nil {
return nil, fmt.Errorf("Error on reading SSH private key: %s", err)
}
// Get basic signer.
signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("Error on parsing SSH private key: %s", err)
}
// This is a workaround, because the default signer returned will always
// sign using the insecure KeyAlgoRSA, which openssh now rejects. The
// custom signer forces signing using SigAlgoRSASHA2256 instead.
sshAlgoSigner, err := NewAlgorithmSignerFromSigner(signer, ssh.SigAlgoRSASHA2256)
if err != nil {
return nil, err
}
// Create ssh client config
sshConfig := &ssh.ClientConfig{
User: "ec2-user",
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Use PublicKeys method to get an Auth callback
if UseAlgoSigner {
// This tells the auth mechanism to use the custom ssh-rsa sha2 algo signer.
sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(sshAlgoSigner))
} else {
sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
}
return sshConfig, err
}
func main() {
sshConfig, err := createSSHConfig()
if err != nil {
log.Printf("error creating config: %s", err)
}
log.Printf("Dialing using ssh.Dial")
client, err := ssh.Dial("tcp", instanceIP, sshConfig)
if err != nil {
log.Printf("error creating session: %s", err)
return
}
log.Printf("creating session")
session, err := client.NewSession()
if err != nil {
client.Close()
log.Printf("error creating session %s", err)
}
if session != nil {
log.Printf("Success! Created a new SSH session!")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment