package ssh
import (
"crypto"
"crypto/fips140"
"crypto/rand"
"fmt"
"io"
"math"
"slices"
"sync"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
)
const (
compressionNone = "none"
serviceUserAuth = "ssh-userauth"
serviceSSH = "ssh-connection"
)
const (
CipherAES128GCM = "aes128-gcm@openssh.com"
CipherAES256GCM = "aes256-gcm@openssh.com"
CipherChaCha20Poly1305 = "chacha20-poly1305@openssh.com"
CipherAES128CTR = "aes128-ctr"
CipherAES192CTR = "aes192-ctr"
CipherAES256CTR = "aes256-ctr"
InsecureCipherAES128CBC = "aes128-cbc"
InsecureCipherTripleDESCBC = "3des-cbc"
InsecureCipherRC4 = "arcfour"
InsecureCipherRC4128 = "arcfour128"
InsecureCipherRC4256 = "arcfour256"
)
const (
InsecureKeyExchangeDH1SHA1 = "diffie-hellman-group1-sha1"
InsecureKeyExchangeDH14SHA1 = "diffie-hellman-group14-sha1"
KeyExchangeDH14SHA256 = "diffie-hellman-group14-sha256"
KeyExchangeDH16SHA512 = "diffie-hellman-group16-sha512"
KeyExchangeECDHP256 = "ecdh-sha2-nistp256"
KeyExchangeECDHP384 = "ecdh-sha2-nistp384"
KeyExchangeECDHP521 = "ecdh-sha2-nistp521"
KeyExchangeCurve25519 = "curve25519-sha256"
InsecureKeyExchangeDHGEXSHA1 = "diffie-hellman-group-exchange-sha1"
KeyExchangeDHGEXSHA256 = "diffie-hellman-group-exchange-sha256"
KeyExchangeMLKEM768X25519 = "mlkem768x25519-sha256"
keyExchangeCurve25519LibSSH = "curve25519-sha256@libssh.org"
)
const (
HMACSHA256ETM = "hmac-sha2-256-etm@openssh.com"
HMACSHA512ETM = "hmac-sha2-512-etm@openssh.com"
HMACSHA256 = "hmac-sha2-256"
HMACSHA512 = "hmac-sha2-512"
HMACSHA1 = "hmac-sha1"
InsecureHMACSHA196 = "hmac-sha1-96"
)
var (
supportedKexAlgos = []string {
KeyExchangeMLKEM768X25519 ,
KeyExchangeCurve25519 ,
KeyExchangeECDHP256 ,
KeyExchangeECDHP384 ,
KeyExchangeECDHP521 ,
KeyExchangeDH14SHA256 ,
KeyExchangeDH16SHA512 ,
KeyExchangeDHGEXSHA256 ,
}
defaultKexAlgos = []string {
KeyExchangeMLKEM768X25519 ,
KeyExchangeCurve25519 ,
KeyExchangeECDHP256 ,
KeyExchangeECDHP384 ,
KeyExchangeECDHP521 ,
KeyExchangeDH14SHA256 ,
InsecureKeyExchangeDH14SHA1 ,
}
insecureKexAlgos = []string {
InsecureKeyExchangeDH14SHA1 ,
InsecureKeyExchangeDH1SHA1 ,
InsecureKeyExchangeDHGEXSHA1 ,
}
supportedCiphers = []string {
CipherAES128GCM ,
CipherAES256GCM ,
CipherChaCha20Poly1305 ,
CipherAES128CTR ,
CipherAES192CTR ,
CipherAES256CTR ,
}
defaultCiphers = supportedCiphers
insecureCiphers = []string {
InsecureCipherAES128CBC ,
InsecureCipherTripleDESCBC ,
InsecureCipherRC4256 ,
InsecureCipherRC4128 ,
InsecureCipherRC4 ,
}
supportedMACs = []string {
HMACSHA256ETM ,
HMACSHA512ETM ,
HMACSHA256 ,
HMACSHA512 ,
HMACSHA1 ,
}
defaultMACs = []string {
HMACSHA256ETM ,
HMACSHA512ETM ,
HMACSHA256 ,
HMACSHA512 ,
HMACSHA1 ,
InsecureHMACSHA196 ,
}
insecureMACs = []string {
InsecureHMACSHA196 ,
}
supportedHostKeyAlgos = []string {
CertAlgoRSASHA256v01 ,
CertAlgoRSASHA512v01 ,
CertAlgoECDSA256v01 ,
CertAlgoECDSA384v01 ,
CertAlgoECDSA521v01 ,
CertAlgoED25519v01 ,
KeyAlgoRSASHA256 ,
KeyAlgoRSASHA512 ,
KeyAlgoECDSA256 ,
KeyAlgoECDSA384 ,
KeyAlgoECDSA521 ,
KeyAlgoED25519 ,
}
defaultHostKeyAlgos = []string {
CertAlgoRSASHA256v01 ,
CertAlgoRSASHA512v01 ,
CertAlgoRSAv01 ,
InsecureCertAlgoDSAv01 ,
CertAlgoECDSA256v01 ,
CertAlgoECDSA384v01 ,
CertAlgoECDSA521v01 ,
CertAlgoED25519v01 ,
KeyAlgoECDSA256 ,
KeyAlgoECDSA384 ,
KeyAlgoECDSA521 ,
KeyAlgoRSASHA256 ,
KeyAlgoRSASHA512 ,
KeyAlgoRSA ,
InsecureKeyAlgoDSA ,
KeyAlgoED25519 ,
}
insecureHostKeyAlgos = []string {
KeyAlgoRSA ,
InsecureKeyAlgoDSA ,
CertAlgoRSAv01 ,
InsecureCertAlgoDSAv01 ,
}
supportedPubKeyAuthAlgos = []string {
KeyAlgoED25519 ,
KeyAlgoSKED25519 ,
KeyAlgoSKECDSA256 ,
KeyAlgoECDSA256 ,
KeyAlgoECDSA384 ,
KeyAlgoECDSA521 ,
KeyAlgoRSASHA256 ,
KeyAlgoRSASHA512 ,
}
defaultPubKeyAuthAlgos = []string {
KeyAlgoED25519 ,
KeyAlgoSKED25519 ,
KeyAlgoSKECDSA256 ,
KeyAlgoECDSA256 ,
KeyAlgoECDSA384 ,
KeyAlgoECDSA521 ,
KeyAlgoRSASHA256 ,
KeyAlgoRSASHA512 ,
KeyAlgoRSA ,
InsecureKeyAlgoDSA ,
}
insecurePubKeyAuthAlgos = []string {
KeyAlgoRSA ,
InsecureKeyAlgoDSA ,
}
)
type NegotiatedAlgorithms struct {
KeyExchange string
HostKey string
Read DirectionAlgorithms
Write DirectionAlgorithms
}
type Algorithms struct {
KeyExchanges []string
Ciphers []string
MACs []string
HostKeys []string
PublicKeyAuths []string
}
func init() {
if fips140 .Enabled () {
defaultHostKeyAlgos = slices .DeleteFunc (defaultHostKeyAlgos , func (algo string ) bool {
_ , err := hashFunc (underlyingAlgo (algo ))
return err != nil
})
defaultPubKeyAuthAlgos = slices .DeleteFunc (defaultPubKeyAuthAlgos , func (algo string ) bool {
_ , err := hashFunc (underlyingAlgo (algo ))
return err != nil
})
}
}
func hashFunc(format string ) (crypto .Hash , error ) {
switch format {
case KeyAlgoRSASHA256 , KeyAlgoECDSA256 , KeyAlgoSKED25519 , KeyAlgoSKECDSA256 :
return crypto .SHA256 , nil
case KeyAlgoECDSA384 :
return crypto .SHA384 , nil
case KeyAlgoRSASHA512 , KeyAlgoECDSA521 :
return crypto .SHA512 , nil
case KeyAlgoED25519 :
return 0 , nil
case KeyAlgoRSA , InsecureKeyAlgoDSA :
if fips140 .Enabled () {
return 0 , fmt .Errorf ("ssh: hash algorithm for format %q not allowed in FIPS 140 mode" , format )
}
return crypto .SHA1 , nil
default :
return 0 , fmt .Errorf ("ssh: hash algorithm for format %q not mapped" , format )
}
}
func SupportedAlgorithms () Algorithms {
return Algorithms {
Ciphers : slices .Clone (supportedCiphers ),
MACs : slices .Clone (supportedMACs ),
KeyExchanges : slices .Clone (supportedKexAlgos ),
HostKeys : slices .Clone (supportedHostKeyAlgos ),
PublicKeyAuths : slices .Clone (supportedPubKeyAuthAlgos ),
}
}
func InsecureAlgorithms () Algorithms {
return Algorithms {
KeyExchanges : slices .Clone (insecureKexAlgos ),
Ciphers : slices .Clone (insecureCiphers ),
MACs : slices .Clone (insecureMACs ),
HostKeys : slices .Clone (insecureHostKeyAlgos ),
PublicKeyAuths : slices .Clone (insecurePubKeyAuthAlgos ),
}
}
var supportedCompressions = []string {compressionNone }
func algorithmsForKeyFormat(keyFormat string ) []string {
switch keyFormat {
case KeyAlgoRSA :
return []string {KeyAlgoRSASHA256 , KeyAlgoRSASHA512 , KeyAlgoRSA }
case CertAlgoRSAv01 :
return []string {CertAlgoRSASHA256v01 , CertAlgoRSASHA512v01 , CertAlgoRSAv01 }
default :
return []string {keyFormat }
}
}
func keyFormatForAlgorithm(sigAlgo string ) string {
switch sigAlgo {
case KeyAlgoRSA , KeyAlgoRSASHA256 , KeyAlgoRSASHA512 :
return KeyAlgoRSA
case CertAlgoRSAv01 , CertAlgoRSASHA256v01 , CertAlgoRSASHA512v01 :
return CertAlgoRSAv01
case KeyAlgoED25519 ,
KeyAlgoSKED25519 ,
KeyAlgoSKECDSA256 ,
KeyAlgoECDSA256 ,
KeyAlgoECDSA384 ,
KeyAlgoECDSA521 ,
InsecureKeyAlgoDSA ,
InsecureCertAlgoDSAv01 ,
CertAlgoECDSA256v01 ,
CertAlgoECDSA384v01 ,
CertAlgoECDSA521v01 ,
CertAlgoSKECDSA256v01 ,
CertAlgoED25519v01 ,
CertAlgoSKED25519v01 :
return sigAlgo
default :
return ""
}
}
func isRSA(algo string ) bool {
algos := algorithmsForKeyFormat (KeyAlgoRSA )
return slices .Contains (algos , underlyingAlgo (algo ))
}
func isRSACert(algo string ) bool {
_ , ok := certKeyAlgoNames [algo ]
if !ok {
return false
}
return isRSA (algo )
}
func unexpectedMessageError(expected , got uint8 ) error {
return fmt .Errorf ("ssh: unexpected message type %d (expected %d)" , got , expected )
}
func parseError(tag uint8 ) error {
return fmt .Errorf ("ssh: parse error in message type %d" , tag )
}
func findCommon(what string , client []string , server []string , isClient bool ) (string , error ) {
for _ , c := range client {
for _ , s := range server {
if c == s {
return c , nil
}
}
}
err := &AlgorithmNegotiationError {
What : what ,
}
if isClient {
err .SupportedAlgorithms = client
err .RequestedAlgorithms = server
} else {
err .SupportedAlgorithms = server
err .RequestedAlgorithms = client
}
return "" , err
}
type AlgorithmNegotiationError struct {
What string
RequestedAlgorithms []string
SupportedAlgorithms []string
}
func (a *AlgorithmNegotiationError ) Error () string {
return fmt .Sprintf ("ssh: no common algorithm for %s; we offered: %v, peer offered: %v" ,
a .What , a .SupportedAlgorithms , a .RequestedAlgorithms )
}
type DirectionAlgorithms struct {
Cipher string
MAC string
compression string
}
func (a *DirectionAlgorithms ) rekeyBytes () int64 {
switch a .Cipher {
case CipherAES128CTR , CipherAES192CTR , CipherAES256CTR , CipherAES128GCM , CipherAES256GCM , InsecureCipherAES128CBC :
return 16 * (1 << 32 )
}
return 1 << 30
}
var aeadCiphers = map [string ]bool {
CipherAES128GCM : true ,
CipherAES256GCM : true ,
CipherChaCha20Poly1305 : true ,
}
func findAgreedAlgorithms(isClient bool , clientKexInit , serverKexInit *kexInitMsg ) (algs *NegotiatedAlgorithms , err error ) {
result := &NegotiatedAlgorithms {}
result .KeyExchange , err = findCommon ("key exchange" , clientKexInit .KexAlgos , serverKexInit .KexAlgos , isClient )
if err != nil {
return
}
result .HostKey , err = findCommon ("host key" , clientKexInit .ServerHostKeyAlgos , serverKexInit .ServerHostKeyAlgos , isClient )
if err != nil {
return
}
stoc , ctos := &result .Write , &result .Read
if isClient {
ctos , stoc = stoc , ctos
}
ctos .Cipher , err = findCommon ("client to server cipher" , clientKexInit .CiphersClientServer , serverKexInit .CiphersClientServer , isClient )
if err != nil {
return
}
stoc .Cipher , err = findCommon ("server to client cipher" , clientKexInit .CiphersServerClient , serverKexInit .CiphersServerClient , isClient )
if err != nil {
return
}
if !aeadCiphers [ctos .Cipher ] {
ctos .MAC , err = findCommon ("client to server MAC" , clientKexInit .MACsClientServer , serverKexInit .MACsClientServer , isClient )
if err != nil {
return
}
}
if !aeadCiphers [stoc .Cipher ] {
stoc .MAC , err = findCommon ("server to client MAC" , clientKexInit .MACsServerClient , serverKexInit .MACsServerClient , isClient )
if err != nil {
return
}
}
ctos .compression , err = findCommon ("client to server compression" , clientKexInit .CompressionClientServer , serverKexInit .CompressionClientServer , isClient )
if err != nil {
return
}
stoc .compression , err = findCommon ("server to client compression" , clientKexInit .CompressionServerClient , serverKexInit .CompressionServerClient , isClient )
if err != nil {
return
}
return result , nil
}
const minRekeyThreshold uint64 = 256
type Config struct {
Rand io .Reader
RekeyThreshold uint64
KeyExchanges []string
Ciphers []string
MACs []string
}
func (c *Config ) SetDefaults () {
if c .Rand == nil {
c .Rand = rand .Reader
}
if c .Ciphers == nil {
c .Ciphers = defaultCiphers
}
var ciphers []string
for _ , c := range c .Ciphers {
if cipherModes [c ] != nil {
ciphers = append (ciphers , c )
}
}
c .Ciphers = ciphers
if c .KeyExchanges == nil {
c .KeyExchanges = defaultKexAlgos
}
var kexs []string
for _ , k := range c .KeyExchanges {
if kexAlgoMap [k ] != nil {
kexs = append (kexs , k )
if k == KeyExchangeCurve25519 && !slices .Contains (c .KeyExchanges , keyExchangeCurve25519LibSSH ) {
kexs = append (kexs , keyExchangeCurve25519LibSSH )
}
}
}
c .KeyExchanges = kexs
if c .MACs == nil {
c .MACs = defaultMACs
}
var macs []string
for _ , m := range c .MACs {
if macModes [m ] != nil {
macs = append (macs , m )
}
}
c .MACs = macs
if c .RekeyThreshold == 0 {
} else if c .RekeyThreshold < minRekeyThreshold {
c .RekeyThreshold = minRekeyThreshold
} else if c .RekeyThreshold >= math .MaxInt64 {
c .RekeyThreshold = math .MaxInt64
}
}
func buildDataSignedForAuth(sessionID []byte , req userAuthRequestMsg , algo string , pubKey []byte ) []byte {
data := struct {
Session []byte
Type byte
User string
Service string
Method string
Sign bool
Algo string
PubKey []byte
}{
sessionID ,
msgUserAuthRequest ,
req .User ,
req .Service ,
req .Method ,
true ,
algo ,
pubKey ,
}
return Marshal (data )
}
func appendU16(buf []byte , n uint16 ) []byte {
return append (buf , byte (n >>8 ), byte (n ))
}
func appendU32(buf []byte , n uint32 ) []byte {
return append (buf , byte (n >>24 ), byte (n >>16 ), byte (n >>8 ), byte (n ))
}
func appendU64(buf []byte , n uint64 ) []byte {
return append (buf ,
byte (n >>56 ), byte (n >>48 ), byte (n >>40 ), byte (n >>32 ),
byte (n >>24 ), byte (n >>16 ), byte (n >>8 ), byte (n ))
}
func appendInt(buf []byte , n int ) []byte {
return appendU32 (buf , uint32 (n ))
}
func appendString(buf []byte , s string ) []byte {
buf = appendU32 (buf , uint32 (len (s )))
buf = append (buf , s ...)
return buf
}
func appendBool(buf []byte , b bool ) []byte {
if b {
return append (buf , 1 )
}
return append (buf , 0 )
}
func newCond() *sync .Cond { return sync .NewCond (new (sync .Mutex )) }
type window struct {
*sync .Cond
win uint32
writeWaiters int
closed bool
}
func (w *window ) add (win uint32 ) bool {
if win == 0 {
return true
}
w .L .Lock ()
if w .win +win < win {
w .L .Unlock ()
return false
}
w .win += win
w .Broadcast ()
w .L .Unlock ()
return true
}
func (w *window ) close () {
w .L .Lock ()
w .closed = true
w .Broadcast ()
w .L .Unlock ()
}
func (w *window ) reserve (win uint32 ) (uint32 , error ) {
var err error
w .L .Lock ()
w .writeWaiters ++
w .Broadcast ()
for w .win == 0 && !w .closed {
w .Wait ()
}
w .writeWaiters --
if w .win < win {
win = w .win
}
w .win -= win
if w .closed {
err = io .EOF
}
w .L .Unlock ()
return win , err
}
func (w *window ) waitWriterBlocked () {
w .Cond .L .Lock ()
for w .writeWaiters == 0 {
w .Cond .Wait ()
}
w .Cond .L .Unlock ()
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .