package com.googlecode.mp4parser.authoring.tracks; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.Box; import com.coremedia.iso.boxes.OriginalFormatBox; import com.coremedia.iso.boxes.SampleDescriptionBox; import com.coremedia.iso.boxes.SchemeTypeBox; import com.coremedia.iso.boxes.h264.AvcConfigurationBox; import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry; import com.googlecode.mp4parser.MemoryDataSourceImpl; import com.googlecode.mp4parser.authoring.*; import com.googlecode.mp4parser.boxes.cenc.CencSampleAuxiliaryDataFormat; import com.googlecode.mp4parser.util.Path; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.AbstractList; import java.util.LinkedList; import java.util.List; import static com.googlecode.mp4parser.util.CastUtils.l2i; public class CencDecryptingTrackImpl extends AbstractTrack { CencDecryptingSampleList samples; Track original; public CencDecryptingTrackImpl(CencEncyprtedTrack original, SecretKey key) { this.original = original; SchemeTypeBox schm = (SchemeTypeBox) Path.getPath(original.getSampleDescriptionBox(), "enc./sinf/schm"); if (!"cenc".equals(schm.getSchemeType())) { throw new RuntimeException("You can only use the CencDecryptingTrackImpl with CENC encrypted tracks"); } samples = new CencDecryptingSampleList(key, original.getSamples(), original, original.getSampleEncryptionEntries()); } public void close() throws IOException { original.close(); } public long[] getSyncSamples() { return original.getSyncSamples(); } public SampleDescriptionBox getSampleDescriptionBox() { OriginalFormatBox frma = (OriginalFormatBox) Path.getPath(original.getSampleDescriptionBox(), "enc./sinf/frma"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); SampleDescriptionBox stsd; try { original.getSampleDescriptionBox().getBox(Channels.newChannel(baos)); stsd = (SampleDescriptionBox) new IsoFile(new MemoryDataSourceImpl(baos.toByteArray())).getBoxes().get(0); } catch (IOException e) { throw new RuntimeException("Dumping stsd to memory failed"); } if (stsd.getSampleEntry() instanceof AudioSampleEntry) { ((AudioSampleEntry) stsd.getSampleEntry()).setType(frma.getDataFormat()); } else if (stsd.getSampleEntry() instanceof VisualSampleEntry) { ((VisualSampleEntry) stsd.getSampleEntry()).setType(frma.getDataFormat()); } else { throw new RuntimeException("I don't know " + stsd.getSampleEntry().getType()); } List<Box> nuBoxes = new LinkedList<Box>(); for (Box box : stsd.getSampleEntry().getBoxes()) { if (!box.getType().equals("sinf")) { nuBoxes.add(box); } } stsd.getSampleEntry().setBoxes(nuBoxes); return stsd; } public long[] getSampleDurations() { return original.getSampleDurations(); } public TrackMetaData getTrackMetaData() { return original.getTrackMetaData(); } public String getHandler() { return original.getHandler(); } public List<Sample> getSamples() { return samples; } public Box getMediaHeaderBox() { return original.getMediaHeaderBox(); } public class CencDecryptingSampleList extends AbstractList<Sample> { List<CencSampleAuxiliaryDataFormat> sencInfo; AvcConfigurationBox avcC = null; SecretKey secretKey; List<Sample> parent; public CencDecryptingSampleList(SecretKey secretKey, List<Sample> parent, Track track, List<CencSampleAuxiliaryDataFormat> sencInfo) { this.sencInfo = sencInfo; this.secretKey = secretKey; this.parent = parent; List<Box> boxes = track.getSampleDescriptionBox().getSampleEntry().getBoxes(); for (Box box : boxes) { if (box instanceof AvcConfigurationBox) { avcC = (AvcConfigurationBox) box; } } } Cipher getCipher(SecretKey sk, byte[] iv) { byte[] fullIv = new byte[16]; System.arraycopy(iv, 0, fullIv, 0, iv.length); // The IV try { Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, sk, new IvParameterSpec(fullIv)); return cipher; } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (NoSuchPaddingException e) { throw new RuntimeException(e); } } @Override public Sample get(int index) { Sample encSample = parent.get(index); final ByteBuffer encSampleBuffer = encSample.asByteBuffer(); encSampleBuffer.rewind(); final ByteBuffer decSampleBuffer = ByteBuffer.allocate(encSampleBuffer.limit()); final CencSampleAuxiliaryDataFormat sencEntry = sencInfo.get(index); Cipher cipher = getCipher(secretKey, sencEntry.iv); try { if (avcC != null) { for (CencSampleAuxiliaryDataFormat.Pair pair : sencEntry.pairs) { final int clearBytes = pair.clear(); final int encrypted = l2i(pair.encrypted()); byte[] clears = new byte[clearBytes]; encSampleBuffer.get(clears); decSampleBuffer.put(clears); if (encrypted > 0) { byte[] encs = new byte[encrypted]; encSampleBuffer.get(encs); final byte[] decr = cipher.update(encs); decSampleBuffer.put(decr); } } if (encSampleBuffer.remaining() > 0) { System.err.println("Decrypted sample but still data remaining: " + encSample.getSize()); } decSampleBuffer.put(cipher.doFinal()); } else { byte[] fullyEncryptedSample = new byte[encSampleBuffer.limit()]; encSampleBuffer.get(fullyEncryptedSample); decSampleBuffer.put(cipher.doFinal(fullyEncryptedSample)); } encSampleBuffer.rewind(); } catch (IllegalBlockSizeException e) { throw new RuntimeException(e); } catch (BadPaddingException e) { throw new RuntimeException(e); } decSampleBuffer.rewind(); return new SampleImpl(decSampleBuffer); } @Override public int size() { return parent.size(); } } }