go.crypto/ssh: Begin adding server side support for more than RSA for client key auth
R=agl, dave, hanwen CC=ekg, golang-dev https://golang.org/cl/13528044
This commit is contained in:
committed by
Adam Langley
parent
f17d130281
commit
6a743c56c7
+3
-27
@@ -9,7 +9,6 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -248,21 +247,10 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte
|
||||
}
|
||||
|
||||
// Select hash function to match the hostkey algorithm, as per
|
||||
// RFC 4253, section 6.1 (for RSA/DSS) and RFC 5656, section
|
||||
// RFC 4253, section 6.6 (for RSA/DSS) and RFC 5656, section
|
||||
// 6.2.1 (for ECDSA).
|
||||
var hashFunc crypto.Hash
|
||||
switch hostKeyAlgo {
|
||||
case KeyAlgoRSA:
|
||||
hashFunc = crypto.SHA1
|
||||
case KeyAlgoDSA:
|
||||
hashFunc = crypto.SHA1
|
||||
case KeyAlgoECDSA256:
|
||||
hashFunc = crypto.SHA256
|
||||
case KeyAlgoECDSA384:
|
||||
hashFunc = crypto.SHA384
|
||||
case KeyAlgoECDSA521:
|
||||
hashFunc = crypto.SHA512
|
||||
default:
|
||||
hashFunc, ok := hashFuncs[hostKeyAlgo]
|
||||
if !ok {
|
||||
return errors.New("ssh: unknown key algorithm: " + hostKeyAlgo)
|
||||
}
|
||||
|
||||
@@ -281,18 +269,6 @@ func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte
|
||||
return verifySignature(digest, sig, hostKey)
|
||||
}
|
||||
|
||||
func verifySignature(hash []byte, sig *signature, key interface{}) error {
|
||||
switch pubKey := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return verifyRSASignature(hash, sig, pubKey)
|
||||
}
|
||||
return fmt.Errorf("ssh: unknown key type %T", key)
|
||||
}
|
||||
|
||||
func verifyRSASignature(hash []byte, sig *signature, key *rsa.PublicKey) error {
|
||||
return rsa.VerifyPKCS1v15(key, crypto.SHA1, hash, sig.Blob)
|
||||
}
|
||||
|
||||
// kexResult captures the outcome of a key exchange.
|
||||
type kexResult struct {
|
||||
// Session hash. See also RFC 4253, section 8.
|
||||
|
||||
+1
-1
@@ -340,7 +340,7 @@ func handleAuthResponse(t *transport) (bool, []string, error) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// ClientAuthKeyring returns a ClientAuth using public key authentication via
|
||||
// ClientAuthAgent returns a ClientAuth using public key authentication via
|
||||
// an agent.
|
||||
func ClientAuthAgent(agent *AgentClient) ClientAuth {
|
||||
return ClientAuthKeyring(&agentKeyring{agent: agent})
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/rsa"
|
||||
_ "crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
@@ -18,6 +17,8 @@ import (
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "crypto/sha1"
|
||||
)
|
||||
|
||||
// private key for mock server
|
||||
|
||||
+96
-12
@@ -14,6 +14,10 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
@@ -38,6 +42,21 @@ var supportedKexAlgos = []string{
|
||||
var supportedHostKeyAlgos = []string{hostAlgoRSA}
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
||||
// hashes needed for signature verification.
|
||||
var hashFuncs = map[string]crypto.Hash{
|
||||
KeyAlgoRSA: crypto.SHA1,
|
||||
KeyAlgoDSA: crypto.SHA1,
|
||||
KeyAlgoECDSA256: crypto.SHA256,
|
||||
KeyAlgoECDSA384: crypto.SHA384,
|
||||
KeyAlgoECDSA521: crypto.SHA512,
|
||||
CertAlgoRSAv01: crypto.SHA1,
|
||||
CertAlgoDSAv01: crypto.SHA1,
|
||||
CertAlgoECDSA256v01: crypto.SHA256,
|
||||
CertAlgoECDSA384v01: crypto.SHA384,
|
||||
CertAlgoECDSA521v01: crypto.SHA512,
|
||||
}
|
||||
|
||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
||||
type dhGroup struct {
|
||||
g, p *big.Int
|
||||
@@ -222,21 +241,11 @@ func ecHash(curve elliptic.Curve) crypto.Hash {
|
||||
|
||||
// serialize a signed slice according to RFC 4254 6.6.
|
||||
func serializeSignature(algoname string, sig []byte) []byte {
|
||||
switch algoname {
|
||||
// The corresponding private key to a public certificate is always a normal
|
||||
// private key. For signature serialization purposes, ensure we use the
|
||||
// proper key algorithm name in case the public cert algorithm name is passed.
|
||||
case CertAlgoRSAv01:
|
||||
algoname = KeyAlgoRSA
|
||||
case CertAlgoDSAv01:
|
||||
algoname = KeyAlgoDSA
|
||||
case CertAlgoECDSA256v01:
|
||||
algoname = KeyAlgoECDSA256
|
||||
case CertAlgoECDSA384v01:
|
||||
algoname = KeyAlgoECDSA384
|
||||
case CertAlgoECDSA521v01:
|
||||
algoname = KeyAlgoECDSA521
|
||||
}
|
||||
algoname = pubAlgoToPrivAlgo(algoname)
|
||||
|
||||
length := stringLength(len(algoname))
|
||||
length += stringLength(len(sig))
|
||||
|
||||
@@ -247,6 +256,60 @@ func serializeSignature(algoname string, sig []byte) []byte {
|
||||
return ret
|
||||
}
|
||||
|
||||
func verifySignature(hash []byte, sig *signature, key interface{}) error {
|
||||
switch pubKey := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return verifyRSASignature(hash, sig, pubKey)
|
||||
case *dsa.PublicKey:
|
||||
return verifyDSASignature(hash, sig, pubKey)
|
||||
case *ecdsa.PublicKey:
|
||||
return verifyECDSASignature(hash, sig, pubKey)
|
||||
case *OpenSSHCertV01:
|
||||
return verifySignature(hash, sig, pubKey.Key)
|
||||
}
|
||||
return fmt.Errorf("ssh: unknown key type %T", key)
|
||||
}
|
||||
|
||||
func verifyRSASignature(hash []byte, sig *signature, key *rsa.PublicKey) error {
|
||||
return rsa.VerifyPKCS1v15(key, crypto.SHA1, hash, sig.Blob)
|
||||
}
|
||||
|
||||
func verifyDSASignature(hash []byte, sig *signature, key *dsa.PublicKey) error {
|
||||
// Per RFC 4253, section 6.6,
|
||||
// The value for 'dss_signature_blob' is encoded as a string containing
|
||||
// r, followed by s (which are 160-bit integers, without lengths or
|
||||
// padding, unsigned, and in network byte order).
|
||||
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
||||
if len(sig.Blob) != 40 {
|
||||
return fmt.Errorf("ssh: improper dss signature length of %d", len(sig.Blob))
|
||||
}
|
||||
r := new(big.Int).SetBytes(sig.Blob[:20])
|
||||
s := new(big.Int).SetBytes(sig.Blob[20:])
|
||||
if !dsa.Verify(key, hash, r, s) {
|
||||
return errors.New("ssh: unable to verify dsa signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyECDSASignature(hash []byte, sig *signature, key *ecdsa.PublicKey) error {
|
||||
// Per RFC 5656, section 3.1.2,
|
||||
// The ecdsa_signature_blob value has the following specific encoding:
|
||||
// mpint r
|
||||
// mpint s
|
||||
r, rest, ok := parseInt(sig.Blob)
|
||||
if !ok {
|
||||
return errors.New("ssh: ecdsa signature blob parse failed")
|
||||
}
|
||||
s, rest, ok := parseInt(rest)
|
||||
if !ok || len(rest) > 0 {
|
||||
return errors.New("ssh: ecdsa signature blob parse failed")
|
||||
}
|
||||
if !ecdsa.Verify(key, hash, r, s) {
|
||||
return errors.New("ssh: unable to verify ecdsa signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialize a *rsa.PublicKey or *dsa.PublicKey according to RFC 4253 6.6.
|
||||
func serializePublicKey(key interface{}) []byte {
|
||||
var pubKeyBytes []byte
|
||||
@@ -307,6 +370,27 @@ func algoName(key interface{}) string {
|
||||
panic(fmt.Sprintf("unexpected key type %T", key))
|
||||
}
|
||||
|
||||
// pubAlgoToPrivAlgo returns the private key algorithm format name that
|
||||
// corresponds to a given public key algorithm format name. For most
|
||||
// public keys, the private key algorithm name is the same. For some
|
||||
// situations, such as openssh certificates, the private key algorithm and
|
||||
// public key algorithm names differ. This accounts for those situations.
|
||||
func pubAlgoToPrivAlgo(pubAlgo string) string {
|
||||
switch pubAlgo {
|
||||
case CertAlgoRSAv01:
|
||||
return KeyAlgoRSA
|
||||
case CertAlgoDSAv01:
|
||||
return KeyAlgoDSA
|
||||
case CertAlgoECDSA256v01:
|
||||
return KeyAlgoECDSA256
|
||||
case CertAlgoECDSA384v01:
|
||||
return KeyAlgoECDSA384
|
||||
case CertAlgoECDSA521v01:
|
||||
return KeyAlgoECDSA521
|
||||
}
|
||||
return pubAlgo
|
||||
}
|
||||
|
||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||
// posession of a private key. See RFC 4252, section 7.
|
||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||
|
||||
+29
-38
@@ -20,12 +20,14 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
_ "crypto/sha1"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
rsa *rsa.PrivateKey
|
||||
rsa *rsa.PrivateKey
|
||||
|
||||
// rsaSerialized is the serialized form of the public key that
|
||||
// corresponds to the private key held in the rsa field.
|
||||
rsaSerialized []byte
|
||||
|
||||
// Rand provides the source of entropy for key exchange. If Rand is
|
||||
@@ -86,18 +88,6 @@ func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRSASig(in []byte) (sig []byte, ok bool) {
|
||||
algo, in, ok := parseString(in)
|
||||
if !ok || string(algo) != hostAlgoRSA {
|
||||
return nil, false
|
||||
}
|
||||
sig, in, ok = parseString(in)
|
||||
if len(in) > 0 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cachedPubKey contains the results of querying whether a public key is
|
||||
// acceptable for a user. The cache only applies to a single ServerConn.
|
||||
type cachedPubKey struct {
|
||||
@@ -490,7 +480,12 @@ func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexIni
|
||||
}
|
||||
|
||||
func isAcceptableAlgo(algo string) bool {
|
||||
return algo == hostAlgoRSA
|
||||
switch algo {
|
||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// testPubKey returns true if the given public key is acceptable for the user.
|
||||
@@ -607,38 +602,34 @@ userAuthLoop:
|
||||
continue userAuthLoop
|
||||
}
|
||||
} else {
|
||||
sig, payload, ok := parseString(payload)
|
||||
sig, payload, ok := parseSignature(payload)
|
||||
if !ok || len(payload) > 0 {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
if !isAcceptableAlgo(algo) {
|
||||
// Ensure the public key algo and signature algo
|
||||
// are supported. Compare the private key
|
||||
// algorithm name that corresponds to algo with
|
||||
// sig.Format. This is usually the same, but
|
||||
// for certs, the names differ.
|
||||
if !isAcceptableAlgo(algo) || !isAcceptableAlgo(sig.Format) || pubAlgoToPrivAlgo(algo) != sig.Format {
|
||||
break
|
||||
}
|
||||
rsaSig, ok := parseRSASig(sig)
|
||||
signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
|
||||
key, _, ok := parsePubKey(pubKey)
|
||||
if !ok {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
|
||||
switch algo {
|
||||
case hostAlgoRSA:
|
||||
hashFunc := crypto.SHA1
|
||||
h := hashFunc.New()
|
||||
h.Write(signedData)
|
||||
digest := h.Sum(nil)
|
||||
key, _, ok := parsePubKey(pubKey)
|
||||
if !ok {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
rsaKey, ok := key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
if rsa.VerifyPKCS1v15(rsaKey, hashFunc, digest, rsaSig) != nil {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
default:
|
||||
hashFunc, ok := hashFuncs[algo]
|
||||
if !ok {
|
||||
return errors.New("ssh: isAcceptableAlgo incorrect")
|
||||
}
|
||||
h := hashFunc.New()
|
||||
h.Write(signedData)
|
||||
digest := h.Sum(nil)
|
||||
if verifySignature(digest, sig, key) != nil {
|
||||
return ParseError{msgUserAuthRequest}
|
||||
}
|
||||
// TODO(jmpittman): Implement full validation for certificates.
|
||||
s.User = userAuthReq.User
|
||||
if s.testPubKey(userAuthReq.User, algo, pubKey) {
|
||||
break userAuthLoop
|
||||
|
||||
Reference in New Issue
Block a user