212 lines
9.0 KiB
Go
212 lines
9.0 KiB
Go
// Copyright 2018 Google LLC. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package ctutil contains utilities for Certificate Transparency.
|
|
package ctutil
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
|
|
ct "github.com/google/certificate-transparency-go"
|
|
"github.com/google/certificate-transparency-go/tls"
|
|
"github.com/google/certificate-transparency-go/x509"
|
|
)
|
|
|
|
var emptyHash = [sha256.Size]byte{}
|
|
|
|
// LeafHashB64 does as LeafHash does, but returns the leaf hash base64-encoded.
|
|
// The base64-encoded leaf hash returned by B64LeafHash can be used with the
|
|
// get-proof-by-hash API endpoint of Certificate Transparency Logs.
|
|
func LeafHashB64(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (string, error) {
|
|
hash, err := LeafHash(chain, sct, embedded)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(hash[:]), nil
|
|
}
|
|
|
|
// LeafHash calculates the leaf hash of the certificate or precertificate at
|
|
// chain[0] that sct was issued for.
|
|
//
|
|
// sct is required because the SCT timestamp is used to calculate the leaf hash.
|
|
// Leaf hashes are unique to (pre)certificate-SCT pairs.
|
|
//
|
|
// This function can be used with three different types of leaf certificate:
|
|
// - X.509 Certificate:
|
|
// If using this function to calculate the leaf hash for a normal X.509
|
|
// certificate then it is enough to just provide the end entity
|
|
// certificate in chain. This case assumes that the SCT being provided is
|
|
// not embedded within the leaf certificate provided, i.e. the certificate
|
|
// is what was submitted to the Certificate Transparency Log in order to
|
|
// obtain the SCT. For this case, set embedded to false.
|
|
// - Precertificate:
|
|
// If using this function to calculate the leaf hash for a precertificate
|
|
// then the issuing certificate must also be provided in chain. The
|
|
// precertificate should be at chain[0], and its issuer at chain[1]. For
|
|
// this case, set embedded to false.
|
|
// - X.509 Certificate containing the SCT embedded within it:
|
|
// If using this function to calculate the leaf hash for a certificate
|
|
// where the SCT provided is embedded within the certificate you
|
|
// are providing at chain[0], set embedded to true. LeafHash will
|
|
// calculate the leaf hash by building the corresponding precertificate.
|
|
// LeafHash will return an error if the provided SCT cannot be found
|
|
// embedded within chain[0]. As with the precertificate case, the issuing
|
|
// certificate must also be provided in chain. The certificate containing
|
|
// the embedded SCT should be at chain[0], and its issuer at chain[1].
|
|
//
|
|
// Note: LeafHash doesn't check that the provided SCT verifies for the given
|
|
// chain. It simply calculates what the leaf hash would be for the given
|
|
// (pre)certificate-SCT pair.
|
|
func LeafHash(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) ([sha256.Size]byte, error) {
|
|
leaf, err := createLeaf(chain, sct, embedded)
|
|
if err != nil {
|
|
return emptyHash, err
|
|
}
|
|
return ct.LeafHashForLeaf(leaf)
|
|
}
|
|
|
|
// VerifySCT takes the public key of a Certificate Transparency Log, a
|
|
// certificate chain, and an SCT and verifies whether the SCT is a valid SCT for
|
|
// the certificate at chain[0], signed by the Log that the public key belongs
|
|
// to. If the SCT does not verify, an error will be returned.
|
|
//
|
|
// This function can be used with three different types of leaf certificate:
|
|
// - X.509 Certificate:
|
|
// If using this function to verify an SCT for a normal X.509 certificate
|
|
// then it is enough to just provide the end entity certificate in chain.
|
|
// This case assumes that the SCT being provided is not embedded within
|
|
// the leaf certificate provided, i.e. the certificate is what was
|
|
// submitted to the Certificate Transparency Log in order to obtain the
|
|
// SCT. For this case, set embedded to false.
|
|
// - Precertificate:
|
|
// If using this function to verify an SCT for a precertificate then the
|
|
// issuing certificate must also be provided in chain. The precertificate
|
|
// should be at chain[0], and its issuer at chain[1]. For this case, set
|
|
// embedded to false.
|
|
// - X.509 Certificate containing the SCT embedded within it:
|
|
// If the SCT you wish to verify is embedded within the certificate you
|
|
// are providing at chain[0], set embedded to true. VerifySCT will
|
|
// verify the provided SCT by building the corresponding precertificate.
|
|
// VerifySCT will return an error if the provided SCT cannot be found
|
|
// embedded within chain[0]. As with the precertificate case, the issuing
|
|
// certificate must also be provided in chain. The certificate containing
|
|
// the embedded SCT should be at chain[0], and its issuer at chain[1].
|
|
func VerifySCT(pubKey crypto.PublicKey, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error {
|
|
s, err := ct.NewSignatureVerifier(pubKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating signature verifier: %s", err)
|
|
}
|
|
|
|
return VerifySCTWithVerifier(s, chain, sct, embedded)
|
|
}
|
|
|
|
// VerifySCTWithVerifier takes a ct.SignatureVerifier, a certificate chain, and
|
|
// an SCT and verifies whether the SCT is a valid SCT for the certificate at
|
|
// chain[0], signed by the Log whose public key was used to set up the
|
|
// ct.SignatureVerifier. If the SCT does not verify, an error will be returned.
|
|
//
|
|
// This function can be used with three different types of leaf certificate:
|
|
// - X.509 Certificate:
|
|
// If using this function to verify an SCT for a normal X.509 certificate
|
|
// then it is enough to just provide the end entity certificate in chain.
|
|
// This case assumes that the SCT being provided is not embedded within
|
|
// the leaf certificate provided, i.e. the certificate is what was
|
|
// submitted to the Certificate Transparency Log in order to obtain the
|
|
// SCT. For this case, set embedded to false.
|
|
// - Precertificate:
|
|
// If using this function to verify an SCT for a precertificate then the
|
|
// issuing certificate must also be provided in chain. The precertificate
|
|
// should be at chain[0], and its issuer at chain[1]. For this case, set
|
|
// embedded to false.
|
|
// - X.509 Certificate containing the SCT embedded within it:
|
|
// If the SCT you wish to verify is embedded within the certificate you
|
|
// are providing at chain[0], set embedded to true. VerifySCT will
|
|
// verify the provided SCT by building the corresponding precertificate.
|
|
// VerifySCT will return an error if the provided SCT cannot be found
|
|
// embedded within chain[0]. As with the precertificate case, the issuing
|
|
// certificate must also be provided in chain. The certificate containing
|
|
// the embedded SCT should be at chain[0], and its issuer at chain[1].
|
|
func VerifySCTWithVerifier(sv *ct.SignatureVerifier, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error {
|
|
if sv == nil {
|
|
return errors.New("ct.SignatureVerifier is nil")
|
|
}
|
|
|
|
leaf, err := createLeaf(chain, sct, embedded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return sv.VerifySCTSignature(*sct, ct.LogEntry{Leaf: *leaf})
|
|
}
|
|
|
|
func createLeaf(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (*ct.MerkleTreeLeaf, error) {
|
|
if len(chain) == 0 {
|
|
return nil, errors.New("chain is empty")
|
|
}
|
|
if sct == nil {
|
|
return nil, errors.New("sct is nil")
|
|
}
|
|
|
|
if embedded {
|
|
sctPresent, err := ContainsSCT(chain[0], sct)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error checking for SCT in leaf certificate: %s", err)
|
|
}
|
|
if !sctPresent {
|
|
return nil, errors.New("SCT provided is not embedded within leaf certificate")
|
|
}
|
|
}
|
|
|
|
certType := ct.X509LogEntryType
|
|
if chain[0].IsPrecertificate() || embedded {
|
|
certType = ct.PrecertLogEntryType
|
|
}
|
|
|
|
var leaf *ct.MerkleTreeLeaf
|
|
var err error
|
|
if embedded {
|
|
leaf, err = ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp)
|
|
} else {
|
|
leaf, err = ct.MerkleTreeLeafFromChain(chain, certType, sct.Timestamp)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating MerkleTreeLeaf: %s", err)
|
|
}
|
|
return leaf, nil
|
|
}
|
|
|
|
// ContainsSCT checks to see whether the given SCT is embedded within the given
|
|
// certificate.
|
|
func ContainsSCT(cert *x509.Certificate, sct *ct.SignedCertificateTimestamp) (bool, error) {
|
|
if cert == nil || sct == nil {
|
|
return false, nil
|
|
}
|
|
|
|
sctBytes, err := tls.Marshal(*sct)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error tls.Marshalling SCT: %s", err)
|
|
}
|
|
for _, s := range cert.SCTList.SCTList {
|
|
if bytes.Equal(sctBytes, s.Val) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|