Adding a new Extension to my generated certificate - ssl-certificate

I need to add a new Extension of OID 1.3.6.1.5.5.7.1.26 in my certificate. I got this OID extension in my certificate but with the following error:
Certificate Extensions: 10
[1]: ObjectId: 1.3.6.1.5.5.7.1.26 Criticality=false
Extension unknown: DER encoded OCTET string =
0000: 04 0C 30 0A 13 08 33 39 20 64 63 20 32 62 ..0...
39 dc 2b
I want this OID to be recognized similar to other extensions like AuthorityInfoAccess, etc.
Do I need to edit the jar of Bouncy Castle X509 class?
Im using ACME4j as a client and Letsencrypt Boulder as my server.
Here is the CSR Builder code for signing up the certificate.
public void sign(KeyPair keypair) throws IOException {
//Security.addProvider(new BouncyCastleProvider());
Objects.requireNonNull(keypair, "keypair");
if (namelist.isEmpty()) {
throw new IllegalStateException("No domain was set");
}
try {
GeneralName[] gns = new GeneralName[namelist.size()];
for (int ix = 0; ix < namelist.size(); ix++) {
gns[ix] = new GeneralName(GeneralName.dNSName,namelist.get(ix));
}
SignatureAlgorithmIdentifierFinder algFinder = new
DefaultSignatureAlgorithmIdentifierFinder();
GeneralNames subjectAltName = new GeneralNames(gns);
PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(namebuilder.build(), keypair.getPublic());
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
//extensionsGenerator.addExtension(Extension.authorityInfoAccess, true, subjectAltName);
//extensionsGenerator.addExtension(new ASN1ObjectIdentifier("TBD"), false, subjectAltName);
//extensionsGenerator.addExtension(new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.24"), false, subjectAltName);
extensionsGenerator.addExtension(new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.26").intern(), false, subjectAltName);
//extentionsGenerator.addExtension();
p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate());
PrivateKey pk = keypair.getPrivate();
/*JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder(
pk instanceof ECKey ? EC_SIGNATURE_ALG : EC_SIGNATURE_ALG);
ContentSigner signer = csBuilder.build(pk);*/
if(pk instanceof ECKey)
{
AlgorithmIdentifier sigAlg = algFinder.find("SHA1withECDSA");
AlgorithmIdentifier digAlg = new DefaultDigestAlgorithmIdentifierFinder().
find(sigAlg);
ContentSigner signer = new JcaContentSignerBuilder("SHA256with"+pk.getAlgorithm()).setProvider(BOUNCY_CASTL E_PROVIDER).build(keypair.getPrivate());
csr=p10Builder.build(signer);
System.out.println("ZIPED CSR ECDSA: "+csr);
}
else
{
ContentSigner signer = new JcaContentSignerBuilder("SHA256with"+pk.getAlgorithm()).build(keypair.getPrivate ());
csr = p10Builder.build(signer);
System.out.println("ZIPED CSR RSA: "+csr);
}
//csr = p10Builder.build(signer);
} catch (Exception ex) {
ex.printStackTrace();;
}
}

Note: for these codes I used bcprov-jdk15on 1.56
Some comments about your code. First of all, note the ASN1 structure:
TNAuthorizationList ::= SEQUENCE SIZE (1..MAX) OF TNEntry
TNEntry ::= CHOICE {
spc [0] ServiceProviderCodeList,
range [1] TelephoneNumberRange,
one E164Number
}
Note that TNEntry is a choice, and TNAuthorizationList is a sequence of TNEntry objects. So your class name should be changed to TNEntry. In the code below, please remember that I've changed the class name to TNEntry.
I've also changed some things in this class. In getInstance(Object obj) method, the types of spc and range fields are incorrect (according to ASN1 definition, they are both sequences):
switch (tag) {
case spc:
case range: // both are sequences
return new TNEntry(tag, ASN1Sequence.getInstance(tagObj, false));
// not sure about "one" field, as it's not tagged
}
I just don't know how to handle the one field, as it's not tagged. Maybe it should be a DERIA5String, or maybe there's another type for "untagged" choices.
In this same class (remember, I've changed its name to TNEntry), I also removed the constructor public TNEntry(int tag, String name) because I'm not sure if it applies (at least I didn't need to use it, but you can keep it if you want), and I've changed toString method to return a more readable string:
public String toString() {
String sep = System.getProperty("line.separator");
StringBuffer buf = new StringBuffer();
buf.append(this.getClass().getSimpleName());
buf.append(" [").append(tag);
buf.append("]: ");
switch (tag) {
case spc:
buf.append("ServiceProviderCodeList: ").append(sep);
ASN1Sequence seq = (ASN1Sequence) this.obj;
int size = seq.size();
for (int i = 0; i < size; i++) {
// all elements are DERIA5Strings
DERIA5String str = (DERIA5String) seq.getObjectAt(i);
buf.append(" ");
buf.append(str.getString());
buf.append(sep);
}
break;
case range:
buf.append("TelephoneNumberRange: ").append(sep);
// there are always 2 elements in TelephoneNumberRange
ASN1Sequence s = (ASN1Sequence) this.obj;
DERIA5String str = (DERIA5String) s.getObjectAt(0);
buf.append(" start: ");
buf.append(str.getString());
buf.append(sep);
ASN1Integer count = (ASN1Integer) s.getObjectAt(1);
buf.append(" count: ");
buf.append(count.toString());
buf.append(sep);
break;
default:
buf.append(obj.toString());
}
return buf.toString();
}
And I also created a TNAuthorizationList class, which holds the sequence of TNEntry objects (remember that I've changed your class name to TNEntry, so this TNAuthorizationList class is a different one). Note that I also created a constant to hold the OID (just to make things a little bit easier):
public class TNAuthorizationList extends ASN1Object {
// put OID in a constant, so I don't have to remember it all the time
public static final ASN1ObjectIdentifier TN_AUTH_LIST_OID = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.26");
private TNEntry[] entries;
public TNAuthorizationList(TNEntry[] entries) {
this.entries = entries;
}
public static TNAuthorizationList getInstance(Object obj) {
if (obj instanceof TNAuthorizationList) {
return (TNAuthorizationList) obj;
}
if (obj != null) {
return new TNAuthorizationList(ASN1Sequence.getInstance(obj));
}
return null;
}
public static TNAuthorizationList getInstance(ASN1TaggedObject obj, boolean explicit) {
return getInstance(ASN1Sequence.getInstance(obj, explicit));
}
private TNAuthorizationList(ASN1Sequence seq) {
this.entries = new TNEntry[seq.size()];
for (int i = 0; i != seq.size(); i++) {
entries[i] = TNEntry.getInstance(seq.getObjectAt(i));
}
}
public TNEntry[] getEntries() {
TNEntry[] tmp = new TNEntry[entries.length];
System.arraycopy(entries, 0, tmp, 0, entries.length);
return tmp;
}
#Override
public ASN1Primitive toASN1Primitive() {
return new DERSequence(entries);
}
public String toString() {
String sep = System.getProperty("line.separator");
StringBuffer buf = new StringBuffer();
buf.append(this.getClass().getSimpleName());
buf.append(":").append(sep);
for (TNEntry tnEntry : entries) {
buf.append(" ");
buf.append(tnEntry.toString());
buf.append(sep);
}
return buf.toString();
}
}
Now, to add this extension to a certificate, I've done this code (with some sample data as I don't know what should be in each field in a real world situation):
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(etc...);
// create TNEntries for TNAuthorizationList
TNEntry[] entries = new TNEntry[2];
// create a "spc" entry
DERIA5String[] cList = new DERIA5String[] { new DERIA5String("spc1"), new DERIA5String("spc2") };
DERSequence spc = new DERSequence(cList);
entries[0] = TNEntry.getInstance(new DERTaggedObject(false, TNEntry.spc, spc));
// create a "range" entry
DERSequence range = new DERSequence(new ASN1Encodable[] { new DERIA5String("123456"), new ASN1Integer(1) });
entries[1] = TNEntry.getInstance(new DERTaggedObject(false, TNEntry.range, range));
TNAuthorizationList tnAuthList = new TNAuthorizationList(entries);
builder.addExtension(TNAuthorizationList.TN_AUTH_LIST_OID, false, tnAuthList);
Once you have the certificate object (a X509Certificate in my example), you can do:
// cert is a X509Certificate instance
ASN1Primitive value = X509ExtensionUtil.fromExtensionValue(cert.getExtensionValue(TNAuthorizationList.TN_AUTH_LIST_OID.getId()));
TNAuthorizationList authList = TNAuthorizationList.getInstance(value);
System.out.println(authList.toString());
The output will be:
TNAuthorizationList:
TNEntry [0]: ServiceProviderCodeList:
spc1
spc2
TNEntry [1]: TelephoneNumberRange:
start: 123456
count: 1
Notes:
As I said, this code is incomplete because I'm not sure how to handle the one field of TNEntry, because it's not tagged (I don't know if it must be a DERIA5String or if there's another type of object for an "untagged" field).
You could also do some improvements:
ServiceProviderCodeList can have 1 to 3 elements, so you could validate its size
TelephoneNumberRange: the start field has a specific format (FROM ("0123456789#*") which I think it means only these characters are accepted), so you could also validate it
To create the values for ServiceProviderCodeList and TelephoneNumberRange, I've created the DERSequence objects by hand, but you can create custom classes for them if you want: ServiceProviderCodeList could hold a list of DERIA5String and perform proper validations in its constructor (size from 1 to 3), and TelephoneNumberRange could have start and count fields (with proper validation of start value) - and toASN1Primitive just need to return a DERSequence of its fields in the right order
For your parsing issues, I've checked acme4j code and it uses a java.security.cert.X509Certificate class. The toString() method of this class (when using Sun's default provider) is generating this "extension unknown" output (according to the corresponding code).
So, in order to parse it correctly (show the formatted output as described above), you'll probably have to change acme4j's code (or write your own), creating a new toString() method and include the new TNAuthorizationList classes in this method.
When you provide the code showing how you're using acme4j, I'll update this answer accordingly, if needed.

As the OID 1.3.6.1.5.5.7.1.26 is still a draft, I believe it's very unlikely that tools and systems like Let's Encrypt recognize this extension (they'll probably do it after this extension becomes official, and I really don't know the bureaucratic process behind such approvals).
Which means you'll probably have to code it. I've been using Bouncy Castle for a couple of years but never had to create a new ASN1 structure. But if I had to, I'd take a look at its source code as an initial guidance.
Considering the ASN1 structure of this extension:
TNAuthorizationList ::= SEQUENCE SIZE (1..MAX) OF TNEntry
TNEntry ::= CHOICE {
spc [0] ServiceProviderCodeList,
range [1] TelephoneNumberRange,
one E164Number
}
ServiceProviderCodeList ::= SEQUENCE SIZE (1..3) OF IA5String
-- Service Provider Codes may be OCNs, various SPIDs, or other
-- SP identifiers from the telephone network
TelephoneNumberRange ::= SEQUENCE {
start E164Number,
count INTEGER
}
E164Number ::= IA5String (SIZE (1..15)) (FROM ("0123456789#*"))
The extension value must be a SEQUENCE of TNEntry. So you could use ASN1Sequence (or its subclass DERSequence) and put instances of TNEntry inside it.
To create a TNEntry, you need to implement ASN1Choice (take a look at source of GeneralName class and do something similar).
And so on, until you have the whole structure mapped to their respective classes, using Bouncy Castle built-in classes to support you (there are DERIA5String for IA5String and DERInteger for INTEGER, which can be used in ServiceProviderCodeList and TelephoneNumberRange)
After that you can build your own parser, which can recognize this extension. But as I said, don't expect other tools to recognize it.

Right now, for testing purpose , Im just passing a string value from my CA Boulder. So to read that, this is the custom ASN1 Object STructure for TNAUthList.
public class TNAuthorizationList extends ASN1Object implements ASN1Choice{
public static final int spc = 0;
public static final int range = 1;
private ASN1Encodable obj;
private int tag;
public TNAuthorizationList(
int tag,
ASN1Encodable name)
{
this.obj = name;
this.tag = tag;
}
public TNAuthorizationList(
int tag,
String name)
{
this.tag = tag;
if (tag == spc)
{
this.obj = new DERIA5String(name);
}
else if (tag == range)
{
this.obj = new ASN1ObjectIdentifier(name);
}
else
{
throw new IllegalArgumentException("can't process String for tag: " + tag);
}
}
public static TNAuthorizationList getInstance(
Object obj)
{
if (obj == null || obj instanceof TNAuthorizationList)
{
return (TNAuthorizationList)obj;
}
if (obj instanceof ASN1TaggedObject)
{
ASN1TaggedObject tagObj = (ASN1TaggedObject)obj;
int tag = tagObj.getTagNo();
switch (tag)
{
case spc:
return new TNAuthorizationList(tag, DERIA5String.getInstance(tagObj, false));
}
}
if (obj instanceof byte[])
{
try
{
return getInstance(ASN1Primitive.fromByteArray((byte[])obj));
}
catch (IOException e)
{
throw new IllegalArgumentException("unable to parse encoded general name");
}
}
throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
}
public static TNAuthorizationList getInstance(
ASN1TaggedObject tagObj,
boolean explicit)
{
return TNAuthorizationList.getInstance(ASN1TaggedObject.getInstance(tagObj, true));
}
public int getTagNo()
{
return tag;
}
public ASN1Encodable getSpc()
{
return obj;
}
public String toString()
{
StringBuffer buf = new StringBuffer();
buf.append(tag);
buf.append(": ");
switch (tag)
{
case spc:
buf.append(DERIA5String.getInstance(obj).getString());
break;
default:
buf.append(obj.toString());
}
return buf.toString();
}
/**
*TNEntry ::= CHOICE {
* spc [0] ServiceProviderCodeList,
* range [1] TelephoneNumberRange,
* one E164Number
* }
*/
#Override
public ASN1Primitive toASN1Primitive() {
// TODO Auto-generated method stub
return new DERTaggedObject(false, tag, obj);
}
}
As You suggested I have passed the OID value to X509Util class and printed the Output.
ASN1Object o = X509ExtensionUtil.fromExtensionValue(cert.getExtensionValue("1.3.6.1.5.5.7.1.26"));
System.out.println("ASN1 Object: "+o);
System.out.println("get Class "+o.getClass());
and the O/P is
ASN1 Object: [SPID : 39 dc 2b]
get Class class org.bouncycastle.asn1.DLSequence
Is this fine. How do i parse this with my custom ASN1 Structure?

This is how im using ACME4j.
public class RSASignedCertificate {
private static final int KEY_SIZE = 2048;
private static final Logger LOG = Logger.getLogger(CCIDClient.class);
#SuppressWarnings("unused")
public void fetchCertificate(String domain,String spid, String email, int port,
String username, String password, String certPath) throws Exception {
// Load or create a key pair for the user's account
boolean createdNewKeyPair = true;
KeyPair domainKeyPair = null;
DomainKeyStore details = null;
KeyPair userKeyPair = null;
userKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
DateFormat dateTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date;
details = new DomainKeyStore();
// Create Hibernate Util class Object
// dao=new HibernateDAO();
boolean isDomainExist = new HibernateDAO().isDomainExist(domain);
if (isDomainExist) {
details.setDomain(domain);
details.setEmail(email);
date = new Date();
details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
boolean updateresult = new HibernateDAO().updateDetails(details);
LOG.info("User Details Updated ");
}
else {
date = new Date();
// Date currentDateTime = dateTime.parse(dateTime.format(date));
details.setEmail(email);
details.setDomain(domain);
details.setStatus("Not Registered");
details.setCreatedOn(dateTime.parse(dateTime.format(date)));
details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
boolean isInserted = new HibernateDAO().insertDetails(details);
if (!isInserted) {
throw new AcmeException("Unable to insert details");
}
LOG.info("User Details inserted ");
}
// details=dao.getDetails(domain);
Session session = null;
if (userKeyPair != null) {
session = new Session("http://192.168.1.143:4000/directory", userKeyPair);
System.out.println(session.getServerUri().toString());
System.out.println(session.resourceUri(Resource.NEW_REG));
}
Registration reg = null;
try {
reg = new RegistrationBuilder().create(session);
LOG.info("Registered a new user, URI: " + reg.getLocation());
} catch (AcmeConflictException ex) {
reg = Registration.bind(session, ex.getLocation());
LOG.info("Account does already exist, URI: " + reg.getLocation());
}
date = new Date();
details.setStatus("Registered");
details.setRegistrationDate(dateTime.parse(dateTime.format(date)));
details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
new HibernateDAO().updateRegistration(details);
URI agreement = reg.getAgreement();
LOG.info("Terms of Service: " + agreement);
if (createdNewKeyPair) {
boolean accepted = acceptAgreement(reg, agreement);
if (!accepted) {
return;
}
}
Authorization auth = null;
try {
auth = reg.authorizeDomain(spid);
} catch (AcmeUnauthorizedException ex) {
// Maybe there are new T&C to accept?
boolean accepted = acceptAgreement(reg, agreement);
if (!accepted) {
return;
}
// Then try again...
auth = reg.authorizeDomain(spid);
}
LOG.info("New authorization for domain " + spid);
LOG.info("Authorization " + auth);
Challenge challenge = tokenChallenge(auth);
// System.out.println("Challendg status before trigger :"+challenge.getStatus());
if (challenge == null) {
throw new AcmeException("No Challenge found");
}
if (challenge.getStatus() == Status.VALID) {
return;
}
challenge.trigger();
int attempts = 1;
// System.out.println("Challendg status after trigger :"+challenge.getStatus());
while (challenge.getStatus() != Status.VALID && attempts-- > 0) {
// System.out.println(challenge.getStatus());
if (challenge.getStatus().equals(Status.PENDING)) {
challenge.update();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
LOG.warn("interrupted", e);
e.printStackTrace();
}
}
if (challenge.getStatus() == Status.INVALID) {
LOG.error("Challenge failed... Giving up.");
throw new AcmeServerException("Challenge Failed");
}
try {
Thread.sleep(3000L);
} catch (InterruptedException ex) {
LOG.warn("interrupted", ex);
}
challenge.update();
}
if (challenge.getStatus() != Status.VALID) {
LOG.error("Failed to pass the challenge... Giving up.");
throw new AcmeServerException("Challenge Failed");
}
date = new Date();
details.setStatus("Clallenge Completed");
details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
new HibernateDAO().updateChallenge(details);
domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
// Generate a CSR for the domain
CSRBuilder csrb = new CSRBuilder();
csrb.addDomains(spid);
csrb.sign(domainKeyPair);
// System.out.println("CSR:" +csrb.getCSR());
LOG.info("Keys Algorithm: "
+ domainKeyPair.getPrivate().getAlgorithm());
PrivateKeyStore privatekey = new PrivateKeyStore();
privatekey.setDomain(spid);
privatekey.setEmail(email);
privatekey.setPrivateKey(domainKeyPair.getPrivate().getEncoded());
PublicKeyStore publickey = new PublicKeyStore();
publickey.setDomain(spid);
publickey.setEmail(email);
publickey.setPublicKey(domainKeyPair.getPublic().getEncoded());
// Request a signed certificate
Certificate certificate = reg.requestCertificate(csrb.getEncoded());
LOG.info("Success! The certificate for spids " + spid
+ " has been generated!");
LOG.info("Certificate URI: " + certificate.getLocation());
String nameFile = spid.replace(".", "") + ".cer";
X509Certificate sscert = CertificateUtils.createTlsSniCertificate(domainKeyPair,spid);
System.out.println("Certificate :" +sscert);
ASN1Primitive o = X509ExtensionUtil.fromExtensionValue(sscert.getExtensionValue(TNAuthorizationList.TN_AUTH_LIST_OID.getId()));
System.out.println("ASN1:Object "+o+" class: "+o.getClass());
TNAuthorizationList TNList = TNAuthorizationList.getInstance(o);
System.out.println(TNList.toString());
File createFile = new File(certPath + nameFile);
if (!createFile.exists()) {
createFile.createNewFile();
}
try (FileWriter fw = new FileWriter(createFile.getAbsoluteFile())) {
CertificateUtils.writeX509Certificate(sscert, fw);
System.out.println("Certificate " + sscert);
System.out.println("Certificate Content" + fw);
}
date = new Date();
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DATE, 90);
details.setIssueDate(dateTime.parse(dateTime.format(date)));
details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
details.setValidUntil(dateTime.parse(dateTime.format(c.getTime())));
details.setStatus("Issued");
details.setCertPath(certPath + nameFile);
new HibernateDAO().updateCertificate(details);
}
public boolean acceptAgreement(Registration reg, URI agreement) throws AcmeException
{
reg.modify().setAgreement(agreement).commit();
LOG.info("Updated user's ToS");
return true;
}
public Challenge tokenChallenge(Authorization auth)
{
TokenChallenge chall = auth.findChallenge(TokenChallenge.TYPE);
LOG.info("File name: " + chall.getType());
//LOG.info("Content: " + chall.`);
return chall;
}

