How can I read the X509 Certificate Extension: Subject Directory Attributes with OpenSSL in C++?
My Certificate contains the SubjectDirectoryAttributes-Extension with the following Attributes:
OID : Value
-------------------------------------------------------------------
(1.3.6.1.5.5.7.9.4) countryOfCitizenship : DE
(1.3.6.1.5.5.7.9.3) gender : F
(1.3.6.1.5.5.7.9.1) dateOfBirth : 1971-10-14 12:00:00 UTC
(1.3.6.1.5.5.7.9.2) placeOfBirth : Darmstadt
So i want to get these pairs of OID and Value.
I found no Struct like SUBJECT_DIRECTORY_ATTRIBUTES in the Source-Code I can use. I got the Extension this way:
int loc = X509_get_ext_by_NID(certificate, NID_subject_directory_attributes, -1);
X509_EXTENSION *ex = X509_get_ext(certificate, loc);
But how can I get then all the data, which means all the OIDs and Values to the OIDs? The ASN.1 Structure is:
SubjectDirectoryAttributes ::= Attributes
Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
Attribute ::= SEQUENCE
{
type AttributeType
values SET OF AttributeValue
}
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY DEFINED BY AttributeType
I found out that I get a custom extension with: X509_EXTENSION_get_object(ex) and that the OpenSSL-Type X509_NAME_ENTRY is the equvivalent to the ASN.1-Structure Attribute resp. AttributeTypeAndValue.
So i tried to cast the result of X509_EXTENSION_get_data(ex) to a STACK_OF(X509_NAME_ENTRY) and to X509_NAME. But X509_NAME is the same as STACK_OF(X509_NAME_ENTRY).
Then I tried to get the number of attributes by calling the sk_X509_NAME_ENTRY_num() function on the STACK_OF(X509_NAME_ENTRY) resp. X509_NAME.entries, but I got not the right number.
I expect to get the number 3 or 4 (don't know the exactly internal counting - but the example cert contains 4 Attributes, so the output should be 3 or 4 depending if the counting will start at 0 or 1).
But instead of 3 or 4 I got a much larger number like 34335029 and this number is different every time I run the code. So I think there is a problem with the casting or I did not choose the right Data-Type(s).
I'm using OpenSSL 1.0.2j.
So what's wrong and how can I fix it?
Here a short excerpt of my code:
X509_EXTENSION *ex = ....
STACK_OF(X509_NAME_ENTRY) *st = (STACK_OF(X509_NAME_ENTRY)*) X509_EXTENSION_get_data(ex);
printf(sk_X509_NAME_ENTRY_num(st));
// or alternative
X509_Name *name = (X509_Name*) X509_EXTENSION_get_data(ex);
printf(sk_X509_NAME_ENTRY_num(name.entries));
Here I append the certificate if you need it. It's from the RFC specification:
-----BEGIN CERTIFICATE-----
MIIDEDCCAnmgAwIBAgIESZYC0jANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQGEwJE
RTE5MDcGA1UECgwwR01EIC0gRm9yc2NodW5nc3plbnRydW0gSW5mb3JtYXRpb25z
dGVjaG5payBHbWJIMB4XDTA0MDIwMTEwMDAwMFoXDTA4MDIwMTEwMDAwMFowZTEL
MAkGA1UEBhMCREUxNzA1BgNVBAoMLkdNRCBGb3JzY2h1bmdzemVudHJ1bSBJbmZv
cm1hdGlvbnN0ZWNobmlrIEdtYkgxHTAMBgNVBCoMBVBldHJhMA0GA1UEBAwGQmFy
emluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc50zVodVa6wHPXswg88P8
p4fPy1caIaqKIK1d/wFRMN5yTl7T+VOS57sWxKcdDzGzqZJqjwjqAP3DqPK7AW3s
o7lBG6JZmiqMtlXG3+olv+3cc7WU+qDv5ZXGEqauW4x/DKGc7E/nq2BUZ2hLsjh9
Xy9+vbw+8KYE9rQEARdpJQIDAQABo4HpMIHmMGQGA1UdCQRdMFswEAYIKwYBBQUH
CQQxBBMCREUwDwYIKwYBBQUHCQMxAxMBRjAdBggrBgEFBQcJATERGA8xOTcxMTAx
NDEyMDAwMFowFwYIKwYBBQUHCQIxCwwJRGFybXN0YWR0MA4GA1UdDwEB/wQEAwIG
QDASBgNVHSAECzAJMAcGBSskCAEBMB8GA1UdIwQYMBaAFAABAgMEBQYHCAkKCwwN
Dg/+3LqYMDkGCCsGAQUFBwEDBC0wKzApBggrBgEFBQcLAjAdMBuBGW11bmljaXBh
bGl0eUBkYXJtc3RhZHQuZGUwDQYJKoZIhvcNAQEFBQADgYEAj4yAu7LYa3X04h+C
7+DyD2xViJCm5zEYg1m5x4znHJIMZsYAU/vJJIJQkPKVsIgm6vP/H1kXyAu0g2Ep
z+VWPnhZK1uw+ay1KRXw8rw2mR8hQ2Ug6QZHYdky2HH3H/69rWSPp888G8CW8RLU
uIKzn+GhapCuGoC4qWdlGLWqfpc=
-----END CERTIFICATE-----
Since OpenSSL has no direct support in the crypto libary there is no built in code for parsing this extension. I have either to wait for official support or implement the whole thing by myself.
Related
I'm testing out Goa for an API. I want to use uuid as an ID data type. I modified the following function in controller.go:
// Show runs the show action.
func (c *PersonnelController) Show(ctx *app.ShowPersonnelContext) error {
// v4 UUID validate regex
var validate = `^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[8|9|aA|bB][a-f0-9]{3}-[a-f0-9]{12}$`
uuidValidator := regexp.MustCompile(validate)
if !uuidValidator.Match(ctx.MemberID) {
return ctx.NotFound()
}
// Build the resource using the generated data structure
personnel := app.GoaCrewWorkforce{
MemberID: ctx.MemberID,
FirstName: fmt.Sprintf("First-Name #%s", ctx.MemberID),
LastName: fmt.Sprintf("Last-Name #%s", ctx.MemberID),
}
What I would like to do is validate a v4 uuid in my controller using Regexp so that it does not poll the server if it does not validate. It's my understanding a uuid is a [16]byte slice. Regexp has a Match []byte function. Yet I can't seem to understand why I get the following error:
cannot use ctx.MemberID (type uuid.UUID) as type []byte in argument to uuidValidator.Match
How can I type assert ctx.MemberID? I don't think its possible to do a cast conversion in this case? Any guidance is appreciated.
If you want to validate the uuid, you can check the bits directly. There's not much to verify, since most of the 16 bytes are random, but you can check the top 4 bits of the 6th byte for the version number, or the top 3 bits of the 8th byte for the variant.
// enforce only V4 uuids
if ctx.MemberID[6] >> 4 != 4 {
log.Fatal("not a V4 UUID")
}
// enforce only RFC4122 type UUIDS
if (ctx.MemberID[8]&0xc0)|0x80 != 0x80 {
log.Fatal("not an RFC4122 UUID")
}
first of all: Sorry for my bad english. I hope you can understand me.
I using OpenSSL 1.0.2 and I have some problems with it. I have a STACK_OF(X509) object with three certificates and I want to convert this stack to an ASN1 object (like OCTET STRING or ANY). Is this possible? I use an external asn.1 library and not OpenSSL-ASN1. So I need the data as unsigned char (raw binary or DER encoded).
I can convert a single certificate via i2d_X509() into DER format. This is good, but I want the complete stack. This is my goal:
myAsn1Data ::= sequence {
... -- (some ASN.1 data)
... -- (some ASN.1 data)
ANY -- contain the DER encoded STACK_OF(X509)....somehow
}
But possibly is this the right way (ASN.1 syntax):
stackOfX509 ::= SEQUENCE_Of {
TBSCertificate
}
or like this:
stackOfX509 ::= SET_OF {
ANY -- contain a DER encoded X509 certificate
}
I hope someone can help me.
People normally do one of two things for this.
1) "Cheat"! If the DER encoded representation of the certificate is available but the original schema for it is not, simply have an OCTET STRING to store the bytes.
myAsn1Data ::= SEQUENCE {
..., -- (some ASN.1 Data)
..., -- (some ASN.1 Data)
cert [2] OCTET STRING -- (the DER bytes would go in this field)
}
and the flow of your application code would be something like
myAsn1Data data = new(myAsn1Data);
data.cert = readbytesfromfile(certificatefile);
data.something = 10;
data.else = 20;
asn1encode(data, outputfile)
2) Acquire the original schema for an X509 certificate. It ought to be in here somewhere, looks like section 7.2. With this the source your ASN.1 compiler will create for you should be able to decode the certificate, allowing you to place it inside an instance of your myAsn1Data.
IMPORTS Certificate FROM <ITU's X509 Schema file>
CertificateStack ::= SEQUENCE of Certificate
myAsn1Data ::= SEQUENCE {
..., -- (some ASN.1 Data)
..., -- (some ASN.1 Data)
cert [2] CertificateStack
}
and the sequences in your application code would look something like this:
Certificate cert1 = asn1decode(certificatefile);
Certificate cert2 = asn1decode(anothercertificatefile);
Certificate cert3 = asn1decode(yetanothercertificatefile);
CertificateStack stack;
stack.push_back(cert1);
stack.push_back(cert2);
stack.push_back(cert3);
myAsn1Data data = new(myAsn1Data);
data.cert = stack;
data.something = 10;
data.else = 20;
asn1encode(data, outputfile)
This way is better because it completely defines what the 'cert' field actually is. The first way isn't quite so good if you are exchanging data with someone else because they need to be told what the cert field actually is.
I have a list of countries that i need to convert into standardized format (iso3c). Some have long names, others have 2 or 3 digit codes, and others do not display the whole country name like "Africa" instead of "South Africa". Ive done some research and come up to use countrycode package in R. However, when i tried to use "regex" R doesnt seem to recognize it. Im getting the error below:
> countrycode(data,"regex","iso3c", warn = TRUE)
Error in countrycode(data, "regex", "iso3c", :
Origin code not supported
Any other option I need to do?
Thanks!
You can view the README for the countrycode package here https://github.com/vincentarelbundock/countrycode, or you can pull up the help file in R by entering this into your R console ?countrycode::countrycode.
"regex" is not a valid 'origin' value (2nd argument in the countrycode() function). You must use one of "cowc", "cown", "eurostat", "fao", "fips105", "imf", "ioc", "iso2c", "iso3c", "iso3n", "p4_ccode", "p4_scode", "un", "wb", "wb_api2c", "wb_api3c", "wvs", "country.name", "country.name.de" (using latest version 0.19).
If you use either of the following 'origin' values, regex matching will be performed automatically: "country.name" or "country.name.de"
If you're using a custom dictionary with the new (as of version 0.19) custom_dict argument, you must set the origin_regex argument to TRUE for regex matching to occur.
In your example, this should do what you want:
countrycode(data, origin = "country.name", destination = "iso3c", warn = TRUE)
I am at my wit's end on this one, I just can't find the right combination of code to make this work. I'm trying to create an authentication digest for an API query. I've tried many CFML functions (for example: Coldfusion HMAC-SHA1 encryption and HMAC SHA1 ColdFusion), but I'm not coming up with the same results that are cited in the API documentation. Here's that example (basically elements of the request header with line breaks as delimiters.):
application/xml\nTue, 30 Jun 2009 12:10:24 GMT\napi.summon.serialssolutions.com\n/2.0.0/search\ns.ff=ContentType,or,1,15&s.q=forest\n
and here's the key:
ed2ee2e0-65c1-11de-8a39-0800200c9a66
which according to the documentation should result in:
3a4+j0Wrrx6LF8X4iwOLDetVOu4=
when the HMAC hash is converted to Base64. Any ideas would be most appreciated!
The problem is your input string, not the functions. The first one works fine. Though I would change the charset to UTF-8, or make it an argument. Otherwise, the results are dependent on the jvm default, which may not always be correct, and can change which would break the code.
Verify you are constructing the sample string correctly. Are you using chr(10) for new lines? Note: It must also end with a new line.
Code:
<cfscript>
headers = [ "application/xml"
, "Tue, 30 Jun 2009 12:10:24 GMT"
, "api.summon.serialssolutions.com"
, "/2.0.0/search"
, "s.ff=ContentType,or,1,15&s.q=forest"
];
theText = arrayToList(headers, chr(10)) & chr(10);
theKey = "ed2ee2e0-65c1-11de-8a39-0800200c9a66";
theHash = binaryEncode( hmacEncrypt(theKey, theText), "base64");
writeDump(theHash);
</cfscript>
Result:
3a4+j0Wrrx6LF8X4iwOLDetVOu4=
Is there any way to authenticate users from other web apps using the DNN logins?
We have a main site that is using DNN and user logins are stored in the asp net membership table. From what I have been reading, the passwords are encrypted using the machine key and then salted. I see where this info is, but can't seem to encrypt passwords correctly using this method.
I'm trying with a Coldfusion web application on the same server where our DNN site is, but it doesn't want to work. You'd think it would be strait forward with the ColdFusion encryption function:
Encrypt(passwordstring, key [, algorithm, encoding, IVorSalt, iterations])
No matter what I try, I never get a matching value.
Any help, insight or pointing me in the right direction would be greatly appreciated!
(Edit: Original answer did not work in all cases. Substantially revised ...)
From what I have read, DNN uses an "SHA1" hash by default. The thread #barnyr posted shows it simply hashes the concatenated salt and password, but with a few twists.
DNN uses UTF-16LE to extract the password bytes, rather than CF's typical UTF-8.
It also extracts the salt and password bytes separately, which may produce different results than just decoding everything as a single string, which is what hash() does. (See demo below)
Given that CF9's Hash function does not accept binary (supported in CF11), I do not think it is possible to duplicate the results with native CF functions alone. Instead I would suggest decoding the strings into binary, then using java directly:
Code:
<cfscript>
thePassword = "DT!#12";
base64Salt = "+muo6gAmjvvyy5doTdjyaA==";
// extract bytes of the salt and password
saltBytes = binaryDecode(base64Salt, "base64");
passBytes = charsetDecode(thePassword, "UTF-16LE" );
// next combine the bytes. note, the returned arrays are immutable,
// so we cannot use the standard CF tricks to merge them
ArrayUtils = createObject("java", "org.apache.commons.lang.ArrayUtils");
dataBytes = ArrayUtils.addAll( saltBytes, passBytes );
// hash binary using java
MessageDigest = createObject("java", "java.security.MessageDigest").getInstance("SHA-1");
MessageDigest.update(dataBytes);
theBase64Hash = binaryEncode(MessageDigest.digest(), "base64");
WriteOutput("theBase64Hash= "& theBase64Hash &"<br/>");
</cfscript>
Demo of Differences:
<cfscript>
theEncoding = "UTF-16LE";
thePassword = "DT!#12";
base64Salt = "+muo6gAmjvvyy5doTdjyaA==";
// extract the bytes SEPARATELY
saltBytes = binaryDecode(base64Salt, "base64");
passBytes = charsetDecode(thePassword, theEncoding );
ArrayUtils = createObject("java", "org.apache.commons.lang.ArrayUtils");
separateBytes = ArrayUtils.addAll( saltBytes, passBytes );
// concatenate first, THEN extract the bytes
theSalt = charsetEncode( binaryDecode(base64Salt, "base64"), theEncoding );
concatenatedBytes = charsetDecode( theSalt & thePassword, theEncoding );
// these are the raw bytes BEFORE hashing
WriteOutput("separateBytes= "& arrayToList(separateBytes, "|") &"<br>");
WriteOutput("concatenatedBytes"& arrayToList(concatenatedBytes, "|") );
</cfscript>
Results:
separateBytes = -6|107|-88|-22|0|38|-114|-5|-14|-53|-105|104|77|-40|-14|104|68|0|84|0|33|0|64|0|49|0|50|0
concatenatedBytes = -6|107|-88|-22|0|38|-114|-5|-14|-53|-105|104|-3|-1|68|0|84|0|33|0|64|0|49|0|50|0
Most likely the password is not encrypted, it is hashed. Hashing is different from encrypting, because it is not reversible.
You would not use ColdFusion's encrypt() function for this, you would use its hash() function.
So the questions you'll need to answer to figure out how to hash the passwords in CF to be able to auth against the DNN users are:
What algorithm is DNN using to hash the passwords?
How is the salt being used with the password prior to hashing?
Is DNN iterating over the hash X number of times to improve security?
All of those questions must be answered to determine how CF must use the hash() function in combination with the salt and user-submitted passwords.
I'll make some assumptions to provide an answer.
If we assume that noiteration is being done and that the salt is simply being appended to the password prior to using SHA1 to hash the password, then you'd be able to reproduce the hash digest like this:
<cfset hashDigest = hash(FORM.usersubmittedPassword & saltFromDB, "SHA1") />
(Posting a new response to keep the "encrypted" process separate from "hashing")
For "encrypted" keys, the DNN side uses the standard algorithms ie DES, 3DES or AES - depending on your machineKey settings. But with a few differences you need to match in your CF code. Without knowing your actual settings, I will assume you are using the default 3DES for now.
Data To Encrypt
The encrypted value is a combination of the salt and password. But as with hashing, DNN uses UTF-16LE. Unfortunately, ColdFusion's Encrypt() function always assumes UTF-8, which will produce a very different result. So you need to use the EncryptBinary function instead.
// sample valus
plainPassword = "password12345";
base64Salt = "x7le6CBSEvsFeqklvLbMUw==";
hexDecryptKey = "303132333435363738393031323334353637383930313233";
// first extract the bytes of the salt and password
saltBytes = binaryDecode(base64Salt, "base64");
passBytes = charsetDecode(plainPassword, "UTF-16LE" );
// next combine the bytes. note, the returned arrays are immutable,
// so we cannot use the standard CF tricks to merge them
ArrayUtils = createObject("java", "org.apache.commons.lang.ArrayUtils");
dataBytes = ArrayUtils.addAll( saltBytes, passBytes );
Encryption Algorithm
With block ciphers, ColdFusion defaults to ECB mode. (See Strong Encryption in ColdFusion) Whereas .NET defaults to CBC mode, which requires an additional IV value. So you must adjust your CF code to match.
// convert DNN hex key to base64 for ColdFusion
base64Key = binaryEncode(binaryDecode( hexDecryptKey, "hex"), "base64");
// create an IV and intialize it with all zeroes
// block size: 16 => AES, 8=> DES or TripleDES
blockSize = 8;
iv = javacast("byte[]", listToArray(repeatString("0,", blocksize)));
// encrypt using CBC mode
bytes = encryptBinary(dataBytes, base64Key, "DESede/CBC/PKCS5Padding", iv);
// result: WBAnoV+7cLVI95LwVQhtysHb5/pjqVG35nP5Zdu7T/Cn94Sd8v1Vk9zpjQSFGSkv
WriteOutput("encrypted password="& binaryEncode( bytes, "base64" ));