|
20 | 20 | package signify
|
21 | 21 |
|
22 | 22 | import (
|
| 23 | + "bytes" |
| 24 | + "crypto/ed25519" |
23 | 25 | "encoding/base64"
|
24 | 26 | "errors"
|
25 | 27 | "fmt"
|
26 | 28 | "io/ioutil"
|
27 |
| - "os" |
28 | 29 | "strings"
|
29 | 30 | "time"
|
30 |
| - |
31 |
| - "crypto/ed25519" |
32 | 31 | )
|
33 | 32 |
|
34 | 33 | var (
|
35 |
| - errInvalidKeyHeader = errors.New("Incorrect key header") |
| 34 | + errInvalidKeyHeader = errors.New("incorrect key header") |
36 | 35 | errInvalidKeyLength = errors.New("invalid, key length != 104")
|
37 | 36 | )
|
38 | 37 |
|
39 |
| -func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { |
| 38 | +func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum []byte, err error) { |
40 | 39 | keydata, err := base64.StdEncoding.DecodeString(key)
|
41 | 40 | if err != nil {
|
42 | 41 | return nil, nil, nil, err
|
43 | 42 | }
|
44 |
| - |
45 | 43 | if len(keydata) != 104 {
|
46 | 44 | return nil, nil, nil, errInvalidKeyLength
|
47 | 45 | }
|
48 |
| - |
49 | 46 | if string(keydata[:2]) != "Ed" {
|
50 | 47 | return nil, nil, nil, errInvalidKeyHeader
|
51 | 48 | }
|
52 |
| - |
53 | 49 | return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil
|
54 | 50 | }
|
55 | 51 |
|
56 |
| -func commentHasManyLines(comment string) bool { |
57 |
| - firstLFIndex := strings.IndexByte(comment, 10) |
58 |
| - return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) |
59 |
| -} |
60 |
| - |
61 |
| -// SignifySignFile creates a signature of the input file. |
62 |
| -func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { |
63 |
| - in, err := os.Open(input) |
64 |
| - if err != nil { |
65 |
| - return err |
| 52 | +// SignFile creates a signature of the input file. |
| 53 | +// |
| 54 | +// This accepts base64 keys in the format created by the 'signify' tool. |
| 55 | +// The signature is written to the 'output' file. |
| 56 | +func SignFile(input string, output string, key string, untrustedComment string, trustedComment string) error { |
| 57 | + // Pre-check comments and ensure they're set to something. |
| 58 | + if strings.IndexByte(untrustedComment, '\n') >= 0 { |
| 59 | + return errors.New("untrusted comment must not contain newline") |
66 | 60 | }
|
67 |
| - defer in.Close() |
68 |
| - |
69 |
| - out, err := os.Create(output) |
70 |
| - if err != nil { |
71 |
| - return err |
| 61 | + if strings.IndexByte(trustedComment, '\n') >= 0 { |
| 62 | + return errors.New("trusted comment must not contain newline") |
| 63 | + } |
| 64 | + if untrustedComment == "" { |
| 65 | + untrustedComment = "verify with " + input + ".pub" |
| 66 | + } |
| 67 | + if trustedComment == "" { |
| 68 | + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) |
72 | 69 | }
|
73 |
| - defer out.Close() |
74 | 70 |
|
75 |
| - skey, header, keyNum, err := parsePrivateKey(key) |
| 71 | + filedata, err := ioutil.ReadFile(input) |
76 | 72 | if err != nil {
|
77 | 73 | return err
|
78 | 74 | }
|
79 |
| - |
80 |
| - filedata, err := ioutil.ReadAll(in) |
| 75 | + skey, header, keyNum, err := parsePrivateKey(key) |
81 | 76 | if err != nil {
|
82 | 77 | return err
|
83 | 78 | }
|
84 | 79 |
|
| 80 | + // Create the main data signature. |
85 | 81 | rawSig := ed25519.Sign(skey, filedata)
|
86 |
| - |
87 |
| - var sigdata []byte |
88 |
| - sigdata = append(sigdata, header...) |
89 |
| - sigdata = append(sigdata, keyNum...) |
90 |
| - sigdata = append(sigdata, rawSig...) |
91 |
| - |
92 |
| - // Check that the trusted comment fits in one line |
93 |
| - if commentHasManyLines(unTrustedComment) { |
94 |
| - return errors.New("untrusted comment must fit on a single line") |
95 |
| - } |
96 |
| - |
97 |
| - if unTrustedComment == "" { |
98 |
| - unTrustedComment = "verify with " + input + ".pub" |
99 |
| - } |
100 |
| - out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) |
101 |
| - |
102 |
| - // Add the trusted comment if unavailable |
103 |
| - if trustedComment == "" { |
104 |
| - trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) |
105 |
| - } |
106 |
| - |
107 |
| - // Check that the trusted comment fits in one line |
108 |
| - if commentHasManyLines(trustedComment) { |
109 |
| - return errors.New("trusted comment must fit on a single line") |
110 |
| - } |
111 |
| - |
112 |
| - var sigAndComment []byte |
113 |
| - sigAndComment = append(sigAndComment, rawSig...) |
114 |
| - sigAndComment = append(sigAndComment, []byte(trustedComment)...) |
115 |
| - out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) |
116 |
| - |
117 |
| - return nil |
| 82 | + var dataSig []byte |
| 83 | + dataSig = append(dataSig, header...) |
| 84 | + dataSig = append(dataSig, keyNum...) |
| 85 | + dataSig = append(dataSig, rawSig...) |
| 86 | + |
| 87 | + // Create the comment signature. |
| 88 | + var commentSigInput []byte |
| 89 | + commentSigInput = append(commentSigInput, rawSig...) |
| 90 | + commentSigInput = append(commentSigInput, []byte(trustedComment)...) |
| 91 | + commentSig := ed25519.Sign(skey, commentSigInput) |
| 92 | + |
| 93 | + // Create the output file. |
| 94 | + var out = new(bytes.Buffer) |
| 95 | + fmt.Fprintln(out, "untrusted comment:", untrustedComment) |
| 96 | + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(dataSig)) |
| 97 | + fmt.Fprintln(out, "trusted comment:", trustedComment) |
| 98 | + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(commentSig)) |
| 99 | + return ioutil.WriteFile(output, out.Bytes(), 0644) |
118 | 100 | }
|
0 commit comments