Skip to content

Commit

Permalink
[domainca][pledge] Fix the Thread Domain Name encoding in the LDevID …
Browse files Browse the repository at this point in the history
…according to latest specs.
  • Loading branch information
EskoDijk committed Oct 6, 2024
1 parent 057935c commit 169569f
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 41 deletions.
16 changes: 4 additions & 12 deletions src/main/java/com/google/openthread/domainca/DomainCA.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 =
Expand Down
44 changes: 20 additions & 24 deletions src/main/java/com/google/openthread/pledge/Pledge.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -165,43 +168,36 @@ 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) {
return null;
}

try {
Collection<List<?>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
2 changes: 1 addition & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date %-5level %-38(%logger{36}) -- %-96(%msg) [%thread]%n</pattern>
<pattern>%date %-5level %-38(%logger{36}) -- %-82(%msg) [%thread]%n</pattern>
</encoder>
</appender>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down

0 comments on commit 169569f

Please sign in to comment.