-
Notifications
You must be signed in to change notification settings - Fork 1
/
sign.go
164 lines (145 loc) · 5.33 KB
/
sign.go
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package sshsig
import (
crand "crypto/rand"
"errors"
"fmt"
"io"
"golang.org/x/crypto/ssh"
)
// ErrMissingNamespace is returned by Sign if the namespace value is missing.
var ErrMissingNamespace = errors.New("missing namespace")
// Signature represents the SSH signature of a message. It can be marshaled
// into an SSH wire format using Marshal, or into an armored (PEM) format using
// Armor.
//
// Manually construction of this type is not recommended. Use ParseSignature or
// Unarmor instead to retrieve a Signature from a wire or armored (PEM) format.
type Signature struct {
// Version is the version of the signature format.
// It currently supports version 1, any other value will be rejected with
// ErrUnsupportedSignatureVersion.
Version uint32
// PublicKey is the public key used to create the Signature.
PublicKey ssh.PublicKey
// Namespace is the domain of the signature, and is used to prevent signature
// reuse across different applications.
Namespace string
// HashAlgorithm is the hash algorithm used to hash the Signature message.
HashAlgorithm HashAlgorithm
// Signature is the SSH signature of the hash of the message.
Signature *ssh.Signature
}
// Marshal returns the Signature in SSH wire format.
func (s Signature) Marshal() []byte {
return blob{
Version: s.Version,
PublicKey: string(s.PublicKey.Marshal()),
Namespace: s.Namespace,
HashAlgorithm: s.HashAlgorithm.String(),
Signature: string(ssh.Marshal(s.Signature)),
}.Marshal()
}
// ParseSignature parses a signature in SSH wire format into a Signature.
// It returns an error if the signature is invalid.
func ParseSignature(b []byte) (*Signature, error) {
var sig blob
if err := ssh.Unmarshal(b, &sig); err != nil {
return nil, err
}
if err := sig.Validate(); err != nil {
return nil, err
}
sshSig := ssh.Signature{}
if err := ssh.Unmarshal([]byte(sig.Signature), &sshSig); err != nil {
return nil, err
}
pub, err := ssh.ParsePublicKey([]byte(sig.PublicKey))
if err != nil {
return nil, err
}
// For RSA signatures, the signature algorithm must be "rsa-sha2-512" or
// "rsa-sha2-256".
// xref: https://github.com/openssh/openssh-portable/blob/V_9_2_P1/PROTOCOL.sshsig#L69-L72
if pub.Type() == ssh.KeyAlgoRSA && sshSig.Format != ssh.KeyAlgoRSASHA256 && sshSig.Format != ssh.KeyAlgoRSASHA512 {
return nil, fmt.Errorf("invalid signature format %q: expected %q or %q", sshSig.Format, ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512)
}
return &Signature{
Version: sig.Version,
PublicKey: pub,
Namespace: sig.Namespace,
HashAlgorithm: HashAlgorithm(sig.HashAlgorithm),
Signature: &sshSig,
}, nil
}
// Sign generates a signature of the message from the io.Reader using the
// given ssh.Signer private key. The signature hash is computed using the provided
// HashAlgorithm.
//
// The purpose of the namespace value is to specify an unambiguous interpretation
// domain for the signature, e.g. file signing. This prevents cross-protocol
// attacks caused by signatures intended for one intended domain being accepted
// in another. The namespace must not be empty, or ErrMissingNamespace will be
// returned.
//
// When the signer is an RSA key, the signature algorithm will always be
// "rsa-sha2-512". This is the same default used by OpenSSH, and is required by
// the SSH signature wire protocol.
//
// Sign returns a Signature containing the signed message and metadata, or an
// error if the signing process fails.
func Sign(m io.Reader, signer ssh.Signer, h HashAlgorithm, namespace string) (*Signature, error) {
return SignWithRand(m, crand.Reader, signer, h, namespace)
}
// SignWithRand is like Sign, but uses the provided rand io.Reader to create any
// necessary random values. Most callers likely want to use Sign instead.
func SignWithRand(m, rand io.Reader, signer ssh.Signer, h HashAlgorithm, namespace string) (*Signature, error) {
if namespace == "" {
// xref: https://github.com/openssh/openssh-portable/blob/V_9_2_P1/PROTOCOL.sshsig#LL57C13-L57C13
return nil, ErrMissingNamespace
}
if err := h.Available(); err != nil {
return nil, err
}
hf := h.Hash()
if _, err := io.Copy(hf, m); err != nil {
return nil, err
}
mh := hf.Sum(nil)
var (
sd = signedData{
Namespace: namespace,
HashAlgorithm: h.String(),
Hash: string(mh),
}
sig *ssh.Signature
err error
)
switch signer.PublicKey().Type() {
case ssh.KeyAlgoRSA:
// For RSA signatures, the signature algorithm must be "rsa-sha2-512" or
// "rsa-sha2-256". We use the same "rsa-sha2-512" default as OpenSSH.
// xref: https://github.com/openssh/openssh-portable/blob/V_9_2_P1/PROTOCOL.sshsig#L69-L72
// xref: https://github.com/openssh/openssh-portable/blob/V_9_2_P1/ssh-keygen.c#L1804-L1805
algo := ssh.KeyAlgoRSASHA512
// This should always succeed as an SSH signer must implement the
// AlgorithmSigner, but we check anyway.
as, ok := signer.(ssh.AlgorithmSigner)
if !ok {
return nil, fmt.Errorf("signer does not support non-default signature algorithm %q", algo)
}
if sig, err = as.SignWithAlgorithm(rand, sd.Marshal(), algo); err != nil {
return nil, err
}
default:
if sig, err = signer.Sign(rand, sd.Marshal()); err != nil {
return nil, err
}
}
return &Signature{
Version: sigVersion,
PublicKey: signer.PublicKey(),
Namespace: namespace,
HashAlgorithm: h,
Signature: sig,
}, nil
}