136 lines
4.4 KiB
Go
136 lines
4.4 KiB
Go
package arc
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// VerifyChain checks an ARC chain against the public keys of the sealing
|
|
// domains and returns the chain validation state a downstream hop should
|
|
// report in its own "cv=" tag.
|
|
//
|
|
// publicKeyFor resolves the key for a domain and selector; it is injected so
|
|
// the caller decides how lookups happen (DNS in production, a fixed map in a
|
|
// test).
|
|
func VerifyChain(message []byte, publicKeyFor func(domain, selector string) (crypto.PublicKey, error)) (ChainValidation, error) {
|
|
headers, body := splitMessage(string(message))
|
|
|
|
highest := 0
|
|
for _, h := range headers {
|
|
if strings.EqualFold(h.name, HeaderSeal) {
|
|
if i, err := strconv.Atoi(parseTags(h.value)["i"]); err == nil && i > highest {
|
|
highest = i
|
|
}
|
|
}
|
|
}
|
|
|
|
if highest == 0 {
|
|
return ChainNone, nil
|
|
}
|
|
if highest > maxChainInstance {
|
|
return ChainFail, fmt.Errorf("arc: chain exceeds the maximum of %d instances", maxChainInstance)
|
|
}
|
|
|
|
// RFC 8617 section 5.2: the most recent ARC-Message-Signature must
|
|
// validate, and every ARC-Seal in the chain must validate.
|
|
if err := verifyMessageSignature(headers, body, highest, publicKeyFor); err != nil {
|
|
return ChainFail, err
|
|
}
|
|
|
|
for instance := 1; instance <= highest; instance++ {
|
|
if err := verifySeal(headers, instance, publicKeyFor); err != nil {
|
|
return ChainFail, err
|
|
}
|
|
}
|
|
|
|
return ChainPass, nil
|
|
}
|
|
|
|
func verifyMessageSignature(headers []header, body string, instance int, publicKeyFor func(string, string) (crypto.PublicKey, error)) error {
|
|
value, ok := findInstanceHeader(headers, HeaderMessageSignature, instance)
|
|
if !ok {
|
|
return fmt.Errorf("arc: no %s for instance %d", HeaderMessageSignature, instance)
|
|
}
|
|
|
|
tags := parseTags(value)
|
|
if tags["bh"] != bodyHash(body) {
|
|
return fmt.Errorf("arc: body hash mismatch at instance %d", instance)
|
|
}
|
|
|
|
signedNames := strings.Split(tags["h"], ":")
|
|
material := canonicalHeaderBlock(headers, signedNames)
|
|
material += canonicalizeHeaderRelaxed(HeaderMessageSignature, stripSignature(value))
|
|
|
|
return verifySignature(tags, []byte(material), publicKeyFor)
|
|
}
|
|
|
|
func verifySeal(headers []header, instance int, publicKeyFor func(string, string) (crypto.PublicKey, error)) error {
|
|
value, ok := findInstanceHeader(headers, HeaderSeal, instance)
|
|
if !ok {
|
|
return fmt.Errorf("arc: no %s for instance %d", HeaderSeal, instance)
|
|
}
|
|
|
|
var material strings.Builder
|
|
for i := 1; i < instance; i++ {
|
|
for _, name := range []string{HeaderAuthenticationResults, HeaderMessageSignature, HeaderSeal} {
|
|
if previous, ok := findInstanceHeader(headers, name, i); ok {
|
|
material.WriteString(canonicalizeHeaderRelaxed(name, previous))
|
|
}
|
|
}
|
|
}
|
|
|
|
if aar, ok := findInstanceHeader(headers, HeaderAuthenticationResults, instance); ok {
|
|
material.WriteString(canonicalizeHeaderRelaxed(HeaderAuthenticationResults, aar))
|
|
}
|
|
if ams, ok := findInstanceHeader(headers, HeaderMessageSignature, instance); ok {
|
|
material.WriteString(canonicalizeHeaderRelaxed(HeaderMessageSignature, ams))
|
|
}
|
|
material.WriteString(canonicalizeHeaderRelaxed(HeaderSeal, stripSignature(value)))
|
|
|
|
return verifySignature(parseTags(value), []byte(material.String()), publicKeyFor)
|
|
}
|
|
|
|
// stripSignature blanks the "b=" tag, which is how the signature over a header
|
|
// that contains itself is defined (RFC 6376 section 3.7).
|
|
func stripSignature(value string) string {
|
|
index := strings.LastIndex(value, "b=")
|
|
if index < 0 {
|
|
return value
|
|
}
|
|
return value[:index+2]
|
|
}
|
|
|
|
func verifySignature(tags map[string]string, material []byte, publicKeyFor func(string, string) (crypto.PublicKey, error)) error {
|
|
signature, err := base64.StdEncoding.DecodeString(tags["b"])
|
|
if err != nil {
|
|
return fmt.Errorf("arc: signature is not valid base64: %w", err)
|
|
}
|
|
|
|
key, err := publicKeyFor(tags["d"], tags["s"])
|
|
if err != nil {
|
|
return fmt.Errorf("arc: cannot resolve key for %s/%s: %w", tags["d"], tags["s"], err)
|
|
}
|
|
|
|
switch typed := key.(type) {
|
|
case ed25519.PublicKey:
|
|
if !ed25519.Verify(typed, material, signature) {
|
|
return fmt.Errorf("arc: ed25519 signature does not verify for %s", tags["d"])
|
|
}
|
|
return nil
|
|
case *rsa.PublicKey:
|
|
digest := sha256.Sum256(material)
|
|
if err := rsa.VerifyPKCS1v15(typed, crypto.SHA256, digest[:], signature); err != nil {
|
|
return fmt.Errorf("arc: rsa signature does not verify for %s: %w", tags["d"], err)
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("arc: unsupported key type %T", key)
|
|
}
|
|
}
|