Skip to content

Commit 6965fbd

Browse files
committed
PEM to vkey for MTC
1 parent d2d9eaa commit 6965fbd

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

cmd/mtc/pemtovkey/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# PEMtoVKey
2+
3+
`pemtovkey` is a small utility to convert a PEM encoded X.509 certificate containing an MTC cosigner ID to a vkey for use with the MTC mirror.
4+
5+
## Example use
6+
7+
Here is the PEM file containing Cloudflare's CA ID for one of their test logs:
8+
9+
```
10+
-----BEGIN CERTIFICATE-----
11+
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
44+
-----END CERTIFICATE-----
45+
```
46+
47+
Assuming that PEM data is in a file called `cert.pem`, then running the command produces the following output:
48+
49+
```
50+
$ go run ./cmd/mtc/pemtovkey --cert=cert.pem
51+
oid/1.3.6.1.4.1.44363.47.1.11129.11.99.1+f3cdeae8+BqEI2mLl390er8KhOhcQIXsl5MiNpfCY+J8pPHfWOSIKph/S87zYk9IwoM/T+z5Q0VNMHw1AZrluGTb1VAEPzzZhdl/M6hPaMv5YCOvvhk68C9eeFzyibqH/WppChBwnfKx3/8DwtEtsb6tL8V0QYdOnEg9h8F+HxIusAaL5zBLpagaswSM1gSzmNf8EYeFqGC21CtdJqIzXtDVmn448mt7ojrxjekR4p9EoLDT74l6Hm2ONdbeBf4SexYfiXkb9JV3VcCzWzCzcol5MrNwpbYj1XfruEFlFdI5vffpS9Kj3ZkFgX5yEEVPv+WHIn9nqrL1BgqagIUFlYZdnrS8gHBiyD2Dtk4IZ6JfZ9xcFrvahoduGqprhbrCME/1sl3gRpw0rsg6xEXjko3EcgmOF0iGz+6df/pNDcc+6/3DvIp82DxSfLmTuwVN7lY/C4HT1LY7cIg7DzIZq4Qk8okkEuNv6mdnWZohIT+c8EuwFr8IG1kZqAA8b1VUwOCxMRr2YnxW0kUHPUeEADRk5SchNlg3L1mz9JfNFGOSGtFweCYztKuEZdxKgZWQ3iY+fF97KryfqkvYTfEj2KiIWKOT1xo/lcmFxrDe5+sMprle2wv3w2EOnkLk390zWr/NaLm2l8iic9L0t+SPLaWE17t9+t0TD34eaPrQOUEzYG8DAMMoOkFN+JbVGg7dEjZ1B/Lo85g9yHZaeAOb/4V0SwMQevCt9kbF9yEDCvWEf1wIrpYuKhYY+ErssEWdZzfhD3EDMnLAFPoLapL6AGZmxw0DmiOYJdhuexEEOKJuc9iEf4PCmnuVtxkOpiuVpZeoO8jjYFOj6WUpLo60/Pn8TTdKH4WJZyM3uOjGHV4oMNBSQzRb59IpbE/Xmifr4OR8+IIWM+3e1/VXcPpc/pgkQT5k/Ehgh3NFD1knfbtbq9rlOiszpz/sfEQ38rGeCRFU9hygDSIfkPV8q8Cqwm9IuERD1BNereYQBeF/TuS0+AgPJiGlnsJnw/KNrVkjnUMTQqeMbF9vx0VdN4v/RH+TGMgFKUfhl+fcUyjgni27/Wq9EqhrfZDsUFwaJmbJ021f2n7mGQ2m8F30GnnVjDtrNdZc6ssFB6BoN+YL7JM2IdyQDVy44PMSCqsTCLkquIR85GJPqfINPO+LWSbIKlcOObwLjxhcMzHguUm2eTews6YtdtnCSM9sjv2HVOJqiCbDB85559AhebR8m7cJCRDiw7JoocdDhBEc1lZIUOpq8VnivPwzBG92SXx5NbWUqYs2xkwFlf0vBMqD0hkTE/rUlWr48T7Fz9ITOVt8OJi9t9E+wYnwY7+CW6nzPFFscoyWC+WKxChnoOPNiZH7AUPa76rZkB2PFWBTKGkm220ZR66DGa16pxCUB2ZqCFvkRvEnq/sWM9jru3PBSTdBuLtZfWdt5st7HKcZkXHUV69opEchDtotairk1YCyBNnsvIOf5f/IOzKYCNEBuUAnfWxnQqSUjS89GZhBBoMOLs1nQNjTovy+Zgt5y6PVAsZjLvfRzYRFcwozCXQUrtlbpUbd17yL1XUbVg74shXywahFl6rSzrw2h8KG/5YpE5NLZIdko/uBtsXH0X+/wet4C/ygp/uA0Gz5550WXoM8IM2VbzuEA4xgjRMyWSP+B1SpCxG2xnR98shHtkYAnx2BNw9AQ44ygo2/W3Oq+IwCWvmle5YxiiYadblXy4R28oOIhpG4wHXMsCkaegT7O4Wf9jmcd5hrA2r4=
52+
```
53+
54+
The `vkey` is the final (very long) line

cmd/mtc/pemtovkey/main.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2026 The Tessera authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// pemtovkey is a utility to convert a PEM containing an ML-DSA-44 key and MTC CA subject to a vkey representation.
16+
package main
17+
18+
import (
19+
"crypto/x509"
20+
"crypto/sha256"
21+
"encoding/binary"
22+
"encoding/asn1"
23+
"encoding/base64"
24+
"encoding/pem"
25+
"flag"
26+
"fmt"
27+
"os"
28+
29+
fnote "github.com/transparency-dev/formats/note"
30+
)
31+
32+
const (
33+
algMLDSA44 = 0x06
34+
)
35+
36+
var (
37+
certFile = flag.String("cert", "", "Path to x509 PEM file to read")
38+
39+
mtcTrustAnchorExperimental = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 47, 1}
40+
)
41+
42+
func main() {
43+
flag.Parse()
44+
45+
c, err := os.ReadFile(*certFile)
46+
if err != nil {
47+
exit("Failed to read file %q: %v", *certFile, err)
48+
}
49+
50+
der, _ := pem.Decode(c)
51+
crt, err := x509.ParseCertificate(der.Bytes)
52+
if err != nil {
53+
exit("Failed to parse certificate: %v", err)
54+
}
55+
56+
var relOID string
57+
for _, n := range crt.Subject.Names {
58+
if mtcTrustAnchorExperimental.Equal(n.Type) {
59+
v, ok := n.Value.(string)
60+
if !ok {
61+
exit("Found MTC trust anchor ID %q, but couldn't convert value to a string", n)
62+
}
63+
relOID = v
64+
break
65+
/*
66+
if err := asn1.Unmarshal(v, &relOID); err != nil {
67+
exit("Failed to unmarshal relative OID MTC ID from Subject %q: %v", crt.Subject, err)
68+
}
69+
*/
70+
}
71+
}
72+
if relOID == "" {
73+
exit("No MTC trust anchor ID present in cert.")
74+
}
75+
76+
name := fmt.Sprintf("oid/1.3.6.1.4.1.44363.47.1.%s", relOID)
77+
78+
pub := crt.RawSubjectPublicKeyInfo[22:]
79+
80+
if l := len(pub); l != 1312 {
81+
exit("Invalid public key length %d, expected 1312", l)
82+
}
83+
pubKBytes := append([]byte{algMLDSA44}, pub...)
84+
85+
h := keyHashMLDSA(name, pubKBytes)
86+
87+
vkey := fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubKBytes))
88+
89+
_, err = fnote.NewVerifierForCosignatureV1(vkey)
90+
if err != nil {
91+
exit("Generated vkey is invalid: %v", err)
92+
}
93+
94+
fmt.Println(vkey)
95+
}
96+
97+
func keyHashMLDSA(name string, key []byte) uint32 {
98+
h := sha256.New()
99+
h.Write([]byte(name))
100+
h.Write([]byte("\n"))
101+
h.Write(key)
102+
sum := h.Sum(nil)
103+
return binary.BigEndian.Uint32(sum)
104+
}
105+
106+
func exit(m string, a ...any) {
107+
fmt.Printf(m, a...)
108+
os.Exit(1)
109+
}

0 commit comments

Comments
 (0)