Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Go implementation of Merkle Tree Certificates

License

NotificationsYou must be signed in to change notification settings

bwesterb/mtc

Repository files navigation

🚨 Merkle Tree Certificates (MTC) is a moving target.

Implementation ofMerkle Tree Certificates for TLSin Go. This contains a Certification Authority (CA), Mirror, andcode to verify certificates.This does not contain integration with TLS (yet) or the ACME bits (yet).At the moment we differ from-04 andmain branch of the specification,by including someunmerged PRs.

Demo

For a proper introduction and motivation, check out thedraft specificationandDavid's TLS working group presentation at IETF116.

Merkle Tree Certificates is anoptimisation to the WebPKI (includingCertificate Transparency)motivated by thelarge sizes oftypical post-quantum signatures and public keys,to reduce the number of keys and signatures required for the common case where

  1. Certificate issuance does not have to be immediate. For instance, becausea certificate can be requested ahead of time for an existing domainby anACME clientlikecertbot.

  2. The relying party (eg. browser) has a trusted update mechanism.There are also several ways to use MTC without trusted update mechanism,with various trade-offs: see theRelying Party Policysection of the specification.

If we're not in this case (which is estimated to beless than 0.1% of the time),then we fall back to regular X.509 certificates.

Intermezzo:mtc commandline tool

To play around with MTC, you can install themtc commandline tool:

$ go install github.com/bwesterb/mtc/cmd/mtc@v0.1.2

Assertions

In MTC CAs certifyassertions, which bind asubject to aclaim.An informal example of an assertion is:

For TLS, you can trust the P-256 public keya02342ff2…23efwhen visitingexample.com or198.51.100.60.

The first part (TLS and the public key) is thesubject, and thelatter (domain and IP) are theclaim.Roughly, an assertion is like a certificate without the signature.

You can create a request for an assertion to be signed with themtc new-assertion-request command. First, let's quickly createa P-256 public key to play with.

$ openssl ecparam -name prime256v1 -genkey -out p256.priv$ openssl ec -in p256.priv -pubout -out p256.pub

Now we create an assertion that this P-256 public key shouldbe valid forexample.com and198.51.100.60, and write it tothemy-asr.

$ mtc new-assertion-request --tls-pem p256.pub --dns example.com --ip4 198.51.100.60 -o my-asr

Let's check it usingmtc inspect:

$ mtc inspect assertion-request my-asrchecksum         2024bdbffe399acca37d299a03c047aa33ef596ae471c17698a0566d00951bd9not_after        unsetsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]evidence-list (0 entries)

An assertion request can contain two bits of extra information besidesthe assertion itself. First is anot_after field that limitsthe validity of the assertion when published.The second is optional "evidence" that's published alongside theassertions. In the future this could for instance be used forserialized DNSSEC proofs.

We can also create an assertion request derived from an existing X.509certificate at a TLS server using the-X flag:

$ mtc new-assertion-request -X example.com:443 | mtc inspect assertion-requestchecksum         015d4da06412b4e48f8d93bcbe7bbf43c4684579322cbfbc88d8b653bb2f7e51not_after        unsetsubject_type     TLSsignature_scheme p256public_key_hash  8d566a5407ab85b413925911c4ce6b13013516006fa8568bf2ec58b9abe04af1dns              [example.com]dns_wildcard     [example.com]evidence-list (1 entries)umbilical certificate 0  subject    CN=*.example.com,O=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US  issuer     CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US  serial_no  ad893bafa68b0b7fb7a404f06ecaf9a  not_before 2025-01-15 00:00:00 +0000 UTC  not_after  2026-01-15 23:59:59 +0000 UTC certificate 1  subject    CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US  issuer     CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US  serial_no  b00e92d4d6d731fca3059c7cb1e1886  not_before 2021-04-14 00:00:00 +0000 UTC  not_after  2031-04-13 23:59:59 +0000 UTC

Batches, merkle trees and signed validity windows

An MTCA doesn't give you a certificate for an assertion request immediately.Instead, assertions are queued and issued inbatches with a fixed rhythm,for instance a batch is issued once every hour.All assertions in a single batch by default are valid for the same period oftime, thevalidity window, which is, for instance, two weeks.The CA publishes these batches publicly over HTTP.

For each batch, the CA computes aMerkle tree.This condenses all the assertions in that batch into a singletree head hash.For every batch, the CA signs that tree head together with all the tree headsof the currently valid batches. This signature, together with thosesigned tree heads is called thesigned validity window for that batch,which is published alongside the assertions.

Creating a CA

Let's create an MTC CA.

$ mtc ca new --batch-duration 5m --lifetime 1h 62253.12.15 ca.example.com/path

This creates a new MTC CA in the current working directory. It's configuredto issue a batch every 5 minutes, and for each batch to be valid for an hour.For a real CA we'd want batch durations in the order of an hour,and a lifetime of a week or two. In this demo we shorten things a bit, sowe don't have to wait too long.

The CA is configured to be hosted athttps://ca.example.com/path andto be identified by thetrust anchor identifier 62253.12.15.You can get your own by requesting aprivate enterprise number here.

Let's have a look at the files created:

$ find .../signing.key./www./www/mtc./www/mtc/v04b./www/mtc/v04b/ca-params./www/mtc/v04b/batches./queue./tmp

Thesigning.key file contains the private key of the keypair used by the CA.

Thewww folder contains the files that have to be servedathttps://ca.example.com/path. At the moment, the only file of interestisca-params, which contains the information about the CA:

$ mtc inspect ca-params www/mtc/v04b/ca-paramsissuer                 62253.12.15start_time             1745420554 2025-04-23 15:02:34 +0000 UTCbatch_duration         300        5m0slife_time              3600       1h0m0sstorage_window_size    24         2h0m0svalidity_window_size   12server_prefix          ca.example.com/pathpublic_key fingerprint ml-dsa-87:84489bcb42b411a85d163ae95e7deb92b106a75840819a985e44d0e01ae3238e

Thebatches folder is empty, because there are no batches issued yet.

Thequeue file contains the assertion requests that will be fulfilledduring the next issuance.

Issuing our first batch

Let's issue our first assertion. We can read the assertion request from disk we'vecreated earlier withmtc new-assertion-request:

$ mtc ca queue -i my-asr $ mtc ca show-queuenot_after        2025-04-23 16:02:33 +0000 UTCsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]evidence-list (0 entries)Total number of assertion requests in queue: 1

We can also queue an assertion request ad hoc:

$ mtc ca queue --tls-pem p256.pub -d other.example.com -d second.example.com$ mtc ca show-queue | tail -n 10not_after        2025-04-23 16:02:33 +0000 UTCsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]evidence-list (0 entries)Total number of assertion requests in queue: 2

Let's issue our first batch.

$ mtc ca issue2025/04/23 17:05:20 INFO Starting issuance time=2025-04-23T15:05:20.664Z2025/04/23 17:05:20 INFO Current state expectedStored=0 expectedActive=0 existingBatches=⌀2025/04/23 17:05:20 INFO To issue batches=0

And let's check:

$ find .../signing.key./www./www/mtc./www/mtc/v04b./www/mtc/v04b/ca-params./www/mtc/v04b/batches./www/mtc/v04b/batches/0./www/mtc/v04b/batches/0/validity-window./www/mtc/v04b/batches/0/tree./www/mtc/v04b/batches/0/entries./www/mtc/v04b/batches/0/evidence./www/mtc/v04b/batches/0/index./www/mtc/v04b/batches/latest./queue./tmp

We see batch0 has been created.latest is a symlink to to0.

Now, let's have a look at the batch. Theentries file is essentiallythe list of assertions: the difference between a regular assertionand an entry is that with an entry, the public key has been replacedby the hash of the public key.

$ mtc inspect entries www/mtc/v04b/batches/0/entrieskey              0b65c8a5f69e88fd1eb58dff4d317f6173bd31773e14d99ace88a2aa7062fdd9not_after        2025-04-23 16:02:33 +0000 UTCsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [other.example.com second.example.com]key              78b5ccc905b693659bf6581011f8efb17fd7aedf9ca70a196a22923f560feecanot_after        2025-04-23 16:02:33 +0000 UTCsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]Total number of entries: 2

Thevalidity-window is the signed validity window: the tree heads ofthe currently valid batches:

$ mtc inspect -ca-params www/mtc/v04b/ca-params validity-window www/mtc/v04b/batches/0/validity-window               signature       ✅batch_number    0tree_heads[0]   043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345tree_heads[-1]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-2]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-3]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-4]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-5]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-6]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-7]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-8]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-9]  a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-10] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-11] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cf

We need to pass theca-params file to be able to parse the file, andcheck the signature therein. (As not all previous batches exist, they usethe placeholder value for an empty tree.)

Thetree file contains the Merkle tree.

$ mtc inspect tree www/mtc/v04b/batches/0/tree number of leaves 2number of nodes  3tree head        043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345

Theevidence file contains the optional evidence that can be providedwith the assertion request. We did not pass any, so they're empty:

$ mtc inspect evidence www/mtc/v04b/batches/0/evidenceevidence-list (0 entries)evidence-list (0 entries)Total number of evidence lists: 2

Finally, theindex file allows a quick lookup inentries (andevidence)by key (hash of the assertion):

$ mtc inspect index www/mtc/v04b/batches/0/index                                                             key   seqno  offset0b65c8a5f69e88fd1eb58dff4d317f6173bd31773e14d99ace88a2aa7062fdd9       0       0       078b5ccc905b693659bf6581011f8efb17fd7aedf9ca70a196a22923f560feeca       1      91       3total number of entries: 2

Issuing more batches

As we just issued a new batch, we need to wait a while before thenext batch is ready to issue.

Let's queue some more assertions, wait a bit, and issue a new batch.

$ mtc ca queue --tls-pem p256.pub -d 1.example.com$ mtc ca queue --tls-pem p256.pub -d 2.example.com$ mtc ca queue --tls-pem p256.pub -d 3.example.com$ mtc ca issue2025/04/23 17:12:45 INFO Starting issuance time=2025-04-23T15:12:45.869Z2025/04/23 17:12:45 INFO Current state expectedStored=0,…,2 expectedActive=0,…,2 existingBatches=02025/04/23 17:12:45 INFO To issue batches=1,2$ find .../signing.key./www./www/mtc./www/mtc/v04b./www/mtc/v04b/ca-params./www/mtc/v04b/batches./www/mtc/v04b/batches/0./www/mtc/v04b/batches/0/validity-window./www/mtc/v04b/batches/0/tree./www/mtc/v04b/batches/0/entries./www/mtc/v04b/batches/0/evidence./www/mtc/v04b/batches/0/latest./www/mtc/v04b/batches/0/index./www/mtc/v04b/batches/latest./www/mtc/v04b/batches/1./www/mtc/v04b/batches/1/validity-window./www/mtc/v04b/batches/1/tree./www/mtc/v04b/batches/1/entries./www/mtc/v04b/batches/1/evidence./www/mtc/v04b/batches/1/index./www/mtc/v04b/batches/2./www/mtc/v04b/batches/2/validity-window./www/mtc/v04b/batches/2/tree./www/mtc/v04b/batches/2/entries./www/mtc/v04b/batches/2/evidence./www/mtc/v04b/batches/2/index./queue./tmp

As we waited a bit longer, the current batch is2, which will containthe queued assertions. The batch1 in between will be empty.Nowlatest points to2, and its signed validity window is more interesting.

$ mtc inspect -ca-params www/mtc/v04b/ca-params validity-window www/mtc/v04b/batches/1/validity-windowsignature      ✅batch_number   2tree_heads[2]  03a95ba3c354e2b0eb4bea9b111dbc8b97e2c90b85ddcc63d4b635b16f77005dtree_heads[1]  7ceda88ec6c8e34ecacde47588e2605fb86192b94ca96cb897fa6ff442198c8ctree_heads[0]  043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345tree_heads[-1] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-2] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-3] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-4] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-5] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-6] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-7] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-8] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cftree_heads[-9] a7b081f10c7116c30781a957c3f52625c4d831c8d61ceea021db101ab3c901cf

Creating a certificate

In MTC, acertificate is an assertion together with a trust anchor identifier(to identify the CA), and an authentication path in the Merkle tree.Let's create one for our initial assertion.

$ mtc ca cert -i my-asr -o my-cert

If we inspect the certificate, it can recompute the root from theauthentication path and CA parameters:

$ mtc inspect -ca-params www/mtc/v04b/ca-params cert my-certsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]proof_type           merkle_tree_sha256CA TAI               62253.12.15Batch number         0index                1recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345authentication path 8964f010faa9e499b21917f8792b541b7b1ac19f313a5d53094c698c2edc330b

