зеркало из https://github.com/microsoft/RIoT.git
Added the DICETest command-line certificate verification tool
This commit is contained in:
Родитель
fa4e21befb
Коммит
480f1bb1c3
|
@ -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>
|
|
@ -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")]
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче