forked from TrueCloudLab/certificates
df1f7e5a2e
SSH does not have a real concept of ssh certificate request, but we are using the type to encapsulate the parameters coming in the request.
104 lines
2.9 KiB
Go
104 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(cr CertificateRequest, opts ...Option) (*Certificate, error) {
|
|
o, err := new(Options).apply(cr, 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 = cr.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
|
|
}
|