-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathsign.go
More file actions
190 lines (153 loc) · 4.58 KB
/
sign.go
File metadata and controls
190 lines (153 loc) · 4.58 KB
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// sign.go -- Ed25519 keys and signature handling
//
// (c) 2016 Sudhi Herle <sudhi@herle.net>
//
// Licensing Terms: GPLv2
//
// If you need a commercial license for this work, please contact
// the author.
//
// This software does not come with any express or implied
// warranty; it is provided "as is". No claim is made to its
// suitability for any purpose.
// This file implements:
// - sign/verify of files and byte strings
package sigtool
import (
"crypto"
"crypto/rand"
"crypto/sha3"
"crypto/sha512"
"encoding/base64"
"fmt"
"hash"
"strings"
Ed "crypto/ed25519"
)
const _Sigtool_sign_prefix = "sigtool signed message"
// Sigtool signatures are always of the form
// fingerprint.signature
// Sign a prehashed Message; return the sigtool signature of the form
//
// fingerprint.signature
func (sk *PrivateKey) SignMessage(msg []byte) (string, error) {
var hb [sha512.Size]byte
h := sha512.New()
h.Write([]byte(_Sigtool_sign_prefix))
h.Write(msg)
ck := h.Sum(hb[:0])[:]
x := Ed.PrivateKey(sk.sk)
sig, err := x.Sign(rand.Reader, ck, crypto.Hash(0))
if err != nil {
return "", fmt.Errorf("can't sign %x: %s", ck, err)
}
ss := tob64(sig)
return fmt.Sprintf("%s.%s", sk.Fingerprint(), ss), nil
}
// Read and sign a file
//
// We calculate the signature differently here: We first calculate
// the SHA3-512 checksum of the file and its size. We sign the
// checksum.
func (sk *PrivateKey) SignFile(fn string) (string, error) {
ck, err := fileCksum(fn, func() hash.Hash {
return sha3.New512()
})
if err != nil {
return "", err
}
return sk.SignMessage(ck)
}
// Verify a signature 'sig' for a pre-calculated checksum 'ck' against public key 'pk'
// Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyMessage(ck []byte, signature string) (bool, error) {
fp, sig, err := parseSig(signature)
if err != nil {
return false, fmt.Errorf("sigtool: verify %s: %w", signature, err)
}
if fp != pk.Fingerprint() {
return false, fmt.Errorf("sigtool: verify %s: wrong PK %s", signature, fp)
}
return pk.verifySig(ck, sig), nil
}
// Verify a signature 'sig' for file 'fn' against public key 'pk'
// Return True if signature matches, False otherwise
func (pk *PublicKey) VerifyFile(fn string, signature string) (bool, error) {
fp, sig, err := parseSig(signature)
if err != nil {
return false, fmt.Errorf("sigtool: verify %s: %w", signature, err)
}
if fp != pk.Fingerprint() {
return false, fmt.Errorf("sigtool: verify %s: wrong PK %s", signature, fp)
}
ck, err := fileCksum(fn, func() hash.Hash {
return sha3.New512()
})
if err != nil {
return false, err
}
return pk.verifySig(ck, sig), nil
}
// verify the signature 'sig' for the message 'msg'
func (pk *PublicKey) verifySig(msg, sig []byte) bool {
var hb [sha512.Size]byte
h := sha512.New()
h.Write([]byte(_Sigtool_sign_prefix))
h.Write(msg)
ck := h.Sum(hb[:0])[:]
x := Ed.PublicKey(pk.pk)
return Ed.Verify(x, ck, sig)
}
func tob64(b []byte) string {
return base64.RawURLEncoding.EncodeToString(b)
}
func fromb64(s string) ([]byte, error) {
return base64.RawURLEncoding.DecodeString(s)
}
func b64len(n int) int {
return base64.RawURLEncoding.EncodedLen(n)
}
// Return the length in bytes of an encoded signature
func sigLen() int {
return b64len(_FpSize) + 1 + b64len(Ed.SignatureSize)
}
// take a string representation of a sigtool signature and return the
// fingerprint and decoded signature bytes
func parseSig(ss string) (string, []byte, error) {
i := strings.IndexByte(ss, '.')
if i < 0 {
return "", nil, fmt.Errorf("invalid signature format")
}
fp, rest := ss[:i], ss[i+1:]
// fp and rest must be base64 decodable
_, err := fromb64(fp)
if err != nil {
return "", nil, fmt.Errorf("invalid fingerprint")
}
sig, err := fromb64(rest)
if err != nil {
return "", nil, fmt.Errorf("invalid signature")
}
return fp, sig, nil
}
// make a raandom looking signature
func randSig() string {
fp := randBuf(_FpSize)
sig := randBuf(Ed.SignatureSize)
return fmt.Sprintf("%s.%s", tob64(fp), tob64(sig))
}
// signRaw produces a raw Ed25519 signature over msg. No prefix, no encoding,
// no fingerprint wrapping — for internal callers that handle those layers
// themselves (e.g. per-capsule signing during encrypt).
func (sk *PrivateKey) signRaw(msg []byte) ([]byte, error) {
x := Ed.PrivateKey(sk.sk)
return x.Sign(rand.Reader, msg, crypto.Hash(0))
}
// verifyRaw verifies a raw Ed25519 signature over msg.
func (pk *PublicKey) verifyRaw(msg, sig []byte) bool {
if len(sig) != Ed.SignatureSize {
return false
}
return Ed.Verify(Ed.PublicKey(pk.pk), msg, sig)
}
// vim: noexpandtab:ts=8:sw=8:tw=92: