Ad

Our DNA is written in Swift
Jump

Parsing ASN.1 for Certificates and Pleasure

When figuring out how to work with self-signed certificates and helping somebody debug an SSL problem I did a bit of research regarding the certificates being used on iOS and what functions are available to inspect these.

There are two functions provided to get more information from a SCCertificateRef , one to get a meager description, the other to get the entire certificate, encoded in ASN.1. But there you’re stuck, unless you want to mess with compiling and installing OpenSSL.

OpenSSL is the de-facto standard method of decoding certificates on Mac. For example you can parse a binary ASN.1-encoded (aka DER) certificate as easy as:

openssl asn1parse -in certi.der
   0:d=0  hl=4 l=1086 cons: SEQUENCE
    4:d=1  hl=4 l= 806 cons: SEQUENCE
    8:d=2  hl=2 l=   3 cons: cont [ 0 ]
   10:d=3  hl=2 l=   1 prim: INTEGER           :02
   13:d=2  hl=2 l=  16 prim: INTEGER           :19D031A4DCCFB1691284E00750BA7A23
   31:d=2  hl=2 l=  13 cons: SEQUENCE
   33:d=3  hl=2 l=   9 prim: OBJECT            :sha1WithRSAEncryption
   44:d=3  hl=2 l=   0 prim: NULL
   46:d=2  hl=2 l=  94 cons: SEQUENCE
   48:d=3  hl=2 l=  11 cons: SET
   50:d=4  hl=2 l=   9 cons: SEQUENCE
   52:d=5  hl=2 l=   3 prim: OBJECT            :countryName
   57:d=5  hl=2 l=   2 prim: PRINTABLESTRING   :US
   61:d=3  hl=2 l=  21 cons: SET
   63:d=4  hl=2 l=  19 cons: SEQUENCE
   65:d=5  hl=2 l=   3 prim: OBJECT            :organizationName
   70:d=5  hl=2 l=  12 prim: PRINTABLESTRING   :Thawte, Inc.
...

OpenSSL comes preinstalled on all Macs, but we don’t know what it was that Apple did not like about it to omit it on iOS. Was it the “Open”? Was is the license? We will never know.

The above is exactly the same information that Safari shows you if an SSL connection cannot be verified. Wouldn’t it be nice to be able to get this kind of mechanism for our own apps as well? Some clients might want to be able to accept self-signed certificates, but not do so without notice, but rather show all certificate details to the user like Safari does.  With DTASN1Parser this becomes possible.

Another scenario that needs to decode certificates is to validate them, for example to check if the certificate your app is signed with is indeed one for the app store …

I remember – a couple of years ago – I wrote an ASN.1 Decoder and Encoder in C++, but those backups were lost in time. So I scraped together in information about the format what I could find. Unfortunately it is just as cryptic as the cryptography that is is used for in our case.

I found and borrowed ideas from two web-based decoders (1, 2), skimmed through a “Layman’s Guide“,  marveled at The Anatomy of a X.509 Certificate, but then gave up on the mother of all ASN1 tutorials: ASN1 Complete.

Getting the Certificate

If you do an NSURLConnection to a server with a self-signed (or shady) certificate you get a callback for an authentication space. From this you can get the SecTrust and from that you may retrieve the certificates used.

– (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef secTrust = [challenge.protectionSpace serverTrust];

SecCertificateRef certi = SecTrustGetCertificateAtIndex(secTrust, 0);
NSData *data = (__bridge_transfer NSData *) SecCertificateCopyData(certi);
// .. we have a certificate in DER format!

NSString *summ = (__bridge_transfer NSString *) SecCertificateCopySubjectSummary(certi);
// … a useless summary

I saved a certificate to disk with this method and – lo and behold! – you can inspect the certificate with QuickLook.

That’s quite convenient, because you have a direct comparison with what data we can decode with DTASN1Parser.

DTASN1Parser walks through the hierarchy contained within the ASN.1 data and emits events whenever it encounters something noteworthy. To build a certificate class with it you would create a parser with the data and then act on the individual events. Here’s the basic structure:

@implementation DTCertificate
{
	NSData *_data;
 
	id _rootContainer;
	id _currentContainer;
	NSMutableArray *_stack;
 
	NSString *currentObjectIdentifier;
	DTASN1Parser *_parser;
}
 
- (id)initWithData:(NSData *)data
{
	self = [super init];
	if (self)
	{
		_parser = [[DTASN1Parser alloc] initWithData:data];
		_parser.delegate = self;
 
		_stack = [[NSMutableArray alloc] init];
 
		if (![_parser parse])
		{
			return nil;
		}
	}
	return self;
}
 
#pragma mark DTASN1ParserDelegate
 
- (void)parser:(DTASN1Parser *)parser foundObjectIdentifier:(NSString *)objIdentifier
{
	currentObjectIdentifier = objIdentifier;
}
 
- (void)parser:(DTASN1Parser *)parser didStartContainerWithType:(DTASN1Type)type
{
	// start a new container
	id previousContainer = _currentContainer;
 
	// we don't care about the sequences, just the structure
	_currentContainer = [[NSMutableArray alloc] init];
	currentObjectIdentifier = nil;
 
	[previousContainer addObject:_currentContainer];
 
	if (!_rootContainer)
	{
		_rootContainer = _currentContainer;
	}
 
	[_stack addObject:_currentContainer];
}
 
- (void)parser:(DTASN1Parser *)parser didEndContainerWithType:(DTASN1Type)type
{
	// remove item from stack
	[_stack removeLastObject];
 
	_currentContainer = [_stack lastObject];
}
 
- (void)addObject:(id)object forIdentifier:(NSString *)identifier
{
	if (identifier)
	{
		NSDictionary *dict = [NSDictionary dictionaryWithObject:object forKey:identifier];
		NSLog(@"%@", dict);
		[_currentContainer addObject:dict];
	}
	else
	{
		[_currentContainer addObject:object];
		NSLog(@"%@", object);
	}
}
 
- (void)parserFoundNull:(DTASN1Parser *)parser
{
	[self addObject:[NSNull null] forIdentifier:currentObjectIdentifier];
}
 
- (void)parser:(DTASN1Parser *)parser foundDate:(NSDate *)date
{
	[self addObject:date forIdentifier:currentObjectIdentifier];
}
 
- (void)parser:(DTASN1Parser *)parser foundString:(NSString *)string
{
	[self addObject:string forIdentifier:currentObjectIdentifier];
}
 
- (void)parser:(DTASN1Parser *)parser foundData:(NSData *)data
{
	[self addObject:data forIdentifier:currentObjectIdentifier];
}
 
- (void)parser:(DTASN1Parser *)parser foundNumber:(NSNumber *)number
{
	[self addObject:number forIdentifier:currentObjectIdentifier];
}
 
@end

The general method of representing certificate elements in ASN.1 is to have an object identifier and a value for it. Those object identifiers are standardized and often they are represented concatenated with dots. You usually have a SET container, with the first element being an object identifier, followed by a string.

    "2.5.4.6" = "US";
    "2.5.4.10" = "Thawte, Inc.";
    "2.5.4.11" = "Domain Validated SSL";
    "2.5.4.3" = "Thawte DV SSL CA";

Here’s a list of the object identifiers you might encounter:

0.2.262.1.10.0,extension
0.2.262.1.10.1.1,signature
1.2.840.113549.1.1,pkcs-1
1.2.840.113549.1.1.1,rsaEncryption
1.2.840.113549.1.1.4,md5withRSAEncryption
1.2.840.113549.1.1.5,sha1withRSAEncryption
1.2.840.113549.1.1.6,rsaOAEPEncryptionSET
1.2.840.113549.1.7,pkcs-7
1.2.840.113549.1.7.1,data
1.2.840.113549.1.7.2,signedData
1.2.840.113549.1.7.3,envelopedData
1.2.840.113549.1.7.4,signedAndEnvelopedData
1.2.840.113549.1.7.5,digestedData
1.2.840.113549.1.7.6,encryptedData
1.2.840.113549.1.7.7,dataWithAttributes
1.2.840.113549.1.7.8,encryptedPrivateKeyInfo
1.2.840.113549.1.9.22.1,x509Certificate(for.PKCS.#12)
1.2.840.113549.1.9.23.1,x509Crl(for.PKCS.#12)
1.2.840.113549.1.9.3,contentType
1.2.840.113549.1.9.4,messageDigest
1.2.840.113549.1.9.5,signingTime
2.16.840.1.113730.1,cert-extension
2.16.840.1.113730.1.1,netscape-cert-type
2.16.840.1.113730.1.12,netscape-ssl-server-name
2.16.840.1.113730.1.13,netscape-comment
2.16.840.1.113730.1.2,netscape-base-url
2.16.840.1.113730.1.3,netscape-revocation-url
2.16.840.1.113730.1.4,netscape-ca-revocation-url
2.16.840.1.113730.1.7,netscape-cert-renewal-url
2.16.840.1.113730.1.8,netscape-ca-policy-url
2.23.42.0,contentType
2.23.42.1,msgExt
2.23.42.10,national
2.23.42.2,field
2.23.42.2.0,fullName
2.23.42.2.1,givenName
2.23.42.2.10,amount
2.23.42.2.2,familyName
2.23.42.2.3,birthFamilyName
2.23.42.2.4,placeName
2.23.42.2.5,identificationNumber
2.23.42.2.6,month
2.23.42.2.7,date
2.23.42.2.7.11,accountNumber
2.23.42.2.7.12,passPhrase
2.23.42.2.8,address
2.23.42.3,attribute
2.23.42.3.0,cert
2.23.42.3.0.0,rootKeyThumb
2.23.42.3.0.1,additionalPolicy
2.23.42.4,algorithm
2.23.42.5,policy
2.23.42.5.0,root
2.23.42.6,module
2.23.42.7,certExt
2.23.42.7.0,hashedRootKey
2.23.42.7.1,certificateType
2.23.42.7.2,merchantData
2.23.42.7.3,cardCertRequired
2.23.42.7.5,setExtensions
2.23.42.7.6,setQualifier
2.23.42.8,brand
2.23.42.9,vendor
2.23.42.9.22,eLab
2.23.42.9.31,espace-net
2.23.42.9.37,e-COMM
2.5.29.1,authorityKeyIdentifier
2.5.29.10,basicConstraints
2.5.29.11,nameConstraints
2.5.29.12,policyConstraints
2.5.29.13,basicConstraints
2.5.29.14,subjectKeyIdentifier
2.5.29.15,keyUsage
2.5.29.16,privateKeyUsagePeriod
2.5.29.17,subjectAltName
2.5.29.18,issuerAltName
2.5.29.19,basicConstraints
2.5.29.2,keyAttributes
2.5.29.20,cRLNumber
2.5.29.21,cRLReason
2.5.29.22,expirationDate
2.5.29.23,instructionCode
2.5.29.24,invalidityDate
2.5.29.25,cRLDistributionPoints
2.5.29.26,issuingDistributionPoint
2.5.29.27,deltaCRLIndicator
2.5.29.28,issuingDistributionPoint
2.5.29.29,certificateIssuer
2.5.29.3,certificatePolicies
2.5.29.30,nameConstraints
2.5.29.31,cRLDistributionPoints
2.5.29.32,certificatePolicies
2.5.29.33,policyMappings
2.5.29.34,policyConstraints
2.5.29.35,authorityKeyIdentifier
2.5.29.36,policyConstraints
2.5.29.37,extKeyUsage
2.5.29.4,keyUsageRestriction
2.5.29.5,policyMapping
2.5.29.6,subtreesConstraint
2.5.29.7,subjectAltName
2.5.29.8,issuerAltName
2.5.29.9,subjectDirectoryAttributes
2.5.4.0,objectClass
2.5.4.1,aliasedEntryName
2.5.4.10,organizationName
2.5.4.10.1,collectiveOrganizationName
2.5.4.11,organizationalUnitName
2.5.4.11.1,collectiveOrganizationalUnitName
2.5.4.12,title
2.5.4.13,description
2.5.4.14,searchGuide
2.5.4.15,businessCategory
2.5.4.16,postalAddress
2.5.4.16.1,collectivePostalAddress
2.5.4.17,postalCode
2.5.4.17.1,collectivePostalCode
2.5.4.18,postOfficeBox
2.5.4.18.1,collectivePostOfficeBox
2.5.4.19,physicalDeliveryOfficeName
2.5.4.19.1,collectivePhysicalDeliveryOfficeName
2.5.4.2,knowledgeInformation
2.5.4.20,telephoneNumber
2.5.4.20.1,collectiveTelephoneNumber
2.5.4.21,telexNumber
2.5.4.21.1,collectiveTelexNumber
2.5.4.22.1,collectiveTeletexTerminalIdentifier
2.5.4.23,facsimileTelephoneNumber
2.5.4.23.1,collectiveFacsimileTelephoneNumber
2.5.4.25,internationalISDNNumber
2.5.4.25.1,collectiveInternationalISDNNumber
2.5.4.26,registeredAddress
2.5.4.27,destinationIndicator
2.5.4.28,preferredDeliveryMehtod
2.5.4.29,presentationAddress
2.5.4.3,commonName
2.5.4.31,member
2.5.4.32,owner
2.5.4.33,roleOccupant
2.5.4.34,seeAlso
2.5.4.35,userPassword
2.5.4.36,userCertificate
2.5.4.37,caCertificate
2.5.4.38,authorityRevocationList
2.5.4.39,certificateRevocationList
2.5.4.4,surname
2.5.4.40,crossCertificatePair
2.5.4.41,name
2.5.4.42,givenName
2.5.4.43,initials
2.5.4.44,generationQualifier
2.5.4.45,uniqueIdentifier
2.5.4.46,dnQualifier
2.5.4.47,enhancedSearchGuide
2.5.4.48,protocolInformation
2.5.4.49,distinguishedName
2.5.4.5,serialNumber
2.5.4.50,uniqueMember
2.5.4.51,houseIdentifier
2.5.4.52,supportedAlgorithms
2.5.4.53,deltaRevocationList
2.5.4.55,clearance
2.5.4.58,crossCertificatePair
2.5.4.6,countryName
2.5.4.7,localityName
2.5.4.7.1,collectiveLocalityName
2.5.4.8,stateOrProvinceName
2.5.4.8.1,collectiveStateOrProvinceName
2.5.4.9,streetAddress
2.5.4.9.1,collectiveStreetAddress
2.5.6.0,top
2.5.6.1,alias
2.5.6.10,residentialPerson
2.5.6.11,applicationProcess
2.5.6.12,applicationEntity
2.5.6.13,dSA
2.5.6.14,device
2.5.6.15,strongAuthenticationUser
2.5.6.16,certificateAuthority
2.5.6.17,groupOfUniqueNames
2.5.6.2,country
2.5.6.21,pkiUser
2.5.6.22,pkiCA
2.5.6.3,locality
2.5.6.4,organization
2.5.6.5,organizationalUnit
2.5.6.6,person
2.5.6.7,organizationalPerson
2.5.6.8,organizationalRole
2.5.6.9,groupOfNames
2.5.8,X.500-Algorithms
2.5.8.1,X.500-Alg-Encryption
2.5.8.1.1,rsa
2.54.1775.2,hashedRootKey
2.54.1775.3,certificateType
2.54.1775.4,merchantData
2.54.1775.5,cardCertRequired
2.54.1775.7,setQualifier
2.54.1775.99,set-data

There’s a catch when dealing with bytes. I found that sometimes an element actually consists of sub-elements. For example the 1.2.840.113549.1.1.1 public key data also encodes an exponent field. I was unable to find documentation on how to know that just from the data. One of the JS decoders explodes this field, the other doesn’t. Worst case you’ll have to know which fields do that and parse these out yourself.

Another thing I did not understand is the use of the context fields. Those are represented as [0] and [3] in the JS tools. The must have some meaning, but I couldn’t find it.

Conclusion

With DTASN1Parser (new member of DTFoundation) you can now knock yourself out making nice dialogs giving all the certificate information to your user he needs to be able to know that this certificate is ok to permanently accept. The big advantage of this parser over OpenSSL is that you only have to add two files to your project (or DTFoundation) and don’t need to mess around with getting OpenSSL compiled for iOS.

If you do set out to create such an Accept Certificate dialog, then get in touch, we should collaborate on that.

DTASN1Parser is quite crude, there are many special cases that are not yet implemented, because I didn’t encounter them in working with a DER-encoded certificate. If you have DER-data that makes use of these, then please let me have that for testing … or better yet, implement it in the Open Source project and send me a pull request.


Categories: Projects

2 Comments »

Trackbacks

  1. RFC: DTCertificateViewer | Cocoanetics
  2. Apple’s ASN.1 OID Names | Cocoanetics

Leave a Comment