Added the DICETest command-line certificate verification tool

This commit is contained in:
Paul England 2017-11-03 13:37:01 -07:00
Родитель fa4e21befb
Коммит 480f1bb1c3
10 изменённых файлов: 1041 добавлений и 20 удалений

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

Просмотреть файл

@ -0,0 +1,637 @@
/*
* Microsoft Copyright, 2017
* Author: pengland
*/
namespace DICETest
{
using System;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.X509.Extension;
/// <summary>
/// DICE Certificate and CSR validity checker. This is not an exhaustive validation library for X509 certificates, but
/// checks the mandatory fields created by DICE implementations, and smoke-tests the certificate chains that result.
/// </summary>
class CertChecker
{
static readonly string SigAlgName = "SHA-256withECDSA";
private static string DICEExtensionOid = "2.23.133.5.4.1";
private static int DICEExtensionVersionNumber = 1;
private static int MinimumCertLifetimeInYears = 10;
private static int serialNumMinBytes = 8;
SubjectPublicKeyInfo PubKeyInfoFromDICEExtension=null;
string[] PEMCertFile;
/// <summary>
/// First cert is always the Alias (leaf) cert
/// </summary>
X509Certificate[] Certs;
int NumCerts;
internal CertChecker()
{
}
/// <summary>
/// Specify the PEM file names for the certificates to be checked. The first cert should be the Alias Cert, and the
/// subsequent certs should be in order "up the chain" to a self-signed cert (vendor/root or DeviceID)
/// </summary>
/// <param name="pemFileNames"></param>
/// <returns></returns>
internal bool SetCerts(string[] pemFileNames)
{
PEMCertFile = pemFileNames;
NumCerts = PEMCertFile.Length;
Certs = new X509Certificate[NumCerts];
for(int j=0;j<NumCerts;j++)
{
try
{
using (var reader = File.OpenText(PEMCertFile[j]))
{
Certs[j] = (X509Certificate)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
}
}
catch(Exception e)
{
Program.Print($"Failed to parse the certificate: {PEMCertFile[j]}", NotifyType.Error);
Program.Print($"Error is : {e.ToString()}", NotifyType.Error);
return false;
}
}
return true;
}
/// <summary>
/// This is the main certificate and certificate chain validation function.
///
/// Checks a DICE certificate chain. The chain must always contain the Alias Cert and the DeiceID cert
/// as the first two elements. Zero or any number of vendor intermediate certs may follow. THe last cert
/// must always be self signed (either the vendor root-CA cert, or the DeviceID cert).
/// </summary>
/// <returns>All tests passed</returns>
internal bool CheckChain()
{
bool ok = true;
// check subject/issuer linkage (and print subjects)
ok &= CheckSubjectIssuerLinkage();
// check the the signatures good includign the self-signed root-cert
ok &= CheckSigningLinkage();
// check basic constraints for all certs
ok &= CheckCaAndPathLengthConstraint();
// Check the optional Authority Key Identifier linkage
ok &= CheckAuthKeyIdentifierLinkage();
// next check that the chain is well-formed using the .NET built-in validation routines
// (i.e. not the bouncy-castle validator.)
ok &= ChainChecker.CheckChain(Certs);
// Now check the cert fields are all good for all of the certificates in the chain
for (int j = 0; j < NumCerts; j++)
{
string tp = "";
switch(j)
{
case 0: tp = "Alias Cert"; break;
case 1: tp = "DeviceID Cert"; break;
default:
if (j == NumCerts - 1)
{
tp = "Vendor Root CA";
} else
{
tp = "Vendor Intermediate CA";
}
break;
}
Notify($"Checking {tp}: {Certs[j].SubjectDN.ToString()}");
bool certOk = CheckCertFields(Certs[j], j);
if(certOk)
{
NotifySuccess("OK");
}
else
{
Error("Certificate has errors");
}
ok &= certOk;
}
return ok;
}
/// <summary>
/// Does basic validity checks for a CSR (is it properly self-signed)
/// </summary>
/// <param name="csrPEM"></param>
/// <returns>CSR is properly signed</returns>
internal static bool CheckCSR(string csrPEM)
{
Pkcs10CertificationRequest csr = null;
try
{
using (var reader = File.OpenText(csrPEM))
{
csr = (Pkcs10CertificationRequest)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
}
}
catch (Exception e)
{
Program.Print($"Failed to parse the csr: {csrPEM}", NotifyType.Error);
Program.Print($"Error is : {e.ToString()}", NotifyType.Error);
return false;
}
bool sigOk = csr.Verify();
if(!sigOk)
{
Error("CSR signature did not verify");
return false;
}
return true;
}
/// <summary>
/// A PoP cert is a "fake" DeviceID cert containing a challenge subject common name. To check this there must
/// be two certs in the chain: the PoP cert and the vendor root cert.
/// </summary>
/// <param name="cn"></param>
/// <returns>PoP cert is properly signed, and the subject common name is correct.</returns>
internal bool CheckPopCert(string cn)
{
X509Name name=null;
try
{
name = new X509Name(cn);
}
catch(Exception e)
{
Error($"Name {cn} is not a valid X509 Name (e.g. CN=XXXQQQ) {e.ToString()}");
return false;
}
Notify("Checking PoP Cert");
bool ok = CheckSigningLinkage();
if(!ok)
{
return false;
}
var certSubject = Certs[0].SubjectDN;
if(certSubject.ToString()!= name.ToString())
{
Error($"Cert subject is incorrect. Should be {name.ToString()} but is {certSubject.ToString()}");
return false;
}
return true;
}
/// <summary>
/// Checks that the issuer of all certs in a chain matches the subject of the parent.
/// </summary>
/// <returns>The subject/issuer correspondence is correct</returns>
internal bool CheckSubjectIssuerLinkage()
{
bool ok = true;
foreach (var c in Certs)
{
Notify($" {c.SubjectDN.ToString()}");
}
if(Certs[NumCerts-1].SubjectDN.ToString() != Certs[NumCerts - 1].IssuerDN.ToString())
{
Error($"Root cert subject and issuer are different Subject:{Certs[NumCerts - 1].SubjectDN.ToString()} Issuer:{Certs[NumCerts - 1].IssuerDN.ToString()}");
ok = false;
}
// alias to root
for(int j=0;j<NumCerts-1;j++)
{
if (Certs[j].IssuerDN.ToString() != Certs[j+1].SubjectDN.ToString())
{
Error($"Cert issuer is not issuer subject. Claimed issuer:{Certs[j].IssuerDN.ToString()}.\r\nActual Issuer:{Certs[j+1].SubjectDN.ToString()}");
ok = false;
}
}
return ok;
}
/// <summary>
/// Checks that the root cert is self signed, and that all other certs are propely signed by the issuer.
/// </summary>
/// <returns>Chain signing linkage is OK</returns>
internal bool CheckSigningLinkage()
{
bool ok = true;
// alias to cert-before-root should be signed by parent
for (int j = 0; j < NumCerts - 1; j++)
{
var target = Certs[j];
var signer = Certs[j + 1];
var signerPubKey = signer.GetPublicKey();
try
{
target.Verify(signerPubKey);
}
catch(Exception e)
{
Error($"Cert {target.SubjectDN.ToString()} is not properly signed by {signer.SubjectDN.ToString()}. Error is {e.ToString()}");
ok = false;
}
}
// The root should be self-signed
var root = Certs[NumCerts - 1];
var rootPubKey = root.GetPublicKey();
try
{
root.Verify(rootPubKey);
}
catch (Exception e)
{
Error($"Root cert is not properly self-signed. Error is {e.ToString()}");
ok = false;
}
return ok;
}
/// <summary>
/// Authority Key Identifier is an optional field. If present, it should be the key identifier of the parent.
/// Absence of the field is a warning. If present, bad linkage is an error.
/// </summary>
/// <returns></returns>
internal bool CheckAuthKeyIdentifierLinkage()
{
bool ok = true;
// alias to root
for (int j = 0; j < NumCerts - 1; j++)
{
var signer = Certs[j + 1];
var target = Certs[j];
var akiData = target.GetExtensionValue(X509Extensions.AuthorityKeyIdentifier);
if(akiData ==null)
{
Warning($"Certificate does not contain an Authority Key Identifier Extension: {target.SubjectDN.ToString()}");
continue;
}
if (akiData != null)
{
var aki = new AuthorityKeyIdentifierStructure(akiData);
var signerKeyId = new AuthorityKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(signer.GetPublicKey()));
if(!signerKeyId.Equals(aki))
{
Error($"Authority Key Identifier does not match signer for certificate with subject: {target.SubjectDN.ToString()}");
ok = false;
}
}
}
return ok;
}
/// <summary>
/// Check that the CA fields and path length constraint allows a valid chain to be built
/// </summary>
/// <returns>Basic constraints are OK</returns>
internal bool CheckCaAndPathLengthConstraint()
{
bool ok = true;
// alias to root
for (int j = 0; j < NumCerts - 1; j++)
{
var c = Certs[j];
int basicConstraint = c.GetBasicConstraints();
if(j==0)
{
if(basicConstraint!=-1)
{
Error($"Alias Cert has basic constraint");
ok = false;
}
}
else
{
if (basicConstraint < j-1)
{
Error($"Root or intermediate cert BasicConstraint is incorrect for {c.SubjectDN.ToString()}. Require >{j-1}, but it is {basicConstraint}");
ok = false;
}
}
}
return ok;
}
/// <summary>
/// Checks the fields in the certificate are OK
/// </summary>
/// <param name="c">The certifcate to be checked</param>
/// <param name="offsetFromAlias">How far up the chain the cert is (alias=zero)</param>
/// <returns>The cert fields are good</returns>
bool CheckCertFields(X509Certificate c, int offsetFromAlias)
{
bool ok = true;
// processing depends on the type of cert
bool isAlias = offsetFromAlias == 0;
bool isCa = offsetFromAlias != 0;
bool isAliasOrDevId = offsetFromAlias <= 1;
ok&=CheckAlgs(c);
ok&=CheckKeyUsage(c, isCa);
ok&=CheckExtendedKeyUsage(c, isCa) ;
ok&=CheckValidityDates(c);
if (isAlias)
{
ok&= CheckDICEExtension(c) ;
}
if (isAliasOrDevId)
{
ok&= CheckSerialNumber(c);
}
return ok;
}
/// <summary>
/// Checks the signature algorithm (only P256SHA256, at this time)
/// </summary>
/// <param name="c">Certificate to be checked</param>
/// <returns>Algorithm set is OK</returns>
bool CheckAlgs(X509Certificate c)
{
if (!CheckExpected("Signature algorithm", SigAlgName, c.SigAlgName)) return false;
return true;
}
/// <summary>
/// Parses and checks contents of the DICE extension
/// </summary>
/// <param name="c">Certificate to validate</param>
/// <returns>Extension is well formed</returns>
bool CheckDICEExtension(X509Certificate c)
{
var criticalOids = c.GetCriticalExtensionOids();
if (criticalOids.Contains(DICEExtensionOid))
{
Error("DICE extension is marked critical and should be non-critical");
return false;
}
var nonCriticalOids = c.GetNonCriticalExtensionOids();
if(!nonCriticalOids.Contains(DICEExtensionOid))
{
Error("DICE extension not found");
return false;
}
var diceExtension = c.GetExtensionValue(new DerObjectIdentifier(DICEExtensionOid));
try
{
DerOctetString envelope = (DerOctetString) DerOctetString.FromByteArray(diceExtension.GetEncoded());
DerSequence seq = (DerSequence) DerSequence.FromByteArray(envelope.GetOctets());
// first field is version number
var versionNumber = (DerInteger)seq[0];
if (versionNumber.PositiveValue.IntValue != 1)
{
Error($"DICE Extension has Wrong version number. Expecing {DICEExtensionVersionNumber}, cert contains {versionNumber.ToString()}");
return false;
}
// second field is DeviceID
var devIdPubKey = SubjectPublicKeyInfo.GetInstance(seq[1]);
// will check it's good later
PubKeyInfoFromDICEExtension = devIdPubKey;
// third field contains {hashOid, hashVal}
var hashEnvelope = (DerSequence)seq[2];
var hashAlg = (DerObjectIdentifier)hashEnvelope[0];
if (hashAlg.Id != NistObjectIdentifiers.IdSha256.ToString())
{
Error("DICE Extension hash alg is wrong. ");
return false;
}
var hashVal = (DerOctetString)hashEnvelope[1];
if (hashVal.GetOctets().Length != 32)
{
Error("DICE Extension hash value length is wrong. ");
return false;
}
}
catch (Exception e)
{
Error($"Failed to parse the DICE extension. Parsing exception was {e.ToString()}");
return false ;
}
return true;
}
/// <summary>
/// Sanity checks the Key Usage certificate extension
/// </summary>
/// <param name="c">Certificate to validate</param>
/// <param name="isCA">Whether the cert is a CA cert (intermediate or root)</param>
/// <returns>Key Usage is OK</returns>
bool CheckKeyUsage(X509Certificate c, bool isCA)
{
int requiredFlags = (isCA? KeyUsage.KeyCertSign : 0);
// Currently none of the reference implemtations use these fields.
int badFlags = KeyUsage.DataEncipherment | KeyUsage.CrlSign | KeyUsage.DecipherOnly | KeyUsage.EncipherOnly |
KeyUsage.KeyAgreement | KeyUsage.KeyEncipherment | KeyUsage.NonRepudiation;
bool flagsOk = true;
try
{
var keyUsage= c.GetKeyUsage();
if(keyUsage.Length>9)
{
Error($"Unsupported KeyUsage. This usually means that DecipherOnly is asserted, which is an error");
return false;
}
for (int j = 0; j < 9; j++)
{
if (j >= keyUsage.Length) break;
int flag = 1 << (7-j);
if ((requiredFlags & flag) != 0)
{
if (!keyUsage[j])
{
Error($"Required key usage NOT asserted: {KeyUsageFlagToString(flag)}");
flagsOk = false;
}
}
if ((badFlags & flag) != 0)
{
if (keyUsage[j])
{
Error($"Bad key usage asserted: {KeyUsageFlagToString(flag)}");
flagsOk = false;
}
}
}
}
catch (Exception e)
{
Error("Failed to parse the flags extension:" + e.ToString());
flagsOk = false;
}
return flagsOk;
}
/// <summary>
/// Checks the extended key usage for CA (including DeviceID) and alias certs
/// </summary>
/// <param name="c">The certififcate to check</param>
/// <param name="isCa">Whether the cert is a root or intermediate CA cert</param>
/// <returns>Extended usage is OK</returns>
bool CheckExtendedKeyUsage(X509Certificate c, bool isCa)
{
var extendedKeyUsage = c.GetExtendedKeyUsage();
var setUsage = "";
if (isCa)
{
if (extendedKeyUsage!= null)
{
foreach (var s in extendedKeyUsage) setUsage += s.ToString() + " ";
Error("One of more ExtendedKeyUsage asserted on non-alias certificate:" + setUsage);
return false;
}
return true;
}
// else is the alias
foreach (var s in extendedKeyUsage) setUsage += s.ToString() + " ";
{
if (extendedKeyUsage.Count != 1)
{
Error("Too many ExtendedKeyUsage asserted for non-alias certificate:" + setUsage);
return false;
}
if (extendedKeyUsage[0].ToString() != KeyPurposeID.IdKPClientAuth.ToString())
{
Error("Extended Key Usage ClientAuth not asserted");
return false;
}
}
return true;
}
/// <summary>
/// Basic validity period checks
/// </summary>
/// <param name="c">Certificate to validate</param>
/// <returns>Validity period is OK</returns>
bool CheckValidityDates(X509Certificate c)
{
TimeSpan MinimumCertLifetime = new TimeSpan(MinimumCertLifetimeInYears * 365, 0, 0, 0, 0);
if(!c.IsValidNow)
{
Warning("Certificate is not valid now");
return true;
}
TimeSpan certLifetime = c.NotAfter - DateTime.Now;
if (certLifetime<MinimumCertLifetime)
{
double actualLifeInYears = certLifetime.TotalDays / 365;
Warning($"Certificate lifetime is {actualLifeInYears} years, which is less than the recommended {MinimumCertLifetimeInYears} years");
return true;
}
return true;
}
/// <summary>
/// Device and Alias cert serial numbers must be "long enough" integers
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
bool CheckSerialNumber(X509Certificate c)
{
var sn = c.SerialNumber;
int snNumBytes = sn.ToByteArray().Length;
if(snNumBytes<serialNumMinBytes)
{
Error($"Serial number is only {snNumBytes} bytes, but {serialNumMinBytes} is recommended");
return false;
}
return true;
}
/// <summary>
/// Helper checker and notification function.
/// </summary>
/// <param name="category">Error message if expected!=value</param>
/// <param name="expected"></param>
/// <param name="actual">Actual data</param>
/// <returns>Expected==actual</returns>
bool CheckExpected(string category, string expected, string actual)
{
if (expected == actual) return true;
Error($"Bad field: {category}. Expected={expected}, Actual={actual}");
return false;
}
/// <summary>
/// ASN.1 Key Usage value-to-string translator
/// </summary>
/// <param name="flag"></param>
/// <returns></returns>
static string KeyUsageFlagToString(int flag)
{
switch (flag)
{
case (1 << 7): return "DigitalSignature";
case (1 << 6): return "NonRepudiation";
case (1 << 5): return "KeyEncipherment";
case (1 << 4): return "DataEncipherment";
case (1 << 3): return "KeyAgreement";
case (1 << 2): return "KeyCertSign";
case (1 << 1): return "CrlSign";
case (1 << 0): return "EncipherOnly";
case (1 << 15): return "DecipherOnly";
default: return $"Undefined flag at bit position {flag}";
}
}
/// <summary>
/// Print/Log an error
/// </summary>
/// <param name="error">String to print</param>
static void Error(string error)
{
Program.Print(error, NotifyType.Error);
return;
}
/// <summary>
/// Print/log a warning
/// </summary>
/// <param name="warning">Warning to print</param>
static void Warning(string warning)
{
Program.Print(warning, NotifyType.Warning);
return;
}
/// <summary>
/// Print/log a notification message
/// </summary>
/// <param name="message">Message to print</param>
static void Notify(string message)
{
Program.Print(message, NotifyType.Notify);
return;
}
/// <summary>
/// Print/log a success notification
/// </summary>
/// <param name="message">Message to print</param>
static void NotifySuccess(string message)
{
Program.Print(message, NotifyType.Success);
return;
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
/*
* Microsoft Copyright, 2017
* Author: pengland
*/
namespace DICETest
{
using System.Security.Cryptography.X509Certificates;
/// <summary>
/// Checks a chain using the system (rather than bouncy castle) chain validator. BC seems to have
/// problems with EKU - clientAuth
/// </summary>
class ChainChecker
{
public static bool CheckChain(Org.BouncyCastle.X509.X509Certificate[] certs)
{
int numCerts = certs.Length;
var sysCerts = new System.Security.Cryptography.X509Certificates.X509Certificate2[numCerts];
for(int j=0;j<certs.Length;j++)
{
sysCerts[j] = new System.Security.Cryptography.X509Certificates.X509Certificate2(certs[j].GetEncoded());
}
X509Chain chain = new X509Chain(false);
// todo: this seems like a reasonable starting point for the flags
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Note: this flag seems to be ignored. In any case, we don't use the
// machine or user-context cert store: we check the root against DeviceCA
// provided as a parameter during class instantiation (or a database of authorized
// CAs in the final service.)
chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
// Add the intermediate and root-CA certs to the ExtraStore
for (int j=1;j<numCerts;j++)
{
chain.ChainPolicy.ExtraStore.Add(sysCerts[j]);
}
// Can we build a chain using the leaf/alias cert?
bool valid = chain.Build(sysCerts[0]);
if (!valid)
{
Program.Print($"Chain building failed. Errors are:", NotifyType.Error);
foreach (var err in chain.ChainStatus)
{
// the UnstrustedRoot error does not indicate a problem with the chain, but instead the fact that
// the root is not in the system/user store (if the only error is UntrustedRoot, then Build() succeeds.)
if (err.Status == X509ChainStatusFlags.UntrustedRoot) continue;
Program.Print($" Error:{err.Status.ToString()}", NotifyType.Error);
}
return false;
}
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DICETest</RootNamespace>
<AssemblyName>DICETest</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\DICETest.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CertChecker.cs" />
<Compile Include="ChainChecker.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

203
Tools/DICETest/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,203 @@
/*
* Microsoft Copyright, 2017
* Author: pengland
*/
using System;
using System.Threading;
using System.IO;
using System.Diagnostics;
namespace DICETest
{
/// <summary>
/// This command line program performs certificate validation for DICE certificate chains.
/// </summary>
class Program
{
static void Main(string[] args)
{
// Usage:
// -option c0 c1 c2...
if (args.Length == 0 || args[1] == "?")
{
PrintHelp();
return;
}
if (args[0] == "-chain")
{
if (args.Length < 2)
{
ParmCountError("At least two certs in chain");
return;
}
var certs = GetCertNames(args, 1);
if (certs == null) { Finish(); return; }
CertChecker c = new CertChecker();
bool ok = c.SetCerts(certs);
if (!ok) { Finish(); return; };
ok = c.CheckChain();
if (!ok)
{
Program.Print("One or more errors in certificate chain.", NotifyType.Error);
}
{
Program.Print("Certificates and certificate chain are valid.", NotifyType.Success);
}
Finish();
return;
}
if (args[0] == "-pop")
{
if (args.Length != 4)
{
ParmCountError("challenge and cert needed");
return;
}
var certs = GetCertNames(args, 2);
if (certs == null) { Finish(); return; }
CertChecker c = new CertChecker();
bool ok = c.SetCerts(certs);
if (!ok) { Finish(); return; };
string challengeCn = args[1];
ok = c.CheckPopCert(challengeCn);
if (!ok)
{
Program.Print("One or more errors in PoP cert.", NotifyType.Error);
}
else
{
Program.Print("PoP cert is good", NotifyType.Success);
}
Finish();
return;
}
if (args[0] == "-csr")
{
if (args.Length != 2)
{
ParmCountError("Just CSR.PEM needed");
return;
}
bool ok = CertChecker.CheckCSR(args[1]);
if (!ok)
{
Program.Print("One or more errors in CSR.", NotifyType.Error);
}
{
Program.Print("CSR is well formed.", NotifyType.Success);
}
Finish();
return;
}
Program.Print($"Option not recognized: {args[0]}", NotifyType.Error);
PrintHelp();
Finish();
}
/// <summary>
/// Helper message writer for argument errors
/// </summary>
/// <param name="message"></param>
static void ParmCountError(string message)
{
Program.Print($"Wrong number of parameters: {message}", NotifyType.Error);
PrintHelp();
Finish();
return;
}
/// <summary>
/// Print the help and usage message
/// </summary>
static void PrintHelp()
{
string eol = "\r\n";
String nm = System.IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location);
String s = $"Validates DICE certificates and certificate chains. Usage:{eol}" +
$"{nm} -chain AliasCert.PEM DeviceIDCert.PEM ... Root.PEM - Checks the alias, deviceID, and vendor cert chain (DevID may be self-signed){eol}" +
$"{nm} -csr CSR.PEM - Checks a CSR (certificate signing request){eol}" +
$"{nm} -pop SubjectName RootPOP.PEM - Checks a proof-of-possession cert{eol}";
Print(s, NotifyType.Error);
return;
}
/// <summary>
/// Print helper with color highlighing
/// </summary>
/// <param name="s"></param>
/// <param name="tp"></param>
internal static void Print(string s, NotifyType tp)
{
switch(tp)
{
case NotifyType.Error: Console.ForegroundColor = ConsoleColor.Red; break;
case NotifyType.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break;
case NotifyType.Success: Console.ForegroundColor = ConsoleColor.Green; break;
case NotifyType.Notify: Console.ResetColor(); break;
}
Debug.WriteLine(s);
Console.WriteLine(s);
Console.ResetColor();
return;
}
/// <summary>
/// Get file names from the command line parameters, and check that the files exist.
/// </summary>
/// <param name="args">Args parameter passed to main()</param>
/// <param name="startAt">Optionally skip one or more parms</param>
/// <returns>Array of cert file names</returns>
static string[] GetCertNames(string[] args, int startAt)
{
int numCerts = args.Length - startAt;
string[] certNames = new string[numCerts];
bool ok = true;
for (int j = 0; j < numCerts;j++)
{
string fileName = args[j+startAt];
if(!File.Exists(fileName))
{
Print($"File not found: {args[j+startAt]}", NotifyType.Error);
ok = false;
} else
{
certNames[j] = fileName;
}
}
if (!ok) return null;
return certNames;
}
/// <summary>
/// Clean up prior to exit
/// </summary>
static void Finish()
{
if (Debugger.IsAttached)
{
Thread.Sleep(3000);
}
}
}
/// <summary>
/// Type of message
/// </summary>
internal enum NotifyType
{
Error,
Warning,
Success,
Notify
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DICETest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DICETest")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3717e055-2380-4a9f-8d6e-7e6b980ea3c6")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

17
Tools/DICETest/README.txt Normal file
Просмотреть файл

@ -0,0 +1,17 @@

DICETest is a command-line tool for doing basic validation of the certificates
produced by a DICE/RIoT implementation.
Example usage:
// Check the validity of various certificate chains
DICETest -chain AliasCert.PEM DeviceIDCert.PEM RootCert.PEM
DICETest -chain AliasCert.PEM DeviceIDCert.PEM IntermediateCert.PEM RootCert.PEM
DICETest -chain AliasCert.PEM DeviceIDSelfSignedCert.PEM
// Check a "proof of posession" DeviceID certificate for the given root cert
DICETest -pop CN=XXXXyyyyZZZZ DevIDPopCert.PEM RootCert.PEM
// Check that the CSR is valid (self-signed)
DICETest -csr DevIDCSR.PEM

Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.1" targetFramework="net452" />
</packages>

Просмотреть файл

@ -1,20 +1,7 @@
TODO
This solution contains tools and utilities for testing DICE/RIoT systems.
If you run RIoT and RIoTDemo together, you get the update demo.
In the version in the repo, the devices look at twin.targetProperties and updae and
self-report.
The version being worked here introduces a new deevice/twin called ControlDevice with a property VersionUmber
that is uddated by the GUI app, and can be queried by the devices AND by the attestaion server to
have an authoritative check that the attestation data is good.
This is just started on friday.
Create chains given a CSR (and an alias cert)
-dir d:\tmp\Emulator -csr
-nogen -dir d:\tmp\Emulator -e2e
DICETest - Command-line tool that checks certificates and certificate chains created by DICE/RIoT systems
RIoT - More in-depth certificate testing, including TLS tests
RIoTDemo - (deprecated)
TlsClient - (deprecated)

Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RIoT", "RIoT\RIoT.csproj", "{20A30499-7F02-446F-8716-E85FCDBB0CE4}"
EndProject
@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RIoTDemo", "RIoTDemo\RIoTDemo.csproj", "{70669FD8-B9BB-4EA2-B9BB-6E387B2E5788}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DICETest", "DICETest\DICETest.csproj", "{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -59,6 +61,18 @@ Global
{70669FD8-B9BB-4EA2-B9BB-6E387B2E5788}.Release|x64.Build.0 = Release|Any CPU
{70669FD8-B9BB-4EA2-B9BB-6E387B2E5788}.Release|x86.ActiveCfg = Release|Any CPU
{70669FD8-B9BB-4EA2-B9BB-6E387B2E5788}.Release|x86.Build.0 = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|x64.ActiveCfg = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|x64.Build.0 = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|x86.ActiveCfg = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Debug|x86.Build.0 = Debug|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|Any CPU.Build.0 = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|x64.ActiveCfg = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|x64.Build.0 = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|x86.ActiveCfg = Release|Any CPU
{3717E055-2380-4A9F-8D6E-7E6B980EA3C6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE