|
|
@@ -0,0 +1,306 @@
|
|
|
+// 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
|
|
|
+
|
|
|
+// References
|
|
|
+// [PROTOCOL.certkeys]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/dsa"
|
|
|
+ "crypto/rsa"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// String constants in [PROTOCOL.certkeys] for certificate algorithm names.
|
|
|
+const (
|
|
|
+ hostAlgoRSACertV01 = "ssh-rsa-cert-v01@openssh.com"
|
|
|
+ hostAlgoDSACertV01 = "ssh-dss-cert-v01@openssh.com"
|
|
|
+ hostAlgoECDSA256CertV01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
|
|
+ hostAlgoECDSA384CertV01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
|
|
+ hostAlgoECDSA521CertV01 = "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
|
|
|
+}
|
|
|
+
|
|
|
+// An OpenSSHCertV01 represents an OpenSSH certificate as defined in
|
|
|
+// [PROTOCOL.certkeys] rev 1.8. Supported formats include
|
|
|
+// ssh-rsa-cert-v01@openssh.com and ssh-dss-cert-v01@openssh.com.
|
|
|
+type OpenSSHCertV01 struct {
|
|
|
+ Nonce []byte
|
|
|
+ Key interface{} // rsa or dsa *PublicKey
|
|
|
+ Serial uint64
|
|
|
+ Type uint32
|
|
|
+ KeyId string
|
|
|
+ ValidPrincipals []string
|
|
|
+ ValidAfter, ValidBefore time.Time
|
|
|
+ CriticalOptions []tuple
|
|
|
+ Extensions []tuple
|
|
|
+ Reserved []byte
|
|
|
+ SignatureKey interface{} // rsa, dsa, or ecdsa *PublicKey
|
|
|
+ Signature *signature
|
|
|
+}
|
|
|
+
|
|
|
+func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) {
|
|
|
+ cert := new(OpenSSHCertV01)
|
|
|
+
|
|
|
+ if cert.Nonce, in, ok = parseString(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ switch algo {
|
|
|
+ case hostAlgoRSACertV01:
|
|
|
+ var rsaPubKey *rsa.PublicKey
|
|
|
+ if rsaPubKey, in, ok = parseRSA(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ cert.Key = rsaPubKey
|
|
|
+ case hostAlgoDSACertV01:
|
|
|
+ var dsaPubKey *dsa.PublicKey
|
|
|
+ if dsaPubKey, in, ok = parseDSA(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ cert.Key = dsaPubKey
|
|
|
+ default:
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if cert.Serial, in, ok = parseUint64(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if cert.Type, in, ok = parseUint32(in); !ok || cert.Type != UserCert && cert.Type != HostCert {
|
|
|
+ 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 = time.Unix(int64(va), 0)
|
|
|
+
|
|
|
+ vb, in, ok := parseUint64(in)
|
|
|
+ if !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ cert.ValidBefore = time.Unix(int64(vb), 0)
|
|
|
+
|
|
|
+ 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 = parsePubKey(sigKey); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if cert.Signature, in, ok = parseSignature(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ ok = true
|
|
|
+ return cert, in, ok
|
|
|
+}
|
|
|
+
|
|
|
+func marshalOpenSSHCertV01(cert *OpenSSHCertV01) []byte {
|
|
|
+ var pubKey []byte
|
|
|
+ switch cert.Key.(type) {
|
|
|
+ case *rsa.PublicKey:
|
|
|
+ k := cert.Key.(*rsa.PublicKey)
|
|
|
+ pubKey = marshalPubRSA(k)
|
|
|
+ case *dsa.PublicKey:
|
|
|
+ k := cert.Key.(*dsa.PublicKey)
|
|
|
+ pubKey = marshalPubDSA(k)
|
|
|
+ default:
|
|
|
+ panic("ssh: unknown public key type in cert")
|
|
|
+ }
|
|
|
+
|
|
|
+ sigKey := serializePublickey(cert.SignatureKey)
|
|
|
+
|
|
|
+ length := stringLength(cert.Nonce)
|
|
|
+ length += len(pubKey)
|
|
|
+ length += 8 // Length of Serial
|
|
|
+ length += 4 // Length of Type
|
|
|
+ length += stringLength([]byte(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(cert.Reserved)
|
|
|
+ length += stringLength(sigKey)
|
|
|
+ length += signatureLength(cert.Signature)
|
|
|
+
|
|
|
+ ret := make([]byte, length)
|
|
|
+ r := marshalString(ret, 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.Unix()))
|
|
|
+ r = marshalUint64(r, uint64(cert.ValidBefore.Unix()))
|
|
|
+ r = marshalTupleList(r, cert.CriticalOptions)
|
|
|
+ r = marshalTupleList(r, cert.Extensions)
|
|
|
+ r = marshalString(r, cert.Reserved)
|
|
|
+ r = marshalString(r, sigKey)
|
|
|
+ r = marshalSignature(r, cert.Signature)
|
|
|
+ if len(r) > 0 {
|
|
|
+ panic("internal error")
|
|
|
+ }
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+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
|
|
|
+ var ok bool
|
|
|
+ next, list, ok = parseString(list)
|
|
|
+ if !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([]byte(sig.Format))
|
|
|
+ length += stringLength(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 parseSignature(in []byte) (out *signature, rest []byte, ok bool) {
|
|
|
+ var sigBytes, format []byte
|
|
|
+ sig := new(signature)
|
|
|
+
|
|
|
+ if sigBytes, rest, ok = parseString(in); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if format, sigBytes, ok = parseString(sigBytes); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sig.Format = string(format)
|
|
|
+
|
|
|
+ if sig.Blob, sigBytes, ok = parseString(sigBytes); !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ return sig, rest, ok
|
|
|
+}
|