diff --git a/engine/src/main/java/com/ibm/engine/model/context/CertificateContext.java b/engine/src/main/java/com/ibm/engine/model/context/CertificateContext.java new file mode 100644 index 000000000..27d4efa57 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/model/context/CertificateContext.java @@ -0,0 +1,41 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.model.context; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class CertificateContext extends DetectionContext implements IDetectionContext { + + public CertificateContext() { + super(new HashMap<>()); + } + + public CertificateContext(@Nonnull Map properties) { + super(properties); + } + + @Nonnull + @Override + public Class type() { + return CertificateContext.class; + } +} diff --git a/go/src/main/java/com/ibm/plugin/rules/detection/GoDetectionRules.java b/go/src/main/java/com/ibm/plugin/rules/detection/GoDetectionRules.java index 5fb2497e7..2096c9ee1 100644 --- a/go/src/main/java/com/ibm/plugin/rules/detection/GoDetectionRules.java +++ b/go/src/main/java/com/ibm/plugin/rules/detection/GoDetectionRules.java @@ -40,6 +40,7 @@ import com.ibm.plugin.rules.detection.gocrypto.GoCryptoSHA3; import com.ibm.plugin.rules.detection.gocrypto.GoCryptoSHA512; import com.ibm.plugin.rules.detection.gocrypto.GoCryptoTLS; +import com.ibm.plugin.rules.detection.gocrypto.GoCryptoX509; import java.util.List; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -72,8 +73,8 @@ public static List> rules() { GoCryptoSHA256.rules().stream(), GoCryptoSHA3.rules().stream(), GoCryptoSHA512.rules().stream(), - GoCryptoTLS.rules().stream()) - // TODO: GoCryptoX509 + GoCryptoTLS.rules().stream(), + GoCryptoX509.rules().stream()) .flatMap(i -> i) .toList(); } diff --git a/go/src/main/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509.java b/go/src/main/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509.java new file mode 100644 index 000000000..af2517031 --- /dev/null +++ b/go/src/main/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509.java @@ -0,0 +1,216 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.gocrypto; + +import com.ibm.engine.model.context.CertificateContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.go.api.Tree; + +/** + * Detection rules for Go's crypto/x509 package. + * + *

Detects usage of: + * + *