This is indeed the root of batch0, and so this certificate is valid.

Verify certificate

To automate this, there is themtc verify command that takesa certificate, the CA parameters, and a signed validity window.

$ mtc verify -ca-params www/mtc/v04b/ca-params -validity-window www/mtc/v04b/batches/1/validity-window my-cert$ echo $?0

Status code 0 means verification succeeded.

For transparency, you should not get the signed validity window directlyfrom the CA, but rather from one or more mirrors (see below).

Run CA as server

An Merkle Tree CA can be run just from the commandline, but it's oftenmore convenient to run it as a server. To start the server, run:

$ mtc ca serve -listen-addr localhost:8080

This will accept HTTP requests onlocalhost:8080 and serve the staticfiles. It will also accept queue requests; periodically issue new batches;and return issued certificates.

Get and inspect CA parameters.

$ curl -s "http://localhost:8080/mtc/v04b/ca-params" -o ca-params$ mtc inspect ca-params ca-paramsissuer                 62253.12.15start_time             1745420554 2025-04-23 15:02:34 +0000 UTCbatch_duration         300        5m0slife_time              3600       1h0m0sstorage_window_size    24         2h0m0svalidity_window_size   12server_prefix          ca.example.com/pathpublic_key fingerprint ml-dsa-87:84489bcb42b411a85d163ae95e7deb92b106a75840819a985e44d0e01ae3238e

Queue up the assertion created in above.

$ curl -X POST "http://localhost:8080/ca/queue" --data-binary "@my-asr" -w "%{http_code}"200

After it's been issued, we can get the certificate via the/ca/cert endpoint:

$ curl -X POST "http://localhost:8080/ca/cert" --data-binary "@my-asr" -o my-cert$ mtc inspect -ca-params ca-params cert my-certsubject_type     TLSsignature_scheme p256public_key_hash  20b57b9c55dab26db14fb6cc801b7d7294cbf448abb1196e1ffc19d73013498adns              [example.com]ip4              [198.51.100.60]proof_type           merkle_tree_sha256CA TAI               62253.12.15Batch number         0index                1recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345authentication path 8964f010faa9e499b21917f8792b541b7b1ac19f313a5d53094c698c2edc330b

Mirroring a CA

We can set up a new mirror with themtc mirror new command:

$ mtc mirror new ca.example.com/path

This will download theca-paramsfromhttps://ca.example.com/path/mtc/v04b/ca-params andset up a directory structure similar to that of a CA:

$ find .../www./www/mtc./www/mtc/v04b./www/mtc/v04b/ca-params./www/mtc/v04b/batches./tmp

To bring the mirror up to date with the CA, use theupdate command:

$ mtc mirror update2025/04/24 11:54:53 INFO Current state expectedStoredRemote=0 expectedActiveRemote=0 latestRemoteBatch=0 mirroredBatches=⌀2025/04/24 11:54:53 INFO Fetching batch=02025/04/24 11:54:53 INFO Next batch at the earliest in 49s$ find .../www./www/mtc./www/mtc/v04b./www/mtc/v04b/ca-params./www/mtc/v04b/batches./www/mtc/v04b/batches/0./www/mtc/v04b/batches/0/validity-window./www/mtc/v04b/batches/0/tree./www/mtc/v04b/batches/0/entries./www/mtc/v04b/batches/0/evidence./www/mtc/v04b/batches/latest./tmp

Local testing

To make local testing convenient, when you uselocalhost as server prefix,the mirror will usehttp instead ofhttps. This allows a quick testingset up as follows:

# Set up a CA in the ca folder$ mtc ca -p ca new --batch-duration 5m --lifetime 1h 62253.12.15 localhost:8080$ mtc ca -p ca queue -X example.com:443$ mtc ca -p ca issue$ mtc ca -p ca server -listen-addr localhost:8080 &# Set up a mirror of the CA in the mirror folder$ mtc mirror -p mirror new localhost:8080$ mtc mirror -p mirror update

About

Go implementation of Merkle Tree Certificates

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors4

  •  
  •  
  •  
  •  

Languages


[8]ページ先頭

©2009-2025 Movatter.jp