-
Notifications
You must be signed in to change notification settings - Fork 14
/
cert.go
176 lines (152 loc) · 5.31 KB
/
cert.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
165
166
167
168
169
170
171
172
173
174
175
176
package dnscrypt
import (
"bytes"
"crypto/ed25519"
"encoding/binary"
"fmt"
"time"
)
// Cert is a DNSCrypt server certificate
// See ResolverConfig for more info on how to create one
type Cert struct {
// Serial is a 4 byte serial number in big-endian format. If more than
// one certificates are valid, the client must prefer the certificate
// with a higher serial number.
Serial uint32
// <es-version> ::= the cryptographic construction to use with this
// certificate.
// For X25519-XSalsa20Poly1305, <es-version> must be 0x00 0x01.
// For X25519-XChacha20Poly1305, <es-version> must be 0x00 0x02.
EsVersion CryptoConstruction
// Signature is a 64-byte signature of (<resolver-pk> <client-magic>
// <serial> <ts-start> <ts-end> <extensions>) using the Ed25519 algorithm and the
// provider secret key. Ed25519 must be used in this version of the
// protocol.
Signature [ed25519.SignatureSize]byte
// ResolverPk is the resolver's short-term public key, which is 32 bytes when using X25519.
// This key is used to encrypt/decrypt DNS queries
ResolverPk [keySize]byte
// ResolverSk is the resolver's short-term private key, which is 32 bytes when using X25519.
// Note that it's only used in the server implementation and never serialized/deserialized.
// This key is used to encrypt/decrypt DNS queries
ResolverSk [keySize]byte
// ClientMagic is the first 8 bytes of a client query that is to be built
// using the information from this certificate. It may be a truncated
// public key. Two valid certificates cannot share the same <client-magic>.
ClientMagic [clientMagicSize]byte
// NotAfter is the date the certificate is valid from, as a big-endian
// 4-byte unsigned Unix timestamp.
NotBefore uint32
// NotAfter is the date the certificate is valid until (inclusive), as a
// big-endian 4-byte unsigned Unix timestamp.
NotAfter uint32
}
// Serialize serializes the cert to bytes
// <cert> ::= <cert-magic> <es-version> <protocol-minor-version> <signature>
// <resolver-pk> <client-magic> <serial> <ts-start> <ts-end>
// <extensions>
// Certificates made of these information, without extensions, are 116 bytes
// long. With the addition of the cert-magic, es-version and
// protocol-minor-version, the record is 124 bytes long.
func (c *Cert) Serialize() ([]byte, error) {
// validate
if c.EsVersion == UndefinedConstruction {
return nil, ErrEsVersion
}
if !c.VerifyDate() {
return nil, ErrInvalidDate
}
// start serializing
b := make([]byte, 124)
// <cert-magic>
copy(b[:4], certMagic[:])
// <es-version>
binary.BigEndian.PutUint16(b[4:6], uint16(c.EsVersion))
// <protocol-minor-version> - always 0x00 0x00
copy(b[6:8], []byte{0, 0})
// <signature>
copy(b[8:72], c.Signature[:ed25519.SignatureSize])
// signed: (<resolver-pk> <client-magic> <serial> <ts-start> <ts-end> <extensions>)
c.writeSigned(b[72:])
// done
return b, nil
}
// Deserialize deserializes certificate from a byte array
// <cert> ::= <cert-magic> <es-version> <protocol-minor-version> <signature>
// <resolver-pk> <client-magic> <serial> <ts-start> <ts-end>
// <extensions>
func (c *Cert) Deserialize(b []byte) error {
if len(b) < 124 {
return ErrCertTooShort
}
// <cert-magic>
if !bytes.Equal(b[:4], certMagic[:4]) {
return ErrCertMagic
}
// <es-version>
switch esVersion := binary.BigEndian.Uint16(b[4:6]); esVersion {
case uint16(XSalsa20Poly1305):
c.EsVersion = XSalsa20Poly1305
case uint16(XChacha20Poly1305):
c.EsVersion = XChacha20Poly1305
default:
return ErrEsVersion
}
// Ignore 6:8, <protocol-minor-version>
// <signature>
copy(c.Signature[:], b[8:72])
// <resolver-pk>
copy(c.ResolverPk[:], b[72:104])
// <client-magic>
copy(c.ClientMagic[:], b[104:112])
// <serial>
c.Serial = binary.BigEndian.Uint32(b[112:116])
// <ts-start> <ts-end>
c.NotBefore = binary.BigEndian.Uint32(b[116:120])
c.NotAfter = binary.BigEndian.Uint32(b[120:124])
// Deserialized with no issues
return nil
}
// VerifyDate checks that the cert is valid at this moment
func (c *Cert) VerifyDate() bool {
if c.NotBefore >= c.NotAfter {
return false
}
now := uint32(time.Now().Unix())
if now > c.NotAfter || now < c.NotBefore {
return false
}
return true
}
// VerifySignature checks if the cert is properly signed with the specified signature
func (c *Cert) VerifySignature(publicKey ed25519.PublicKey) bool {
b := make([]byte, 52)
c.writeSigned(b)
return ed25519.Verify(publicKey, b, c.Signature[:])
}
// Sign creates cert.Signature
func (c *Cert) Sign(privateKey ed25519.PrivateKey) {
b := make([]byte, 52)
c.writeSigned(b)
signature := ed25519.Sign(privateKey, b)
copy(c.Signature[:64], signature[:64])
}
// String Cert's string representation
func (c *Cert) String() string {
return fmt.Sprintf("Certificate Serial=%d NotBefore=%s NotAfter=%s EsVersion=%s",
c.Serial, time.Unix(int64(c.NotBefore), 0).String(),
time.Unix(int64(c.NotAfter), 0).String(), c.EsVersion.String())
}
// writeSigned writes (<resolver-pk> <client-magic> <serial> <ts-start> <ts-end> <extensions>)
func (c *Cert) writeSigned(dst []byte) {
// <resolver-pk>
copy(dst[:32], c.ResolverPk[:keySize])
// <client-magic>
copy(dst[32:40], c.ClientMagic[:clientMagicSize])
// <serial>
binary.BigEndian.PutUint32(dst[40:44], c.Serial)
// <ts-start>
binary.BigEndian.PutUint32(dst[44:48], c.NotBefore)
// <ts-end>
binary.BigEndian.PutUint32(dst[48:52], c.NotAfter)
}