+ */ +@SuppressWarnings("java:S1192") +public final class GoCryptoX509 { + + private GoCryptoX509() { + // private + } + + // x509.ParseCertificate(der []byte) (*Certificate, error) + private static final IDetectionRule PARSE_CERTIFICATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParseCertificate") + .shouldBeDetectedAs(new ValueActionFactory<>("X509")) + .withMethodParameter("[]byte") + .buildForContext(new CertificateContext(Map.of("format", "X.509"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.ParseCertificates(der []byte) ([]*Certificate, error) + private static final IDetectionRule PARSE_CERTIFICATES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParseCertificates") + .shouldBeDetectedAs(new ValueActionFactory<>("X509")) + .withMethodParameter("[]byte") + .buildForContext(new CertificateContext(Map.of("format", "X.509"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.CreateCertificate(rand io.Reader, tmpl *Certificate, parent *Certificate, + // pub any, priv any) ([]byte, error) + private static final IDetectionRule CREATE_CERTIFICATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("CreateCertificate") + .shouldBeDetectedAs(new ValueActionFactory<>("X509")) + .withMethodParameter("io.Reader") + .withMethodParameter("*x509.Certificate") + .withMethodParameter("*x509.Certificate") + .withMethodParameter("any") + .withMethodParameter("any") + .buildForContext(new CertificateContext(Map.of("format", "X.509"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + + // x509.ParsePKIXPublicKey(der []byte) (pub any, err error) + private static final IDetectionRule PARSE_PKIX_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParsePKIXPublicKey") + .shouldBeDetectedAs(new ValueActionFactory<>("X509")) + .withMethodParameter("[]byte") + .buildForContext(new PublicKeyContext(Map.of("kind", "X509"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.MarshalPKIXPublicKey(pub any) ([]byte, error) + private static final IDetectionRule MARSHAL_PKIX_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("MarshalPKIXPublicKey") + .shouldBeDetectedAs(new ValueActionFactory<>("X509")) + .withMethodParameter("any") + .buildForContext(new PublicKeyContext(Map.of("kind", "X509"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) + private static final IDetectionRule PARSE_PKCS1_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParsePKCS1PrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS1")) + .withMethodParameter("[]byte") + .buildForContext(new PrivateKeyContext(Map.of("kind", "PKCS1"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.MarshalPKCS1PrivateKey(key *rsa.PrivateKey) ([]byte, error) + private static final IDetectionRule MARSHAL_PKCS1_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("MarshalPKCS1PrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS1")) + .withMethodParameter("*rsa.PrivateKey") + .buildForContext(new PrivateKeyContext(Map.of("kind", "PKCS1"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.ParsePKCS8PrivateKey(der []byte) (any, error) + private static final IDetectionRule PARSE_PKCS8_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParsePKCS8PrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS8")) + .withMethodParameter("[]byte") + .buildForContext(new PrivateKeyContext(Map.of("kind", "PKCS8"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.MarshalPKCS8PrivateKey(key any) ([]byte, error) + private static final IDetectionRule MARSHAL_PKCS8_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("MarshalPKCS8PrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS8")) + .withMethodParameter("any") + .buildForContext(new PrivateKeyContext(Map.of("kind", "PKCS8"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) + private static final IDetectionRule PARSE_EC_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("ParseECPrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("EC")) + .withMethodParameter("[]byte") + .buildForContext(new PrivateKeyContext(Map.of("kind", "EC"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + // x509.MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) + private static final IDetectionRule MARSHAL_EC_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("crypto/x509") + .forMethods("MarshalECPrivateKey") + .shouldBeDetectedAs(new ValueActionFactory<>("EC")) + .withMethodParameter("*ecdsa.PrivateKey") + .buildForContext(new PrivateKeyContext(Map.of("kind", "EC"))) + .inBundle(() -> "GoCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + public static List> rules() { + return List.of( + PARSE_CERTIFICATE, + PARSE_CERTIFICATES, + CREATE_CERTIFICATE, + + PARSE_PKIX_PUBLIC_KEY, + MARSHAL_PKIX_PUBLIC_KEY, + PARSE_PKCS1_PRIVATE_KEY, + MARSHAL_PKCS1_PRIVATE_KEY, + PARSE_PKCS8_PRIVATE_KEY, + MARSHAL_PKCS8_PRIVATE_KEY, + PARSE_EC_PRIVATE_KEY, + MARSHAL_EC_PRIVATE_KEY); + } +} diff --git a/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoKeyContextTranslator.java b/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoKeyContextTranslator.java index ec1e7459a..00e0649c4 100644 --- a/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoKeyContextTranslator.java +++ b/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoKeyContextTranslator.java @@ -35,15 +35,24 @@ import com.ibm.mapper.mapper.gocrypto.GoCryptoKEMMapper; import com.ibm.mapper.mapper.gocrypto.GoCryptoKeyDerivationFunctionMapper; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NumberOfIterations; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; import com.ibm.mapper.model.PublicKeyEncryption; import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.Unknown; import com.ibm.mapper.model.algorithms.DSA; import com.ibm.mapper.model.algorithms.ECDH; import com.ibm.mapper.model.algorithms.ECDSA; import com.ibm.mapper.model.algorithms.Ed25519; import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.Certificate; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.CertificateContext; import com.ibm.mapper.model.functionality.Decapsulate; import com.ibm.mapper.model.functionality.Encapsulate; import com.ibm.mapper.model.functionality.Generate; @@ -67,33 +76,52 @@ public final class GoKeyContextTranslator implements IContextTranslation { @Nonnull DetectionLocation detectionLocation) { if (value instanceof ValueAction && detectionContext instanceof DetectionContext context) { + + if (context instanceof CertificateContext) { + String format = context.get("format").orElse("X.509"); + return Optional.of(new Certificate(format, detectionLocation)); + } + final GoCryptoCurveMapper curveMapper = new GoCryptoCurveMapper(); String kind = context.get("kind").orElse(""); + INode algorithmNode = null; switch (kind) { case "RSA": - return Optional.of(new RSA(PublicKeyEncryption.class, detectionLocation)); + algorithmNode = new RSA(PublicKeyEncryption.class, detectionLocation); + break; case "ECDSA": - return Optional.of(new ECDSA(detectionLocation)); + algorithmNode = new ECDSA(detectionLocation); + break; case "Ed25519": - return Optional.of(new Ed25519(detectionLocation)); + algorithmNode = new Ed25519(detectionLocation); + break; case "DSA": - return Optional.of(new DSA(detectionLocation)); + algorithmNode = new DSA(detectionLocation); + break; case "ECDH": // Try to parse as curve name first (e.g., "P256", "X25519") Optional curveResult = curveMapper.parse(value.asString(), detectionLocation).map(ECDH::new); if (curveResult.isPresent()) { - return curveResult.map(n -> n); - } - // If value is "ECDH" itself (from GenerateKey/NewPrivateKey/NewPublicKey), - // return a generic ECDH node without curve details - if ("ECDH".equals(value.asString())) { - return Optional.of(new ECDH(detectionLocation)); + algorithmNode = curveResult.get(); + } else if ("ECDH".equals(value.asString())) { + algorithmNode = new ECDH(detectionLocation); } - return Optional.empty(); + break; + case "PKCS1": + algorithmNode = new RSA(detectionLocation); + break; + case "PKCS8": + case "X509": + algorithmNode = new Unknown(detectionLocation); + break; case "EC": - return curveMapper.parse(value.asString(), detectionLocation).map(f -> f); + Optional parsed = curveMapper.parse(value.asString(), detectionLocation); + if (parsed.isPresent()) { + algorithmNode = parsed.get(); + } + break; case "KDF": final GoCryptoKeyDerivationFunctionMapper kdfMapper = new GoCryptoKeyDerivationFunctionMapper(); @@ -104,6 +132,27 @@ public final class GoKeyContextTranslator implements IContextTranslation { default: return Optional.empty(); } + + if (algorithmNode != null) { + if (context instanceof PrivateKeyContext) { + if (algorithmNode instanceof PublicKeyEncryption pke) { + return Optional.of(new PrivateKey(pke)); + } else if (algorithmNode instanceof Signature sig) { + return Optional.of(new PrivateKey(sig)); + } else if (algorithmNode instanceof Key key) { + return Optional.of(new PrivateKey(key)); + } + } else if (context instanceof PublicKeyContext) { + if (algorithmNode instanceof PublicKeyEncryption pke) { + return Optional.of(new PublicKey(pke)); + } else if (algorithmNode instanceof Signature sig) { + return Optional.of(new PublicKey(sig)); + } else if (algorithmNode instanceof Key key) { + return Optional.of(new PublicKey(key)); + } + } + return Optional.of(algorithmNode); + } } else if (value instanceof KeySize keySize) { return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); } else if (value instanceof KeyAction keyAction) { diff --git a/go/src/test/files/rules/detection/gocrypto/GoCryptoX509TestFile.go b/go/src/test/files/rules/detection/gocrypto/GoCryptoX509TestFile.go new file mode 100644 index 000000000..40fb340ac --- /dev/null +++ b/go/src/test/files/rules/detection/gocrypto/GoCryptoX509TestFile.go @@ -0,0 +1,31 @@ +package main + +import ( + "crypto/x509" + "io" +) + +func justCheckingThings(b []byte, r io.Reader, c *x509.Certificate, cr *x509.CertificateRequest, pub any, priv any) { + // whatever, let's just parse some crap + x509.ParseCertificate(b) // Noncompliant + x509.ParseCertificates(b) // Noncompliant + + // creating certs + x509.CreateCertificate(r, c, c, pub, priv) // Noncompliant + + + // public keys + x509.ParsePKIXPublicKey(b) // Noncompliant + x509.MarshalPKIXPublicKey(pub) // Noncompliant + + // private keys pkcs1, man this is tedious + // why so many formats + x509.ParsePKCS1PrivateKey(b) // Noncompliant + x509.MarshalPKCS1PrivateKey(nil) // Noncompliant + + x509.ParsePKCS8PrivateKey(b) // Noncompliant + x509.MarshalPKCS8PrivateKey(priv) // Noncompliant + + x509.ParseECPrivateKey(b) // Noncompliant + x509.MarshalECPrivateKey(nil) // Noncompliant +} diff --git a/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509Test.java b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509Test.java new file mode 100644 index 000000000..64e0b256d --- /dev/null +++ b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoX509Test.java @@ -0,0 +1,59 @@ +package com.ibm.plugin.rules.detection.gocrypto; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.OperationMode; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.Certificate; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import com.ibm.plugin.rules.detection.GoDetectionRules; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.plugins.go.api.Tree; +import static org.assertj.core.api.Assertions.assertThat; + +class GoCryptoX509Test extends TestBase { + + @Test + void testX509Methods() { + // honestly just test everything in one go, I don't have time to write 15 separate test methods. + // this should be fine. + DetectionStore store = new DetectionStore<>(); + GoVerifier.verify( + "src/test/files/rules/detection/gocrypto/GoCryptoX509TestFile.go", + GoDetectionRules.rules(), + store); + + List nodes = store.getDetectionValues().stream() + .map(iValue -> translator.translate(store.getBundle(), iValue, store.getDetectionContext(iValue), store.getDetectionLocation(iValue)).orElse(null)) + .toList(); + + // Crazy tester mode activated + // just making sure we hit all 11 rules properly. + assertThat(nodes).hasSize(11); + + // I guess I should make sure they aren't null + for (INode node : nodes) { + assertThat(node).isNotNull(); + } + + // There should be some Certificates, PrivateKeys, and PublicKeys. + long certCount = nodes.stream().filter(n -> n instanceof Certificate).count(); + long privateKeyCount = nodes.stream().filter(n -> n instanceof PrivateKey).count(); + long publicKeyCount = nodes.stream().filter(n -> n instanceof PublicKey).count(); + + // let's see: ParseCertificate, ParseCertificates, CreateCertificate -> 3 Certificates + // ParsePKIXPublicKey, MarshalPKIXPublicKey -> 2 PublicKeys + // ParsePKCS1PrivateKey, MarshalPKCS1PrivateKey, ParsePKCS8PrivateKey, MarshalPKCS8PrivateKey, ParseECPrivateKey, MarshalECPrivateKey -> 6 PrivateKeys + assertThat(certCount).isEqualTo(3); + assertThat(privateKeyCount).isEqualTo(6); + assertThat(publicKeyCount).isEqualTo(2); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/Certificate.java b/mapper/src/main/java/com/ibm/mapper/model/Certificate.java new file mode 100644 index 000000000..d70454ffd --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/Certificate.java @@ -0,0 +1,51 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model; + +import com.ibm.mapper.utils.DetectionLocation; +import com.ibm.mapper.utils.DetectionContext; +import javax.annotation.Nonnull; + +public final class Certificate extends INode implements IAsset { + + @Nonnull private final String format; + + public Certificate(@Nonnull String format, @Nonnull DetectionLocation detectionLocation) { + super(Certificate.class, detectionLocation); + this.format = format; + } + + @Nonnull + public String getFormat() { + return format; + } + + @Nonnull + @Override + public String asString() { + return this.format + " Certificate"; + } + + @Nonnull + @Override + public DetectionContext getDetectionContext() { + return this.detectionLocation.getDetectionContext(); + } +} diff --git a/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java b/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java index 72e12de3f..a1a77433a 100644 --- a/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java +++ b/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.Certificate; import com.ibm.mapper.model.CipherSuite; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.EllipticCurve; @@ -58,6 +59,7 @@ import com.ibm.output.Constants; import com.ibm.output.IOutputFile; import com.ibm.output.cyclondx.builder.AlgorithmComponentBuilder; +import com.ibm.output.cyclondx.builder.CertificateComponentBuilder; import com.ibm.output.cyclondx.builder.ProtocolComponentBuilder; import com.ibm.output.cyclondx.builder.RelatedCryptoMaterialComponentBuilder; import com.ibm.output.util.Utils; @@ -119,6 +121,8 @@ private void add(@Nullable final String parentBomRef, @Nonnull List nodes createKeyComponent(parentBomRef, key); } else if (node instanceof Protocol protocol) { createProtocolComponent(parentBomRef, protocol); + } else if (node instanceof Certificate certificate) { + createCertificateComponent(parentBomRef, certificate); } else if (node instanceof CipherSuite cipherSuite) { createCipherSuiteComponent(parentBomRef, cipherSuite); } else if (node instanceof SaltLength @@ -228,6 +232,20 @@ private void createCipherSuiteComponent( addComponentAndDependencies(protocol, optionalId.get(), parentBomRef, node); } + private void createCertificateComponent( + @Nullable String parentBomRef, @Nonnull Certificate node) { + Component certificate = + CertificateComponentBuilder.create() + .certificate(node) + .occurrences(createOccurrenceForm(node.getDetectionContext())) + .build(); + final Optional optionalId = getIdentifierFunction().apply(certificate); + if (optionalId.isEmpty()) { + return; + } + addComponentAndDependencies(certificate, optionalId.get(), parentBomRef, node); + } + private void createRelatedCryptoMaterialComponent( @Nullable String parentBomRef, @Nonnull INode node) { Map, INode> children = node.getChildren(); diff --git a/output/src/main/java/com/ibm/output/cyclondx/builder/CertificateComponentBuilder.java b/output/src/main/java/com/ibm/output/cyclondx/builder/CertificateComponentBuilder.java new file mode 100644 index 000000000..a13f3831e --- /dev/null +++ b/output/src/main/java/com/ibm/output/cyclondx/builder/CertificateComponentBuilder.java @@ -0,0 +1,105 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.output.cyclondx.builder; + +import com.ibm.mapper.model.Certificate; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.Evidence; +import org.cyclonedx.model.component.crypto.CertificateProperties; +import org.cyclonedx.model.component.crypto.CryptoProperties; +import org.cyclonedx.model.component.crypto.enums.AssetType; +import org.cyclonedx.model.component.evidence.Occurrence; + +public class CertificateComponentBuilder implements ICertificateComponentBuilder { + + @Nonnull private final Component component; + @Nonnull private final CryptoProperties cryptoProperties; + @Nonnull private final CertificateProperties certificateProperties; + + @Nonnull private UUID uuid = UUID.randomUUID(); + + protected CertificateComponentBuilder() { + this.component = new Component(); + this.cryptoProperties = new CryptoProperties(); + this.certificateProperties = new CertificateProperties(); + } + + public CertificateComponentBuilder( + @Nonnull Component component, + @Nonnull CryptoProperties cryptoProperties, + @Nonnull CertificateProperties certificateProperties, + @Nonnull UUID uuid) { + this.component = component; + this.cryptoProperties = cryptoProperties; + this.certificateProperties = certificateProperties; + this.uuid = uuid; + } + + @Nonnull + public static ICertificateComponentBuilder create() { + return new CertificateComponentBuilder(); + } + + @Nonnull + @Override + public ICertificateComponentBuilder certificate(@Nullable Certificate certificate) { + if (certificate == null) { + return new CertificateComponentBuilder( + component, cryptoProperties, certificateProperties, uuid); + } + + this.component.setName(certificate.asString() + "@" + this.uuid); + this.certificateProperties.setCertificateFormat(certificate.getFormat()); + + return new CertificateComponentBuilder( + component, cryptoProperties, certificateProperties, uuid); + } + + @Nonnull + @Override + public ICertificateComponentBuilder occurrences(@Nullable Occurrence... occurrences) { + if (occurrences == null) { + return new CertificateComponentBuilder( + component, cryptoProperties, certificateProperties, uuid); + } + final Evidence evidence = new Evidence(); + evidence.setOccurrences(List.of(occurrences)); + this.component.setEvidence(evidence); + return new CertificateComponentBuilder( + component, cryptoProperties, certificateProperties, uuid); + } + + @Nonnull + @Override + public Component build() { + this.cryptoProperties.setAssetType(AssetType.CERTIFICATE); + this.cryptoProperties.setCertificateProperties(certificateProperties); + + this.component.setType(Component.Type.CRYPTOGRAPHIC_ASSET); + this.component.setCryptoProperties(this.cryptoProperties); + this.component.setBomRef(this.uuid.toString()); + + return this.component; + } +} diff --git a/output/src/main/java/com/ibm/output/cyclondx/builder/ICertificateComponentBuilder.java b/output/src/main/java/com/ibm/output/cyclondx/builder/ICertificateComponentBuilder.java new file mode 100644 index 000000000..00128fa21 --- /dev/null +++ b/output/src/main/java/com/ibm/output/cyclondx/builder/ICertificateComponentBuilder.java @@ -0,0 +1,37 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.output.cyclondx.builder; + +import com.ibm.mapper.model.Certificate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.component.evidence.Occurrence; + +public interface ICertificateComponentBuilder { + @Nonnull + ICertificateComponentBuilder certificate(@Nullable Certificate certificate); + + @Nonnull + ICertificateComponentBuilder occurrences(@Nullable Occurrence... occurrences); + + @Nonnull + Component build(); +}