Related

.net core identity server Custom token provider generating long token string

This is how I configured my custom token provider,
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Tokens.ProviderMap.Add("CustomEmailConfirmationTokenProvider",
new TokenProviderDescriptor(typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
options.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmationTokenProvider";
})
.AddEntityFrameworkStores<IdentityDbContext>()
.AddTokenProvider<CustomEmailConfirmationTokenProvider<ApplicationUser>>("CustomEmailConfirmationTokenProvider");
And this is code for custom token provider class,
public class CustomEmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,IOptions<CustomEmailConfirmationTokenProviderOptions> options
, ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options,logger)
{ }
}
public class CustomEmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public CustomEmailConfirmationTokenProviderOptions()
{
Name = "CustomEmailConfirmationTokenProvider";
TokenLifespan = TimeSpan.FromMinutes(1);
}
}
this is how I generate user token,
var myToken = await usrManager.GenerateUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose);
and this is how I verify the token validity using custom provider,
var isValid = await usrManager.VerifyUserTokenAsync(userinfo, "CustomEmailConfirmationTokenProvider",
UserManager<object>.ResetPasswordTokenPurpose, model.Token);
The above codes,including setting of TokenLifeSpan are working fine.
But the token is generating as a long string as example below,
CfDJ8IvIvIomoPJKkcJtJSNCN4wB6Fp82OPzYvkVaHtBzJBjY9EwOBt2nMg1WudWBTc1giurpRIXhSHeJTe3CLswJEOL7nng9Hd7H/ctDVNSEL5eBnzXVZpvSNmVCvgwIg3cwSNtcjjsYmGFA01EgyEkXXkBZg+jLDiEsKU8YgmaoQd5bOLE3WLopZo2lboG7dOnZv777SMHitbQNJ2SdRyZf2aMAybKAkHnKGIR3ZSyQXRM
I want to change this toke as just 6 digits characters.
Where should I modify to solve this issue?
If you look at the asp.net sources, you will see that the default token provider is inherited from the TotpSecurityStampBasedTokenProvider DefaultEmailTokenProvider which uses the "D6" format in the GenerateAsync method TotpSecurityStampBasedTokenProvider whereas your custom provider is inherited from the DataProtectorTokenProvider which uses the Base64String format in the GenerateAsync method DataProtectorTokenProvider. I think you can try to change the base class for your custom provider to get a 6 digits token, but the Rfc6238AuthenticationService that is used by the TotpSecurityStampBasedTokenProvider has a hardcoded lifespan of 3 minutes Rfc6238AuthenticationService. For creating a custom Rfc6238AuthenticationService see this answer.
CustomEmailConfirmationTokenProvider
public class CustomEmailConfirmationTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser> where TUser : class
{
private readonly TimeSpan _timeStep;
public CustomEmailConfirmationTokenProvider()
{
// Here you can setup expiration time.
_timeStep = TimeSpan.FromMinutes(1);
}
public override async Task<string> GenerateAsync(
string purpose,
UserManager<TUser> manager,
TUser user)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
byte[] token = await manager.CreateSecurityTokenAsync(user);
string async = CustomRfc6238AuthenticationService.GenerateCode
(
token,
await this.GetUserModifierAsync(purpose, manager, user),
_timeStep
)
.ToString("D6", (IFormatProvider)CultureInfo.InvariantCulture);
token = (byte[])null;
return async;
}
public override async Task<bool> ValidateAsync(
string purpose,
string token,
UserManager<TUser> manager,
TUser user)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
int code;
if (!int.TryParse(token, out code))
return false;
byte[] securityToken = await manager.CreateSecurityTokenAsync(user);
string userModifierAsync = await this.GetUserModifierAsync(purpose, manager, user);
return securityToken != null && CustomRfc6238AuthenticationService.ValidateCode(
securityToken,
code,
userModifierAsync,
_timeStep);
}
// It's just a dummy for inheritance.
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
{
return Task.FromResult(true);
}
}
CustomRfc6238AuthenticationService
public static class CustomRfc6238AuthenticationService
{
private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
internal static int ComputeTotp(
HashAlgorithm hashAlgorithm,
ulong timestepNumber,
byte[]? modifierBytes)
{
// # of 0's = length of pin
const int Mod = 1000000;
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifierBytes));
// Generate DT string
var offset = hash[hash.Length - 1] & 0xf;
Debug.Assert(offset + 4 < hash.Length);
var binaryCode = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| (hash[offset + 3] & 0xff);
return binaryCode % Mod;
}
private static byte[] ApplyModifier(Span<byte> input, byte[] modifierBytes)
{
var combined = new byte[checked(input.Length + modifierBytes.Length)];
input.CopyTo(combined);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
// More info: https://tools.ietf.org/html/rfc6238#section-4
private static ulong GetCurrentTimeStepNumber(TimeSpan timeStep)
{
var delta = DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch;
return (ulong)(delta.Ticks / timeStep.Ticks);
}
public static int GenerateCode(byte[] securityToken, string? modifier = null, TimeSpan? timeStep = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than time step in either direction
var currentTimeStep = GetCurrentTimeStepNumber(timeStep ?? _timestep);
var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
return ComputeTotp(hashAlgorithm, currentTimeStep, modifierBytes);
}
}
public static bool ValidateCode(byte[] securityToken, int code, string? modifier = null, TimeSpan? timeStep = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than time step in either direction
var currentTimeStep = GetCurrentTimeStepNumber(timeStep ?? _timestep);
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
for (var i = -2; i <= 2; i++)
{
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep - i), modifierBytes);
if (computedTotp == code)
{
return true;
}
}
}
// No match
return false;
}
}
Usage
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
var token = await _userManager.GenerateUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose);
var isValid = await _userManager.VerifyUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose, token);
}
Note: This algorithm has a variance of no greater than time step in either direction.

Library to verify OAuth 1.0a signature in ASP.NET Core

I need to validate OAuth 1.0a (RFC 5849) requests on an ASP.NET Core site. Upgrading the client to OAuth 2.0 or anything else is not an option. I understand the spec, but implementing the verification process for the oauth_signature seems like it would be a bit fragile, and surely there's no need to reinvent the wheel here.
Does .NET Core have any built-in classes for handling this? Ideally, something where you just pass in the HttpRequest and the secret key and it tells you if the signature is valid?
If there's nothing built in, any recommendations on third-party libraries that could handle this for me?
I really didn't feel comfortable taking a dependency hit on this one by bringing in a third-party NuGet package. Many of the options provided far more than I needed, and most were (understandably) no longer in active development or supported. Taking on an unsupported "black box" dependency with anything related to security doesn't sit quite right with me.
So I rolled my own implementation for just the subset of features that I needed to support (verification only, and OAuth parameters passed as form post data). This is not meant to be a complete implementation, but can serve as a starting point for anyone else who finds themselves in a similar situation and isn't interested in bringing in a dependency.
The latest code is on GitHub.
public static class OAuth1Utilities
{
private static readonly Lazy<bool[]> UnreservedCharacterMask = new Lazy<bool[]>(CreateUnreservedCharacterMask);
public static string EncodeString(string value)
{
byte[] characterBytes = Encoding.UTF8.GetBytes(value);
StringBuilder encoded = new StringBuilder();
foreach (byte character in characterBytes)
{
if (UnreservedCharacterMask.Value[character])
{
encoded.Append((char)character);
}
else
{
encoded.Append($"%{character:X2}");
}
}
return encoded.ToString();
}
public static string GetBaseStringUri(HttpRequest request)
{
StringBuilder baseStringUri = new StringBuilder();
baseStringUri.Append(request.Scheme.ToLowerInvariant());
baseStringUri.Append("://");
baseStringUri.Append(request.Host.ToString().ToLowerInvariant());
baseStringUri.Append(request.Path.ToString().ToLowerInvariant());
return baseStringUri.ToString();
}
public static string GetNormalizedParameterString(HttpRequest request)
{
var parameters = new List<(string key, string value)>();
foreach (var queryItem in request.Query)
{
foreach (var queryValue in queryItem.Value)
{
parameters.Add((queryItem.Key, queryValue));
}
}
foreach (var formItem in request.Form)
{
foreach (var formValue in formItem.Value)
{
parameters.Add((formItem.Key, formValue));
}
}
parameters.RemoveAll(_ => _.key == "oauth_signature");
parameters = parameters
.Select(_ => (key: EncodeString(_.key), value: EncodeString(_.value)))
.OrderBy(_ => _.key)
.ThenBy(_ => _.value).ToList();
return string.Join("&", parameters.Select(_ => $"{_.key}={_.value}"));
}
public static string GetSignature(HttpRequest request, string clientSharedSecret, string tokenSharedSecret)
{
string signatureBaseString = GetSignatureBaseString(request);
return GetSignature(signatureBaseString, clientSharedSecret, tokenSharedSecret);
}
public static string GetSignature(string signatureBaseString, string clientSharedSecret, string tokenSharedSecret)
{
string key = $"{EncodeString(clientSharedSecret)}&{EncodeString(tokenSharedSecret)}";
var signatureAlgorithm = new HMACSHA1(Encoding.ASCII.GetBytes(key));
byte[] digest = signatureAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString));
return Convert.ToBase64String(digest);
}
public static string GetSignatureBaseString(HttpRequest request)
{
StringBuilder signatureBaseString = new StringBuilder();
signatureBaseString.Append(request.Method.ToUpperInvariant());
signatureBaseString.Append("&");
signatureBaseString.Append(EncodeString(GetBaseStringUri(request)));
signatureBaseString.Append("&");
signatureBaseString.Append(EncodeString(GetNormalizedParameterString(request)));
return signatureBaseString.ToString();
}
public static bool VerifySignature(HttpRequest request, string clientSharedSecret, string tokenSharedSecret)
{
string actualSignature = request.Form["oauth_signature"];
string expectedSignature = GetSignature(request, clientSharedSecret, tokenSharedSecret);
return expectedSignature == actualSignature;
}
private static bool[] CreateUnreservedCharacterMask()
{
bool[] mask = new bool[byte.MaxValue];
// hyphen
mask[45] = true;
// period
mask[46] = true;
// 0-9
for (int pos = 48; pos <= 57; pos++)
{
mask[pos] = true;
}
// A-Z
for (int pos = 65; pos <= 90; pos++)
{
mask[pos] = true;
}
// underscore
mask[95] = true;
// a-z
for (int pos = 97; pos <= 122; pos++)
{
mask[pos] = true;
}
// tilde
mask[126] = true;
return mask;
}
}

