Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,17 @@ func (pb *permissionsBuilder) createPermissions(
tlsAddress = fmt.Sprintf("%s:%d", actual.InstanceAddress, mapping.ContainerTlsProxyPort)
}

// Prefer SHA256 fingerprint if available, fall back to legacy fingerprint
hostFingerprint := sshRoute.Host256Fingerprint
if hostFingerprint == "" {
hostFingerprint = sshRoute.HostFingerprint
}

targetConfig = &proxy.TargetConfig{
Address: fmt.Sprintf("%s:%d", address, port),
TLSAddress: tlsAddress,
ServerCertDomainSAN: actual.ActualLRPInstanceKey.InstanceGuid,
HostFingerprint: sshRoute.HostFingerprint,
HostFingerprint: hostFingerprint,
User: sshRoute.User,
Password: sshRoute.Password,
PrivateKey: sshRoute.PrivateKey,
Expand Down
33 changes: 33 additions & 0 deletions src/code.cloudfoundry.org/diego-ssh/cmd/ssh-proxy/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ var _ = Describe("SSH proxy", Serial, func() {
healthCheckAddress string
diegoCredentials string
hostKeyFingerprint string
hostKeySHA256Fingerprint string
expectedGetActualLRPRequest *models.ActualLRPsRequest
actualLRPsResponse *models.ActualLRPsResponse
getDesiredLRPRequest *models.DesiredLRPByProcessGuidRequest
Expand Down Expand Up @@ -110,6 +111,7 @@ var _ = Describe("SSH proxy", Serial, func() {
privateKey, err := ssh.ParsePrivateKey([]byte(hostKeyPem))
Expect(err).NotTo(HaveOccurred())
hostKeyFingerprint = helpers.MD5Fingerprint(privateKey.PublicKey())
hostKeySHA256Fingerprint = helpers.SHA256Fingerprint(privateKey.PublicKey())

address = fmt.Sprintf("127.0.0.1:%d", sshProxyPort)
healthCheckAddress = fmt.Sprintf("127.0.0.1:%d", healthCheckProxyPort)
Expand Down Expand Up @@ -553,6 +555,37 @@ var _ = Describe("SSH proxy", Serial, func() {
Expect(string(output)).To(Equal("hello"))
})

Context("when SHA256 fingerprint is provided", func() {
BeforeEach(func() {
paddedFingerprint := fmt.Sprintf("%s=", hostKeySHA256Fingerprint)

sshRoute, err := json.Marshal(routes.SSHRoute{
ContainerPort: uint32(sshdContainerPort),
PrivateKey: privateKeyPem,
Host256Fingerprint: paddedFingerprint,
})
Expect(err).NotTo(HaveOccurred())

sshRouteMessage := json.RawMessage(sshRoute)
desiredLRPResponse.DesiredLrp.Routes = &models.Routes{
routes.DIEGO_SSH: &sshRouteMessage,
}
})

It("successfully validates using SHA256 fingerprint", func() {
client, err := ssh.Dial("tcp", address, clientConfig)
Expect(err).NotTo(HaveOccurred())

session, err := client.NewSession()
Expect(err).NotTo(HaveOccurred())
output, err := session.Output("echo -n hello")
Expect(err).NotTo(HaveOccurred())
Expect(string(output)).To(Equal("hello"))

client.Close()
})
})

Context("when a tls intermediary is configured", func() {
Context("when ssh-proxy is configured to connect to the intermediary", func() {
BeforeEach(func() {
Expand Down
13 changes: 7 additions & 6 deletions src/code.cloudfoundry.org/diego-ssh/helpers/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@ import (

const MD5_FINGERPRINT_LENGTH = 47
const SHA1_FINGERPRINT_LENGTH = 59
const SHA256_FINGERPRINT_LENGTH = 44 //unpadded base64
const SHA256_FINGERPRINT_LENGTH = 43

func MD5Fingerprint(key ssh.PublicKey) string {
md5sum := md5.Sum(key.Marshal())
return colonize(fmt.Sprintf("% x", md5sum))
}

func SHA256Fingerprint(key ssh.PublicKey) string {
value := ssh.FingerprintSHA256(key)
return strings.TrimPrefix(value, "SHA256:")
}

func SHA1Fingerprint(key ssh.PublicKey) string {
sha1sum := sha1.Sum(key.Marshal())
return colonize(fmt.Sprintf("% x", sha1sum))
}

// SHA256Fingerprint returns the SHA256 fingerprint of a public key as unpadded base64
func SHA256Fingerprint(key ssh.PublicKey) string {
value := ssh.FingerprintSHA256(key)
return strings.TrimPrefix(value, "SHA256:")
}

func colonize(s string) string {
return strings.Replace(s, " ", ":", -1)
}
41 changes: 35 additions & 6 deletions src/code.cloudfoundry.org/diego-ssh/helpers/fingerprint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ HbXzxBM4Ki0l1kaUjDVKjz3fsIq9Pl/lBoKYAmDvkK4xoxcs05ws

ExpectedMD5Fingerprint = `24:2e:53:c3:72:4f:25:b8:72:29:2d:e3:56:63:4b:c8`
ExpectedSHA1Fingerprint = `8b:d1:ce:b8:3a:f0:37:7f:56:9e:33:1a:72:4b:32:5a:bc:9d:3b:49`
ExpectedSHA256Fingerprint = `x+EcRzt7EfVuXTxnFt01lkxabPULguUgpvcpo52/Puc=`
ExpectedSHA256Fingerprint = `x+EcRzt7EfVuXTxnFt01lkxabPULguUgpvcpo52/Puc`
)

var _ = Describe("Fingerprint", func() {
Expand Down Expand Up @@ -86,15 +86,44 @@ var _ = Describe("Fingerprint", func() {

Describe("SHA256 Fingerprint", func() {
BeforeEach(func() {
fingerprint = fmt.Sprintf("%s=", helpers.SHA256Fingerprint(publicKey))
fingerprint = helpers.SHA256Fingerprint(publicKey)
})

It("should have the correct length", func() {
Expect(utf8.RuneCountInString(fingerprint)).To(Equal(helpers.SHA256_FINGERPRINT_LENGTH))
It("should return unpadded base64 (43 characters)", func() {
// Go's ssh.FingerprintSHA256 returns unpadded base64
Expect(utf8.RuneCountInString(fingerprint)).To(Equal(43))
})

It("should match the expected fingerprint", func() {
Expect(fingerprint).To(Equal(ExpectedSHA256Fingerprint))
Context("when comparing with CAPI format", func() {
It("matches CAPI fingerprint when padding is added", func() {
// CAPI uses Base64.strict_encode64 which includes padding
// Ruby sends: "x+EcRzt7EfVuXTxnFt01lkxabPULguUgpvcpo52/Puc="
paddedFingerprint := fmt.Sprintf("%s=", fingerprint)

// This is what CAPI sends (44 chars with padding)
capiFormat := `x+EcRzt7EfVuXTxnFt01lkxabPULguUgpvcpo52/Puc=`
Expect(paddedFingerprint).To(Equal(capiFormat))
Expect(utf8.RuneCountInString(paddedFingerprint)).To(Equal(44))
})

})
})

Describe("Fingerprint comparison", func() {
It("generates different fingerprints with different algorithms", func() {
md5 := helpers.MD5Fingerprint(publicKey)
sha1 := helpers.SHA1Fingerprint(publicKey)
sha256 := helpers.SHA256Fingerprint(publicKey)

Expect(md5).NotTo(Equal(sha1))
Expect(md5).NotTo(Equal(sha256))
Expect(sha1).NotTo(Equal(sha256))
})

It("generates consistent fingerprints for the same key", func() {
fp1 := helpers.SHA256Fingerprint(publicKey)
fp2 := helpers.SHA256Fingerprint(publicKey)
Expect(fp1).To(Equal(fp2))
})
})
})
113 changes: 83 additions & 30 deletions src/code.cloudfoundry.org/diego-ssh/keys/fake_keys/fake_key_pair.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading