1 package minisign
2
3 import (
4 "encoding/base64"
5 "errors"
6 "io/ioutil"
7 "strings"
8
9 "golang.org/x/crypto/blake2b"
10 "golang.org/x/crypto/ed25519"
11 )
12
13 type PublicKey struct {
14 SignatureAlgorithm [2]byte
15 KeyId [8]byte
16 PublicKey [32]byte
17 }
18
19 type Signature struct {
20 UntrustedComment string
21 SignatureAlgorithm [2]byte
22 KeyId [8]byte
23 Signature [64]byte
24 TrustedComment string
25 GlobalSignature [64]byte
26 }
27
28 func NewPublicKey(publicKeyStr string) (PublicKey, error) {
29 var publicKey PublicKey
30 bin, err := base64.StdEncoding.DecodeString(publicKeyStr)
31 if err != nil || len(bin) != 42 {
32 return publicKey, errors.New("Invalid encoded public key")
33 }
34 copy(publicKey.SignatureAlgorithm[:], bin[0:2])
35 copy(publicKey.KeyId[:], bin[2:10])
36 copy(publicKey.PublicKey[:], bin[10:42])
37 return publicKey, nil
38 }
39
40 func DecodePublicKey(in string) (PublicKey, error) {
41 var publicKey PublicKey
42 lines := strings.SplitN(in, "\n", 2)
43 if len(lines) < 2 {
44 return publicKey, errors.New("Incomplete encoded public key")
45 }
46 return NewPublicKey(lines[1])
47 }
48
49 func trimCarriageReturn(input string) string {
50 return strings.TrimRight(input, "\r")
51 }
52
53 func DecodeSignature(in string) (Signature, error) {
54 var signature Signature
55 lines := strings.SplitN(in, "\n", 4)
56 if len(lines) < 4 {
57 return signature, errors.New("Incomplete encoded signature")
58 }
59 signature.UntrustedComment = trimCarriageReturn(lines[0])
60 bin1, err := base64.StdEncoding.DecodeString(lines[1])
61 if err != nil || len(bin1) != 74 {
62 return signature, errors.New("Invalid encoded signature")
63 }
64 signature.TrustedComment = trimCarriageReturn(lines[2])
65 bin2, err := base64.StdEncoding.DecodeString(lines[3])
66 if err != nil || len(bin2) != 64 {
67 return signature, errors.New("Invalid encoded signature")
68 }
69 copy(signature.SignatureAlgorithm[:], bin1[0:2])
70 copy(signature.KeyId[:], bin1[2:10])
71 copy(signature.Signature[:], bin1[10:74])
72 copy(signature.GlobalSignature[:], bin2)
73 return signature, nil
74 }
75
76 func NewPublicKeyFromFile(file string) (PublicKey, error) {
77 var publicKey PublicKey
78 bin, err := ioutil.ReadFile(file)
79 if err != nil {
80 return publicKey, err
81 }
82 return DecodePublicKey(string(bin))
83 }
84
85 func NewSignatureFromFile(file string) (Signature, error) {
86 var signature Signature
87 bin, err := ioutil.ReadFile(file)
88 if err != nil {
89 return signature, err
90 }
91 return DecodeSignature(string(bin))
92 }
93
94 func (publicKey *PublicKey) Verify(bin []byte, signature Signature) (bool, error) {
95 if publicKey.SignatureAlgorithm != [2]byte{'E', 'd'} {
96 return false, errors.New("Incompatible signature algorithm")
97 }
98 prehashed := false
99 if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x64 {
100 prehashed = false
101 } else if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x44 {
102 prehashed = true
103 } else {
104 return false, errors.New("Unsupported signature algorithm")
105 }
106 if publicKey.KeyId != signature.KeyId {
107 return false, errors.New("Incompatible key identifiers")
108 }
109 if !strings.HasPrefix(signature.TrustedComment, "trusted comment: ") {
110 return false, errors.New("Unexpected format for the trusted comment")
111 }
112
113 if prehashed {
114 h, _ := blake2b.New512(nil)
115 h.Write(bin)
116 bin = h.Sum(nil)
117 }
118 if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), bin, signature.Signature[:]) {
119 return false, errors.New("Invalid signature")
120 }
121 if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), append(signature.Signature[:], []byte(signature.TrustedComment)[17:]...), signature.GlobalSignature[:]) {
122 return false, errors.New("Invalid global signature")
123 }
124 return true, nil
125 }
126
127 func (publicKey *PublicKey) VerifyFromFile(file string, signature Signature) (bool, error) {
128 bin, err := ioutil.ReadFile(file)
129 if err != nil {
130 return false, err
131 }
132 return publicKey.Verify(bin, signature)
133 }
134
View as plain text