diff --git a/src/main/java/com/google/openthread/domainca/DomainCA.java b/src/main/java/com/google/openthread/domainca/DomainCA.java index 57a32b3..2c5a167 100644 --- a/src/main/java/com/google/openthread/domainca/DomainCA.java +++ b/src/main/java/com/google/openthread/domainca/DomainCA.java @@ -99,7 +99,7 @@ public X509Certificate getCertificate() { * Get the Thread Domain Name currently used by this Domain CA. Note that a Domain CA may use any number of Thread Domains within its own Enterprise Domain, with arbitrary string identifiers. In the * present implementation only one Thread Domain is used. * - * @return the currently used Thread Domain Name for signing LDevID certificates. + * @return the currently used Thread Domain Name used when creating new LDevID certificates. */ public String getDomainName() { return domainName; @@ -148,17 +148,9 @@ public X509Certificate signCertificate(PKCS10CertificationRequest csr) throws Ex SubjectPublicKeyInfo.getInstance(getPublicKey().getEncoded())); builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyId); - // Includes Thread Domain name in SubjectAltName extension field, otherName subfield, - // otherName type-id 1.3.6.1.4.1.44970.1 with value IA5String. This is tweaked - // to look the same as OpenSSL commandline output. - DERSequence otherName = - new DERSequence( - new ASN1Encodable[]{ - THREAD_DOMAIN_NAME_OID_ASN1, new DERTaggedObject(0, new DERIA5String(domainName)) - }); - GeneralNames subjectAltNames = - new GeneralNames(new GeneralName(GeneralName.otherName, otherName)); - builder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); + // Includes Thread Domain name in X.509v3 extensions section, with value IA5String. + DERIA5String domainNameStr = new DERIA5String(domainName); + builder.addExtension(THREAD_DOMAIN_NAME_OID_ASN1, false, domainNameStr); // 2. Sign and verify certificate ContentSigner signer = diff --git a/src/main/java/com/google/openthread/pledge/Pledge.java b/src/main/java/com/google/openthread/pledge/Pledge.java index adb7949..8582f2b 100644 --- a/src/main/java/com/google/openthread/pledge/Pledge.java +++ b/src/main/java/com/google/openthread/pledge/Pledge.java @@ -68,10 +68,13 @@ import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; +import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.x500.X500Name; @@ -165,8 +168,8 @@ public X509Certificate getOperationalCert() { /** * Get the Thread Domain Name as encoded in the operational certificate of the Pledge. * - * @return the Thread Domain Name, encoded in the SubjectAltName/Othername field as per Thread spec. Or "DefaultDomain" if not encoded in there. Null if no operational cert present or if it couldn't - * be parsed. + * @return the Thread Domain Name, encoded per Thread spec in an X509v3 extension. Or, "DefaultDomain" if name is not encoded in cert. + * Null if no operational cert is present or if the extension couldn't be parsed. */ public String getDomainName() { if (operationalCertificate == null) { @@ -174,34 +177,27 @@ public String getDomainName() { } try { - Collection> cSubjAltNames = operationalCertificate.getSubjectAlternativeNames(); - if (cSubjAltNames == null) { + byte[] derThreadDomainNameExt = operationalCertificate.getExtensionValue(ConstantsThread.THREAD_DOMAIN_NAME_OID); + if (derThreadDomainNameExt == null) { + // if cert correct but not encoded in there, infer it's the domain name - as defined by Thread spec. return ConstantsThread.THREAD_DOMAIN_NAME_DEFAULT; } - // loop all subject-alt-names to find a matching 'otherName' item. - for (List l : cSubjAltNames) { - if (l.size() == 2 && l.get(0).equals(ConstantsBrski.ASN1_TAG_GENERALNAME_OTHERNAME)) { - ASN1Sequence ds = DERSequence.getInstance(DLSequence.fromByteArray((byte[]) l.get(1))); - // check that the otherName item has the Thread domain OID - if (ds.size() == 2 && ds.getObjectAt(0).equals(THREAD_DOMAIN_NAME_OID_ASN1)) { - ASN1TaggedObject ato = ASN1TaggedObject.getInstance(ds.getObjectAt(1)); - if (ato.getTagNo() == 0) { - // get to the deepest embedded tagged object. - while (ato.getObject() instanceof ASN1TaggedObject) { - ato = (ASN1TaggedObject) ato.getObject(); - } - // must be stored as IA5String - return DERIA5String.getInstance(ato.getObject()).toString(); - } - } + // MUST be stored as IA5String wrapped inside an OctetString + ASN1InputStream asn1Input = new ASN1InputStream(new ByteArrayInputStream(derThreadDomainNameExt)); + Object obj = asn1Input.readObject(); + if (obj instanceof DEROctetString) { + byte[] derIa5String = ((DEROctetString) obj).getOctets(); + asn1Input = new ASN1InputStream(new ByteArrayInputStream(derIa5String)); + obj = asn1Input.readObject(); + if (obj instanceof DERIA5String) { + return ((DERIA5String)obj).toString(); } } } catch (Exception ex) { - logger.error("getDomainName(): couldn't parse operational certificate", ex); - return null; + logger.error("getDomainName(): couldn't parse Thread Domain Name extension in LDevID", ex); } - // if cert correct but not encoded in there, use default name. - return ConstantsThread.THREAD_DOMAIN_NAME_DEFAULT; + + return null; } // BRSKI protocol diff --git a/src/main/java/com/google/openthread/thread/ConstantsThread.java b/src/main/java/com/google/openthread/thread/ConstantsThread.java index 7daceca..b02fbba 100644 --- a/src/main/java/com/google/openthread/thread/ConstantsThread.java +++ b/src/main/java/com/google/openthread/thread/ConstantsThread.java @@ -34,9 +34,9 @@ public class ConstantsThread { // --- OID items - public static final String THREAD_DOMAIN_NAME_OID = "1.3.6.1.4.1.44970.1"; // per Thread 1.2 spec + public static final String THREAD_DOMAIN_NAME_OID = "1.3.6.1.4.1.44970.1"; // per Thread 1.4 spec // -- Other items - // Default Thread Domain Name per Thread 1.2 spec. Must not be changed, unless spec changes. + // Default Thread Domain Name per Thread 1.4 spec. Must not be changed, unless spec changes. public static final String THREAD_DOMAIN_NAME_DEFAULT = "DefaultDomain"; } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ed80b96..c37e00a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - %date %-5level %-38(%logger{36}) -- %-96(%msg) [%thread]%n + %date %-5level %-38(%logger{36}) -- %-82(%msg) [%thread]%n diff --git a/src/test/java/com/google/openthread/registrar/IETFConstrainedBrskiTest.java b/src/test/java/com/google/openthread/registrar/IETFConstrainedBrskiTest.java index 496c45a..3612d57 100644 --- a/src/test/java/com/google/openthread/registrar/IETFConstrainedBrskiTest.java +++ b/src/test/java/com/google/openthread/registrar/IETFConstrainedBrskiTest.java @@ -47,7 +47,7 @@ public class IETFConstrainedBrskiTest { public static final String REGISTRAR_URI = "coaps://[::1]:" + ConstantsBrski.DEFAULT_REGISTRAR_COAPS_PORT; - public static final String DEFAULT_DOMAIN_NAME = "Thread-Test"; + public static final String THREAD_DOMAIN_NAME = "Thread-Test"; public static final String CREDENTIALS_KEYSTORE_FILE = "credentials/ietf-draft-constrained-brski/credentials.p12"; // the acting entities @@ -90,7 +90,7 @@ protected void initEntities(CredentialGenerator credGen) throws Exception { pledge = new Pledge(credGen.getCredentials(CredentialGenerator.PLEDGE_ALIAS), REGISTRAR_URI); pledge.setLightweightClientCertificates(true); - domainCA = new DomainCA(DEFAULT_DOMAIN_NAME, credGen.getCredentials(CredentialGenerator.DOMAINCA_ALIAS)); + domainCA = new DomainCA(THREAD_DOMAIN_NAME, credGen.getCredentials(CredentialGenerator.DOMAINCA_ALIAS)); RegistrarBuilder registrarBuilder = new RegistrarBuilder(); registrar =