File: certificate.go

package info (click to toggle)
golang-github-smallstep-crypto 0.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,800 kB
  • sloc: sh: 66; makefile: 50
file content (137 lines) | stat: -rw-r--r-- 3,936 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package sshutil implements utilities to build SSH certificates based on JSON
// templates.
package sshutil

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh"

	"go.step.sm/crypto/internal/utils"
	"go.step.sm/crypto/randutil"
)

// 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      time.Time         `json:"validAfter"`
	ValidBefore     time.Time         `json:"validBefore"`
	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
}

// GetCertificate return the ssh.Certificate representation of the certificate.
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:      toValidity(c.ValidAfter),
		ValidBefore:     toValidity(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.
//
// If the signer is an RSA key, it will use rsa-sha2-256 instead of the default
// ssh-rsa (SHA-1), this method is currently deprecated and rsa-sha2-256/512 are
// supported since OpenSSH 7.2 (2016).
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 certificate.
	//
	// crypto/ssh signer defaults to SHA-1 with RSA signers, we will default to
	// SHA256.
	if cert.SignatureKey.Type() == "ssh-rsa" {
		if algSigner, ok := signer.(ssh.AlgorithmSigner); ok {
			sig, err := algSigner.SignWithAlgorithm(rand.Reader, data, ssh.KeyAlgoRSASHA256)
			if err != nil {
				return nil, errors.Wrap(err, "error signing certificate")
			}
			cert.Signature = sig
			return cert, nil
		}
	}

	// Rest of the keys
	sig, err := signer.Sign(rand.Reader, data)
	if err != nil {
		return nil, errors.Wrap(err, "error signing certificate")
	}
	cert.Signature = sig

	return cert, nil
}

func toValidity(t time.Time) uint64 {
	if t.IsZero() {
		return 0
	}

	return utils.MustUint64(t.Unix())
}