package ssh
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"sort"
"time"
)
const (
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
CertAlgoSKECDSA256v01 = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
CertAlgoSKED25519v01 = "sk-ssh-ed25519-cert-v01@openssh.com"
CertAlgoRSASHA256v01 = "rsa-sha2-256-cert-v01@openssh.com"
CertAlgoRSASHA512v01 = "rsa-sha2-512-cert-v01@openssh.com"
)
const (
CertSigAlgoRSAv01 = CertAlgoRSAv01
CertSigAlgoRSASHA2256v01 = CertAlgoRSASHA256v01
CertSigAlgoRSASHA2512v01 = CertAlgoRSASHA512v01
)
const (
UserCert = 1
HostCert = 2
)
type Signature struct {
Format string
Blob []byte
Rest []byte `ssh:"rest"`
}
const CertTimeInfinity = 1 <<64 - 1
type Certificate struct {
Nonce []byte
Key PublicKey
Serial uint64
CertType uint32
KeyId string
ValidPrincipals []string
ValidAfter uint64
ValidBefore uint64
Permissions
Reserved []byte
SignatureKey PublicKey
Signature *Signature
}
type genericCertData struct {
Serial uint64
CertType uint32
KeyId string
ValidPrincipals []byte
ValidAfter uint64
ValidBefore uint64
CriticalOptions []byte
Extensions []byte
Reserved []byte
SignatureKey []byte
Signature []byte
}
func marshalStringList(namelist []string ) []byte {
var to []byte
for _ , name := range namelist {
s := struct { N string }{name }
to = append (to , Marshal (&s )...)
}
return to
}
type optionsTuple struct {
Key string
Value []byte
}
type optionsTupleValue struct {
Value string
}
func marshalTuples(tups map [string ]string ) []byte {
keys := make ([]string , 0 , len (tups ))
for key := range tups {
keys = append (keys , key )
}
sort .Strings (keys )
var ret []byte
for _ , key := range keys {
s := optionsTuple {Key : key }
if value := tups [key ]; len (value ) > 0 {
s .Value = Marshal (&optionsTupleValue {value })
}
ret = append (ret , Marshal (&s )...)
}
return ret
}
func parseTuples(in []byte ) (map [string ]string , error ) {
tups := map [string ]string {}
var lastKey string
var haveLastKey bool
for len (in ) > 0 {
var key , val , extra []byte
var ok bool
if key , in , ok = parseString (in ); !ok {
return nil , errShortRead
}
keyStr := string (key )
if haveLastKey && keyStr <= lastKey {
return nil , fmt .Errorf ("ssh: certificate options are not in lexical order" )
}
lastKey , haveLastKey = keyStr , true
if val , in , ok = parseString (in ); !ok {
return nil , errShortRead
}
if len (val ) > 0 {
val , extra , ok = parseString (val )
if !ok {
return nil , errShortRead
}
if len (extra ) > 0 {
return nil , fmt .Errorf ("ssh: unexpected trailing data after certificate option value" )
}
tups [keyStr ] = string (val )
} else {
tups [keyStr ] = ""
}
}
return tups , nil
}
func parseCert(in []byte , privAlgo string ) (*Certificate , error ) {
nonce , rest , ok := parseString (in )
if !ok {
return nil , errShortRead
}
key , rest , err := parsePubKey (rest , privAlgo )
if err != nil {
return nil , err
}
var g genericCertData
if err := Unmarshal (rest , &g ); err != nil {
return nil , err
}
c := &Certificate {
Nonce : nonce ,
Key : key ,
Serial : g .Serial ,
CertType : g .CertType ,
KeyId : g .KeyId ,
ValidAfter : g .ValidAfter ,
ValidBefore : g .ValidBefore ,
}
for principals := g .ValidPrincipals ; len (principals ) > 0 ; {
principal , rest , ok := parseString (principals )
if !ok {
return nil , errShortRead
}
c .ValidPrincipals = append (c .ValidPrincipals , string (principal ))
principals = rest
}
c .CriticalOptions , err = parseTuples (g .CriticalOptions )
if err != nil {
return nil , err
}
c .Extensions , err = parseTuples (g .Extensions )
if err != nil {
return nil , err
}
c .Reserved = g .Reserved
k , err := ParsePublicKey (g .SignatureKey )
if err != nil {
return nil , err
}
c .SignatureKey = k
c .Signature , rest , ok = parseSignatureBody (g .Signature )
if !ok || len (rest ) > 0 {
return nil , errors .New ("ssh: signature parse error" )
}
return c , nil
}
type openSSHCertSigner struct {
pub *Certificate
signer Signer
}
type algorithmOpenSSHCertSigner struct {
*openSSHCertSigner
algorithmSigner AlgorithmSigner
}
func NewCertSigner (cert *Certificate , signer Signer ) (Signer , error ) {
if !bytes .Equal (cert .Key .Marshal (), signer .PublicKey ().Marshal ()) {
return nil , errors .New ("ssh: signer and cert have different public key" )
}
switch s := signer .(type ) {
case MultiAlgorithmSigner :
return &multiAlgorithmSigner {
AlgorithmSigner : &algorithmOpenSSHCertSigner {
&openSSHCertSigner {cert , signer }, s },
supportedAlgorithms : s .Algorithms (),
}, nil
case AlgorithmSigner :
return &algorithmOpenSSHCertSigner {
&openSSHCertSigner {cert , signer }, s }, nil
default :
return &openSSHCertSigner {cert , signer }, nil
}
}
func (s *openSSHCertSigner ) Sign (rand io .Reader , data []byte ) (*Signature , error ) {
return s .signer .Sign (rand , data )
}
func (s *openSSHCertSigner ) PublicKey () PublicKey {
return s .pub
}
func (s *algorithmOpenSSHCertSigner ) SignWithAlgorithm (rand io .Reader , data []byte , algorithm string ) (*Signature , error ) {
return s .algorithmSigner .SignWithAlgorithm (rand , data , algorithm )
}
const sourceAddressCriticalOption = "source-address"
type CertChecker struct {
SupportedCriticalOptions []string
IsUserAuthority func (auth PublicKey ) bool
IsHostAuthority func (auth PublicKey , address string ) bool
Clock func () time .Time
UserKeyFallback func (conn ConnMetadata , key PublicKey ) (*Permissions , error )
HostKeyFallback HostKeyCallback
IsRevoked func (cert *Certificate ) bool
}
func (c *CertChecker ) CheckHostKey (addr string , remote net .Addr , key PublicKey ) error {
cert , ok := key .(*Certificate )
if !ok {
if c .HostKeyFallback != nil {
return c .HostKeyFallback (addr , remote , key )
}
return errors .New ("ssh: non-certificate host key" )
}
if cert .CertType != HostCert {
return fmt .Errorf ("ssh: certificate presented as a host key has type %d" , cert .CertType )
}
if !c .IsHostAuthority (cert .SignatureKey , addr ) {
return fmt .Errorf ("ssh: no authorities for hostname: %v" , addr )
}
hostname , _ , err := net .SplitHostPort (addr )
if err != nil {
return err
}
return c .CheckCert (hostname , cert )
}
func (c *CertChecker ) Authenticate (conn ConnMetadata , pubKey PublicKey ) (*Permissions , error ) {
cert , ok := pubKey .(*Certificate )
if !ok {
if c .UserKeyFallback != nil {
return c .UserKeyFallback (conn , pubKey )
}
return nil , errors .New ("ssh: normal key pairs not accepted" )
}
if cert .CertType != UserCert {
return nil , fmt .Errorf ("ssh: cert has type %d" , cert .CertType )
}
if !c .IsUserAuthority (cert .SignatureKey ) {
return nil , fmt .Errorf ("ssh: certificate signed by unrecognized authority" )
}
if err := c .CheckCert (conn .User (), cert ); err != nil {
return nil , err
}
return &cert .Permissions , nil
}
func (c *CertChecker ) CheckCert (principal string , cert *Certificate ) error {
if c .IsRevoked != nil && c .IsRevoked (cert ) {
return fmt .Errorf ("ssh: certificate serial %d revoked" , cert .Serial )
}
for opt := range cert .CriticalOptions {
if opt == sourceAddressCriticalOption {
continue
}
found := false
for _ , supp := range c .SupportedCriticalOptions {
if supp == opt {
found = true
break
}
}
if !found {
return fmt .Errorf ("ssh: unsupported critical option %q in certificate" , opt )
}
}
if len (cert .ValidPrincipals ) > 0 {
found := false
for _ , p := range cert .ValidPrincipals {
if p == principal {
found = true
break
}
}
if !found {
return fmt .Errorf ("ssh: principal %q not in the set of valid principals for given certificate: %q" , principal , cert .ValidPrincipals )
}
}
clock := c .Clock
if clock == nil {
clock = time .Now
}
unixNow := clock ().Unix ()
if after := int64 (cert .ValidAfter ); after < 0 || unixNow < int64 (cert .ValidAfter ) {
return fmt .Errorf ("ssh: cert is not yet valid" )
}
if before := int64 (cert .ValidBefore ); cert .ValidBefore != uint64 (CertTimeInfinity ) && (unixNow >= before || before < 0 ) {
return fmt .Errorf ("ssh: cert has expired" )
}
if err := cert .SignatureKey .Verify (cert .bytesForSigning (), cert .Signature ); err != nil {
return fmt .Errorf ("ssh: certificate signature does not verify" )
}
return nil
}
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 ()
if v , ok := authority .(MultiAlgorithmSigner ); ok {
if len (v .Algorithms ()) == 0 {
return errors .New ("the provided authority has no signature algorithm" )
}
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 {
sig , err := v .SignWithAlgorithm (rand , c .bytesForSigning (), KeyAlgoRSASHA512 )
if err != nil {
return err
}
c .Signature = sig
return nil
}
sig , err := authority .Sign (rand , c .bytesForSigning ())
if err != nil {
return err
}
c .Signature = sig
return nil
}
var certKeyAlgoNames = map [string ]string {
CertAlgoRSAv01 : KeyAlgoRSA ,
CertAlgoRSASHA256v01 : KeyAlgoRSASHA256 ,
CertAlgoRSASHA512v01 : KeyAlgoRSASHA512 ,
CertAlgoDSAv01 : KeyAlgoDSA ,
CertAlgoECDSA256v01 : KeyAlgoECDSA256 ,
CertAlgoECDSA384v01 : KeyAlgoECDSA384 ,
CertAlgoECDSA521v01 : KeyAlgoECDSA521 ,
CertAlgoSKECDSA256v01 : KeyAlgoSKECDSA256 ,
CertAlgoED25519v01 : KeyAlgoED25519 ,
CertAlgoSKED25519v01 : KeyAlgoSKED25519 ,
}
func underlyingAlgo(algo string ) string {
if a , ok := certKeyAlgoNames [algo ]; ok {
return a
}
return algo
}
func certificateAlgo(algo string ) (certAlgo string , ok bool ) {
for certName , algoName := range certKeyAlgoNames {
if algoName == algo {
return certName , true
}
}
return "" , false
}
func (cert *Certificate ) bytesForSigning () []byte {
c2 := *cert
c2 .Signature = nil
out := c2 .Marshal ()
return out [:len (out )-4 ]
}
func (c *Certificate ) Marshal () []byte {
generic := genericCertData {
Serial : c .Serial ,
CertType : c .CertType ,
KeyId : c .KeyId ,
ValidPrincipals : marshalStringList (c .ValidPrincipals ),
ValidAfter : uint64 (c .ValidAfter ),
ValidBefore : uint64 (c .ValidBefore ),
CriticalOptions : marshalTuples (c .CriticalOptions ),
Extensions : marshalTuples (c .Extensions ),
Reserved : c .Reserved ,
SignatureKey : c .SignatureKey .Marshal (),
}
if c .Signature != nil {
generic .Signature = Marshal (c .Signature )
}
genericBytes := Marshal (&generic )
keyBytes := c .Key .Marshal ()
_, keyBytes , _ = parseString (keyBytes )
prefix := Marshal (&struct {
Name string
Nonce []byte
Key []byte `ssh:"rest"`
}{c .Type (), c .Nonce , keyBytes })
result := make ([]byte , 0 , len (prefix )+len (genericBytes ))
result = append (result , prefix ...)
result = append (result , genericBytes ...)
return result
}
func (c *Certificate ) Type () string {
certName , ok := certificateAlgo (c .Key .Type ())
if !ok {
panic ("unknown certificate type for key type " + c .Key .Type ())
}
return certName
}
func (c *Certificate ) Verify (data []byte , sig *Signature ) error {
return c .Key .Verify (data , sig )
}
func parseSignatureBody(in []byte ) (out *Signature , rest []byte , ok bool ) {
format , in , ok := parseString (in )
if !ok {
return
}
out = &Signature {
Format : string (format ),
}
if out .Blob , in , ok = parseString (in ); !ok {
return
}
switch out .Format {
case KeyAlgoSKECDSA256 , CertAlgoSKECDSA256v01 , KeyAlgoSKED25519 , CertAlgoSKED25519v01 :
out .Rest = in
return out , nil , ok
}
return out , in , ok
}
func parseSignature(in []byte ) (out *Signature , rest []byte , ok bool ) {
sigBytes , rest , ok := parseString (in )
if !ok {
return
}
out , trailing , ok := parseSignatureBody (sigBytes )
if !ok || len (trailing ) > 0 {
return nil , nil , false
}
return
}
The pages are generated with Golds v0.6.7 . (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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds .