Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authenticate with application id using Certificated-based Authentication #2075

Open
0B7002 opened this issue Jul 9, 2024 · 10 comments
Open
Assignees
Labels

Comments

@0B7002
Copy link

0B7002 commented Jul 9, 2024

I registered the app on the Microsoft Entra admin center by referring to this page:

https://learn.microsoft.com/en-us/graph/auth-v2-service?tabs=http

Then I created public/private key using openssl. Here is the commands.

$ openssl genrsa -out ms365-private.key 2048
$ openssl req -new -x509 -key ms365-private.key -out ms365-public.crt -days 365000

Uploaded the public key (ms365-private.key) on the Microsoft Entra admin center, and I created this code.

TokenCredential credential = new ClientCertificateCredentialBuilder().tenantId("xxxxxxxx")
		.clientId("xxxxxxxx")
		.pemCertificate("/xxxx/xxxxx/ms365-private.key")
		.build();
graphClient = new GraphServiceClient(credential);

but if i execute some methods on this graphClient , this following error occurs

[ERROR] com.azure.identity.implementation.util.CertificateUtil.performLogging - PEM certificate provided does not contain -----BEGIN CERTIFICATE-----END CERTIFICATE----- block 
java.lang.IllegalArgumentException: PEM certificate provided does not contain -----BEGIN CERTIFICATE-----END CERTIFICATE----- block
    at com.azure.identity.implementation.util.CertificateUtil.publicKeyFromPem(CertificateUtil.java:79)
    at com.azure.identity.implementation.IdentityClientBase.getConfidentialClient(IdentityClientBase.java:196)
    at com.azure.identity.implementation.IdentitySyncClient.lambda$new$2(IdentitySyncClient.java:91)
    at com.azure.identity.implementation.SynchronousAccessor.getValue(SynchronousAccessor.java:45)
    at com.azure.identity.implementation.IdentitySyncClient.authenticateWithConfidentialClientCache(IdentitySyncClient.java:171)
    at com.azure.identity.ClientCertificateCredential.getTokenSync(ClientCertificateCredential.java:150)
    at com.microsoft.kiota.authentication.AzureIdentityAccessTokenProvider.getAuthorizationToken(AzureIdentityAccessTokenProvider.java:146)
    at com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider.authenticateRequest(BaseBearerTokenAuthenticationProvider.java:46)

Q. Should I also specify public key? If so, how to specify public key by using ClientCertificateCredentialBuilder?

@0B7002 0B7002 added the status:waiting-for-triage An issue that is yet to be reviewed or assigned label Jul 9, 2024
@Ndiritu Ndiritu self-assigned this Jul 15, 2024
@Ndiritu Ndiritu added type:question An issue that's a question and removed status:waiting-for-triage An issue that is yet to be reviewed or assigned labels Jul 15, 2024
@0B7002
Copy link
Author

0B7002 commented Jul 29, 2024

I tried the following but all failed.

  • Change a path specify to pemCertificate() from the private key (ms365-private.key) to the public key (ms365-public.crt)
  • Change the format of the private key (ms365-private.key) to pem format (contains -----BEGIN CERTIFICATE-----END CERTIFICATE----- block)

please help.

@0B7002
Copy link
Author

0B7002 commented Aug 7, 2024

Is anyone working on this issue? I have to resolve this issue as soon as possible.

@Ndiritu
Copy link
Contributor

Ndiritu commented Aug 7, 2024

@0B7002 I'll try to reproduce this and get back to you later today. Apologies for the delay

@0B7002
Copy link
Author

0B7002 commented Aug 13, 2024

When can I get a reply?

@0B7002
Copy link
Author

0B7002 commented Aug 19, 2024

Is anyone working on this issue?

@Ndiritu
Copy link
Contributor

Ndiritu commented Aug 19, 2024

Sorry for the delay @0B7002.

From my understanding of certificate-based authentication & these docs what you should be uploading to your app registration is the X509 certificate generated.

