I'm having issues parsing a DER attribute certificate using the BouncyCastle Java API.
Errors
Here is the code I'm attempting to use. I first read the cert file as an InputStream named stream
, and try to convert it to a Bouncycastle AttributeCertificate
object:
ASN1InputStream derIn = new ASN1InputStream(stream);
ASN1Sequence seq = (ASN1Sequence) derIn.readObject();
AttributeCertificate cert = AttributeCertificate.getInstance(seq); <-- exception here
ac = new X509AttributeCertificateHolder(cert);
The third line causes an IllegalArgumentException
:
IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
at org.bouncycastle.asn1.x509.IssuerSerial.getInstance(Unknown Source)
at org.bouncycastle.asn1.x509.Holder.getInstance(Unknown Source)
at org.bouncycastle.asn1.x509.AttributeCertificateInfo.getInstance(Unknown Source)
...
Bouncycastle also throws an IOException
saying an "unknown tag" has been encountered:
java.io.IOException: unknown tag 28 encountered
at org.bouncycastle.asn1.ASN1InputStream.buildObject(Unknown Source)
at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source)
...
Analysis
I've linked the certificate file I'm attempting to parse here. The cert itself was generated using OpenSSL.
The cert also includes the ABAC credential I'm using, which is a simple string:
[some_uuid].experiment_create <- [some_uuid].partner.experiment_create
I looked into the "unknown tag 28" and it seems ASN.1 tag 28 denotes a UniversalString. I found online that "the UniversalString type models a Unicode character string implicitly serialized into UTF-32 big endian."
This to me suggested a possible character encoding issue in the cert. But when I looked at the cert file using openssl asn1parse
, it seemed fine:
0:d=0 hl=4 l= 660 cons: SEQUENCE
4:d=1 hl=4 l= 509 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= 1 prim: INTEGER :00
16:d=2 hl=2 l= 13 cons: SEQUENCE
18:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
29:d=3 hl=2 l= 0 prim: NULL
31:d=2 hl=2 l= 51 cons: SEQUENCE
33:d=3 hl=2 l= 49 cons: SET
35:d=4 hl=2 l= 47 cons: SEQUENCE
37:d=5 hl=2 l= 3 prim: OBJECT :commonName
42:d=5 hl=2 l= 40 prim: UTF8STRING :bf3a72c271f661dae81647a16c1babf3a52da28e
84:d=2 hl=2 l= 30 cons: SEQUENCE
86:d=3 hl=2 l= 13 prim: UTCTIME :170828213103Z
101:d=3 hl=2 l= 13 prim: UTCTIME :270826213103Z
116:d=2 hl=2 l= 51 cons: SEQUENCE
118:d=3 hl=2 l= 49 cons: SET
120:d=4 hl=2 l= 47 cons: SEQUENCE
122:d=5 hl=2 l= 3 prim: OBJECT :commonName
127:d=5 hl=2 l= 40 prim: UTF8STRING :bf3a72c271f661dae81647a16c1babf3a52da28e
169:d=2 hl=3 l= 159 cons: SEQUENCE
172:d=3 hl=2 l= 13 cons: SEQUENCE
174:d=4 hl=2 l= 9 prim: OBJECT :rsaEncryption
185:d=4 hl=2 l= 0 prim: NULL
187:d=3 hl=3 l= 141 prim: BIT STRING
331:d=2 hl=3 l= 183 cons: cont [ 3 ]
334:d=3 hl=3 l= 180 cons: SEQUENCE
337:d=4 hl=3 l= 144 cons: SEQUENCE
340:d=5 hl=2 l= 8 prim: OBJECT :id-aca-group
350:d=5 hl=3 l= 131 prim: OCTET STRING [HEX DUMP]:0C8180626633613732633237316636363164616538313634376131366331626162663361353264613238652E6578706572696D656E745F637265617465203C2D20626633613732633237316636363164616538313634376131366331626162663361353264613238652E706172746E65722E6578706572696D656E745F637265617465
484:d=4 hl=2 l= 31 cons: SEQUENCE
486:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Authority Key Identifier
491:d=5 hl=2 l= 24 prim: OCTET STRING [HEX DUMP]:30168014162E4EF6CD52F37CD2EDDFBEA484E70D6CDE9048
517:d=1 hl=2 l= 13 cons: SEQUENCE
519:d=2 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
530:d=2 hl=2 l= 0 prim: NULL
532:d=1 hl=3 l= 129 prim: BIT STRING
Other programs also seemed to work fine; openssl x509
provides this output:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 0 (0x0)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=bf3a72c271f661dae81647a16c1babf3a52da28e
Validity
Not Before: Aug 28 21:31:03 2017 GMT
Not After : Aug 26 21:31:03 2027 GMT
Subject: CN=bf3a72c271f661dae81647a16c1babf3a52da28e
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:c5:d4:35:49:4d:bd:ee:9a:93:51:a4:e1:46:df:
46:c0:1e:f6:b1:85:70:4d:31:2b:20:6f:ab:82:16:
b4:9d:3e:ea:f3:38:53:e2:7b:be:36:37:f3:11:7f:
90:5d:aa:ad:e7:e8:61:3c:46:8d:7a:69:4d:c6:89:
e2:f7:07:d9:3f:b0:5d:f7:ee:40:e5:86:48:7b:4c:
0e:0f:11:0c:96:41:e6:99:02:17:df:4e:60:3d:d0:
42:b5:dc:22:e0:64:6d:ad:17:22:b7:a2:15:ec:dd:
89:c2:b4:58:01:64:d7:db:fe:62:1e:c5:40:0c:e0:
b9:12:7e:fe:4c:31:65:e6:51
Exponent: 65537 (0x10001)
X509v3 extensions:
id-aca-group:
...bf3a72c271f661dae81647a16c1babf3a52da28e.experiment_create <- bf3a72c271f661dae81647a16c1babf3a52da28e.partner.experiment_create
X509v3 Authority Key Identifier:
keyid:16:2E:4E:F6:CD:52:F3:7C:D2:ED:DF:BE:A4:84:E7:0D:6C:DE:90:48
Signature Algorithm: sha256WithRSAEncryption
1e:0f:2a:7a:cf:95:77:0f:5c:48:f3:12:c4:b9:8a:5a:d9:b9:
62:1c:60:0c:a0:13:70:f3:c5:aa:de:6d:6f:92:7f:0d:a2:3b:
c9:bd:cc:45:6c:4b:21:8d:32:81:8b:af:13:6e:a3:96:18:05:
3b:83:fb:8c:3b:2a:d8:87:22:56:9e:4b:1d:06:e6:7f:ba:36:
89:e8:c6:8a:5a:9e:2c:9b:44:5e:19:fe:68:13:12:93:48:df:
f9:34:42:01:d5:62:c1:ca:e4:e2:3b:86:b7:4c:75:ba:60:5b:
c9:f7:68:9a:b0:b5:1c:33:01:5e:77:c0:7c:13:11:e1:09:67:
42:dd
Based off of these I can't find anything clearly wrong with the certificate. The only clue I could find is in this block of Bouncycastle source code for ASN1InputStream.java which throws the exception I found:
// Build an object given its tag and the number of bytes to construct it from.
protected ASN1Primitive buildObject(
int tag,
int tagNo,
int length)
throws IOException
{
...
if (isConstructed)
{
// TODO There are other tags that may be constructed (e.g. BIT_STRING)
switch (tagNo)
{
case OCTET_STRING:
//
// yes, people actually do this...
//
ASN1EncodableVector v = buildDEREncodableVector(defIn);
ASN1OctetString[] strings = new ASN1OctetString[v.size()];
for (int i = 0; i != strings.length; i++)
{
strings[i] = (ASN1OctetString)v.get(i);
}
return new BEROctetString(strings);
case SEQUENCE:
if (lazyEvaluate)
{
return new LazyEncodedSequence(defIn.toByteArray());
}
else
{
return DERFactory.createSequence(buildDEREncodableVector(defIn));
}
case SET:
return DERFactory.createSet(buildDEREncodableVector(defIn));
case EXTERNAL:
return new DERExternal(buildDEREncodableVector(defIn));
default:
throw new IOException("unknown tag " + tagNo + " encountered");
}
}
return createPrimitiveDERObject(tagNo, defIn, tmpBuffers);
}
It seems support for these "other tags that may be constructed," including tag 28, hasn't been coded in yet, but I'm not sure where my certificate is broken or how to fix the tag.
Could anyone point me in the right direction? Thanks.
Your certificate is not a valid attribute certificate, it is a (nearly-valid) public-key certificate, although it contains an extension OID intended for use with attribute certs (defined in rfc3281/5755) with a content structure that doesn't satisfy the standard definition.
openssl x509
handles only public-key certificates; OpenSSL doesn't support attribute certs at all (unless you use the low-level asn1 routines and implement everything yourself). BC supports both, but in practice almost nobody uses attribute certs.The outer wrapper of both public-key and attribute certs is the old SIGNED macro, so that level parses successfully. The first field is actually wrong -- AttributeCertificateInfo should begin with
version INTEGER
containing 1 (not 2) and your actual TBSCertificate begins with effectivelyversion [context0] EXPLICIT INTEGER
containing 2 -- but BC doesn't catch this because according to the source in v1 version was optional and defaulted. It then tries to parseHolder
which should be aSEQUENCE
of several tagged OPTIONAL items from what is actually the tagged-INTEGER version; I'm not sure how it manages to get as far as trying to parse Holder.IssuerSerial before detecting this error. And I have no clue where or how it's finding a tag 28.