diff --git a/data-modules/output/asn/SignedNFTAttestation.asn b/data-modules/output/asn/SignedNFTAttestation.asn index 570ed9c9..bb17d164 100644 --- a/data-modules/output/asn/SignedNFTAttestation.asn +++ b/data-modules/output/asn/SignedNFTAttestation.asn @@ -9,6 +9,7 @@ IMPORTS AlgorithmIdentifier FROM AuthenticationFramework; +-- Version 1 or 2, newer uses EIP712 -- SignedNFTAttestation ::= SEQUENCE { nftAttestation NFTAttestation, signingVersion INTEGER OPTIONAL, diff --git a/data-modules/src/SignedNFTAttestation.asd b/data-modules/src/SignedNFTAttestation.asd index ac4382a4..d5330f72 100644 --- a/data-modules/src/SignedNFTAttestation.asd +++ b/data-modules/src/SignedNFTAttestation.asd @@ -6,6 +6,7 @@ + diff --git a/src/intTest/java/io/alchemynft/attestation/NFTSmartContractTest.java b/src/intTest/java/io/alchemynft/attestation/NFTSmartContractTest.java index d42c0f07..1f68c978 100644 --- a/src/intTest/java/io/alchemynft/attestation/NFTSmartContractTest.java +++ b/src/intTest/java/io/alchemynft/attestation/NFTSmartContractTest.java @@ -27,7 +27,7 @@ public class NFTSmartContractTest { SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, issuerKeys); */ static SignedIdentifierAttestation attestation; - private SignedNFTAttestation nftAttestation; + private LegacySignedNFTAttestation nftAttestation; private final SmartContract contract = new SmartContract(); // the URL as King Mida's public ID, plus a label (in case of twitter, the permanent numeric ID) static final String labeledURI = "https://twitter.com/zhangweiwu 205521676"; @@ -58,7 +58,7 @@ public void checkNFTSmartContract() throws Exception NFTAttestation nftAtt = new NFTAttestation(attestation, myNFTs); //construct SignedNFTAttestation using subject key - nftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); // <-- signing step, NFT attestation is signed by owner of identifier, referenced below + nftAttestation = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); // <-- signing step, NFT attestation is signed by owner of identifier, referenced below System.out.println("DER: " + Numeric.toHexString(nftAttestation.getDerEncoding())); @@ -69,7 +69,7 @@ public void checkNFTSmartContract() throws Exception assertTrue(nftAttestation2.verify()); //Generate SignedNFTAttestation using the reconstructed NFTAttestation and the extracted Ethereum signature - SignedNFTAttestation signedNFTAttestation2 = new SignedNFTAttestation(nftAttestation2, subjectKeys); + LegacySignedNFTAttestation signedNFTAttestation2 = new LegacySignedNFTAttestation(nftAttestation2, subjectKeys.getPrivate()); assertTrue(signedNFTAttestation2.checkValidity()); assertTrue(nftAttestation.checkValidity()); assertArrayEquals(signedNFTAttestation2.getUnsignedAttestation().getDerEncoding(), nftAtt.getDerEncoding()); diff --git a/src/main/java/io/alchemynft/attestation/Eip712SignedNFTAttestation.java b/src/main/java/io/alchemynft/attestation/Eip712SignedNFTAttestation.java new file mode 100644 index 00000000..57ec012c --- /dev/null +++ b/src/main/java/io/alchemynft/attestation/Eip712SignedNFTAttestation.java @@ -0,0 +1,98 @@ +package io.alchemynft.attestation; + +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.core.ExceptionUtil; +import org.tokenscript.attestation.eip712.Eip712ObjectSigner; +import org.tokenscript.attestation.eip712.Eip712ObjectValidator; +import org.tokenscript.eip712.FullEip712InternalData; + +public class Eip712SignedNFTAttestation implements InternalSignedNFTAttestation { + private static final Logger logger = LogManager.getLogger(Eip712SignedNFTAttestation.class); + public static final String DEFAULT_DOMAIN = "https://autographnft.io"; + + private Eip712ObjectValidator validator; + + private final NFTAttestation att; + private final String signature; + private final String signedEIP712; + + public Eip712SignedNFTAttestation(NFTAttestation att, AsymmetricKeyParameter subjectSigningKey) { + try { + NFTAttestationDecoder decoder = new NFTAttestationDecoder( + att.getSignedIdentifierAttestation().getAttestationVerificationKey()); + validator = new Eip712ObjectValidator(decoder, new NFTAttestationEncoder(), + DEFAULT_DOMAIN); + Eip712ObjectSigner eipSigner = new Eip712ObjectSigner(subjectSigningKey, + new NFTAttestationEncoder()); + this.signedEIP712 = eipSigner.buildSignedToken(att, DEFAULT_DOMAIN); + this.att = validator.retrieveUnderlyingObject(signedEIP712); + this.signature = eipSigner.getSignatureFromJson(signedEIP712); + } catch (IOException e) { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Could not decode underlying NFTAttestation")); + } + constructorCheck(); + } + + public Eip712SignedNFTAttestation(String signedEIP712, AsymmetricKeyParameter identifierAttestationVerificationKey) throws IOException { + NFTAttestationDecoder decoder = new NFTAttestationDecoder(identifierAttestationVerificationKey); + validator = new Eip712ObjectValidator(decoder, new NFTAttestationEncoder(), DEFAULT_DOMAIN); + this.signedEIP712 = signedEIP712; + this.att = validator.retrieveUnderlyingObject(signedEIP712); + this.signature = validator.getSignatureFromJson(signedEIP712); + constructorCheck(); + } + + private void constructorCheck() { + if (!verify()) { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("The NFTAttestation is invalid")); + } + } + + /** + * Returns the public key of the NFTattestation signer + */ + public AsymmetricKeyParameter getNFTAttestationVerificationKey() { + return validator.retrieveUserPublicKey(signedEIP712, FullEip712InternalData.class); + } + + public String getSignedEIP712() { + return signedEIP712; + } + + @Override + public NFTAttestation getUnsignedAttestation() { + return att; + } + + public String getSignature() { + return signature; + } + + /** + * Checks the validity of the underlying NFTAttestation + */ + @Override + public boolean checkValidity() { + return att.checkValidity(); + } + + /** + * Verifies the entire EIP712 request, including domain, timestamp, usage string, signature and the + * @return + */ + @Override + public boolean verify() { + return validator.validateRequest(signedEIP712); + } + + /** + * Returns the ASN encoding of the underlying NFTAttestation + */ + @Override + public byte[] getDerEncoding() { + return att.getDerEncoding(); + } +} diff --git a/src/main/java/io/alchemynft/attestation/InternalSignedNFTAttestation.java b/src/main/java/io/alchemynft/attestation/InternalSignedNFTAttestation.java new file mode 100644 index 00000000..cf9b01ba --- /dev/null +++ b/src/main/java/io/alchemynft/attestation/InternalSignedNFTAttestation.java @@ -0,0 +1,10 @@ +package io.alchemynft.attestation; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.CheckableObject; + +public interface InternalSignedNFTAttestation extends CheckableObject { + AsymmetricKeyParameter getNFTAttestationVerificationKey(); + NFTAttestation getUnsignedAttestation(); +} + diff --git a/src/main/java/io/alchemynft/attestation/LegacySignedNFTAttestation.java b/src/main/java/io/alchemynft/attestation/LegacySignedNFTAttestation.java new file mode 100644 index 00000000..1c25313f --- /dev/null +++ b/src/main/java/io/alchemynft/attestation/LegacySignedNFTAttestation.java @@ -0,0 +1,160 @@ +package io.alchemynft.attestation; + +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.core.CompressedMsgSignature; +import org.tokenscript.attestation.core.ExceptionUtil; +import org.tokenscript.attestation.core.PersonalSignature; +import org.tokenscript.attestation.core.Signature; + +public class LegacySignedNFTAttestation implements InternalSignedNFTAttestation { + private static final Logger logger = LogManager.getLogger(LegacySignedNFTAttestation.class); + public static final int DEFAULT_SIGNING_VERSION = 2; + + private final NFTAttestation nftAtt; + private final Signature signature; + private final int signingVersion; + + public LegacySignedNFTAttestation(NFTAttestation nftAtt, AsymmetricKeyParameter subjectSigningKey) { + this(nftAtt, subjectSigningKey, DEFAULT_SIGNING_VERSION); + } + + public LegacySignedNFTAttestation(NFTAttestation nftAtt, AsymmetricKeyParameter subjectSigningKey, int signingVersion) { + this.nftAtt = nftAtt; + this.signature = makeSignature(subjectSigningKey, signingVersion); + this.signingVersion = signingVersion; + + if (!verify()) { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("The signature is not valid")); + } + } + + /** + * Constructor used for when we supply the signature separately + */ + public LegacySignedNFTAttestation(NFTAttestation NftAtt, Signature signature) { + this.nftAtt = NftAtt; + this.signature = signature; + this.signingVersion = determineSigningVersion(); + if (!verify()) { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("The signature is not valid")); + } + } + + public LegacySignedNFTAttestation(byte[] derEncoding, AsymmetricKeyParameter identifierAttestationVerificationKey) throws IOException { + ASN1InputStream input = new ASN1InputStream(derEncoding); + ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject()); + input.close(); + int currentPos = 0; + ASN1Sequence nftEncoding = ASN1Sequence.getInstance(asn1.getObjectAt(currentPos++)); + this.nftAtt = new NFTAttestation(nftEncoding.getEncoded(), identifierAttestationVerificationKey); + if (asn1.getObjectAt(currentPos) instanceof ASN1Integer) { + this.signingVersion = ASN1Integer.getInstance(asn1.getObjectAt(currentPos++)).intValueExact(); + } else { + // If signingVersion is not present we default to version 1 + this.signingVersion = 1; + } + // todo this actually not used + AlgorithmIdentifier algorithmIdentifier = AlgorithmIdentifier.getInstance(asn1.getObjectAt(currentPos++)); + DERBitString signatureEnc = DERBitString.getInstance(asn1.getObjectAt(currentPos++)); + this.signature = makeSignature(signatureEnc.getBytes(), signingVersion); + } + + private int determineSigningVersion() { + if (signature instanceof PersonalSignature) { + return 1; + } + else if (signature instanceof CompressedMsgSignature) { + return 2; + } else { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unexpected signature type used")); + } + } + + Signature makeSignature(byte[] encodedBytes, int signingVersion) { + if (signingVersion == 1) { + return new PersonalSignature(encodedBytes); + } + else if (signingVersion == 2) { + return new CompressedMsgSignature(encodedBytes, SignedNFTAttestation.PREFIX_MSG, SignedNFTAttestation.POSTFIX_MSG); + } else { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unknown signing version")); + } + } + + Signature makeSignature(AsymmetricKeyParameter key, int signingVersion) { + if (signingVersion == 1) { + return new PersonalSignature(key, nftAtt.getDerEncoding()); + } + else if (signingVersion == 2) { + return new CompressedMsgSignature(key, nftAtt.getDerEncoding(), SignedNFTAttestation.PREFIX_MSG, SignedNFTAttestation.POSTFIX_MSG); + } else { + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unknown signing version")); + } + } + + @Override + public NFTAttestation getUnsignedAttestation() { + return nftAtt; + } + + public Signature getSignature() { + return signature; + } + + /** + * Returns the public key of the NFTattestation signer + */ + @Override + public AsymmetricKeyParameter getNFTAttestationVerificationKey() { + return nftAtt.getAttestedUserKey(); + } + + @Override + public byte[] getDerEncoding() { + return constructSignedAttestation(this.nftAtt, this.signature.getRawSignature()); + } + + byte[] constructSignedAttestation(NFTAttestation unsignedAtt, byte[] signature) { + try { + byte[] rawAtt = unsignedAtt.getDerEncoding(); + ASN1EncodableVector res = new ASN1EncodableVector(); + res.add(ASN1Primitive.fromByteArray(rawAtt)); + // Only include version number if it is greater than 1 + if (signingVersion > 1) { + res.add(new ASN1Integer(signingVersion)); + } + res.add(unsignedAtt.getSigningAlgorithm()); + res.add(new DERBitString(signature)); + return new DERSequence(res).getEncoded(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean checkValidity() { + return getUnsignedAttestation().checkValidity(); + } + + @Override + public boolean verify() { + if (!signature.verify(nftAtt.getDerEncoding(), getNFTAttestationVerificationKey())) { + return false; + } + if (!nftAtt.verify()) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/io/alchemynft/attestation/NFTAttestation.java b/src/main/java/io/alchemynft/attestation/NFTAttestation.java index acddad8b..96b3e548 100644 --- a/src/main/java/io/alchemynft/attestation/NFTAttestation.java +++ b/src/main/java/io/alchemynft/attestation/NFTAttestation.java @@ -10,12 +10,13 @@ import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.tokenscript.attestation.AttestedKeyObject; import org.tokenscript.attestation.ERC721Token; import org.tokenscript.attestation.SignedIdentifierAttestation; -import org.tokenscript.attestation.core.ASNEncodable; -import org.tokenscript.attestation.core.Validateable; +import org.tokenscript.attestation.core.ExceptionUtil; -public class NFTAttestation implements ASNEncodable, Validateable { +public class NFTAttestation extends AttestedKeyObject { private static final Logger logger = LogManager.getLogger(NFTAttestation.class); private final SignedIdentifierAttestation signedIdentifierAttestation; @@ -80,4 +81,15 @@ public boolean checkValidity() { public boolean verify() { return signedIdentifierAttestation.verify(); } + + @Override + public AsymmetricKeyParameter getAttestedUserKey() { + try { + return PublicKeyFactory.createKey( + getSignedIdentifierAttestation().getUnsignedAttestation() + .getSubjectPublicKeyInfo()); + } catch (IOException e) { + throw ExceptionUtil.makeRuntimeException(logger, "Could not restore key from signed signed attestation", e); + } + } } diff --git a/src/main/java/io/alchemynft/attestation/NFTAttestationDecoder.java b/src/main/java/io/alchemynft/attestation/NFTAttestationDecoder.java new file mode 100644 index 00000000..166d5569 --- /dev/null +++ b/src/main/java/io/alchemynft/attestation/NFTAttestationDecoder.java @@ -0,0 +1,34 @@ +package io.alchemynft.attestation; + +import java.io.IOException; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.ERC721Token; +import org.tokenscript.attestation.ObjectDecoder; +import org.tokenscript.attestation.SignedIdentifierAttestation; + +public class NFTAttestationDecoder implements ObjectDecoder { + private final AsymmetricKeyParameter identifierAttestationVerificationKey; + public NFTAttestationDecoder(AsymmetricKeyParameter identifierAttestationVerificationKey) { + this.identifierAttestationVerificationKey = identifierAttestationVerificationKey; + } + + @Override + public NFTAttestation decode(byte[] encoding) throws IOException { + ASN1InputStream input = new ASN1InputStream(encoding); + ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject()); + input.close(); + ASN1Sequence attestationEnc = ASN1Sequence.getInstance(asn1.getObjectAt(0)); //root attestation, should be signed att + SignedIdentifierAttestation signedIdentifierAttestation = new SignedIdentifierAttestation(attestationEnc.getEncoded(), identifierAttestationVerificationKey); + + ASN1Sequence tokensEnc = ASN1Sequence.getInstance(asn1.getObjectAt(1)); + DERSequence tokens = DERSequence.convert(tokensEnc); + ERC721Token[] erc721Tokens = new ERC721Token[tokens.size()]; + for (int i = 0; i< erc721Tokens.length; i++) { + erc721Tokens[i] = new ERC721Token(tokens.getObjectAt(i).toASN1Primitive().getEncoded()); + } + return new NFTAttestation(signedIdentifierAttestation, erc721Tokens); + } +} diff --git a/src/main/java/io/alchemynft/attestation/NFTAttestationEncoder.java b/src/main/java/io/alchemynft/attestation/NFTAttestationEncoder.java new file mode 100644 index 00000000..1e7c583d --- /dev/null +++ b/src/main/java/io/alchemynft/attestation/NFTAttestationEncoder.java @@ -0,0 +1,21 @@ +package io.alchemynft.attestation; + +import com.alphawallet.token.web.Ethereum.web3j.StructuredData.Entry; +import java.util.HashMap; +import java.util.List; +import org.tokenscript.eip712.Eip712Encoder; + +public class NFTAttestationEncoder extends Eip712Encoder { + private static final String PROTOCOL_VERSION = "0.1"; + private static final String PRIMARY_NAME = "NFTAttestation";//"Signed request to be used only for"; + private static final String USAGE_VALUE = "Single-use Alchemy NFT"; + + public NFTAttestationEncoder() { + super(USAGE_VALUE, PROTOCOL_VERSION, PRIMARY_NAME); + } + + @Override + public HashMap> getTypes() { + return getDefaultTypes(); + } +} diff --git a/src/main/java/io/alchemynft/attestation/SignedNFTAttestation.java b/src/main/java/io/alchemynft/attestation/SignedNFTAttestation.java index 60b377fe..5482fadc 100644 --- a/src/main/java/io/alchemynft/attestation/SignedNFTAttestation.java +++ b/src/main/java/io/alchemynft/attestation/SignedNFTAttestation.java @@ -1,182 +1,98 @@ package io.alchemynft.attestation; import java.io.IOException; +import java.io.InvalidObjectException; +import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.bouncycastle.asn1.ASN1EncodableVector; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.ASN1Sequence; -import org.bouncycastle.asn1.DERBitString; -import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.util.PublicKeyFactory; -import org.tokenscript.attestation.core.ASNEncodable; +import org.tokenscript.attestation.core.CompressedMsgSignature; import org.tokenscript.attestation.core.ExceptionUtil; -import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.core.Validateable; -import org.tokenscript.attestation.core.Verifiable; - -public class SignedNFTAttestation implements ASNEncodable, Verifiable, Validateable { - private static final Logger logger = LogManager.getLogger(SignedNFTAttestation.class); - public static final int DEFAULT_SIGNING_VERSION = 2; - public static final String PREFIX_MSG = "The digest of the ERC721 tokens for AlchemyNFT is: "; - public static final String POSTFIX_MSG = ""; - - private final NFTAttestation att; - private final int signingVersion; - private final Signature signature; - private final AsymmetricKeyParameter attestationVerificationKey; - - public SignedNFTAttestation(NFTAttestation att, AsymmetricCipherKeyPair subjectSigningKey) { - this(att, subjectSigningKey, DEFAULT_SIGNING_VERSION); +import org.tokenscript.attestation.core.PersonalSignature; +import org.tokenscript.attestation.core.Signature; + +public class SignedNFTAttestation implements InternalSignedNFTAttestation { + private static final Logger logger = LogManager.getLogger(SignedNFTAttestation.class); + public static final String PREFIX_MSG = "The digest of the ERC721 tokens for AlchemyNFT is: "; + public static final String POSTFIX_MSG = ""; + + private final InternalSignedNFTAttestation internalNftAtt; + + public SignedNFTAttestation(NFTAttestation nftAtt, Signature signature) { + if (signature instanceof PersonalSignature || signature instanceof CompressedMsgSignature) { + this.internalNftAtt = new LegacySignedNFTAttestation(nftAtt, signature); + } else { + throw ExceptionUtil.throwException(logger, + new IllegalArgumentException("Signature is not version 1 or 2, this constructor only works with version 1 or version 2")); } - - public SignedNFTAttestation(NFTAttestation att, AsymmetricCipherKeyPair subjectSigningKey, int signingVersion) { - this.att = att; - this.attestationVerificationKey = subjectSigningKey.getPublic(); - this.signature = makeSignature(subjectSigningKey, signingVersion); - this.signingVersion = signingVersion; - - if (!verify()) { - throw ExceptionUtil.throwException(logger, new IllegalArgumentException("The signature is not valid")); - } + } + + public SignedNFTAttestation(NFTAttestation att, AsymmetricKeyParameter subjectSigningKey, int signingVersion) { + if (signingVersion == 1 || signingVersion == 2) { + this.internalNftAtt = new LegacySignedNFTAttestation(att, subjectSigningKey, signingVersion); + } else if (signingVersion == 3) { + this.internalNftAtt = new Eip712SignedNFTAttestation(att, subjectSigningKey); + } else { + throw ExceptionUtil.throwException(logger, + new IllegalArgumentException("Unknown signature version")); } + } - /** - * Constructor used for when we supply the signature separately - */ - public SignedNFTAttestation(NFTAttestation att, Signature signature) { - this.att = att; - this.attestationVerificationKey = getKeyFromAttestation(); - this.signature = signature; - this.signingVersion = determineSigningVersion(); - if (!verify()) { - throw ExceptionUtil.throwException(logger, new IllegalArgumentException("The signature is not valid")); - } + public SignedNFTAttestation(byte[] derEncoding, AsymmetricKeyParameter identifierAttestationVerificationKey) throws IOException { + InternalSignedNFTAttestation tempAtt = constructLegacy(derEncoding, identifierAttestationVerificationKey); + if (tempAtt != null) { + this.internalNftAtt = tempAtt; + return; } - - public SignedNFTAttestation(byte[] derEncoding, AsymmetricKeyParameter identifierAttestationVerificationKey) throws IOException { - ASN1InputStream input = new ASN1InputStream(derEncoding); - ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject()); - input.close(); - int currentPos = 0; - ASN1Sequence nftEncoding = ASN1Sequence.getInstance(asn1.getObjectAt(currentPos++)); - this.att = new NFTAttestation(nftEncoding.getEncoded(), identifierAttestationVerificationKey); - if (asn1.getObjectAt(currentPos) instanceof ASN1Integer) { - this.signingVersion = ASN1Integer.getInstance(asn1.getObjectAt(currentPos++)).intValueExact(); - } else { - // If signingVersion is not present we default to version 1 - this.signingVersion = 1; - } - // todo this actually not used - AlgorithmIdentifier algorithmIdentifier = AlgorithmIdentifier.getInstance(asn1.getObjectAt(currentPos++)); - DERBitString signatureEnc = DERBitString.getInstance(asn1.getObjectAt(currentPos++)); - this.signature = makeSignature(signatureEnc.getBytes(), signingVersion); - this.attestationVerificationKey = getKeyFromAttestation(); + tempAtt = constructEip(derEncoding, identifierAttestationVerificationKey); + if (tempAtt != null) { + this.internalNftAtt = tempAtt; + return; } - - private int determineSigningVersion() { - if (signature instanceof PersonalSignature) { - return 1; - } - else if (signature instanceof CompressedMsgSignature) { - return 2; - } else { - throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unexpected signature type used")); - } - } - - Signature makeSignature(byte[] encodedBytes, int signingVersion) { - if (signingVersion == 1) { - return new PersonalSignature(encodedBytes); - } - else if (signingVersion == 2) { - return new CompressedMsgSignature(encodedBytes, PREFIX_MSG, POSTFIX_MSG); - } else { - throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unknown signing version")); - } - } - - Signature makeSignature(AsymmetricCipherKeyPair keys, int signingVersion) { - if (signingVersion == 1) { - return new PersonalSignature(keys, att.getDerEncoding()); - } - else if (signingVersion == 2) { - return new CompressedMsgSignature(keys, att.getDerEncoding(), PREFIX_MSG, POSTFIX_MSG); - } else { - throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Unknown signing version")); - } - } - - private AsymmetricKeyParameter getKeyFromAttestation() { - AsymmetricKeyParameter key = null; - try { - key = PublicKeyFactory.createKey( - att.getSignedIdentifierAttestation().getUnsignedAttestation() - .getSubjectPublicKeyInfo()); - } catch (IOException e) { - throw ExceptionUtil.makeRuntimeException(logger, "Could not restore key from signed signed attestation", e); - } - return key; + throw ExceptionUtil.throwException(logger, + new IllegalArgumentException("Could not decode SignedNFTAttestation")); + } + + private LegacySignedNFTAttestation constructLegacy(byte[] derEncoding, AsymmetricKeyParameter identifierAttestationVerificationKey) { + try { + return new LegacySignedNFTAttestation(derEncoding, identifierAttestationVerificationKey); + } catch (IllegalArgumentException|IOException e) { + // The NFTAttestation is not version 1 + return null; } - - public NFTAttestation getUnsignedAttestation() { - return att; - } - - public Signature getSignature() { - return signature; - } - - /** - * Returns the public key of the attestation signer - */ - public AsymmetricKeyParameter getAttestationVerificationKey() { return attestationVerificationKey; } - - @Override - public byte[] getDerEncoding() { - return constructSignedAttestation(this.att, this.signingVersion, this.signature.getRawSignature()); - } - - static byte[] constructSignedAttestation(NFTAttestation unsignedAtt, int signingVersion, byte[] signature) { - try { - byte[] rawAtt = unsignedAtt.getDerEncoding(); - ASN1EncodableVector res = new ASN1EncodableVector(); - res.add(ASN1Primitive.fromByteArray(rawAtt)); - // Only include version number if it is greater than 1 - if (signingVersion > 1) { - res.add(new ASN1Integer(signingVersion)); - } - res.add(unsignedAtt.getSigningAlgorithm()); - res.add(new DERBitString(signature)); - return new DERSequence(res).getEncoded(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean checkValidity() { - return getUnsignedAttestation().checkValidity(); - } - - @Override - public boolean verify() { - if (!signature.verify(att.getDerEncoding(), attestationVerificationKey)) { - return false; - } - if (!att.verify()) { - return false; - } - // Verify that signature is done using thew right key - if (!SignatureUtility.addressFromKey(attestationVerificationKey).equals( - SignatureUtility.addressFromKey(getKeyFromAttestation()))) { - return false; - } - return true; + } + + private Eip712SignedNFTAttestation constructEip(byte[] derEncoding, AsymmetricKeyParameter identifierAttestationVerificationKey) { + try { + return new Eip712SignedNFTAttestation(new String(derEncoding, StandardCharsets.UTF_8), identifierAttestationVerificationKey); + } catch (IllegalArgumentException|IOException e) { + // The NFTAttestation is not version 1 + return null; } -} \ No newline at end of file + } + + @Override + public AsymmetricKeyParameter getNFTAttestationVerificationKey() { + return internalNftAtt.getNFTAttestationVerificationKey(); + } + + @Override + public NFTAttestation getUnsignedAttestation() { + return internalNftAtt.getUnsignedAttestation(); + } + + @Override + public byte[] getDerEncoding() throws InvalidObjectException { + return internalNftAtt.getDerEncoding(); + } + + @Override + public boolean checkValidity() { + return internalNftAtt.checkValidity(); + } + + @Override + public boolean verify() { + return internalNftAtt.verify(); + } +} diff --git a/src/main/java/org/devcon/ticket/Ticket.java b/src/main/java/org/devcon/ticket/Ticket.java index 9c386ac0..b37561a8 100644 --- a/src/main/java/org/devcon/ticket/Ticket.java +++ b/src/main/java/org/devcon/ticket/Ticket.java @@ -24,7 +24,7 @@ import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; -public class Ticket implements Attestable { +public class Ticket extends Attestable { private static final Logger logger = LogManager.getLogger(Ticket.class); private final BigInteger ticketId; diff --git a/src/main/java/org/devcon/ticket/TicketDecoder.java b/src/main/java/org/devcon/ticket/TicketDecoder.java index dccab9a9..120feaf5 100644 --- a/src/main/java/org/devcon/ticket/TicketDecoder.java +++ b/src/main/java/org/devcon/ticket/TicketDecoder.java @@ -18,11 +18,11 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; -import org.tokenscript.attestation.AttestableObjectDecoder; +import org.tokenscript.attestation.ObjectDecoder; import org.tokenscript.attestation.core.ExceptionUtil; import org.tokenscript.attestation.core.SignatureUtility; -public class TicketDecoder implements AttestableObjectDecoder { +public class TicketDecoder implements ObjectDecoder { private static final Logger logger = LogManager.getLogger(TicketDecoder.class); private static final String DEFAULT = "default"; diff --git a/src/main/java/org/tokenscript/auth/UnpredictableNumberBundle.java b/src/main/java/org/devcon/ticket/UnpredictableNumberBundle.java similarity index 97% rename from src/main/java/org/tokenscript/auth/UnpredictableNumberBundle.java rename to src/main/java/org/devcon/ticket/UnpredictableNumberBundle.java index 8cca2cfe..ac90f665 100644 --- a/src/main/java/org/tokenscript/auth/UnpredictableNumberBundle.java +++ b/src/main/java/org/devcon/ticket/UnpredictableNumberBundle.java @@ -1,4 +1,4 @@ -package org.tokenscript.auth; +package org.devcon.ticket; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/src/main/java/org/tokenscript/auth/UnpredictableNumberTool.java b/src/main/java/org/devcon/ticket/UnpredictableNumberTool.java similarity index 99% rename from src/main/java/org/tokenscript/auth/UnpredictableNumberTool.java rename to src/main/java/org/devcon/ticket/UnpredictableNumberTool.java index 4556197a..38c4a1e5 100644 --- a/src/main/java/org/tokenscript/auth/UnpredictableNumberTool.java +++ b/src/main/java/org/devcon/ticket/UnpredictableNumberTool.java @@ -1,4 +1,4 @@ -package org.tokenscript.auth; +package org.devcon.ticket; import org.tokenscript.attestation.core.AttestationCrypto; import org.tokenscript.attestation.core.ExceptionUtil; diff --git a/src/main/java/org/devcon/ticket/UseTicketBundle.java b/src/main/java/org/devcon/ticket/UseTicketBundle.java index 4c42246c..046b9a4a 100644 --- a/src/main/java/org/devcon/ticket/UseTicketBundle.java +++ b/src/main/java/org/devcon/ticket/UseTicketBundle.java @@ -13,8 +13,6 @@ import org.tokenscript.attestation.core.SignatureUtility; import org.tokenscript.attestation.core.Verifiable; import org.tokenscript.attestation.Timestamp; -import org.tokenscript.auth.UnpredictableNumberBundle; -import org.tokenscript.auth.UnpredictableNumberTool; public class UseTicketBundle implements Verifiable { private static final Logger logger = LogManager.getLogger(UseTicketBundle.class); @@ -113,7 +111,7 @@ public boolean verify() { logger.error("UseTicket could not be verified"); return false; } - if (!SignatureUtility.verifyPersonalEthereumSignature(computeMessage(un), signature, useTicket.getUserPublicKey())) { + if (!SignatureUtility.verifyPersonalEthereumSignature(computeMessage(un), signature, useTicket.getAttestedUserKey())) { logger.error("Signature could not be verified"); return false; } diff --git a/src/main/java/org/tokenscript/attestation/AttestedKeyObject.java b/src/main/java/org/tokenscript/attestation/AttestedKeyObject.java new file mode 100644 index 00000000..9159ae34 --- /dev/null +++ b/src/main/java/org/tokenscript/attestation/AttestedKeyObject.java @@ -0,0 +1,7 @@ +package org.tokenscript.attestation; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public abstract class AttestedKeyObject implements CheckableObject { + public abstract AsymmetricKeyParameter getAttestedUserKey(); +} diff --git a/src/main/java/org/tokenscript/attestation/AttestedObject.java b/src/main/java/org/tokenscript/attestation/AttestedObject.java index febae5b3..a4d69f4f 100644 --- a/src/main/java/org/tokenscript/attestation/AttestedObject.java +++ b/src/main/java/org/tokenscript/attestation/AttestedObject.java @@ -1,11 +1,5 @@ package org.tokenscript.attestation; -import org.tokenscript.attestation.core.ASNEncodable; -import org.tokenscript.attestation.core.Attestable; -import org.tokenscript.attestation.core.AttestationCrypto; -import org.tokenscript.attestation.core.ExceptionUtil; -import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.core.Verifiable; import java.io.IOException; import java.io.InvalidObjectException; import java.math.BigInteger; @@ -17,8 +11,12 @@ import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.tokenscript.attestation.core.Attestable; +import org.tokenscript.attestation.core.AttestationCrypto; +import org.tokenscript.attestation.core.ExceptionUtil; +import org.tokenscript.attestation.core.SignatureUtility; -public class AttestedObject implements ASNEncodable, Verifiable { +public class AttestedObject extends AttestedKeyObject { private static final Logger logger = LogManager.getLogger(AttestedObject.class); private final T attestableObject; private final SignedIdentifierAttestation att; @@ -30,18 +28,18 @@ public class AttestedObject implements ASNEncodable, Verif public AttestedObject(T attestableObject, SignedIdentifierAttestation att, AsymmetricKeyParameter userPublicKey, BigInteger attestationSecret, BigInteger chequeSecret, AttestationCrypto crypto) { - this(attestableObject, att, userPublicKey, attestationSecret, chequeSecret, new byte[0], crypto); + this(attestableObject, att, attestationSecret, chequeSecret, new byte[0], crypto); } - public AttestedObject(T attestableObject, SignedIdentifierAttestation att, AsymmetricKeyParameter userPublicKey, - BigInteger attestationSecret, BigInteger chequeSecret, byte[] unpredictableNumber, - AttestationCrypto crypto) + public AttestedObject(T attestableObject, SignedIdentifierAttestation att, + BigInteger attestationSecret, BigInteger chequeSecret, byte[] unpredictableNumber, + AttestationCrypto crypto) { this.attestableObject = attestableObject; this.att = att; - this.userPublicKey = userPublicKey; try { + this.userPublicKey = PublicKeyFactory.createKey(att.getUnsignedAttestation().getSubjectPublicKeyInfo()); this.pok = makeProof(attestationSecret, chequeSecret, unpredictableNumber, crypto); ASN1EncodableVector vec = new ASN1EncodableVector(); vec.add(ASN1Sequence.getInstance(this.attestableObject.getDerEncoding())); @@ -72,14 +70,14 @@ public AttestedObject(T object, SignedIdentifierAttestation att, ProofOfExponent constructorCheck(); } - public AttestedObject(byte[] derEncoding, AttestableObjectDecoder decoder, + public AttestedObject(byte[] derEncoding, ObjectDecoder attestableObjectDecoder, AsymmetricKeyParameter publicAttestationSigningKey) { this.encoding = derEncoding; try { ASN1InputStream input = new ASN1InputStream(derEncoding); ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject()); input.close(); - this.attestableObject = decoder.decode(asn1.getObjectAt(0).toASN1Primitive().getEncoded()); + this.attestableObject = attestableObjectDecoder.decode(asn1.getObjectAt(0).toASN1Primitive().getEncoded()); this.att = new SignedIdentifierAttestation(asn1.getObjectAt(1).toASN1Primitive().getEncoded(), publicAttestationSigningKey); this.pok = new UsageProofOfExponent(asn1.getObjectAt(2).toASN1Primitive().getEncoded()); this.userPublicKey = PublicKeyFactory.createKey(att.getUnsignedAttestation().getSubjectPublicKeyInfo()); @@ -108,7 +106,8 @@ public ProofOfExponent getPok() { return pok; } - public AsymmetricKeyParameter getUserPublicKey() { + @Override + public AsymmetricKeyParameter getAttestedUserKey() { return userPublicKey; } @@ -116,6 +115,7 @@ public AsymmetricKeyParameter getUserPublicKey() { * Verifies that the redeem request will be accepted by the smart contract * @return true if the redeem request should be accepted by the smart contract */ + @Override public boolean checkValidity() { // CHECK: that it is an identifier attestation otherwise not all the checks of validity needed gets carried out try { @@ -142,7 +142,7 @@ public boolean checkValidity() { // CHECK: the Ethereum address on the attestation matches receivers signing key String attestationEthereumAddress = getAtt().getUnsignedAttestation().getAddress(); - if (!attestationEthereumAddress.equals(SignatureUtility.addressFromKey(getUserPublicKey()))) { + if (!attestationEthereumAddress.equals(SignatureUtility.addressFromKey(getAttestedUserKey()))) { logger.error("The attestation is not to the same Ethereum user who is sending this request"); return false; } diff --git a/src/main/java/org/tokenscript/attestation/AttestedObjectDecoder.java b/src/main/java/org/tokenscript/attestation/AttestedObjectDecoder.java new file mode 100644 index 00000000..a63a54de --- /dev/null +++ b/src/main/java/org/tokenscript/attestation/AttestedObjectDecoder.java @@ -0,0 +1,20 @@ +package org.tokenscript.attestation; + +import java.io.IOException; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.core.Attestable; + +public class AttestedObjectDecoder implements ObjectDecoder> { + private ObjectDecoder underlyingDecoder; + private AsymmetricKeyParameter publicAttestationSigningKey; + + public AttestedObjectDecoder(ObjectDecoder underlyingObjectDecoder, AsymmetricKeyParameter publicAttestationSigningKey) { + this.underlyingDecoder = underlyingObjectDecoder; + this.publicAttestationSigningKey = publicAttestationSigningKey; + } + + @Override + public AttestedObject decode(byte[] encoding) throws IOException { + return new AttestedObject(encoding, underlyingDecoder, publicAttestationSigningKey); + } +} diff --git a/src/main/java/org/tokenscript/attestation/CheckableObject.java b/src/main/java/org/tokenscript/attestation/CheckableObject.java new file mode 100644 index 00000000..5f5d8b21 --- /dev/null +++ b/src/main/java/org/tokenscript/attestation/CheckableObject.java @@ -0,0 +1,11 @@ +package org.tokenscript.attestation; + +import org.tokenscript.attestation.core.ASNEncodable; +import org.tokenscript.attestation.core.Validateable; +import org.tokenscript.attestation.core.Verifiable; + +/** + * Interface consolidating ASNEncodable, Verifiable, Validateable which is needed to make multiple interfaces play nicely with generics + */ +public interface CheckableObject extends ASNEncodable, Verifiable, Validateable { +} diff --git a/src/main/java/org/tokenscript/attestation/IdentifierAttestation.java b/src/main/java/org/tokenscript/attestation/IdentifierAttestation.java index fff04aa3..e4fb8fd2 100644 --- a/src/main/java/org/tokenscript/attestation/IdentifierAttestation.java +++ b/src/main/java/org/tokenscript/attestation/IdentifierAttestation.java @@ -41,7 +41,7 @@ public class IdentifierAttestation extends Attestation implements Validateable { public enum AttestationType { PHONE ("phone"), EMAIL ("email"), - INETPERSONA("InetPersona"); + INETPERSONA("inetpersona"); private final String type; @@ -144,7 +144,7 @@ public IdentifierAttestation(byte[] commitment, AsymmetricKeyParameter key) { * issuer, smartcontracts. * This is done using labeledURL, hence URL must be a valid URL */ - public IdentifierAttestation(String label, String URL, AsymmetricKeyParameter key) throws MalformedURLException { + public IdentifierAttestation(String label, String URL, AsymmetricKeyParameter subjectKey) throws MalformedURLException { super(); super.setVersion(NFT_VERSION); super.setSubject(makeLabeledURI(label, URL)); @@ -152,7 +152,7 @@ public IdentifierAttestation(String label, String URL, AsymmetricKeyParameter ke super.setIssuer("CN=attestation.id"); super.setSerialNumber(1); try { - SubjectPublicKeyInfo spki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(key); + SubjectPublicKeyInfo spki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(subjectKey); super.setSubjectPublicKeyInfo(spki); } catch (IOException e) { throw ExceptionUtil.makeRuntimeException(logger, "Could not decode asn1", e); diff --git a/src/main/java/org/tokenscript/attestation/AttestableObjectDecoder.java b/src/main/java/org/tokenscript/attestation/ObjectDecoder.java similarity index 51% rename from src/main/java/org/tokenscript/attestation/AttestableObjectDecoder.java rename to src/main/java/org/tokenscript/attestation/ObjectDecoder.java index e9504528..8be1c1fd 100644 --- a/src/main/java/org/tokenscript/attestation/AttestableObjectDecoder.java +++ b/src/main/java/org/tokenscript/attestation/ObjectDecoder.java @@ -1,8 +1,7 @@ package org.tokenscript.attestation; -import org.tokenscript.attestation.core.Attestable; import java.io.IOException; -public interface AttestableObjectDecoder { +public interface ObjectDecoder { public T decode(byte[] encoding) throws IOException; } diff --git a/src/main/java/org/tokenscript/attestation/cheque/Cheque.java b/src/main/java/org/tokenscript/attestation/cheque/Cheque.java index b8d1102b..f3d33268 100644 --- a/src/main/java/org/tokenscript/attestation/cheque/Cheque.java +++ b/src/main/java/org/tokenscript/attestation/cheque/Cheque.java @@ -25,7 +25,7 @@ import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; -public class Cheque implements Attestable { +public class Cheque extends Attestable { private static final Logger logger = LogManager.getLogger(Cheque.class); private final byte[] commitment; private final long amount; diff --git a/src/main/java/org/tokenscript/attestation/cheque/ChequeDecoder.java b/src/main/java/org/tokenscript/attestation/cheque/ChequeDecoder.java index 22825e26..5c64efb5 100644 --- a/src/main/java/org/tokenscript/attestation/cheque/ChequeDecoder.java +++ b/src/main/java/org/tokenscript/attestation/cheque/ChequeDecoder.java @@ -1,6 +1,6 @@ package org.tokenscript.attestation.cheque; -import org.tokenscript.attestation.AttestableObjectDecoder; +import org.tokenscript.attestation.ObjectDecoder; import org.tokenscript.attestation.core.ExceptionUtil; import org.tokenscript.attestation.core.SignatureUtility; import java.io.IOException; @@ -15,7 +15,7 @@ import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -public class ChequeDecoder implements AttestableObjectDecoder { +public class ChequeDecoder implements ObjectDecoder { private static final Logger logger = LogManager.getLogger(ChequeDecoder.class); public ChequeDecoder() {} diff --git a/src/main/java/io/alchemynft/attestation/AbstractSignature.java b/src/main/java/org/tokenscript/attestation/core/AbstractSignature.java similarity index 69% rename from src/main/java/io/alchemynft/attestation/AbstractSignature.java rename to src/main/java/org/tokenscript/attestation/core/AbstractSignature.java index f35f96f8..58366796 100644 --- a/src/main/java/io/alchemynft/attestation/AbstractSignature.java +++ b/src/main/java/org/tokenscript/attestation/core/AbstractSignature.java @@ -1,16 +1,14 @@ -package io.alchemynft.attestation; +package org.tokenscript.attestation.core; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.attestation.core.SignatureUtility; public abstract class AbstractSignature implements Signature { private final String type; private final byte[] rawSignature; - public AbstractSignature(AsymmetricCipherKeyPair keys, byte[] unprocessedMessage, String type) { + public AbstractSignature(AsymmetricKeyParameter signingKey, byte[] unprocessedMessage, String type) { this.type = type; - this.rawSignature = sign(keys, unprocessedMessage); + this.rawSignature = sign(signingKey, unprocessedMessage); } public AbstractSignature(byte[] rawSignature, String type) { @@ -18,8 +16,8 @@ public AbstractSignature(byte[] rawSignature, String type) { this.rawSignature = rawSignature; } - protected byte[] sign(AsymmetricCipherKeyPair keys, byte[] unprocessedMessage) { - return SignatureUtility.signWithEthereum(processMessage(unprocessedMessage), keys.getPrivate()); + protected byte[] sign(AsymmetricKeyParameter keys, byte[] unprocessedMessage) { + return SignatureUtility.signWithEthereum(processMessage(unprocessedMessage), keys); } @Override diff --git a/src/main/java/org/tokenscript/attestation/core/Attestable.java b/src/main/java/org/tokenscript/attestation/core/Attestable.java index 7f094bff..7f63602b 100644 --- a/src/main/java/org/tokenscript/attestation/core/Attestable.java +++ b/src/main/java/org/tokenscript/attestation/core/Attestable.java @@ -1,5 +1,7 @@ package org.tokenscript.attestation.core; -public interface Attestable extends ASNEncodable, Verifiable, Validateable { - public byte[] getCommitment(); +import org.tokenscript.attestation.CheckableObject; + +public abstract class Attestable implements CheckableObject { + public abstract byte[] getCommitment(); } diff --git a/src/main/java/io/alchemynft/attestation/CompressedMsgSignature.java b/src/main/java/org/tokenscript/attestation/core/CompressedMsgSignature.java similarity index 73% rename from src/main/java/io/alchemynft/attestation/CompressedMsgSignature.java rename to src/main/java/org/tokenscript/attestation/core/CompressedMsgSignature.java index 14b18544..80ef3212 100644 --- a/src/main/java/io/alchemynft/attestation/CompressedMsgSignature.java +++ b/src/main/java/org/tokenscript/attestation/core/CompressedMsgSignature.java @@ -1,11 +1,8 @@ -package io.alchemynft.attestation; +package org.tokenscript.attestation.core; import java.nio.charset.StandardCharsets; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.util.encoders.Hex; -import org.tokenscript.attestation.core.AttestationCrypto; -import org.tokenscript.attestation.core.SignatureUtility; public class CompressedMsgSignature implements Signature { private static final String TYPE_OF_SIGNATURE = "compressed"; @@ -23,21 +20,21 @@ public CompressedMsgSignature(byte[] rawSignature, String messagePrefix, String this.rawSignature = rawSignature; } - public CompressedMsgSignature(AsymmetricCipherKeyPair keys, byte[] unprocessedMsg) { - this(keys, unprocessedMsg, "", ""); + public CompressedMsgSignature(AsymmetricKeyParameter signingKey, byte[] unprocessedMsg) { + this(signingKey, unprocessedMsg, "", ""); } /** * Constructs a compressed signature of the format @messagePrefix concatenated with Keccak(@unprocessedMsg) concatenated with @messagePostfix. */ - public CompressedMsgSignature(AsymmetricCipherKeyPair keys, byte[] unprocessedMsg, String messagePrefix, String messagePostfix) { + public CompressedMsgSignature(AsymmetricKeyParameter signingKey, byte[] unprocessedMsg, String messagePrefix, String messagePostfix) { this.messagePrefix = messagePrefix; this.messagePostfix = messagePostfix; - this.rawSignature = sign(keys, unprocessedMsg); + this.rawSignature = sign(signingKey, unprocessedMsg); } - protected byte[] sign(AsymmetricCipherKeyPair keys, byte[] unprocessedMsg) { - return SignatureUtility.signWithEthereum(processMessage(unprocessedMsg), keys.getPrivate()); + protected byte[] sign(AsymmetricKeyParameter keys, byte[] unprocessedMsg) { + return SignatureUtility.signWithEthereum(processMessage(unprocessedMsg), keys); } @Override diff --git a/src/main/java/io/alchemynft/attestation/PersonalSignature.java b/src/main/java/org/tokenscript/attestation/core/PersonalSignature.java similarity index 56% rename from src/main/java/io/alchemynft/attestation/PersonalSignature.java rename to src/main/java/org/tokenscript/attestation/core/PersonalSignature.java index c37a2fd5..d7efafbb 100644 --- a/src/main/java/io/alchemynft/attestation/PersonalSignature.java +++ b/src/main/java/org/tokenscript/attestation/core/PersonalSignature.java @@ -1,13 +1,12 @@ -package io.alchemynft.attestation; +package org.tokenscript.attestation.core; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.tokenscript.attestation.core.SignatureUtility; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; public class PersonalSignature extends AbstractSignature { private static final String TYPE_OF_SIGNATURE = "personal"; - public PersonalSignature(AsymmetricCipherKeyPair keys, byte[] unprocessedMsg) { - super(keys, unprocessedMsg, TYPE_OF_SIGNATURE); + public PersonalSignature(AsymmetricKeyParameter signingKey, byte[] unprocessedMsg) { + super(signingKey, unprocessedMsg, TYPE_OF_SIGNATURE); } public PersonalSignature(byte[] rawSignature) { diff --git a/src/main/java/io/alchemynft/attestation/RawSignature.java b/src/main/java/org/tokenscript/attestation/core/RawSignature.java similarity index 55% rename from src/main/java/io/alchemynft/attestation/RawSignature.java rename to src/main/java/org/tokenscript/attestation/core/RawSignature.java index 3090e0a6..1a3e3390 100644 --- a/src/main/java/io/alchemynft/attestation/RawSignature.java +++ b/src/main/java/org/tokenscript/attestation/core/RawSignature.java @@ -1,12 +1,12 @@ -package io.alchemynft.attestation; +package org.tokenscript.attestation.core; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; public class RawSignature extends AbstractSignature { private static final String TYPE_OF_SIGNATURE = "raw"; - public RawSignature(AsymmetricCipherKeyPair keys, byte[] unprocessedMsg) { - super(keys, unprocessedMsg, TYPE_OF_SIGNATURE); + public RawSignature(AsymmetricKeyParameter signingKey, byte[] unprocessedMsg) { + super(signingKey, unprocessedMsg, TYPE_OF_SIGNATURE); } public RawSignature(byte[] signature) { diff --git a/src/main/java/io/alchemynft/attestation/Signature.java b/src/main/java/org/tokenscript/attestation/core/Signature.java similarity index 86% rename from src/main/java/io/alchemynft/attestation/Signature.java rename to src/main/java/org/tokenscript/attestation/core/Signature.java index b7304c61..74c25d27 100644 --- a/src/main/java/io/alchemynft/attestation/Signature.java +++ b/src/main/java/org/tokenscript/attestation/core/Signature.java @@ -1,4 +1,4 @@ -package io.alchemynft.attestation; +package org.tokenscript.attestation.core; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; diff --git a/src/main/java/org/tokenscript/auth/AuthenticatorEncoder.java b/src/main/java/org/tokenscript/attestation/eip712/AuthenticatorEncoder.java similarity index 94% rename from src/main/java/org/tokenscript/auth/AuthenticatorEncoder.java rename to src/main/java/org/tokenscript/attestation/eip712/AuthenticatorEncoder.java index 36253024..19944050 100644 --- a/src/main/java/org/tokenscript/auth/AuthenticatorEncoder.java +++ b/src/main/java/org/tokenscript/attestation/eip712/AuthenticatorEncoder.java @@ -1,4 +1,4 @@ -package org.tokenscript.auth; +package org.tokenscript.attestation.eip712; import com.alphawallet.token.web.Ethereum.web3j.StructuredData.Entry; import java.security.SecureRandom; diff --git a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequest.java b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequest.java index b28f90f9..6c40574f 100644 --- a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequest.java +++ b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequest.java @@ -1,5 +1,9 @@ package org.tokenscript.attestation.eip712; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.tokenscript.attestation.AttestationRequest; import org.tokenscript.attestation.FullProofOfExponent; import org.tokenscript.attestation.IdentifierAttestation.AttestationType; @@ -10,11 +14,8 @@ import org.tokenscript.attestation.core.Validateable; import org.tokenscript.attestation.core.Verifiable; import org.tokenscript.attestation.eip712.Eip712AttestationRequestEncoder.AttestationRequestInternalData; -import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.eip712.Eip712Encoder; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Validator; import org.tokenscript.eip712.JsonEncodable; @@ -67,7 +68,7 @@ public Eip712AttestationRequest(String attestorDomain, long acceptableTimeLimit, this.attestationRequest = request; this.jsonEncoding = makeToken(identifier, signingKey); this.publicKey = retrieveUserPublicKey(jsonEncoding, AttestationRequestInternalData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationRequestInternalData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationRequestInternalData.class); } catch (Exception e ) { throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Could not encode object")); @@ -91,7 +92,7 @@ public Eip712AttestationRequest(String attestorDomain, long acceptableTimeLimit, this.acceptableTimeLimit = acceptableTimeLimit; this.jsonEncoding = jsonEncoding; this.publicKey = retrieveUserPublicKey(jsonEncoding, AttestationRequestInternalData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationRequestInternalData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationRequestInternalData.class); this.attestationRequest = new AttestationRequest(URLUtility.decodeData(data.getPayload())); } catch (Exception e ) { throw ExceptionUtil.throwException(logger, @@ -108,7 +109,7 @@ void constructorCheck() throws IllegalArgumentException { } String makeToken(String identifier, AsymmetricKeyParameter signingKey) throws JsonProcessingException { - Eip712Issuer issuer = new Eip712Issuer(signingKey, encoder); + Eip712Signer issuer = new Eip712Signer(signingKey, encoder); String encodedAttestationRequest = URLUtility.encodeData(attestationRequest.getDerEncoding()); Timestamp timestamp = Nonce.getTimestamp(attestationRequest.getPok().getUnpredictableNumber()); AttestationRequestInternalData data = new AttestationRequestInternalData( @@ -140,7 +141,7 @@ public String getJsonEncoding() { @Override public boolean verify() { if (!attestationRequest.verify()) { - logger.error("Could not verify signature"); + logger.error("Could not verify proof"); return false; } return true; @@ -148,6 +149,10 @@ public boolean verify() { @Override public boolean checkValidity() { + if (!validateDomain(jsonEncoding)) { + logger.error("Domain invalid"); + return false; + } if (!data.getDescription().equals(encoder.getUsageValue())){ logger.error("Description field is incorrect"); return false; diff --git a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequestWithUsage.java b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequestWithUsage.java index fb7478f3..8095273e 100644 --- a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequestWithUsage.java +++ b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationRequestWithUsage.java @@ -18,7 +18,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Validator; import org.tokenscript.eip712.JsonEncodable; @@ -49,7 +49,7 @@ public Eip712AttestationRequestWithUsage(String attestorDomain, this.attestationRequestWithUsage = attestationRequestWithUsage; this.jsonEncoding = makeToken(identifier, attestationRequestWithUsage, signingKey); this.userPublicKey = retrieveUserPublicKey(jsonEncoding, AttestationRequestWUsageData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationRequestWUsageData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationRequestWUsageData.class); } catch (Exception e ) { throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Could not encode object")); @@ -69,7 +69,7 @@ public Eip712AttestationRequestWithUsage(String attestorDomain, this.maxTokenValidityInMs = maxTokenValidityInMs; this.jsonEncoding = jsonEncoding; this.userPublicKey = retrieveUserPublicKey(jsonEncoding, AttestationRequestWUsageData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationRequestWUsageData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationRequestWUsageData.class); this.attestationRequestWithUsage = new AttestationRequestWithUsage(URLUtility.decodeData(data.getPayload())); } catch (Exception e ) { throw ExceptionUtil.throwException(logger, @@ -87,7 +87,7 @@ void constructorCheck() throws IllegalArgumentException { String makeToken(String identifier, AttestationRequestWithUsage attestationRequestWithUsage, AsymmetricKeyParameter signingKey) throws IOException { - Eip712Issuer issuer = new Eip712Issuer(signingKey, encoder); + Eip712Signer issuer = new Eip712Signer(signingKey, encoder); String encodedUseAttestation = URLUtility.encodeData(attestationRequestWithUsage.getDerEncoding()); Timestamp now = new Timestamp(); Timestamp expirationTime = new Timestamp(now.getTime() + maxTokenValidityInMs); diff --git a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationUsage.java b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationUsage.java index e2efee83..c322553e 100644 --- a/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationUsage.java +++ b/src/main/java/org/tokenscript/attestation/eip712/Eip712AttestationUsage.java @@ -14,7 +14,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Validator; import org.tokenscript.eip712.JsonEncodable; @@ -49,7 +49,7 @@ public Eip712AttestationUsage(String attestorDomain, long maxTokenValidityInMs, this.maxTokenValidityInMs = maxTokenValidityInMs; this.jsonEncoding = makeToken(identifier, useAttestation, signingKey); this.userPublicKey = retrieveUserPublicKey(jsonEncoding, AttestationUsageData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationUsageData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationUsageData.class); this.validator = new AttestationAndUsageValidator(useAttestation, identifier, userPublicKey); } catch (Exception e ) { throw ExceptionUtil.throwException(logger, @@ -71,7 +71,7 @@ public Eip712AttestationUsage(String attestorDomain, AsymmetricKeyParameter atte this.maxTokenValidityInMs = maxTokenValidityInMs; this.jsonEncoding = jsonEncoding; this.userPublicKey = retrieveUserPublicKey(jsonEncoding, AttestationUsageData.class); - this.data = retrieveUnderlyingObject(jsonEncoding, AttestationUsageData.class); + this.data = retrieveUnderlyingJson(jsonEncoding, AttestationUsageData.class); UseAttestation useAttestation = new UseAttestation(URLUtility.decodeData(data.getPayload()), attestationIssuerVerificationKey); this.validator = new AttestationAndUsageValidator(useAttestation, data.getIdentifier(), userPublicKey); } catch (Exception e ) { @@ -90,7 +90,7 @@ void constructorCheck() throws IllegalArgumentException { String makeToken(String identifier, UseAttestation useAttestation, AsymmetricKeyParameter signingKey) throws IOException { - Eip712Issuer issuer = new Eip712Issuer(signingKey, encoder); + Eip712Signer issuer = new Eip712Signer(signingKey, encoder); String encodedUseAttestation = URLUtility.encodeData(useAttestation.getDerEncoding()); Timestamp now = new Timestamp(); Timestamp expirationTime = new Timestamp(now.getTime() + maxTokenValidityInMs); diff --git a/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectSigner.java b/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectSigner.java new file mode 100644 index 00000000..914bb82c --- /dev/null +++ b/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectSigner.java @@ -0,0 +1,42 @@ +package org.tokenscript.attestation.eip712; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.InvalidObjectException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.tokenscript.attestation.Timestamp; +import org.tokenscript.attestation.core.ASNEncodable; +import org.tokenscript.attestation.core.ExceptionUtil; +import org.tokenscript.attestation.core.URLUtility; +import org.tokenscript.eip712.Eip712Encoder; +import org.tokenscript.eip712.Eip712Signer; +import org.tokenscript.eip712.FullEip712InternalData; + +/** + * Class for issuing EIP712 tokens containing any ASNEncodable object. + */ +public class Eip712ObjectSigner extends + Eip712Signer { + private static final Logger logger = LogManager.getLogger(Eip712ObjectSigner.class); + private final Eip712Encoder encoder; + + public Eip712ObjectSigner(AsymmetricKeyParameter signingKey, Eip712Encoder encoder) { + super(signingKey, encoder); + this.encoder = encoder; + } + + + public String buildSignedToken(ObjectT attestedObject, String webDomain) { + try { + String encodedObject = URLUtility.encodeData(attestedObject.getDerEncoding()); + Timestamp time = new Timestamp(); + FullEip712InternalData auth = new FullEip712InternalData(encoder.getUsageValue(), encodedObject, time); + return buildSignedTokenFromJsonObject(auth, webDomain); + } catch (InvalidObjectException e) { + throw ExceptionUtil.makeRuntimeException(logger, "Could not retrieve DER encoding of attested object", e); + } catch (JsonProcessingException e) { + throw ExceptionUtil.makeRuntimeException(logger, "Could not build json token", e); + } + } +} diff --git a/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectValidator.java b/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectValidator.java new file mode 100644 index 00000000..8ae8d209 --- /dev/null +++ b/src/main/java/org/tokenscript/attestation/eip712/Eip712ObjectValidator.java @@ -0,0 +1,89 @@ +package org.tokenscript.attestation.eip712; + +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.tokenscript.attestation.AttestedKeyObject; +import org.tokenscript.attestation.ObjectDecoder; +import org.tokenscript.attestation.Timestamp; +import org.tokenscript.attestation.core.SignatureUtility; +import org.tokenscript.attestation.core.URLUtility; +import org.tokenscript.eip712.Eip712Encoder; +import org.tokenscript.eip712.Eip712Validator; +import org.tokenscript.eip712.FullEip712InternalData; + +/** + * Class for validating EIP712 tokens containing any ASNEncodable object. + */ +public class Eip712ObjectValidator extends Eip712Validator { + private static final Logger logger = LogManager.getLogger(Eip712ObjectValidator.class); + private final ObjectDecoder decoder; + private final long acceptableTimeLimit; + + public Eip712ObjectValidator(ObjectDecoder decoder, Eip712Encoder authenticator, + String domain) { + this(decoder, authenticator, domain, Nonce.DEFAULT_NONCE_TIME_LIMIT_MS); + } + + public Eip712ObjectValidator(ObjectDecoder decoder, Eip712Encoder authenticator, + String domain, long acceptableTimeLimit) { + super(domain, authenticator); + this.acceptableTimeLimit = acceptableTimeLimit; + this.decoder = decoder; + } + + public boolean validateRequest(String jsonInput) { + try { + FullEip712InternalData eip712InternalData = retrieveUnderlyingJson(jsonInput, FullEip712InternalData.class); + T attestedObject = retrieveUnderlyingObject(jsonInput); + String signerAddress = SignatureUtility.addressFromKey(attestedObject.getAttestedUserKey()); + + if (!verifySignature(jsonInput, signerAddress, FullEip712InternalData.class)) { + logger.error("Could not verify signature"); + return false; + } + if (!validateDomain(jsonInput)) { + logger.error("Could not validate the domain data"); + return false; + } + if (!validateEip712InternalData(eip712InternalData)) { + logger.error("Could not validate authentication request data"); + return false; + } + if (!attestedObject.checkValidity()) { + logger.error("Could not validate attested object"); + return false; + } + if (!attestedObject.verify()) { + logger.error("Could not verify attested object"); + return false; + } + } catch (Exception e) { + logger.error("Could not decode json request"); + return false; + } + return true; + } + + public T retrieveUnderlyingObject(String jsonInput) throws IOException { + FullEip712InternalData message = retrieveUnderlyingJson(jsonInput, FullEip712InternalData.class); + byte[] attestedObjectBytes = URLUtility.decodeData(message.getPayload()); + T attestedObject = decoder.decode(attestedObjectBytes); + return attestedObject; + } + + private boolean validateEip712InternalData(FullEip712InternalData eip712InternalData) { + if (!eip712InternalData.getDescription().equals(encoder.getUsageValue())) { + logger.error("Description is incorrect"); + return false; + } + Timestamp timestamp = new Timestamp(eip712InternalData.getTimestamp()); + timestamp.setValidity(acceptableTimeLimit); + if (!timestamp.validateTimestamp()) { + logger.error("Invalid timestamp"); + return false; + } + return true; + } + +} diff --git a/src/main/java/org/tokenscript/auth/Eip712AuthIssuer.java b/src/main/java/org/tokenscript/auth/Eip712AuthIssuer.java deleted file mode 100644 index 43e9fffa..00000000 --- a/src/main/java/org/tokenscript/auth/Eip712AuthIssuer.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.tokenscript.auth; - -import org.tokenscript.attestation.AttestedObject; -import org.tokenscript.attestation.core.URLUtility; -import org.tokenscript.attestation.Timestamp; -import com.fasterxml.jackson.core.JsonProcessingException; -import java.security.SecureRandom; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.eip712.Eip712Issuer; -import org.tokenscript.eip712.FullEip712InternalData; - -/** - * Class for issuing EIP712 tokens containing a useDevconTicket object. - * The tokens are supposed to be issued by the user for consumption by a third party website. - */ -public class Eip712AuthIssuer extends Eip712Issuer { - private final AuthenticatorEncoder encoder; - - public Eip712AuthIssuer(AsymmetricKeyParameter signingKey, long chainId) { - this(signingKey, new AuthenticatorEncoder(chainId, new SecureRandom())); - } - - public Eip712AuthIssuer(AsymmetricKeyParameter signingKey, AuthenticatorEncoder encoder) { - super(signingKey, encoder); - this.encoder = encoder; - } - - - public String buildSignedToken(AttestedObject attestedObject, String webDomain) throws JsonProcessingException { - String encodedObject = URLUtility.encodeData(attestedObject.getDerEncoding()); - Timestamp time = new Timestamp(); - FullEip712InternalData auth = new FullEip712InternalData(encoder.getUsageValue(), encodedObject, time); - return buildSignedTokenFromJsonObject(auth, webDomain); - } -} diff --git a/src/main/java/org/tokenscript/auth/Eip712AuthValidator.java b/src/main/java/org/tokenscript/auth/Eip712AuthValidator.java deleted file mode 100644 index db267e5f..00000000 --- a/src/main/java/org/tokenscript/auth/Eip712AuthValidator.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.tokenscript.auth; - -import org.tokenscript.attestation.AttestableObjectDecoder; -import org.tokenscript.attestation.AttestedObject; -import org.tokenscript.attestation.core.Attestable; -import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.core.URLUtility; -import org.tokenscript.attestation.eip712.Nonce; -import org.tokenscript.attestation.Timestamp; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.tokenscript.eip712.Eip712Validator; -import org.tokenscript.eip712.FullEip712InternalData; - -/** - * Class for validating EIP712 tokens containing a useDevconTicket object. - * The tokens are supposed to be issued by the user for consumption by a third party website. - */ -public class Eip712AuthValidator extends Eip712Validator { - private static final Logger logger = LogManager.getLogger(Eip712AuthValidator.class); - private final AsymmetricKeyParameter attestorPublicKey; - private final AttestableObjectDecoder decoder; - private final long acceptableTimeLimit; - - public Eip712AuthValidator(AttestableObjectDecoder decoder, AuthenticatorEncoder authenticator, AsymmetricKeyParameter attestorPublicKey, String domain) { - this(decoder, authenticator, attestorPublicKey, domain, Nonce.DEFAULT_NONCE_TIME_LIMIT_MS); - } - - public Eip712AuthValidator(AttestableObjectDecoder decoder, AuthenticatorEncoder authenticator, AsymmetricKeyParameter attestorPublicKey, String domain, long acceptableTimeLimit) { - super(domain, authenticator); - this.acceptableTimeLimit = acceptableTimeLimit; - this.attestorPublicKey = attestorPublicKey; - this.decoder = decoder; - } - - public boolean validateRequest(String jsonInput) { - try { - FullEip712InternalData auth = retrieveUnderlyingObject(jsonInput, FullEip712InternalData.class); - AttestedObject attestedObject = retrieveAttestedObject(auth); - String signerAddress = SignatureUtility.addressFromKey(attestedObject.getUserPublicKey()); - - if (!verifySignature(jsonInput, signerAddress, FullEip712InternalData.class)) { - logger.error("Could not verify signature"); - return false; - } - if (!validateAuthentication(auth)) { - logger.error("Could not validate authentication request data"); - return false; - } - if (!validateAttestedObject(attestedObject)) { - logger.error("Could not validate attested object"); - return false; - } - } catch (Exception e) { - logger.error("Could not decode json request"); - return false; - } - return true; - } - - private AttestedObject retrieveAttestedObject(FullEip712InternalData message) { - byte[] attestedObjectBytes = URLUtility.decodeData(message.getPayload()); - AttestedObject decodedAttestedObject = new AttestedObject<>(attestedObjectBytes, decoder, attestorPublicKey); - return decodedAttestedObject; - } - - private boolean validateAuthentication(FullEip712InternalData authentication) { - if (!authentication.getDescription().equals(encoder.getUsageValue())) { - logger.error("Description is incorrect"); - return false; - } - Timestamp timestamp = new Timestamp(authentication.getTimestamp()); - timestamp.setValidity(acceptableTimeLimit); - if (!timestamp.validateTimestamp()) { - logger.error("Invalid timestamp"); - return false; - } - return true; - } - - private boolean validateAttestedObject(AttestedObject attestedObject) { - // Validate useAttestableObject - if (!attestedObject.verify()) { - logger.error("Could not verify the attested object"); - return false; - } - if (!attestedObject.checkValidity()) { - logger.error("Attested object is not valid"); - return false; - } - return true; - } - -} diff --git a/src/main/java/org/tokenscript/eip712/Eip712Common.java b/src/main/java/org/tokenscript/eip712/Eip712Common.java index 8e4a5b61..1fcf34f5 100644 --- a/src/main/java/org/tokenscript/eip712/Eip712Common.java +++ b/src/main/java/org/tokenscript/eip712/Eip712Common.java @@ -6,7 +6,10 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.Security; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.tokenscript.attestation.core.ExceptionUtil; import org.tokenscript.attestation.core.Validateable; import org.tokenscript.attestation.core.Verifiable; @@ -14,6 +17,7 @@ * Common class for EIP712 JSON issuance and validation */ public abstract class Eip712Common { + private static final Logger logger = LogManager.getLogger(Eip712Common.class); protected final CryptoFunctions cryptoFunctions; protected final ObjectMapper mapper; protected final Eip712Encoder encoder; @@ -26,6 +30,15 @@ public Eip712Common(Eip712Encoder encoder) { this.encoder = encoder; } + public String getSignatureFromJson(String signedJson) { + try { + Eip712ExternalData data = mapper.readValue(signedJson, Eip712ExternalData.class); + return data.getSignatureInHex(); + } catch (Exception e) { + throw ExceptionUtil.makeRuntimeException(logger, "Could not recover signature from signed json", e); + } + } + public static boolean isDomainValid(String domain) { try { // Check if we get a malformed exception diff --git a/src/main/java/org/tokenscript/eip712/Eip712Issuer.java b/src/main/java/org/tokenscript/eip712/Eip712Signer.java similarity index 56% rename from src/main/java/org/tokenscript/eip712/Eip712Issuer.java rename to src/main/java/org/tokenscript/eip712/Eip712Signer.java index 79b89bea..d4f9aa06 100644 --- a/src/main/java/org/tokenscript/eip712/Eip712Issuer.java +++ b/src/main/java/org/tokenscript/eip712/Eip712Signer.java @@ -1,6 +1,5 @@ package org.tokenscript.eip712; -import org.tokenscript.attestation.core.SignatureUtility; import com.alphawallet.token.entity.EthereumTypedMessage; import com.alphawallet.token.web.Ethereum.web3j.StructuredData; import com.alphawallet.token.web.Ethereum.web3j.StructuredData.EIP712Domain; @@ -9,11 +8,12 @@ import java.nio.charset.StandardCharsets; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.util.encoders.Hex; +import org.tokenscript.attestation.core.SignatureUtility; -public class Eip712Issuer extends Eip712Common { +public class Eip712Signer extends Eip712Common { protected final AsymmetricKeyParameter signingKey; - public Eip712Issuer(AsymmetricKeyParameter signingKey, Eip712Encoder encoder) { + public Eip712Signer(AsymmetricKeyParameter signingKey, Eip712Encoder encoder) { super(encoder); this.signingKey = signingKey; } @@ -22,16 +22,19 @@ public String buildSignedTokenFromJsonObject(T jsonEncodableObject, String webDo if (!Eip712Common.isDomainValid(webDomain)) { throw new IllegalArgumentException("Invalid domain"); } - // Construct a more compact version of the JSON that is more suited for human reading than the full data - String jsonToSign = getEncodedObject(jsonEncodableObject.getSignableVersion(), webDomain); - // Sign this compacted version - EthereumTypedMessage ethereumMessage = new EthereumTypedMessage(jsonToSign, null, 0, - cryptoFunctions); - String signatureInHex = signEIP712Message(ethereumMessage); - // Include the full version of the JSON in the external data - Eip712ExternalData data = new Eip712ExternalData(signatureInHex, - getEncodedObject(jsonEncodableObject, webDomain)); - return mapper.writeValueAsString(data); + byte[] processedMessage = buildTokenToBeSigned(jsonEncodableObject, webDomain); + // Include the full version of the JSON in the external data + Eip712ExternalData data = new Eip712ExternalData(signEIP712Message(processedMessage), + getEncodedObject(jsonEncodableObject, webDomain)); + return mapper.writeValueAsString(data); + } + + public byte[] buildTokenToBeSigned(T jsonEncodableObject, String webDomain) throws JsonProcessingException { + // Construct a more compact version of the JSON that is more suited for human reading than the full data + String jsonToSign = getEncodedObject(jsonEncodableObject.getSignableVersion(), webDomain); + EthereumTypedMessage ethereumMessage = new EthereumTypedMessage(jsonToSign, null, 0, + cryptoFunctions); + return ethereumMessage.getPrehash(); } String getEncodedObject(Eip712InternalData jsonEncodableObject, String webDomain) throws JsonProcessingException { @@ -42,8 +45,8 @@ String getEncodedObject(Eip712InternalData jsonEncodableObject, String webDomain return mapper.writeValueAsString(message); } - private String signEIP712Message(EthereumTypedMessage msg) { - byte[] signature = SignatureUtility.signWithEthereum(msg.getPrehash(), signingKey); - return "0x" + new String(Hex.encode(signature), StandardCharsets.UTF_8); + private String signEIP712Message(byte[] msg) { + byte[] rawSignature = SignatureUtility.signWithEthereum(msg, signingKey); + return "0x" + new String(Hex.encode(rawSignature), StandardCharsets.UTF_8); } } diff --git a/src/main/java/org/tokenscript/eip712/Eip712Validator.java b/src/main/java/org/tokenscript/eip712/Eip712Validator.java index 9065fcc3..67886ab0 100644 --- a/src/main/java/org/tokenscript/eip712/Eip712Validator.java +++ b/src/main/java/org/tokenscript/eip712/Eip712Validator.java @@ -1,7 +1,5 @@ package org.tokenscript.eip712; -import org.tokenscript.attestation.core.ExceptionUtil; -import org.tokenscript.attestation.core.SignatureUtility; import com.alphawallet.token.entity.EthereumTypedMessage; import com.alphawallet.token.web.Ethereum.web3j.StructuredData; import com.alphawallet.token.web.Ethereum.web3j.StructuredData.EIP712Domain; @@ -9,7 +7,6 @@ import com.alphawallet.token.web.Ethereum.web3j.StructuredData.Entry; import com.alphawallet.token.web.Ethereum.web3j.StructuredDataEncoder; import com.fasterxml.jackson.databind.JsonNode; -import java.io.InvalidObjectException; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -18,6 +15,8 @@ import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.util.encoders.Hex; +import org.tokenscript.attestation.core.ExceptionUtil; +import org.tokenscript.attestation.core.SignatureUtility; public class Eip712Validator extends Eip712Common { private static final Logger logger = LogManager.getLogger(Eip712Validator.class); @@ -35,7 +34,7 @@ public Eip712Validator(String domain, Eip712Encoder encoder) { /** * Retrieve the underlying JSON object */ - public T retrieveUnderlyingObject(String signedJsonInput, Class type) { + public T retrieveUnderlyingJson(String signedJsonInput, Class type) { try { Eip712ExternalData allData = mapper.readValue(signedJsonInput, Eip712ExternalData.class); // Use StructuredDataEncoder to ensure that the data structure gets verified @@ -46,28 +45,35 @@ public T retrieveUnderlyingObject(String signedJs } } - private boolean validateDomain(EIP712Domain domainToCheck) { - if (!domainToCheck.getName().equals(domain)) { - logger.error("Domain name is not valid"); - return false; - } - if (!domainToCheck.getVersion().equals(encoder.getProtocolVersion())) { - logger.error("Protocol version is wrong"); - return false; - } - if (!Objects.equals(domainToCheck.getChainId(), encoder.getChainId())) { - logger.error("Chain ID is wrong"); - return false; - } - if (!Objects.equals(domainToCheck.getVerifyingContract(), encoder.getVerifyingContract())) { - logger.error("Verifying contract is wrong"); - return false; + public boolean validateDomain(String signedJsonInput) { + try { + EIP712Domain domainToCheck = restoreDomain(signedJsonInput); + if (!domainToCheck.getName().equals(domain)) { + logger.error("Domain name is not valid"); + return false; + } + if (!domainToCheck.getVersion().equals(encoder.getProtocolVersion())) { + logger.error("Protocol version is wrong"); + return false; + } + if (!Objects.equals(domainToCheck.getChainId(), encoder.getChainId())) { + logger.error("Chain ID is wrong"); + return false; + } + if (!Objects.equals(domainToCheck.getVerifyingContract(), encoder.getVerifyingContract())) { + logger.error("Verifying contract is wrong"); + return false; + } + if (!Objects.equals(domainToCheck.getSalt(), encoder.getSalt())) { + logger.error("Salt is wrong"); + return false; + } + return true; } - if (!Objects.equals(domainToCheck.getSalt(), encoder.getSalt())) { - logger.error("Salt is wrong"); - return false; + catch (Exception e) { + logger.error("Could not restore domain from json"); + throw ExceptionUtil.makeRuntimeException(logger, "Could not restore domain from json", e); } - return true; } public boolean verifySignature(String signedJsonInput, String pkAddress, Class type) { @@ -77,42 +83,42 @@ public boolean verifySignature(String signedJ logger.error("Could not verify signature"); return false; } - } catch (InvalidObjectException e) { - logger.error("Could not decode signature"); + } catch (IllegalArgumentException e) { + logger.error("Could not recover the user key"); return false; } return true; } - public ECPublicKeyParameters retrieveUserPublicKey(String signedJsonInput, Class type) throws InvalidObjectException { + public ECPublicKeyParameters retrieveUserPublicKey(String signedJsonInput, Class type) { try { - byte[] signature = getSignatureFromJson(signedJsonInput); + // substring(2) is needed to remove the "0x" prefix + byte[] rawSignature = Hex.decode(getSignatureFromJson(signedJsonInput).substring(2)); String actuallySignedJson = restoreSignableJson(signedJsonInput, type); EthereumTypedMessage ethereumMessage = new EthereumTypedMessage(actuallySignedJson,null, 0, cryptoFunctions); byte[] messageSigned = ethereumMessage.getPrehash(); - return SignatureUtility.recoverEthPublicKeyFromSignature(messageSigned, signature); + return SignatureUtility.recoverEthPublicKeyFromSignature(messageSigned, rawSignature); } catch (Exception e) { - throw ExceptionUtil.throwException(logger, new InvalidObjectException("Could not recover a valid key")); + throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Could not recover a valid key")); } } - byte[] getSignatureFromJson(String signedJsonInput) throws Exception { - Eip712ExternalData data = mapper.readValue(signedJsonInput, Eip712ExternalData.class); - // Remove the "0x" prefix - String prunedSignature = data.getSignatureInHex().substring(2); - return Hex.decode(prunedSignature); - } - - String restoreSignableJson(String signedJsonInput, Class type) throws Exception { - T fullInternalData = retrieveUnderlyingObject(signedJsonInput, type); + public String restoreSignableJson(String signedJsonInput, Class type) throws Exception { + T fullInternalData = retrieveUnderlyingJson(signedJsonInput, type); Eip712ExternalData data = mapper.readValue(signedJsonInput, Eip712ExternalData.class); + EIP712Domain eip712Domain = restoreDomain(signedJsonInput); JsonNode rootNode = mapper.readTree(data.getJsonSigned()); - EIP712Domain eip712Domain = getDomainFromJson(rootNode); StructuredData.EIP712Message message = new EIP712Message(getTypes(rootNode), getPrimaryType(rootNode), fullInternalData.getSignableVersion(), eip712Domain); return mapper.writeValueAsString(message); } + private EIP712Domain restoreDomain(String signedJsonInput) throws Exception { + Eip712ExternalData data = mapper.readValue(signedJsonInput, Eip712ExternalData.class); + JsonNode rootNode = mapper.readTree(data.getJsonSigned()); + return mapper.readValue(rootNode.get("domain").toString(), EIP712Domain.class); + } + HashMap> getTypes(JsonNode rootOfEip712) throws Exception { return mapper.readValue(rootOfEip712.get("types").toString(), HashMap.class); } @@ -120,15 +126,4 @@ HashMap> getTypes(JsonNode rootOfEip712) throws Exception { String getPrimaryType(JsonNode rootOfEip712) { return rootOfEip712.get("primaryType").asText(); } - - /** - * Retrieve and validate the domain - */ - EIP712Domain getDomainFromJson(JsonNode rootOfEip712) throws Exception { - EIP712Domain eip712Domain = mapper.readValue(rootOfEip712.get("domain").toString(), EIP712Domain.class); - if (!validateDomain(eip712Domain)) { - throw ExceptionUtil.throwException(logger, new InvalidObjectException("Could not verify message")); - } - return eip712Domain; - } } diff --git a/src/test/java/io/alchemynft/attestation/NFTAttestationTest.java b/src/test/java/io/alchemynft/attestation/NFTAttestationTest.java index c21c58ba..db8d53a5 100644 --- a/src/test/java/io/alchemynft/attestation/NFTAttestationTest.java +++ b/src/test/java/io/alchemynft/attestation/NFTAttestationTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -22,16 +21,18 @@ import org.tokenscript.attestation.HelperTest; import org.tokenscript.attestation.IdentifierAttestation; import org.tokenscript.attestation.SignedIdentifierAttestation; +import org.tokenscript.attestation.core.PersonalSignature; +import org.tokenscript.attestation.core.RawSignature; +import org.tokenscript.attestation.core.Signature; import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.core.URLUtility; public class NFTAttestationTest { private static AsymmetricCipherKeyPair subjectKeys; - private static AsymmetricCipherKeyPair issuerKeys; private static AsymmetricCipherKeyPair attestorKeys; private static SecureRandom rand; - static SignedIdentifierAttestation signedIdentifierAtt; - private static SignedNFTAttestation signedNftAttestation; + private static SignedIdentifierAttestation signedIdentifierAtt; + private static NFTAttestation nftAtt; + private static IdentifierAttestation att; private static ERC721Token[] nfts; @Mock @@ -50,41 +51,52 @@ public static void setupKeys() throws Exception { rand.setSeed("seed".getBytes()); subjectKeys = SignatureUtility.constructECKeysWithSmallestY(rand); attestorKeys = SignatureUtility.constructECKeys(rand); - issuerKeys = SignatureUtility.constructECKeys(rand); - IdentifierAttestation att = new IdentifierAttestation("205521676", "https://twitter.com/zhangweiwu", subjectKeys.getPublic()); + att = new IdentifierAttestation("205521676", "https://twitter.com/zhangweiwu", subjectKeys.getPublic()); assertTrue(att.checkValidity()); signedIdentifierAtt = new SignedIdentifierAttestation(att, attestorKeys); nfts = new ERC721Token[] { new ERC721Token("0xa567f5A165545Fa2639bBdA79991F105EADF8522", "25"), new ERC721Token("0xa567f5A165545Fa2639bBdA79991F105EADF8522", "26") }; + nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); System.out.println("SubjectPublicKey's Fingerprint (summarised as Ethereum address):\n" + SignatureUtility.addressFromKey(subjectKeys.getPublic())); } @Test - public void sunshine() { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - //construct SignedNFTAttestation using subject key - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - assertTrue(signedNftAttestation.verify()); - assertTrue(signedNftAttestation.checkValidity()); + public void sunshineV1() { + sunshine(new SignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 1)); + } + + @Test + public void sunshineV2() { + sunshine(new SignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 2)); + } + + @Test + public void sunshineEip() { + sunshine(new SignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 3)); + } + + public void sunshine(InternalSignedNFTAttestation signedNFTAttestation) { + assertTrue(signedNFTAttestation.verify()); + assertTrue(signedNFTAttestation.checkValidity()); + assertEquals(SignatureUtility.addressFromKey(subjectKeys.getPublic()), SignatureUtility.addressFromKey(signedNFTAttestation.getNFTAttestationVerificationKey())); + assertArrayEquals(nftAtt.getDerEncoding(), signedNFTAttestation.getUnsignedAttestation().getDerEncoding()); } @Test - public void testNFTAttestation() throws Exception + public void testNFTAttestationV1() throws Exception { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - //construct SignedNFTAttestation using subject key - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); + LegacySignedNFTAttestation signedNFTAttestation = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 1); Path p = Files.createTempFile("unsigned_nftAttestation", ".der"); System.out.println("To check the unsigned NFT attestation, run this:"); System.out.println("$ openssl asn1parse -inform DER -in " + p.toString()); - Files.write(p, signedNftAttestation.getDerEncoding()); + Files.write(p, signedNFTAttestation.getDerEncoding()); //Extract the Ethereum signature - Signature sig = signedNftAttestation.getSignature(); + Signature sig = signedNFTAttestation.getSignature(); //generate NFTAttestation from the NFTAttestation bytes NFTAttestation nftAttestation2 = new NFTAttestation(nftAtt.getDerEncoding(), @@ -94,151 +106,192 @@ public void testNFTAttestation() throws Exception assertTrue(nftAttestation2.verify()); //Generate SignedNFTAttestation using the reconstructed NFTAttestation and the extracted Ethereum signature - SignedNFTAttestation signedNFTAttestation2 = new SignedNFTAttestation(nftAttestation2, sig); + LegacySignedNFTAttestation signedNFTAttestation2 = new LegacySignedNFTAttestation(nftAttestation2, sig); assertTrue(signedNFTAttestation2.checkValidity()); - assertTrue(signedNftAttestation.checkValidity()); + assertTrue(signedNFTAttestation.checkValidity()); assertArrayEquals(signedNFTAttestation2.getUnsignedAttestation().getDerEncoding(), nftAtt.getDerEncoding()); - assertArrayEquals(signedNFTAttestation2.getDerEncoding(), signedNftAttestation.getDerEncoding()); + assertArrayEquals(signedNFTAttestation2.getDerEncoding(), signedNFTAttestation.getDerEncoding()); } @Test - public void consistentEncoding() throws IOException { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - SignedNFTAttestation decodedNFTAtt = new SignedNFTAttestation(signedNftAttestation.getDerEncoding(), attestorKeys.getPublic()); + public void testEipNFTAttestation() throws Exception { + InternalSignedNFTAttestation signedNFTAttestation = new Eip712SignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); + Path p = Files.createTempFile("unsigned_nftAttestation", ".der"); + + System.out.println("To check the unsigned NFT attestation, run this:"); + System.out.println("$ openssl asn1parse -inform DER -in " + p.toString()); + Files.write(p, signedNFTAttestation.getDerEncoding()); + + //generate NFTAttestation from the NFTAttestation bytes + NFTAttestation nftAttestation2 = new NFTAttestation(nftAtt.getDerEncoding(), + attestorKeys.getPublic()); + + //check recovered signed attestation within the wrapping + assertTrue(nftAttestation2.verify()); + } + + @Test + public void versionDiscovery() throws Exception { + LegacySignedNFTAttestation att = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 2); + SignedNFTAttestation otherAtt = new SignedNFTAttestation(att.getUnsignedAttestation(), att.getSignature()); + assertTrue(otherAtt.checkValidity()); + assertTrue(otherAtt.verify()); + assertArrayEquals(att.getDerEncoding(), otherAtt.getDerEncoding()); + } + + @Test + public void consistentEncodingV1() throws Exception { + consistentEncodingLegacy(new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 1)); + } + + @Test + public void consistentEncodingV2() throws Exception { + consistentEncodingLegacy(new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 2)); + } + + private void consistentEncodingLegacy(LegacySignedNFTAttestation signedNFTAttestation) throws Exception { + LegacySignedNFTAttestation decodedNFTAtt = new LegacySignedNFTAttestation(signedNFTAttestation.getDerEncoding(), attestorKeys.getPublic()); assertTrue(decodedNFTAtt.verify()); assertTrue(decodedNFTAtt.checkValidity()); - assertArrayEquals(signedNftAttestation.getDerEncoding(), decodedNFTAtt.getDerEncoding()); + assertArrayEquals(signedNFTAttestation.getSignature().getRawSignature(), decodedNFTAtt.getSignature().getRawSignature()); + assertEquals(SignatureUtility.addressFromKey(signedNFTAttestation.getNFTAttestationVerificationKey()), + SignatureUtility.addressFromKey(signedNFTAttestation.getNFTAttestationVerificationKey())); + assertArrayEquals(signedNFTAttestation.getDerEncoding(), decodedNFTAtt.getDerEncoding()); } @Test - public void testGetters() throws IOException { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - //construct SignedNFTAttestation using subject key - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - assertEquals(SignatureUtility.addressFromKey(signedNftAttestation.getAttestationVerificationKey()), - SignatureUtility.addressFromKey(subjectKeys.getPublic())); - assertArrayEquals(nftAtt.getTokens(), nfts); + public void consistentEncodingEip() throws Exception { + Eip712SignedNFTAttestation signedNFTAttestation = new Eip712SignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); + Eip712SignedNFTAttestation decodedNFTAtt = new Eip712SignedNFTAttestation(signedNFTAttestation.getSignedEIP712(), attestorKeys.getPublic()); + assertTrue(decodedNFTAtt.verify()); + assertTrue(decodedNFTAtt.checkValidity()); + assertEquals(signedNFTAttestation.getSignature(), decodedNFTAtt.getSignature()); + assertEquals(SignatureUtility.addressFromKey(signedNFTAttestation.getNFTAttestationVerificationKey()), + SignatureUtility.addressFromKey(signedNFTAttestation.getNFTAttestationVerificationKey())); + assertEquals(signedNFTAttestation.getSignedEIP712(), decodedNFTAtt.getSignedEIP712()); + assertArrayEquals(signedNFTAttestation.getUnsignedAttestation().getDerEncoding(), + decodedNFTAtt.getDerEncoding()); } @Test - public void testPublicAttestation() { - assertTrue(signedIdentifierAtt.checkValidity()); - assertTrue(signedIdentifierAtt.verify()); - assertTrue(SignatureUtility.verifyEthereumSignature( - signedIdentifierAtt.getUnsignedAttestation().getPrehash(), signedIdentifierAtt.getSignature(), attestorKeys.getPublic())); + public void APIWrapperV1() throws Exception { + LegacySignedNFTAttestation refAtt = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 1); + sunshine(new SignedNFTAttestation(refAtt.getDerEncoding(), attestorKeys.getPublic())); + sunshine(new SignedNFTAttestation(refAtt.getUnsignedAttestation(), refAtt.getSignature())); + } + + @Test + public void APIWrapperV2() throws Exception { + LegacySignedNFTAttestation refAtt = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 2); + sunshine(new SignedNFTAttestation(refAtt.getDerEncoding(), attestorKeys.getPublic())); + sunshine(new SignedNFTAttestation(refAtt.getUnsignedAttestation(), refAtt.getSignature())); + } + + @Test + public void APIWrapperV3() throws Exception { + Eip712SignedNFTAttestation refAtt = new Eip712SignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); + sunshine(new SignedNFTAttestation(refAtt.getSignedEIP712().getBytes(StandardCharsets.UTF_8), attestorKeys.getPublic())); } @Test public void testDecoding() throws Exception { IdentifierAttestation att = HelperTest.makeMaximalAtt(subjectKeys.getPublic()); - SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, issuerKeys); - assertTrue(SignatureUtility.verifyEthereumSignature(att.getPrehash(), signed.getSignature(), issuerKeys.getPublic())); + SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); + assertTrue(SignatureUtility.verifyEthereumSignature(att.getPrehash(), signed.getSignature(), attestorKeys.getPublic())); assertArrayEquals(att.getPrehash(), signed.getUnsignedAttestation().getPrehash()); byte[] signedEncoded = signed.getDerEncoding(); - SignedIdentifierAttestation newSigned = new SignedIdentifierAttestation(signedEncoded, issuerKeys.getPublic()); + SignedIdentifierAttestation newSigned = new SignedIdentifierAttestation(signedEncoded, attestorKeys.getPublic()); assertArrayEquals(signed.getDerEncoding(), newSigned.getDerEncoding()); } @Test - public void defaultSigningVersion() throws IOException { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - SignedNFTAttestation newSignedNftAtt = new SignedNFTAttestation(signedNftAttestation.getUnsignedAttestation(), signedNftAttestation.getSignature()); - assertArrayEquals(signedNftAttestation.getDerEncoding(), newSignedNftAtt.getDerEncoding()); - assertTrue(newSignedNftAtt.verify()); - assertTrue(newSignedNftAtt.checkValidity()); - SignedNFTAttestation otherConstructor = new SignedNFTAttestation(newSignedNftAtt.getDerEncoding(), attestorKeys.getPublic()); - assertArrayEquals(signedNftAttestation.getDerEncoding(), otherConstructor.getDerEncoding()); - assertTrue(otherConstructor.verify()); - assertTrue(otherConstructor.checkValidity()); - } - - @Test - public void oldVersionSigning() throws IOException { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys, 1); - SignedNFTAttestation newSignedNftAtt = new SignedNFTAttestation(signedNftAttestation.getUnsignedAttestation(), signedNftAttestation.getSignature()); - assertArrayEquals(signedNftAttestation.getDerEncoding(), newSignedNftAtt.getDerEncoding()); - assertTrue(newSignedNftAtt.verify()); - assertTrue(newSignedNftAtt.checkValidity()); - SignedNFTAttestation otherConstructor = new SignedNFTAttestation(newSignedNftAtt.getDerEncoding(), attestorKeys.getPublic()); - assertArrayEquals(signedNftAttestation.getDerEncoding(), otherConstructor.getDerEncoding()); - assertTrue(otherConstructor.verify()); - assertTrue(otherConstructor.checkValidity()); + public void legacyDoesNotSupportV3() { + assertThrows(IllegalArgumentException.class, ()-> new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 3)); } @Test - public void unknownVersion() { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, subjectKeys, 42)); + public void wrongSignatureType() { + assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, new RawSignature(new byte[] {0x00}))); } @Test - public void unknownVersionOtherConstructor() { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - Signature rawSig = new RawSignature(subjectKeys, nftAtt.getDerEncoding()); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, rawSig)); + public void wrongSignatureVersion() { + assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, subjectKeys.getPrivate(), 4)); } @Test - public void badSignatureVersion() { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - assertThrows(IllegalArgumentException.class, ()-> signedNftAttestation.makeSignature(new byte[] {0x42}, 42)); + public void testInvalidEncoding() { + Mockito.when(mockedNftAttestation.getDerEncoding()).thenReturn(new byte[] {0x42}); + Mockito.when(mockedNftAttestation.getSignedIdentifierAttestation()).thenReturn(signedIdentifierAtt); + Exception e = assertThrows(IllegalArgumentException.class, ()-> new Eip712SignedNFTAttestation(mockedNftAttestation, + subjectKeys.getPrivate())); + assertEquals("Could not decode underlying NFTAttestation", e.getMessage()); } @Test - public void badSignatureVersionOtherVersion() { - NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - assertThrows(IllegalArgumentException.class, ()-> signedNftAttestation.makeSignature(subjectKeys, 42)); + public void testInvalidEipKey() { + Eip712SignedNFTAttestation refAtt = new Eip712SignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); + Exception e = assertThrows(IllegalArgumentException.class, ()-> + new SignedNFTAttestation(refAtt.getSignedEIP712().getBytes(StandardCharsets.UTF_8), SignatureUtility.constructECKeys(rand).getPublic())); + assertEquals("Could not decode SignedNFTAttestation", e.getMessage()); } @Test - public void signingVersion1Included() throws IOException { - String urlEncodedSignedNftAtt = "MIICqTCCAlMwggIXMIIBxKADAgETAgEBMAkGByqGSM49BAIwGTEXMBUGA1UEAwwOYXR0ZXN0YXRpb24uaWQwIhgPMjAyMTExMDkxNjIwMThaGA85OTk5MTIzMTIyNTk1OVowOTE3MDUGCSsGAQQBgXoBOQwoaHR0cHM6Ly90d2l0dGVyLmNvbS96aGFuZ3dlaXd1IDIwNTUyMTY3NjCCATMwgewGByqGSM49AgEwgeACAQEwLAYHKoZIzj0BAQIhAP____________________________________7___wvMEQEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwRBBHm-Zn753LusVaBilc6HCwcCm_zbLc4o2VnygVsW-BeYSDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1LgCIQD____________________-uq7c5q9IoDu_0l6M0DZBQQIBAQNCAASVDHwL7SPDysXMMbu5qtm7VTI4eIJnCsKxzfB5mrDrx2TCZ_cE6P3aB5arg5ek0hAQJNJMTv_2lbOkF_LtDkjNMAkGByqGSM49BAIDQgD8Wu2eGeRW1GNFxOk5Srdn4E968ML7MUINj55zBqhuOhUWmosV5d4VsarkmpCmlwAXxvIpt7UcFP4cK8QuwH89GzA2MBkEFKVn9aFlVF-iY5u9p5mR8QXq34UiBAEZMBkEFKVn9aFlVF-iY5u9p5mR8QXq34UiBAEaAgEBMAkGByqGSM49BAIDQgCrpY0RQ3LNfJd6YgYEC-etEU_oJKUAA6WP0TRfZITeQVNNm21BOFQc-iiXs053UcSy1y29tbUPt1wp4VRU8Qu4Gw=="; - signedNftAttestation = new SignedNFTAttestation(URLUtility.decodeData(urlEncodedSignedNftAtt), attestorKeys.getPublic()); - SignedNFTAttestation newSignedNftAtt = new SignedNFTAttestation(signedNftAttestation.getUnsignedAttestation(), signedNftAttestation.getSignature()); - assertTrue(newSignedNftAtt.verify()); - assertTrue(newSignedNftAtt.checkValidity()); + public void badSignatureV1() { + Signature wrongSignature = new PersonalSignature(subjectKeys.getPrivate(), "something wrong".getBytes( + StandardCharsets.UTF_8)); + assertThrows(IllegalArgumentException.class, ()-> new LegacySignedNFTAttestation(nftAtt, wrongSignature)); } @Test - public void badSignature() { + public void badSigningKeyV1() { + AsymmetricCipherKeyPair notAttestedKeys = SignatureUtility.constructECKeysWithSmallestY(rand); NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - Signature wrongSignature = new PersonalSignature(subjectKeys, "something wrong".getBytes( - StandardCharsets.UTF_8)); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, wrongSignature)); + assertThrows(IllegalArgumentException.class, ()-> new LegacySignedNFTAttestation(nftAtt, notAttestedKeys.getPrivate())); } - @Test - public void badSigningKey() { + public void badSigningKeyV2() { AsymmetricCipherKeyPair notAttestedKeys = SignatureUtility.constructECKeysWithSmallestY(rand); NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAtt, nfts); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, notAttestedKeys)); + assertThrows(IllegalArgumentException.class, ()-> new Eip712SignedNFTAttestation(nftAtt, notAttestedKeys.getPrivate())); } @Test - public void badNftAttestation() { + public void badNftAttestationV1() { Mockito.when(mockedNftAttestation.verify()).thenReturn(false); Mockito.when(mockedNftAttestation.getDerEncoding()).thenReturn(new byte[] {0x42}); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(mockedNftAttestation, subjectKeys)); + assertThrows(RuntimeException.class, ()-> new LegacySignedNFTAttestation(mockedNftAttestation, subjectKeys.getPrivate())); + } + @Test + public void badNftAttestationV2() { + Mockito.when(mockedNftAttestation.verify()).thenReturn(false); + Mockito.when(mockedNftAttestation.getDerEncoding()).thenReturn(new byte[] {0x42}); + assertThrows(RuntimeException.class, ()-> new Eip712SignedNFTAttestation(mockedNftAttestation, subjectKeys.getPrivate())); } @Test - public void unverifiableSignedIdentifierAtt() { - NFTAttestation realNftAtt = new NFTAttestation(signedIdentifierAtt, nfts); + public void unverifiableSignedIdentifierAttV1() { Mockito.when(mockedSignedIdentifierAtt.verify()).thenReturn(false); - Mockito.when(mockedSignedIdentifierAtt.getDerEncoding()).thenReturn(realNftAtt.getDerEncoding()); - NFTAttestation nftAtt = new NFTAttestation(mockedSignedIdentifierAtt, nfts); - assertThrows(IllegalArgumentException.class, ()-> new SignedNFTAttestation(nftAtt, subjectKeys)); + Mockito.when(mockedSignedIdentifierAtt.getDerEncoding()).thenReturn(nftAtt.getDerEncoding()); + Mockito.when(mockedSignedIdentifierAtt.getUnsignedAttestation()).thenReturn( + att); + NFTAttestation mockedNftAtt = new NFTAttestation(mockedSignedIdentifierAtt, nfts); + assertThrows(IllegalArgumentException.class, ()-> new LegacySignedNFTAttestation(mockedNftAtt, subjectKeys.getPrivate())); + } + + @Test + public void unverifiableSignedIdentifierAttV2() { + Mockito.when(mockedSignedIdentifierAtt.verify()).thenReturn(false); + Mockito.when(mockedSignedIdentifierAtt.getDerEncoding()).thenReturn(nftAtt.getDerEncoding()); + Mockito.when(mockedSignedIdentifierAtt.getUnsignedAttestation()).thenReturn( + att); + NFTAttestation mockedNftAtt = new NFTAttestation(mockedSignedIdentifierAtt, nfts); + assertThrows(IllegalArgumentException.class, ()-> new LegacySignedNFTAttestation(mockedNftAtt, subjectKeys.getPrivate())); } @Test - public void invalidSignedIdentifierAtt() throws Exception { + public void invalidSignedIdentifierAttV1() throws Exception { NFTAttestation realNftAtt = new NFTAttestation(signedIdentifierAtt, nfts); IdentifierAttestation identifierAttestation = new IdentifierAttestation("205521676", "https://twitter.com/zhangweiwu", subjectKeys.getPublic()); Mockito.when(mockedSignedIdentifierAtt.verify()).thenReturn(true); @@ -246,8 +299,18 @@ public void invalidSignedIdentifierAtt() throws Exception { Mockito.when(mockedSignedIdentifierAtt.getDerEncoding()).thenReturn(realNftAtt.getDerEncoding()); Mockito.when(mockedSignedIdentifierAtt.getUnsignedAttestation()).thenReturn(identifierAttestation); NFTAttestation nftAtt = new NFTAttestation(mockedSignedIdentifierAtt, nfts); - signedNftAttestation = new SignedNFTAttestation(nftAtt, subjectKeys); - assertTrue(signedNftAttestation.verify()); - assertFalse(signedNftAttestation.checkValidity()); + InternalSignedNFTAttestation newSignedNftAttestation = new LegacySignedNFTAttestation(nftAtt, subjectKeys.getPrivate()); + assertTrue(newSignedNftAttestation.verify()); + assertFalse(newSignedNftAttestation.checkValidity()); + } + + @Test + public void invalidSignedIdentifierAttV2() throws Exception { + AsymmetricCipherKeyPair otherKeys = SignatureUtility.constructECKeys(rand); + IdentifierAttestation identifierAttestation = new IdentifierAttestation("205521676", "https://twitter.com/zhangweiwu", otherKeys.getPublic()); + SignedIdentifierAttestation signedIdentifierAttestation = new SignedIdentifierAttestation(identifierAttestation, attestorKeys); + NFTAttestation nftAtt = new NFTAttestation(signedIdentifierAttestation, nfts); + Exception e = assertThrows(IllegalArgumentException.class, ()-> new Eip712SignedNFTAttestation(nftAtt, subjectKeys.getPrivate())); + assertEquals("The NFTAttestation is invalid", e.getMessage()); } } diff --git a/src/test/java/io/alchemynft/attestation/SignatureTest.java b/src/test/java/io/alchemynft/attestation/SignatureTest.java deleted file mode 100644 index 728da85d..00000000 --- a/src/test/java/io/alchemynft/attestation/SignatureTest.java +++ /dev/null @@ -1,125 +0,0 @@ -package io.alchemynft.attestation; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; -import java.util.Arrays; -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.tokenscript.attestation.core.SignatureUtility; - -public class SignatureTest { - - private static AsymmetricCipherKeyPair subjectKeys; - private static SecureRandom rand; - private static final byte[] MSG = "some message".getBytes(StandardCharsets.UTF_8); - - @BeforeAll - public static void setupKeys() throws Exception { - rand = SecureRandom.getInstance("SHA1PRNG", "SUN"); - rand.setSeed("seed".getBytes()); - subjectKeys = SignatureUtility.constructECKeysWithSmallestY(rand); - } - - @Test - public void personal() { - AbstractSignature sig = new PersonalSignature(subjectKeys, MSG); - sunshine(sig); - ensureProcessing(sig); - wrongMessage(sig); - wrongKeys(sig); - } - - @Test - public void raw() { - AbstractSignature sig = new RawSignature(subjectKeys, MSG); - sunshine(sig); - wrongMessage(sig); - wrongKeys(sig); - } - - @Test - public void compressed() { - Signature sig = new CompressedMsgSignature(subjectKeys, MSG); - sunshine(sig); - wrongMessage(sig); - wrongKeys(sig); - } - - - @Test - public void expectedRaw() { - AbstractSignature sig = new RawSignature(subjectKeys, MSG); - assertArrayEquals(sig.getRawSignature(), SignatureUtility.signWithEthereum(MSG, subjectKeys.getPrivate())); - assertEquals(sig.getTypeOfSignature(), "raw"); - Arrays.equals(sig.processMessage(MSG), MSG); - } - - @Test - public void expectedCompressedType() { - Signature sig = new CompressedMsgSignature(subjectKeys, MSG, "prefix ", " postfix"); - assertEquals(sig.getTypeOfSignature(), "compressed"); - } - - @Test - public void otherConstructorRaw() { - Signature sig = new RawSignature(subjectKeys, MSG); - Signature newSig = new RawSignature(sig.getRawSignature()); - sunshine(newSig); - assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); - } - - @Test - public void expectedPersonal() { - Signature sig = new PersonalSignature(subjectKeys, MSG); - assertArrayEquals(sig.getRawSignature(), SignatureUtility.signPersonalMsgWithEthereum(MSG, subjectKeys.getPrivate())); - assertEquals(sig.getTypeOfSignature(), "personal"); - } - - @Test - public void compressedReference() { - CompressedMsgSignature sig = new CompressedMsgSignature(subjectKeys, MSG, "prefix ", " postfix"); - assertArrayEquals(sig.processMessage(MSG), "prefix 0x9DF8DBA3720D00BD48AD744722021EF91B035E273BCCFB78660CA8DF9574B086 postfix".getBytes( - StandardCharsets.UTF_8)); - } - - @Test - public void otherConstructorPersonal() { - Signature sig = new PersonalSignature(subjectKeys, MSG); - Signature newSig = new PersonalSignature(sig.getRawSignature()); - sunshine(newSig); - assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); - } - - @Test - public void otherConstructorCompressed() { - Signature sig = new CompressedMsgSignature(subjectKeys, MSG); - Signature newSig = new CompressedMsgSignature(sig.getRawSignature()); - sunshine(newSig); - assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); - } - - public void sunshine(Signature sig) { - assertNotNull(sig.getRawSignature()); - assertTrue(sig.getRawSignature().length > 5); - assertTrue(sig.verify(MSG, subjectKeys.getPublic())); - } - - public void ensureProcessing(AbstractSignature sig) { - assertFalse(Arrays.equals(sig.processMessage(MSG), MSG)); - } - - public void wrongMessage(Signature sig) { - assertFalse(sig.verify("some other message".getBytes(StandardCharsets.UTF_8), subjectKeys.getPublic())); - } - - public void wrongKeys(Signature sig) { - assertFalse(sig.verify(MSG, SignatureUtility.constructECKeysWithSmallestY(rand).getPublic())); - } -} diff --git a/src/test/java/org/tokenscript/auth/UnpredictableNumberToolTest.java b/src/test/java/org/devcon/ticket/UnpredictableNumberToolTest.java similarity index 95% rename from src/test/java/org/tokenscript/auth/UnpredictableNumberToolTest.java rename to src/test/java/org/devcon/ticket/UnpredictableNumberToolTest.java index 5f972de6..4ce5c991 100644 --- a/src/test/java/org/tokenscript/auth/UnpredictableNumberToolTest.java +++ b/src/test/java/org/devcon/ticket/UnpredictableNumberToolTest.java @@ -1,4 +1,4 @@ -package org.tokenscript.auth; +package org.devcon.ticket; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -12,8 +12,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.tokenscript.auth.UnpredictableNumberBundle; -import org.tokenscript.auth.UnpredictableNumberTool; public class UnpredictableNumberToolTest { private static final String DOMAIN = "http://www.hotel-bogota.com"; diff --git a/src/test/java/org/devcon/ticket/UseTicketBundleTest.java b/src/test/java/org/devcon/ticket/UseTicketBundleTest.java index 030afae0..2c6ea38d 100644 --- a/src/test/java/org/devcon/ticket/UseTicketBundleTest.java +++ b/src/test/java/org/devcon/ticket/UseTicketBundleTest.java @@ -24,8 +24,6 @@ import org.tokenscript.attestation.SignedIdentifierAttestation; import org.tokenscript.attestation.core.AttestationCrypto; import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.auth.UnpredictableNumberBundle; -import org.tokenscript.auth.UnpredictableNumberTool; public class UseTicketBundleTest { private static final String DOMAIN = "http://www.hotel-bogota.com"; @@ -73,12 +71,12 @@ public void makeUseTicket() { .makeUnsignedStandardAtt(subjectKeys.getPublic(), ATTESTATION_SECRET, MAIL ); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); - useTicket = new AttestedObject(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, + useTicket = new AttestedObject(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, un.getNumber().getBytes(StandardCharsets.UTF_8), crypto); Mockito.when(mockedUseTicket.verify()).thenReturn(true); Mockito.when(mockedUseTicket.getDerEncoding()).thenReturn(new byte[] {0x00}); - Mockito.when(mockedUseTicket.getUserPublicKey()).thenReturn(subjectKeys.getPublic()); + Mockito.when(mockedUseTicket.getAttestedUserKey()).thenReturn(subjectKeys.getPublic()); Mockito.when(mockedUn.getDomain()).thenReturn(DOMAIN); Mockito.when(mockedUn.getExpiration()).thenReturn(Long.MAX_VALUE); @@ -151,7 +149,7 @@ public void unverifiableUseTicketConstructorFailure() throws Exception { @Test public void badChallengeSignatureConstructor() throws Exception { // Return wrong key, it is supposed to be the subjectKey - Mockito.when(mockedUseTicket.getUserPublicKey()).thenReturn(attestorKeys.getPublic()); + Mockito.when(mockedUseTicket.getAttestedUserKey()).thenReturn(attestorKeys.getPublic()); assertThrows(IllegalArgumentException.class, ()-> new UseTicketBundle(mockedUseTicket, un, subjectKeys.getPrivate())); } diff --git a/src/test/java/org/devcon/ticket/UseTicketTest.java b/src/test/java/org/devcon/ticket/UseTicketTest.java index 55a3af66..84af3f70 100644 --- a/src/test/java/org/devcon/ticket/UseTicketTest.java +++ b/src/test/java/org/devcon/ticket/UseTicketTest.java @@ -35,8 +35,6 @@ import org.tokenscript.attestation.core.DERUtility; import org.tokenscript.attestation.core.SignatureUtility; import org.tokenscript.attestation.demo.SmartContract; -import org.tokenscript.auth.UnpredictableNumberBundle; -import org.tokenscript.auth.UnpredictableNumberTool; public class UseTicketTest { private static final String MAIL = "test@test.ts"; @@ -75,7 +73,7 @@ public void makeAttestedTicket() { IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(subjectKeys.getPublic(), ATTESTATION_SECRET, MAIL ); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); - attestedTicket = new AttestedObject(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); + attestedTicket = new AttestedObject(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); assertTrue(attestedTicket.verify()); assertTrue(attestedTicket.checkValidity()); } @@ -117,7 +115,8 @@ public void testWithUnpredictableNumberBundle() { Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); UnpredictableNumberTool unt = new UnpredictableNumberTool(rand, new byte[] { 0x01, 0x02}, "http://www.domain.com"); UnpredictableNumberBundle un = unt.getUnpredictableNumberBundle(); - AttestedObject attestedTicket = new AttestedObject(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, un.getNumber().getBytes(), crypto); + AttestedObject attestedTicket = new AttestedObject(ticket, signed, + ATTESTATION_SECRET, TICKET_SECRET, un.getNumber().getBytes(), crypto); assertTrue(attestedTicket.verify()); assertTrue(attestedTicket.checkValidity()); // Validate the UN is correct @@ -139,7 +138,8 @@ public void testDecoding() throws InvalidObjectException { newAttestedTicket.getAttestableObject().getDerEncoding()); assertArrayEquals(attestedTicket.getAtt().getDerEncoding(), newAttestedTicket.getAtt().getDerEncoding()); assertArrayEquals(attestedTicket.getPok().getDerEncoding(), newAttestedTicket.getPok().getDerEncoding()); - assertEquals(attestedTicket.getUserPublicKey(), subjectKeys.getPublic()); + assertEquals(SignatureUtility.addressFromKey(attestedTicket.getAttestedUserKey()), + SignatureUtility.addressFromKey(subjectKeys.getPublic())); assertArrayEquals(attestedTicket.getDerEncoding(), newAttestedTicket.getDerEncoding()); AttestedObject newConstructor = new AttestedObject(attestedTicket.getAttestableObject(), @@ -154,7 +154,7 @@ public void testSmartContractDecode() throws Exception { IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(subjectKeys.getPublic(), ATTESTATION_SECRET, MAIL); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); - AttestedObject useTicket = new AttestedObject<>(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); + AttestedObject useTicket = new AttestedObject<>(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); //now attempt to dump data from contract: TicketAttestationReturn tar = contract.callVerifyTicketAttestation(useTicket.getDerEncoding()); @@ -172,7 +172,7 @@ public void testRebuildComponents() { IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(subjectKeys.getPublic(), ATTESTATION_SECRET, MAIL ); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); - AttestedObject useTicket = new AttestedObject<>(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); + AttestedObject useTicket = new AttestedObject<>(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); AttestedObject newUseTicket = new AttestedObject(useTicket.getDerEncoding(), new TicketDecoder( ticketIssuerKeys.getPublic()), attestorKeys.getPublic()); @@ -278,7 +278,7 @@ public void testNegativeConstruction() { // Add an extra t in the mail Ticket ticket = new Ticket("testt@test.ts", CONFERENCE_ID, TICKET_ID, TICKET_CLASS, subjectKeys, TICKET_SECRET); try { - AttestedObject current = new AttestedObject(ticket, signed, subjectKeys.getPublic(), ATTESTATION_SECRET, + AttestedObject current = new AttestedObject(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); fail(); } catch (RuntimeException e) { @@ -293,7 +293,7 @@ public void testNegativeConstruction2() { Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, subjectKeys, TICKET_SECRET); try { // Wrong subject secret - AttestedObject current = new AttestedObject(ticket, signed, subjectKeys.getPublic(), + AttestedObject current = new AttestedObject(ticket, signed, TICKET_SECRET.add(BigInteger.ONE), ATTESTATION_SECRET, UN, crypto); fail(); } catch (RuntimeException e) { @@ -301,7 +301,7 @@ public void testNegativeConstruction2() { } try { // Wrong attestation secret - AttestedObject current = new AttestedObject(ticket, signed, subjectKeys.getPublic(), TICKET_SECRET, + AttestedObject current = new AttestedObject(ticket, signed, TICKET_SECRET, ATTESTATION_SECRET.add(BigInteger.ONE), UN, crypto); fail(); } catch (RuntimeException e) { @@ -309,22 +309,11 @@ public void testNegativeConstruction2() { } try { // Correlated secrets - AttestedObject current = new AttestedObject(ticket, signed, subjectKeys.getPublic(), + AttestedObject current = new AttestedObject(ticket, signed, TICKET_SECRET.add(BigInteger.ONE), ATTESTATION_SECRET.add(BigInteger.ONE), UN, crypto); fail(); } catch (RuntimeException e) { // Expected not to be able to construct a proof for a wrong secret } } - - @Test - public void testNonAttestedSigningKey() { - IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(subjectKeys.getPublic(), ATTESTATION_SECRET, MAIL ); - SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); - Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketIssuerKeys, TICKET_SECRET); - AsymmetricCipherKeyPair newKeys = SignatureUtility.constructECKeysWithSmallestY(rand); - attestedTicket = new AttestedObject(ticket, signed, newKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); - assertTrue(attestedTicket.verify()); - assertFalse(attestedTicket.checkValidity()); - } } diff --git a/src/test/java/org/tokenscript/attestation/cheque/TestRedeemCheque.java b/src/test/java/org/tokenscript/attestation/cheque/TestRedeemCheque.java index 96a40c36..f9907ac4 100644 --- a/src/test/java/org/tokenscript/attestation/cheque/TestRedeemCheque.java +++ b/src/test/java/org/tokenscript/attestation/cheque/TestRedeemCheque.java @@ -107,7 +107,8 @@ public void testDecoding() throws InvalidObjectException { attestedCheque.getAttestableObject().getDerEncoding(), newRedeem.getAttestableObject().getDerEncoding()); assertArrayEquals(attestedCheque.getAtt().getDerEncoding(), newRedeem.getAtt().getDerEncoding()); assertArrayEquals(attestedCheque.getPok().getDerEncoding(), newRedeem.getPok().getDerEncoding()); - assertEquals(attestedCheque.getUserPublicKey(), subjectKeys.getPublic()); + assertEquals(SignatureUtility.addressFromKey(attestedCheque.getAttestedUserKey()), + SignatureUtility.addressFromKey(subjectKeys.getPublic())); assertArrayEquals(attestedCheque.getDerEncoding(), newRedeem.getDerEncoding()); AttestedObject newConstructor = new AttestedObject(attestedCheque.getAttestableObject(), attestedCheque diff --git a/src/test/java/org/tokenscript/attestation/core/SignatureTest.java b/src/test/java/org/tokenscript/attestation/core/SignatureTest.java index b457a98b..7451e8b8 100644 --- a/src/test/java/org/tokenscript/attestation/core/SignatureTest.java +++ b/src/test/java/org/tokenscript/attestation/core/SignatureTest.java @@ -1,202 +1,124 @@ package org.tokenscript.attestation.core; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; -import java.security.Security; -import java.security.Signature; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import org.bouncycastle.asn1.sec.SECNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; +import java.util.Arrays; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.digests.KeccakDigest; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.crypto.signers.HMacDSAKCalculator; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class SignatureTest { - private static final X9ECParameters SECP364R1 = SECNamedCurves.getByName("secp384r1"); - private AsymmetricCipherKeyPair largeKeys; - private AsymmetricCipherKeyPair userKeys; - private SecureRandom rand; - - @BeforeEach - public void setupCrypto() throws Exception { - Security.addProvider(new BouncyCastleProvider()); + + private static AsymmetricCipherKeyPair subjectKeys; + private static SecureRandom rand; + private static final byte[] MSG = "some message".getBytes(StandardCharsets.UTF_8); + + @BeforeAll + public static void setupKeys() throws Exception { rand = SecureRandom.getInstance("SHA1PRNG", "SUN"); rand.setSeed("seed".getBytes()); - largeKeys = SignatureUtility.constructECKeys(SECP364R1, rand); - userKeys = SignatureUtility.constructECKeysWithSmallestY(rand); + subjectKeys = SignatureUtility.constructECKeysWithSmallestY(rand); } @Test - public void testKeyConversion() throws Exception { - byte[] message = "test".getBytes(StandardCharsets.UTF_8); - byte[] digest = AttestationCrypto.hashWithSHA256(message); - byte[] bcSignature = SignatureUtility.signHashedRandomized(digest, largeKeys.getPrivate()); - - ECPrivateKey javaPriv = (ECPrivateKey) SignatureUtility.convertPrivateBouncyCastleKeyToJavaKey(largeKeys.getPrivate()); - Signature signer = Signature.getInstance("SHA256withECDSA"); - signer.initSign(javaPriv); - signer.update(message); - byte[] javaSignature = signer.sign(); - - ECPublicKey javaPub = (ECPublicKey) SignatureUtility.convertPublicBouncyCastleKeyToJavaKey(largeKeys.getPublic()); - Signature verifier = Signature.getInstance("SHA256withECDSA"); - verifier.initVerify(javaPub); - verifier.update(message); - assertTrue(verifier.verify(javaSignature)); - - verifier.initVerify(javaPub); - verifier.update(message); - assertTrue(verifier.verify(bcSignature)); - - assertTrue(SignatureUtility.verifyHashed(digest, javaSignature, largeKeys.getPublic())); - assertTrue(SignatureUtility.verifyHashed(digest, bcSignature, largeKeys.getPublic())); + public void personal() { + AbstractSignature sig = new PersonalSignature(subjectKeys.getPrivate(), MSG); + sunshine(sig); + ensureProcessing(sig); + wrongMessage(sig); + wrongKeys(sig); } @Test - public void testSignDeterministic() { - byte[] message = new byte[515]; - message[0] = 42; - message[514] = 13; - - byte[] signature = SignatureUtility.signDeterministicSHA256(message, largeKeys.getPrivate()); - assertTrue(SignatureUtility.verifySHA256(message, signature, largeKeys.getPublic())); + public void raw() { + AbstractSignature sig = new RawSignature(subjectKeys.getPrivate(), MSG); + sunshine(sig); + wrongMessage(sig); + wrongKeys(sig); } @Test - public void testSignRandomized() { - for (int i = 0; i < 50; i++) { - byte[] message = new byte[256]; - message[0] = 0x42; - message[255] = (byte) i; - - byte[] signature = SignatureUtility.signHashedRandomized(message, largeKeys.getPrivate()); - assertTrue(SignatureUtility.verifyHashed(message, signature, largeKeys.getPublic())); - } + public void compressed() { + Signature sig = new CompressedMsgSignature(subjectKeys.getPrivate(), MSG); + sunshine(sig); + wrongMessage(sig); + wrongKeys(sig); } + @Test - public void testEthereumSigning() { - byte[] message = new byte[515]; - message[0] = 43; - message[514] = 15; - byte[] signature = SignatureUtility.signPersonalMsgWithEthereum(message, userKeys.getPrivate()); - assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message, signature, userKeys.getPublic())); + public void expectedRaw() { + AbstractSignature sig = new RawSignature(subjectKeys.getPrivate(), MSG); + assertArrayEquals(sig.getRawSignature(), SignatureUtility.signWithEthereum(MSG, subjectKeys.getPrivate())); + assertEquals(sig.getTypeOfSignature(), "raw"); + Arrays.equals(sig.processMessage(MSG), MSG); } @Test - public void testEthereumSigningNewChain() { - byte[] message = new byte[515]; - message[0] = 41; - message[514] = 45; - byte[] signature = SignatureUtility.signPersonalMsgWithEthereum(message, 2, userKeys.getPrivate()); - assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message, signature, - SignatureUtility.addressFromKey(userKeys.getPublic()), 2)); + public void expectedCompressedType() { + Signature sig = new CompressedMsgSignature(subjectKeys.getPrivate(), MSG, "prefix ", " postfix"); + assertEquals(sig.getTypeOfSignature(), "compressed"); } @Test - public void testEthereumSigningAgainstReference() { - for (int i = 0; i < 50; i++) { - // We make an extra long message and ensure that both the first and last bytes are not 0 - byte[] message = new byte[515]; - message[0] = 0x42; - message[514] = (byte) i; - - BigInteger[] ourSig = SignatureUtility - .computeInternalSignature(AttestationCrypto.hashWithKeccak(message), (ECPrivateKeyParameters) userKeys.getPrivate()); - BigInteger[] refSig = signDeterministic(message, userKeys.getPrivate()); - // We need to adjust the s part of the signature if it happens to be - // less than N/2+1 since these are the only valid Ethereum signatures. - if (refSig[1].compareTo(SignatureUtility.ECDSA_DOMAIN.getN().shiftRight(1)) > 0) { - refSig[1] = SignatureUtility.ECDSA_DOMAIN.getN().subtract(refSig[1]); - } - assertEquals(refSig[0], ourSig[0]); - assertEquals(refSig[1], ourSig[1]); - } + public void otherConstructorRaw() { + Signature sig = new RawSignature(subjectKeys.getPrivate(), MSG); + Signature newSig = new RawSignature(sig.getRawSignature()); + sunshine(newSig); + assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); } @Test - public void addressRecovery() { - String address = SignatureUtility.addressFromKey(userKeys.getPublic()); - assertTrue(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), address)); - assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), address+"00")); - assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), "0"+address)); - byte[] addressBytes = address.getBytes(); - addressBytes[5] ^= 0x01; - assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), new String(addressBytes))); + public void expectedPersonal() { + Signature sig = new PersonalSignature(subjectKeys.getPrivate(), MSG); + assertArrayEquals(sig.getRawSignature(), SignatureUtility.signPersonalMsgWithEthereum(MSG, subjectKeys.getPrivate())); + assertEquals(sig.getTypeOfSignature(), "personal"); } @Test - public void recoverPublicKey() { - byte[] message = new byte[] {0x42}; - byte[] testSignature = SignatureUtility.signWithEthereum(message, userKeys.getPrivate()); - String address = SignatureUtility.addressFromKey(userKeys.getPublic()); - AsymmetricKeyParameter key = SignatureUtility.recoverEthPublicKeyFromSignature(message, testSignature); - assertEquals(address, SignatureUtility.addressFromKey(key)); + public void compressedReference() { + CompressedMsgSignature sig = new CompressedMsgSignature(subjectKeys.getPrivate(), MSG, "prefix ", " postfix"); + assertArrayEquals(sig.processMessage(MSG), "prefix 0x9DF8DBA3720D00BD48AD744722021EF91B035E273BCCFB78660CA8DF9574B086 postfix".getBytes( + StandardCharsets.UTF_8)); } @Test - public void personalSigning() { - String message = "hello world"; - byte[] personalSignature = SignatureUtility.signPersonalMsgWithEthereum(message.getBytes( - StandardCharsets.UTF_8), userKeys.getPrivate()); - assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message.getBytes(StandardCharsets.UTF_8), - personalSignature, userKeys.getPublic())); - // A personal signature does not verify as a normal signature - assertFalse(SignatureUtility.verifyEthereumSignature(message.getBytes(StandardCharsets.UTF_8), - personalSignature, userKeys.getPublic())); - byte[] normalSignature = SignatureUtility.signWithEthereum(message.getBytes( - StandardCharsets.UTF_8), userKeys.getPrivate()); - assertFalse(SignatureUtility.verifyPersonalEthereumSignature(message.getBytes(StandardCharsets.UTF_8), - normalSignature, userKeys.getPublic())); - assertTrue(SignatureUtility.verifyEthereumSignature(message.getBytes(StandardCharsets.UTF_8), - normalSignature, userKeys.getPublic())); + public void otherConstructorPersonal() { + Signature sig = new PersonalSignature(subjectKeys.getPrivate(), MSG); + Signature newSig = new PersonalSignature(sig.getRawSignature()); + sunshine(newSig); + assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); } @Test - public void verifyingChainId() { - byte[] signature = new byte[65]; - signature[64] = 27; - assertEquals(SignatureUtility.getChainIdFromSignature(signature), 0); - signature[64] = 28; - assertEquals(SignatureUtility.getChainIdFromSignature(signature), 0); - signature[64] = 37; - assertEquals(SignatureUtility.getChainIdFromSignature(signature), 1); - signature[64] = 42; - assertEquals(SignatureUtility.getChainIdFromSignature(signature), 3); + public void otherConstructorCompressed() { + Signature sig = new CompressedMsgSignature(subjectKeys.getPrivate(), MSG); + Signature newSig = new CompressedMsgSignature(sig.getRawSignature()); + sunshine(newSig); + assertArrayEquals(sig.getRawSignature(), newSig.getRawSignature()); } - @Test - public void verifyWrongChain() { - byte[] msgWithoutPrefix = new byte[] {0x42}; - byte[] personalSignature = SignatureUtility.signPersonalMsgWithEthereum(msgWithoutPrefix, 4, userKeys.getPrivate()); - assertTrue(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, - SignatureUtility.addressFromKey(userKeys.getPublic()), 4)); - assertFalse(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, userKeys.getPublic())); - assertFalse(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, - SignatureUtility.addressFromKey(userKeys.getPublic()), 5)); + public void sunshine(Signature sig) { + assertNotNull(sig.getRawSignature()); + assertTrue(sig.getRawSignature().length > 5); + assertTrue(sig.verify(MSG, subjectKeys.getPublic())); + } + + public void ensureProcessing(AbstractSignature sig) { + assertFalse(Arrays.equals(sig.processMessage(MSG), MSG)); + } + + public void wrongMessage(Signature sig) { + assertFalse(sig.verify("some other message".getBytes(StandardCharsets.UTF_8), subjectKeys.getPublic())); } - private static BigInteger[] signDeterministic(byte[] toSign, AsymmetricKeyParameter key) { - Digest keccak = new KeccakDigest(256); - keccak.update(toSign, 0, toSign.length); - HMacDSAKCalculator randomnessProvider = new HMacDSAKCalculator(keccak); - byte[] digest = new byte[256/8]; - keccak.doFinal(digest, 0); - ECDSASigner signer = new ECDSASigner(randomnessProvider); - signer.init(true, key); - return signer.generateSignature(digest); + public void wrongKeys(Signature sig) { + assertFalse(sig.verify(MSG, SignatureUtility.constructECKeysWithSmallestY(rand).getPublic())); } } diff --git a/src/test/java/org/tokenscript/attestation/core/SignatureUtilityTest.java b/src/test/java/org/tokenscript/attestation/core/SignatureUtilityTest.java new file mode 100644 index 00000000..3b66975d --- /dev/null +++ b/src/test/java/org/tokenscript/attestation/core/SignatureUtilityTest.java @@ -0,0 +1,202 @@ +package org.tokenscript.attestation.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.Security; +import java.security.Signature; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.KeccakDigest; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SignatureUtilityTest { + private static final X9ECParameters SECP364R1 = SECNamedCurves.getByName("secp384r1"); + private AsymmetricCipherKeyPair largeKeys; + private AsymmetricCipherKeyPair userKeys; + private SecureRandom rand; + + @BeforeEach + public void setupCrypto() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + rand = SecureRandom.getInstance("SHA1PRNG", "SUN"); + rand.setSeed("seed".getBytes()); + largeKeys = SignatureUtility.constructECKeys(SECP364R1, rand); + userKeys = SignatureUtility.constructECKeysWithSmallestY(rand); + } + + @Test + public void testKeyConversion() throws Exception { + byte[] message = "test".getBytes(StandardCharsets.UTF_8); + byte[] digest = AttestationCrypto.hashWithSHA256(message); + byte[] bcSignature = SignatureUtility.signHashedRandomized(digest, largeKeys.getPrivate()); + + ECPrivateKey javaPriv = (ECPrivateKey) SignatureUtility.convertPrivateBouncyCastleKeyToJavaKey(largeKeys.getPrivate()); + Signature signer = Signature.getInstance("SHA256withECDSA"); + signer.initSign(javaPriv); + signer.update(message); + byte[] javaSignature = signer.sign(); + + ECPublicKey javaPub = (ECPublicKey) SignatureUtility.convertPublicBouncyCastleKeyToJavaKey(largeKeys.getPublic()); + Signature verifier = Signature.getInstance("SHA256withECDSA"); + verifier.initVerify(javaPub); + verifier.update(message); + assertTrue(verifier.verify(javaSignature)); + + verifier.initVerify(javaPub); + verifier.update(message); + assertTrue(verifier.verify(bcSignature)); + + assertTrue(SignatureUtility.verifyHashed(digest, javaSignature, largeKeys.getPublic())); + assertTrue(SignatureUtility.verifyHashed(digest, bcSignature, largeKeys.getPublic())); + } + + @Test + public void testSignDeterministic() { + byte[] message = new byte[515]; + message[0] = 42; + message[514] = 13; + + byte[] signature = SignatureUtility.signDeterministicSHA256(message, largeKeys.getPrivate()); + assertTrue(SignatureUtility.verifySHA256(message, signature, largeKeys.getPublic())); + } + + @Test + public void testSignRandomized() { + for (int i = 0; i < 50; i++) { + byte[] message = new byte[256]; + message[0] = 0x42; + message[255] = (byte) i; + + byte[] signature = SignatureUtility.signHashedRandomized(message, largeKeys.getPrivate()); + assertTrue(SignatureUtility.verifyHashed(message, signature, largeKeys.getPublic())); + } + } + + @Test + public void testEthereumSigning() { + byte[] message = new byte[515]; + message[0] = 43; + message[514] = 15; + byte[] signature = SignatureUtility.signPersonalMsgWithEthereum(message, userKeys.getPrivate()); + assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message, signature, userKeys.getPublic())); + } + + @Test + public void testEthereumSigningNewChain() { + byte[] message = new byte[515]; + message[0] = 41; + message[514] = 45; + byte[] signature = SignatureUtility.signPersonalMsgWithEthereum(message, 2, userKeys.getPrivate()); + assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message, signature, + SignatureUtility.addressFromKey(userKeys.getPublic()), 2)); + } + + @Test + public void testEthereumSigningAgainstReference() { + for (int i = 0; i < 50; i++) { + // We make an extra long message and ensure that both the first and last bytes are not 0 + byte[] message = new byte[515]; + message[0] = 0x42; + message[514] = (byte) i; + + BigInteger[] ourSig = SignatureUtility + .computeInternalSignature(AttestationCrypto.hashWithKeccak(message), (ECPrivateKeyParameters) userKeys.getPrivate()); + BigInteger[] refSig = signDeterministic(message, userKeys.getPrivate()); + // We need to adjust the s part of the signature if it happens to be + // less than N/2+1 since these are the only valid Ethereum signatures. + if (refSig[1].compareTo(SignatureUtility.ECDSA_DOMAIN.getN().shiftRight(1)) > 0) { + refSig[1] = SignatureUtility.ECDSA_DOMAIN.getN().subtract(refSig[1]); + } + assertEquals(refSig[0], ourSig[0]); + assertEquals(refSig[1], ourSig[1]); + } + } + + @Test + public void addressRecovery() { + String address = SignatureUtility.addressFromKey(userKeys.getPublic()); + assertTrue(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), address)); + assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), address+"00")); + assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), "0"+address)); + byte[] addressBytes = address.getBytes(); + addressBytes[5] ^= 0x01; + assertFalse(SignatureUtility.verifyKeyAgainstAddress(userKeys.getPublic(), new String(addressBytes))); + } + + @Test + public void recoverPublicKey() { + byte[] message = new byte[] {0x42}; + byte[] testSignature = SignatureUtility.signWithEthereum(message, userKeys.getPrivate()); + String address = SignatureUtility.addressFromKey(userKeys.getPublic()); + AsymmetricKeyParameter key = SignatureUtility.recoverEthPublicKeyFromSignature(message, testSignature); + assertEquals(address, SignatureUtility.addressFromKey(key)); + } + + @Test + public void personalSigning() { + String message = "hello world"; + byte[] personalSignature = SignatureUtility.signPersonalMsgWithEthereum(message.getBytes( + StandardCharsets.UTF_8), userKeys.getPrivate()); + assertTrue(SignatureUtility.verifyPersonalEthereumSignature(message.getBytes(StandardCharsets.UTF_8), + personalSignature, userKeys.getPublic())); + // A personal signature does not verify as a normal signature + assertFalse(SignatureUtility.verifyEthereumSignature(message.getBytes(StandardCharsets.UTF_8), + personalSignature, userKeys.getPublic())); + byte[] normalSignature = SignatureUtility.signWithEthereum(message.getBytes( + StandardCharsets.UTF_8), userKeys.getPrivate()); + assertFalse(SignatureUtility.verifyPersonalEthereumSignature(message.getBytes(StandardCharsets.UTF_8), + normalSignature, userKeys.getPublic())); + assertTrue(SignatureUtility.verifyEthereumSignature(message.getBytes(StandardCharsets.UTF_8), + normalSignature, userKeys.getPublic())); + } + + @Test + public void verifyingChainId() { + byte[] signature = new byte[65]; + signature[64] = 27; + assertEquals(SignatureUtility.getChainIdFromSignature(signature), 0); + signature[64] = 28; + assertEquals(SignatureUtility.getChainIdFromSignature(signature), 0); + signature[64] = 37; + assertEquals(SignatureUtility.getChainIdFromSignature(signature), 1); + signature[64] = 42; + assertEquals(SignatureUtility.getChainIdFromSignature(signature), 3); + } + + @Test + public void verifyWrongChain() { + byte[] msgWithoutPrefix = new byte[] {0x42}; + byte[] personalSignature = SignatureUtility.signPersonalMsgWithEthereum(msgWithoutPrefix, 4, userKeys.getPrivate()); + assertTrue(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, + SignatureUtility.addressFromKey(userKeys.getPublic()), 4)); + assertFalse(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, userKeys.getPublic())); + assertFalse(SignatureUtility.verifyPersonalEthereumSignature(msgWithoutPrefix, personalSignature, + SignatureUtility.addressFromKey(userKeys.getPublic()), 5)); + } + + private static BigInteger[] signDeterministic(byte[] toSign, AsymmetricKeyParameter key) { + Digest keccak = new KeccakDigest(256); + keccak.update(toSign, 0, toSign.length); + HMacDSAKCalculator randomnessProvider = new HMacDSAKCalculator(keccak); + byte[] digest = new byte[256/8]; + keccak.doFinal(digest, 0); + ECDSASigner signer = new ECDSASigner(randomnessProvider); + signer.init(true, key); + return signer.generateSignature(digest); + } +} diff --git a/src/test/java/org/tokenscript/auth/EIP712AuthenticationTest.java b/src/test/java/org/tokenscript/attestation/eip712/EIP712ObjectTest.java similarity index 55% rename from src/test/java/org/tokenscript/auth/EIP712AuthenticationTest.java rename to src/test/java/org/tokenscript/attestation/eip712/EIP712ObjectTest.java index d04eba11..8f02f8dd 100644 --- a/src/test/java/org/tokenscript/auth/EIP712AuthenticationTest.java +++ b/src/test/java/org/tokenscript/attestation/eip712/EIP712ObjectTest.java @@ -1,4 +1,4 @@ -package org.tokenscript.auth; +package org.tokenscript.attestation.eip712; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.fasterxml.jackson.core.JsonProcessingException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; @@ -18,20 +17,27 @@ import org.devcon.ticket.Ticket; import org.devcon.ticket.TicketDecoder; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.tokenscript.attestation.AttestableObjectDecoder; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.tokenscript.attestation.AttestedObject; +import org.tokenscript.attestation.AttestedObjectDecoder; import org.tokenscript.attestation.HelperTest; import org.tokenscript.attestation.IdentifierAttestation; +import org.tokenscript.attestation.ObjectDecoder; import org.tokenscript.attestation.SignedIdentifierAttestation; +import org.tokenscript.attestation.Timestamp; +import org.tokenscript.attestation.core.ASNEncodable; import org.tokenscript.attestation.core.AttestationCrypto; import org.tokenscript.attestation.core.SignatureUtility; import org.tokenscript.attestation.core.URLUtility; -import org.tokenscript.attestation.Timestamp; import org.tokenscript.eip712.Eip712Test; import org.tokenscript.eip712.FullEip712InternalData; -public class EIP712AuthenticationTest { +public class EIP712ObjectTest { private static final String validatorDomain = "http://www.hotelbogota.com"; private static final X9ECParameters SECP364R1 = SECNamedCurves.getByName("secp384r1"); @@ -46,10 +52,15 @@ public class EIP712AuthenticationTest { private static AsymmetricCipherKeyPair userKeys, attestorKeys, ticketKeys; private static SecureRandom rand; private static AttestationCrypto crypto; - private static Eip712AuthValidator validator; - private static Eip712AuthIssuer issuer; + private static Eip712ObjectValidator validator; + private static Eip712ObjectSigner issuer; private static AuthenticatorEncoder encoder; + @BeforeEach + public void init() { + MockitoAnnotations.initMocks(this); + } + @BeforeAll public static void setupKeys() throws Exception { rand = SecureRandom.getInstance("SHA1PRNG", "SUN"); @@ -58,17 +69,19 @@ public static void setupKeys() throws Exception { userKeys = SignatureUtility.constructECKeysWithSmallestY(rand); attestorKeys = SignatureUtility.constructECKeysWithSmallestY(rand); ticketKeys = SignatureUtility.constructECKeysWithSmallestY(rand); - AttestableObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); - encoder = new AuthenticatorEncoder(1, rand); - validator = new Eip712AuthValidator(decoder, encoder, attestorKeys.getPublic(), validatorDomain); - issuer = new Eip712AuthIssuer(userKeys.getPrivate(), encoder); + ObjectDecoder ticketDecoder = new TicketDecoder(ticketKeys.getPublic()); + ObjectDecoder> attestedObjectDecoder = new AttestedObjectDecoder(ticketDecoder, + attestorKeys.getPublic()); + encoder = new AuthenticatorEncoder(0, rand); + validator = new Eip712ObjectValidator(attestedObjectDecoder, encoder, validatorDomain); + issuer = new Eip712ObjectSigner(userKeys.getPrivate(), encoder); } private static AttestedObject makeAttestedTicket() { IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(userKeys.getPublic(), attestorKeys.getPublic(), ATTESTATION_SECRET, MAIL ); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketKeys, TICKET_SECRET); - AttestedObject attestedTicket = new AttestedObject(ticket, signed, userKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); + AttestedObject attestedTicket = new AttestedObject(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); assertTrue(attestedTicket.verify()); assertTrue(attestedTicket.checkValidity()); return attestedTicket; @@ -88,14 +101,24 @@ public void eipEncoding() throws Exception { Eip712Test.validateEncoding(encoder, token); } + @Test + public void eipEncodingDefaultIssuer() throws Exception { + AttestedObject attestedTicket = makeAttestedTicket(); + String token = issuer.buildSignedToken(attestedTicket, validatorDomain); + Eip712Test.validateEncoding(encoder, token); + } + @Test public void testNewChainID() throws Exception { AuthenticatorEncoder localAuthenticator = new AuthenticatorEncoder(42, rand); - Eip712AuthIssuer localIssuer = new Eip712AuthIssuer(userKeys.getPrivate(), localAuthenticator); + Eip712ObjectSigner localIssuer = new Eip712ObjectSigner(userKeys.getPrivate(), localAuthenticator); AttestedObject attestedTicket = makeAttestedTicket(); String token = localIssuer.buildSignedToken(attestedTicket, validatorDomain); - AttestableObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); - Eip712AuthValidator localValidator = new Eip712AuthValidator(decoder, localAuthenticator, attestorKeys.getPublic(), validatorDomain); + ObjectDecoder ticketDecoder = new TicketDecoder(ticketKeys.getPublic()); + ObjectDecoder> attestedObjectDecoder = new AttestedObjectDecoder(ticketDecoder, + attestorKeys.getPublic()); + Eip712ObjectValidator localValidator = new Eip712ObjectValidator(attestedObjectDecoder, localAuthenticator, + validatorDomain); assertTrue(localValidator.validateRequest(token)); assertFalse(validator.validateRequest(token)); } @@ -104,7 +127,7 @@ public void testNewChainID() throws Exception { public void testConsistency() throws Exception { AttestedObject attestedTicket = makeAttestedTicket(); long testTimestamp = Clock.systemUTC().millis(); - Eip712AuthIssuer testIssuer = new TestEip712Authentication(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); + Eip712ObjectSigner testIssuer = new TestEip712ObjectSigner(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); String newToken = testIssuer.buildSignedToken(attestedTicket, validatorDomain); assertEquals(token, newToken); @@ -121,7 +144,7 @@ public void wrongAttestedKey() throws Exception { IdentifierAttestation att = HelperTest.makeUnsignedStandardAtt(newKeys.getPublic(), attestorKeys.getPublic(), ATTESTATION_SECRET, MAIL ); SignedIdentifierAttestation signed = new SignedIdentifierAttestation(att, attestorKeys); Ticket ticket = new Ticket(MAIL, CONFERENCE_ID, TICKET_ID, TICKET_CLASS, ticketKeys, TICKET_SECRET); - AttestedObject attestedTicket = new AttestedObject(ticket, signed, newKeys.getPublic(), ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); + AttestedObject attestedTicket = new AttestedObject(ticket, signed, ATTESTATION_SECRET, TICKET_SECRET, UN, crypto); String token = issuer.buildSignedToken(attestedTicket, validatorDomain); assertFalse(validator.validateRequest(token)); @@ -130,7 +153,7 @@ public void wrongAttestedKey() throws Exception { @Test public void wrongSignature() throws Exception { AsymmetricCipherKeyPair newKeys = SignatureUtility.constructECKeysWithSmallestY(rand); - Eip712AuthIssuer newIssuer = new Eip712AuthIssuer(newKeys.getPrivate(), encoder.getChainId()); + Eip712ObjectSigner newIssuer = new Eip712ObjectSigner(newKeys.getPrivate(), new AuthenticatorEncoder(encoder.getChainId(),rand)); AttestedObject attestedTicket = makeAttestedTicket(); String token = newIssuer.buildSignedToken(attestedTicket, validatorDomain); assertFalse(validator.validateRequest(token)); @@ -140,7 +163,7 @@ public void wrongSignature() throws Exception { public void tooNew() throws Exception { AttestedObject attestedTicket = makeAttestedTicket(); long testTimestamp = Clock.systemUTC().millis() + 2 * Timestamp.ALLOWED_ROUNDING; - Eip712AuthIssuer testIssuer = new TestEip712Authentication(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); + Eip712ObjectSigner testIssuer = new TestEip712ObjectSigner(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); assertFalse(validator.validateRequest(token)); } @@ -149,10 +172,10 @@ public void tooNew() throws Exception { public void tooOld() throws Exception { AttestedObject attestedTicket = makeAttestedTicket(); long testTimestamp = 10000; - Eip712AuthIssuer testIssuer = new TestEip712Authentication(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); + Eip712ObjectSigner testIssuer = new TestEip712ObjectSigner(userKeys.getPrivate(), new TestAuthenticatorEncoder(), testTimestamp); String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); - AttestableObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); - Eip712AuthValidator newValidator = new Eip712AuthValidator(decoder, encoder, attestorKeys.getPublic(), validatorDomain); + ObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); + Eip712ObjectValidator newValidator = new Eip712ObjectValidator(decoder, encoder, validatorDomain); assertFalse(newValidator.validateRequest(token)); } @@ -176,9 +199,9 @@ public void incorrectDomain() throws Exception { @Test public void invalidDomainVerifier() { - AttestableObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); + ObjectDecoder decoder = new TicketDecoder(ticketKeys.getPublic()); assertThrows( RuntimeException.class, () -> { - new Eip712AuthValidator(decoder, encoder, attestorKeys.getPublic(), "www.noHttpPrefix.com"); + new Eip712ObjectValidator(decoder, encoder, "www.noHttpPrefix.com"); }); } @@ -187,7 +210,7 @@ public void invalidDomainIssuer() { AttestedObject attestedTicket = makeAttestedTicket(); assertThrows( RuntimeException.class, () -> { AuthenticatorEncoder authenticator = new AuthenticatorEncoder(1, rand); - Eip712AuthIssuer issuer = new Eip712AuthIssuer(userKeys.getPrivate(), authenticator); + Eip712ObjectSigner issuer = new Eip712ObjectSigner(userKeys.getPrivate(), authenticator); issuer.buildSignedToken(attestedTicket, "www.noHttpPrefix.com"); }); } @@ -195,11 +218,61 @@ public void invalidDomainIssuer() { @Test public void invalidVersion() throws Exception { AttestedObject attestedTicket = makeAttestedTicket(); - Eip712AuthIssuer testIssuer = new Eip712AuthIssuer(userKeys.getPrivate(), new TestAuthenticatorEncoder("2.2", 1)); + Eip712ObjectSigner testIssuer = new Eip712ObjectSigner(userKeys.getPrivate(), new TestAuthenticatorEncoder("2.2", encoder.getChainId())); + String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); + assertFalse(validator.validateRequest(token)); + } + + @Test + public void badDescription() { + AttestedObject attestedTicket = makeAttestedTicket(); + Eip712ObjectSigner testIssuer = new Eip712ObjectSigner(userKeys.getPrivate(), + new TestAuthenticatorEncoder(encoder.getVerifyingContract(), encoder.getSalt(), + "Wrong description", encoder.getProtocolVersion(), encoder.getChainId())); + String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); + assertFalse(validator.validateRequest(token)); + } + + @Test + public void badVerifyingContract() { + AttestedObject attestedTicket = makeAttestedTicket(); + Eip712ObjectSigner testIssuer = new Eip712ObjectSigner(userKeys.getPrivate(), + new TestAuthenticatorEncoder("0xDEADBEEF", encoder.getSalt(), + "Wrong description", encoder.getProtocolVersion(), encoder.getChainId())); String token = testIssuer.buildSignedToken(attestedTicket, validatorDomain); assertFalse(validator.validateRequest(token)); } + @Mock + AttestedObject mockedAttestedObject; + @Mock + ObjectDecoder> mockedDecoder; + @Test + public void unvalidatableUnderlyingObject() throws Exception { + Mockito.when(mockedAttestedObject.checkValidity()).thenReturn(false); + Mockito.when(mockedAttestedObject.verify()).thenReturn(true); + Mockito.when(mockedAttestedObject.getDerEncoding()).thenReturn(new byte[] {0x42}); + Mockito.when(mockedAttestedObject.getAttestedUserKey()).thenReturn(userKeys.getPublic()); + + Mockito.when(mockedDecoder.decode(ArgumentMatchers.any())).thenReturn(mockedAttestedObject); + Eip712ObjectValidator newValidator = new Eip712ObjectValidator(mockedDecoder, encoder, validatorDomain); + String token = issuer.buildSignedToken(mockedAttestedObject, validatorDomain); + assertFalse(newValidator.validateRequest(token)); + } + + @Test + public void unverifiableUnderlyingObject() throws Exception { + Mockito.when(mockedAttestedObject.checkValidity()).thenReturn(true); + Mockito.when(mockedAttestedObject.verify()).thenReturn(false); + Mockito.when(mockedAttestedObject.getDerEncoding()).thenReturn(new byte[] {0x42}); + Mockito.when(mockedAttestedObject.getAttestedUserKey()).thenReturn(userKeys.getPublic()); + + Mockito.when(mockedDecoder.decode(ArgumentMatchers.any())).thenReturn(mockedAttestedObject); + Eip712ObjectValidator newValidator = new Eip712ObjectValidator(mockedDecoder, encoder, validatorDomain); + String token = issuer.buildSignedToken(mockedAttestedObject, validatorDomain); + assertFalse(newValidator.validateRequest(token)); + } + @Test public void consistentSalt() { String salt = encoder.getSalt(); @@ -208,43 +281,67 @@ public void consistentSalt() { assertEquals(salt, otherSalt); } - private class TestEip712Authentication extends Eip712AuthIssuer { + private class TestEip712ObjectSigner extends Eip712ObjectSigner { private final Timestamp testTimestamp; - public TestEip712Authentication(AsymmetricKeyParameter signingKey, AuthenticatorEncoder authenticator, long testTimestamp) { + public TestEip712ObjectSigner(AsymmetricKeyParameter signingKey, AuthenticatorEncoder authenticator, long testTimestamp) { super(signingKey, authenticator); this.testTimestamp = new Timestamp(testTimestamp); } @Override - public String buildSignedToken(AttestedObject attestedObject, String webDomain) throws JsonProcessingException { - String encodedObject = URLUtility.encodeData(attestedObject.getDerEncoding()); - FullEip712InternalData auth = new FullEip712InternalData( - encoder.getUsageValue(), encodedObject, testTimestamp); - return buildSignedTokenFromJsonObject(auth, webDomain); + public String buildSignedToken(T attestedObject, String webDomain) { + try { + String encodedObject = URLUtility.encodeData(attestedObject.getDerEncoding()); + FullEip712InternalData auth = new FullEip712InternalData( + encoder.getUsageValue(), encodedObject, testTimestamp); + return buildSignedTokenFromJsonObject(auth, webDomain); + } catch (Exception e) { + throw new RuntimeException("", e); + } } } private class TestAuthenticatorEncoder extends AuthenticatorEncoder { - private String protoVersion = super.getProtocolVersion(); + private String protoVersion; + private String usageValue; + private String salt; + private String verifyingContract; + + public TestAuthenticatorEncoder() { + this(encoder.getProtocolVersion(), encoder.getChainId()); + } public TestAuthenticatorEncoder(String protoVersion, long chainId) { + this(encoder.getVerifyingContract(), encoder.getSalt(), encoder.getUsageValue(), protoVersion, chainId); + } + + public TestAuthenticatorEncoder(String verifyingContract, String salt, String usageValue, String protoVersion, long chainId) { super(chainId, new SecureRandom()); this.protoVersion = protoVersion; + this.usageValue = usageValue; + this.salt = salt; + this.verifyingContract = verifyingContract; } - public TestAuthenticatorEncoder() { - super(1, new SecureRandom()); + @Override + public String getUsageValue() { + return usageValue; } @Override public String getSalt() { - return "0102030405060708090001020304050607080900010203040506070809000102"; + return salt; } @Override public String getProtocolVersion() { return protoVersion; } + + @Override + public String getVerifyingContract() { + return verifyingContract; + } } } diff --git a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestEip712.java b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestEip712.java index 64a114d5..74e3ed11 100644 --- a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestEip712.java +++ b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestEip712.java @@ -5,15 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.spy; -import org.tokenscript.attestation.AttestationRequest; -import org.tokenscript.attestation.FullProofOfExponent; -import org.tokenscript.attestation.IdentifierAttestation.AttestationType; -import org.tokenscript.attestation.Timestamp; -import org.tokenscript.attestation.core.AttestationCrypto; -import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.core.URLUtility; -import org.tokenscript.attestation.eip712.Eip712AttestationRequestEncoder.AttestationRequestInternalData; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Objects; @@ -26,7 +19,15 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.attestation.AttestationRequest; +import org.tokenscript.attestation.FullProofOfExponent; +import org.tokenscript.attestation.IdentifierAttestation.AttestationType; +import org.tokenscript.attestation.Timestamp; +import org.tokenscript.attestation.core.AttestationCrypto; +import org.tokenscript.attestation.core.SignatureUtility; +import org.tokenscript.attestation.core.URLUtility; +import org.tokenscript.attestation.eip712.Eip712AttestationRequestEncoder.AttestationRequestInternalData; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Test; public class TestAttestationRequestEip712 { @@ -133,11 +134,27 @@ public void eipSignableEncoding() throws Exception { AttestationRequestInternalData data = new AttestationRequestInternalData( encoder.getUsageValue(), MAIL, URLUtility.encodeData(attRequest.getDerEncoding()), new Timestamp()); - Eip712Issuer issuer = new Eip712Issuer(userSigningKey, encoder); + Eip712Signer issuer = new Eip712Signer(userSigningKey, encoder); String json = issuer.buildSignedTokenFromJsonObject(data.getSignableVersion(), DOMAIN); Eip712Test.validateEncoding(encoder, json); } + @Mock + Eip712AttestationRequestEncoder mockedEncoder; + @Test + public void badDescription() { + byte[] nonce = Nonce.makeNonce(userAddress, DOMAIN, new Timestamp()); + FullProofOfExponent pok = crypto.computeAttestationProof(ATTESTATION_SECRET, nonce); + AttestationRequest attRequest = new AttestationRequest(TYPE, pok); + Eip712AttestationRequest request = new Eip712AttestationRequest(DOMAIN, MAIL, attRequest, userSigningKey); + Eip712AttestationRequestEncoder encoder = new Eip712AttestationRequestEncoder(Eip712AttestationRequestEncoder.LEGACY_USAGE_VALUE); + mockedEncoder = spy(encoder); + Mockito.when(mockedEncoder.getUsageValue()).thenReturn("Something wrong"); + Eip712AttestationRequest badRequest = new Eip712AttestationRequest(DOMAIN, Timestamp.DEFAULT_TIME_LIMIT_MS, request.getJsonEncoding(), mockedEncoder); + assertTrue(badRequest.verify()); + assertFalse(badRequest.checkValidity()); + } + @Test public void automaticDecoder() { byte[] nonce = Nonce.makeNonce(userAddress, DOMAIN, new Timestamp()); @@ -160,7 +177,11 @@ public void badDomain() { AttestationRequest attRequest = new AttestationRequest(TYPE, pok); Eip712AttestationRequest request = new Eip712AttestationRequest(DOMAIN, MAIL, attRequest, userSigningKey); - assertThrows( IllegalArgumentException.class, () -> new Eip712AttestationRequest("http://www.someOtherDomain.com", request.getJsonEncoding())); + assertTrue(request.checkValidity()); + assertTrue(request.verify()); + Eip712AttestationRequest newRequest = new Eip712AttestationRequest("http://www.someOtherDomain.com", request.getJsonEncoding()); + assertTrue(newRequest.verify()); + assertFalse(newRequest.checkValidity()); } @Test diff --git a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestWithUsageEip712.java b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestWithUsageEip712.java index 70bf372a..9e2af5a5 100644 --- a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestWithUsageEip712.java +++ b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationRequestWithUsageEip712.java @@ -31,7 +31,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Test; public class TestAttestationRequestWithUsageEip712 { @@ -147,7 +147,7 @@ public void eipSignableEncoding() throws Exception { Timestamp expirationTime = new Timestamp(now.getTime() + 1000); AttestationRequestWUsageData data = new AttestationRequestWUsageData( encoder.getUsageValue(), MAIL, URLUtility.encodeData(requestWithUsage.getDerEncoding()), now, expirationTime); - Eip712Issuer issuer = new Eip712Issuer(userSigningKey, encoder); + Eip712Signer issuer = new Eip712Signer(userSigningKey, encoder); String json = issuer.buildSignedTokenFromJsonObject(data.getSignableVersion(), DOMAIN); Eip712Test.validateEncoding(encoder, json); } diff --git a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationUsageEip712.java b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationUsageEip712.java index 5df217b3..7b22ed0d 100644 --- a/src/test/java/org/tokenscript/attestation/eip712/TestAttestationUsageEip712.java +++ b/src/test/java/org/tokenscript/attestation/eip712/TestAttestationUsageEip712.java @@ -31,7 +31,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.tokenscript.eip712.Eip712Issuer; +import org.tokenscript.eip712.Eip712Signer; import org.tokenscript.eip712.Eip712Test; public class TestAttestationUsageEip712 { @@ -143,7 +143,7 @@ public void eipSignableEncoding() throws Exception { AttestationUsageData data = new AttestationUsageData( encoder.getUsageValue(), MAIL, URLUtility.encodeData(usage.getDerEncoding()), now, expirationTime); - Eip712Issuer issuer = new Eip712Issuer(userSigningKey, encoder); + Eip712Signer issuer = new Eip712Signer(userSigningKey, encoder); String json = issuer.buildSignedTokenFromJsonObject(data.getSignableVersion(), DOMAIN); Eip712Test.validateEncoding(encoder, json); } diff --git a/src/test/java/org/tokenscript/eip712/Eip712Test.java b/src/test/java/org/tokenscript/eip712/Eip712Test.java index 58e3752c..58dfaa93 100644 --- a/src/test/java/org/tokenscript/eip712/Eip712Test.java +++ b/src/test/java/org/tokenscript/eip712/Eip712Test.java @@ -6,12 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.tokenscript.attestation.core.SignatureUtility; -import org.tokenscript.attestation.Timestamp; import com.alphawallet.token.web.Ethereum.web3j.StructuredData.Entry; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.InvalidObjectException; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.HashMap; @@ -19,6 +16,8 @@ import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.tokenscript.attestation.Timestamp; +import org.tokenscript.attestation.core.SignatureUtility; public class Eip712Test { private static final String testDomain = "http://www.test.com"; @@ -27,7 +26,7 @@ public class Eip712Test { private static AsymmetricCipherKeyPair userKeys; private static SecureRandom rand; private static Eip712Validator validator; - private static Eip712Issuer issuer; + private static Eip712Signer issuer; private static Eip712Encoder encoder; private static ObjectMapper mapper; @@ -48,7 +47,7 @@ public static void setupKeys() throws Exception { userKeys = SignatureUtility.constructECKeysWithSmallestY(rand); encoder = new TestEncoder(); validator = new Eip712Validator(testDomain, encoder); - issuer = new Eip712Issuer(userKeys.getPrivate(), encoder); + issuer = new Eip712Signer(userKeys.getPrivate(), encoder); mapper = new ObjectMapper(); } @@ -62,7 +61,7 @@ private void checkEquality(FullEip712InternalData computedObject) { @Test public void testSunshine() throws Exception { String token = issuer.buildSignedTokenFromJsonObject(testObject, testDomain); - checkEquality(validator.retrieveUnderlyingObject(token, FullEip712InternalData.class)); + checkEquality(validator.retrieveUnderlyingJson(token, FullEip712InternalData.class)); assertTrue(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); } @@ -71,7 +70,7 @@ public void referenceEncoding() throws Exception { String token = "{\"signatureInHex\":\"0x3a65f28b33d92973327ae616bb74c28a9c9f14ef5741a388d00449888fa2a7eb7602d1bf03cc89ff1c39b6cbbf4839bbed609161d36c364274a302e41e7aa0ae1c\",\"jsonSigned\":\"{\\\"types\\\":{\\\"Test\\\":[{\\\"name\\\":\\\"payload\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"description\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"timestamp\\\",\\\"type\\\":\\\"string\\\"}],\\\"EIP712Domain\\\":[{\\\"name\\\":\\\"name\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"version\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"chainId\\\",\\\"type\\\":\\\"uint256\\\"},{\\\"name\\\":\\\"verifyingContract\\\",\\\"type\\\":\\\"address\\\"},{\\\"name\\\":\\\"salt\\\",\\\"type\\\":\\\"bytes32\\\"}]},\\\"primaryType\\\":\\\"Test\\\",\\\"message\\\":{\\\"payload\\\":\\\"payload\\\",\\\"description\\\":\\\"description\\\",\\\"timestamp\\\":\\\"Thu Jan 1 1970 00:00:00 GMT+0000\\\"},\\\"domain\\\":{\\\"name\\\":\\\"http://www.test.com\\\",\\\"version\\\":\\\"1.0\\\",\\\"chainId\\\":1,\\\"verifyingContract\\\":\\\"0x0123456789012345678901234567890123456789\\\",\\\"salt\\\":\\\"0x0000000000000000000000000000000000000000000000000000000000000000\\\"}}\"}"; String recomputedToken = issuer.buildSignedTokenFromJsonObject(testObject, testDomain); assertEquals(token, recomputedToken); - checkEquality(validator.retrieveUnderlyingObject(token, FullEip712InternalData.class)); + checkEquality(validator.retrieveUnderlyingJson(token, FullEip712InternalData.class)); assertTrue(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); } @@ -81,7 +80,7 @@ public void referenceEncodingOtherOrder() throws Exception { + "\\\"types\\\":{\\\"Test\\\":[{\\\"name\\\":\\\"payload\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"description\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"timestamp\\\",\\\"type\\\":\\\"string\\\"}],\\\"EIP712Domain\\\":[{\\\"name\\\":\\\"name\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"version\\\",\\\"type\\\":\\\"string\\\"},{\\\"name\\\":\\\"chainId\\\",\\\"type\\\":\\\"uint256\\\"},{\\\"name\\\":\\\"verifyingContract\\\",\\\"type\\\":\\\"address\\\"},{\\\"name\\\":\\\"salt\\\",\\\"type\\\":\\\"bytes32\\\"}]}," + " \\\"message\\\":{\\\"description\\\":\\\"description\\\", \\\"payload\\\":\\\"payload\\\", \\\"timestamp\\\":\\\"Thu Jan 1 1970 00:00:00 GMT+0000\\\"},\\\"domain\\\":{\\\"name\\\":\\\"http://www.test.com\\\",\\\"version\\\":\\\"1.0\\\",\\\"chainId\\\":1,\\\"verifyingContract\\\":\\\"0x0123456789012345678901234567890123456789\\\",\\\"salt\\\":\\\"0x0000000000000000000000000000000000000000000000000000000000000000\\\"}}\"," + " \"signatureInHex\":\"0x3a65f28b33d92973327ae616bb74c28a9c9f14ef5741a388d00449888fa2a7eb7602d1bf03cc89ff1c39b6cbbf4839bbed609161d36c364274a302e41e7aa0ae1c\" }"; - checkEquality(validator.retrieveUnderlyingObject(token, FullEip712InternalData.class)); + checkEquality(validator.retrieveUnderlyingJson(token, FullEip712InternalData.class)); assertTrue(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); } @@ -96,15 +95,17 @@ public void eipEncoding() throws Exception { @Test public void testNewChainID() throws Exception { TestEncoder localTestEncoder = new TestEncoder("1", 42); - Eip712Issuer localIssuer = new Eip712Issuer(userKeys.getPrivate(), localTestEncoder); + Eip712Signer localIssuer = new Eip712Signer(userKeys.getPrivate(), localTestEncoder); String token = localIssuer.buildSignedTokenFromJsonObject(testObject, testDomain); String otherToken = localIssuer.buildSignedTokenFromJsonObject(testObject, testDomain); assertEquals(token, otherToken); Eip712Validator localValidator = new Eip712Validator(testDomain, localTestEncoder); - checkEquality(localValidator.retrieveUnderlyingObject(token, FullEip712InternalData.class)); + checkEquality(localValidator.retrieveUnderlyingJson(token, FullEip712InternalData.class)); assertTrue(localValidator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); + assertTrue(localValidator.validateDomain(token)); // Other chain ID in global validator - assertFalse(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); + assertTrue(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); + assertFalse(validator.validateDomain(token)); } @Test @@ -116,14 +117,14 @@ public void testConsistency() throws Exception { @Test public void nullInput() { - assertThrows( IllegalArgumentException.class, () -> validator.retrieveUnderlyingObject(null, FullEip712InternalData.class)); + assertThrows( IllegalArgumentException.class, () -> validator.retrieveUnderlyingJson(null, FullEip712InternalData.class)); } @Test public void testDifferenceWithDifferentChainIds() throws Exception { String token = issuer.buildSignedTokenFromJsonObject(testObject, testDomain); TestEncoder localTestEncoder = new TestEncoder("1.0", 42); - Eip712Issuer localIssuer = new Eip712Issuer(userKeys.getPrivate(), localTestEncoder); + Eip712Signer localIssuer = new Eip712Signer(userKeys.getPrivate(), localTestEncoder); String newToken = localIssuer.buildSignedTokenFromJsonObject(testObject, testDomain); assertFalse(token.equals(newToken)); } @@ -131,9 +132,9 @@ public void testDifferenceWithDifferentChainIds() throws Exception { @Test public void wrongSignature() throws Exception { AsymmetricCipherKeyPair newKeys = SignatureUtility.constructECKeysWithSmallestY(rand); - Eip712Issuer newIssuer = new Eip712Issuer(newKeys.getPrivate(), encoder); + Eip712Signer newIssuer = new Eip712Signer(newKeys.getPrivate(), encoder); String token = newIssuer.buildSignedTokenFromJsonObject(testObject, testDomain); - checkEquality(validator.retrieveUnderlyingObject(token, FullEip712InternalData.class)); + checkEquality(validator.retrieveUnderlyingJson(token, FullEip712InternalData.class)); assertTrue(validator.verifySignature(token, SignatureUtility.addressFromKey(newKeys.getPublic()), FullEip712InternalData.class)); assertFalse(validator.verifySignature(token, SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); } @@ -145,15 +146,13 @@ public void incorrectModifiedToken() throws Exception { // Flip a bit tokenBytes[0] ^= 0x01; assertFalse(validator.verifySignature(new String(tokenBytes, StandardCharsets.UTF_8), SignatureUtility.addressFromKey(userKeys.getPublic()), FullEip712InternalData.class)); - assertThrows(IllegalArgumentException.class, () -> validator.retrieveUnderlyingObject(new String(tokenBytes, StandardCharsets.UTF_8), FullEip712InternalData.class)); + assertThrows(IllegalArgumentException.class, () -> validator.retrieveUnderlyingJson(new String(tokenBytes, StandardCharsets.UTF_8), FullEip712InternalData.class)); } @Test public void incorrectDomain() throws Exception { String token = issuer.buildSignedTokenFromJsonObject(testObject, "http://www.not-test.com"); - Eip712ExternalData data = mapper.readValue(token, Eip712ExternalData.class); - JsonNode rootNode = mapper.readTree(data.getJsonSigned()); - assertThrows(InvalidObjectException.class, () -> validator.getDomainFromJson(rootNode)); + assertFalse(validator.validateDomain(token)); } @Test @@ -168,22 +167,26 @@ public void invalidDomainVerifier() { @Test public void invalidVersionIssuer() throws Exception { - Eip712Issuer newIssuer = new Eip712Issuer(userKeys.getPrivate(), new TestEncoder("2.0", 1)); + Eip712Signer newIssuer = new Eip712Signer(userKeys.getPrivate(), new TestEncoder("2.0", 1)); String token = newIssuer.buildSignedTokenFromJsonObject(testObject, testDomain); - Eip712ExternalData data = mapper.readValue(token, Eip712ExternalData.class); - JsonNode rootNode = mapper.readTree(data.getJsonSigned()); - assertThrows(InvalidObjectException.class, () -> validator.getDomainFromJson(rootNode)); + assertFalse(validator.validateDomain(token)); } @Test public void invalidVersionValidator() throws Exception { Eip712Validator newValidator = new Eip712Validator(testDomain, new TestEncoder("2.0", 1)); String token = issuer.buildSignedTokenFromJsonObject(testObject, testDomain); - Eip712ExternalData data = mapper.readValue(token, Eip712ExternalData.class); - JsonNode rootNode = mapper.readTree(data.getJsonSigned()); - assertThrows(InvalidObjectException.class, () -> newValidator.getDomainFromJson(rootNode)); + assertFalse(newValidator.validateDomain(token)); } + @Test + public void invalidSalt() throws Exception { + byte[] salt = new byte[32]; + salt[0] = (byte) 0xff; + Eip712Validator newValidator = new Eip712Validator(testDomain, new TestEncoder(encoder.getProtocolVersion(), encoder.getChainId(), encoder.getVerifyingContract(), salt)); + String token = issuer.buildSignedTokenFromJsonObject(testObject, testDomain); + assertFalse(newValidator.validateDomain(token)); + } @Test public void invalidAddressInEncoder() {