378 lines
8.9 KiB
Go
378 lines
8.9 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
|
// for certificate types supported by this package.
|
|
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"
|
|
)
|
|
|
|
// Certificate types are used to specify whether a certificate is for identification
|
|
// of a user or a host. Current identities are defined in [PROTOCOL.certkeys].
|
|
const (
|
|
UserCert = 1
|
|
HostCert = 2
|
|
)
|
|
|
|
type signature struct {
|
|
Format string
|
|
Blob []byte
|
|
}
|
|
|
|
type tuple struct {
|
|
Name string
|
|
Data string
|
|
}
|
|
|
|
const (
|
|
maxUint64 = 1<<64 - 1
|
|
maxInt64 = 1<<63 - 1
|
|
)
|
|
|
|
// CertTime represents an unsigned 64-bit time value in seconds starting from
|
|
// UNIX epoch. We use CertTime instead of time.Time in order to properly handle
|
|
// the "infinite" time value ^0, which would become negative when expressed as
|
|
// an int64.
|
|
type CertTime uint64
|
|
|
|
func (ct CertTime) Time() time.Time {
|
|
if ct > maxInt64 {
|
|
return time.Unix(maxInt64, 0)
|
|
}
|
|
return time.Unix(int64(ct), 0)
|
|
}
|
|
|
|
func (ct CertTime) IsInfinite() bool {
|
|
return ct == maxUint64
|
|
}
|
|
|
|
// An OpenSSHCertV01 represents an OpenSSH certificate as defined in
|
|
// [PROTOCOL.certkeys]?rev=1.8.
|
|
type OpenSSHCertV01 struct {
|
|
Nonce []byte
|
|
Key PublicKey
|
|
Serial uint64
|
|
Type uint32
|
|
KeyId string
|
|
ValidPrincipals []string
|
|
ValidAfter, ValidBefore CertTime
|
|
CriticalOptions []tuple
|
|
Extensions []tuple
|
|
Reserved []byte
|
|
SignatureKey PublicKey
|
|
Signature *signature
|
|
}
|
|
|
|
// validateOpenSSHCertV01Signature uses the cert's SignatureKey to verify that
|
|
// the cert's Signature.Blob is the result of signing the cert bytes starting
|
|
// from the algorithm string and going up to and including the SignatureKey.
|
|
func validateOpenSSHCertV01Signature(cert *OpenSSHCertV01) bool {
|
|
return cert.SignatureKey.Verify(cert.BytesForSigning(), cert.Signature.Blob)
|
|
}
|
|
|
|
var certAlgoNames = map[string]string{
|
|
KeyAlgoRSA: CertAlgoRSAv01,
|
|
KeyAlgoDSA: CertAlgoDSAv01,
|
|
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
|
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
|
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
|
}
|
|
|
|
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
|
// Panics if a non-certificate algorithm is passed.
|
|
func certToPrivAlgo(algo string) string {
|
|
for privAlgo, pubAlgo := range certAlgoNames {
|
|
if pubAlgo == algo {
|
|
return privAlgo
|
|
}
|
|
}
|
|
panic("unknown cert algorithm")
|
|
}
|
|
|
|
func (cert *OpenSSHCertV01) marshal(includeAlgo, includeSig bool) []byte {
|
|
algoName := cert.PublicKeyAlgo()
|
|
pubKey := cert.Key.Marshal()
|
|
sigKey := MarshalPublicKey(cert.SignatureKey)
|
|
|
|
var length int
|
|
if includeAlgo {
|
|
length += stringLength(len(algoName))
|
|
}
|
|
length += stringLength(len(cert.Nonce))
|
|
length += len(pubKey)
|
|
length += 8 // Length of Serial
|
|
length += 4 // Length of Type
|
|
length += stringLength(len(cert.KeyId))
|
|
length += lengthPrefixedNameListLength(cert.ValidPrincipals)
|
|
length += 8 // Length of ValidAfter
|
|
length += 8 // Length of ValidBefore
|
|
length += tupleListLength(cert.CriticalOptions)
|
|
length += tupleListLength(cert.Extensions)
|
|
length += stringLength(len(cert.Reserved))
|
|
length += stringLength(len(sigKey))
|
|
if includeSig {
|
|
length += signatureLength(cert.Signature)
|
|
}
|
|
|
|
ret := make([]byte, length)
|
|
r := ret
|
|
if includeAlgo {
|
|
r = marshalString(r, []byte(algoName))
|
|
}
|
|
r = marshalString(r, cert.Nonce)
|
|
copy(r, pubKey)
|
|
r = r[len(pubKey):]
|
|
r = marshalUint64(r, cert.Serial)
|
|
r = marshalUint32(r, cert.Type)
|
|
r = marshalString(r, []byte(cert.KeyId))
|
|
r = marshalLengthPrefixedNameList(r, cert.ValidPrincipals)
|
|
r = marshalUint64(r, uint64(cert.ValidAfter))
|
|
r = marshalUint64(r, uint64(cert.ValidBefore))
|
|
r = marshalTupleList(r, cert.CriticalOptions)
|
|
r = marshalTupleList(r, cert.Extensions)
|
|
r = marshalString(r, cert.Reserved)
|
|
r = marshalString(r, sigKey)
|
|
if includeSig {
|
|
r = marshalSignature(r, cert.Signature)
|
|
}
|
|
if len(r) > 0 {
|
|
panic("ssh: internal error, marshaling certificate did not fill the entire buffer")
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (cert *OpenSSHCertV01) BytesForSigning() []byte {
|
|
return cert.marshal(true, false)
|
|
}
|
|
|
|
func (cert *OpenSSHCertV01) Marshal() []byte {
|
|
return cert.marshal(false, true)
|
|
}
|
|
|
|
func (c *OpenSSHCertV01) PublicKeyAlgo() string {
|
|
algo, ok := certAlgoNames[c.Key.PublicKeyAlgo()]
|
|
if !ok {
|
|
panic("unknown cert key type")
|
|
}
|
|
return algo
|
|
}
|
|
|
|
func (c *OpenSSHCertV01) PrivateKeyAlgo() string {
|
|
return c.Key.PrivateKeyAlgo()
|
|
}
|
|
|
|
func (c *OpenSSHCertV01) Verify(data []byte, sig []byte) bool {
|
|
return c.Key.Verify(data, sig)
|
|
}
|
|
|
|
func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) {
|
|
cert := new(OpenSSHCertV01)
|
|
|
|
if cert.Nonce, in, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
privAlgo := certToPrivAlgo(algo)
|
|
cert.Key, in, ok = parsePubKey(in, privAlgo)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// We test PublicKeyAlgo to make sure we don't use some weird sub-cert.
|
|
if cert.Key.PublicKeyAlgo() != privAlgo {
|
|
ok = false
|
|
return
|
|
}
|
|
|
|
if cert.Serial, in, ok = parseUint64(in); !ok {
|
|
return
|
|
}
|
|
|
|
if cert.Type, in, ok = parseUint32(in); !ok {
|
|
return
|
|
}
|
|
|
|
keyId, in, ok := parseString(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
cert.KeyId = string(keyId)
|
|
|
|
if cert.ValidPrincipals, in, ok = parseLengthPrefixedNameList(in); !ok {
|
|
return
|
|
}
|
|
|
|
va, in, ok := parseUint64(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
cert.ValidAfter = CertTime(va)
|
|
|
|
vb, in, ok := parseUint64(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
cert.ValidBefore = CertTime(vb)
|
|
|
|
if cert.CriticalOptions, in, ok = parseTupleList(in); !ok {
|
|
return
|
|
}
|
|
|
|
if cert.Extensions, in, ok = parseTupleList(in); !ok {
|
|
return
|
|
}
|
|
|
|
if cert.Reserved, in, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
sigKey, in, ok := parseString(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
if cert.SignatureKey, _, ok = ParsePublicKey(sigKey); !ok {
|
|
return
|
|
}
|
|
|
|
if cert.Signature, in, ok = parseSignature(in); !ok {
|
|
return
|
|
}
|
|
|
|
ok = true
|
|
return cert, in, ok
|
|
}
|
|
|
|
func lengthPrefixedNameListLength(namelist []string) int {
|
|
length := 4 // length prefix for list
|
|
for _, name := range namelist {
|
|
length += 4 // length prefix for name
|
|
length += len(name)
|
|
}
|
|
return length
|
|
}
|
|
|
|
func marshalLengthPrefixedNameList(to []byte, namelist []string) []byte {
|
|
length := uint32(lengthPrefixedNameListLength(namelist) - 4)
|
|
to = marshalUint32(to, length)
|
|
for _, name := range namelist {
|
|
to = marshalString(to, []byte(name))
|
|
}
|
|
return to
|
|
}
|
|
|
|
func parseLengthPrefixedNameList(in []byte) (out []string, rest []byte, ok bool) {
|
|
list, rest, ok := parseString(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
for len(list) > 0 {
|
|
var next []byte
|
|
if next, list, ok = parseString(list); !ok {
|
|
return nil, nil, false
|
|
}
|
|
out = append(out, string(next))
|
|
}
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func tupleListLength(tupleList []tuple) int {
|
|
length := 4 // length prefix for list
|
|
for _, t := range tupleList {
|
|
length += 4 // length prefix for t.Name
|
|
length += len(t.Name)
|
|
length += 4 // length prefix for t.Data
|
|
length += len(t.Data)
|
|
}
|
|
return length
|
|
}
|
|
|
|
func marshalTupleList(to []byte, tuplelist []tuple) []byte {
|
|
length := uint32(tupleListLength(tuplelist) - 4)
|
|
to = marshalUint32(to, length)
|
|
for _, t := range tuplelist {
|
|
to = marshalString(to, []byte(t.Name))
|
|
to = marshalString(to, []byte(t.Data))
|
|
}
|
|
return to
|
|
}
|
|
|
|
func parseTupleList(in []byte) (out []tuple, rest []byte, ok bool) {
|
|
list, rest, ok := parseString(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
for len(list) > 0 {
|
|
var name, data []byte
|
|
var ok bool
|
|
name, list, ok = parseString(list)
|
|
if !ok {
|
|
return nil, nil, false
|
|
}
|
|
data, list, ok = parseString(list)
|
|
if !ok {
|
|
return nil, nil, false
|
|
}
|
|
out = append(out, tuple{string(name), string(data)})
|
|
}
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func signatureLength(sig *signature) int {
|
|
length := 4 // length prefix for signature
|
|
length += stringLength(len(sig.Format))
|
|
length += stringLength(len(sig.Blob))
|
|
return length
|
|
}
|
|
|
|
func marshalSignature(to []byte, sig *signature) []byte {
|
|
length := uint32(signatureLength(sig) - 4)
|
|
to = marshalUint32(to, length)
|
|
to = marshalString(to, []byte(sig.Format))
|
|
to = marshalString(to, sig.Blob)
|
|
return to
|
|
}
|
|
|
|
func parseSignatureBody(in []byte) (out *signature, rest []byte, ok bool) {
|
|
var format []byte
|
|
if format, in, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
out = &signature{
|
|
Format: string(format),
|
|
}
|
|
|
|
if out.Blob, in, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
return out, in, ok
|
|
}
|
|
|
|
func parseSignature(in []byte) (out *signature, rest []byte, ok bool) {
|
|
var sigBytes []byte
|
|
if sigBytes, rest, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
out, sigBytes, ok = parseSignatureBody(sigBytes)
|
|
if !ok || len(sigBytes) > 0 {
|
|
return nil, nil, false
|
|
}
|
|
return
|
|
}
|