--- /dev/null
+package org.cacert.gigi.crypto;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Signature;
+import java.security.SignatureException;
+import sun.security.util.DerInputStream;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.X509Key;
+
+/**
+ * This class handles a SPKAC. A SPKAC has the following structure;
+ *
+ * <pre>
+ * PublicKeyAndChallenge ::= SEQUENCE {
+ * spki SubjectPublicKeyInfo,
+ * challenge IA5STRING
+ * }
+ *
+ * SignedPublicKeyAndChallenge ::= SEQUENCE {
+ * publicKeyAndChallenge PublicKeyAndChallenge,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING
+ * }
+ * </pre>
+ */
+public class SPKAC {
+
+ private X509Key pubkey;
+
+ private String challenge;
+
+ public SPKAC(byte[] data) throws IOException, GeneralSecurityException {
+ DerInputStream derIn = new DerInputStream(data);
+
+ DerValue derSPKACContent[] = derIn.getSequence(3);
+ if (derIn.available() != 0) {
+ throw new IllegalArgumentException("Additional data after SPKAC.");
+ }
+
+ AlgorithmId id = AlgorithmId.parse(derSPKACContent[1]);
+
+ derIn = derSPKACContent[0].toDerInputStream();
+
+ pubkey = (X509Key) X509Key.parse(derIn.getDerValue());
+
+ DerValue derChallenge = derIn.getDerValue();
+ if (derIn.available() != 0) {
+ throw new IllegalArgumentException("Additional data after SPKAC.");
+ }
+ if (derChallenge.length() != 0) {
+ challenge = derChallenge.getIA5String();
+ }
+
+ Signature s = Signature.getInstance(id.getName());
+ s.initVerify(pubkey);
+ s.update(derSPKACContent[0].toByteArray());
+ byte[] signature = derSPKACContent[2].getBitString();
+ if ( !s.verify(signature)) {
+ throw new SignatureException();
+ }
+
+ }
+
+ public String getChallenge() {
+ return challenge;
+ }
+
+ public X509Key getPubkey() {
+ return pubkey;
+ }
+
+ public SPKAC(X509Key pubkey, String challange) {
+ this.pubkey = pubkey;
+ challenge = challange;
+ }
+
+ public byte[] getEncoded(Signature sign) throws GeneralSecurityException, IOException {
+ DerOutputStream SPKAC = new DerOutputStream();
+ DerOutputStream SPKI = new DerOutputStream();
+
+ pubkey.encode(SPKI);
+ SPKI.putIA5String(challenge);
+
+ SPKAC.write(DerValue.tag_Sequence, SPKI);
+ byte[] toSign = SPKAC.toByteArray();
+ SPKI.close();
+
+ AlgorithmId aid = AlgorithmId.get(sign.getAlgorithm());
+ aid.encode(SPKAC);
+ sign.update(toSign);
+ SPKAC.putBitString(sign.sign());
+
+ DerOutputStream res = new DerOutputStream();
+ res.write(DerValue.tag_Sequence, SPKAC);
+ SPKAC.close();
+ byte[] result = res.toByteArray();
+ res.close();
+ return result;
+ }
+}
--- /dev/null
+package org.cacert.gigi.crypto;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAKey;
+import java.util.Base64;
+
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+import sun.security.x509.X509Key;
+
+public class TestSPKAC {
+
+ @Test
+ public void testParse() throws GeneralSecurityException, IOException {
+ String spkac = IOUtils.readURL(new InputStreamReader(TestSPKAC.class.getResourceAsStream("sampleSPKAC.txt")));
+ SPKAC parsed = new SPKAC(Base64.getDecoder().decode(spkac.replaceAll("[\r\n]", "")));
+ assertEquals("i am in the testcase", parsed.getChallenge());
+ RSAKey k = ((RSAKey) parsed.getPubkey());
+ assertEquals("a4004c2addf204fb26ce98f5963cc76def609ec0c50905e091fb84e986e3cb" + //
+ "0d5e14edb9cb8e10524350bd2351589284a4f631ddf9b87f04ea0e58f7d8d816b58" + //
+ "d052ce08b6576c04a7d45daf25b0ac9306f9cbb1f626e4ac301b7a4a3a062252b9a" + //
+ "472b2cde5ec803407b18879a59ccba7716016b1de4537a005b2bd0fd6071", k.getModulus().toString(16));
+ }
+
+ @Test
+ public void testAddData() throws GeneralSecurityException, IOException {
+ String spkac = IOUtils.readURL(new InputStreamReader(TestSPKAC.class.getResourceAsStream("sampleSPKAC.txt")));
+ byte[] data = Base64.getDecoder().decode(spkac.replaceAll("[\r\n]", ""));
+ byte[] tampered = new byte[data.length + 1];
+ System.arraycopy(data, 0, tampered, 0, data.length);
+ try {
+ new SPKAC(tampered);
+ fail("Expected illegal arg exception.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ // change the last byte of the signature.
+ data[data.length - 1]--;
+ try {
+ new SPKAC(data);
+ fail("Expected SignatureException.");
+ } catch (SignatureException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGen() throws GeneralSecurityException, IOException {
+ KeyPairGenerator pkg = KeyPairGenerator.getInstance("RSA");
+ pkg.initialize(1024);
+ KeyPair kp = pkg.generateKeyPair();
+
+ SPKAC s = new SPKAC((X509Key) kp.getPublic(), "this is a even bigger challange");
+ Signature sign = Signature.getInstance("SHA512withRSA");
+ sign.initSign(kp.getPrivate());
+
+ byte[] res = s.getEncoded(sign);
+ SPKAC parsed = new SPKAC(res);
+ assertEquals(s.getChallenge(), parsed.getChallenge());
+ assertEquals(s.getPubkey(), parsed.getPubkey());
+
+ }
+}
--- /dev/null
+MIIBTjCBuDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApABMKt3yBPsmzpj
+1ljzHbe9gnsDFCQXgkfuE6Ybjyw1eFO25y44QUkNQvSNRWJKEpPYx3fm4fwTqDl
+j32NgWtY0FLOCLZXbASn1F2vJbCskwb5y7H2JuSsMBt6SjoGIlK5pHKyzeXsgDQ
+HsYh5pZzLp3FgFrHeRTegBbK9D9YHECAwEAARYUaSBhbSBpbiB0aGUgdGVzdGNh
+c2UwDQYJKoZIhvcNAQEEBQADgYEAamIuLgSIgEfy5VSp+0R2wCtmxVvX9mIEhVg
+kbnNjC1bNJdtM3HPVlYbPRKuSaucgF4A/pPX55l6JECsIu6yGIyAszwGc1+rspg
+zEztsuU38IOl0sdxZBujyv2v1eoMB6TzBlsJ2hb/pC8XlmrCa5g6n1hI6XLgdu6
+3tyT4lBQ6E=