After going down a million rabbit holes I got it working!
This test shows two methods: either reading the public key, signature and signed text from within the code or from file (āusefileā - true/false).
Signed with a ECC curve, can also use RSA.
There is no error handling for malformed signatures/public keys or incorrect public key.
I wasted a lot of time because I was incorrectly copying and pasting the signed file ātestā, the original example had a line break chr(10) ā\nā at end which I missed, this example has a <br>
at end.
<!--- for jar file (bcpg-jdk15on-169.jar) see: https://www.bouncycastle.org/latest_releases.html
Lucee - put jar in bundles folder: bcpg-jdk15on-169.jar
java version: 1.8.0_201 (Oracle Corporation) 64bit
--->
<cfset ByteArrayInputStream = CreateObject("java", "java.io.ByteArrayInputStream")>
<cfset StandardCharsets = CreateObject("java", "java.nio.charset.StandardCharsets")>
<cfset BufferedInputStream = CreateObject("java", "java.io.BufferedInputStream")>
<cfset Security = CreateObject("java", "java.security.Security")>
<cfset Base64 = CreateObject("java", "java.util.Base64")>
<cfset FileInputStream = CreateObject("java", "java.io.FileInputStream")>
<cfset Long = CreateObject("java", "java.lang.Long")>
<cfset PGPObjectFactory = CreateObject("java", "org.bouncycastle.openpgp.PGPObjectFactory", "bcpg", "1.69.0")>
<cfset BcKeyFingerprintCalculator = CreateObject("java", "org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator", "bcpg", "1.69.0")>
<cfset PGPSignatureList = CreateObject("java", "org.bouncycastle.openpgp.PGPSignatureList", "bcpg", "1.69.0")>
<cfset PGPPublicKeyRingCollection = CreateObject("java", "org.bouncycastle.openpgp.PGPPublicKeyRingCollection", "bcpg", "1.69.0")>
<cfset PGPUtil = CreateObject("java", "org.bouncycastle.openpgp.PGPUtil", "bcpg", "1.69.0")>
<cfset BcPGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>
<cfset JcaPGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>
<cfset PGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>
<cfset JcaKeyFingerprintCalculator = CreateObject("java", "org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator", "bcpg", "1.69.0")>
<cfset pubkey = "-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.10.10
Comment: https://kodamail.com
xpMEYSQMghMJKyQDAwIIAQENBAMEMzh+ochOHrTCoShos6Igh1jn+vjZe35L
y701FgSa2UMzIf09ZDMsa5j26mvvZqVmmsnLpxpbyG8y2Kgx0k/ztYBJOQSa
uWI3pt2c8eIEkbNW/2irBDfgtkbMGzfH2J0Edpeo9usWUtydYKnPeiNcchka
lqd16sCRdBXh0juYM37NIUFuZHJldyBHcm9zc2V0IDxhZzU3NDNAZ21haWwu
Y29tPsLADwQQEwoAIAUCYSQMggYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4B
ACEJEB9Xkh/GktNtFiEELKKK+ioVLteSmHEYH1eSH8aS021+rAIAh1HYzroS
aEnHa96afXJPLEtzS/vz1DtNo2bLDqRIfTHFXZr7T9gKEAEWHIzo4W1Sjty5
szRscjinf+UTHHGyrAH/fRHJsWQWqnvkbx7OhLclYx89z8ZZYZ1ucnpsnmbF
Wj9bLYKmToI95TYM6V8IxVPVgxxdRyITx2A1nL2KxflqQs6XBGEkDIISCSsk
AwMCCAEBDQQDBGoRKGi7vtlO5dO42B5sliuygYdjlc3GEvUjsSj+7fwRWmj7
CRaJWSVkkMyoJVhShfOuxjG3BBAeBLOQNRdveTR5+XWApV48F0m/5qn9rn0Q
aBT7jdd3bX8Gakp4xv3FkkObUPgOelr6oKSQgJtTWoGnW4jsOTRJWlqPjB2Q
BTcZAwEKCcK4BBgTCgAJBQJhJAyCAhsMACEJEB9Xkh/GktNtFiEELKKK+ioV
LteSmHEYH1eSH8aS020vIQIAlP6lhJg+Dn1gaypg9OwyFRf9kmB0LeYqaIwy
KFd02wUsI5vWGaqnpSXXMs1u5NqoK4dclaO4JMlsQeXhWiEpBAH/eB4J/62v
x4H9DsdMx+sJ0Ixrm/Sb/r2cmKJ33s/1/KjN4eiPf4KxpItItEIvjBaLPfji
Z1NgrXNzszMQweh/oA==
=DN/d
-----END PGP PUBLIC KEY BLOCK-----">
<cfset sig="-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.10
Comment: https://kodamail.com
wrUEARMKAAYFAmEkDL8AIQkQH1eSH8aS020WIQQsoor6KhUu15KYcRgfV5If
xpLTbZMQAf0S25Tat7sbvsgFqwbSbW2Qy4SYwDGF3obW/VxwaNnP1pcJov5C
JMrYf5ud/cv1c37xSJZLFkKx3rUYG1SrFTIxAf4gODASYlP9BlYGnW/Ab3e6
G6ba5ZUXf9Lj/HseLHPKYpLREhUorEMZLwgwHAl9LfO3m7pu4c45w2Um5trn
UUG2
=0BCl
-----END PGP SIGNATURE-----">
<!--- In this example 'signedFile' the newline (<br>) is important! it can also be chr(10) - \n
if copying/pasting this can easily be missed --->
<cfset signedFile = "test<br>"> <!--- test#Chr(10)# --->
<cftry>
<cfscript>
function removeArmor(str){
if(find('-----BEGIN PGP SIGNATURE-----',str) || find('-----BEGIN PGP PUBLIC KEY BLOCK-----',str)){
var afterComment = find('#chr(10)##chr(13)#',str);
var header = str.mid( start=1, count=afterComment );
str = str.replace(header,'');
str = str.replace('#chr(10)#-----END PGP SIGNATURE-----','');
str = str.replace('#chr(10)#-----END PGP PUBLIC KEY BLOCK-----','');
return str
}
return str;
}
//useful sources:
//http://www.java2s.com/example/java-src/pkg/cc/arduino/contributions/gpgdetachedsignatureverifier-6df76.html
//https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
/*
If using Base64.getDecoder().decode(data) have to remove the checksum (=KzaN) at the end of base64 signature (sig) and public key (pubkey)
Alternatively BinaryDecode( data, "Base64" ) can be used and checksums can be left intact.
*/
function getStream(data){
var bytes = BinaryDecode( data, "Base64" );//or Base64.getDecoder().decode(data);
var byteArrayInputStream = ByteArrayInputStream.init(bytes);
var BufferedInputStream = BufferedInputStream.init(byteArrayInputStream);
byteArrayInputStream.close();
return BufferedInputStream;
}
function verify(signedFile, signature, publicKey, usefile){
var signatureInputStream;
if(usefile){
signatureInputStream = BufferedInputStream.init(FileInputStream.init(signature));
}else{
signatureInputStream = getStream( removeArmor(signature) );
}
var pgpObjectFactory = PGPObjectFactory.init( PGPUtil.getDecoderStream(signatureInputStream), BcKeyFingerprintCalculator.init() );//
var nextObject = pgpObjectFactory.nextObject();
var pgpSignatureList = nextObject;
signatureInputStream.close();//close BufferedInputStream
var pgpSignature = pgpSignatureList.get(0);
//dump(label='pgpSignature.getHashAlgorithm()',var=pgpSignature.getHashAlgorithm())//->10 (sha512)
//getKeyID() feed this signature keyID (long) back to pgpPubRingCollection.getPublicKey(keyID)
var keyID = pgpSignature.getKeyID();//2258434403322745600
dump(label='keyID from signature and then toHexString(keyID)',var='#keyID# - #UCase(Long.toHexString(keyID))#');
var pubStream;
if(usefile){
pubStream = BufferedInputStream.init(FileInputStream.init(publicKey));
}else{
pubStream = getStream( removeArmor(publicKey) );
}
var pgpPubRingCollection = PGPPublicKeyRingCollection.init(PGPUtil.getDecoderStream(pubStream), JcaKeyFingerprintCalculator.init());
pubStream.close();//close BufferedInputStream
/* -----------------------------------------------------------------------------------------------------------------------------------
var keyRingIter = pgpPubRingCollection.getKeyRings();
var keyRing;
var keyIter;
var key;
while (keyRingIter.hasNext()) {
keyRing = keyRingIter.next();
keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
key = keyIter.next();
if (key.isMasterKey()){
dump(label='key.isMasterKey()',var=key.getKeyID());
}
dump(label='key',var=key);
//dump(pgpPubRingCollection.getPublicKey(key.getKeyID()));
dump(label='key.getKeyID()',var=key.getKeyID());
dump(key.getCreationTime());
userids = pgpPubRingCollection.getPublicKey(key.getKeyID()).getUserIDs()
while(userids.hasNext()){
dump(userids.next());
}
}
}
-------------------------------------------------------------------------------------------------------------------------
*/
//get the public key according to keyID found in signature: 2258434403322745600
var pgpPublicKey = pgpPubRingCollection.getPublicKey(keyID);
pgpSignature.init(JcaPGPContentVerifierBuilderProvider.init().setProvider(Security.getProvider("BC")), pgpPublicKey);
//also works:
//pgpSignature.init(BcPGPContentVerifierBuilderProvider.init(), pgpPublicKey);
//the string we have signed: 'test'
var signedBufferedInputStream;
if(usefile){
signedBufferedInputStream = BufferedInputStream.init(FileInputStream.init(signedFile));
}else{
signedBufferedInputStream = BufferedInputStream.init(ByteArrayInputStream.init(signedFile.toString().getBytes('UTF-8')));
}
var ch;
while ((ch = signedBufferedInputStream.read()) >= 0)
{
//dump(JavaCast( "int", ch));
pgpSignature.update(ch);
}
if(!usefile){
ByteArrayInputStream.close();
}
BufferedInputStream.close();
//alternative to looping BufferedInputStream....
//pgpSignature.update(signedFileInputStream);
//returns true or false
return pgpSignature.verify();
}
//2 possible calling methods: either from string or from files:
dump(label='verify result',var=verify(signedFile, sig, pubkey, false));
//uses pub key:99656C36B79A57E7 ecc with hash 512
//dump(label='verify result',var=verify('/var/www/vhosts/******/www/test/encryption/test2.txt', '/var/www/vhosts/******/www/test/encryption/sig2.asc', '/var/www/vhosts/******/www/test/encryption/pubkey2.asc', true));
</cfscript>
<cfcatch><cfdump var="#cfcatch#"></cfcatch>
</cftry>