1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語でPKI入門(勝手に復刻版):CRL/OCSP編

Posted at

はじめに

Go言語でPKI入門(勝手に復刻版)

の続編です。今回は、証明書の失効に関連したCRLとOCSPに話です。

証明書を失効するとは?

CA(認証局)が発行した証明書を失効するというのは証明書のファイルを何か変更するわけではありません。
CAの中で、その証明書を失効したとマークを付けるだけです。ファイルやデータベースに失効した日時を記録すればよいだけです。

なので、証明書だけ見ても有効なのか失効しているのかわかりません。CAに聞かなかればなりません。CAに聞く方法が2つあります。CRLとOCSPです。

他力ですが、CRLとOCSPの説明は

がわかりやすいと思います。

CRL(証明書失効リスト)を作る

Go言語でCRLを作るには

// CRL
// RFC5280, 5.2.5
type issuingDistributionPoint struct {
	DistributionPoint          distributionPointName `asn1:"optional,tag:0"`
	OnlyContainsUserCerts      bool                  `asn1:"optional,tag:1"`
	OnlyContainsCACerts        bool                  `asn1:"optional,tag:2"`
	OnlySomeReasons            asn1.BitString        `asn1:"optional,tag:3"`
	IndirectCRL                bool                  `asn1:"optional,tag:4"`
	OnlyContainsAttributeCerts bool                  `asn1:"optional,tag:5"`
}

type distributionPointName struct {
	FullName     []asn1.RawValue  `asn1:"optional,tag:0"`
	RelativeName pkix.RDNSequence `asn1:"optional,tag:1"`
}

func CreateCRL() {
	dp := distributionPointName{
		FullName: []asn1.RawValue{
			{Tag: 6, Class: 2, Bytes: []byte("http://127.0.0.1:5585/crl")},
		},
	}
	var oidExtensionIssuingDistributionPoint = []int{2, 5, 29, 28}

	idp := issuingDistributionPoint{
		DistributionPoint: dp,
	}

	v, err := asn1.Marshal(idp)
	if err != nil {
		panic(err)
	}

	cdpExt := pkix.Extension{
		Id:       oidExtensionIssuingDistributionPoint,
		Critical: true,
		Value:    v,
	}

	key, ok := rootCAPrivateKey.(crypto.Signer)
	if !ok {
		panic("invalid key type")
	}
	ca, err := x509.ParseCertificate(rootCACertificate)
	if err != nil {
		panic(err)
	}
	revokedCerts := []pkix.RevokedCertificate{
		{
			SerialNumber:   big.NewInt(3),
			RevocationTime: time.Now(),
		},
	}
	crlTpl := &x509.RevocationList{
		SignatureAlgorithm:  x509.ECDSAWithSHA256,
		RevokedCertificates: revokedCerts,
		Number:              big.NewInt(2),
		ThisUpdate:          time.Now(),
		NextUpdate:          time.Now().Add(24 * time.Hour),
		ExtraExtensions:     []pkix.Extension{cdpExt},
	}
	crl, err = x509.CreateRevocationList(rand.Reader, crlTpl, ca, key)
	if err != nil {
		panic(err)
	}
	OutPem(crl, "./rootCA.crl", "X509 CRL")
}

のようなコードになります。
必要なものは

  • CAの秘密鍵と証明書
  • CRLを配布するURL
  • 失効した証明書のリスト(シリアル番号と失効した日時)

です。これらをx509.RevocationListにまとめてx509.CreateRevocationList関数に渡せば、
CLRを作成できます。失効した証明書はシリアル番号3にしています。
そのまま、保存すればDER形式、上の例ではPEM形式で保存しています。

このファイルをHTTPサーバーからダウンロードできるようにして配布します。定期的にダウンロードして証明書の失効をチェックすることになります。
結構たいへんです。

OCSP(Online Certificate Status Protocol)

CRLよりましにしたのがOCSPです。証明書の状態を問い合わせるプロトコルです。
基本的はHTTPサーバーで実現します。(HTTPSの必要はないです。)

Go言語でOCSPサーバーを実現するのは

package main

import (
	"bytes"
	"crypto"
	"crypto/sha1"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"golang.org/x/crypto/ocsp"
)

// OCSP Server
func OCSPServer() {
	e := echo.New()
	e.GET("/crl", func(c echo.Context) error {
		log.Printf("get crl %+v", c)
		c.Response().Header().Add(echo.HeaderCacheControl, "max-age=0, no-cache")
		return c.Blob(http.StatusOK, "application/pkix-crl", crl)
	})
	e.GET("/ocsp/:req", func(c echo.Context) error {
		req := c.Param("req")
		b, err := base64.StdEncoding.DecodeString(req)
		if err != nil {
			log.Printf("err=%v", err)
			return c.JSON(http.StatusBadRequest, err)
		}
		return ocspFunc(c, b)
	})
	e.POST("/ocsp", func(c echo.Context) error {
		b, err := io.ReadAll(c.Request().Body)
		if err != nil {
			log.Printf("err=%v", err)
			return c.JSON(http.StatusBadRequest, err)
		}
		return ocspFunc(c, b)
	})
	e.Logger.Fatal(e.Start("127.0.0.1:5585"))
}

func ocspFunc(c echo.Context, b []byte) error {
	req, err := ocsp.ParseRequest(b)
	if err != nil {
		log.Printf("err=%v", err)
		return c.JSON(http.StatusBadRequest, err)
	}
	log.Printf("post ocsp %+v", req)
	key, ok := rootCAPrivateKey.(crypto.Signer)
	if !ok {
		log.Printf("err=%v", err)
		return c.JSON(http.StatusBadRequest, err)
	}
	ca, err := x509.ParseCertificate(rootCACertificate)
	if err != nil {
		log.Printf("err=%v", err)
		return c.JSON(http.StatusBadRequest, err)
	}
	res := ocsp.Response{
		Status:       ocsp.Unknown,
		SerialNumber: req.SerialNumber,
		ThisUpdate:   time.Now(),
		NextUpdate:   time.Now().Add(time.Second * 60 * 10),
		IssuerHash:   req.HashAlgorithm,
	}
	if bytes.Equal(req.IssuerKeyHash, ca.SubjectKeyId) {
		sn := req.SerialNumber.String()
		if isCertRevokeed(sn) {
			res.Status = ocsp.Revoked
			res.RevokedAt = time.Now().Add(time.Hour * -3)
			res.RevocationReason = ocsp.Unspecified
		} else {
			res.Status = ocsp.Good
		}
	}
	bres, err := ocsp.CreateResponse(ca, ca, res, key)
	if err != nil {
		log.Printf("err=%v", err)
		return c.JSON(http.StatusBadRequest, err)
	}
	return c.Blob(http.StatusOK, "application/ocsp-response", bres)
}

func HashedBySha1(b []byte) string {
	sha1 := sha1.New()
	sha1.Write(b)
	return hex.EncodeToString(sha1.Sum(nil))
}

func isCertRevokeed(sn string) bool {
	return sn == "3"
}

のような感じになります。
先ほど作成したCRLの配布も実装しています。

HTTPサーバーは、Echo

を使っています。これは便利です。

OCSPのプロトコルは

のパッケージが使えます。
リクエストの解析と応答の作成ができます。

OCSPはHTTPのGETリクエストまたは、POSTリクエストで送信されます。
両方に対応できるようになっています。データを取り出す場所が違います

isCertRevokeed関数の中で、証明書の失効を判断しています。上の例ではシリアル番号が3を失効
としています。きちんと実装する時は、DBなどを検索して判断すればよいです。

余談

CRLの作成やOCSPサーバーは、

に組み込むために作っています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?