001/* 002 * Copyright 2023 Emmanuel Bourg 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package net.jsign.navx; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.ByteBuffer; 022import java.nio.ByteOrder; 023import java.nio.channels.SeekableByteChannel; 024import java.nio.file.Files; 025import java.nio.file.StandardOpenOption; 026import java.security.MessageDigest; 027import java.util.List; 028 029import org.bouncycastle.asn1.ASN1Object; 030import org.bouncycastle.asn1.DERNull; 031import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 032import org.bouncycastle.asn1.x509.DigestInfo; 033import org.bouncycastle.cms.CMSSignedData; 034 035import net.jsign.DigestAlgorithm; 036import net.jsign.Signable; 037import net.jsign.SignatureUtils; 038import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers; 039import net.jsign.asn1.authenticode.SpcAttributeTypeAndOptionalValue; 040import net.jsign.asn1.authenticode.SpcIndirectDataContent; 041import net.jsign.asn1.authenticode.SpcSipInfo; 042import net.jsign.asn1.authenticode.SpcUuid; 043 044import static net.jsign.ChannelUtils.*; 045 046/** 047 * Microsoft Dynamics 365 extension package (NAVX) 048 * 049 * @author Emmanuel Bourg 050 * @since 6.0 051 */ 052public class NAVXFile implements Signable { 053 054 /** The channel used for in-memory signing */ 055 private final SeekableByteChannel channel; 056 057 /** The underlying file */ 058 private File file; 059 060 /** The file header */ 061 private final NAVXHeader header = new NAVXHeader(); 062 063 /** 064 * Tells if the specified file is a NAVX file. 065 * 066 * @param file the file to check 067 * @return <code>true</code> if the file is a NAVX file, <code>false</code> otherwise 068 * @throws IOException if an I/O error occurs 069 */ 070 public static boolean isNAVXFile(File file) throws IOException { 071 if (file.length() < NAVXHeader.SIZE) { 072 return false; 073 } 074 075 // read the signature 076 try (SeekableByteChannel channel = Files.newByteChannel(file.toPath(), StandardOpenOption.READ)) { 077 ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); 078 channel.read(buffer); 079 buffer.flip(); 080 return buffer.getInt() == NAVXHeader.SIGNATURE; 081 } 082 } 083 084 /** 085 * Create a NAVXFile from the specified file. 086 * 087 * @param file the file to open 088 * @throws IOException if an I/O error occurs 089 */ 090 public NAVXFile(File file) throws IOException { 091 this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)); 092 } 093 094 /** 095 * Create a NAVXFile from the specified channel. 096 * 097 * @param channel the channel to read the file from 098 * @throws IOException if an I/O error occurs 099 */ 100 public NAVXFile(SeekableByteChannel channel) throws IOException { 101 this.channel = channel; 102 103 channel.position(0); 104 header.read(channel); 105 106 if (header.contentSize + NAVXHeader.SIZE > channel.size()) { 107 throw new IOException("NAVX file is corrupt: invalid size in the header"); 108 } 109 } 110 111 @Override 112 public byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException { 113 MessageDigest digest = digestAlgorithm.getMessageDigest(); 114 updateDigest(channel, digest, 0, getSignatureOffset()); 115 return digest.digest(); 116 } 117 118 @Override 119 public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException { 120 AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE); 121 DigestInfo digestInfo = new DigestInfo(algorithmIdentifier, computeDigest(digestAlgorithm)); 122 123 SpcUuid uuid = new SpcUuid("12341234-F804-0000-781D-123412341234"); 124 SpcAttributeTypeAndOptionalValue data = new SpcAttributeTypeAndOptionalValue(AuthenticodeObjectIdentifiers.SPC_SIPINFO_OBJID, new SpcSipInfo(1, uuid)); 125 126 return new SpcIndirectDataContent(data, digestInfo); 127 } 128 129 private int getSignatureOffset() { 130 return NAVXHeader.SIZE + header.contentSize; 131 } 132 133 @Override 134 public List<CMSSignedData> getSignatures() throws IOException { 135 channel.position(getSignatureOffset()); 136 NAVXSignatureBlock signatureBlock = new NAVXSignatureBlock(); 137 signatureBlock.read(channel); 138 139 return SignatureUtils.getSignatures(signatureBlock.signedData); 140 } 141 142 @Override 143 public void setSignature(CMSSignedData signature) throws IOException { 144 NAVXSignatureBlock signatureBlock = new NAVXSignatureBlock(); 145 signatureBlock.signedData = signature; 146 147 channel.position(getSignatureOffset()); 148 signatureBlock.write(channel); 149 channel.truncate(channel.position()); 150 } 151 152 @Override 153 public void save() throws IOException { 154 } 155 156 @Override 157 public void close() throws IOException { 158 channel.close(); 159 } 160}