Java tutorial
package ch.cyberduck.core.aquaticprime; /* * Copyright (c) 2002-2011 David Kocher. All rights reserved. * * http://cyberduck.ch/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Bug fixes, suggestions and comments should be sent to: * dkocher@cyberduck.ch */ import ch.cyberduck.core.PathFilter; import ch.cyberduck.core.Preferences; import ch.cyberduck.core.i18n.Locale; import ch.cyberduck.core.local.Local; import ch.cyberduck.core.local.LocalFactory; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.DEREncodable; import org.bouncycastle.asn1.DERInteger; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.jce.PKCS7SignedData; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.FileInputStream; import java.net.NetworkInterface; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.Security; import java.util.Arrays; import java.util.Enumeration; /** * @version $Id: Receipt.java 10696 2012-12-21 14:41:05Z dkocher $ */ public class Receipt extends AbstractLicense { private static Logger log = Logger.getLogger(Receipt.class); public static void register() { LicenseFactory.addFactory(Factory.NATIVE_PLATFORM, new Factory()); } /** * */ private static final int APPSTORE_VALIDATION_FAILURE = 173; private static class Factory extends LicenseFactory { @Override protected License open(final Local file) { AbstractLicense l = new Receipt(file); // Verify immediatly and exit if not a valid receipt if (!l.verify()) { System.exit(APPSTORE_VALIDATION_FAILURE); } else { // Copy to Application Support for users switching versions Local support = LocalFactory .createLocal(Preferences.instance().getProperty("application.support.path")); file.copy(LocalFactory.createLocal(support, l.getName() + ".cyberduckreceipt")); } return l; } @Override protected License open() { Local receipt = LocalFactory .createLocal(Preferences.instance().getProperty("application.receipt.path")); if (receipt.exists()) { for (Local key : receipt.children(new PathFilter<Local>() { @Override public boolean accept(Local file) { return "receipt".equals(file.getName()); } })) { return open(key); } } log.info("No receipt found"); System.exit(APPSTORE_VALIDATION_FAILURE); return LicenseFactory.EMPTY_LICENSE; } @Override protected License create() { return this.open(); } } private String name; /** * @param file The license key file. */ public Receipt(Local file) { super(file); } /** * Verifies the App Store Receipt * * @return False if receipt validation failed. */ @Override public boolean verify() { try { Security.addProvider(new BouncyCastleProvider()); PKCS7SignedData signature = new PKCS7SignedData( IOUtils.toByteArray(new FileInputStream(this.getFile().getAbsolute()))); signature.verify(); // For additional security, you may verify the fingerprint of the root CA and the OIDs of the // intermediate CA and signing certificate. The OID in the Certificate Policies Extension of the // intermediate CA is (1 2 840 113635 100 5 6 1), and the Marker OID of the signing certificate // is (1 2 840 113635 100 6 11 1). // Extract the receipt attributes CMSSignedData s = new CMSSignedData(new FileInputStream(this.getFile().getAbsolute())); CMSProcessable signedContent = s.getSignedContent(); byte[] originalContent = (byte[]) signedContent.getContent(); ASN1Object asn = ASN1Object.fromByteArray(originalContent); byte[] opaque = null; String bundleIdentifier = null; String bundleVersion = null; byte[] hash = null; if (asn instanceof DERSet) { // 2 Bundle identifier Interpret as an ASN.1 UTF8STRING. // 3 Application version Interpret as an ASN.1 UTF8STRING. // 4 Opaque value Interpret as a series of bytes. // 5 SHA-1 hash Interpret as a 20-byte SHA-1 digest value. DERSet set = (DERSet) asn; Enumeration enumeration = set.getObjects(); while (enumeration.hasMoreElements()) { Object next = enumeration.nextElement(); if (next instanceof DERSequence) { DERSequence sequence = (DERSequence) next; DEREncodable type = sequence.getObjectAt(0); if (type instanceof DERInteger) { if (((DERInteger) type).getValue().intValue() == 2) { DEREncodable value = sequence.getObjectAt(2); if (value instanceof DEROctetString) { bundleIdentifier = new String(((DEROctetString) value).getOctets(), "utf-8"); } } else if (((DERInteger) type).getValue().intValue() == 3) { DEREncodable value = sequence.getObjectAt(2); if (value instanceof DEROctetString) { bundleVersion = new String(((DEROctetString) value).getOctets(), "utf-8"); } } else if (((DERInteger) type).getValue().intValue() == 4) { DEREncodable value = sequence.getObjectAt(2); if (value instanceof DEROctetString) { opaque = ((DEROctetString) value).getOctets(); } } else if (((DERInteger) type).getValue().intValue() == 5) { DEREncodable value = sequence.getObjectAt(2); if (value instanceof DEROctetString) { hash = ((DEROctetString) value).getOctets(); } } } } } } else { log.error(String.format("Expected set of attributes for %s", asn)); return false; } if (!StringUtils.equals("ch.sudo.cyberduck", StringUtils.trim(bundleIdentifier))) { log.error("Bundle identifier in ASN set does not match"); return false; } if (!StringUtils.equals(Preferences.instance().getDefault("CFBundleShortVersionString"), StringUtils.trim(bundleVersion))) { log.warn("Bundle version in ASN set does not match"); } NetworkInterface en0 = NetworkInterface.getByName("en0"); if (null == en0) { // Interface is not found when link is down #fail log.warn("No network interface en0"); } else { byte[] mac = en0.getHardwareAddress(); if (null == mac) { log.error("Cannot determine MAC address"); // Continue without validation return true; } final String hex = Hex.encodeHexString(mac); if (log.isDebugEnabled()) { log.debug("Interface en0:" + hex); } // Compute the hash of the GUID MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(mac); digest.update(opaque); digest.update(bundleIdentifier.getBytes(Charset.forName("utf-8"))); byte[] result = digest.digest(); if (Arrays.equals(result, hash)) { if (log.isInfoEnabled()) { log.info(String.format("Valid receipt for GUID %s", hex)); } this.name = hex; } else { log.error(String.format("Failed verification. Hash with GUID %s does not match hash in receipt", hex)); return false; } } } catch (Exception e) { log.error("Unknown receipt validation error", e); // Shutdown if receipt is not valid return false; } // Always return true to dismiss donation prompt. return true; } @Override public boolean isReceipt() { return true; } @Override public String getValue(String property) { return Locale.localizedString("Unknown"); } @Override public String getName() { if (StringUtils.isEmpty(name)) { log.warn("Need to validate first before hash is available"); return Locale.localizedString("Unknown"); } return name; } }