/***************************************************************************
*
* This file is part of the 'NDEF Tools for Android' project at
* http://code.google.com/p/ndef-tools-for-android/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
****************************************************************************/
package org.ndeftools.wellknown;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.ndeftools.Record;
import android.nfc.NdefRecord;
/**
* Signature record.<br/><br/>
*
* Digital signing of NDEF data is a trustworthy method for providing information about the origin of NDEF data.
* It provides users with the possibility of verifying the authenticity and integrity of data within the NDEF message.
*
* @author thomas
*
*/
public class SignatureRecord extends Record {
public static byte[] type = {'S', 'i', 'g'};
public static SignatureRecord parseNdefRecord(NdefRecord ndefRecord) {
byte[] payload = ndefRecord.getPayload();
SignatureRecord signatureRecord = new SignatureRecord();
int index = 0;
int version = payload[index++] & 0xFF;
signatureRecord.setVersion((byte)version);
int header = payload[index++] & 0xFF;
boolean signatureUriPresent = (header & 0x80) != 0;
SignatureType type = SignatureType.toSignatureType((header & 0x7F));
signatureRecord.setSignatureType(type);
if(signatureUriPresent || type != SignatureType.NOT_PRESENT) {
int size = ((payload[index++] & 0xFF) << 8) + ((payload[index++] & 0xFF) << 0); // unsigned short
if(size > 0) {
byte[] signatureOrUri = new byte[size];
System.arraycopy(payload, index, signatureOrUri, 0, size);
index+= size;
if(signatureUriPresent) {
signatureRecord.setSignatureUri(new String(signatureOrUri, Charset.forName("UTF-8")));
} else {
signatureRecord.setSignature(signatureOrUri);
}
}
int certificateHeader = payload[index++] & 0xFF;
signatureRecord.setCertificateFormat(CertificateFormat.toCertificateFormat((certificateHeader >> 4) & 0x7));
int numberOfCertificates = certificateHeader & 0xF;
for(int i = 0; i < numberOfCertificates; i++) {
int certificateSize = ((payload[index++] & 0xFF) << 8) + ((payload[index++] & 0xFF) << 0); // unsigned short
byte[] certificate = new byte[certificateSize];
System.arraycopy(payload, index, certificate, 0, certificateSize);
index+= certificateSize;
signatureRecord.add(certificate);
}
if((certificateHeader & 0x80) != 0) { // has certificate uri
int certificateUriSize = ((payload[index++] & 0xFF) << 8) + ((payload[index++] & 0xFF) << 0); // unsigned short
byte[] certificateUri = new byte[certificateUriSize];
System.arraycopy(payload, index, certificateUri, 0, certificateUriSize);
index+= certificateUriSize;
signatureRecord.setCertificateUri(new String(certificateUri, Charset.forName("UTF-8")));
}
} else {
// start marker
}
return signatureRecord;
}
public enum SignatureType {
NOT_PRESENT((byte)0x00), // No signature present
RSASSA_PSS_SHA_1((byte)0x01), // PKCS_1
RSASSA_PKCS1_v1_5_WITH_SHA_1((byte)0x02), // PKCS_1
DSA((byte)0x03),
ECDSA((byte)0x04);
private SignatureType(byte value) {
this.value = value;
}
private byte value;
public byte getValue() {
return value;
}
public static SignatureType toSignatureType(int i) {
for(SignatureType type : values()) {
if(type.value == i) {
return type;
}
}
throw new IllegalArgumentException("Unexpected signature type " + i);
}
}
public enum CertificateFormat {
X_509((byte)0x00),
X9_68((byte)0x01);
private CertificateFormat(byte value) {
this.value = value;
}
private byte value;
public byte getValue() {
return value;
}
public static CertificateFormat toCertificateFormat(int i) {
for(CertificateFormat type : values()) {
if(type.value == i) {
return type;
}
}
throw new IllegalArgumentException("Unexpected certificate format " + i);
}
}
private byte version = 0x01;
private SignatureType signatureType;
private byte[] signature;
private String signatureUri;
private CertificateFormat certificateFormat;
private String certificateUri;
private List<byte[]> certificates = new ArrayList<byte[]>();
public SignatureRecord() {
}
public SignatureRecord(SignatureType signatureType) {
this.signatureType = signatureType;
}
public SignatureRecord(SignatureType signatureType, byte[] signature) {
this(signatureType);
this.signature = signature;
}
public SignatureRecord(SignatureType signatureType, String signatureUri) {
this(signatureType);
this.signatureUri = signatureUri;
}
public SignatureRecord(SignatureType signatureType, byte[] signature, CertificateFormat certificateFormat) {
this(signatureType, signature);
this.certificateFormat = certificateFormat;
}
public SignatureRecord(SignatureType signatureType, String signatureUri, CertificateFormat certificateFormat) {
this(signatureType, signatureUri);
this.certificateFormat = certificateFormat;
}
public SignatureRecord(SignatureType signatureType, byte[] signature, CertificateFormat certificateFormat, String certificateUri) {
this(signatureType, signature, certificateFormat);
this.signature = signature;
}
public SignatureRecord(SignatureType signatureType, String signatureUri, CertificateFormat certificateFormat, String certificateUri) {
this(signatureType, signatureUri, certificateFormat);
this.signatureUri = signatureUri;
}
public boolean isStartMarker() {
return signatureType == SignatureType.NOT_PRESENT && signature == null && signatureUri == null;
}
public boolean hasCertificateUri() {
return certificateUri != null;
}
public boolean hasSignature() {
return signature != null;
}
public boolean hasSignatureUri() {
return signatureUri != null;
}
public SignatureType getSignatureType() {
return signatureType;
}
public void setSignatureType(SignatureType signatureType) {
this.signatureType = signatureType;
}
public CertificateFormat getCertificateFormat() {
return certificateFormat;
}
public void setCertificateFormat(CertificateFormat certificateFormat) {
this.certificateFormat = certificateFormat;
}
public List<byte[]> getCertificates() {
return certificates;
}
public void setCertificates(List<byte[]> certificates) {
this.certificates = certificates;
}
public byte getVersion() {
return version;
}
public void setVersion(byte version) {
this.version = version;
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public String getSignatureUri() {
return signatureUri;
}
public void setSignatureUri(String signatureUri) {
this.signatureUri = signatureUri;
}
public String getCertificateUri() {
return certificateUri;
}
public void setCertificateUri(String certificateUri) {
this.certificateUri = certificateUri;
}
public boolean hasSignatureType() {
return signatureType != null;
}
public boolean hasCertificateFormat() {
return certificateFormat != null;
}
public void add(byte[] certificate) {
this.certificates.add(certificate);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime
* result
+ ((certificateFormat == null) ? 0 : certificateFormat
.hashCode());
result = prime * result
+ ((certificateUri == null) ? 0 : certificateUri.hashCode());
result = prime * result
+ ((certificates == null) ? 0 : certificatesHash());
result = prime * result + Arrays.hashCode(signature);
result = prime * result
+ ((signatureType == null) ? 0 : signatureType.hashCode());
result = prime * result
+ ((signatureUri == null) ? 0 : signatureUri.hashCode());
result = prime * result + version;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
SignatureRecord other = (SignatureRecord) obj;
if (certificateFormat != other.certificateFormat)
return false;
if (certificateUri == null) {
if (other.certificateUri != null)
return false;
} else if (!certificateUri.equals(other.certificateUri))
return false;
if (!Arrays.equals(signature, other.signature))
return false;
if (signatureType != other.signatureType)
return false;
if (signatureUri == null) {
if (other.signatureUri != null)
return false;
} else if (!signatureUri.equals(other.signatureUri))
return false;
if (version != other.version)
return false;
return certificatesEquals(other);
}
private int certificatesHash() {
int hash;
if(certificates != null) {
hash = certificates.size();
for(byte[] certificate : certificates) {
hash += Arrays.hashCode(certificate);
}
} else {
hash = 0;
}
return hash;
}
private boolean certificatesEquals(SignatureRecord other) {
if (certificates == null) {
if (other.certificates != null)
return false;
} else {
if (other.certificates == null) {
return false;
}
if(other.certificates.size() != certificates.size()) {
return false;
}
for(int i = 0; i < certificates.size(); i++) {
byte[] otherCertificate = other.certificates.get(i);
byte[] thisCertificate = certificates.get(i);
if(!Arrays.equals(otherCertificate, thisCertificate)) {
return false;
}
}
}
return true;
}
@Override
public NdefRecord getNdefRecord() {
if(isStartMarker()) {
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, type, id != null ? id : EMPTY, new byte[]{0x01, 0x00});// version 1 and type 0
} else {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(version);
if(!hasSignatureType()) {
throw new IllegalArgumentException("Expected signature type");
}
if(hasSignature() && hasSignatureUri()) {
throw new IllegalArgumentException("Expected signature or signature uri, not both");
} else if(!hasSignature() && !hasSignatureUri()) {
throw new IllegalArgumentException("Expected signature or signature uri");
}
baos.write(((hasSignatureUri() ? 1 : 0) << 7) | (signatureType.getValue() & 0x7F));
byte[] signatureOrUri;
if(hasSignature()) {
signatureOrUri = signature;
if(signatureOrUri.length > 65535) {
throw new IllegalArgumentException("Expected signature size " + signatureOrUri.length + " <= 65535");
}
} else {
signatureOrUri = signatureUri.getBytes(Charset.forName("UTF-8"));
if(signatureOrUri.length > 65535) {
throw new IllegalArgumentException("Expected signature uri byte size " + signatureOrUri.length + " <= 65535");
}
}
baos.write((signatureOrUri.length >> 8) & 0xFF);
baos.write(signatureOrUri.length & 0xFF);
baos.write(signatureOrUri);
if(!hasCertificateFormat()) {
throw new IllegalArgumentException("Expected certificate format");
}
if(certificates.size() > 16) {
throw new IllegalArgumentException("Expected number of certificates " + certificates.size() + " <= 15");
}
baos.write(((hasCertificateUri() ? 1 : 0) << 7) | (certificateFormat.getValue() << 4) | (certificates.size() & 0xF));
for(int i = 0; i < certificates.size(); i++) {
byte[] certificate = certificates.get(i);
if(certificate.length > 65535) {
throw new IllegalArgumentException("Expected certificate " + i + " size " + certificate.length + " <= 65535");
}
baos.write((certificate.length >> 8) & 0xFF);
baos.write(certificate.length & 0xFF);
baos.write(certificate);
}
if(hasCertificateUri()) {
byte[] certificateUriBytes = certificateUri.getBytes(Charset.forName("UTF-8"));
if(certificateUriBytes.length > 65535) {
throw new IllegalArgumentException("Expected certificate uri byte size " + certificateUriBytes.length + " <= 65535");
}
baos.write((certificateUriBytes.length >> 8) & 0xFF);
baos.write(certificateUriBytes.length & 0xFF);
baos.write(certificateUriBytes);
}
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, type, id != null ? id : EMPTY, baos.toByteArray());
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}