Tests unitaires pour le fichier AdeptActivate.java et ses classes associées. La classe AdeptActivateCli.java n'est pas inclue.
This commit is contained in:
179
src/test/java/AdeptActivateTest.java
Normal file
179
src/test/java/AdeptActivateTest.java
Normal file
@@ -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<String, String> 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 ));
|
||||
}
|
||||
}
|
||||
93
src/test/java/common/BCCrypto.java
Normal file
93
src/test/java/common/BCCrypto.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
300
src/test/java/common/MockHttpUtils.java
Normal file
300
src/test/java/common/MockHttpUtils.java
Normal file
@@ -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<String, String> 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<String,String>(), 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 ));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<activationServiceInfo xmlns="http://ns.adobe.com/adept">
|
||||
<authURL>http://localhost/adept</authURL>
|
||||
<userInfoURL>http://localhost/adept</userInfoURL>
|
||||
<certificate>MIIEsjCCA5qgAwIBAgIER2q5eDANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCVVMxIzAhBgNVBAoTGkFkb2JlIFN5c3RlbXMgSW5jb3Jwb3JhdGVkMRswGQYDVQQLExJEaWdpdGFsIFB1Ymxpc2hpbmcxMzAxBgNVBAMTKkFkb2JlIENvbnRlbnQgU2VydmVyIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wODAxMDkxODM3NDVaFw0xMzAxMDkxOTA3NDVaMH0xCzAJBgNVBAYTAlVTMSMwIQYDVQQKExpBZG9iZSBTeXN0ZW1zIEluY29ycG9yYXRlZDEbMBkGA1UECxMSRGlnaXRhbCBQdWJsaXNoaW5nMSwwKgYDVQQDEyNodHRwOi8vYWRlYWN0aXZhdGUuYWRvYmUuY29tL2FkZXB0LzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyXpCCWFh0Q3Bi1S7xf+CJfMd+cZz3HB0NknDScB1Cs8KdU0ygO7iqAgdiAdPliITkUTVEgUPvK+4yYCUderzBjq13/IrKlwEAyWeNgssJekpYgqNywo7Md1OApXzM47wVThNePNydhGYuNEEDDxzO+0JxucfhfArwnp7kIWA6q8CAwEAAaOCAbQwggGwMAsGA1UdDwQEAwIFoDBYBglghkgBhvprHgEESwxJVGhlIHByaXZhdGUga2V5IGNvcnJlc3BvbmRpbmcgdG8gdGhpcyBjZXJ0aWZpY2F0ZSBtYXkgaGF2ZSBiZWVuIGV4cG9ydGVkLjAUBgNVHSUEDTALBgkqhkiG9y8CAQQwgbIGA1UdIASBqjCBpzCBpAYJKoZIhvcvAQIDMIGWMIGTBggrBgEFBQcCAjCBhhqBg1lvdSBhcmUgbm90IHBlcm1pdHRlZCB0byB1c2UgdGhpcyBMaWNlbnNlIENlcnRpZmljYXRlIGV4Y2VwdCBhcyBwZXJtaXR0ZWQgYnkgdGhlIGxpY2Vuc2UgYWdyZWVtZW50IGFjY29tcGFueWluZyB0aGUgQWRvYmUgc29mdHdhcmUuMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwuYWRvYmUuY29tL2Fkb2JlQ1MuY3JsMB8GA1UdIwQYMBaAFIvu8IFgyaLaHg5SwVgMBLBD94/oMB0GA1UdDgQWBBT9A+kXOPL6N57MN/zovbCGEx2+BTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQBVjUalliql3VjpLdT8si7OwPU1wQODllwlgfLH7tI/Ubq5wHDlprGtbf3jZm6tXY1qmh9mz1WnTmQHU3uPk8qgpihrpx4HJTjhAhLP0CXU1rd/t5whwhgT1lYfw77RRG2lZ5BzpHb/XjnY5yc3awd6F3Dli6kTkbcPyOCNoXlW4wiF+jkL+jBImY8xo2EewiJioY/iTYZH5HF+PjHF5mffANiLK/Q43l4f0YF8UagTfAJkD3iQV9lrTOWxKBgpfdyvekGqFCDq9AKzfpllqctxsC29W5bXU0cVYzf6Bj5ALs6tyi7r5fsIPSwszH/i4ixsuD0qccIgTXCwMNbt9zQu</certificate>
|
||||
</activationServiceInfo>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user