From Azure Identity's logic it seems that we expect a certificate file containing a private key. The certs generated by OpenSSL only have the public key in the encoded payload.

Still investigating this.

@0B7002
Copy link
Author

0B7002 commented Aug 20, 2024

Thank you for your answer.

As mentioned earlier, I generated and uploaded a public key with the x509 option.

And I read docs but could not find how to generate pem file.

What kind of files should I upload to my app and how to generate it? what value should I specify to pemCertificat()? I have no idea, please help.

@0B7002
Copy link
Author

0B7002 commented Aug 23, 2024

From my understanding, this error has occured causing private key, not public key.

And as mentioned earlier, I created public/private key using openssl, Here is the commands.

$ openssl genrsa -out ms365-private.key 2048
$ openssl req -new -x509 -key ms365-private.key -out ms365-public.crt -days 365000

So I tried to get public key information from private key by a following command, and suceed.

$ openssl rsa -pubout < ms365-private.key
-----BEGIN PUBLIC KEY-----
XXXXX
-----END PUBLIC KEY-----
writing RSA key

Why pemCertificat() can not read this private key? How should I create a key?

@0B7002
Copy link
Author

0B7002 commented Sep 3, 2024

Using the same public/private key, I successed to get access token by the following code.

// private key
byte[] encoded = Base64.getDecoder().decode("[private key]");
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(encoded));

// public key
X509Certificate publicKey = (X509Certificate) CertificateFactory.getInstance("X.509")
		.generateCertificate(Files.newInputStream(Paths.get("[public key file path]")));

// parameter
String authority = "https://login.microsoftonline.com/[tenantid]/";
Set<String> scope = Set.of("https://graph.microsoft.com/.default");

// client
IClientCredential credential = ClientCredentialFactory.createFromCertificate(privateKey, publicKey);
ConfidentialClientApplication cca = ConfidentialClientApplication.builder("[clientid]", credential)
		.authority(authority)
		.build();
ClientCredentialParameters parameters = ClientCredentialParameters.builder(scope).build();

// get token
CompletableFuture<IAuthenticationResult> result = cca.acquireToken(parameters);
System.out.println(result.get().accessToken());

Because of this, I think that the public/private key is correct.

How to do this in using GraphServiceClient?
And, do I need to have and specify the public key on the client side as well?

@0B7002
Copy link
Author

0B7002 commented Sep 12, 2024

Using a private key and thumbprint, I successed to authorize by the following code.

import java.security.KeyFactory;
import java.security.interfaces.RSAKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.OffsetDateTime;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.codec.binary.Hex;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.azure.core.credential.TokenCredential;
import com.azure.identity.ClientAssertionCredentialBuilder;
import com.microsoft.graph.serviceclient.GraphServiceClient;

public class MyService {

	public GraphServiceClient getGraphClient() throws Exception {
		// private key
		byte[] encoded = Base64.getDecoder().decode("[private key base64 text]");
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
		RSAKey privateKey = (RSAKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec);

		// generate JWT
		byte[] bytes = Hex.decodeHex("thumbprint".toCharArray());
		String x5t = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
		Map<String, Object> header = new HashMap<>();
		header.put("x5t", x5t);
		String assertion = JWT.create()
				.withHeader(header)
				.withAudience("https://login.microsoftonline.com/" + "[tenantId]" + "/oauth2/v2.0/token")
				.withExpiresAt(OffsetDateTime.now().plusMinutes(5).toInstant())
				.withIssuer("[clientId]")
				.withJWTId(UUID.randomUUID().toString())
				.withNotBefore(OffsetDateTime.now().toInstant())
				.withSubject("[clientId]")
				.withIssuedAt(OffsetDateTime.now().toInstant())
				.sign(Algorithm.RSA256(privateKey));

		// generate GraphServiceClient
		TokenCredential credential = new ClientAssertionCredentialBuilder().tenantId("[tenantId]")
				.clientId("[clientId]")
				.clientAssertion(() -> assertion)
				.build();
		return new GraphServiceClient(credential);
	}
}

Is this a best practice?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants