| // 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(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)) |
| 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 |
| 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 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 |
| } |