forked from TrueCloudLab/certificates
105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
|
package sshutil
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"encoding/binary"
|
||
|
"encoding/json"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
"github.com/smallstep/cli/crypto/randutil"
|
||
|
"golang.org/x/crypto/ssh"
|
||
|
)
|
||
|
|
||
|
// Certificate is the json representation of ssh.Certificate.
|
||
|
type Certificate struct {
|
||
|
Nonce []byte `json:"nonce"`
|
||
|
Key ssh.PublicKey `json:"-"`
|
||
|
Serial uint64 `json:"serial"`
|
||
|
Type CertType `json:"type"`
|
||
|
KeyID string `json:"keyId"`
|
||
|
Principals []string `json:"principals"`
|
||
|
ValidAfter uint64 `json:"-"`
|
||
|
ValidBefore uint64 `json:"-"`
|
||
|
CriticalOptions map[string]string `json:"criticalOptions"`
|
||
|
Extensions map[string]string `json:"extensions"`
|
||
|
Reserved []byte `json:"reserved"`
|
||
|
SignatureKey ssh.PublicKey `json:"-"`
|
||
|
Signature *ssh.Signature `json:"-"`
|
||
|
}
|
||
|
|
||
|
// NewCertificate creates a new certificate with the given key after parsing a
|
||
|
// template given in the options.
|
||
|
func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) {
|
||
|
o, err := new(Options).apply(key, opts)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if o.CertBuffer == nil {
|
||
|
return nil, errors.New("certificate template cannot be empty")
|
||
|
}
|
||
|
|
||
|
// With templates
|
||
|
var cert Certificate
|
||
|
if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil {
|
||
|
return nil, errors.Wrap(err, "error unmarshaling certificate")
|
||
|
}
|
||
|
|
||
|
// Complete with public key
|
||
|
cert.Key = key
|
||
|
|
||
|
return &cert, nil
|
||
|
}
|
||
|
|
||
|
func (c *Certificate) GetCertificate() *ssh.Certificate {
|
||
|
return &ssh.Certificate{
|
||
|
Nonce: c.Nonce,
|
||
|
Key: c.Key,
|
||
|
Serial: c.Serial,
|
||
|
CertType: uint32(c.Type),
|
||
|
KeyId: c.KeyID,
|
||
|
ValidPrincipals: c.Principals,
|
||
|
ValidAfter: c.ValidAfter,
|
||
|
ValidBefore: c.ValidBefore,
|
||
|
Permissions: ssh.Permissions{
|
||
|
CriticalOptions: c.CriticalOptions,
|
||
|
Extensions: c.Extensions,
|
||
|
},
|
||
|
Reserved: c.Reserved,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CreateCertificate signs the given certificate with the given signer. If the
|
||
|
// certificate does not have a nonce or a serial, it will create random ones.
|
||
|
func CreateCertificate(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate, error) {
|
||
|
if len(cert.Nonce) == 0 {
|
||
|
nonce, err := randutil.ASCII(32)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
cert.Nonce = []byte(nonce)
|
||
|
}
|
||
|
|
||
|
if cert.Serial == 0 {
|
||
|
if err := binary.Read(rand.Reader, binary.BigEndian, &cert.Serial); err != nil {
|
||
|
return nil, errors.Wrap(err, "error reading random number")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set signer public key.
|
||
|
cert.SignatureKey = signer.PublicKey()
|
||
|
|
||
|
// Get bytes for signing trailing the signature length.
|
||
|
data := cert.Marshal()
|
||
|
data = data[:len(data)-4]
|
||
|
|
||
|
// Sign the certificate.
|
||
|
sig, err := signer.Sign(rand.Reader, data)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "error signing certificate")
|
||
|
}
|
||
|
cert.Signature = sig
|
||
|
|
||
|
return cert, nil
|
||
|
}
|