ビルドプロセスで、RFC-3161準拠のTSAからのタイムスタンプを含めたいと思います。実行時に、コードはこのタイムスタンプを検証します。できればサードパーティのライブラリを使用しないでください。 (これは.NETアプリケーションであるため、標準のハッシュおよび非対称暗号化機能をすぐに使用できます。)
RFC 3161は、ASN.1やX.690などに依存しているため、実装が簡単ではないため、少なくとも今のところ、Bouncy Castleを使用してTimeStampReq(要求)を生成し、TimeStampResp(応答)を解析しています。応答を検証する方法がよくわかりません。
これまで、署名自体、公開証明書、タイムスタンプが作成された時刻、および送信したメッセージインプリントダイジェストとナンス(ビルド時の検証用)を抽出する方法を理解しました。私が理解できないのは、このデータを組み合わせて、ハッシュ化および署名されたデータを生成する方法です。
これが私がしていることと私がしようとしていることの大まかな考えです。これはテストコードなので、いくつかのショートカットを使用しました。うまくいくものができたら、いくつかのことをクリーンアップして、正しい方法で実行する必要があります。
// a lot of fully-qualified type names here to make sure it's clear what I'm using
static void WriteTimestampToBuild(){
var dataToTimestamp = Encoding.UTF8.GetBytes("The rain in Spain falls mainly on the plain");
var hashToTimestamp = new System.Security.Cryptography.SHA1Cng().ComputeHash(dataToTimestamp);
var nonce = GetRandomNonce();
var tsr = GetTimestamp(hashToTimestamp, nonce, "http://some.rfc3161-compliant.server");
var tst = tsr.TimeStampToken;
var tsi = tst.TimeStampInfo;
ValidateNonceAndHash(tsi, hashToTimestamp, nonce);
var cms = tst.ToCmsSignedData();
var signer =
cms.GetSignerInfos().GetSigners()
.Cast<Org.BouncyCastle.Cms.SignerInformation>().First();
// TODO: handle multiple signers?
var signature = signer.GetSignature();
var cert =
tst.GetCertificates("Collection").GetMatches(signer.SignerID)
.Cast<Org.BouncyCastle.X509.X509Certificate>().First();
// TODO: handle multiple certs (for one or multiple signers)?
ValidateCert(cert);
var timeString = tsi.TstInfo.GenTime.TimeString;
var time = tsi.GenTime; // not sure which is more useful
// TODO: Do I care about tsi.TstInfo.Accuracy or tsi.GenTimeAccuracy?
var serialNumber = tsi.SerialNumber.ToByteArray(); // do I care?
WriteToBuild(cert.GetEncoded(), signature, timeString/*or time*/, serialNumber);
// TODO: Do I need to store any more values?
}
static Org.BouncyCastle.Math.BigInteger GetRandomNonce(){
var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
var bytes = new byte[10]; // TODO: make it a random length within a range
rng.GetBytes(bytes);
return new Org.BouncyCastle.Math.BigInteger(bytes);
}
static Org.BouncyCastle.Tsp.TimeStampResponse GetTimestamp(byte[] hash, Org.BouncyCastle.Math.BigInteger nonce, string url){
var reqgen = new Org.BouncyCastle.Tsp.TimeStampRequestGenerator();
reqgen.SetCertReq(true);
var tsrequest = reqgen.Generate(Org.BouncyCastle.Tsp.TspAlgorithms.Sha1, hash, nonce);
var data = tsrequest.GetEncoded();
var webreq = WebRequest.CreateHttp(url);
webreq.Method = "POST";
webreq.ContentType = "application/timestamp-query";
webreq.ContentLength = data.Length;
using(var reqStream = webreq.GetRequestStream())
reqStream.Write(data, 0, data.Length);
using(var respStream = webreq.GetResponse().GetResponseStream())
return new Org.BouncyCastle.Tsp.TimeStampResponse(respStream);
}
static void ValidateNonceAndHash(Org.BouncyCastle.Tsp.TimeStampTokenInfo tsi, byte[] hashToTimestamp, Org.BouncyCastle.Math.BigInteger nonce){
if(tsi.Nonce != nonce)
throw new Exception("Nonce doesn't match. Man-in-the-middle attack?");
var messageImprintDigest = tsi.GetMessageImprintDigest();
var hashMismatch =
messageImprintDigest.Length != hashToTimestamp.Length ||
Enumerable.Range(0, messageImprintDigest.Length).Any(i=>
messageImprintDigest[i] != hashToTimestamp[i]
);
if(hashMismatch)
throw new Exception("Message imprint doesn't match. Man-in-the-middle attack?");
}
static void ValidateCert(Org.BouncyCastle.X509.X509Certificate cert){
// not shown, but basic X509Chain validation; throw exception on failure
// TODO: Validate certificate subject and policy
}
static void WriteToBuild(byte[] cert, byte[] signature, string time/*or DateTime time*/, byte[] serialNumber){
// not shown
}
// a lot of fully-qualified type names here to make sure it's clear what I'm using
static void VerifyTimestamp(){
var timestampedData = Encoding.UTF8.GetBytes("The rain in Spain falls mainly on the plain");
var timestampedHash = new System.Security.Cryptography.SHA1Cng().ComputeHash(timestampedData);
byte[] certContents;
byte[] signature;
string time; // or DateTime time
byte[] serialNumber;
GetDataStoredDuringBuild(out certContents, out signature, out time, out serialNumber);
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certContents);
ValidateCert(cert);
var signedData = MagicallyCombineThisStuff(timestampedHash, time, serialNumber);
// TODO: What other stuff do I need to magically combine?
VerifySignature(signedData, signature, cert);
// not shown: Use time from timestamp to validate cert for other signed data
}
static void GetDataStoredDuringBuild(out byte[] certContents, out byte[] signature, out string/*or DateTime*/ time, out byte[] serialNumber){
// not shown
}
static void ValidateCert(System.Security.Cryptography.X509Certificates.X509Certificate2 cert){
// not shown, but basic X509Chain validation; throw exception on failure
}
static byte[] MagicallyCombineThisStuff(byte[] timestampedhash, string/*or DateTime*/ time, byte[] serialNumber){
// HELP!
}
static void VerifySignature(byte[] signedData, byte[] signature, System.Security.Cryptography.X509Certificates.X509Certificate2 cert){
var key = (RSACryptoServiceProvider)cert.PublicKey.Key;
// TODO: Handle DSA keys, too
var okay = key.VerifyData(signedData, CryptoConfig.MapNameToOID("SHA1"), signature);
// TODO: Make sure to use the same hash algorithm as the TSA
if(!okay)
throw new Exception("Timestamp doesn't match! Don't trust this!");
}
ご想像のとおり、私が行き詰まっていると思うのはMagicallyCombineThisStuff
関数です。
応答で署名されたデータ構造を再構築する理由がわかりません。実際、タイムスタンプサーバーの応答から署名されたデータを抽出する場合は、次のようにすることができます。
var tsr = GetTimestamp(hashToTimestamp, nonce, "http://some.rfc3161-compliant.server");
var tst = tsr.TimeStampToken;
var tsi = tst.TimeStampInfo;
var signature = // Get the signature
var certificate = // Get the signer certificate
var signedData = tsi.GetEncoded(); // Similar to tsi.TstInfo.GetEncoded();
VerifySignature(signedData, signature, certificate)
データ構造を再構築する場合は、応答に含まれるすべての要素を使用して、新しいOrg.BouncyCastle.Asn1.Tsp.TstInfo
インスタンス(tsi.TstInfo
はOrg.BouncyCastle.Asn1.Tsp.TstInfo
オブジェクト)を作成する必要があります。
RFC 3161では、署名されたデータ構造は次のASN.1シーケンスとして定義されています。
TSTInfo ::= SEQUENCE {
version INTEGER { v1(1) },
policy TSAPolicyId,
messageImprint MessageImprint,
-- MUST have the same value as the similar field in
-- TimeStampReq
serialNumber INTEGER,
-- Time-Stamping users MUST be ready to accommodate integers
-- up to 160 bits.
genTime GeneralizedTime,
accuracy Accuracy OPTIONAL,
ordering BOOLEAN DEFAULT FALSE,
nonce INTEGER OPTIONAL,
-- MUST be present if the similar field was present
-- in TimeStampReq. In that case it MUST have the same value.
tsa [0] GeneralName OPTIONAL,
extensions [1] IMPLICIT Extensions OPTIONAL }
トリッキーなプロトコル作業が完了しました。おめでとうございます。
Pythonクライアントの実装 rfc3161ng 2.0.4 も参照してください。
Web Science and Digital Libraries Research Group:2017-04-20:Trusted Timestamping of Mementos およびその他の出版物で説明されているように、RFC 3161 TSPプロトコルでは、あなたとあなたの依拠当事者は、 Time-Stamping Authority(TSA)は適切かつ安全に運用されています。もちろん、ほとんどのTSAで実行されているようなオンラインサーバーを実際に保護することは、不可能ではないにしても、非常に困難です。
その論文でも説明されているように、TSPとの比較では、信頼が分散され、(場合によっては)注意深く監視されるさまざまなパブリックブロックチェーンが世界に存在するようになったため、新しい信頼できるタイムスタンプオプションがあります(ドキュメントの「存在の証明」を提供します) 。たとえば、 OriginStamp-ビットコインを使用した信頼できるタイムスタンプ を参照してください。プロトコルははるかに単純であり、多種多様な言語のクライアントコードを提供します。オンラインサーバーも危険にさらされる可能性がありますが、クライアントはハッシュがビットコインブロックチェーンに適切に埋め込まれているかどうかを確認できるため、OriginStampサービス自体を信頼する必要がなくなります。 1つの欠点は、追加の支払いが行われない限り、タイムスタンプが1日に1回しか投稿されないことです。ビットコインの取引はかなり高額になっているため、サービスは他のブロックチェーンのサポートも検討しており、コストを削減し、よりタイムリーな投稿をより安く取得できるようにしています。