29 |
30 | boolean preSet; // Number serialization optimizer
31 |
32 | boolean readFlag; // For verifying that we didn't forgot something
33 |
34 | JSONValue(JSONTypes type, Object value) throws IOException {
35 | this.type = type;
36 | this.value = value;
37 | if (value == null) {
38 | throw new IOException("Value of type " + type.toString() + " must not be null");
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/AsymKeySignerInterface.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 |
20 | import java.io.IOException;
21 |
22 | import java.security.GeneralSecurityException;
23 |
24 | /**
25 | * Common interface for asymmetric key signatures.
26 | *
27 | */
28 | public interface AsymKeySignerInterface {
29 |
30 | /**
31 | * Sign data.
32 | *
33 | * @param data Data to sign
34 | * @return Signed data
35 | * @throws IOException
36 | * @throws GeneralSecurityException
37 | */
38 | byte[] signData(byte[] data) throws IOException, GeneralSecurityException;
39 |
40 | /**
41 | * Get signature algorithm.
42 | *
43 | * @return Signature algorithm
44 | * @throws IOException
45 | * @throws GeneralSecurityException
46 | */
47 | AsymSignatureAlgorithms getAlgorithm() throws IOException, GeneralSecurityException;
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/CryptoAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | /**
20 | * Crypto algorithm base interface.
21 | */
22 | public interface CryptoAlgorithms {
23 |
24 | boolean isMandatorySksAlgorithm();
25 |
26 | String getAlgorithmId(AlgorithmPreferences algorithmPreferences)
27 | throws IllegalArgumentException;
28 |
29 | default String getJoseAlgorithmId() {
30 | return getAlgorithmId(AlgorithmPreferences.JOSE);
31 | }
32 |
33 |
34 | default int getCoseAlgorithmId() {
35 | throw new IllegalArgumentException("COSE algorithm not defined for " + getJceName());
36 | }
37 |
38 | String getOid();
39 |
40 | String getJceName();
41 |
42 | KeyTypes getKeyType();
43 |
44 | default boolean isSymmetric() {
45 | return getKeyType() == KeyTypes.SYM;
46 | }
47 |
48 | boolean isDeprecated();
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/HmacSignerInterface.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 |
20 | import java.io.IOException;
21 |
22 | import java.security.GeneralSecurityException;
23 |
24 | /**
25 | * Common interface for HMAC signatures.
26 | *
27 | */
28 | public interface HmacSignerInterface {
29 |
30 | /**
31 | * Sign data.
32 | *
33 | * @param data Data to sign
34 | * @return Signed data
35 | * @throws IOException
36 | * @throws GeneralSecurityException
37 | */
38 | byte[] signData(byte[] data) throws IOException, GeneralSecurityException;
39 |
40 | /**
41 | * Get signature algorithm.
42 | *
43 | * @return Signature algorithm
44 | * @throws IOException
45 | * @throws GeneralSecurityException
46 | */
47 | default HmacAlgorithms getAlgorithm() throws IOException, GeneralSecurityException {
48 | throw new GeneralSecurityException("Missing implementation!");
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/rsaprivatekey_jwk:
--------------------------------------------------------------------------------
1 | {
2 | "kid": "example.com:r2048",
3 | "kty": "RSA",
4 | "n": "hFWEXArvaZEpSP5qNX7x4C4Hl28GJQTNvnDwkfqiWs63kXbdyPeS06bz6GnY3tfQ_093nGauWsimqKBmGAGMPtsV83Qxw1OIeO4ujbIIb9pema0qtVqs0MWlHxklZGFkYfAmbuEUFxYDeLDHe0bkkXbSlB7_t8pCSvc8HLgHjEQjYOlFRwjR0D-uLo-xgsCbpmCtYkB5lcT_zFgpRgY4zJNLSv7GZiz2S4Fc5ArGjd34lL47-L8bozuYjqNOv9sqX0Zgll5XaJ1ndvr7UqZu1xQFgm38reoM3IarBP_SkEFbt_v9iak602VO3k28fQhMaocP7JWR2YLT3kZM0-WTFw",
5 | "e": "AQAB",
6 | "d": "Q6iBYpnIrB2mkQZagP1lZuvBv9_osVaSZpLRvKD7DxhvbDTs0coaTJIoVCSB1_VZip8zlUg-TnYWF1Liv9VSwfQ7ddxrcOUtej60mId0ntNz2HhbxJsWjiru8EZoArl0nEovLDNxlRgRMEyZwOKPC_xHT6nFrk7_s9pR5pEEcubGLAVBKnLCoPdLr-CBjCvWfJo73W5AZxoSb8MdWQOi5viXHURpr1Y_uBRsMuclovM56Vt05etMsB1AbcTLUDwAuYrZWa1c08ql60ft7b3v6Q_rCL7EHtFU3PHAuP0mV7tM5BfAPf4T0g9pbr4GOw7eqQCiYgPFE7gmCR_PDxv5YQ",
7 | "p": "6DIM343hAtj1hQprJaVQ3T8YeIytIQ7Ma544C0A8BX-irjJfARy4fAlTSyBFeauZ0WdbMGtKpAIgNVmfCfuP7W1bXw7UaxpqsQlbw54K1VtBs8xG-lee_2YQ3lUlIiC1at6L0jxWYNkvp-LIfU2F5ZQir5ZWVXwgdMcgoNBABMc",
8 | "q": "keacq0goV7pAtG2h33OAk-XOSclIF1agvEMMOKuud5V-vGQ6OaYldlYqZmSGgF7RVlX0GZO70nPqatjd2G-tI8wEq5K_xmLQurUPFW8g___z0CTgJ62KbjFxCtGny5rsObX9im6cCc_EOtWZRaApzO8ykxfo1QcEjT4k1na7DzE",
9 | "dp": "nPmJPnFal2Q5x_GdMlwq6QhI8OaZ_OlWRcM3PFP2v_jj8ERZehUCm8hqKTXuAi2C1dC8E2XVlj9hqu-l10fcq7Tsurz52laHnpwnD35-8HK7XmRR79jgwuUrrkN90S6vt0ow2La15s-tqiBlTmDkjqqxMGfAghZiktA0PMPNI-0",
10 | "dq": "D3c1lkZw2FPK9hVE-m3A7GyIwHOQq8CoCyzER-GS_eQf6hJpxaCiCfg6SF5Rj5v9brxvwqJRX46gA7F3WrED1m6S9Cj7ISlqXNBCiBAenGRiUOcHx8zyhpnBFNeChOeoMLnk5V6yNawLbf0kYSgIJkwYvVTkfmhfCCXVO9KcI5E",
11 | "qi": "wV0NzfCakfog1NFjtPzcga1MtkpizgPkxcP9LjNdvXW2YQZhM6GIEGjsu3ivTrHrrM-4_bTQHOoTtfIY7wdqBKlwQTJOI0dH9FbNJ4ecGojRwgv83TN8aNKh17Tt44jI5oibs2P-31B_VW9R1wwhnnOuCYpABfoSbtHIoCRme5I"
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONAsymKeyEncrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.PublicKey;
22 |
23 | import org.webpki.crypto.KeyEncryptionAlgorithms;
24 |
25 | /**
26 | * Initiator object for asymmetric key encryptions.
27 | */
28 | public class JSONAsymKeyEncrypter extends JSONEncrypter {
29 |
30 | /**
31 | * Constructor for JCE based solutions.
32 | * @param publicKey Public key used for encrypting the key
33 | * @param keyEncryptionAlgorithm The algorithm used for encrypting the key
34 | * @throws IOException
35 | */
36 | public JSONAsymKeyEncrypter(PublicKey publicKey,
37 | KeyEncryptionAlgorithms keyEncryptionAlgorithm) throws IOException {
38 | this.publicKey = publicKey;
39 | this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
40 | }
41 |
42 | @Override
43 | void writeKeyData(JSONObjectWriter wr) throws IOException {
44 | wr.setPublicKey(publicKey, algorithmPreferences);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONX509Verifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.X509VerifierInterface;
24 |
25 | /**
26 | * Initiator object for X.509 signature verifiers.
27 | */
28 | public class JSONX509Verifier extends JSONVerifier {
29 |
30 | X509VerifierInterface verifier;
31 |
32 | /**
33 | * Verifier for X509-based keys.
34 | * Note that you can also access the received X509 key from {@link JSONSignatureDecoder}.
35 | *
36 | * @param verifier Verifier which presumably would do full PKIX path validation etc.
37 | */
38 | public JSONX509Verifier(X509VerifierInterface verifier) {
39 | super(JSONSignatureTypes.X509_CERTIFICATE);
40 | this.verifier = verifier;
41 | }
42 |
43 | @Override
44 | void verify(JSONSignatureDecoder signatureDecoder) throws IOException,
45 | GeneralSecurityException {
46 | verifier.verifyCertificatePath(signatureDecoder.certificatePath);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSHmacSigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.HmacAlgorithms;
24 |
25 | /**
26 | * JWS HMAC signer
27 | */
28 | public class JWSHmacSigner extends JWSSigner {
29 |
30 | HmacAlgorithms hmacAlgorithm;
31 | byte[] secretKey;
32 |
33 | /**
34 | * Initialize signer.
35 | *
36 | * Note that a signer object may be used any number of times
37 | * (assuming that the same parameters are valid). It is also
38 | * thread-safe.
39 | * @param secretKey The key to use
40 | * @param algorithm HMAC Algorithm to use
41 | * @throws IOException
42 | */
43 | public JWSHmacSigner(byte[] secretKey, HmacAlgorithms algorithm) throws IOException {
44 | super(algorithm);
45 | this.secretKey = secretKey;
46 | this.hmacAlgorithm = algorithm;
47 | }
48 |
49 | @Override
50 | byte[] signObject(byte[] dataToBeSigned) throws IOException, GeneralSecurityException {
51 | return hmacAlgorithm.digest(secretKey, dataToBeSigned);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/ec_certpath_json:
--------------------------------------------------------------------------------
1 | [
2 | "MIIB-TCCAVigAwIBAgIGAWFcc4YkMAwGCCqGSM49BAMEBQAwLTELMAkGA1UEBhMCRVUxHjAcBgNVBAMTFVRydXN0IE5ldHdvcmsgU3ViIENBMzAeFw0xODAxMDEwMDAwMDBaFw0yMjEyMzEyMzU5NTlaMDIxCzAJBgNVBAYTAkZSMQ0wCwYDVQQFEwQ0NTAxMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHHp7A83DBJIInj8-g1we3A7sBXprIQBUfdFDVUBQoPExq8rze6ewG0-eVcSF72J77gKiD0IHnzpwHaU7t6nVeajXTBbMAkGA1UdEwQCMAAwDgYDVR0PAQH_BAQDAgP4MB0GA1UdDgQWBBQQyJ9rXSIskoUuA946von62LoxqzAfBgNVHSMEGDAWgBTUWrS54qC2NgG3UK6rVAr0gbQ0MTAMBggqhkjOPQQDBAUAA4GMADCBiAJCAaWoVQ0r6jFjhO5e0WJTgyMmA8BhpO1t7gXQ6xoKGso9jCOYf9OG9BFfZoVmdIyfYiwkhy1ld27tiOJ5X4m6WasRAkIBpEkUDf8irbSZ1V7zXALaR2mJTjKQV_5jRHsiBQWA-5DxEa-x_zJVRz8tpp-jjT2tSCU82bwUOBLu6te1YIDpWCA",
3 | "MIIDsTCCAZmgAwIBAgIBAzANBgkqhkiG9w0BAQ0FADAuMQswCQYDVQQGEwJVUzEfMB0GA1UEAxMWVHJ1c3QgTmV0d29yayBSb290IENBMTAeFw0xNjA3MTAxMDAwMDBaFw0yNTA3MTAwOTU5NTlaMC0xCzAJBgNVBAYTAkVVMR4wHAYDVQQDExVUcnVzdCBOZXR3b3JrIFN1YiBDQTMwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAGJzPZsjniwyZeXrgrlQM3Y13r3znR8FSQpKbC2bplrOWySQJPGm-GFObe5Dk4t3Jrtk_Pbs8-3VW_4q5drL0YqYwBYNJPhqjbSM6SGHrc6wNdPZRw_WnJVa0ELXKICC73lkjskWPfE-cLpZ3sTq1ovEmoNjgaySVRUH1wFDdkqyReJaKNjMGEwDwYDVR0TAQH_BAUwAwEB_zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNRatLnioLY2AbdQrqtUCvSBtDQxMB8GA1UdIwQYMBaAFEkmC1HDAh0fXehpiUhUGE868Hk2MA0GCSqGSIb3DQEBDQUAA4ICAQAs2KADYyGQCVy8tJZWakNtGdww4OumZpBuR66p_2xK7veRubQEhG-nJn7oVkJ4w5pEec3sYQEqtPbHyZcEKEYbOJ2cVf1nMH-DvFZ6ypQocGRp3WSWsTzL3SgqiWrQdPX1Y5dO6Hvx7p9ST9H2WgkxB-Q75Jov1gVF3bScAbxb7Mw7tf5z3Cvqmfo0Gatkgzz6-jDPrtUK7AAAOw3C0kHMbE3EnNarsfhBkUerE8QVmHIvz373mWt0SnguaHq0A9ZuSia_pF7bgfVRZi2ZzIzpu2O276sB2Yji9tcSn5l21jq63rXtvY_DLAi4kaLyf9sHT_tkH-gkTdkdkfQq8sA5ysRW21wPQbmjTIVwsfY4JjajVIUitjPbkUJqURpf2VD0JXdYQHS6KVPWqHWTlKPlsKbhw4ghuLqCMYda88L9rxWnSC5L8s0DJSuBBm-nq23NtHl5FbCzeXWcKRayIgimT-An1WIOeJP4F7-BctYLIooKoQzJZR1tOWvprUs22_xAivVBz7J_LmJyVlKesB2ic8qYdt7YVoCsWrnEUgoNoJPwLHeva8KPvd0gLXrwaMyTCCjeoemXFj6nCbbMHJeVffh6jYBAzlbcAEvTiZcdzrVVr54kOtWskyaeDnAcMXW4Of1vWdUJ2as5nyfletfTp4E6A9P2dZ5g7nMoL90yIw"
4 | ]
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONX509Encrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import java.security.cert.X509Certificate;
24 |
25 | import org.webpki.crypto.KeyEncryptionAlgorithms;
26 |
27 | /**
28 | * Initiator object for certificate based encryptions.
29 | */
30 | public class JSONX509Encrypter extends JSONEncrypter {
31 |
32 | X509Certificate[] certificatePath;
33 |
34 | /**
35 | * Constructor for JCE based solutions.
36 | * @param certificatePath Certificate path used for encrypting the key
37 | * @param keyEncryptionAlgorithm The algorithm used for encrypting the key
38 | * @throws IOException
39 | */
40 | public JSONX509Encrypter(X509Certificate[] certificatePath,
41 | KeyEncryptionAlgorithms keyEncryptionAlgorithm) throws IOException {
42 | this.certificatePath = certificatePath;
43 | this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
44 | this.publicKey = certificatePath[0].getPublicKey();
45 | }
46 |
47 | @Override
48 | void writeKeyData(JSONObjectWriter wr) throws IOException, GeneralSecurityException {
49 | wr.setCertificatePath(certificatePath);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/OkpSupport.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.KeyFactory;
23 | import java.security.PrivateKey;
24 | import java.security.PublicKey;
25 | import java.security.Key;
26 |
27 | public class OkpSupport {
28 | public static byte[] public2RawOkpKey(PublicKey publicKey, KeyAlgorithms keyAlgorithm)
29 | throws IOException {
30 | throw new IOException("Feature not yet available in Android");
31 | }
32 | public static PublicKey raw2PublicOkpKey(byte[] x, KeyAlgorithms keyAlgorithm)
33 | throws IOException {
34 | throw new IOException("Feature not yet available in Android");
35 | }
36 |
37 | public static byte[] private2RawOkpKey(PrivateKey privateKey, KeyAlgorithms keyAlgorithm)
38 | throws IOException {
39 | throw new IOException("Feature not yet available in Android");
40 | }
41 | public static PrivateKey raw2PrivateOkpKey(byte[] d, KeyAlgorithms keyAlgorithm)
42 | throws IOException {
43 | throw new IOException("Feature not yet available in Android");
44 | }
45 | public static KeyAlgorithms getOkpKeyAlgorithm(Key key) {
46 | throw new RuntimeException("Feature not yet available in Android");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | /**
24 | * Base class for java classes which can be created from specific JSON object types.
25 | *
26 | * It is designed to use {@link JSONDecoderCache} to get automatic instantiation.
27 | */
28 | public abstract class JSONDecoder {
29 |
30 | JSONObject root; // Of parsed document
31 |
32 | /**
33 | * INTERNAL USE ONLY.
34 | *
35 | * @param rd A JSON reader
36 | * @throws IOException For any kind of error...
37 | * @throws GeneralSecurityException
38 | */
39 | protected abstract void readJSONData(JSONObjectReader rd)
40 | throws IOException, GeneralSecurityException;
41 |
42 | /**
43 | * Emulation of XML namespace
44 | *
45 | * @return The context name
46 | */
47 | public abstract String getContext();
48 |
49 | /**
50 | * Optional type indicator for JSON objects belonging to the same @context.
51 | *
52 | * @return The qualifier name
53 | */
54 | public String getQualifier() {
55 | return null;
56 | }
57 |
58 | public JSONObjectWriter getWriter() throws IOException {
59 | return new JSONObjectWriter(root);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSHmacValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.HmacAlgorithms;
24 |
25 | import org.webpki.util.ArrayUtil;
26 |
27 | /**
28 | * JWS HMAC signature validator
29 | */
30 | public class JWSHmacValidator extends JWSValidator {
31 |
32 | byte[] secretKey;
33 |
34 | /**
35 | * Initialize validator.
36 | *
37 | * Note that a validator object may be used any number of times
38 | * (assuming that the same parameters are valid). It is also
39 | * thread-safe.
40 | * @param secretKey The anticipated secret key
41 | */
42 | public JWSHmacValidator(byte[] secretKey) {
43 | this.secretKey = secretKey;
44 | }
45 |
46 | @Override
47 | void validateObject(byte[] signedData, JWSDecoder jwsDecoder)
48 | throws IOException, GeneralSecurityException {
49 | if (!ArrayUtil.compare(
50 | ((HmacAlgorithms)jwsDecoder.signatureAlgorithm).digest(secretKey,
51 | signedData),
52 | jwsDecoder.signature)) {
53 | throw new IOException("HMAC signature validation error");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONOutputFormats.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | /**
20 | * JSON output formats.
21 | * JSON tokens are always formatted according to JCS (RFC 8785).
22 | * Original property order is always maintained, unless otherwise noted.
23 | * This enumeration is used by {@link JSONObjectWriter}.
24 | */
25 | public enum JSONOutputFormats {
26 |
27 | /**
28 | * As a string without whitespace compatible with ECMAScript's JSON.stringify().
29 | */
30 | NORMALIZED (false, false, false, false),
31 | /**
32 | * JCS (RFC 8785) compatible formatting. That is, properties are sorted as well.
33 | */
34 | CANONICALIZED (false, false, false, true),
35 | /**
36 | * Pretty-printed with JavaScript syntax.
37 | */
38 | PRETTY_JS_NATIVE (true, true, false, false),
39 | /**
40 | * Pretty-printed.
41 | */
42 | PRETTY_PRINT (true, false, false, false),
43 | /**
44 | * Pretty-printed with HTML format.
45 | */
46 | PRETTY_HTML (true, false, true, false);
47 |
48 | boolean pretty;
49 | boolean javascript;
50 | boolean html;
51 | boolean canonicalized;
52 |
53 | JSONOutputFormats(boolean pretty, boolean javascript, boolean html,boolean canonicalized) {
54 | this.pretty = pretty;
55 | this.javascript = javascript;
56 | this.html = html;
57 | this.canonicalized = canonicalized;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONAsymKeyVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PublicKey;
23 |
24 | import org.webpki.crypto.KeyAlgorithms;
25 |
26 | /**
27 | * Initiator object for asymmetric key signature verifiers.
28 | */
29 | public class JSONAsymKeyVerifier extends JSONVerifier {
30 |
31 | PublicKey expectedPublicKey;
32 |
33 | /**
34 | * Verifier for asymmetric keys.
35 | * Note that you can access the received public key from {@link JSONSignatureDecoder}
36 | * which is useful if there are multiple keys possible.
37 | *
38 | * @param expectedPublicKey Expected public key
39 | * @throws GeneralSecurityException
40 | * @throws IOException
41 | */
42 | public JSONAsymKeyVerifier(PublicKey expectedPublicKey)
43 | throws GeneralSecurityException, IOException {
44 | super(JSONSignatureTypes.ASYMMETRIC_KEY);
45 | this.expectedPublicKey = KeyAlgorithms.normalizePublicKey(expectedPublicKey);
46 | }
47 |
48 | @Override
49 | void verify(JSONSignatureDecoder signatureDecoder) throws IOException {
50 | if (signatureDecoder.publicKey == null) {
51 | signatureDecoder.asymmetricSignatureVerification(expectedPublicKey);
52 | } else if (!signatureDecoder.publicKey.equals(expectedPublicKey)) {
53 | throw new IOException("Provided public key differs from the signature key");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/androidjsondemo/RawReader.java:
--------------------------------------------------------------------------------
1 | package org.webpki.androidjsondemo;
2 |
3 | import android.content.Context;
4 |
5 | import org.webpki.json.JSONObjectReader;
6 | import org.webpki.json.JSONParser;
7 |
8 | import org.webpki.util.ArrayUtil;
9 | import org.webpki.util.Base64URL;
10 |
11 | import java.security.KeyPair;
12 |
13 | import java.security.cert.X509Certificate;
14 |
15 | public class RawReader {
16 |
17 | static Context appContext;
18 |
19 | public static byte[] dataToBeEncrypted;
20 |
21 | public static String rsaKeyId;
22 | public static String ecKeyId;
23 |
24 | public static KeyPair rsaKeyPair;
25 | public static KeyPair ecKeyPair;
26 |
27 | public static X509Certificate[] ecCertPath;
28 |
29 | public static byte[] secretKey;
30 | public static String secretKeyId;
31 |
32 | static KeyPair currentKeyPair;
33 |
34 | static byte[] getRawResource(int resource) throws Exception {
35 | return ArrayUtil.getByteArrayFromInputStream(appContext.getResources()
36 | .openRawResource(resource));
37 | }
38 |
39 | static String getStringResource(int resource) throws Exception {
40 | return new String(getRawResource(resource), "utf-8");
41 | }
42 |
43 | static JSONObjectReader getJSONResource(int resource) throws Exception {
44 | return JSONParser.parse(getRawResource(resource));
45 | }
46 |
47 | static String getKeyId(int resource) throws Exception {
48 | JSONObjectReader jwk = getJSONResource(resource);
49 | String keyId = jwk.getString("kid");
50 | jwk.removeProperty("kid");
51 | currentKeyPair = jwk.getKeyPair();
52 | return keyId;
53 | }
54 |
55 | RawReader(Context appContext) throws Exception {
56 | this.appContext = appContext;
57 | ecKeyId = getKeyId(R.raw.ecprivatekey_jwk);
58 | ecKeyPair = currentKeyPair;
59 | rsaKeyId = getKeyId(R.raw.rsaprivatekey_jwk);
60 | rsaKeyPair = currentKeyPair;
61 | dataToBeEncrypted = getRawResource(R.raw.data2beencrypted_txt);
62 | ecCertPath = getJSONResource(R.raw.ec_certpath_json).getJSONArrayReader().getCertificatePath();
63 | secretKey = Base64URL.decode(getStringResource(R.raw.secretkey_b64u));
64 | secretKeyId = getStringResource(R.raw.secret_key_id_txt);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSAsymSignatureValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PublicKey;
23 |
24 | import org.webpki.crypto.AsymSignatureAlgorithms;
25 | import org.webpki.crypto.SignatureWrapper;
26 |
27 | /**
28 | * JWS asymmetric key signature validator
29 | */
30 | public class JWSAsymSignatureValidator extends JWSValidator {
31 |
32 | PublicKey publicKey;
33 |
34 | /**
35 | * Initialize validator.
36 | *
37 | * Note that a validator object may be used any number of times
38 | * (assuming that the same parameters are valid). It is also
39 | * thread-safe.
40 | * @param publicKey The anticipated public key
41 | */
42 | public JWSAsymSignatureValidator(PublicKey publicKey) {
43 | this.publicKey = publicKey;
44 | }
45 |
46 | @Override
47 | void validateObject(byte[] signedData, JWSDecoder jwsDecoder)
48 | throws IOException, GeneralSecurityException {
49 | if (jwsDecoder.optionalPublicKey != null &&
50 | !jwsDecoder.optionalPublicKey.equals(publicKey)) {
51 | throw new GeneralSecurityException(
52 | "Supplied validation key differs from the signature key specified in the JWS header");
53 | }
54 | AsymSignatureAlgorithms algorithm =
55 | (AsymSignatureAlgorithms) jwsDecoder.signatureAlgorithm;
56 | if (!new SignatureWrapper(algorithm, publicKey, provider)
57 | .update(signedData)
58 | .verify(jwsDecoder.signature)) {
59 | throw new GeneralSecurityException("Signature did not validate for key: " +
60 | publicKey.toString());
61 | }
62 | JWSSigner.checkEcJwsCompliance(publicKey, algorithm);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONEncoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | /**
24 | * Base class for java classes which are used for creating specific JSON object types.
25 | */
26 | public abstract class JSONEncoder {
27 |
28 | JSONObject root; // Of written document
29 |
30 | /**
31 | * INTERNAL USE ONLY.
32 | */
33 | protected JSONEncoder() {}
34 |
35 | /**
36 | * INTERNAL USE ONLY.
37 | *
38 | * @param wr A JSON writer
39 | * @throws IOException For any underlying error
40 | * @throws GeneralSecurityException
41 | */
42 | protected abstract void writeJSONData(JSONObjectWriter wr) throws IOException, GeneralSecurityException;
43 |
44 | /**
45 | * Emulation of XML namespace
46 | *
47 | * @return The context name
48 | */
49 | public abstract String getContext();
50 |
51 | /**
52 | * Optional type indicator for JSON objects belonging to the same @context.
53 | *
54 | * @return The qualifier name
55 | */
56 | public String getQualifier() {
57 | return null;
58 | }
59 |
60 | /**
61 | * @param outputFormat The wanted formatting
62 | * @return Document in JSON [binary] format
63 | * @throws IOException For any underlying error
64 | * @throws GeneralSecurityException
65 | */
66 | public byte[] serializeJSONDocument(JSONOutputFormats outputFormat)
67 | throws IOException, GeneralSecurityException {
68 | JSONObjectWriter wr = new JSONObjectWriter();
69 | root = wr.root;
70 | wr.setString(JSONDecoderCache.CONTEXT_JSON, getContext());
71 | if (getQualifier() != null) {
72 | wr.setString(JSONDecoderCache.QUALIFIER_JSON, getQualifier());
73 | }
74 | writeJSONData(wr);
75 | return wr.serializeToBytes(outputFormat);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONHmacVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.HmacAlgorithms;
24 | import org.webpki.crypto.HmacVerifierInterface;
25 |
26 | import org.webpki.util.ArrayUtil;
27 |
28 | /**
29 | * Initiator object for HMAC signature verifiers.
30 | */
31 | public class JSONHmacVerifier extends JSONVerifier {
32 |
33 | HmacVerifierInterface verifier;
34 |
35 | /**
36 | * Custom crypto verifier for symmetric keys.
37 | * Note that you can access the received KeyIi from {@link JSONSignatureDecoder}.
38 | *
39 | * @param verifier Handle to implementation
40 | */
41 | public JSONHmacVerifier(HmacVerifierInterface verifier) {
42 | super(JSONSignatureTypes.SYMMETRIC_KEY);
43 | this.verifier = verifier;
44 | }
45 |
46 | /**
47 | * JCE based verifier for symmetric keys.
48 | * Note that you can access the received KeyId from {@link JSONSignatureDecoder}.
49 | *
50 | * @param rawKey Key
51 | */
52 | public JSONHmacVerifier(final byte[] rawKey) {
53 | this(new HmacVerifierInterface() {
54 |
55 | @Override
56 | public boolean verifyData(byte[] data,
57 | byte[] digest,
58 | HmacAlgorithms algorithm,
59 | String keyId) throws IOException, GeneralSecurityException {
60 | return ArrayUtil.compare(digest, algorithm.digest(rawKey, data));
61 | }
62 |
63 | });
64 | }
65 |
66 | @Override
67 | void verify(JSONSignatureDecoder signatureDecoder) throws IOException, GeneralSecurityException {
68 | if (!verifier.verifyData(signatureDecoder.normalizedData,
69 | signatureDecoder.signatureValue,
70 | (HmacAlgorithms) signatureDecoder.signatureAlgorithm,
71 | signatureDecoder.keyId)) {
72 | throw new IOException("Bad signature for key: " + signatureDecoder.keyId);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONHmacSigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.AlgorithmPreferences;
24 | import org.webpki.crypto.HmacAlgorithms;
25 | import org.webpki.crypto.SignatureAlgorithms;
26 | import org.webpki.crypto.HmacSignerInterface;
27 |
28 | /**
29 | * Initiator object for HMAC signatures.
30 | */
31 | public class JSONHmacSigner extends JSONSigner {
32 |
33 | HmacSignerInterface signer;
34 |
35 | /**
36 | * Constructor for custom crypto solutions.
37 | * @param signer Handle to implementation
38 | * @throws IOException
39 | * @throws GeneralSecurityException
40 | */
41 | public JSONHmacSigner(HmacSignerInterface signer) throws IOException,
42 | GeneralSecurityException {
43 | this.signer = signer;
44 | }
45 |
46 | /**
47 | * Constructor for JCE based solutions.
48 | * @param rawKey Key
49 | * @param algorithm MAC algorithm
50 | * @throws IOException
51 | */
52 | public JSONHmacSigner(final byte[] rawKey, final HmacAlgorithms algorithm)
53 | throws IOException {
54 | signer = new HmacSignerInterface() {
55 |
56 | @Override
57 | public byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
58 | return algorithm.digest(rawKey, data);
59 | }
60 |
61 | @Override
62 | public HmacAlgorithms getAlgorithm() throws IOException {
63 | return algorithm;
64 | }
65 |
66 | };
67 | }
68 |
69 | public JSONHmacSigner setAlgorithmPreferences(AlgorithmPreferences algorithmPreferences) {
70 | this.algorithmPreferences = algorithmPreferences;
71 | return this;
72 | }
73 |
74 | @Override
75 | SignatureAlgorithms getAlgorithm() throws IOException, GeneralSecurityException {
76 | return signer.getAlgorithm();
77 | }
78 |
79 | @Override
80 | byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
81 | return signer.signData(data);
82 | }
83 |
84 | @Override
85 | void writeKeyData(JSONObjectWriter wr) throws IOException {
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/CertificateUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.io.IOException;
20 | import java.io.ByteArrayInputStream;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.HashSet;
25 |
26 | import java.security.cert.X509Certificate;
27 | import java.security.cert.CertificateFactory;
28 |
29 | import java.security.GeneralSecurityException;
30 |
31 | /**
32 | * X509 related operations.
33 | *
34 | * Source configured for the free-standing Android JSON library.
35 | */
36 | public class CertificateUtil {
37 |
38 | private CertificateUtil() {} // No instantiation please
39 |
40 | public static X509Certificate[] checkCertificatePath(X509Certificate[] certificatePath)
41 | throws IOException, GeneralSecurityException {
42 | X509Certificate signedCertificate = certificatePath[0];
43 | int i = 0;
44 | while (++i < certificatePath.length) {
45 | X509Certificate signerCertificate = certificatePath[i];
46 | String issuer = signedCertificate.getIssuerX500Principal().getName();
47 | String subject = signerCertificate.getSubjectX500Principal().getName();
48 | if (!issuer.equals(subject)) {
49 | throw new IOException("Path issuer order error, '" +
50 | issuer + "' versus '" + subject + "'");
51 | }
52 | signedCertificate.verify(signerCertificate.getPublicKey());
53 | signedCertificate = signerCertificate;
54 | }
55 | return certificatePath;
56 | }
57 |
58 | public static X509Certificate getCertificateFromBlob(byte[] encoded)
59 | throws IOException, GeneralSecurityException {
60 | CertificateFactory cf = CertificateFactory.getInstance("X.509");
61 | return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(encoded));
62 | }
63 |
64 | public static X509Certificate[] makeCertificatePath(List certificateBlobs)
65 | throws IOException, GeneralSecurityException {
66 | ArrayList certificates = new ArrayList<>();
67 | for (byte[] certificateBlob : certificateBlobs) {
68 | certificates.add(getCertificateFromBlob(certificateBlob));
69 | }
70 | return checkCertificatePath(certificates.toArray(new X509Certificate[0]));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/KeyEncryptionAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | /**
20 | * JWE and COSE key encryption algorithms.
21 | *
22 | * Note that JOSE and COSE use different KDFs.
23 | */
24 | public enum KeyEncryptionAlgorithms {
25 |
26 | // ECDH
27 | ECDH_ES ("ECDH-ES", -25, false, false, -1),
28 | ECDH_ES_A128KW ("ECDH-ES+A128KW", -29, false, true, 16),
29 | ECDH_ES_A192KW ("ECDH-ES+A192KW", -30, false, true, 24),
30 | ECDH_ES_A256KW ("ECDH-ES+A256KW", -31, false, true, 32),
31 |
32 | // RSA
33 | RSA_OAEP ("RSA-OAEP", -40, true, true, -1),
34 | RSA_OAEP_256 ("RSA-OAEP-256", -41, true, true, -1);
35 |
36 | String joseId;
37 | int coseId;
38 | boolean rsa;
39 | boolean keyWrap;
40 | int keyEncryptionKeyLength;
41 |
42 | KeyEncryptionAlgorithms(String joseId,
43 | int coseId,
44 | boolean rsa,
45 | boolean keyWrap,
46 | int keyEncryptionKeyLength) {
47 | this.joseId = joseId;
48 | this.coseId = coseId;
49 | this.rsa = rsa;
50 | this.keyWrap = keyWrap;
51 | this.keyEncryptionKeyLength = keyEncryptionKeyLength;
52 | }
53 |
54 | public boolean isRsa() {
55 | return rsa;
56 | }
57 |
58 | public boolean isKeyWrap() {
59 | return keyWrap;
60 | }
61 |
62 | public String getJoseAlgorithmId() {
63 | return joseId;
64 | }
65 |
66 | public int getCoseAlgorithmId() {
67 | return coseId;
68 | }
69 |
70 | public static KeyEncryptionAlgorithms getAlgorithmFromId(String joseAlgorithmId) {
71 | for (KeyEncryptionAlgorithms algorithm : KeyEncryptionAlgorithms.values()) {
72 | if (joseAlgorithmId.equals(algorithm.joseId)) {
73 | return algorithm;
74 | }
75 | }
76 | throw new IllegalArgumentException("Unexpected algorithm: " + joseAlgorithmId);
77 | }
78 |
79 | public static KeyEncryptionAlgorithms getAlgorithmFromId(int coseAlgorithmId) {
80 | for (KeyEncryptionAlgorithms algorithm : KeyEncryptionAlgorithms.values()) {
81 | if (coseAlgorithmId == algorithm.coseId) {
82 | return algorithm;
83 | }
84 | }
85 | throw new IllegalArgumentException("Unexpected algorithm: " + coseAlgorithmId);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONSigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.crypto.AlgorithmPreferences;
24 | import org.webpki.crypto.SignatureAlgorithms;
25 |
26 | /**
27 | * Support class for signature generators.
28 | */
29 | public abstract class JSONSigner extends JSONCryptoHelper.ExtensionsEncoder {
30 |
31 | JSONObjectReader extensionData;
32 |
33 | String[] excluded;
34 |
35 | String keyId;
36 |
37 | String provider;
38 |
39 | byte[] normalizedData;
40 |
41 | AlgorithmPreferences algorithmPreferences = AlgorithmPreferences.JOSE_ACCEPT_PREFER;
42 |
43 | JSONSigner() {
44 | }
45 |
46 | abstract SignatureAlgorithms getAlgorithm() throws IOException, GeneralSecurityException;
47 |
48 | abstract byte[] signData(byte[] data) throws IOException, GeneralSecurityException;
49 |
50 | abstract void writeKeyData(JSONObjectWriter wr) throws IOException, GeneralSecurityException;
51 |
52 | /**
53 | * Set (object level) list of permitted extension elements.
54 | * This must only be done for the first signer in a multi-signature
55 | * scenario
56 | * @param names A list of permitted extensions
57 | * @return this
58 | * @throws IOException
59 | */
60 | public JSONSigner setExtensionNames(String[] names) throws IOException {
61 | super.setExtensionNames(names, false);
62 | return this;
63 | }
64 |
65 | /**
66 | * Set specific extension data for this signature.
67 | * @param extensions JSON object holding the extension properties and associated values
68 | * @return this
69 | * @throws IOException
70 | */
71 | public JSONSigner setExtensionData(JSONObjectWriter extensions) throws IOException {
72 | this.extensionData = new JSONObjectReader(extensions);
73 | JSONCryptoHelper.checkExtensions(this.extensionData.getProperties(), false);
74 | return this;
75 | }
76 |
77 | /**
78 | * Set "excl" for this signature.
79 | * @param excluded Array holding the names of properties that must be excluded from the signature
80 | * @return this
81 | * @throws IOException
82 | */
83 | public JSONSigner setExcluded(String[] excluded) throws IOException {
84 | this.excluded = excluded;
85 | JSONSignatureDecoder.checkExcluded(excluded);
86 | return this;
87 | }
88 |
89 | /**
90 | * Set optional "keyId" for this signature.
91 | * Note: default null.
92 | * @param keyId The identifier. If null no KeyId is generated
93 | * @return this
94 | */
95 | public JSONSigner setKeyId(String keyId) {
96 | this.keyId = keyId;
97 | return this;
98 | }
99 |
100 | public byte[] getNormalizedData() {
101 | return normalizedData;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.util.LinkedHashMap;
22 | import java.util.ArrayList;
23 |
24 | /**
25 | * Local support class for holding JSON objects.
26 | * Note that outer-level arrays are ("hackishly") represented as a
27 | * JSON object having a single null property.
28 | */
29 | class JSONObject {
30 |
31 | LinkedHashMap properties = new LinkedHashMap<>();
32 |
33 | JSONObject() {
34 | }
35 |
36 | void setProperty(String name, JSONValue value) throws IOException {
37 | if (properties.put(name, value) != null) {
38 | throw new IOException("Duplicate property: " + name);
39 | }
40 | }
41 |
42 | static void checkObjectForUnread(JSONObject jsonObject) throws IOException {
43 | for (String name : jsonObject.properties.keySet()) {
44 | JSONValue value = jsonObject.properties.get(name);
45 | if (!value.readFlag) {
46 | throw new IOException("Property \"" + name + "\" was never read");
47 | }
48 | if (value.type == JSONTypes.OBJECT) {
49 | checkObjectForUnread((JSONObject) value.value);
50 | } else if (value.type == JSONTypes.ARRAY) {
51 | checkArrayForUnread(value, name);
52 | }
53 | }
54 | }
55 |
56 | @SuppressWarnings("unchecked")
57 | static void checkArrayForUnread(JSONValue array, String name) throws IOException {
58 | for (JSONValue arrayElement : (ArrayList) array.value) {
59 | if (arrayElement.type == JSONTypes.OBJECT) {
60 | checkObjectForUnread((JSONObject) arrayElement.value);
61 | } else if (arrayElement.type == JSONTypes.ARRAY) {
62 | checkArrayForUnread(arrayElement, name);
63 | } else if (!arrayElement.readFlag) {
64 | throw new IOException("Value \"" + (String) arrayElement.value + "\" of array \"" + name + "\" was never read");
65 | }
66 | }
67 | }
68 |
69 | static void setObjectAsRead(JSONObject jsonObject) throws IOException {
70 | for (String name : jsonObject.properties.keySet()) {
71 | JSONValue value = jsonObject.properties.get(name);
72 | value.readFlag = true;
73 | if (value.type == JSONTypes.OBJECT) {
74 | setObjectAsRead((JSONObject) value.value);
75 | } else if (value.type == JSONTypes.ARRAY) {
76 | setArrayAsRead(value);
77 | }
78 | }
79 | }
80 |
81 | @SuppressWarnings("unchecked")
82 | static void setArrayAsRead(JSONValue array) throws IOException {
83 | for (JSONValue arrayElement : (ArrayList) array.value) {
84 | if (arrayElement.type == JSONTypes.OBJECT) {
85 | setObjectAsRead((JSONObject) arrayElement.value);
86 | } else if (arrayElement.type == JSONTypes.ARRAY) {
87 | setArrayAsRead(arrayElement);
88 | } else {
89 | arrayElement.readFlag = true;
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import org.webpki.util.Base64URL;
24 |
25 | /**
26 | * JWS validator base class
27 | */
28 | public abstract class JWSValidator {
29 |
30 | String provider;
31 |
32 | JWSValidator() {}
33 |
34 | abstract void validateObject(byte[] signedData, JWSDecoder jwsDecoder)
35 | throws IOException, GeneralSecurityException;
36 |
37 | /**
38 | * Set cryptographic provider.
39 | * @param provider Name of provider like "BC"
40 | * @return this
41 | */
42 | public JWSValidator setProvider(String provider) {
43 | this.provider = provider;
44 | return this;
45 | }
46 |
47 | /**
48 | * Validate JWS object in "detached" mode.
49 | * Note that the detached mode follows the specification
50 | * described in
51 | * https://tools.ietf.org/html/rfc7515#appendix-F.
53 | * @param jwsDecoder Decoded JWS data
54 | * @param detachedPayload Detached payload
55 | * @return JwsDecoder
56 | * @throws IOException
57 | * @throws GeneralSecurityException
58 | */
59 | public JWSDecoder validate(JWSDecoder jwsDecoder, byte[] detachedPayload)
60 | throws IOException, GeneralSecurityException {
61 |
62 | // Dealing with detached signatures
63 | if (detachedPayload == null) {
64 | throw new IllegalArgumentException("Detached payload must not be \"null\"");
65 | }
66 | if (jwsDecoder.jwsPayloadB64U != null) {
67 | throw new IllegalArgumentException("Mixing detached and JWS-supplied payload");
68 | }
69 | jwsDecoder.jwsPayloadB64U = Base64URL.encode(detachedPayload);
70 |
71 | // Main JWS validator
72 | return validate(jwsDecoder);
73 | }
74 |
75 | /**
76 | * Validate JWS or JWS/CT object.
77 | * Note that for JWS the "standard" mode is assumed while
78 | * JWS/CT implicitly builds on the "detached" mode.
79 | * @param jwsDecoder Decoded JWS data
80 | * @return JwsDecoder
81 | * @throws IOException
82 | * @throws GeneralSecurityException
83 | */
84 | public JWSDecoder validate(JWSDecoder jwsDecoder)
85 | throws IOException, GeneralSecurityException {
86 |
87 | // Dealing with in-line signatures
88 | if (jwsDecoder.jwsPayloadB64U == null) {
89 | throw new IllegalArgumentException(
90 | "Missing payload, use \"validate(JwsDecoder, byte[])\"");
91 | }
92 |
93 | // Delegated validation
94 | validateObject((jwsDecoder.jwsHeaderB64U +
95 | "." +
96 | jwsDecoder.jwsPayloadB64U).getBytes("utf-8"),
97 | jwsDecoder);
98 |
99 | // No access to payload without having passed validation
100 | jwsDecoder.validated = true;
101 |
102 | // Convenience return
103 | return jwsDecoder;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/ContentEncryptionAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | /**
20 | * JWE and COSE content encryption algorithms.
21 | */
22 | public enum ContentEncryptionAlgorithms {
23 |
24 | // Currently only defined by JOSE
25 | A128CBC_HS256 ("A128CBC-HS256", 200, 32, EncryptionCore.AES_CBC_IV_LENGTH,
26 | 16, "HMACSHA256", false),
27 | A192CBC_HS384 ("A192CBC-HS384", 201, 48, EncryptionCore.AES_CBC_IV_LENGTH,
28 | 24, "HMACSHA384", false),
29 | A256CBC_HS512 ("A256CBC-HS512", 202, 64, EncryptionCore.AES_CBC_IV_LENGTH,
30 | 32, "HMACSHA512", false),
31 |
32 | // JOSE + COSE
33 | A128GCM ("A128GCM", 1, 16, EncryptionCore.AES_GCM_IV_LENGTH,
34 | EncryptionCore.AES_GCM_TAG_LENGTH, null, true),
35 | A192GCM ("A192GCM", 2, 24, EncryptionCore.AES_GCM_IV_LENGTH,
36 | EncryptionCore.AES_GCM_TAG_LENGTH, null, true),
37 | A256GCM ("A256GCM", 3, 32, EncryptionCore.AES_GCM_IV_LENGTH,
38 | EncryptionCore.AES_GCM_TAG_LENGTH, null, true);
39 |
40 | String joseId;
41 | int coseId;
42 | int keyLength;
43 | int ivLength;
44 | int tagLength;
45 | String jceNameOfTagHmac;
46 | boolean gcm;
47 |
48 | ContentEncryptionAlgorithms(String joseId,
49 | int coseId,
50 | int keyLength,
51 | int ivLength,
52 | int tagLength,
53 | String jceNameOfTagHmac,
54 | boolean gcm) {
55 | this.joseId = joseId;
56 | this.coseId = coseId;
57 | this.keyLength = keyLength;
58 | this.ivLength = ivLength;
59 | this.tagLength = tagLength;
60 | this.jceNameOfTagHmac = jceNameOfTagHmac;
61 | this.gcm = gcm;
62 | }
63 |
64 | public int getKeyLength() {
65 | return keyLength;
66 | }
67 |
68 | public int getIvLength() {
69 | return ivLength;
70 | }
71 |
72 | public int getTagLength() {
73 | return tagLength;
74 | }
75 |
76 | public String getJoseAlgorithmId() {
77 | return joseId;
78 | }
79 |
80 | public int getCoseAlgorithmId() {
81 | return coseId;
82 | }
83 |
84 | public static ContentEncryptionAlgorithms getAlgorithmFromId(String joseAlgorithmId) {
85 | for (ContentEncryptionAlgorithms algorithm : ContentEncryptionAlgorithms.values()) {
86 | if (joseAlgorithmId.equals(algorithm.joseId)) {
87 | return algorithm;
88 | }
89 | }
90 | throw new IllegalArgumentException("Unexpected algorithm: " + joseAlgorithmId);
91 | }
92 |
93 | public static ContentEncryptionAlgorithms getAlgorithmFromId(int coseAlgorithmId) {
94 | for (ContentEncryptionAlgorithms algorithm : ContentEncryptionAlgorithms.values()) {
95 | if (coseAlgorithmId == algorithm.coseId) {
96 | return algorithm;
97 | }
98 | }
99 | throw new IllegalArgumentException("Unexpected algorithm: " + coseAlgorithmId);
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONAsymKeySigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PublicKey;
23 | import java.security.PrivateKey;
24 |
25 | import org.webpki.crypto.AsymKeySignerInterface;
26 | import org.webpki.crypto.AsymSignatureAlgorithms;
27 | import org.webpki.crypto.AlgorithmPreferences;
28 | import org.webpki.crypto.KeyAlgorithms;
29 | import org.webpki.crypto.SignatureAlgorithms;
30 | import org.webpki.crypto.SignatureWrapper;
31 |
32 | /**
33 | * Initiator object for asymmetric key signatures.
34 | */
35 | public class JSONAsymKeySigner extends JSONSigner {
36 |
37 | AsymSignatureAlgorithms algorithm;
38 |
39 | AsymKeySignerInterface signer;
40 |
41 | PublicKey publicKey;
42 |
43 | /**
44 | * Constructor for custom crypto solutions.
45 | *
46 | * @param signer Handle to implementation
47 | * @throws IOException
48 | * @throws GeneralSecurityException
49 | */
50 | public JSONAsymKeySigner(AsymKeySignerInterface signer) throws IOException,
51 | GeneralSecurityException {
52 | this.signer = signer;
53 | this.algorithm = signer.getAlgorithm();
54 | }
55 |
56 | /**
57 | * Constructor for JCE based solutions.
58 |
59 | * @param privateKey Private key
60 | * @throws IOException
61 | * @throws GeneralSecurityException
62 | */
63 | public JSONAsymKeySigner(PrivateKey privateKey) throws IOException, GeneralSecurityException {
64 | algorithm = KeyAlgorithms.getKeyAlgorithm(privateKey).getRecommendedSignatureAlgorithm();
65 | signer = new AsymKeySignerInterface() {
66 |
67 | @Override
68 | public byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
69 | return new SignatureWrapper(algorithm, privateKey, provider)
70 | .update(data)
71 | .sign();
72 | }
73 |
74 | @Override
75 | public AsymSignatureAlgorithms getAlgorithm() throws IOException,
76 | GeneralSecurityException {
77 | return algorithm;
78 | }
79 |
80 | };
81 | }
82 |
83 | public JSONAsymKeySigner setPublicKey(PublicKey publicKey) {
84 | this.publicKey = publicKey;
85 | return this;
86 | }
87 |
88 | public JSONAsymKeySigner setAlgorithm(AsymSignatureAlgorithms algorithm)
89 | throws IOException, GeneralSecurityException {
90 | this.algorithm = algorithm;
91 | return this;
92 | }
93 |
94 | public JSONAsymKeySigner setAlgorithmPreferences(AlgorithmPreferences algorithmPreferences) {
95 | this.algorithmPreferences = algorithmPreferences;
96 | return this;
97 | }
98 |
99 | @Override
100 | SignatureAlgorithms getAlgorithm() throws IOException, GeneralSecurityException {
101 | return signer.getAlgorithm();
102 | }
103 |
104 | @Override
105 | byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
106 | return signer.signData(data);
107 | }
108 |
109 | @Override
110 | void writeKeyData(JSONObjectWriter wr) throws IOException {
111 | if (publicKey != null) {
112 | wr.setPublicKey(publicKey, algorithmPreferences);
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONX509Signer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PrivateKey;
23 |
24 | import java.security.cert.X509Certificate;
25 |
26 | import org.webpki.crypto.AsymSignatureAlgorithms;
27 | import org.webpki.crypto.AlgorithmPreferences;
28 | import org.webpki.crypto.X509SignerInterface;
29 | import org.webpki.crypto.KeyAlgorithms;
30 | import org.webpki.crypto.SignatureAlgorithms;
31 | import org.webpki.crypto.SignatureWrapper;
32 |
33 | /**
34 | * Initiator object for X.509 signatures.
35 | */
36 | public class JSONX509Signer extends JSONSigner {
37 |
38 | AsymSignatureAlgorithms algorithm;
39 |
40 | X509SignerInterface signer;
41 |
42 | X509Certificate[] certificatePath;
43 |
44 | JSONX509Signer(X509Certificate[] certificatePath) throws IOException {
45 | this.certificatePath = certificatePath;
46 | this.algorithm = KeyAlgorithms.getKeyAlgorithm(certificatePath[0].getPublicKey())
47 | .getRecommendedSignatureAlgorithm();
48 | }
49 |
50 | /**
51 | * Constructor for custom crypto solutions.
52 | *
53 | * @param signer Handle to implementation
54 | * @throws IOException
55 | * @throws GeneralSecurityException
56 | */
57 | public JSONX509Signer(X509SignerInterface signer) throws IOException,
58 | GeneralSecurityException {
59 | this(signer.getCertificatePath());
60 | this.signer = signer;
61 | }
62 |
63 | /**
64 | * Constructor for JCE based solutions.
65 | *
66 | * @param privateKey Private key
67 | * @throws IOException
68 | */
69 | public JSONX509Signer(PrivateKey privateKey, X509Certificate[] certificatePath)
70 | throws IOException {
71 | this(certificatePath);
72 | signer = new X509SignerInterface() {
73 |
74 | @Override
75 | public byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
76 | return new SignatureWrapper(algorithm, privateKey, provider)
77 | .update(data)
78 | .sign();
79 | }
80 |
81 | @Override
82 | public AsymSignatureAlgorithms getAlgorithm() throws IOException,
83 | GeneralSecurityException {
84 | return algorithm;
85 | }
86 |
87 | @Override
88 | public X509Certificate[] getCertificatePath()
89 | throws IOException, GeneralSecurityException {
90 | return null; // Not used here
91 | }
92 |
93 | };
94 | }
95 |
96 | public JSONX509Signer setAlgorithm(AsymSignatureAlgorithms algorithm)
97 | throws IOException, GeneralSecurityException {
98 | this.algorithm = algorithm;
99 | return this;
100 | }
101 |
102 | public JSONX509Signer setAlgorithmPreferences(AlgorithmPreferences algorithmPreferences) {
103 | super.algorithmPreferences = algorithmPreferences;
104 | return this;
105 | }
106 |
107 | @Override
108 | SignatureAlgorithms getAlgorithm() throws IOException, GeneralSecurityException {
109 | return signer.getAlgorithm();
110 | }
111 |
112 | @Override
113 | byte[] signData(byte[] data) throws IOException, GeneralSecurityException {
114 | return signer.signData(data);
115 | }
116 |
117 | @Override
118 | void writeKeyData(JSONObjectWriter wr) throws IOException, GeneralSecurityException {
119 | wr.setCertificatePath(certificatePath);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSAsymKeySigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PrivateKey;
23 | import java.security.PublicKey;
24 |
25 | import java.security.cert.X509Certificate;
26 |
27 | import android.util.Base64;
28 |
29 | import org.webpki.crypto.AlgorithmPreferences;
30 | import org.webpki.crypto.AsymSignatureAlgorithms;
31 | import org.webpki.crypto.KeyAlgorithms;
32 | import org.webpki.crypto.KeyTypes;
33 | import org.webpki.crypto.SignatureWrapper;
34 |
35 | import static org.webpki.jose.JOSEKeyWords.*;
36 |
37 | import org.webpki.json.JSONArrayWriter;
38 | import org.webpki.json.JSONObjectWriter;
39 |
40 | /**
41 | * JWS asymmetric key signer
42 | */
43 | public class JWSAsymKeySigner extends JWSSigner {
44 |
45 | PrivateKey privateKey;
46 | AsymSignatureAlgorithms signatureAlgorithm;
47 |
48 | /**
49 | * Initialize signer.
50 | *
51 | * Note that a signer object may be used any number of times
52 | * (assuming that the same parameters are valid). It is also
53 | * thread-safe.
54 | * @param privateKey The key to sign with
55 | * @param signatureAlgorithm The algorithm to use
56 | * @throws IOException
57 | * @throws GeneralSecurityException
58 | */
59 | public JWSAsymKeySigner(PrivateKey privateKey,
60 | AsymSignatureAlgorithms signatureAlgorithm)
61 | throws IOException, GeneralSecurityException {
62 | super(signatureAlgorithm);
63 | this.privateKey = privateKey;
64 | this.signatureAlgorithm = signatureAlgorithm;
65 | if (signatureAlgorithm.getKeyType() == KeyTypes.EC) {
66 | checkEcJwsCompliance(privateKey, signatureAlgorithm);
67 | }
68 | }
69 |
70 | /**
71 | * Initialize signer.
72 | *
73 | * Note that a signer object may be used any number of times
74 | * (assuming that the same parameters are valid). It is also
75 | * thread-safe.
76 | * The default signature algorithm to use is based on the recommendations
77 | * in RFC 7518.
78 | * @param privateKey The key to sign with
79 | * @throws IOException
80 | * @throws GeneralSecurityException
81 | */
82 | public JWSAsymKeySigner(PrivateKey privateKey)
83 | throws IOException, GeneralSecurityException {
84 | this(privateKey,
85 | KeyAlgorithms.getKeyAlgorithm(privateKey).getRecommendedSignatureAlgorithm());
86 | }
87 |
88 | /**
89 | * Adds "jwk" to the JWS header.
90 | * @param publicKey The public key to be included
91 | * @return JwsAsymKeySigner
92 | * @throws IOException
93 | */
94 | public JWSAsymKeySigner setPublicKey(PublicKey publicKey) throws IOException {
95 | jwsProtectedHeader.setObject(JWK_JSON,
96 | JSONObjectWriter.createCorePublicKey(
97 | publicKey,
98 | AlgorithmPreferences.JOSE));
99 |
100 | return this;
101 | }
102 |
103 | /**
104 | * Adds "x5c" to the JWS header.
105 | * @param certificatePath The certificate(s) to be included
106 | * @return JwsAsymKeySigner
107 | * @throws IOException
108 | * @throws GeneralSecurityException
109 | */
110 | public JWSAsymKeySigner setCertificatePath(X509Certificate[] certificatePath)
111 | throws IOException, GeneralSecurityException {
112 | JSONArrayWriter certPath = jwsProtectedHeader.setArray(X5C_JSON);
113 | for (X509Certificate cert : certificatePath) {
114 | certPath.setString(Base64.encodeToString(cert.getEncoded(), Base64.NO_WRAP));
115 | }
116 | return this;
117 | }
118 |
119 | @Override
120 | byte[] signObject(byte[] dataToBeSigned) throws IOException, GeneralSecurityException {
121 | return new SignatureWrapper(signatureAlgorithm, privateKey, provider)
122 | .update(dataToBeSigned)
123 | .sign();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/HashAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.MessageDigest;
22 | import java.security.GeneralSecurityException;
23 |
24 | /**
25 | * Hash algorithms including an implementation.
26 | *
27 | */
28 | public enum HashAlgorithms implements CryptoAlgorithms {
29 |
30 | SHA1 ("http://www.w3.org/2000/09/xmldsig#sha1", null,
31 | "1.3.14.3.2.26", "SHA-1", 20),
32 |
33 | SHA256 ("http://www.w3.org/2001/04/xmlenc#sha256", "S256",
34 | "2.16.840.1.101.3.4.2.1", "SHA-256", 32),
35 |
36 | SHA384 ("http://www.w3.org/2001/04/xmldsig-more#sha384", "S384",
37 | "2.16.840.1.101.3.4.2.2", "SHA-384", 48),
38 |
39 | SHA512 ("http://www.w3.org/2001/04/xmlenc#sha512", "S512",
40 | "2.16.840.1.101.3.4.2.3", "SHA-512", 64);
41 |
42 | private final String sksName; // As expressed in SKS
43 | private final String joseName; // Alternative JOSE name
44 | private final String oid; // As expressed in ASN.1 messages
45 | private final String jceName; // As expressed for JCE
46 | private final int bytes; // Get number of bytes in result
47 |
48 | private HashAlgorithms(String sksName,
49 | String joseName,
50 | String oid,
51 | String jceName,
52 | int bytes) {
53 | this.sksName = sksName;
54 | this.joseName = joseName;
55 | this.oid = oid;
56 | this.jceName = jceName;
57 | this.bytes = bytes;
58 | }
59 |
60 | @Override
61 | public String getJceName() {
62 | return jceName;
63 | }
64 |
65 | public byte[] digest(byte[] data) throws IOException, GeneralSecurityException {
66 | return MessageDigest.getInstance(getJceName()).digest(data);
67 | }
68 |
69 | public static HashAlgorithms getAlgorithmFromOid(String oid) {
70 | for (HashAlgorithms alg : values()) {
71 | if (oid.equals(alg.oid)) {
72 | return alg;
73 | }
74 | }
75 | throw new IllegalArgumentException("Unknown algorithm: " + oid);
76 | }
77 |
78 | public static HashAlgorithms getAlgorithmFromId(String algorithmId,
79 | AlgorithmPreferences algorithmPreferences) {
80 | for (HashAlgorithms alg : values()) {
81 | if (algorithmId.equals(alg.sksName)) {
82 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
83 | throw new IllegalArgumentException(
84 | "JOSE algorithm expected: " + algorithmId);
85 | }
86 | return alg;
87 | }
88 | if (algorithmId.equals(alg.joseName)) {
89 | if (algorithmPreferences == AlgorithmPreferences.SKS) {
90 | throw new IllegalArgumentException(
91 | "SKS algorithm expected: " + algorithmId);
92 | }
93 | return alg;
94 | }
95 | }
96 | throw new IllegalArgumentException("Unknown algorithm: " + algorithmId);
97 | }
98 |
99 | @Override
100 | public boolean isMandatorySksAlgorithm() {
101 | return false;
102 | }
103 |
104 | @Override
105 | public String getAlgorithmId(AlgorithmPreferences algorithmPreferences) {
106 | if (joseName == null) {
107 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
108 | throw new IllegalArgumentException("There is no JOSE algorithm for: " +
109 | this.toString());
110 | }
111 | return sksName;
112 | }
113 | return algorithmPreferences == AlgorithmPreferences.SKS ? sksName : joseName;
114 | }
115 |
116 | @Override
117 | public String getOid() {
118 | return oid;
119 | }
120 |
121 | @Override
122 | public boolean isSymmetric() {
123 | return true;
124 | }
125 |
126 | @Override
127 | public boolean isDeprecated() {
128 | return this == SHA1;
129 | }
130 |
131 | @Override
132 | public KeyTypes getKeyType() {
133 | return KeyTypes.SYM;
134 | }
135 |
136 | public int getResultBytes() {
137 | return bytes;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONDecoderCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.lang.reflect.InvocationTargetException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import java.io.IOException;
24 |
25 | import java.util.Hashtable;
26 |
27 | /**
28 | * Stores {@link JSONDecoder} classes for automatic instantiation during parsing.
29 | * This is (sort of) an emulation of XML schema caches.
30 | *
31 | * The cache system assumes that JSON documents follow a strict convention:
32 | *
33 | * {
34 | * "@context": "Message Context"
35 | * "@qualifier": "Message Type Qualifier"
36 | * .
37 | * . Arbitrary JSON Payload
38 | * .
39 | * }
40 | * Note: @qualifier is only required if multiple objects share the same @context.
41 | * A restriction imposed by this particular JSON processing model is that all properties must by default be read.
42 | */
43 | public class JSONDecoderCache {
44 |
45 | /**
46 | * Emulation of XML namespace
47 | */
48 | public static final String CONTEXT_JSON = "@context";
49 |
50 | /**
51 | * Emulation of XML top-level element. Optional
52 | */
53 | public static final String QUALIFIER_JSON = "@qualifier";
54 |
55 | static final char CONTEXT_QUALIFIER_DIVIDER = '$';
56 |
57 | boolean checkForUnread = true;
58 |
59 | Hashtable> classMap = new Hashtable<>();
60 |
61 | public JSONDecoder parse(JSONObjectReader reader)
62 | throws IOException, GeneralSecurityException {
63 | String objectTypeIdentifier = reader.getString(CONTEXT_JSON);
64 | if (reader.hasProperty(QUALIFIER_JSON)) {
65 | objectTypeIdentifier += CONTEXT_QUALIFIER_DIVIDER + reader.getString(QUALIFIER_JSON);
66 | }
67 | Class extends JSONDecoder> decoderClass = classMap.get(objectTypeIdentifier);
68 | if (decoderClass == null) {
69 | throw new IOException("Unknown JSONDecoder type: " + objectTypeIdentifier);
70 | }
71 | try {
72 | JSONDecoder decoder = decoderClass.getDeclaredConstructor().newInstance();
73 | decoder.root = reader.root;
74 | decoder.readJSONData(reader);
75 | if (checkForUnread) {
76 | reader.checkForUnread();
77 | }
78 | return decoder;
79 | } catch (InstantiationException | InvocationTargetException |
80 | NoSuchMethodException | IllegalAccessException e) {
81 | throw new IOException (e);
82 | }
83 | }
84 |
85 | public JSONDecoder parse(byte[] jsonUtf8) throws IOException, GeneralSecurityException {
86 | return parse(JSONParser.parse(jsonUtf8));
87 | }
88 |
89 | public void addToCache(Class extends JSONDecoder> jsonDecoder) throws IOException {
90 | try {
91 | JSONDecoder decoder = jsonDecoder.getDeclaredConstructor().newInstance();
92 | String objectTypeIdentifier = decoder.getContext();
93 | if (decoder.getQualifier() != null) {
94 | objectTypeIdentifier += CONTEXT_QUALIFIER_DIVIDER + decoder.getQualifier();
95 | }
96 | if (classMap.put(objectTypeIdentifier, decoder.getClass()) != null) {
97 | throw new IOException("JSON document type already defined: " + objectTypeIdentifier);
98 | }
99 | } catch (InstantiationException | InvocationTargetException |
100 | NoSuchMethodException | IllegalAccessException e) {
101 | throw new IOException (e);
102 | }
103 |
104 | }
105 |
106 | public void addToCache(String jsonDecoderPath) throws IOException {
107 | try {
108 | addToCache(Class.forName(jsonDecoderPath).asSubclass(JSONDecoder.class));
109 | } catch (ClassNotFoundException e) {
110 | throw new IOException("Class " + jsonDecoderPath + " can't be found", e);
111 | }
112 | }
113 |
114 | public void setCheckForUnreadProperties(boolean flag) {
115 | checkForUnread = flag;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/HmacAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 |
23 | import javax.crypto.Mac;
24 |
25 | import javax.crypto.spec.SecretKeySpec;
26 |
27 | /**
28 | * HMAC algorithms including an implementation.
29 | *
30 | */
31 | public enum HmacAlgorithms implements SignatureAlgorithms {
32 |
33 | HMAC_SHA1 ("http://www.w3.org/2000/09/xmldsig#hmac-sha1",
34 | null, 0, "HmacSHA1", HashAlgorithms.SHA1, false),
35 |
36 | HMAC_SHA256 ("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
37 | "HS256", 5, "HmacSHA256", HashAlgorithms.SHA256, true),
38 |
39 | HMAC_SHA384 ("http://www.w3.org/2001/04/xmldsig-more#hmac-sha384",
40 | "HS384", 6, "HmacSHA384", HashAlgorithms.SHA384, true),
41 |
42 | HMAC_SHA512 ("http://www.w3.org/2001/04/xmldsig-more#hmac-sha512",
43 | "HS512", 7, "HmacSHA512", HashAlgorithms.SHA512, true);
44 |
45 | private final String sksId; // As expressed in SKS
46 | private final String joseId; // JOSE
47 | private final int coseId; // COSE
48 | private final String jceName; // As expressed for JCE
49 | private HashAlgorithms digestAlg;
50 | private boolean sksMandatory; // If required in SKS
51 |
52 | private HmacAlgorithms(String sksId, String joseId, int coseId, String jceName,
53 | HashAlgorithms digestAlg, boolean sksMandatory) {
54 | this.sksId = sksId;
55 | this.joseId = joseId;
56 | this.coseId = coseId;
57 | this.jceName = jceName;
58 | this.digestAlg = digestAlg;
59 | this.sksMandatory = sksMandatory;
60 | }
61 |
62 | @Override
63 | public boolean isMandatorySksAlgorithm() {
64 | return sksMandatory;
65 | }
66 |
67 | @Override
68 | public String getJceName() {
69 | return jceName;
70 | }
71 |
72 | @Override
73 | public String getOid() {
74 | return null;
75 | }
76 |
77 | public byte[] digest(byte[] key, byte[] data) throws IOException, GeneralSecurityException {
78 | Mac mac = Mac.getInstance(getJceName());
79 | mac.init(new SecretKeySpec(key, "RAW")); // Note: any length is OK in HMAC
80 | return mac.doFinal(data);
81 | }
82 |
83 | public static boolean testAlgorithmUri(String sksId) {
84 | for (HmacAlgorithms alg : HmacAlgorithms.values()) {
85 | if (sksId.equals(alg.sksId)) {
86 | return true;
87 | }
88 | }
89 | return false;
90 | }
91 |
92 | public static HmacAlgorithms getAlgorithmFromId(String algorithmId,
93 | AlgorithmPreferences algorithmPreferences) {
94 | for (HmacAlgorithms alg : values()) {
95 | if (algorithmId.equals(alg.sksId)) {
96 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
97 | throw new IllegalArgumentException("JOSE algorithm expected: " + algorithmId);
98 | }
99 | return alg;
100 | }
101 | if (algorithmId.equals(alg.joseId)) {
102 | if (algorithmPreferences == AlgorithmPreferences.SKS) {
103 | throw new IllegalArgumentException("SKS algorithm expected: " + algorithmId);
104 | }
105 | return alg;
106 | }
107 | }
108 | throw new IllegalArgumentException("Unknown HMAC algorithm: " + algorithmId);
109 | }
110 |
111 | @Override
112 | public String getAlgorithmId(AlgorithmPreferences algorithmPreferences) {
113 | if (joseId == null) {
114 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
115 | throw new IllegalArgumentException("There is no JOSE algorithm for: " +
116 | this.toString());
117 | }
118 | return sksId;
119 | }
120 | return algorithmPreferences == AlgorithmPreferences.SKS ? sksId : joseId;
121 | }
122 |
123 | @Override
124 | public boolean isDeprecated() {
125 | return this == HMAC_SHA1;
126 | }
127 |
128 | @Override
129 | public HashAlgorithms getDigestAlgorithm() {
130 | return digestAlg;
131 | }
132 |
133 | @Override
134 | public KeyTypes getKeyType() {
135 | return KeyTypes.SYM;
136 | }
137 |
138 | @Override
139 | public int getCoseAlgorithmId() {
140 | if (coseId == 0) {
141 | throw new IllegalArgumentException("There is no COSE HMAC algorithm for :" +
142 | this.toString());
143 | }
144 | return coseId;
145 | }
146 |
147 | public static HmacAlgorithms getAlgorithmFromId(int coseAlgorithmId) {
148 | for (HmacAlgorithms alg : HmacAlgorithms.values()) {
149 | if (coseAlgorithmId == alg.coseId) {
150 | alg.getCoseAlgorithmId();
151 | return alg;
152 | }
153 | }
154 | throw new IllegalArgumentException("Unknown COSE HMAC algorithm: " +
155 | coseAlgorithmId);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSSigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.Key;
23 |
24 | import java.security.interfaces.ECKey;
25 |
26 | import org.webpki.crypto.AlgorithmPreferences;
27 | import org.webpki.crypto.AsymSignatureAlgorithms;
28 | import org.webpki.crypto.KeyAlgorithms;
29 | import org.webpki.crypto.KeyTypes;
30 | import org.webpki.crypto.SignatureAlgorithms;
31 |
32 | import static org.webpki.jose.JOSEKeyWords.*;
33 |
34 | import org.webpki.json.JSONObjectReader;
35 | import org.webpki.json.JSONObjectWriter;
36 | import org.webpki.json.JSONOutputFormats;
37 |
38 | import org.webpki.util.Base64URL;
39 |
40 | /**
41 | * JWS encoder base class
42 | */
43 | public abstract class JWSSigner {
44 |
45 | JWSSigner() {}
46 |
47 | JSONObjectWriter jwsProtectedHeader;
48 |
49 | String provider;
50 |
51 | /*
52 | * Package level constructor
53 | */
54 | JWSSigner(SignatureAlgorithms signatureAlgorithm) throws IOException {
55 | jwsProtectedHeader = new JSONObjectWriter()
56 | .setString(ALG_JSON, signatureAlgorithm.getKeyType() == KeyTypes.EDDSA ?
57 | EdDSA
58 | :
59 | signatureAlgorithm.getAlgorithmId(AlgorithmPreferences.JOSE));
60 | }
61 |
62 | /**
63 | * Set cryptographic provider.
64 | * @param provider Name of provider like "BC"
65 | * @return JwsSigner
66 | */
67 | public JWSSigner setProvider(String provider) {
68 | this.provider = provider;
69 | return this;
70 | }
71 |
72 | /**
73 | * Adds "kid" to the JWS header.
74 | * @param keyId The key identifier to be included.
75 | * @return JwsSigner
76 | * @throws IOException
77 | */
78 | public JWSSigner setKeyId(String keyId) throws IOException {
79 | jwsProtectedHeader.setString(KID_JSON, keyId);
80 | return this;
81 | }
82 |
83 | /**
84 | * Add header elements.
85 | * @param items A set of JSON tokens
86 | * @throws IOException
87 | * @return JwsSigner
88 | */
89 | public JWSSigner addHeaderItems(JSONObjectReader items) throws IOException {
90 | for (String key : items.getProperties()) {
91 | jwsProtectedHeader.copyElement(key, key, items);
92 | }
93 | return this;
94 | }
95 |
96 | /**
97 | * Create JWS/CT object.
98 | * @param objectToBeSigned The JSON object to be signed
99 | * @param signatureProperty Name of property holding the "detached" JWS
100 | * @return The now signed objectToBeSigned
101 | * @throws IOException
102 | * @throws GeneralSecurityException
103 | */
104 | public JSONObjectWriter sign(JSONObjectWriter objectToBeSigned, String signatureProperty)
105 | throws IOException, GeneralSecurityException {
106 | return objectToBeSigned.setString(signatureProperty,
107 | sign(objectToBeSigned
108 | .serializeToBytes(
109 | JSONOutputFormats.CANONICALIZED), true));
110 | }
111 |
112 | /**
113 | * Create compact mode JWS object.
114 | * Note that the detached mode follows the specification
115 | * described in
116 | * https://tools.ietf.org/html/rfc7515#appendix-F.
118 | * @param jwsPayload Binary payload
119 | * @param detached True if payload is not to be supplied in the JWS string
120 | * @return JWS compact (string)
121 | * @throws IOException
122 | * @throws GeneralSecurityException
123 | */
124 | public String sign(byte[] jwsPayload, boolean detached)
125 | throws IOException, GeneralSecurityException {
126 |
127 | // Create data to be signed
128 | String jwsProtectedHeaderB64U = Base64URL.encode(
129 | jwsProtectedHeader.serializeToBytes(JSONOutputFormats.NORMALIZED));
130 | String jwsPayloadB64U = Base64URL.encode(jwsPayload);
131 | byte[] dataToBeSigned = (jwsProtectedHeaderB64U + "." + jwsPayloadB64U).getBytes("utf-8");
132 |
133 | // Sign data and return JWS string
134 | return jwsProtectedHeaderB64U +
135 | "." +
136 | (detached ? "" : jwsPayloadB64U) +
137 | "." +
138 | Base64URL.encode(signObject(dataToBeSigned));
139 | }
140 |
141 | abstract byte[] signObject(byte[] dataToBeSigned) throws IOException, GeneralSecurityException;
142 |
143 | /*
144 | * Verify that EC algorithms follow key types as specified by RFC 7515
145 | */
146 | static void checkEcJwsCompliance(Key key, AsymSignatureAlgorithms signatureAlgorithm)
147 | throws GeneralSecurityException, IOException {
148 | if (key instanceof ECKey &&
149 | KeyAlgorithms.getKeyAlgorithm(key)
150 | .getRecommendedSignatureAlgorithm() != signatureAlgorithm) {
151 | throw new GeneralSecurityException(
152 | "EC key and algorithm does not match the JWS spec");
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONArrayReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.math.BigDecimal;
22 | import java.math.BigInteger;
23 |
24 | import java.security.GeneralSecurityException;
25 |
26 | import java.security.cert.X509Certificate;
27 |
28 | import java.util.EnumSet;
29 | import java.util.GregorianCalendar;
30 | import java.util.ArrayList;
31 |
32 | import org.webpki.crypto.CertificateUtil;
33 |
34 | import org.webpki.util.Base64URL;
35 | import org.webpki.util.ISODateTime;
36 |
37 | /**
38 | * Reads JSON array elements.
39 | * Data types are dealt with as in {@link JSONObjectReader}.
40 | * @see JSONObjectReader#getArray(String)
41 | * @see JSONObjectReader#getJSONArrayReader()
42 | * @see #getArray()
43 | */
44 | public class JSONArrayReader {
45 |
46 | ArrayList array;
47 |
48 | int index;
49 |
50 | JSONArrayReader(ArrayList array) {
51 | this.array = array;
52 | }
53 |
54 | public boolean hasMore() {
55 | return index < array.size();
56 | }
57 |
58 | public boolean isLastElement() {
59 | return index == array.size() - 1;
60 | }
61 |
62 | public int size() {
63 | return array.size();
64 | }
65 |
66 | void inRangeCheck() throws IOException {
67 | if (!hasMore()) {
68 | throw new IOException("Trying to read past of array limit: " + index);
69 | }
70 | }
71 |
72 | JSONValue getNextElementCore(JSONTypes expectedType) throws IOException {
73 | inRangeCheck();
74 | JSONValue value = array.get(index++);
75 | value.readFlag = true;
76 | JSONTypes.compatibilityTest(expectedType, value);
77 | return value;
78 | }
79 |
80 | Object getNextElement(JSONTypes expectedType) throws IOException {
81 | return getNextElementCore(expectedType).value;
82 | }
83 |
84 | public String getString() throws IOException {
85 | return (String) getNextElement(JSONTypes.STRING);
86 | }
87 |
88 | public int getInt() throws IOException {
89 | return JSONObjectReader.parseInt(getNextElementCore(JSONTypes.NUMBER));
90 | }
91 |
92 | public long getInt53() throws IOException {
93 | return JSONObjectReader.parseLong(getNextElementCore(JSONTypes.NUMBER));
94 | }
95 |
96 | public long getLong() throws IOException {
97 | return JSONObjectReader.convertBigIntegerToLong(getBigInteger());
98 | }
99 |
100 | public double getDouble() throws IOException {
101 | return Double.valueOf((String) getNextElement(JSONTypes.NUMBER));
102 | }
103 |
104 | public BigInteger getBigInteger() throws IOException {
105 | return JSONObjectReader.parseBigInteger(getString());
106 | }
107 |
108 | public BigDecimal getMoney() throws IOException {
109 | return JSONObjectReader.parseMoney(getString(), null);
110 | }
111 |
112 | public BigDecimal getMoney(Integer decimals) throws IOException {
113 | return JSONObjectReader.parseMoney(getString(), decimals);
114 | }
115 |
116 | public BigDecimal getBigDecimal() throws IOException {
117 | return JSONObjectReader.parseBigDecimal(getString());
118 | }
119 |
120 | public GregorianCalendar getDateTime(EnumSet constraints) throws IOException {
121 | return ISODateTime.parseDateTime(getString(), constraints);
122 | }
123 |
124 | public byte[] getBinary() throws IOException {
125 | return Base64URL.decode(getString());
126 | }
127 |
128 | public boolean getBoolean() throws IOException {
129 | return Boolean.valueOf((String) getNextElement(JSONTypes.BOOLEAN));
130 | }
131 |
132 | public boolean getIfNULL() throws IOException {
133 | if (getElementType() == JSONTypes.NULL) {
134 | scanAway();
135 | return true;
136 | }
137 | return false;
138 | }
139 |
140 | @SuppressWarnings("unchecked")
141 | public JSONArrayReader getArray() throws IOException {
142 | return new JSONArrayReader((ArrayList) getNextElement(JSONTypes.ARRAY));
143 | }
144 |
145 | public JSONTypes getElementType() throws IOException {
146 | inRangeCheck();
147 | return array.get(index).type;
148 | }
149 |
150 | public JSONObjectReader getObject() throws IOException {
151 | return new JSONObjectReader((JSONObject) getNextElement(JSONTypes.OBJECT));
152 | }
153 |
154 | public void scanAway() throws IOException {
155 | getNextElement(getElementType());
156 | }
157 |
158 | public ArrayList getBinaryArray() throws IOException {
159 | ArrayList blobs = new ArrayList<>();
160 | do {
161 | blobs.add(getBinary());
162 | } while (hasMore());
163 | return blobs;
164 | }
165 |
166 | public X509Certificate[] getCertificatePath() throws IOException, GeneralSecurityException {
167 | ArrayList blobs = new ArrayList<>();
168 | do {
169 | blobs.add(Base64URL.decode(getString()));
170 | } while (hasMore());
171 | return CertificateUtil.makeCertificatePath(blobs);
172 | }
173 |
174 | public JSONSignatureDecoder getSignature(JSONCryptoHelper.Options options)
175 | throws IOException, GeneralSecurityException {
176 | options.initializeOperation(false);
177 | JSONObject dummy = new JSONObject();
178 | dummy.properties.put(null, new JSONValue(JSONTypes.ARRAY, array));
179 | int save = index;
180 | index = array.size() - 1;
181 | JSONObjectReader signature = getObject();
182 | index = save;
183 | return new JSONSignatureDecoder(new JSONObjectReader(dummy),
184 | signature,
185 | signature,
186 | options);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/util/Base64URL.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.util;
18 |
19 | import java.io.IOException;
20 |
21 | import org.webpki.crypto.CryptoRandom;
22 |
23 | /**
24 | * Encodes/decodes base64URL data.
25 | * See RFC 4648 Table 2.
26 | */
27 | public class Base64URL {
28 |
29 | public final static char[] BASE64URL = {
30 | // 0 1 2 3 4 5 6 7
31 | 'A','B','C','D','E','F','G','H', // 0
32 | 'I','J','K','L','M','N','O','P', // 1
33 | 'Q','R','S','T','U','V','W','X', // 2
34 | 'Y','Z','a','b','c','d','e','f', // 3
35 | 'g','h','i','j','k','l','m','n', // 4
36 | 'o','p','q','r','s','t','u','v', // 5
37 | 'w','x','y','z','0','1','2','3', // 6
38 | '4','5','6','7','8','9','-','_' // 7
39 | };
40 |
41 | final static byte[] DECODE_TABLE = {
42 | -1, -1, -1, -1, -1, -1, -1, -1,
43 | -1, -1, -1, -1, -1, -1, -1, -1,
44 | -1, -1, -1, -1, -1, -1, -1, -1,
45 | -1, -1, -1, -1, -1, -1, -1, -1,
46 | -1, -1, -1, -1, -1, -1, -1, -1,
47 | -1, -1, -1, -1, -1, 62, -1, -1,
48 | 52, 53, 54, 55, 56, 57, 58, 59,
49 | 60, 61, -1, -1, -1, -1, -1, -1,
50 | -1, 0, 1, 2, 3, 4, 5, 6,
51 | 7, 8, 9, 10, 11, 12, 13, 14,
52 | 15, 16, 17, 18, 19, 20, 21, 22,
53 | 23, 24, 25, -1, -1, -1, -1, 63,
54 | -1, 26, 27, 28, 29, 30, 31, 32,
55 | 33, 34, 35, 36, 37, 38, 39, 40,
56 | 41, 42, 43, 44, 45, 46, 47, 48,
57 | 49, 50, 51
58 | };
59 |
60 | private Base64URL() {} // No instantiation please
61 |
62 | /**
63 | * Converts a base64url encoded String to a byte array.
64 | * For every 4 base64url characters you'll get 3 binary bytes.
65 | *
66 | * @param base64url Encoded data
67 | * @return Decoded data as a byte array
68 | * @throws IOException If input data isn't valid base64url data
69 | */
70 | public static byte[] decode(String base64url) throws IOException {
71 | byte[] encoded = base64url.getBytes("UTF-8");
72 | byte[] semidecoded = new byte[encoded.length];
73 | for (int i = 0; i < encoded.length; i++) {
74 | byte c = encoded[i];
75 | if (c < 0 || c >= DECODE_TABLE.length || (c = DECODE_TABLE[c]) < 0) {
76 | throw new IOException("bad character at index " + i);
77 | }
78 | semidecoded[i] = c;
79 | }
80 | int decoded_length = (encoded.length / 4) * 3;
81 | int encoded_length_modulo_4 = encoded.length % 4;
82 | if (encoded_length_modulo_4 != 0) {
83 | decoded_length += encoded_length_modulo_4 - 1;
84 | }
85 | byte[] decoded = new byte[decoded_length];
86 | int decoded_length_modulo_3 = decoded.length % 3;
87 | if (decoded_length_modulo_3 == 0 && encoded_length_modulo_4 != 0) {
88 | throw new IOException("Wrong number of Base64URL characters");
89 | }
90 |
91 | // -----: D E C O D E :-----
92 | int i = 0, j = 0;
93 | //decode in groups of four bytes
94 | while (j < decoded.length - decoded_length_modulo_3) {
95 | decoded[j++] = (byte) ((semidecoded[i++] << 2) | (semidecoded[i] >>> 4));
96 | decoded[j++] = (byte) ((semidecoded[i++] << 4) | (semidecoded[i] >>> 2));
97 | decoded[j++] = (byte) ((semidecoded[i++] << 6) | semidecoded[i++]);
98 | }
99 | //decode "odd" bytes
100 | if (decoded_length_modulo_3 == 1) {
101 | decoded[j] = (byte) ((semidecoded[i++] << 2) | (semidecoded[i] >>> 4));
102 | if ((semidecoded[i] & 0x0F) != 0) {
103 | throw new IOException("Wrong termination character");
104 | }
105 | } else if (decoded_length_modulo_3 == 2) {
106 | decoded[j++] = (byte) ((semidecoded[i++] << 2) | (semidecoded[i] >>> 4));
107 | decoded[j] = (byte) ((semidecoded[i++] << 4) | (semidecoded[i] >>> 2));
108 | if ((semidecoded[i] & 0x03) != 0) {
109 | throw new IOException("Wrong termination character");
110 | }
111 | }
112 | return decoded;
113 | }
114 |
115 | /**
116 | * Converts a byte array to a base64url encoded String.
117 | * For every 3 binary bytes, you'll get 4 base64url characters.
118 | *
119 | * @param byteArray Binary data
120 | * @return Encoded data as a String
121 | * @throws IOException
122 | */
123 | public static String encode(byte[] byteArray) throws IOException {
124 | //determine length of output
125 | int i;
126 | int modulo3 = byteArray.length % 3;
127 | //(1)
128 | i = (byteArray.length / 3) * 4;
129 | //(2)
130 | if (modulo3 != 0) {
131 | i += modulo3 + 1;
132 | }
133 | //(3)
134 | char[] encoded = new char[i];
135 | i = 0;
136 | int j = 0;
137 | //encode by threes
138 | while (j < byteArray.length - modulo3) {
139 | encoded[i++] = BASE64URL[(byteArray[j] >>> 2) & 0x3F];
140 | encoded[i++] = BASE64URL[((byteArray[j++] << 4) & 0x30) | ((byteArray[j] >>> 4) & 0x0F)];
141 | encoded[i++] = BASE64URL[((byteArray[j++] << 2) & 0x3C) | ((byteArray[j] >>> 6) & 0x03)];
142 | encoded[i++] = BASE64URL[byteArray[j++] & 0x3F];
143 | }
144 | //encode "odd" bytes
145 | if (modulo3 == 1) {
146 | encoded[i++] = BASE64URL[(byteArray[j] >>> 2) & 0x3F];
147 | encoded[i] = BASE64URL[(byteArray[j] << 4) & 0x30];
148 | } else if (modulo3 == 2) {
149 | encoded[i++] = BASE64URL[(byteArray[j] >>> 2) & 0x3F];
150 | encoded[i++] = BASE64URL[((byteArray[j++] << 4) & 0x30) | ((byteArray[j] >>> 4) & 0x0F)];
151 | encoded[i] = BASE64URL[(byteArray[j] << 2) & 0x3C];
152 | }
153 | return new String(encoded);
154 | }
155 |
156 | /**
157 | * Generates a base64url encoded nonce.
158 | * @param length Number of characters
159 | * @return Encoded nonce
160 | */
161 | public static String generateURLFriendlyRandom(int length) {
162 | byte[] random = CryptoRandom.generateRandom(length);
163 | StringBuilder buffer = new StringBuilder();
164 | for (int i = 0; i < length; i++) {
165 | buffer.append(BASE64URL[random[i] & 0x3F]);
166 | }
167 | return buffer.toString();
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONArrayWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.math.BigDecimal;
22 | import java.math.BigInteger;
23 |
24 | import java.security.GeneralSecurityException;
25 |
26 | import java.security.cert.X509Certificate;
27 |
28 | import java.util.EnumSet;
29 | import java.util.GregorianCalendar;
30 | import java.util.ArrayList;
31 |
32 | import org.webpki.crypto.CertificateUtil;
33 |
34 | import org.webpki.util.Base64URL;
35 | import org.webpki.util.ISODateTime;
36 |
37 | /**
38 | * Writes JSON arrays.
39 | * Data types are dealt with as in {@link JSONObjectWriter}.
40 | * @see JSONObjectWriter#setArray(String)
41 | * @see #setArray()
42 | * @see #setArray(JSONArrayWriter)
43 | * @see #JSONArrayWriter()
44 | */
45 | public class JSONArrayWriter {
46 |
47 | ArrayList array;
48 |
49 | /**
50 | * For creating a JSON array.
51 | * Note that this constructor can be used for creating a JSON structure where the
52 | * outermost part in an array as well as for array sub-objects.
53 | * @see JSONObjectReader#getJSONArrayReader()
54 | * @see JSONObjectWriter#setArray(String, JSONArrayWriter)
55 | * @see #setArray(JSONArrayWriter)
56 | */
57 | public JSONArrayWriter() {
58 | array = new ArrayList<>();
59 | }
60 |
61 | public JSONArrayWriter(JSONArrayReader reader) {
62 | array = reader.array;
63 | }
64 |
65 | JSONArrayWriter add(JSONTypes type, Object value) throws IOException {
66 | array.add(new JSONValue(type, value));
67 | return this;
68 | }
69 |
70 | public JSONArrayWriter setString(String value) throws IOException {
71 | return add(JSONTypes.STRING, value);
72 | }
73 |
74 | public JSONArrayWriter setInt(int value) throws IOException {
75 | return setInt53(value);
76 | }
77 |
78 | public JSONArrayWriter setInt53(long value) throws IOException {
79 | return add(JSONTypes.NUMBER, JSONObjectWriter.serializeLong(value));
80 | }
81 |
82 | public JSONArrayWriter setLong(long value) throws IOException {
83 | return setBigInteger(BigInteger.valueOf(value));
84 | }
85 |
86 | public JSONArrayWriter setMoney(BigDecimal value) throws IOException {
87 | return setString(JSONObjectWriter.moneyToString(value, null));
88 | }
89 |
90 | public JSONArrayWriter setMoney(BigDecimal value, Integer decimals) throws IOException {
91 | return setString(JSONObjectWriter.moneyToString(value, decimals));
92 | }
93 |
94 | public JSONArrayWriter setBigDecimal(BigDecimal value) throws IOException {
95 | return setString(JSONObjectWriter.bigDecimalToString(value));
96 | }
97 |
98 | public JSONArrayWriter setBigInteger(BigInteger value) throws IOException {
99 | return setString(value.toString());
100 | }
101 |
102 | public JSONArrayWriter setDouble(double value) throws IOException {
103 | return add(JSONTypes.NUMBER, NumberToJSON.serializeNumber(value));
104 | }
105 |
106 | public JSONArrayWriter setBoolean(boolean value) throws IOException {
107 | return add(JSONTypes.BOOLEAN, Boolean.toString(value));
108 | }
109 |
110 | public JSONArrayWriter setNULL() throws IOException {
111 | return add(JSONTypes.NULL, "null");
112 | }
113 |
114 | public JSONArrayWriter setDateTime(GregorianCalendar dateTime, EnumSet format) throws IOException {
115 | return setString(ISODateTime.formatDateTime(dateTime, format));
116 | }
117 |
118 | public JSONArrayWriter setBinary(byte[] value) throws IOException {
119 | return setString(Base64URL.encode(value));
120 | }
121 |
122 | JSONObjectWriter createWriteable() throws IOException {
123 | JSONObject dummy = new JSONObject();
124 | dummy.properties.put(null, new JSONValue(JSONTypes.ARRAY, array));
125 | return new JSONObjectWriter(dummy);
126 |
127 | }
128 | public JSONArrayWriter setSignature (JSONSigner signer) throws IOException,
129 | GeneralSecurityException {
130 | JSONObjectWriter signatureObject = setObject();
131 | JSONObjectWriter.coreSign(signer,
132 | signatureObject,
133 | signatureObject,
134 | createWriteable());
135 | return this;
136 | }
137 |
138 | static public JSONArrayWriter createCoreCertificatePath(X509Certificate[] certificatePath)
139 | throws IOException, GeneralSecurityException {
140 | JSONArrayWriter arrayWriter = new JSONArrayWriter();
141 | for (X509Certificate certificate : CertificateUtil.checkCertificatePath(certificatePath)) {
142 | try {
143 | arrayWriter.setString(Base64URL.encode(certificate.getEncoded()));
144 | } catch (GeneralSecurityException e) {
145 | throw new IOException(e);
146 | }
147 | }
148 | return arrayWriter;
149 | }
150 |
151 | /**
152 | * Create nested array.
153 | * This method creates a new array writer at the current position.
154 | * @return Array writer
155 | * @throws IOException
156 | */
157 | public JSONArrayWriter setArray() throws IOException {
158 | JSONArrayWriter writer = new JSONArrayWriter();
159 | add(JSONTypes.ARRAY, writer.array);
160 | return writer;
161 | }
162 |
163 | /**
164 | * Create nested array.
165 | * This method inserts an existing array writer at the current position.
166 | * @param writer Instance of array writer
167 | * @return Array writer
168 | * @throws IOException
169 | */
170 | public JSONArrayWriter setArray(JSONArrayWriter writer) throws IOException {
171 | add(JSONTypes.ARRAY, writer.array);
172 | return this;
173 | }
174 |
175 | public JSONObjectWriter setObject() throws IOException {
176 | JSONObjectWriter writer = new JSONObjectWriter();
177 | add(JSONTypes.OBJECT, writer.root);
178 | return writer;
179 | }
180 |
181 | public JSONArrayWriter setObject(JSONObjectWriter writer) throws IOException {
182 | add(JSONTypes.OBJECT, writer.root);
183 | return this;
184 | }
185 |
186 | public String serializeToString(JSONOutputFormats outputFormat) throws IOException {
187 | return createWriteable().serializeToString(outputFormat);
188 | }
189 |
190 | public byte[] serializeToBytes(JSONOutputFormats outputFormat) throws IOException {
191 | return serializeToString(outputFormat).getBytes("UTF-8");
192 | }
193 |
194 | @Override
195 | public String toString() {
196 | try {
197 | return serializeToString(JSONOutputFormats.PRETTY_PRINT);
198 | } catch (IOException e) {
199 | throw new RuntimeException(e);
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/util/ISODateTime.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.util;
18 |
19 | import java.io.IOException;
20 |
21 | import java.text.SimpleDateFormat;
22 |
23 | import java.util.Calendar;
24 | import java.util.EnumSet;
25 | import java.util.GregorianCalendar;
26 | import java.util.SimpleTimeZone;
27 | import java.util.TimeZone;
28 |
29 | import java.util.regex.Pattern;
30 |
31 | /**
32 | * Useful functions for ISO time.
33 | */
34 | public class ISODateTime {
35 |
36 | private ISODateTime() {} // No instantiation please
37 |
38 | public static enum DatePatterns {UTC,
39 | LOCAL,
40 | MILLISECONDS,
41 | MICROSECONDS,
42 | NANOSECONDS,
43 | ANYFRACTION};
44 |
45 | public static final EnumSet UTC_NO_SUBSECONDS = EnumSet.of(DatePatterns.UTC);
46 | public static final EnumSet LOCAL_NO_SUBSECONDS = EnumSet.of(DatePatterns.LOCAL);
47 | public static final EnumSet COMPLETE = EnumSet.allOf(DatePatterns.class);
48 |
49 | static final Pattern DATE_PATTERN =
50 | Pattern.compile("(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(\\.\\d{1,9})?([+-]\\d{2}:\\d{2}|Z)");
51 |
52 |
53 | /**
54 | * Parse an ISO formatted dateTime string.
55 | * Always: yyyy-mm-ddThh:mm:ss
56 | * Optionally: a '.' followed by 1-9 digits holding fractions of a second
57 | * Finally: 'Z' for UTC or an UTC time-zone difference expressed as +hh:mm or -hh:mm
58 | *
59 | * @param dateTime String to be parsed
60 | * @param constraints Permitted format(s)
61 | * @return GregorianCalendar
62 | * @throws IOException If anything unexpected is found...
63 | */
64 | public static GregorianCalendar parseDateTime(String dateTime,
65 | EnumSet constraints)
66 | throws IOException {
67 |
68 | if (!DATE_PATTERN.matcher(dateTime).matches()) {
69 | throw new IOException("DateTime syntax error: " + dateTime);
70 | }
71 |
72 | GregorianCalendar gc = new GregorianCalendar();
73 | gc.clear();
74 |
75 | gc.set(GregorianCalendar.ERA, GregorianCalendar.AD);
76 | gc.set(GregorianCalendar.YEAR, Integer.parseInt(dateTime.substring(0, 4)));
77 | gc.set(GregorianCalendar.MONTH, Integer.parseInt(dateTime.substring(5, 7)) - 1);
78 |
79 | gc.set(GregorianCalendar.DAY_OF_MONTH, Integer.parseInt(dateTime.substring(8, 10)));
80 |
81 | gc.set(GregorianCalendar.HOUR_OF_DAY, Integer.parseInt(dateTime.substring(11, 13)));
82 |
83 | gc.set(GregorianCalendar.MINUTE, Integer.parseInt(dateTime.substring(14, 16)));
84 |
85 | gc.set(GregorianCalendar.SECOND, Integer.parseInt(dateTime.substring(17, 19)));
86 |
87 | String milliSeconds = null;
88 |
89 | // Find time zone info.
90 | if (dateTime.endsWith("Z")) {
91 | if (!constraints.contains(DatePatterns.UTC)) {
92 | bad(dateTime);
93 | }
94 | gc.setTimeZone(TimeZone.getTimeZone("UTC"));
95 | milliSeconds = dateTime.substring(19, dateTime.length() - 1);
96 | } else {
97 | if (!constraints.contains(DatePatterns.LOCAL)) {
98 | bad(dateTime);
99 | }
100 | int factor = 60 * 1000;
101 | int i = dateTime.indexOf('+');
102 | if (i < 0) {
103 | i = dateTime.lastIndexOf('-');
104 | factor = -factor;
105 | }
106 | milliSeconds = dateTime.substring(19, i);
107 | int tzHour = Integer.parseInt(dateTime.substring(++i, i + 2)),
108 | tzMinute = Integer.parseInt(dateTime.substring(i + 3, i + 5));
109 | gc.setTimeZone(new SimpleTimeZone(((60 * tzHour) + tzMinute) * factor, ""));
110 | }
111 | if (milliSeconds.length() > 0) {
112 | if (!constraints.contains(DatePatterns.ANYFRACTION)) {
113 | if (constraints.contains(DatePatterns.MILLISECONDS)) {
114 | if (milliSeconds.length() != 4) {
115 | bad(dateTime);
116 | }
117 | } else if (constraints.contains(DatePatterns.MICROSECONDS)) {
118 | if (milliSeconds.length() != 7) {
119 | bad(dateTime);
120 | }
121 | } else if (constraints.contains(DatePatterns.NANOSECONDS)) {
122 | if (milliSeconds.length() != 10) {
123 | bad(dateTime);
124 | }
125 | } else {
126 | bad(dateTime);
127 | }
128 | }
129 | // Milliseconds is the only thing we can eat though
130 | milliSeconds = milliSeconds.substring(1, milliSeconds.length() > 4 ? 4 : milliSeconds.length());
131 | int fraction = Integer.parseInt(milliSeconds) * 100;
132 | for (int q = 1; q < milliSeconds.length(); q++) {
133 | fraction /= 10;
134 | }
135 | gc.set(GregorianCalendar.MILLISECOND, fraction);
136 | } else if (constraints.contains(DatePatterns.MILLISECONDS) &&
137 | !constraints.contains(DatePatterns.ANYFRACTION)) {
138 | bad(dateTime);
139 | }
140 | return gc;
141 | }
142 |
143 | private static void bad(String dateTime) throws IOException {
144 | throw new IOException("DateTime format doesn't match specification: " + dateTime);
145 | }
146 |
147 | /**
148 | * Create an ISO formatted dateTime string.
149 | * Always: yyyy-mm-ddThh:mm:ss
150 | * Optional: a '.' followed by 3 digits holding milliseconds
151 | * UTC: Append 'Z'
152 | * Local time: Append time-zone difference expressed as +hh:mm or -hh:mm
153 | * @param dateTime The date/time object
154 | * @param format Format: Note Representation: true for UTC, false for local time
155 | * @return String
156 | */
157 | public static String formatDateTime(GregorianCalendar dateTime, EnumSet format) {
158 | SimpleDateFormat sdf = new SimpleDateFormat(
159 | format.contains(DatePatterns.MILLISECONDS) ?
160 | "yyyy-MM-dd'T'HH:mm:ss.SSS" : "yyyy-MM-dd'T'HH:mm:ss");
161 | sdf.setTimeZone(format.contains(DatePatterns.UTC) ?
162 | TimeZone.getTimeZone("UTC") : dateTime.getTimeZone());
163 | StringBuilder s = new StringBuilder(sdf.format(dateTime.getTime()));
164 | if (format.contains(DatePatterns.UTC)) {
165 | s.append('Z');
166 | } else {
167 | int tzo = (dateTime.get(Calendar.ZONE_OFFSET) + dateTime.get(Calendar.DST_OFFSET)) / (60 * 1000);
168 | if (tzo < 0) {
169 | tzo = - tzo;
170 | s.append('-');
171 | } else {
172 | s.append('+');
173 | }
174 | int tzh = tzo / 60, tzm = tzo % 60;
175 | s.append(tzh < 10 ? "0" : "").append(tzh).append(tzm < 10 ? ":0" : ":").append(tzm);
176 | }
177 | return s.toString();
178 | }
179 | }
180 |
181 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONEncrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PublicKey;
23 |
24 | import java.util.LinkedHashSet;
25 |
26 | import org.webpki.crypto.AlgorithmPreferences;
27 | import org.webpki.crypto.ContentEncryptionAlgorithms;
28 | import org.webpki.crypto.CryptoRandom;
29 | import org.webpki.crypto.EncryptionCore;
30 | import org.webpki.crypto.KeyEncryptionAlgorithms;
31 |
32 | /**
33 | * Support class for encryption generators.
34 | */
35 | public abstract class JSONEncrypter {
36 |
37 | JSONObjectReader extensions;
38 |
39 | String keyId;
40 |
41 | boolean outputPublicKeyInfo = true;
42 |
43 | KeyEncryptionAlgorithms keyEncryptionAlgorithm;
44 |
45 | byte[] contentEncryptionKey;
46 |
47 | PublicKey publicKey;
48 |
49 | AlgorithmPreferences algorithmPreferences = AlgorithmPreferences.JOSE_ACCEPT_PREFER;
50 |
51 | JSONEncrypter() {
52 | }
53 |
54 | static class Header {
55 |
56 | ContentEncryptionAlgorithms contentEncryptionAlgorithm;
57 |
58 | JSONObjectWriter encryptionWriter;
59 |
60 | byte[] contentEncryptionKey;
61 |
62 | LinkedHashSet foundExtensions = new LinkedHashSet<>();
63 |
64 | Header(ContentEncryptionAlgorithms contentEncryptionAlgorithm, JSONEncrypter encrypter)
65 | throws IOException {
66 | this.contentEncryptionAlgorithm = contentEncryptionAlgorithm;
67 | contentEncryptionKey = encrypter.contentEncryptionKey;
68 | encryptionWriter = new JSONObjectWriter();
69 | encryptionWriter.setString(JSONCryptoHelper.ALGORITHM_JSON,
70 | contentEncryptionAlgorithm.getJoseAlgorithmId());
71 | if (encrypter.keyEncryptionAlgorithm != null &&
72 | encrypter.keyEncryptionAlgorithm.isKeyWrap()) {
73 | contentEncryptionKey =
74 | CryptoRandom.generateRandom(contentEncryptionAlgorithm.getKeyLength());
75 | }
76 | }
77 |
78 | void createRecipient(JSONEncrypter encrypter, JSONObjectWriter currentRecipient)
79 | throws IOException, GeneralSecurityException {
80 | if (encrypter.keyEncryptionAlgorithm != null) {
81 | currentRecipient.setString(JSONCryptoHelper.ALGORITHM_JSON,
82 | encrypter.keyEncryptionAlgorithm.getJoseAlgorithmId());
83 | }
84 |
85 | if (encrypter.keyId != null) {
86 | currentRecipient.setString(JSONCryptoHelper.KEY_ID_JSON, encrypter.keyId);
87 | }
88 |
89 | if (encrypter.outputPublicKeyInfo) {
90 | encrypter.writeKeyData(currentRecipient);
91 | }
92 |
93 | // The encrypted key part (if any)
94 | if (encrypter.keyEncryptionAlgorithm != null) {
95 | EncryptionCore.AsymmetricEncryptionResult asymmetricEncryptionResult =
96 | encrypter.keyEncryptionAlgorithm.isRsa() ?
97 | EncryptionCore.rsaEncryptKey(contentEncryptionKey,
98 | encrypter.keyEncryptionAlgorithm,
99 | encrypter.publicKey)
100 | :
101 | EncryptionCore.senderKeyAgreement(false,
102 | contentEncryptionKey,
103 | encrypter.keyEncryptionAlgorithm,
104 | contentEncryptionAlgorithm,
105 | encrypter.publicKey);
106 | contentEncryptionKey = asymmetricEncryptionResult.getContentEncryptionKey();
107 | if (!encrypter.keyEncryptionAlgorithm.isRsa()) {
108 | currentRecipient
109 | .setObject(JSONCryptoHelper.EPHEMERAL_KEY_JSON,
110 | JSONObjectWriter
111 | .createCorePublicKey(
112 | asymmetricEncryptionResult.getEphemeralKey(),
113 | AlgorithmPreferences.JOSE));
114 | }
115 | if (encrypter.keyEncryptionAlgorithm.isKeyWrap()) {
116 | currentRecipient.setBinary(JSONCryptoHelper.ENCRYPTED_KEY_JSON,
117 | asymmetricEncryptionResult.getEncryptedKeyData());
118 | }
119 | }
120 |
121 | if (encrypter.extensions != null) {
122 | for (String property : encrypter.extensions.getProperties()) {
123 | foundExtensions.add(property);
124 | currentRecipient.setProperty(property,
125 | encrypter.extensions.getProperty(property));
126 | }
127 | }
128 | }
129 |
130 | JSONObjectWriter finalizeEncryption(byte[] unencryptedData)
131 | throws IOException, GeneralSecurityException {
132 | if (!foundExtensions.isEmpty()) {
133 | encryptionWriter.setStringArray(JSONCryptoHelper.EXTENSIONS_JSON,
134 | foundExtensions.toArray(new String[0]));
135 | }
136 | byte[] iv = EncryptionCore.createIv(contentEncryptionAlgorithm);
137 | EncryptionCore.SymmetricEncryptionResult symmetricEncryptionResult =
138 | EncryptionCore.contentEncryption(contentEncryptionAlgorithm,
139 | contentEncryptionKey,
140 | iv,
141 | unencryptedData,
142 | encryptionWriter.serializeToBytes(
143 | JSONOutputFormats.CANONICALIZED));
144 | encryptionWriter.setBinary(JSONCryptoHelper.IV_JSON, iv);
145 | encryptionWriter.setBinary(JSONCryptoHelper.TAG_JSON,
146 | symmetricEncryptionResult.getTag());
147 | encryptionWriter.setBinary(JSONCryptoHelper.CIPHER_TEXT_JSON,
148 | symmetricEncryptionResult.getCipherText());
149 | return encryptionWriter;
150 | }
151 | }
152 |
153 | abstract void writeKeyData(JSONObjectWriter wr) throws IOException, GeneralSecurityException;
154 |
155 | /**
156 | * Set "crit" for this encryption object.
157 | * @param extensions JSON object holding the extension properties and associated values
158 | * @return this
159 | * @throws IOException
160 | */
161 | public JSONEncrypter setExtensions(JSONObjectWriter extensions) throws IOException {
162 | this.extensions = new JSONObjectReader(extensions);
163 | JSONCryptoHelper.checkExtensions(this.extensions.getProperties(), true);
164 | return this;
165 | }
166 |
167 | /**
168 | * Set optional "kid" for this encryption object.
169 | * @param keyId The identifier
170 | * @return this
171 | */
172 | public JSONEncrypter setKeyId(String keyId) {
173 | this.keyId = keyId;
174 | return this;
175 | }
176 |
177 | /**
178 | * Set if public key information should be provided in the encryption object.
179 | * Note: default true.
180 | * @param flag true if such information is to be provided
181 | * @return this
182 | */
183 | public JSONEncrypter setOutputPublicKeyInfo(boolean flag) {
184 | this.outputPublicKeyInfo = flag;
185 | return this;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/SignatureWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.Key;
23 | import java.security.PrivateKey;
24 | import java.security.Provider;
25 | import java.security.PublicKey;
26 | import java.security.Signature;
27 |
28 | import java.security.interfaces.ECKey;
29 |
30 | import java.security.spec.ECParameterSpec;
31 |
32 | /**
33 | * Wrapper over java.security.Signature.
34 | *
35 | * Source configured for Android.
36 | */
37 | public class SignatureWrapper {
38 |
39 | static final int ASN1_SEQUENCE = 0x30;
40 | static final int ASN1_INTEGER = 0x02;
41 |
42 | static final int LEADING_ZERO = 0x00;
43 |
44 | boolean ecdsaAsn1EncodedFlag;
45 |
46 | private static int getExtendTo(ECParameterSpec ecParameters)
47 | throws IOException, GeneralSecurityException {
48 | return (KeyAlgorithms.getECKeyAlgorithm(ecParameters).getPublicKeySizeInBits() + 7) / 8;
49 | }
50 |
51 | private byte[] decodeAsn1EncodedEcdsaSignature(byte[] derCodedSignature,
52 | ECParameterSpec ecParameters)
53 | throws IOException, GeneralSecurityException {
54 | int extendTo = getExtendTo(ecParameters);
55 | int index = 2;
56 | int length;
57 | byte[] concatenatedSignature = new byte[extendTo << 1];
58 | if (derCodedSignature[0] != ASN1_SEQUENCE) {
59 | throw new IOException("Not SEQUENCE");
60 | }
61 | length = derCodedSignature[1];
62 | if (length < 4) {
63 | if (length != -127) {
64 | throw new IOException("ASN.1 Length error");
65 | }
66 | length = derCodedSignature[index++] & 0xFF;
67 | }
68 | if (index != derCodedSignature.length - length) {
69 | throw new IOException("ASN.1 Length error");
70 | }
71 | for (int offset = 0; offset <= extendTo; offset += extendTo) {
72 | if (derCodedSignature[index++] != ASN1_INTEGER) {
73 | throw new IOException("Not INTEGER");
74 | }
75 | int l = derCodedSignature[index++];
76 | while (l > extendTo) {
77 | if (derCodedSignature[index++] != LEADING_ZERO) {
78 | throw new IOException("Bad INTEGER");
79 | }
80 | l--;
81 | }
82 | System.arraycopy(derCodedSignature, index, concatenatedSignature, offset + extendTo - l, l);
83 | index += l;
84 | }
85 | if (index != derCodedSignature.length) {
86 | throw new IOException("ASN.1 Length error");
87 | }
88 | return concatenatedSignature;
89 | }
90 |
91 | private byte[] encodeAsn1EncodedEcdsaSignature(byte[] concatenatedSignature,
92 | ECParameterSpec ecParameters)
93 | throws IOException, GeneralSecurityException {
94 | int extendTo = getExtendTo(ecParameters);
95 | if (extendTo != concatenatedSignature.length / 2) {
96 | throw new IOException("Signature length error");
97 | }
98 |
99 | int i = extendTo;
100 | while (i > 0 && concatenatedSignature[extendTo - i] == LEADING_ZERO) {
101 | i--;
102 | }
103 | int j = i;
104 | if (concatenatedSignature[extendTo - i] < 0) {
105 | j++;
106 | }
107 |
108 | int k = extendTo;
109 | while (k > 0 && concatenatedSignature[2 * extendTo - k] == LEADING_ZERO) {
110 | k--;
111 | }
112 | int l = k;
113 | if (concatenatedSignature[2 * extendTo - k] < 0) {
114 | l++;
115 | }
116 |
117 | int len = 2 + j + 2 + l;
118 | int offset = 1;
119 | byte derCodedSignature[];
120 | if (len < 128) {
121 | derCodedSignature = new byte[len + 2];
122 | } else {
123 | derCodedSignature = new byte[len + 3];
124 | derCodedSignature[1] = (byte) 0x81;
125 | offset = 2;
126 | }
127 | derCodedSignature[0] = ASN1_SEQUENCE;
128 | derCodedSignature[offset++] = (byte) len;
129 | derCodedSignature[offset++] = ASN1_INTEGER;
130 | derCodedSignature[offset++] = (byte) j;
131 | System.arraycopy(concatenatedSignature, extendTo - i, derCodedSignature, offset + j - i, i);
132 | offset += j;
133 | derCodedSignature[offset++] = ASN1_INTEGER;
134 | derCodedSignature[offset++] = (byte) l;
135 | System.arraycopy(concatenatedSignature, 2 * extendTo - k, derCodedSignature, offset + l - k, k);
136 | return derCodedSignature;
137 | }
138 |
139 | Signature instance;
140 | boolean unmodifiedSignature;
141 | ECParameterSpec ecParameters;
142 |
143 | private SignatureWrapper(AsymSignatureAlgorithms algorithm, String provider, Key key)
144 | throws GeneralSecurityException, IOException {
145 | KeyAlgorithms keyAlgorithm = KeyAlgorithms.getKeyAlgorithm(key);
146 | if (keyAlgorithm.getKeyType() != algorithm.getKeyType()) {
147 | throw new IllegalArgumentException(
148 | "Supplied key (" +
149 | keyAlgorithm.toString() +
150 | ") is incompatible with specified algorithm (" +
151 | algorithm.toString() +
152 | ")");
153 | }
154 | instance = provider == null ?
155 | Signature.getInstance(algorithm.getJceName())
156 | :
157 | Signature.getInstance(algorithm.getJceName(), provider);
158 | unmodifiedSignature = algorithm.getKeyType() != KeyTypes.EC;
159 | if (!unmodifiedSignature) {
160 | ecParameters = ((ECKey) key).getParams();
161 | }
162 | }
163 |
164 | public SignatureWrapper(AsymSignatureAlgorithms algorithm,
165 | PublicKey publicKey,
166 | String provider) throws GeneralSecurityException, IOException {
167 | this(algorithm, provider, publicKey);
168 | instance.initVerify(publicKey);
169 | }
170 |
171 | public SignatureWrapper(AsymSignatureAlgorithms algorithm,
172 | PublicKey publicKey) throws GeneralSecurityException, IOException {
173 | this(algorithm, publicKey, null);
174 | }
175 |
176 | public SignatureWrapper(AsymSignatureAlgorithms algorithm,
177 | PrivateKey privateKey,
178 | String provider) throws GeneralSecurityException, IOException {
179 | this(algorithm, provider, privateKey);
180 | instance.initSign(privateKey);
181 | }
182 |
183 | public SignatureWrapper(AsymSignatureAlgorithms algorithm,
184 | PrivateKey privateKey) throws GeneralSecurityException, IOException {
185 | this(algorithm, privateKey, null);
186 | }
187 |
188 | public SignatureWrapper ecdsaAsn1SignatureEncoding(boolean flag) {
189 | ecdsaAsn1EncodedFlag = flag;
190 | return this;
191 | }
192 |
193 | public SignatureWrapper update(byte[] data) throws GeneralSecurityException {
194 | instance.update(data);
195 | return this;
196 | }
197 |
198 | public SignatureWrapper update(byte data) throws GeneralSecurityException {
199 | instance.update(data);
200 | return this;
201 | }
202 |
203 | public Provider getProvider() {
204 | return instance.getProvider();
205 | }
206 |
207 | public boolean verify(byte[] signature) throws GeneralSecurityException, IOException {
208 | return instance.verify(ecdsaAsn1EncodedFlag || unmodifiedSignature ?
209 | signature : encodeAsn1EncodedEcdsaSignature(signature, ecParameters));
210 | }
211 |
212 | public byte[] sign() throws GeneralSecurityException, IOException {
213 | return ecdsaAsn1EncodedFlag || unmodifiedSignature ?
214 | instance.sign() : decodeAsn1EncodedEcdsaSignature(instance.sign(), ecParameters);
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/crypto/AsymSignatureAlgorithms.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.crypto;
18 |
19 | import java.security.spec.MGF1ParameterSpec;
20 |
21 | /**
22 | * Asymmetric key signature algorithms.
23 | *
24 | */
25 | public enum AsymSignatureAlgorithms implements SignatureAlgorithms {
26 |
27 | RSA_SHA1 ("http://www.w3.org/2000/09/xmldsig#rsa-sha1", null,
28 | 0, "1.2.840.113549.1.1.5", "SHA1withRSA",
29 | HashAlgorithms.SHA1, false, KeyTypes.RSA, null),
30 |
31 | // Not defined by COSE but RS256 is defined by FIDO
32 | RSA_SHA256 ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RS256",
33 | -257, "1.2.840.113549.1.1.11", "SHA256withRSA",
34 | HashAlgorithms.SHA256, true, KeyTypes.RSA, null),
35 |
36 | // Not defined by COSE
37 | RSA_SHA384 ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RS384",
38 | -258, "1.2.840.113549.1.1.12", "SHA384withRSA",
39 | HashAlgorithms.SHA384, true, KeyTypes.RSA, null),
40 |
41 | // Not defined by COSE
42 | RSA_SHA512 ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RS512",
43 | -259, "1.2.840.113549.1.1.13", "SHA512withRSA",
44 | HashAlgorithms.SHA512, true, KeyTypes.RSA, null),
45 |
46 | RSAPSS_SHA256 ("http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1", "PS256",
47 | -37, "1.2.840.113549.1.1.10", "SHA256withRSA/PSS",
48 | null/* Android != JDK */, false, KeyTypes.RSA, MGF1ParameterSpec.SHA256),
49 |
50 | RSAPSS_SHA384 ("http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1", "PS384",
51 | -38, "1.2.840.113549.1.1.10", "SHA384withRSA/PSS",
52 | null/* Android != JDK */, false, KeyTypes.RSA, MGF1ParameterSpec.SHA384),
53 |
54 | RSAPSS_SHA512 ("http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1", "PS512",
55 | -39, "1.2.840.113549.1.1.10", "SHA512withRSA/PSS",
56 | null/* Android != JDK */, false, KeyTypes.RSA, MGF1ParameterSpec.SHA512),
57 |
58 | ECDSA_SHA256 ("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", "ES256",
59 | -7, "1.2.840.10045.4.3.2", "SHA256withECDSA",
60 | HashAlgorithms.SHA256, true, KeyTypes.EC, null),
61 |
62 | ECDSA_SHA384 ("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", "ES384",
63 | -35, "1.2.840.10045.4.3.3", "SHA384withECDSA",
64 | HashAlgorithms.SHA384, true, KeyTypes.EC, null),
65 |
66 | ECDSA_SHA512 ("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", "ES512",
67 | -36, "1.2.840.10045.4.3.4", "SHA512withECDSA",
68 | HashAlgorithms.SHA512, true, KeyTypes.EC, null),
69 |
70 | // In COSE this is just "EdDSA", but here we say it is Ed25519 which is also
71 | // how FIDO treated this.
72 | ED25519 ("https://webpki.github.io/sks/algorithm#ed25519", "Ed25519",
73 | -8, "1.3.101.112", "Ed25519",
74 | null /*"pure" */, false, KeyTypes.EDDSA, null),
75 |
76 | // Incompatible with COSE, but compatible with most cryptographic
77 | // APIs as well as PKIX's way of dealing with with different EdDSA
78 | // variants. That is, each being treated as a specific algorithm.
79 | ED448 ("https://webpki.github.io/sks/algorithm#ed448", "Ed448",
80 | -9, "1.3.101.113", "Ed448",
81 | null /*"pure" */, false, KeyTypes.EDDSA, null);
82 |
83 | private final String sksId; // As expressed in SKS
84 | private final String joseId; // JOSE
85 | private final int coseId; // COSE
86 | private final String oid; // As expressed in OIDs
87 | private final String jceName; // As expressed for JCE
88 | private final HashAlgorithms digestAlg; // RSA and ECDSA
89 | private final boolean sksMandatory; // If required in SKS
90 | private final KeyTypes keyType; // Core type
91 | private final MGF1ParameterSpec mgf1; // For RSA PSS
92 |
93 | private AsymSignatureAlgorithms(String sksId,
94 | String joseId,
95 | int coseId,
96 | String oid,
97 | String jceName,
98 | HashAlgorithms digestAlg,
99 | boolean sksMandatory,
100 | KeyTypes keyType,
101 | MGF1ParameterSpec mgf1) {
102 | this.sksId = sksId;
103 | this.joseId = joseId;
104 | this.coseId = coseId;
105 | this.oid = oid;
106 | this.jceName = jceName;
107 | this.digestAlg = digestAlg;
108 | this.sksMandatory = sksMandatory;
109 | this.keyType = keyType;
110 | this.mgf1 = mgf1;
111 | }
112 |
113 | @Override
114 | public boolean isMandatorySksAlgorithm() {
115 | return sksMandatory;
116 | }
117 |
118 | @Override
119 | public String getJceName() {
120 | return jceName;
121 | }
122 |
123 | @Override
124 | public String getOid() {
125 | return oid;
126 | }
127 |
128 | @Override
129 | public HashAlgorithms getDigestAlgorithm() {
130 | return digestAlg;
131 | }
132 |
133 | public static boolean testAlgorithmUri(String sksId) {
134 | for (AsymSignatureAlgorithms alg : values()) {
135 | if (sksId.equals(alg.sksId)) {
136 | return true;
137 | }
138 | }
139 | return false;
140 | }
141 |
142 | public static AsymSignatureAlgorithms getAlgorithmFromId(
143 | String algorithmId,
144 | AlgorithmPreferences algorithmPreferences) {
145 | for (AsymSignatureAlgorithms alg : AsymSignatureAlgorithms.values()) {
146 | if (algorithmId.equals(alg.sksId)) {
147 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
148 | throw new IllegalArgumentException("JOSE algorithm expected: " + algorithmId);
149 | }
150 | return alg;
151 | }
152 | if (algorithmId.equals(alg.joseId)) {
153 | if (algorithmPreferences == AlgorithmPreferences.SKS) {
154 | throw new IllegalArgumentException("SKS algorithm expected: " + algorithmId);
155 | }
156 | return alg;
157 | }
158 | }
159 | throw new IllegalArgumentException("Unknown signature algorithm: " + algorithmId);
160 | }
161 |
162 | @Override
163 | public String getAlgorithmId(AlgorithmPreferences algorithmPreferences) {
164 | if (joseId == null) {
165 | if (algorithmPreferences == AlgorithmPreferences.JOSE) {
166 | throw new IllegalArgumentException("There is no JOSE algorithm for: " +
167 | this.toString());
168 | }
169 | return sksId;
170 | } else if (sksId == null) {
171 | if (algorithmPreferences == AlgorithmPreferences.SKS) {
172 | throw new IllegalArgumentException("There is no SKS algorithm for: " +
173 | this.toString());
174 | }
175 | return joseId;
176 | }
177 | return algorithmPreferences == AlgorithmPreferences.SKS ? sksId : joseId;
178 | }
179 |
180 | @Override
181 | public boolean isDeprecated() {
182 | return RSA_SHA1 == this;
183 | }
184 |
185 | @Override
186 | public KeyTypes getKeyType() {
187 | return keyType;
188 | }
189 |
190 | public MGF1ParameterSpec getMGF1ParameterSpec() {
191 | return mgf1;
192 | }
193 |
194 | @Override
195 | public int getCoseAlgorithmId() {
196 | if (coseId == 0) {
197 | throw new IllegalArgumentException("There is no COSE algorithm for :" +
198 | this.toString());
199 | }
200 | return coseId;
201 | }
202 |
203 | public static AsymSignatureAlgorithms getAlgorithmFromId(int coseAlgorithmId) {
204 | for (AsymSignatureAlgorithms alg : AsymSignatureAlgorithms.values()) {
205 | if (coseAlgorithmId == alg.coseId) {
206 | alg.getCoseAlgorithmId();
207 | return alg;
208 | }
209 | }
210 | throw new IllegalArgumentException("Unknown COSE signature algorithm: " +
211 | coseAlgorithmId);
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/jose/jws/JWSDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018-2020 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.jose.jws;
18 |
19 | import java.io.IOException;
20 |
21 | import java.security.GeneralSecurityException;
22 | import java.security.PublicKey;
23 |
24 | import java.security.cert.X509Certificate;
25 |
26 | import org.webpki.crypto.AlgorithmPreferences;
27 | import org.webpki.crypto.AsymSignatureAlgorithms;
28 | import org.webpki.crypto.HmacAlgorithms;
29 | import org.webpki.crypto.SignatureAlgorithms;
30 |
31 | import static org.webpki.jose.JOSEKeyWords.*;
32 |
33 | import org.webpki.json.JSONArrayWriter;
34 | import org.webpki.json.JSONObjectReader;
35 | import org.webpki.json.JSONOutputFormats;
36 | import org.webpki.json.JSONParser;
37 |
38 | import org.webpki.util.Base64URL;
39 |
40 | /**
41 | * JWS and JWS/CT decoder
42 | */
43 | public class JWSDecoder {
44 |
45 | String jwsHeaderB64U;
46 |
47 | JSONObjectReader jwsHeader;
48 |
49 | String jwsPayloadB64U;
50 |
51 | JSONObjectReader savedJWSCtObject;
52 |
53 | boolean validated;
54 |
55 | SignatureAlgorithms signatureAlgorithm;
56 |
57 | byte[] signature;
58 |
59 | PublicKey optionalPublicKey;
60 |
61 | X509Certificate[] optionalCertificatePath;
62 |
63 | String optionalKeyId;
64 |
65 | private void checkValidation() throws GeneralSecurityException {
66 | if (!validated) {
67 | throw new GeneralSecurityException("Trying to access payload before validation");
68 | }
69 | }
70 |
71 | private void decodeJWSString(String jwsString) throws GeneralSecurityException, IOException {
72 |
73 | // Extract the JWS elements
74 | int endOfHeader = jwsString.indexOf('.');
75 | int startOfSignature = jwsString.lastIndexOf('.');
76 | if (endOfHeader == startOfSignature - 1)
77 | if (endOfHeader < 10 || startOfSignature > jwsString.length() - 10) {
78 | throw new GeneralSecurityException("JWS syntax error");
79 | }
80 | if (endOfHeader < startOfSignature - 1) {
81 | // In-line signature
82 | jwsPayloadB64U = jwsString.substring(endOfHeader + 1, startOfSignature);
83 | Base64URL.decode(jwsPayloadB64U); // Syntax check
84 | }
85 |
86 | // Begin decoding the JWS header
87 | jwsHeaderB64U = jwsString.substring(0, endOfHeader);
88 | jwsHeader = JSONParser.parse(Base64URL.decode(jwsHeaderB64U));
89 | String algorithmProperty = jwsHeader.getString(ALG_JSON);
90 |
91 | // Get the binary signature
92 | signature = Base64URL.decode(jwsString.substring(startOfSignature + 1));
93 |
94 | // This is pretty ugly, two different conventions in the same standard!
95 | if (algorithmProperty.equals(EdDSA)) {
96 | signatureAlgorithm = signature.length == 64 ?
97 | AsymSignatureAlgorithms.ED25519 : AsymSignatureAlgorithms.ED448;
98 | } else if (algorithmProperty.startsWith("HS")) {
99 | signatureAlgorithm =
100 | HmacAlgorithms.getAlgorithmFromId(algorithmProperty,
101 | AlgorithmPreferences.JOSE);
102 | } else {
103 | signatureAlgorithm =
104 | AsymSignatureAlgorithms.getAlgorithmFromId(algorithmProperty,
105 | AlgorithmPreferences.JOSE);
106 | }
107 |
108 | // We don't bother about any other header data than possible public key
109 | // elements modulo JKU and X5U
110 |
111 | // Decode possible JWK
112 | if (jwsHeader.hasProperty(JWK_JSON)) {
113 | optionalPublicKey =
114 | jwsHeader.getObject(JWK_JSON)
115 | .getCorePublicKey(AlgorithmPreferences.JOSE);
116 | }
117 |
118 | // Decode possible X5C?
119 | if (jwsHeader.hasProperty(X5C_JSON)) {
120 | if (optionalPublicKey != null) {
121 | throw new GeneralSecurityException("Both X5C and JWK?");
122 | }
123 | JSONArrayWriter path = new JSONArrayWriter();
124 | for (String certB64 : jwsHeader.getStringArray(X5C_JSON)) {
125 | path.setString(certB64.replace("=","")
126 | .replace('/', '_')
127 | .replace('+', '-'));
128 | }
129 | optionalCertificatePath =
130 | JSONParser.parse(path.serializeToString(JSONOutputFormats.NORMALIZED))
131 | .getJSONArrayReader().getCertificatePath();
132 | optionalPublicKey = optionalCertificatePath[0].getPublicKey();
133 | }
134 | if (signatureAlgorithm.isSymmetric() && optionalPublicKey != null) {
135 | throw new GeneralSecurityException("Mix of symmetric and asymmetric elements?");
136 | }
137 |
138 | // Decode possible KID
139 | optionalKeyId = jwsHeader.getStringConditional(KID_JSON);
140 | }
141 |
142 | /**
143 | * JWS compact mode signature decoder.
144 | * @param jwsString The actual JWS string. If there is no payload detached mode is assumed
145 | * @throws IOException
146 | * @throws GeneralSecurityException
147 | */
148 | public JWSDecoder(String jwsString) throws IOException, GeneralSecurityException {
149 | decodeJWSString(jwsString);
150 | }
151 |
152 | /**
153 | * JWS/CT signature decoder.
154 | * Note that the jwsCtObject remains unmodified.
155 | * @param jwsCtObject The signed JSON object
156 | * @param signatureProperty Name of top-level property holding the JWS string
157 | * @throws IOException
158 | * @throws GeneralSecurityException
159 | */
160 | public JWSDecoder(JSONObjectReader jwsCtObject, String signatureProperty)
161 | throws IOException, GeneralSecurityException {
162 |
163 | // Do not alter the original!
164 | savedJWSCtObject = jwsCtObject.clone();
165 | String jwsString = savedJWSCtObject.getString(signatureProperty);
166 | if (!jwsString.contains("..")) {
167 | throw new GeneralSecurityException("JWS detached mode syntax error");
168 | }
169 | savedJWSCtObject.removeProperty(signatureProperty);
170 | jwsPayloadB64U = Base64URL.encode(
171 | savedJWSCtObject.serializeToBytes(JSONOutputFormats.CANONICALIZED));
172 | decodeJWSString(jwsString);
173 | }
174 |
175 | /**
176 | * Get JWS header.
177 | * @return JWS header as a JSON object.
178 | */
179 | public JSONObjectReader getJWSHeaderAsJson() {
180 | return jwsHeader;
181 | }
182 |
183 | /**
184 | * Get JWS header.
185 | * @return JWS header as a verbatim string copy after Base64Url-decoding.
186 | * @throws IOException
187 | */
188 | public String getJWSHeaderAsString() throws IOException {
189 | return new String(Base64URL.decode(jwsHeaderB64U), "utf-8");
190 | }
191 |
192 | /**
193 | * Get signature algorithm.
194 | */
195 | public SignatureAlgorithms getSignatureAlgorithm() {
196 | return signatureAlgorithm;
197 | }
198 |
199 | /**
200 | * Get optional "jwk".
201 | * @return Public key or null if there is no "jwk" property in the JWS header.
202 | */
203 | public PublicKey getOptionalPublicKey() {
204 | return optionalPublicKey;
205 | }
206 |
207 | /**
208 | * Get optional "x5c".
209 | * @return Certificate path or null if there is no "x5c" property in the JWS header.
210 | */
211 | public X509Certificate[] getOptionalCertificatePath() {
212 | return optionalCertificatePath;
213 | }
214 |
215 | /**
216 | * Get optional "kid".
217 | * @return Key identifier or null if there is no "kid" property in the JWS header.
218 | */
219 | public String getOptionalKeyId() {
220 | return optionalKeyId;
221 | }
222 |
223 | /**
224 | * Get JWS payload.
225 | * Note that this method throws an exception if the
226 | * {@link org.webpki.jose.jws.JWSDecoder}
227 | * object signature have not yet been
228 | * {@link org.webpki.jose.jws.JWSValidator#validate(JWSDecoder) validated}.
229 | * For JWS/CT, the payload holds the canonicalized
230 | * version of the
231 | * {@link org.webpki.jose.jws.JWSDecoder#JWSDecoder(JSONObjectReader, String) jwsCtObject}
232 | * with the
233 | * {@link org.webpki.jose.jws.JWSDecoder#JWSDecoder(JSONObjectReader, String) signatureProperty}
234 | * removed.
235 | * @return Payload binary
236 | * @throws GeneralSecurityException
237 | * @throws IOException
238 | */
239 | public byte[] getPayload() throws GeneralSecurityException, IOException {
240 | checkValidation();
241 | return Base64URL.decode(jwsPayloadB64U);
242 | }
243 |
244 | /**
245 | * Get JWS payload.
246 | * Note that this method throws an exception if the
247 | * {@link org.webpki.jose.jws.JWSDecoder}
248 | * object signature have not yet been
249 | * {@link org.webpki.jose.jws.JWSValidator#validate(JWSDecoder) validated}.
250 | * For JWS/CT this method return the JSON that is actually signed. That is,
251 | * all but the
252 | * {@link org.webpki.jose.jws.JWSDecoder#JWSDecoder(JSONObjectReader, String) signatureProperty}
253 | * and its JWS argument.
254 | * @return Payload as JSON
255 | * @throws GeneralSecurityException
256 | * @throws IOException
257 | */
258 | public JSONObjectReader getPayloadAsJson() throws GeneralSecurityException, IOException {
259 | if (savedJWSCtObject == null) {
260 | return JSONParser.parse(getPayload());
261 | }
262 | checkValidation();
263 | return savedJWSCtObject;
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/json/JSONParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.json;
18 |
19 | import java.io.IOException;
20 |
21 | import java.util.ArrayList;
22 |
23 | import java.util.regex.Pattern;
24 |
25 | /**
26 | * Parses JSON string/byte array data.
27 | */
28 | public class JSONParser {
29 |
30 | static final char LEFT_CURLY_BRACKET = '{';
31 | static final char RIGHT_CURLY_BRACKET = '}';
32 | static final char DOUBLE_QUOTE = '"';
33 | static final char COLON_CHARACTER = ':';
34 | static final char LEFT_BRACKET = '[';
35 | static final char RIGHT_BRACKET = ']';
36 | static final char COMMA_CHARACTER = ',';
37 | static final char BACK_SLASH = '\\';
38 |
39 | static final Pattern BOOLEAN_PATTERN = Pattern.compile("true|false");
40 | static final Pattern NUMBER_PATTERN = Pattern.compile("-?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)?");
41 |
42 | int index;
43 |
44 | int maxLength;
45 |
46 | String jsonData;
47 |
48 | static boolean strictNumericMode = false;
49 |
50 | JSONParser() {
51 | }
52 |
53 | JSONObjectReader internalParse(String jsonString) throws IOException {
54 | jsonData = jsonString;
55 | maxLength = jsonData.length();
56 | JSONObject root = new JSONObject();
57 | if (testNextNonWhiteSpaceChar() == LEFT_BRACKET) {
58 | scan();
59 | root.properties.put(null, scanArray());
60 | } else {
61 | scanFor(LEFT_CURLY_BRACKET);
62 | scanObject(root);
63 | }
64 | while (index < maxLength) {
65 | if (!isWhiteSpace(jsonData.charAt(index++))) {
66 | throw new IOException("Improperly terminated JSON object");
67 | }
68 | }
69 | return new JSONObjectReader(root);
70 | }
71 |
72 | /**
73 | * Parse JSON string data.
74 | * @param jsonString The data to be parsed in UTF-8
75 | * @return JSONObjectReader
76 | * @throws IOException
77 | */
78 | public static JSONObjectReader parse(String jsonString) throws IOException {
79 | return new JSONParser().internalParse(jsonString);
80 | }
81 |
82 | /**
83 | * Parse JSON byte array data.
84 | * @param jsonBytes The data to be parsed in UTF-8
85 | * @return JSONObjectReader
86 | * @throws IOException
87 | */
88 | public static JSONObjectReader parse(byte[] jsonBytes) throws IOException {
89 | return parse(new String(jsonBytes, "UTF-8"));
90 | }
91 |
92 | /**
93 | * Define strictness of "Number" parsing.
94 | * In strict mode 1.50 and 1e+3 would fail
95 | * since they are not normalized. Default mode is not strict.
96 | * @param strict True if strict mode is requested
97 | */
98 | public static void setStrictNumericMode(boolean strict) {
99 | strictNumericMode = strict;
100 | }
101 |
102 | JSONValue scanElement() throws IOException {
103 | switch (scan()) {
104 | case LEFT_CURLY_BRACKET:
105 | return scanObject(new JSONObject());
106 |
107 | case DOUBLE_QUOTE:
108 | return scanQuotedString();
109 |
110 | case LEFT_BRACKET:
111 | return scanArray();
112 |
113 | default:
114 | return scanSimpleType();
115 | }
116 | }
117 |
118 | JSONValue scanObject(JSONObject holder) throws IOException {
119 | boolean next = false;
120 | while (testNextNonWhiteSpaceChar() != RIGHT_CURLY_BRACKET) {
121 | if (next) {
122 | scanFor(COMMA_CHARACTER);
123 | }
124 | next = true;
125 | scanFor(DOUBLE_QUOTE);
126 | String name = (String) scanQuotedString().value;
127 | scanFor(COLON_CHARACTER);
128 | holder.setProperty(name, scanElement());
129 | }
130 | scan();
131 | return new JSONValue(JSONTypes.OBJECT, holder);
132 | }
133 |
134 | JSONValue scanArray() throws IOException {
135 | ArrayList array = new ArrayList<>();
136 | boolean next = false;
137 | while (testNextNonWhiteSpaceChar() != RIGHT_BRACKET) {
138 | if (next) {
139 | scanFor(COMMA_CHARACTER);
140 | } else {
141 | next = true;
142 | }
143 | array.add(scanElement());
144 | }
145 | scan();
146 | return new JSONValue(JSONTypes.ARRAY, array);
147 | }
148 |
149 | JSONValue scanSimpleType() throws IOException {
150 | index--;
151 | StringBuilder tempBuffer = new StringBuilder();
152 | char c;
153 | while ((c = testNextNonWhiteSpaceChar()) != COMMA_CHARACTER && c != RIGHT_BRACKET && c != RIGHT_CURLY_BRACKET) {
154 | if (isWhiteSpace(c = nextChar())) {
155 | break;
156 | }
157 | tempBuffer.append(c);
158 | }
159 | String token = tempBuffer.toString();
160 | if (token.length() == 0) {
161 | throw new IOException("Missing argument");
162 | }
163 | JSONTypes type = JSONTypes.NUMBER;
164 | if (NUMBER_PATTERN.matcher(token).matches()) {
165 | double number = Double.valueOf(token); // Syntax check...
166 | if (strictNumericMode) {
167 | String serializedNumber = NumberToJSON.serializeNumber(number);
168 | if (!serializedNumber.equals(token)) {
169 | throw new IOException("In the \"strict\" mode JSON Numbers must be fully normalized " +
170 | "according to ECMAScript. As a consequence " + token +
171 | " must be expressed as " + serializedNumber);
172 | }
173 | JSONValue strictNum = new JSONValue(type, token);
174 | strictNum.preSet = true;
175 | return strictNum;
176 | }
177 | } else if (BOOLEAN_PATTERN.matcher(token).matches()) {
178 | type = JSONTypes.BOOLEAN;
179 | } else if (token.equals("null")) {
180 | type = JSONTypes.NULL;
181 | } else {
182 | throw new IOException("Unrecognized or malformed JSON token: " + token);
183 | }
184 | return new JSONValue(type, token);
185 | }
186 |
187 | JSONValue scanQuotedString() throws IOException {
188 | StringBuilder result = new StringBuilder();
189 | while (true) {
190 | char c = nextChar();
191 | if (c < ' ') {
192 | throw new IOException(c == '\n' ?
193 | "Unterminated string literal" : "Unescaped control character: 0x" + Integer.toString(c, 16));
194 | }
195 | if (c == DOUBLE_QUOTE) {
196 | break;
197 | }
198 | if (c == BACK_SLASH) {
199 | switch (c = nextChar()) {
200 | case '"':
201 | case '\\':
202 | case '/':
203 | break;
204 |
205 | case 'b':
206 | c = '\b';
207 | break;
208 |
209 | case 'f':
210 | c = '\f';
211 | break;
212 |
213 | case 'n':
214 | c = '\n';
215 | break;
216 |
217 | case 'r':
218 | c = '\r';
219 | break;
220 |
221 | case 't':
222 | c = '\t';
223 | break;
224 |
225 | case 'u':
226 | c = 0;
227 | for (int i = 0; i < 4; i++) {
228 | c = (char) ((c << 4) + getHexChar());
229 | }
230 | break;
231 |
232 | default:
233 | throw new IOException("Unsupported escape:" + c);
234 | }
235 | }
236 | result.append(c);
237 | }
238 | return new JSONValue(JSONTypes.STRING, result.toString());
239 | }
240 |
241 | char getHexChar() throws IOException {
242 | char c = nextChar();
243 | switch (c) {
244 | case '0':
245 | case '1':
246 | case '2':
247 | case '3':
248 | case '4':
249 | case '5':
250 | case '6':
251 | case '7':
252 | case '8':
253 | case '9':
254 | return (char) (c - '0');
255 |
256 | case 'a':
257 | case 'b':
258 | case 'c':
259 | case 'd':
260 | case 'e':
261 | case 'f':
262 | return (char) (c - 'a' + 10);
263 |
264 | case 'A':
265 | case 'B':
266 | case 'C':
267 | case 'D':
268 | case 'E':
269 | case 'F':
270 | return (char) (c - 'A' + 10);
271 | }
272 | throw new IOException("Bad hex in \\u escape: " + c);
273 | }
274 |
275 | char testNextNonWhiteSpaceChar() throws IOException {
276 | int save = index;
277 | char c = scan();
278 | index = save;
279 | return c;
280 | }
281 |
282 | void scanFor(char expected) throws IOException {
283 | char c = scan();
284 | if (c != expected) {
285 | throw new IOException("Expected '" + expected + "' but got '" + c + "'");
286 | }
287 | }
288 |
289 | char nextChar() throws IOException {
290 | if (index < maxLength) {
291 | return jsonData.charAt(index++);
292 | }
293 | throw new IOException("Unexpected EOF reached");
294 | }
295 |
296 | boolean isWhiteSpace(char c) {
297 | return c == 0x20 || c == 0x0A || c == 0x0D || c == 0x09;
298 | }
299 |
300 | char scan() throws IOException {
301 | while (true) {
302 | char c = nextChar();
303 | if (isWhiteSpace(c)) {
304 | continue;
305 | }
306 | return c;
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/app/src/main/java/org/webpki/util/ArrayUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2006-2021 WebPKI.org (http://webpki.org).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package org.webpki.util;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.io.File;
22 | import java.io.FileInputStream;
23 | import java.io.FileOutputStream;
24 | import java.io.ByteArrayOutputStream;
25 |
26 | /**
27 | * Useful functions for arrays.
28 | */
29 | public class ArrayUtil {
30 | private ArrayUtil() {
31 | } // No instantiation please
32 |
33 | /*
34 | * Find first difference between two byte arrays.
35 | * return First index i < length for which
36 | * a[aOffset + i] != b[bOffset + i],
37 | * or -1 if no differences are found.
38 | */
39 | public static int firstDiff(byte[] a, int aOffset, byte[] b, int bOffset, int length) {
40 | if (a == null || b == null) {
41 | throw new IllegalArgumentException("Cannot compare null arrays.");
42 | }
43 | if (aOffset + length > a.length ||
44 | bOffset + length > b.length) {
45 | throw new ArrayIndexOutOfBoundsException("Range to compare not contained in array.");
46 | }
47 | if (a == b && aOffset == bOffset) {
48 | return -1;
49 | }
50 | for (int i = 0; i < length; i++) {
51 | if (a[aOffset + i] != b[bOffset + i]) {
52 | System.out.println(i + ": " + Integer.toHexString(0xFF & a[aOffset + i]) + " " + Integer.toHexString(0xFF & b[bOffset + i]));
53 | return i;
54 | }
55 | }
56 | return -1;
57 | }
58 |
59 | /*
60 | * Find first difference between two byte arrays.
61 | */
62 | public static int firstDiff(byte[] a, byte[] b, int offset, int length) {
63 | return firstDiff(a, offset, b, offset, length);
64 | }
65 |
66 | /*
67 | * Find first difference between two byte arrays.
68 | */
69 | public static int firstDiff(byte[] a, byte[] b) {
70 | return firstDiff(a, b, 0, Math.min(a.length, b.length));
71 | }
72 |
73 | /*
74 | * Compare two byte arrays.
75 | * return true if contents are the same and both are non-null.
76 | */
77 | public static boolean compare(byte[] a, int aOffset, byte[] b, int bOffset, int length) {
78 | if (a == null || b == null ||
79 | aOffset + length > a.length ||
80 | bOffset + length > b.length) {
81 | return false;
82 | }
83 | if (a == b && aOffset == bOffset) {
84 | return true;
85 | }
86 | for (int i = 0; i < length; i++) {
87 | if (a[aOffset + i] != b[bOffset + i]) {
88 | return false;
89 | }
90 | }
91 | return true;
92 | }
93 |
94 | /*
95 | * Compare two byte arrays.
96 | * return true if a.length - aOffset == b.length - bOffset and all values in a starting at aOffset match the values in b starting at bOffset.
97 | */
98 | public static boolean compare(byte[] a, int aOffset, byte[] b, int bOffset) {
99 | return a.length - aOffset == b.length - bOffset &&
100 | compare(a, aOffset, b, bOffset, a.length - aOffset);
101 | }
102 |
103 | /*
104 | * Compare two byte arrays.
105 | */
106 | public static boolean compare(byte[] a, byte[] b, int offset, int length) {
107 | return compare(a, offset, b, offset, length);
108 | }
109 |
110 | /*
111 | * Compare two byte arrays.
112 | */
113 | public static boolean compare(byte[] a, byte[] b, int offset) {
114 | return compare(a, offset, b, offset);
115 | }
116 |
117 | /*
118 | * Compare two byte arrays.
119 | */
120 | public static boolean compare(byte[] a, byte[] b) {
121 | return a == b || compare(a, b, 0);
122 | }
123 |
124 | /*
125 | * Returns the index of the (first) minimal element of an int array.
126 | * return The index of the (first) minimal element.
127 | * throws IllegalArgumentException If a is empty.
128 | */
129 | public static int indexOfMin(int[] a) {
130 | if (a.length == 0) {
131 | throw new IllegalArgumentException("Empty array.");
132 | } else {
133 | int r = 0;
134 | for (int i = 1; i < a.length; i++) {
135 | if (a[i] < a[r]) {
136 | r = i;
137 | }
138 | }
139 | return r;
140 | }
141 | }
142 |
143 | /*
144 | * Returns the index of the (first) maximal element of an int array.
145 | * return The index of the (first) maximal element.
146 | * throws IllegalArgumentException If a is empty.
147 | */
148 | public static int indexOfMax(int[] a) {
149 | if (a.length == 0) {
150 | throw new IllegalArgumentException("Empty array.");
151 | } else {
152 | int r = 0;
153 | for (int i = 1; i < a.length; i++) {
154 | if (a[i] > a[r]) {
155 | r = i;
156 | }
157 | }
158 | return r;
159 | }
160 | }
161 |
162 | /*
163 | * Returns the minimal element of an int array.
164 | * throws IllegalArgumentException If a is empty.
165 | */
166 | public static int min(int[] a) {
167 | return a[indexOfMin(a)];
168 | }
169 |
170 | /*
171 | * Returns the maximal element of an int array.
172 | * throws IllegalArgumentException If a is empty.
173 | */
174 | public static int max(int[] a) {
175 | return a[indexOfMax(a)];
176 | }
177 |
178 | /*
179 | * Convert byte array to hex string with formating options.
180 | * @param maxLength Max number of bytes to convert (-1 for full string).
181 | * @param separator Character to insert between converted bytes ('\0' for none).
182 | * @param uppercase Use uppercase letters (for hex symbols).
183 | * @return String
184 | */
185 | public static String toHexString(byte[] value, int startOffset,
186 | int maxLength,
187 | boolean uppercase, char separator) {
188 | if (maxLength == -1 || startOffset + maxLength > value.length) {
189 | maxLength = value.length - startOffset;
190 | }
191 | StringBuilder r = new StringBuilder(maxLength * (separator == -1 ? 2 : 3));
192 | for (int i = 0; i < maxLength; i++) {
193 | if (i > 0 && separator != 0) {
194 | r.append(separator);
195 | }
196 | String t = Integer.toHexString(value[i + startOffset] & 0xFF);
197 | if (t.length() == 1) {
198 | t = "0" + t;
199 | }
200 | if (uppercase) {
201 | t = t.toUpperCase();
202 | }
203 | r.append(t);
204 | }
205 | return r.toString();
206 | }
207 |
208 | /*
209 | * Convert byte array to hex string.
210 | * Calls the more configurable {@link #toHexString(byte[], int, int, boolean, char)
211 | * toHexString} with uppercase set to true and space as separator character.
212 | */
213 | public static String toHexString(byte[] value, int startOffset,
214 | int maxLength) {
215 | return toHexString(value, startOffset, maxLength, true, ' ');
216 | }
217 |
218 | /*
219 | * Convert byte array to hex string.
220 | * Calls the more configurable {@link #toHexString(byte[], int, int, boolean, char)
221 | * toHexString} with no offset, no max length, uppercase set to true and
222 | * space as separator character.
223 | */
224 | public static String toHexString(byte[] value) {
225 | return toHexString(value, 0, -1, true, ' ');
226 | }
227 |
228 | /*
229 | * Convert int to hex string.
230 | * Calls {@link #toHexString(byte[], int, int, boolean, char) toHexString} with an array
231 | * containing the byte representation of the integer (little-endian).
232 | */
233 | public static String toHexString(int value, char byteSeparator) {
234 | return toHexString(new byte[]{(byte) ((value >> 24) & 0xFF),
235 | (byte) ((value >> 16) & 0xFF),
236 | (byte) ((value >> 8) & 0xFF),
237 | (byte) (value & 0xFF)}, 0, -1, true, byteSeparator);
238 | }
239 |
240 | public static byte[] readFile(File file) throws IOException {
241 | return getByteArrayFromInputStream(new FileInputStream(file));
242 | }
243 |
244 | public static byte[] readFile(String filename) throws IOException {
245 | return readFile(new File(filename));
246 | }
247 |
248 | public static void writeFile(File file, byte[] b) throws IOException {
249 | FileOutputStream fos = new FileOutputStream(file);
250 | fos.write(b);
251 | fos.close();
252 | }
253 |
254 | public static void writeFile(String filename, byte[] b) throws IOException {
255 | writeFile(new File(filename), b);
256 | }
257 |
258 | /*
259 | * Copies a part or the whole of the supplied byte array to a new array of the indicated size.
260 | * If newSize is larger than b.length the remaining bytes of the
261 | * result array will be set to zero.
262 | */
263 | public static byte[] copy(byte[] b, int newSize) {
264 | byte[] r = new byte[newSize];
265 | System.arraycopy(b, 0, r, 0, Math.min(b.length, r.length));
266 | return r;
267 | }
268 |
269 | /*
270 | * Makes a copy of the supplied byte array.
271 | */
272 | public static byte[] copy(byte[] b) {
273 | return copy(b, b.length);
274 | }
275 |
276 | /*
277 | * return the added byte array.
278 | */
279 | public static byte[] add(byte[] a, byte[] b) {
280 | byte[] r = new byte[a.length + b.length];
281 | System.arraycopy(a, 0, r, 0, a.length);
282 | System.arraycopy(b, 0, r, a.length, b.length);
283 | return r;
284 | }
285 |
286 | /*
287 | * Makes a reversed copy of the supplied byte array.
288 | */
289 | public static byte[] reverse(byte[] b) {
290 | final int l = b.length;
291 | byte[] r = new byte[l];
292 | for (int i = 0; i < l; i++) {
293 | r[i] = b[l - i - 1];
294 | }
295 | return r;
296 | }
297 |
298 | public static byte[] getByteArrayFromInputStream(InputStream is) throws IOException {
299 | ByteArrayOutputStream baos = new ByteArrayOutputStream(10000);
300 | byte[] buffer = new byte[10000];
301 | int bytes;
302 | while ((bytes = is.read(buffer)) != -1) {
303 | baos.write(buffer, 0, bytes);
304 | }
305 | is.close();
306 | return baos.toByteArray();
307 | }
308 | }
309 |
--------------------------------------------------------------------------------