Open hongkongkiwi opened 5 months ago
Could you try again with the latest build: https://github.com/ebourg/jsign/actions/runs/9286970939/artifacts/1548458586
The latest build fixes the issue above, it almost works, but I found that when I want to pass the certificate file that's going to be used for signing the build and now that's where I'm getting stuck (forgot to add that arg in above command):
java -cp apksigner.jar:jsign-6.1-SNAPSHOT.jar com.android.apksigner.ApkSignerTool sign \
--provider-class "net.jsign.jca.JsignJcaProvider" \
--provider-arg "us-west-1" \
--ks NONE \
--ks-type AWS \
--ks-pass "env:CREDS" \
--ks-key-alias "c00c48df-b7a5-47ce-be55-476db10f407b" \
--in ./app/build/outputs/apk/debug/app-debug.apk \
--out ./test-signed.apk
This above doesn't work as I need the certificate:
Failed to load signer "signer #1"
java.lang.RuntimeException: Failed to load the certificate from
at net.jsign.KeyStoreType.lambda$getCertificateStore$0(KeyStoreType.java:672)
at net.jsign.jca.AmazonSigningService.getCertificateChain(AmazonSigningService.java:152)
at net.jsign.jca.SigningServiceKeyStore.engineGetCertificateChain(SigningServiceKeyStore.java:43)
at java.base/java.security.KeyStore.getCertificateChain(KeyStore.java:1100)
at net.jsign.jca.JsignJcaProvider$JsignJcaKeyStore.engineGetCertificateChain(JsignJcaProvider.java:134)
at java.base/java.security.KeyStore.getCertificateChain(KeyStore.java:1100)
at com.android.apksigner.SignerParams.loadPrivateKeyAndCertsFromKeyStore(SignerParams.java:359)
at com.android.apksigner.SignerParams.loadPrivateKeyAndCerts(SignerParams.java:202)
at com.android.apksigner.ApkSignerTool.getSignerConfig(ApkSignerTool.java:438)
at com.android.apksigner.ApkSignerTool.sign(ApkSignerTool.java:353)
at com.android.apksigner.ApkSignerTool.main(ApkSignerTool.java:92)
Caused by: java.io.FileNotFoundException: (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:152)
at net.jsign.CertificateUtils.loadCertificateChain(CertificateUtils.java:57)
at net.jsign.KeyStoreType.lambda$getCertificateStore$0(KeyStoreType.java:670)
... 10 more
Maybe it's simple, but I couldn't quite figure out a way to do pass jsign the certfile name. I've tried:
--ks-certfile
--ks-cert
--certfile
--cert
Looking at it logically I guess we are actually running apksigner and can only use the arguments it supports and it doesn't seem to support any --ks-cert* option (it does support a --cert option, but it uses this exclusively with local keyfiles only, it's not used for plugins),
The apksigner does mention a --cert option when using jsign as a keystore provider but in the latest apksigner this does not actually work.
Failed to load signer "signer #1": --ks and --cert may not be specified at the same time
So instead I guess we need to pass the certfile to use for signing in a different way back to jsign. Either as part of --ks-key-alias
or perhaps --ks-provider-arg
?
For reference for others reading this the AWS KMS doesn't store certificates at all, only public and private keys. If you want to use it for signing with any kind of certificate that's signed by the key, you'll have to generate a signed certificate, then use that as a file, then store this certificate out of band somewhere.
Amazon does have some (expensive) solutions such as amazon Private CA which can handle storing certificates, but not their low cost KMS product this plugin uses.
Also for others who are going down the same track, if you want to quickly export credentials in a jsign compatible format, you can use this one liner to set the JSIGN_AWS_CREDS environment variable: export JSIGN_AWS_CREDS=$(aws sts get-session-token | jq -r '.Credentials | "\(.AccessKeyId)|\(.SecretAccessKey)|\(.SessionToken)"')
@ebourg I just found that this rejected) PR #215 contains a fix which can exactly fix this issue: https://github.com/ebourg/jsign/pull/215/commits/f81a6bb9ba3fa9cf2f3c11d1cb3f4892a8f90abc , that PR was talking about a problem with jarsigner, but with jarsigner can you pass an option for a certchain so it's not necessary, but for apksigner you cannot, so we need a method like this to pass it.
Essentially, with the above fix we can pass the certificate file as a system parameter:
java -cp apksigner.jar:./jsign-6.1-SNAPSHOT.jar -Djsign.certfile=certificate.pem com.android.apksigner.ApkSignerTool sign ...
If we don't use above, we could change the name of the keystore to be something like "region=us-east-1:certfile=certificate.pem" via the --provider-arg. This might be slightly more complex though.
@hongkongkiwi Thank you for the analysis. I didn't realize that the --ks
and --cert
parameters were mutually exclusive, and indeed it won't work with the services storing only the key (AWS, Google Cloud and Oracle Cloud).
I see several approaches to this problem:
--cert
usable with --ks
. I've contacted the apksigner developers to see if this is possible.jsign signapk --ks-type AWS --ks-key-alias ...
).Idea (1) using the system property wouldn't cause a conflict in my case as I'm calling it each time with the specific property set in a Lambda. But I see what you mean, it's not going to be idea for some people's batch use cases. Probably another option is better.
Ideally, I think (3) is a good move. This seems like a bug and other tools support this just fine.
Google uses the sun PKCS11 plugin along with a google cloud pkcs11 implementation. This uses a separate config file to setup the parameters for the sun PKCS11 plugin. That's why they havn't really come across this probably since the sun pkcs11 plugin has it's own way of configuring.
I think implementing (2) is a also good idea and something that's relatively straightforward and can be done without assistance from third parties. Perhaps it could be done in a way that is backwards compatible. For example first we check if we have key=value then we can pass multiple keys based on jsign's normal input parameters. If no key=value exists then just fallback to existing method of processing the alias.
That could be "alias=abc|certfile=abc.pem". This would assume that aliases don't normally contain a pipe character or equals character (not sure about this?
Methods 4, 5, 6 seem to have a high development burden and don't address other possible tools which may have the same issue (although I don't know any of those).
Google seems to regularly develop and modify apksigner, especially as they are on signing version v4 now. I like the idea that I can just use the latest version of that alongside the latest version of jsign. This is a great product and I don't think it makes sense to add a lot of development distraction for keeping upto date with apksigner.
Looks like this issue affects all keystore types when using apksigner and jsign via JCA, not only AWS. I'm trying to use JKS keystore type as a JCA plugin, but it also has the same issue.
What exception do you get with a JKS keystore?
For reference for others reading this the AWS KMS doesn't store certificates at all, only public and private keys. If you want to use it for signing with any kind of certificate that's signed by the key, you'll have to generate a signed certificate, then use that as a file, then store this certificate out of band somewhere.
@hongkongkiwi
I just wanted to say this comment was very helpful! What I ended up doing is creating a CSR with openssl using a private key and then storing that key material in KMS. Using the modified code from PR-215 I was able to then sign an APK with the cert and the key in KMS.
Hi there,
I'm struggling a little bit to get this working. I'd like to use JSign to sign an apk (using apksigner) using an AWS KMS key.
Here's what I've got (not, I've substituted the jar paths here, but locally I'm using the full path to the jar file).
In this case,
CREDS="<ACCESS_KEY>|<SECRET_KEY>|<SESSION_TOKEN>"
as specified in the docs.Is this the correct usage? I'm getting this error: