From c472fa04bfbf88ba0507c15b1cd1540fe0c0b166 Mon Sep 17 00:00:00 2001 From: olivier Date: Fri, 24 Oct 2025 17:46:05 -0700 Subject: [PATCH] =?UTF-8?q?Tests=20unitaires=20pour=20le=20fichier=20Adept?= =?UTF-8?q?Activate.java=20et=20ses=20classes=20associ=C3=A9es.=20La=20cla?= =?UTF-8?q?sse=20AdeptActivateCli.java=20n'est=20pas=20inclue.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/AdeptActivateTest.java | 179 +++++++++++ src/test/java/common/BCCrypto.java | 93 ++++++ src/test/java/common/MockHttpUtils.java | 300 ++++++++++++++++++ .../mockServerResponses/activationInfo.xml | 5 + .../mockServerResponses/authServiceInfo.xml | 1 + 5 files changed, 578 insertions(+) create mode 100644 src/test/java/AdeptActivateTest.java create mode 100644 src/test/java/common/BCCrypto.java create mode 100644 src/test/java/common/MockHttpUtils.java create mode 100644 src/test/resources/mockServerResponses/activationInfo.xml create mode 100644 src/test/resources/mockServerResponses/authServiceInfo.xml diff --git a/src/test/java/AdeptActivateTest.java b/src/test/java/AdeptActivateTest.java new file mode 100644 index 0000000..cd76aed --- /dev/null +++ b/src/test/java/AdeptActivateTest.java @@ -0,0 +1,179 @@ +import common.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.util.Base64; +import java.util.Comparator; +import java.util.Map; +import java.util.logging.Level; + +import static common.Activation.HOBBES_DEFAULT_VERSION; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.junit.jupiter.api.Assertions.*; + +class AdeptActivateTest +{ + @SuppressWarnings("FieldCanBeLocal") + private String publicAuthKey; + + @BeforeAll + static void setupBouncyCastle() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private Device device; + private Activation activation; + private final Path dirPath = Path.of( "src/test", "resources" ); + private AdeptActivate activator; + + public static void deleteDirectoryWithContents( Path directoryPath ) throws IOException + { + // Check if the path exists and is a directory + if (Files.exists( directoryPath ) && Files.isDirectory( directoryPath )) + { + // Walk the file tree in depth-first order (reverse order for deletion) + Files.walk( directoryPath ) + .sorted( Comparator.reverseOrder() ) // Sort in reverse to delete contents before parent directory + .forEach( path -> { + try + { + Files.delete( path ); // Delete each file or directory + } + catch (IOException ignore) {} + } ); + } + } + + @Test + void createActivation() + throws NoSuchAlgorithmException, IOException, ParserConfigurationException, SAXException + { + Path tempPath = Paths.get( dirPath.toString(), "temp" ); + deleteDirectoryWithContents( tempPath ); + Files.createDirectories( tempPath ); + // copy in a constant key file so we can use it to decrypt. + + Files.copy( Path.of( dirPath.toString(), "devicesalt"), + Path.of( tempPath.toString(), "devicesalt") , REPLACE_EXISTING ); + device = new Device( tempPath.resolve( "device.xml" ), tempPath.resolve( "devicesalt" ), + HOBBES_DEFAULT_VERSION, true, Level.WARNING ); + // Make sure files got created + assertTrue( Files.exists( Path.of( tempPath.toString(), "devicesalt" ) )); + assertTrue( Files.exists( Path.of( tempPath.toString(), "device.xml" ) )); + + activation = Activation.createActivation( tempPath.resolve( "activation.xml" ), + "localhost", new MockHttpUtils( device ), Level.WARNING ); + // Only 5 properties at this point. Check size and activationURL + assertEquals( 5, activation.getPropertiesSize() ); + assertEquals( "http://localhost/adept", activation.getActivationURL()); + } + + @Test + void loadActivation() + throws NoSuchAlgorithmException, IOException, ParserConfigurationException, SAXException + { + device = new Device( dirPath.resolve( "device.xml" ), dirPath.resolve( "devicesalt" ), + HOBBES_DEFAULT_VERSION, true, Level.FINEST ); + + activation = Activation.createActivation( dirPath.resolve( "activation.xml" ), + "localhost", new MockHttpUtils( device ), Level.FINEST ); + assertEquals( 15, activation.getPropertiesSize() ); + + // Did credentials get loaded? + assertEquals( "urn:uuid:26031c45-3be6-46fd-9a66-0457b0b8c2fc", activation.getUUID()); + + // Did the license server info get loaded? + Map services = activation.getLicenseServices(); + assertTrue( services.containsKey( "https://nasigningservice.adobe.com/licensesign" ) ); + assertTrue( activation + .getLicenseServiceCertificate( "https://nasigningservice.adobe.com/licensesign" ) + .startsWith( "MIIEvjCCA6agAwIBAgIER2q5ljANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCVVMx" )); + } + + @Test + void signIn() + throws GeneralSecurityException, IOException, ParserConfigurationException, SAXException + { + createActivation(); // This creates new activation files in the temp directory + Path tempPath = Paths.get( dirPath.toString(), "temp" ); + + activator = new AdeptActivate( activation, device, tempPath.toString() ); + Document signInRequest = activator.buildSignInRequest( "anonymous", "", + activation.getAuthenticationCertificate() ); + + // save the public auth key so we can use it later. + NodeList els = signInRequest.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, + "publicAuthKey" ); + publicAuthKey = els.item( 0 ).getTextContent(); + + els = signInRequest.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, + "encryptedPrivateLicenseKey" ); + String privateKey = els.item( 0 ).getTextContent(); + byte[] encrypted = Base64.getDecoder().decode( privateKey ); + SecretKeySpec secretKey = new SecretKeySpec( device.getDeviceKey(), "AES" ); + privateKey = Base64.getEncoder().encodeToString( BCCrypto.decrypt( encrypted, secretKey )); + + activator.signIn( signInRequest ); + assertEquals( 11, activation.getPropertiesSize() ); + String privateLicenseKey = activation.getPrivateLicenseKey(); + assertEquals( privateKey, privateLicenseKey ); + } + + @Test + void activateDevice() + throws GeneralSecurityException, IOException, ParserConfigurationException, SAXException + { + signIn(); + Document activationRequest = activator.buildActivateReq(); + + activator.activateDevice( activationRequest ); + assertEquals( 15, activation.getPropertiesSize() ); + } + + @Test + void exportPrivateLicenseKey() + throws NoSuchAlgorithmException, IOException, ParserConfigurationException, SAXException + { + File[] filesToDelete = dirPath.toFile().listFiles( + file -> { + return file.isFile() + && ( file.getName().matches(".*\\.der") + || file.getName().matches(".*\\.pem")); // Example: matches all .txt files + } ); + for (File fileToDelete : filesToDelete) + { + try + { + Files.delete( Path.of( fileToDelete.getPath() ) ); + } + catch (IOException ignore){} + } + loadActivation(); + activator = new AdeptActivate( activation, device, dirPath.toString() ); + + byte[] key = activator.exportPrivateLicenseKey(); + assertNotNull( key ); + String uuid = activation.getUUID(); + uuid = uuid.substring( uuid.lastIndexOf('-' ) ); + Path keyPath = Paths.get( dirPath.toString(), "anonymous"+uuid+".pem" ); + assertTrue( Files.exists( keyPath )); + keyPath = Paths.get( dirPath.toString(), "anonymous"+uuid+".der" ); + assertTrue( Files.exists( keyPath )); + } +} \ No newline at end of file diff --git a/src/test/java/common/BCCrypto.java b/src/test/java/common/BCCrypto.java new file mode 100644 index 0000000..03d126e --- /dev/null +++ b/src/test/java/common/BCCrypto.java @@ -0,0 +1,93 @@ +package common; + +import java.math.BigInteger; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Date; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import static javax.crypto.Cipher.DECRYPT_MODE; + +public class BCCrypto +{ + static + { + Security.addProvider( new BouncyCastleProvider() ); + } + + static String uuid = "urn:uuid:28ab4b64-db75-4f76-a69a-c00e04225bf6"; + + public static byte[] decrypt( byte[] encryptedPrivateKey, SecretKeySpec secretKey ) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, + InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException + { + byte[] iv = new byte[16]; + System.arraycopy( encryptedPrivateKey, 0, iv, 0, 16 ); + byte[] encryptedData = new byte[encryptedPrivateKey.length - 16]; + System.arraycopy( encryptedPrivateKey, 16, encryptedData, 0, encryptedPrivateKey.length - 16); + Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding", "BC" ); + cipher.init( DECRYPT_MODE, secretKey, new IvParameterSpec( iv ) ); + return cipher.doFinal( encryptedData ); + } + + public static X509Certificate createSSCertificate( byte[] publicKeyData, byte[] privateKeyData ) + throws CertificateException, OperatorCreationException, NoSuchAlgorithmException, NoSuchProviderException, + InvalidKeySpecException + { + X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyData); + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privateKeyData); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); + { + ASN1ObjectIdentifier customOid = new ASN1ObjectIdentifier("2.5.4.6"); + + X500NameBuilder subjectBuilder = new X500NameBuilder( BCStyle.INSTANCE); + subjectBuilder.addRDN(customOid, uuid ); // Add your custom OID and value + X500Name subjectDN = subjectBuilder.build(); + // Certificate details + X500Name subject = new X500Name( "CN=MySelfSignedCert, O=MyOrg" ); + BigInteger serial = BigInteger.valueOf( System.currentTimeMillis() ); + Date notBefore = new Date( System.currentTimeMillis() - 1000L * 60 * 60 * 24 ); // 1 day ago + Date notAfter = new Date( System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 3650 ); // 10 years from now + + // Build the certificate + PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec); + PublicKey publicKey = keyFactory.generatePublic(pubKeySpec); + X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( + subject, serial, notBefore, notAfter, subjectDN, publicKey); + + ContentSigner contentSigner = new JcaContentSignerBuilder( "SHA256WithRSA" ) + .setProvider( "BC" ) + .build( privateKey ); + + X509Certificate certificate = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter() + .getCertificate( certBuilder.build( contentSigner ) ); + + String b64 = Base64.getEncoder().encodeToString( certificate.getEncoded() ); + return certificate; + } + } +} diff --git a/src/test/java/common/MockHttpUtils.java b/src/test/java/common/MockHttpUtils.java new file mode 100644 index 0000000..f28693f --- /dev/null +++ b/src/test/java/common/MockHttpUtils.java @@ -0,0 +1,300 @@ +package common; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.pkcs.*; +import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +import org.bouncycastle.pkcs.jcajce.JcePKCS12MacCalculatorBuilder; +import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.security.*; + +import static common.BCCrypto.decrypt; +import static common.BCCrypto.uuid; +import static common.WinDevice.hexStringToByteArray; +import static javax.crypto.Cipher.DECRYPT_MODE; +import static javax.crypto.Cipher.ENCRYPT_MODE; +import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_friendlyName; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +public class MockHttpUtils extends HttpUtils +{ + private final Device device; + String mockResponsePath = "src/test/resources/mockServerResponses"; + private PublicKey publicKey; + + public MockHttpUtils( Device device ) + { + Security.addProvider( new BouncyCastleProvider() ); + this.device = device; + } + + @Override + public String HTTPSendRequest( String urlString, String postData, String contentType, + Map responseHeaders, String outputFilePath, boolean resume ) + { + try + { + if ("localhost/ActivationServiceInfo".equalsIgnoreCase( urlString )) + { + // return the mock activation data, modified for localhost + return Files.readString( Paths.get( mockResponsePath, "activationInfo.xml" ) ); + } + else if ("http://localhost/adept/AuthenticationServiceInfo".equalsIgnoreCase( urlString )) + { + // Lots of data here, most of which is discarded, but we keep it for completeness + return Files.readString( Paths.get( mockResponsePath, "authServiceInfo.xml" ) ); + } + else if ("http://localhost/adept/SignInDirect".equalsIgnoreCase( urlString )) + { + return createSignInResponse( postData ); + } + else if ("http://localhost/adept/Activate".equalsIgnoreCase( urlString )) + { + return createActivationResponse( postData ); + } + return null; + } + catch (IOException | ParserConfigurationException | SAXException | GeneralSecurityException | + OperatorCreationException e) + { + LOGGER.log( Level.SEVERE, "HTTP Request error: " + e.getMessage(), e ); + throw new GourouException( "HTTP Request failed", e ); + } + catch (PKCSException e) + { + throw new RuntimeException( e ); + } + } + + private String createActivationResponse( String activationRquest ) + throws IOException, ParserConfigurationException, SAXException, NoSuchAlgorithmException, + NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, IllegalBlockSizeException, + BadPaddingException + { + DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); + fac.setNamespaceAware( true ); + DocumentBuilder db = fac.newDocumentBuilder(); + Document requestDoc = db.parse( new ByteArrayInputStream( activationRquest.getBytes() ) ); + requestDoc.getDocumentElement().normalize(); + + Element activationToken = requestDoc.getDocumentElement(); + NodeList els = requestDoc.getElementsByTagNameNS( + XmlUtils.ADOBE_ADEPT_NS, "signature" ); + Node sigNode = els.item( 0 ); + activationToken.removeChild( sigNode ); + + ByteArrayOutputStream xer = new ByteArrayOutputStream(); + //noinspection Convert2Diamond + XmlUtils.xml2ASN( activationToken, new HashMap(), xer ); + + MessageDigest shaMd = MessageDigest.getInstance( "SHA1" ); + + // the signature as I compute it + String signature = Base64.getEncoder().encodeToString( shaMd.digest( xer.toByteArray() ) ); + + // now get the attached signature, decrypt it with the public key, and see if it matches. + String originalSig = sigNode.getTextContent(); + Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding", "BC" ); + cipher.init( DECRYPT_MODE, publicKey ); + + byte[] enc = cipher.doFinal( Base64.getDecoder().decode( originalSig )); + originalSig = Base64.getEncoder().encodeToString( enc ); + if (!signature.equals( originalSig )) + System.out.println( "Signatures do not match" ); + + Document responseDoc = db.newDocument(); + responseDoc.appendChild( responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "activationToken" ) ); + Element token = responseDoc.getDocumentElement(); + token.setAttribute( "xmlns", XmlUtils.ADOBE_ADEPT_NS ); + + Element child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "device" ); + child.setTextContent( "urn:uuid:f163499a-1dc7-41fd-9ec9-fcf69c8eae18" ); + token.appendChild( child ); + + NodeList elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "fingerprint" ); + String text = elementList.item( 0 ).getTextContent(); + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "fingerprint" ); + child.setTextContent( text ); + token.appendChild( child ); + + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "deviceType" ); + child.setTextContent( "standalone" ); + token.appendChild( child ); + + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "activationURL" ); + child.setTextContent( "http://localhost/activation/adept" ); + token.appendChild( child ); + + elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "user" ); + text = elementList.item( 0 ).getTextContent(); + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "user" ); + child.setTextContent( text ); + token.appendChild( child ); + + + // This will fail the signature check... + String fauxSig = "S4rgc/FJkfK7BbFCrbKNs+SznA+OZfv2TER2vP511YevEUBOupkCw0E2R2u3ohpp0WQV7ds2vKJ6K4oQOlUuuO9e7jElDiaIQa77LOLnKpTl37lcbKR1cPCOEzUlsM7tBZUtdHM+CB5Iy+Yh624zHWFf8zDfGv6aKyPGL948OMo="; + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "signature" ); + child.setTextContent( fauxSig ); + token.appendChild( child ); + + return XmlUtils.docToString( responseDoc, true ); + } + + private String createSignInResponse( String signInRequest ) + throws ParserConfigurationException, IOException, SAXException, GeneralSecurityException, + OperatorCreationException, PKCSException + { + Base64.Decoder b64Decoder = Base64.getDecoder(); + Base64.Encoder b64Encoder = Base64.getEncoder(); + DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); + fac.setNamespaceAware( true ); + DocumentBuilder db = fac.newDocumentBuilder(); + Document requestDoc = db.parse( new ByteArrayInputStream( signInRequest.getBytes() ) ); + requestDoc.getDocumentElement().normalize(); + + Document responseDoc = db.newDocument(); + responseDoc.appendChild( responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "credentials" ) ); + Element credentials = responseDoc.getDocumentElement(); + credentials.setAttribute( "xmlns", XmlUtils.ADOBE_ADEPT_NS ); + Element child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "user" ); + child.setTextContent( uuid ); + credentials.appendChild( child ); + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "username" ); + child.setAttribute( "method", "anonymous" ); + credentials.appendChild( child ); + + NodeList elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "publicAuthKey" ); + byte[] publicAuthKey = b64Decoder.decode( elementList.item( 0 ).getTextContent() ); + + elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "encryptedPrivateAuthKey" ); + byte[] privateAuthKey = b64Decoder.decode( elementList.item( 0 ).getTextContent() ); + SecretKeySpec secretKey = new SecretKeySpec( device.getDeviceKey(), "AES" ); + byte[] decryptedAuthKey = decrypt( privateAuthKey, secretKey ); + + X509Certificate certificate = BCCrypto.createSSCertificate( publicAuthKey, decryptedAuthKey ); + publicKey = certificate.getPublicKey(); // pretend we're the server, and save this for later + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec( decryptedAuthKey ); + + PrivateKey privateKey = KeyFactory.getInstance( "RSA", "BC" ) + .generatePrivate( keySpec ); + + String dk = Base64.getEncoder().encodeToString( device.getDeviceKey() ); + char[] keyPassword = dk.toCharArray(); + JcePKCSPBEOutputEncryptorBuilder encBuilder = + new JcePKCSPBEOutputEncryptorBuilder( PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC ); + encBuilder.setProvider( "BC" ); + OutputEncryptor outputEncryptor = encBuilder.build( keyPassword ); + JcaPKCS12SafeBagBuilder safeBagBuilder = + new JcaPKCS12SafeBagBuilder( privateKey, outputEncryptor ); + + safeBagBuilder.addBagAttribute( pkcs_9_at_friendlyName, new org.bouncycastle.asn1.DERBMPString( uuid ) ); + byte[] data = hexStringToByteArray( "54696D652031373531363730383233323636" ); + DEROctetString octetString = new DEROctetString(data); + + safeBagBuilder.addBagAttribute( PKCSObjectIdentifiers.pkcs_9_at_localKeyId, + ASN1OctetString.getInstance( new DEROctetString( octetString.getEncoded() ))); + + // Build the PKCS#12 Pfx structure + PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder(); + pfxPduBuilder.addData( safeBagBuilder.build() ); + pfxPduBuilder.addEncryptedData( outputEncryptor, safeBagBuilder.build() ); + + // Build the Pfx object with MAC protection + char[] pfxPassword = "".toCharArray(); + org.bouncycastle.pkcs.PKCS12PfxPdu pfx = pfxPduBuilder.build( new JcePKCS12MacCalculatorBuilder(), + pfxPassword ); + String pkcs12 = Base64.getEncoder().encodeToString( pfx.getEncoded() ); + + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "pkcs12" ); + child.setTextContent( pkcs12 ); + credentials.appendChild( child ); + + // reencrypt the private key and add it to the response. + elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "encryptedPrivateLicenseKey" ); + String b64 = elementList.item( 0 ).getTextContent(); + byte[] privateLicKey = b64Decoder.decode( b64 ); + byte[] decryptedLicenseKey = decrypt( privateLicKey, secretKey ); + + Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding", "BC" ); + cipher.init( ENCRYPT_MODE, secretKey); + byte[] encryptedData = cipher.doFinal( decryptedLicenseKey ); + byte[] iv = cipher.getIV(); + byte[] finalEncrypteData = new byte[16 + encryptedData.length]; + System.arraycopy( iv, 0, finalEncrypteData, 0, 16 ); + System.arraycopy( encryptedData, 0, finalEncrypteData, 16, encryptedData.length ); + + b64 = b64Encoder.encodeToString( finalEncrypteData ); + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "encryptedPrivateLicenseKey" ); + child.setTextContent( b64 ); + credentials.appendChild( child ); + + elementList = requestDoc.getElementsByTagNameNS( XmlUtils.ADOBE_ADEPT_NS, "publicLicenseKey" ); + byte[] publicLicKey = b64Decoder.decode( elementList.item( 0 ).getTextContent() ); + certificate = BCCrypto.createSSCertificate( publicLicKey, decryptedLicenseKey ); + + b64 = b64Encoder.encodeToString( certificate.getEncoded() ); + child = responseDoc.createElementNS( XmlUtils.ADOBE_ADEPT_NS, "licenseCertificate" ); + child.setTextContent( b64 ); + credentials.appendChild( child ); + + return XmlUtils.docToString( responseDoc, false ); + } + + private void testEncryption( byte[] data, String pkcs12, String alias, byte[] key ) + throws IOException, GeneralSecurityException + { + Base64.Decoder decoder = Base64.getDecoder(); + byte[] pkcs12Data = decoder.decode( pkcs12 ); + + ByteArrayInputStream inputStream = new ByteArrayInputStream( pkcs12Data) ; + KeyStore keyStore = KeyStore.getInstance( "PKCS12" ); + + // Load the keystore from the input stream and password + keyStore.load( inputStream, null ); + String dk = Base64.getEncoder().encodeToString( key ); + char[] keyPassword = dk.toCharArray(); + + // this keystore contains a private key entry, but may not have a public key certificate! + RSAPrivateKey rsaKey = (RSAPrivateKey) keyStore.getKey( alias, keyPassword ); + Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" ); + cipher.init( ENCRYPT_MODE, rsaKey ); + byte[] enc = cipher.doFinal( data ); + + cipher.init( DECRYPT_MODE, publicKey ); + byte[] decrypted = cipher.doFinal( enc ); + System.out.println( Base64.getEncoder().encodeToString( decrypted )); + } +} diff --git a/src/test/resources/mockServerResponses/activationInfo.xml b/src/test/resources/mockServerResponses/activationInfo.xml new file mode 100644 index 0000000..e14392d --- /dev/null +++ b/src/test/resources/mockServerResponses/activationInfo.xml @@ -0,0 +1,5 @@ + + http://localhost/adept + http://localhost/adept + MIIEsjCCA5qgAwIBAgIER2q5eDANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMRswGQYDVQQLExJEaWdpdGFsIFB1Ymxpc2hpbmcxMzAxBgNVBAMTKkFkb2JlIENvbnRlbnQgU2VydmVyIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wODAxMDkxODM3NDVaFw0xMzAxMDkxOTA3NDVaMH0xCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEbMBkGA1UECxMSRGlnaXRhbCBQdWJsaXNoaW5nMSwwKgYDVQQDEyNodHRwOi8vYWRlYWN0aXZhdGUuYWRvYmUuY29tL2FkZXB0LzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyXpCCWFh0Q3Bi1S7xf+CJfMd+cZz3HB0NknDScB1Cs8KdU0ygO7iqAgdiAdPliITkUTVEgUPvK+4yYCUderzBjq13/IrKlwEAyWeNgssJekpYgqNywo7Md1OApXzM47wVThNePNydhGYuNEEDDxzO+0JxucfhfArwnp7kIWA6q8CAwEAAaOCAbQwggGwMAsGA1UdDwQEAwIFoDBYBglghkgBhvprHgEESwxJVGhlIHByaXZhdGUga2V5IGNvcnJlc3BvbmRpbmcgdG8gdGhpcyBjZXJ0aWZpY2F0ZSBtYXkgaGF2ZSBiZWVuIGV4cG9ydGVkLjAUBgNVHSUEDTALBgkqhkiG9y8CAQQwgbIGA1UdIASBqjCBpzCBpAYJKoZIhvcvAQIDMIGWMIGTBggrBgEFBQcCAjCBhhqBg1lvdSBhcmUgbm90IHBlcm1pdHRlZCB0byB1c2UgdGhpcyBMaWNlbnNlIENlcnRpZmljYXRlIGV4Y2VwdCBhcyBwZXJtaXR0ZWQgYnkgdGhlIGxpY2Vuc2UgYWdyZWVtZW50IGFjY29tcGFueWluZyB0aGUgQWRvYmUgc29mdHdhcmUuMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Fkb2JlQ1MuY3JsMB8GA1UdIwQYMBaAFIvu8IFgyaLaHg5SwVgMBLBD94/oMB0GA1UdDgQWBBT9A+kXOPL6N57MN/zovbCGEx2+BTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQBVjUalliql3VjpLdT8si7OwPU1wQODllwlgfLH7tI/Ubq5wHDlprGtbf3jZm6tXY1qmh9mz1WnTmQHU3uPk8qgpihrpx4HJTjhAhLP0CXU1rd/t5whwhgT1lYfw77RRG2lZ5BzpHb/XjnY5yc3awd6F3Dli6kTkbcPyOCNoXlW4wiF+jkL+jBImY8xo2EewiJioY/iTYZH5HF+PjHF5mffANiLK/Q43l4f0YF8UagTfAJkD3iQV9lrTOWxKBgpfdyvekGqFCDq9AKzfpllqctxsC29W5bXU0cVYzf6Bj5ALs6tyi7r5fsIPSwszH/i4ixsuD0qccIgTXCwMNbt9zQu + diff --git a/src/test/resources/mockServerResponses/authServiceInfo.xml b/src/test/resources/mockServerResponses/authServiceInfo.xml new file mode 100644 index 0000000..714f76f --- /dev/null +++ b/src/test/resources/mockServerResponses/authServiceInfo.xml @@ -0,0 +1 @@ + http://localhost/adept MIIEYDCCA0igAwIBAgIER2q5eTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMRswGQYDVQQLExJEaWdpdGFsIFB1Ymxpc2hpbmcxMzAxBgNVBAMTKkFkb2JlIENvbnRlbnQgU2VydmVyIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wODAxMDkxODQzNDNaFw0xODAxMzEwODAwMDBaMHwxKzApBgNVBAMTImh0dHA6Ly9hZGVhY3RpdmF0ZS5hZG9iZS5jb20vYWRlcHQxGzAZBgNVBAsTEkRpZ2l0YWwgUHVibGlzaGluZzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZAxpzOZ7N38ZGlQjfMY/lfu4Ta4xK3FRm069VwdqGZIwrfTTRxnLE4A9i1X00BnNk/5z7C0pQX435ylIEQPxIFBKTH+ip5rfDNh/Iu6cIlB0N4I/t7Pac8cIDwbc9HxcGTvXg3BFqPjaGVbmVZmoUtSVOsphdA43sZc6j1iFfOQIDAQABo4IBYzCCAV8wEgYDVR0TAQH/BAgwBgEB/wIBATAUBgNVHSUEDTALBgkqhkiG9y8CAQUwgbIGA1UdIASBqjCBpzCBpAYJKoZIhvcvAQIDMIGWMIGTBggrBgEFBQcCAjCBhhqBg1lvdSBhcmUgbm90IHBlcm1pdHRlZCB0byB1c2UgdGhpcyBMaWNlbnNlIENlcnRpZmljYXRlIGV4Y2VwdCBhcyBwZXJtaXR0ZWQgYnkgdGhlIGxpY2Vuc2UgYWdyZWVtZW50IGFjY29tcGFueWluZyB0aGUgQWRvYmUgc29mdHdhcmUuMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Fkb2JlQ1MuY3JsMAsGA1UdDwQEAwIBBjAfBgNVHSMEGDAWgBSL7vCBYMmi2h4OUsFYDASwQ/eP6DAdBgNVHQ4EFgQU9RP19K+lzF03he+0T47hCVkPhdAwDQYJKoZIhvcNAQEFBQADggEBAJoqOj+bUa+bDYyOSljs6SVzWH2BN2ylIeZKpTQYEo7jA62tRqW/rBZcNIgCudFvEYa7vH8lHhvQak1s95g+NaNidb5tpgbS8Q7/XTyEGS/4Q2HYWHD/8ydKFROGbMhfxpdJgkgn21mb7dbsfq5AZVGS3M4PP1xrMDYm50+Sip9QIm1RJuSaKivDa/piA5p8/cv6w44YBefLzGUN674Y7WS5u656MjdyJsN/7Oup+12fHGiye5QS5mToujGd6LpU80gfhNxhrphASiEBYQ/BUhWjHkSi0j4WOiGvGpT1Xvntcj0rf6XV6lNrOddOYUL+KdC1uDIe8PUI+naKI+nWgrs= Adobe ID anonymous CloudLibrary Alcatel-Lucent Books-A-Million Datalogics Google MediaCorp Nokia RM Books UTPL Aust Copyright Sattva Digital Saraiva eBookPass Courseload axisReader Rex Book Store NYPL CloudLibrarySC Booktopia eCampus.com OverDrive PocketBook Librify DIDGIO Campus eBookstore Odilo Kortext Five Senses Education Jamalon UnboundLib FiveSensesAuth KortextQA Freading LibraryReader Fundacao Getulio Vargas Kortext (North America 2) xebook Queens Library The Palace Project We Green Solutions \ No newline at end of file