autodesk design automation

FATAL ERROR: Unhandled Access Violation Reading 0x0008 Exception at 1d8257a5h
Failed missing output
I finally made it work with HostApplicationServices.getRemoteFile in local AutoCAD, then migrated it to Design Automation. It is also working now. The below is the command of .NET plugin.
To have a simple test, I hard-coded the URL in the plugin. you could replace the URL with the workflow at your side (either by an json file, or input argument of Design Automation)
My demo ReadDWG the entities from the remote URL file, then wblock the entities to current drawing (HostDWG), finally save current drawing.
Hope it helps to address the problem at your side.
.NET command
namespace PackageNetPlugin
{
class DumpDwgHostApp: HostApplicationServices
{
public override string FindFile(string fileName,
Database database,
FindFileHint hint)
{
throw new NotImplementedException();
}
public override string GetRemoteFile(Uri url,
bool ignoreCache)
{
//return base.GetRemoteFile(url, ignoreCache);
Database db =
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument.Database;
string localPath = string.Empty;
if (ignoreCache)
{
localPath =
Autodesk.AutoCAD.ApplicationServices.Application.
GetSystemVariable("STARTINFOLDER") as string;
string filename =
System.IO.Path.GetFileName(url.LocalPath);
localPath += filename;
using (var client = new WebClient())
{
client.DownloadFile(url, localPath);
}
}
return localPath;
}
public override bool IsUrl(string filePath)
{
Uri uriResult;
bool result = Uri.TryCreate(filePath,
UriKind.Absolute, out uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp ||
uriResult.Scheme == Uri.UriSchemeHttps);
return result;
}
}
public class Class1
{
[CommandMethod("MyPluginCommand")]
public void MyPluginCommand()
{
try {
string drawingPath =
#"https://s3-us-west-2.amazonaws.com/xiaodong-test-da/remoteurl.dwg";
DumpDwgHostApp oDDA = new DumpDwgHostApp();
string localFileStr = "";
if (oDDA.IsUrl(drawingPath)){
localFileStr = oDDA.GetRemoteFile(
new Uri(drawingPath), true);
}
if(!string.IsNullOrEmpty(localFileStr))
{
//source drawing from drawingPath
Database source_db = new Database(false, true);
source_db.ReadDwgFile(localFileStr,
FileOpenMode.OpenTryForReadShare, false, null);
ObjectIdCollection sourceIds =
new ObjectIdCollection();
using (Transaction tr =
source_db.TransactionManager.StartTransaction())
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(source_db),
OpenMode.ForRead);
foreach (ObjectId id in btr)
{
sourceIds.Add(id);
}
tr.Commit();
}
//current drawing (main drawing working with workitem)
Document current_doc =
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument;
Database current_db = current_doc.Database;
Editor ed = current_doc.Editor;
//copy the objects in source db to current db
using (Transaction tr =
current_doc.TransactionManager.StartTransaction())
{
IdMapping mapping = new IdMapping();
source_db.WblockCloneObjects(sourceIds,
SymbolUtilityServices.GetBlockModelSpaceId(current_db),
mapping, DuplicateRecordCloning.Replace, false);
tr.Commit();
}
}
}
catch(Autodesk.AutoCAD.Runtime.Exception ex)
{
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument.Editor.WriteMessage(ex.ToString());
}
}
}
}

AsyncTask doInBackground() does not execute correctly on run, but works on debugger

#Override
protected ArrayList<HashMap<String, String>> doInBackground(Void... params) {
ArrayList<HashMap<String, String>> PLIST = new ArrayList<>();
HttpHandler sh = new HttpHandler();
String jsonStr = sh.makeServiceCall(jsonUrl);
ArrayList<String> URLList = new ArrayList<>();
if (jsonStr != null) {
placesList.clear();
try {
JSONObject jsonObj = new JSONObject(jsonStr);
// Getting JSON Array node
JSONArray placesJsonArray = jsonObj.getJSONArray("results");
String pToken = "";
// looping through All Places
for (int i = 0; i < placesJsonArray.length(); i++) {
JSONObject placesJSONObject = placesJsonArray.getJSONObject(i);
String id = placesJSONObject.getString("id");
String name = placesJSONObject.getString("name");
HashMap<String, String> places = new HashMap<>();
// adding each child node to HashMap key => value
places.put("id", id);
places.put("name", name);
PLIST.add(places);
}
//TODO: fix this...
if (SEARCH_RADIUS == 1500) {
Log.e(TAG, "did it get to 1500?");
try {
for (int k = 0; k < 2; k++) {
//error is no value for next_page_token... this
ERROR HERE
pToken = jsonObj.getString("next_page_token"); //if I place breakpoint here, debugger runs correctly, and returns more than 20 results if there is a next_page_token.
String newjsonUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location="
+ midpointLocation.getLatitude() + "," + midpointLocation.getLongitude()
+ "&radius=" + SEARCH_RADIUS + "&key=AIzaSyCiK0Gnape_SW-53Fnva09IjEGvn55pQ8I&pagetoken=" + pToken;
URLList.add(newjsonUrl);
jsonObj = new JSONObject(new HttpHandler().makeServiceCall(newjsonUrl)); //moved
Log.e(TAG, "page does this try catch");
}
}
catch (Exception e ) {
Log.e(TAG, "page token not found: " + e.toString());
}
for (String url : URLList){
Log.e(TAG, "url is : " + url);
}
I made an ArrayList of URLS after many attempts to debug this code, I planned on unpacking the ArrayList after all the urls with next_page_tokens were added, and then parsing through each of them later. When running the debugger with the breakpoint on pToken = getString("next_page_token") i get the first url from the Logger, and then the second url correctly. When I run as is, I get the first url, and then the following error: JSONException: No value for next_page_token
Things I've tried
Invalidating Caches and restarting
Clean Build
Run on different SDK versions
Made sure that the if statement is hitting (SEARCH_RADIUS == 1500)
Any help would be much appreciated, thanks!
Function is called in a listener function like this.
new GetPlaces(new AsyncResponse() {
#Override
public void processFinish(ArrayList<HashMap<String, String>> output) {
Log.e(TAG, "outputasync:" );
placesList = output;
}
}).execute();
My onPostExecute method.
#Override
protected void onPostExecute(ArrayList<HashMap<String, String>> result) {
delegate.processFinish(result);
// Dismiss the progress dialog
if (pDialog.isShowing())
pDialog.dismiss();
}
It turns out that the google places api takes a few milliseconds to validate the next_page_token after it is generated. As such, I have used the wait() function to pause before creating the newly generated url based on the next_page_token. This fixed my problem. Thanks for the help.

VCR for ServiceStack's JsonServiceClient

The Ruby VCR library enables you to "Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests."
I'd like to create something similar using ServiceStack's JsonServiceClient, but I can't get it to work. My most recent failed attempt follows. I'd like to either make my current attempt work, or suggestions on another approach that will work.
public static class Memoization
{
public static Func<T, TResult> AsCached<T, TResult>(this Func<T, TResult> function)
{
var cachedResults = new Dictionary<T, TResult>();
string filename = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + (typeof(TResult)).Name + ".jsv";
var serializer = MessagePackSerializer.Create<Dictionary<T, TResult>>();
if (cachedResults.Count == 0)
{
////// load cache from file
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
cachedResults = serializer.Unpack(fs);
}
}
return (argument) =>
{
TResult result;
lock (cachedResults)
{
if (!cachedResults.TryGetValue(argument, out result))
{
result = function(argument);
cachedResults.Add(argument, result);
////// update cache file
using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
serializer.Pack(fs, cachedResults);
}
}
}
return result;
};
}
}
class MemoizeJsonClient<TResponse> : JsonServiceClient, IServiceClient, IRestClient
{
private Func<IReturn<TResponse>, TResponse> _getCached;
private JsonServiceClient client;
public TResponse Get(IReturn<TResponse> request)
{
if (_getCached == null)
{
Func<IReturn<TResponse>, TResponse> func = GetImpl;
_getCached = func.AsCached();
}
return _getCached(request);
}
private TResponse GetImpl(IReturn<TResponse> request)
{
return client.Get(request);
}
public MemoizeJsonClient(string BaseUri) {
client = new JsonServiceClient(BaseUri);
}
}
Called like this:
[Test]
public void TestReports2()
{
string Host = "http://localhost:1337";
string BaseUri = Host + "/";
List<Options> testcases = new List<Options>();
testcases.Add(new Options("Name", "20130815", "20130815"));
foreach (Options options in testcases)
{
TransactionsReq transRequest = new TransactionsReq();
transRequest.source = "Source";
transRequest.name = new List<String>(new string[] { options.Name });
transRequest.startDate = options.StartDate;
transRequest.endDate = options.EndDate;
MemoizeJsonClient<TransactionsReqResponse> client = new MemoizeJsonClient<TransactionsReqResponse>(BaseUri);
List<Transaction> transactions;
TransactionsReqResponse transResponse = client.Get(transRequest);
transactions = transResponse.data;
}
}
But I get the following error:
System.Runtime.Serialization.SerializationException occurred
HResult=-2146233076
Message=Cannot serialize type 'ServiceStack.ServiceHost.IReturn`1[ImagineServerWrapper.DTO.TransactionsReqResponse]' because it does not have any serializable fields nor properties.
Source=MsgPack
StackTrace:
at MsgPack.Serialization.SerializerBuilder`1.CreateSerializer()
InnerException: