Skip to content

Commit d565a32

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

2 files changed

Lines changed: 160 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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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/sha256"
20+
"crypto/x509"
21+
"encoding/asn1"
22+
"encoding/base64"
23+
"encoding/binary"
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+
pubKBytes, name := keyAndIDFromCert(*certFile)
46+
47+
h := keyHashMLDSA(name, pubKBytes)
48+
vkey := fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubKBytes))
49+
50+
if _, err := fnote.NewVerifierForCosignatureV1(vkey); err != nil {
51+
exit("Generated vkey is invalid: %v", err)
52+
}
53+
54+
fmt.Println(vkey)
55+
}
56+
57+
func keyAndIDFromCert(path string) ([]byte, string) {
58+
c, err := os.ReadFile(path)
59+
if err != nil {
60+
exit("Failed to read file %q: %v", path, err)
61+
}
62+
63+
der, _ := pem.Decode(c)
64+
crt, err := x509.ParseCertificate(der.Bytes)
65+
if err != nil {
66+
exit("Failed to parse certificate: %v", err)
67+
}
68+
69+
var relOID string
70+
for _, n := range crt.Subject.Names {
71+
if mtcTrustAnchorExperimental.Equal(n.Type) {
72+
v, ok := n.Value.(string)
73+
if !ok {
74+
exit("Found MTC trust anchor ID %q, but couldn't convert value to a string", n)
75+
}
76+
relOID = v
77+
break
78+
}
79+
}
80+
if relOID == "" {
81+
exit("No MTC trust anchor ID present in cert.")
82+
}
83+
name := fmt.Sprintf("oid/1.3.6.1.4.1.44363.47.1.%s", relOID)
84+
85+
86+
pub := crt.RawSubjectPublicKeyInfo[22:]
87+
if l := len(pub); l != 1312 {
88+
exit("Invalid public key length %d, expected 1312", l)
89+
}
90+
91+
return append([]byte{algMLDSA44}, pub...), name
92+
}
93+
94+
func keyHashMLDSA(name string, key []byte) uint32 {
95+
h := sha256.New()
96+
h.Write([]byte(name))
97+
h.Write([]byte("\n"))
98+
h.Write(key)
99+
sum := h.Sum(nil)
100+
return binary.BigEndian.Uint32(sum)
101+
}
102+
103+
func exit(m string, a ...any) {
104+
fmt.Printf(m, a...)
105+
os.Exit(1)
106+
}

0 commit comments

Comments
 (0)