Skip to content

Commit 7006b82

Browse files
committed
Add a tool to generate a test MTC corpus
Hopefully this will be useful to folks in testing, and eventually to put test vectors in the spec. Almost certainly has some bugs.
1 parent e8435c2 commit 7006b82

9 files changed

Lines changed: 1188 additions & 0 deletions

File tree

demo/config.go

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
package main
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/asn1"
6+
"encoding/json"
7+
"fmt"
8+
"strconv"
9+
"strings"
10+
"time"
11+
)
12+
13+
var (
14+
oidServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1}
15+
)
16+
17+
type SignatureAlgorithm int
18+
19+
const (
20+
SignatureAlgorithmP256WithSHA256 SignatureAlgorithm = iota
21+
SignatureAlgorithmP384WithSHA384
22+
SignatureAlgorithmEd25519
23+
// TODO: Add ML-DSA once Go's standard library supports it.
24+
)
25+
26+
func SignatureAlgorithmFromString(s string) (SignatureAlgorithm, bool) {
27+
switch s {
28+
case "ecdsa_p256_sha256":
29+
return SignatureAlgorithmP256WithSHA256, true
30+
case "ecdsa_p384_sha384":
31+
return SignatureAlgorithmP384WithSHA384, true
32+
case "ed25519":
33+
return SignatureAlgorithmEd25519, true
34+
}
35+
return 0, false
36+
}
37+
38+
func (s SignatureAlgorithm) String() string {
39+
switch s {
40+
case SignatureAlgorithmP256WithSHA256:
41+
return "ecdsa_p256_sha256"
42+
case SignatureAlgorithmP384WithSHA384:
43+
return "ecdsa_p384_sha384"
44+
case SignatureAlgorithmEd25519:
45+
return "ed25519"
46+
default:
47+
panic(fmt.Sprintf("unexpected SignatureAlgorithm: %#v", s))
48+
}
49+
}
50+
51+
func (s *SignatureAlgorithm) UnmarshalJSON(data []byte) error {
52+
var v string
53+
if err := json.Unmarshal(data, &v); err != nil {
54+
return err
55+
}
56+
var ok bool
57+
*s, ok = SignatureAlgorithmFromString(v)
58+
if !ok {
59+
return fmt.Errorf("invalid signature algorithm: %q", v)
60+
}
61+
return nil
62+
}
63+
64+
type CAConfig struct {
65+
LogID TrustAnchorID
66+
Cosigners []CosignerConfig
67+
Entries []EntryConfig
68+
}
69+
70+
type CosignerConfig struct {
71+
CosignerID TrustAnchorID
72+
SignatureAlgorithm SignatureAlgorithm
73+
PrivateKey []byte
74+
}
75+
76+
type EntryConfig struct {
77+
// A number of times to repeat this entry.
78+
Repeat int
79+
Subject SubjectConfig
80+
PublicKey []byte
81+
NotBefore, NotAfter time.Time
82+
DNSNames []string
83+
KeyUsage KeyUsageConfig
84+
ExtKeyUsage []ExtKeyUsageConfig
85+
// A list of checkpoint sequence names that end at this entry. Every
86+
// checkpoint sequence implicitly starts at 0.
87+
Checkpoints []string
88+
// A list of certificates to generate from this entry.
89+
Certificates []CertificateConfig
90+
}
91+
92+
type SubjectConfig struct {
93+
Country, Organization, OrganizationalUnit []string
94+
Locality, Province []string
95+
StreetAddress, PostalCode []string
96+
SerialNumber, CommonName string
97+
}
98+
99+
type CertificateConfig struct {
100+
// At most one of SubtreeStart/SubtreeEnd and Checkpoint may be specified.
101+
// If SubtreeStart/SubtreeEnd is specified, that subtree is used. (Entries
102+
// are one-indexed.)
103+
// If Checkpoint is used, the named checkpoint sequence is used.
104+
SubtreeStart, SubtreeEnd int
105+
Checkpoint string
106+
// Must refer to a cosigner defined in the CAConfig.
107+
Cosigners []TrustAnchorID
108+
}
109+
110+
func parseBase128(in []byte) (ret uint32, rest []byte, ok bool) {
111+
rest = in
112+
if len(rest) == 0 {
113+
return
114+
}
115+
if rest[0] == 0x80 {
116+
return // Not minimally-encoded
117+
}
118+
for {
119+
if len(rest) == 0 || (ret<<7)>>7 != ret {
120+
// Input too small or overflow.
121+
return
122+
}
123+
b := rest[0]
124+
ret <<= 7
125+
ret |= uint32(b & 0x7f)
126+
rest = rest[1:]
127+
if b&0x80 == 0 {
128+
ok = true
129+
return
130+
}
131+
}
132+
}
133+
134+
func appendBase128(dst []byte, v uint32) []byte {
135+
// Count how many bytes are needed.
136+
var l int
137+
for n := v; n != 0; n >>= 7 {
138+
l++
139+
}
140+
// Special-case: zero is encoded with one, not zero bytes.
141+
if v == 0 {
142+
l = 1
143+
}
144+
for ; l > 0; l-- {
145+
b := byte(v>>uint(7*(l-1))) & 0x7f
146+
if l > 1 {
147+
b |= 0x80
148+
}
149+
dst = append(dst, b)
150+
}
151+
return dst
152+
}
153+
154+
type TrustAnchorID []byte
155+
156+
func TrustAnchorIDFromString(s string) (t TrustAnchorID, ok bool) {
157+
for _, part := range strings.Split(s, ".") {
158+
v, err := strconv.ParseUint(part, 10, 32)
159+
if err != nil {
160+
return
161+
}
162+
t = appendBase128(t, uint32(v))
163+
}
164+
if len(t) == 0 {
165+
return
166+
}
167+
ok = true
168+
return
169+
}
170+
171+
func (t TrustAnchorID) String() string {
172+
if len(t) == 0 {
173+
return fmt.Sprintf("<invalid: %x>", []byte(t))
174+
}
175+
var s strings.Builder
176+
for len(t) != 0 {
177+
v, rest, ok := parseBase128(t)
178+
if !ok {
179+
return fmt.Sprintf("<invalid: %x>", []byte(t))
180+
}
181+
if s.Len() != 0 {
182+
s.WriteByte('.')
183+
}
184+
fmt.Fprintf(&s, "%d", v)
185+
t = rest
186+
}
187+
return s.String()
188+
}
189+
190+
func (t *TrustAnchorID) UnmarshalJSON(data []byte) error {
191+
var s string
192+
if err := json.Unmarshal(data, &s); err != nil {
193+
return err
194+
}
195+
var ok bool
196+
*t, ok = TrustAnchorIDFromString(s)
197+
if !ok {
198+
return fmt.Errorf("invalid trust anchor ID: %q", s)
199+
}
200+
return nil
201+
}
202+
203+
type KeyUsageConfig x509.KeyUsage
204+
205+
func (k *KeyUsageConfig) UnmarshalJSON(data []byte) error {
206+
var values []string
207+
if err := json.Unmarshal(data, &values); err != nil {
208+
return err
209+
}
210+
var result x509.KeyUsage
211+
for _, value := range values {
212+
switch value {
213+
case "DigitalSignature":
214+
result |= x509.KeyUsageDigitalSignature
215+
case "ContentCommitment":
216+
result |= x509.KeyUsageContentCommitment
217+
case "KeyEncipherment":
218+
result |= x509.KeyUsageKeyEncipherment
219+
case "DataEncipherment":
220+
result |= x509.KeyUsageDataEncipherment
221+
case "KeyAgreement":
222+
result |= x509.KeyUsageKeyAgreement
223+
case "CertSign":
224+
result |= x509.KeyUsageCertSign
225+
case "CRLSign":
226+
result |= x509.KeyUsageCRLSign
227+
case "EncipherOnly":
228+
result |= x509.KeyUsageEncipherOnly
229+
case "DecipherOnly":
230+
result |= x509.KeyUsageDecipherOnly
231+
default:
232+
return fmt.Errorf("unknown key usage %q", value)
233+
}
234+
}
235+
*k = KeyUsageConfig(result)
236+
return nil
237+
}
238+
239+
type ExtKeyUsageConfig asn1.ObjectIdentifier
240+
241+
func (e *ExtKeyUsageConfig) UnmarshalJSON(data []byte) error {
242+
var value string
243+
if err := json.Unmarshal(data, &value); err != nil {
244+
return err
245+
}
246+
var oid asn1.ObjectIdentifier
247+
switch value {
248+
case "ServerAuth":
249+
oid = oidServerAuth
250+
default:
251+
for _, part := range strings.Split(value, ".") {
252+
v, err := strconv.Atoi(part)
253+
if err != nil || v < 0 {
254+
return fmt.Errorf("invalid extended key usage: %q", value)
255+
}
256+
oid = append(oid, v)
257+
}
258+
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
259+
return fmt.Errorf("invalid extended key usage: %q", value)
260+
}
261+
}
262+
*e = ExtKeyUsageConfig(oid)
263+
return nil
264+
}

demo/config_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestTrustAnchorID(t *testing.T) {
9+
invalidTests := []string{"nope", "-1.-1", "1..2", "1.", ".1", "4294967296"}
10+
for _, test := range invalidTests {
11+
_, ok := TrustAnchorIDFromString(test)
12+
if ok {
13+
t.Errorf("TrustAnchorIDFromString(%q) unexpected succeeded", test)
14+
}
15+
}
16+
17+
var validTests = []struct {
18+
str string
19+
id []byte
20+
}{
21+
{"32473.1", []byte{0x81, 0xfd, 0x59, 0x01}},
22+
{"4294967295", []byte{0x8f, 0xff, 0xff, 0xff, 0x7f}},
23+
}
24+
for _, tt := range validTests {
25+
id, ok := TrustAnchorIDFromString(tt.str)
26+
if !ok {
27+
t.Errorf("TrustAnchorIDFromString(%q) unexpected failed", tt.str)
28+
continue
29+
}
30+
if !bytes.Equal(id, tt.id) {
31+
t.Errorf("TrustAnchorIDFromString(%q) was %x, wanted %x", tt.str, []byte(id), tt.id)
32+
continue
33+
}
34+
if id.String() != tt.str {
35+
t.Errorf("id.String() was %s, wanted %s", id, tt.str)
36+
continue
37+
}
38+
}
39+
}

demo/cosign.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package main
2+
3+
import (
4+
"crypto"
5+
"crypto/ecdsa"
6+
"crypto/ed25519"
7+
"crypto/elliptic"
8+
"crypto/rand"
9+
_ "crypto/sha256"
10+
_ "crypto/sha512"
11+
"crypto/x509"
12+
"fmt"
13+
14+
"golang.org/x/crypto/cryptobyte"
15+
)
16+
17+
func addTrustAnchorID(b *cryptobyte.Builder, id TrustAnchorID) {
18+
b.AddUint8LengthPrefixed(func(child *cryptobyte.Builder) {
19+
child.AddBytes(id)
20+
})
21+
}
22+
23+
func Cosign(c *CosignerConfig, logID TrustAnchorID, start, end int, hash *HashValue) ([]byte, error) {
24+
b := cryptobyte.NewBuilder(nil)
25+
b.AddBytes([]byte("mtc-subtree/v1\n\x00"))
26+
addTrustAnchorID(b, c.CosignerID)
27+
addTrustAnchorID(b, logID)
28+
if !IsValidSubtree(start, end) {
29+
return nil, fmt.Errorf("invalid subtree")
30+
}
31+
b.AddUint64(uint64(start))
32+
b.AddUint64(uint64(end))
33+
b.AddBytes((*hash)[:])
34+
inp, err := b.Bytes()
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
priv, err := x509.ParsePKCS8PrivateKey(c.PrivateKey)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
var signer crypto.Signer
45+
var opts crypto.SignerOpts
46+
switch c.SignatureAlgorithm {
47+
case SignatureAlgorithmP256WithSHA256:
48+
ec, ok := priv.(*ecdsa.PrivateKey)
49+
if !ok {
50+
return nil, fmt.Errorf("not an EC key")
51+
}
52+
if ec.Curve != elliptic.P256() {
53+
return nil, fmt.Errorf("not a P-256 key")
54+
}
55+
signer = ec
56+
opts = crypto.SHA256
57+
case SignatureAlgorithmP384WithSHA384:
58+
ec, ok := priv.(*ecdsa.PrivateKey)
59+
if !ok {
60+
return nil, fmt.Errorf("not an EC key")
61+
}
62+
if ec.Curve != elliptic.P384() {
63+
return nil, fmt.Errorf("not a P-384 key")
64+
}
65+
signer = ec
66+
opts = crypto.SHA384
67+
case SignatureAlgorithmEd25519:
68+
// Unlike the others, ed25519.PrivateKey is not returned as a pointer.
69+
ed, ok := priv.(ed25519.PrivateKey)
70+
if !ok {
71+
return nil, fmt.Errorf("not an Ed25519 key")
72+
}
73+
signer = ed
74+
default:
75+
return nil, fmt.Errorf("unexpected signature algorithm %s", c.SignatureAlgorithm)
76+
}
77+
78+
return crypto.SignMessage(signer, rand.Reader, inp, opts)
79+
}

0 commit comments

Comments
 (0)