code: /*
   * Copyright (c) 2019 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.sdk.client.api.identity;
  
  import java.nio.ByteBuffer;
  import java.security.PrivateKey;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import org.eclipse.milo.opcua.stack.core.StatusCodes;
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
  import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType;
  import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
  import org.eclipse.milo.opcua.stack.core.types.structured.SignatureData;
  import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
  import org.eclipse.milo.opcua.stack.core.types.structured.X509IdentityToken;
  import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
  import org.eclipse.milo.opcua.stack.core.util.NonceUtil;
  import org.eclipse.milo.opcua.stack.core.util.SignatureUtil;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.l;
  
  public class X509IdentityProvider implements IdentityProvider {
  
      private final Logger logger = LoggerFactory.getLogger(getClass());
  
      private final List<X509Certificate> certificateChain =
          Collections.synchronizedList(new ArrayList<>());
  
      private final PrivateKey privateKey;
  
      public X509IdentityProvider(X509Certificate certificate, PrivateKey privateKey) {
          this.privateKey = privateKey;
  
          certificateChain.add(certificate);
      }
  
      public X509IdentityProvider(List<X509Certificate> certificateChain, PrivateKey privateKey) {
          this.privateKey = privateKey;
  
          this.certificateChain.addAll(certificateChain);
      }
  
  
      @Override
      public SignedIdentityToken getIdentityToken(EndpointDescription endpoint,
                                                  ByteString serverNonce) throws Exception {
  
          List<UserTokenPolicy> userIdentityTokens = l(endpoint.getUserIdentityTokens());
  
          UserTokenPolicy tokenPolicy = userIdentityTokens.stream()
              .filter(t -> t.getTokenType() == UserTokenType.Certificate)
              .findFirst().orElseThrow(() -> new Exception("no x509 certificate token policy found"));
  
          SecurityPolicy securityPolicy;
  
          String securityPolicyUri = tokenPolicy.getSecurityPolicyUri();
  
          try {
              if (securityPolicyUri == null || securityPolicyUri.isEmpty()) {
                  securityPolicyUri = endpoint.getSecurityPolicyUri();
              }
              securityPolicy = SecurityPolicy.fromUri(securityPolicyUri);
          } catch (Throwable t) {
              throw new UaException(StatusCodes.Bad_SecurityPolicyRejected, t);
          }
  
          X509IdentityToken token = new X509IdentityToken(
              tokenPolicy.getPolicyId(),
              CertificateUtil.getCertificateChainBytes(certificateChain)
          );
  
          SignatureData signatureData;
  
          if (securityPolicy == SecurityPolicy.None) {
              signatureData = new SignatureData(null, null);
          } else {
              NonceUtil.validateNonce(serverNonce);
  
              List<X509Certificate> serverCertificates = CertificateUtil.decodeCertificates(
                  endpoint.getServerCertificate().bytesOrEmpty()
              );
              byte[] serverCertificateBytes = serverCertificates.get(0).getEncoded();
  
              byte[] serverNonceBytes = serverNonce.bytes();
              if (serverNonceBytes == null) serverNonceBytes = new byte[0];
  
              byte[] signature = SignatureUtil.sign(
                  securityPolicy.getAsymmetricSignatureAlgorithm(),
                  privateKey,
                  ByteBuffer.wrap(serverCertificateBytes),
                  ByteBuffer.wrap(serverNonceBytes)
              );
  
              signatureData = new SignatureData(
                  securityPolicy.getAsymmetricSignatureAlgorithm().getUri(),
                  ByteString.of(signature)
              );
          }
  
          return new SignedIdentityToken(token, signatureData);
      }
  
  }
  