Programme pour creer les fichiers ePub chiffrés, pour les tests.
This commit is contained in:
29
build.gradle
29
build.gradle
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'epub.jgourour' // Replace with your group ID
|
||||
version '1.0-SNAPSHOT' // Replace with your version
|
||||
version '0.1-SNAPSHOT' // Replace with your version
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -14,9 +14,7 @@ dependencies {
|
||||
implementation 'net.java.dev.jna:jna-platform:5.17.0'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
jar.enabled = false
|
||||
|
||||
sourceSets {
|
||||
libraries {
|
||||
@@ -29,3 +27,26 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('EncryptEPub', Jar) {
|
||||
archiveFileName = 'EncryptEPub.jar'
|
||||
manifest {
|
||||
attributes 'Main-Class': 'EncryptEPub' // Replace with your main class
|
||||
}
|
||||
from {sourceSets.main.output.classesDirs}
|
||||
dependsOn configurations.runtimeClasspath
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or INCLUDE, EXCLUDE, WARN, INHERIT
|
||||
}
|
||||
|
||||
jar.enabled = false // instead build serverJar, fullJar, i18neditorJar
|
||||
|
||||
|
||||
artifacts {
|
||||
archives EncryptEPub
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
532
src/main/java/EncryptEPub.java
Normal file
532
src/main/java/EncryptEPub.java
Normal file
@@ -0,0 +1,532 @@
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
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.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.*;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static java.util.zip.ZipEntry.STORED;
|
||||
|
||||
@Command(name = "EncryptEPub", mixinStandardHelpOptions = true,
|
||||
version = "0.1",
|
||||
description = "Adds encryption to a TPM-free epub file." )
|
||||
|
||||
public class EncryptEPub implements Runnable
|
||||
{
|
||||
@Parameters(index = "0",
|
||||
description = "Name and path of the ePub file to process.")
|
||||
private String inputFile = null;
|
||||
|
||||
@Option(names = {"-a", "--activation-file"},
|
||||
description = "an xml file containing the user's UUID and public key certificate",
|
||||
defaultValue = "activation.xml")
|
||||
private String activationFileName = "activation.xml";
|
||||
|
||||
@Option(names = {"-D", "--adept-directory"},
|
||||
description = "directory that includes the activation file (default: current working directory)",
|
||||
defaultValue = ".")
|
||||
private String adeptDir = null;
|
||||
|
||||
@Option(names = {"--win"}, description = "Read license certificate from the Windows registry in lieu of the activation file" )
|
||||
private boolean isWin = false;
|
||||
|
||||
private final Logger LOGGER = Logger.getLogger( EncryptEPub.class.getName() );
|
||||
|
||||
Path activationPath;
|
||||
|
||||
private final String resourceId = "urn:uuid:82919621-81bd-419a-880b-c52eabc22bf1";
|
||||
private final byte[] sessionKey;
|
||||
private String uuid;
|
||||
private String cert;
|
||||
|
||||
public static class AdeptNamespaceContext implements NamespaceContext
|
||||
{
|
||||
private final Document adeptDoc;
|
||||
|
||||
public AdeptNamespaceContext( Document doc )
|
||||
{
|
||||
adeptDoc = doc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespaceURI( String s )
|
||||
{
|
||||
return "http://ns.adobe.com/adept";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix( String s )
|
||||
{
|
||||
return adeptDoc.lookupPrefix( s );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> getPrefixes( String s )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSTRUCTOR
|
||||
*/
|
||||
EncryptEPub()
|
||||
{
|
||||
sessionKey = new byte[16];
|
||||
new SecureRandom().nextBytes( sessionKey );
|
||||
}
|
||||
|
||||
private String readValue( BufferedReader in )
|
||||
throws IOException
|
||||
{
|
||||
String line;
|
||||
do
|
||||
{
|
||||
line = in.readLine();
|
||||
}
|
||||
while (line != null && !line.contains( "value" ));
|
||||
if (null == line)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return line.substring( line.lastIndexOf( "REG_SZ" ) + 7 ).trim();
|
||||
}
|
||||
|
||||
|
||||
private String getRegistryEntry( String key )
|
||||
throws IOException
|
||||
{
|
||||
// 0001\0002 LicenseCertificate
|
||||
// 0001\0000 uuid
|
||||
String command = "reg query HKEY_CURRENT_USER\\Software\\Adobe\\Adept\\Activation\\" + key + " /s /v *";
|
||||
Process process = Runtime.getRuntime().exec( command );
|
||||
BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
|
||||
return readValue( reader );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// an existing input file is a requirement
|
||||
Path inputPath = Path.of( inputFile );
|
||||
if (!Files.exists( inputPath ))
|
||||
{
|
||||
String errMsg = "Error: cannot find" + inputPath.toFile().getCanonicalPath();
|
||||
System.out.println( errMsg );
|
||||
throw new RuntimeException( errMsg );
|
||||
}
|
||||
|
||||
String osName = System.getProperty( "os.name" );
|
||||
if (!osName.toLowerCase().contains( "windows" ))
|
||||
{
|
||||
isWin = false;
|
||||
}
|
||||
if (isWin)
|
||||
{
|
||||
try
|
||||
{
|
||||
uuid = getRegistryEntry( "0001\\0000" );
|
||||
uuid = uuid.substring( 9 );
|
||||
cert = getRegistryEntry( "0001\\0002" );
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
System.out.println( "Error: Adobe adept registry keys not found" );
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (null == adeptDir)
|
||||
{
|
||||
adeptDir = ".";
|
||||
}
|
||||
if (null == activationFileName)
|
||||
activationFileName = "activation.xml";
|
||||
activationPath = Paths.get( adeptDir, activationFileName );
|
||||
|
||||
// An activation file is required! If it doesn't exist we should throw an error here
|
||||
if (!Files.exists( activationPath ))
|
||||
{
|
||||
String errMsg = "Error: cannot find" + activationPath.toFile().getCanonicalPath();
|
||||
System.out.println( errMsg );
|
||||
throw new RuntimeException( errMsg );
|
||||
}
|
||||
Document activationDoc = parseActivationFile(); // Will throw a handled exception on failure.
|
||||
try
|
||||
{
|
||||
//noinspection unused -- for debugging
|
||||
String xmlString = docToString( activationDoc, true );
|
||||
}
|
||||
catch (TransformerException ignore) {}
|
||||
|
||||
XPathFactory xpf = XPathFactory.newInstance();
|
||||
XPath xpath = xpf.newXPath();
|
||||
xpath.setNamespaceContext( new AdeptNamespaceContext( activationDoc ) );
|
||||
Node node = activationDoc.getDocumentElement();
|
||||
cert = (String) xpath.evaluate("//adept:licenseCertificate", node, XPathConstants.STRING);
|
||||
uuid = (String) xpath.evaluate( "//adept:user", node, XPathConstants.STRING);
|
||||
}
|
||||
encrypt( inputPath );
|
||||
}
|
||||
catch (ParserConfigurationException | SAXException | IOException | CertificateException |
|
||||
XPathExpressionException e)
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses data using the Deflate algorithm.
|
||||
*
|
||||
* @param data The data to compress.
|
||||
* @return The compressed data.
|
||||
*/
|
||||
public static byte[] deflate( byte[] data )
|
||||
throws IOException
|
||||
{
|
||||
Deflater deflater = new Deflater( Deflater.DEFAULT_COMPRESSION, true ); // true for no headers
|
||||
deflater.setInput( data );
|
||||
deflater.finish();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream( data.length );
|
||||
byte[] buffer = new byte[1024];
|
||||
while (!deflater.finished())
|
||||
{
|
||||
int count = deflater.deflate( buffer );
|
||||
baos.write( buffer, 0, count );
|
||||
}
|
||||
baos.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private Document parseActivationFile()
|
||||
throws ParserConfigurationException, IOException, SAXException
|
||||
{
|
||||
LOGGER.log( Level.FINE, "DEBUG: Parse activation file " + activationPath );
|
||||
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
docFactory.setNamespaceAware( true ); // Important for XPath with namespaces
|
||||
DocumentBuilder docBuilder;
|
||||
|
||||
docBuilder = docFactory.newDocumentBuilder();
|
||||
Document doc = docBuilder.parse( activationPath.toFile() );
|
||||
doc.getDocumentElement().normalize();
|
||||
return doc;
|
||||
}
|
||||
|
||||
private Element createEncryptedDataNode( Document encryption, String URI )
|
||||
{
|
||||
Element dataNode = encryption.createElement( "EncryptedData" );
|
||||
dataNode.setAttribute( "xmlns", "http://www.w3.org/2001/04/xmlenc#" );
|
||||
Element el = encryption.createElement( "EncryptionMethod" );
|
||||
el.setAttribute( "Algorithm", "http://www.w3.org/2001/04/xmlenc#aes128-cbc" );
|
||||
dataNode.appendChild( el );
|
||||
el = encryption.createElement( "KeyInfo" );
|
||||
el.setAttribute( "xmlns", "http://www.w3.org/2000/09/xmldsig#" );
|
||||
dataNode.appendChild( el );
|
||||
Element child = encryption.createElement( "resource" );
|
||||
child.setAttribute( "xmlns", "http://ns.adobe.com/adept" );
|
||||
child.setTextContent( resourceId );
|
||||
el.appendChild( child );
|
||||
el = encryption.createElement( "CipherData" );
|
||||
dataNode.appendChild( el );
|
||||
child = encryption.createElement( "CipherReference" );
|
||||
child.setAttribute( "URI", URI );
|
||||
el.appendChild( child );
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
private boolean isInSpine( String idref, NodeList spine )
|
||||
{
|
||||
if (null == idref || idref.isBlank())
|
||||
return false;
|
||||
for (int i = 0; i < spine.getLength(); i++)
|
||||
{
|
||||
String attr = ((Element) spine.item( i )).getAttribute( "idref" );
|
||||
if (attr.equals( idref )) // attr will never be null
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getManifestedId( String name, NodeList manifest )
|
||||
{
|
||||
for (int i = 0; i < manifest.getLength(); i++)
|
||||
{
|
||||
Node node = manifest.item( i );
|
||||
if (node instanceof Element)
|
||||
{
|
||||
String attr = ((Element) node).getAttribute( "href" );
|
||||
if (name.equals( attr ))
|
||||
{
|
||||
return ((Element) node).getAttribute( "id" );
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Document createRights()
|
||||
throws ParserConfigurationException, NoSuchPaddingException, IllegalBlockSizeException,
|
||||
CertificateException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException
|
||||
{
|
||||
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
Document rights = documentBuilder.newDocument();
|
||||
Element docRoot = rights.createElement( "adept:rights" );
|
||||
docRoot.setAttribute( "xmlns:adept", "http://ns.adobe.com/adept" );
|
||||
rights.appendChild( docRoot );
|
||||
Element token = rights.createElement( "licenseToken" );
|
||||
token.setAttribute( "xmlns", "http://ns.adobe.com/adept" );
|
||||
docRoot.appendChild( token );
|
||||
Element el = rights.createElement( "user" );
|
||||
el.setTextContent( uuid );
|
||||
token.appendChild( el );
|
||||
el = rights.createElement( "resource" );
|
||||
el.setTextContent( resourceId );
|
||||
token.appendChild( el );
|
||||
el = rights.createElement( "encryptedKey" );
|
||||
token.appendChild( el );
|
||||
|
||||
// Encrypt the session key with the user's public key certificate
|
||||
String encoded = encryptSessionKey( sessionKey, cert );
|
||||
el.setTextContent( encoded );
|
||||
el.setAttribute( "keyInfo", "user" );
|
||||
return rights;
|
||||
}
|
||||
|
||||
private void encrypt( Path inputFile )
|
||||
throws ParserConfigurationException, SAXException, IOException, CertificateException,
|
||||
XPathExpressionException
|
||||
{
|
||||
String extPattern = "(?<!^)[.].*"; // + (removeAllExtensions ? ".*" : "[^.]*$");
|
||||
String outFileName = inputFile.toString().replaceAll(extPattern, "");
|
||||
File outputFile = new File( outFileName + "-enc.epub" );
|
||||
Files.deleteIfExists( outputFile.toPath() );
|
||||
|
||||
System.out.println("Attempting to convert " + inputFile + " to " + outputFile );
|
||||
|
||||
try (ZipFile zf = new ZipFile( inputFile.toFile() );
|
||||
FileOutputStream fos = new FileOutputStream( outputFile );
|
||||
ZipOutputStream zipOut = new ZipOutputStream( fos ))
|
||||
{
|
||||
// get the container and parse it.
|
||||
ZipEntry entry = zf.getEntry( "META-INF/container.xml" );
|
||||
DocumentBuilderFactory docFac = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder documentBuilder = docFac.newDocumentBuilder();
|
||||
Document container = documentBuilder.parse( zf.getInputStream( entry ) );
|
||||
String xmlString; // = XmlUtils.docToString( container, true );
|
||||
|
||||
NodeList roots = container.getElementsByTagName( "rootfile" );
|
||||
String opfFile = ((Element) roots.item( 0 )).getAttribute( "full-path" );
|
||||
entry = zf.getEntry( opfFile );
|
||||
container = documentBuilder.parse( zf.getInputStream( entry ) );
|
||||
Element manifest = (Element) container.getElementsByTagName( "manifest" ).item( 0 );
|
||||
NodeList spines = container.getElementsByTagName( "spine" ); // there can be only one
|
||||
NodeList refList = ((Element) spines.item( 0 )).getElementsByTagName( "itemref" );
|
||||
NodeList itemList = manifest.getElementsByTagName( "item" );
|
||||
|
||||
// Read entries from the epub file, encrypting the elements referenced in the spine
|
||||
CRC32 checksum = new CRC32();
|
||||
ZipEntry newEntry;
|
||||
Document encryption = null;
|
||||
Document rights = null;
|
||||
int bufSize = 1024;
|
||||
byte[] buffer = new byte[bufSize];
|
||||
final Enumeration<? extends ZipEntry> entries = zf.entries();
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
entry = entries.nextElement();
|
||||
// find the file in the manifest, get its id then check that id in the spine
|
||||
String idref = getManifestedId( entry.getName(), itemList );
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
byte[] uncompressed;
|
||||
try (InputStream is = zf.getInputStream( entry ))
|
||||
{
|
||||
int length;
|
||||
while ((length = is.read( buffer )) >= 0)
|
||||
{
|
||||
os.write( buffer, 0, length );
|
||||
}
|
||||
uncompressed = os.toByteArray();
|
||||
}
|
||||
boolean shouldEncrypt = isInSpine( idref, refList );
|
||||
|
||||
if (!shouldEncrypt)
|
||||
{
|
||||
// not an encrypted file, just copy it to the output
|
||||
newEntry = new ZipEntry( entry.getName() );
|
||||
zipOut.putNextEntry( newEntry );
|
||||
for (int length = uncompressed.length, offset = 0;
|
||||
length > offset;
|
||||
offset += 1024)
|
||||
{
|
||||
zipOut.write( uncompressed, offset, Math.min( 1024, length - offset ) );
|
||||
}
|
||||
}
|
||||
else // Encrypt the entry and STORE it in the new zip file
|
||||
{
|
||||
// Build the encryption and rights XML documents if they do not yet exist
|
||||
if (null == encryption)
|
||||
{
|
||||
encryption = documentBuilder.newDocument();
|
||||
Element docRoot = encryption.createElement( "encryption" );
|
||||
docRoot.setAttribute( "xmlns", "urn:oasis:names:tc:opendocument:xmlns:container" );
|
||||
encryption.appendChild( docRoot );
|
||||
|
||||
// create rights document.
|
||||
rights = createRights();
|
||||
}
|
||||
byte[] compressedData = deflate( uncompressed );
|
||||
|
||||
// now encrypt the data with the aes session key
|
||||
Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" );
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec( sessionKey, "AES" );
|
||||
cipher.init( Cipher.ENCRYPT_MODE, secretKeySpec ); // let cipher create the initialization vector for us
|
||||
byte[] encryptedPayload = cipher.doFinal( compressedData );
|
||||
byte[] initVector = cipher.getIV();
|
||||
|
||||
// Combine IV and the actual encrypted payload
|
||||
int encryptedLength = encryptedPayload.length;
|
||||
byte[] result = new byte[16 + encryptedLength];
|
||||
arraycopy( initVector, 0, result, 0, 16 ); // Copy IV
|
||||
arraycopy( encryptedPayload, 0, result, 16, encryptedLength ); // Copy encrypted payload
|
||||
|
||||
// store the encrypted payload for testing purposes
|
||||
String parent = outputFile.getCanonicalFile().getParent();
|
||||
Path file = Paths.get(null == parent ? "" : parent , entry.getName() + ".b64" );
|
||||
Files.write( file, Base64.getEncoder().encode( result ) );
|
||||
|
||||
// Store the result in the zip file
|
||||
newEntry = new ZipEntry( entry.getName() );
|
||||
newEntry.setMethod( STORED );
|
||||
checksum.reset();
|
||||
checksum.update( result, 0, result.length );
|
||||
newEntry.setCrc( checksum.getValue() );
|
||||
newEntry.setSize( result.length );
|
||||
newEntry.setCompressedSize( result.length );
|
||||
zipOut.putNextEntry( newEntry );
|
||||
zipOut.write( result, 0, result.length );
|
||||
|
||||
// Add this entry to encryption.xml
|
||||
Element dataNode = createEncryptedDataNode( encryption, newEntry.getName() );
|
||||
encryption.getDocumentElement().appendChild( dataNode );
|
||||
}
|
||||
} // end while (entries.hasMoreElements())
|
||||
// if there's an encryption document, add it to the output file
|
||||
if (null != encryption)
|
||||
{
|
||||
xmlString = docToString( encryption, true );
|
||||
newEntry = new ZipEntry( "META-INF/encryption.xml" );
|
||||
zipOut.putNextEntry( newEntry );
|
||||
zipOut.write( xmlString.getBytes( StandardCharsets.UTF_8 ) );
|
||||
}
|
||||
// do the same thing with the abbreviated rights.xml.
|
||||
if (null != rights)
|
||||
{
|
||||
xmlString = docToString( rights, false );
|
||||
newEntry = new ZipEntry( "META-INF/rights.xml" );
|
||||
zipOut.putNextEntry( newEntry );
|
||||
zipOut.write( xmlString.getBytes( StandardCharsets.UTF_8 ));
|
||||
}
|
||||
} // end try with resources.
|
||||
catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
||||
IllegalBlockSizeException | BadPaddingException | TransformerException e)
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
System.out.println( inputFile + " encrypted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an XML Document to a formatted String.
|
||||
*
|
||||
* @param doc The XML Document to convert.
|
||||
* @param omitXmlDeclaration a flag to suppress the xml declaration at the beginning of the document
|
||||
* @return A string representation of the XML.
|
||||
*/
|
||||
private static String docToString( Document doc, boolean omitXmlDeclaration )
|
||||
throws TransformerException
|
||||
{
|
||||
System.setProperty( "line.separator", "\n" );
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
|
||||
transformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2" );
|
||||
transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); // Ensure UTF-8 output
|
||||
if (omitXmlDeclaration)
|
||||
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
|
||||
StringWriter writer = new StringWriter();
|
||||
transformer.transform( new DOMSource( doc ), new StreamResult( writer ) );
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
String xmlStr = writer.toString();
|
||||
// Java's XML Transformer has a tendency to create unnecessary blonk lines; use a regex function to remove them.
|
||||
// return collapseLines( xmlStr );
|
||||
return xmlStr;
|
||||
}
|
||||
|
||||
|
||||
String encryptSessionKey( byte[] sessionKey, String cert )
|
||||
throws NoSuchPaddingException, NoSuchAlgorithmException, CertificateException, InvalidKeyException,
|
||||
IllegalBlockSizeException, BadPaddingException
|
||||
{
|
||||
byte[] certBytes = Base64.getDecoder().decode( cert ); // my public key certificate
|
||||
CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
|
||||
X509Certificate x509AuthCertificate = (X509Certificate) cf.generateCertificate(
|
||||
new ByteArrayInputStream( certBytes ) );
|
||||
|
||||
// encrypt with the certificate
|
||||
Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" );
|
||||
cipher.init( Cipher.ENCRYPT_MODE, x509AuthCertificate );
|
||||
byte[] enc = cipher.doFinal( sessionKey ); // The session key encrypted with the public key. Should be 128 bytes
|
||||
return Base64.getEncoder().encodeToString( enc );
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
int exitCode = new CommandLine( new EncryptEPub() ).execute( args );
|
||||
System.exit( exitCode );
|
||||
}
|
||||
}
|
||||
101
src/main/resources/EncryptEPub-fr.md
Normal file
101
src/main/resources/EncryptEPub-fr.md
Normal file
@@ -0,0 +1,101 @@
|
||||
#### Aperçu
|
||||
|
||||
Pendant la traduction de libjourou du C++ vers Java, j'ai réalisé que j'avais
|
||||
besoin d'un ou de plusieurs fichiers ePub chiffrés pour mes tests. J'ai donc
|
||||
écrit EncryptEPub pour accomplir cette tâche. EncryptEPub.jar prend un
|
||||
fichier ePub non chiffré et le transforme en un fichier ePub chiffré.
|
||||
|
||||
Il commence par créer une clé symétrique AES de 128 bits (16 octets). Ensuite,
|
||||
en utilisant soit un fichier "activation.xml" compatible avec libgourou, soit
|
||||
les données du registre Windows, il chiffre cette clé symétrique avec la clé
|
||||
publique provenant du certificat de licence X509 de l'utilisateur
|
||||
("adept:licenseCertificate"). Il encode ensuite cette clé en base64 et, avec
|
||||
l'UUID de l'utilisateur et une UUID constante, il crée un document XML minimal
|
||||
"rights".
|
||||
|
||||
Il lit ensuite l'entrée '.opf' du fichier ePub original, telle qu'enregistrée
|
||||
dans l'entrée ZIP "META-INF/container.xml", et analyse son nœud XML <spine>.
|
||||
Il parcourt ensuite l'intégralité du fichier ePub original, copiant les entrées
|
||||
du fichier d'origine vers un nouveau fichier d'archive ZIP de sortie. Il
|
||||
commence par extraire chaque entrée et la décompresse en utilisant la méthode
|
||||
'Inflate'. Si l'entrée n'est pas référencée dans le nœud <spine> du fichier
|
||||
'.opf', il l'ajoute à la nouvelle archive en utilisant la méthode 'Deflate'
|
||||
(compression). Si elle est référencée, il la compresse en utilisant la méthode
|
||||
'Deflate' sans l'ajouter, chiffre les données compressées avec la clé symétrique
|
||||
AES, puis ajoute les données chiffrées à la nouvelle archive ePub en utilisant
|
||||
la méthode 'Store' (sans compression). Pour chaque fichier chiffré, il crée
|
||||
également un élément <EncryptedData> dans un document XML d'encryption,
|
||||
référençant le nom de l'entrée et spécifiant l'algorithme de chiffrement
|
||||
(AES-128-CBC).
|
||||
|
||||
#### Par example:
|
||||
|
||||
`<EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#">`
|
||||
 `<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>`
|
||||
 `<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">`
|
||||
  `<resource xmlns="http://ns.adobe.com/adept">urn:uuid:82919621-81bd-419a-880b-c52eabc22bf1</resource>`
|
||||
 `</KeyInfo>`
|
||||
 `<CipherData>`
|
||||
  `<CipherReference URI="toc.html"/>`
|
||||
 `</CipherData>`
|
||||
`</EncryptedData>`
|
||||
|
||||
Enfin, les documents XML "rights" et "encryption" sont convertis en fichiers
|
||||
XML et ajoutés à l'ePub de sortie. Cette archive obtenue est le fichier ePub
|
||||
fortement chiffré, complet avec les métadonnées TPM nécessaire, le rendant
|
||||
prêt pour les tests.
|
||||
|
||||
#### Format d'utilisation:
|
||||
|
||||
> java -jar EncryptEPub.jar \[-hV\] \[--win\] \[-a=<nomFichierActivation>\]
|
||||
> \[-D=<dossierAdept>\] <fichierEPub>
|
||||
|
||||
#### Options de Ligne de Commande
|
||||
|
||||
> ##### "-h" ou "--help"
|
||||
>
|
||||
> Affiche l'aide du programme (instructions d'utilisation) puis se termine.
|
||||
>
|
||||
> ##### "-V" ou "--version"
|
||||
>
|
||||
> Affiche les informations de version puis se termine.
|
||||
>
|
||||
> ##### "<fichierEPub>"
|
||||
>
|
||||
> Le nom et le chemin du fichier ePub à traiter. S'il n'est pas spécifié, ou
|
||||
> s'il n'existe pas, le programme affichera un message d'erreur et se terminera.
|
||||
>
|
||||
> ##### "-a" ou "--activation-file" \[<nomFichierActivation>\]
|
||||
>
|
||||
> Spécifie le nom du fichier XML contenant l'UUID de l'utilisateur et son
|
||||
> certificat de clé publique, dans le même format que celui utilisé par le
|
||||
> fichier activation.xml de libgourou. Seuls les éléments <adept:user> et
|
||||
> <adept:privateLicenseKey> sont requis. Si ce paramètre n'est pas fourni,
|
||||
> le nom "activation.xml" sera utilisé par défaut.
|
||||
>
|
||||
> ##### "-D" ou "--adept-directory" \[<cheminDossier>\]
|
||||
>
|
||||
> Spécifie le répertoire qui contient le fichier d'activation. S'il n'est pas
|
||||
> spécifié, le répertoire de travail actuel sera utilisé.
|
||||
>
|
||||
> ##### "--win"
|
||||
>
|
||||
> Lit le certificat de licence et l'UUID de l'utilisateur directement du
|
||||
> Registre Windows au lieu d'utiliser le fichier d'activation. Si cette option
|
||||
> est spécifiée _et_ que le programme est exécuté sur Windows, il tentera de
|
||||
> lire l'UUID de l'utilisateur et le certificat de clé publique X509 à partir
|
||||
> des clés de Registre :
|
||||
>
|
||||
> HKEY_CURRENT_USER\Software\Adobe\Adept\Activation\0001\0000, et
|
||||
> HKEY_CURRENT_USER\Software\Adobe\Adept\Activation\0001\0002
|
||||
>
|
||||
> Ces clés doivent exister si Adobe Digital Editions a été installé et activé
|
||||
> sur la même machine que celle où EncryptEPub est exécuté.
|
||||
> ##### Gestion des erreurs
|
||||
>
|
||||
> Si l'option "--win" est spécifiée, et que les clés de Registre ne peuvent
|
||||
> être trouvées, le programme affichera un message d'erreur et se terminera.
|
||||
>
|
||||
> Si l'option "--win" n'est pas spécifiée, et que le fichier d'activation ne
|
||||
> peut être trouvé, le programme affichera un message d'erreur et se
|
||||
> terminera.
|
||||
Reference in New Issue
Block a user