├── settings.gradle ├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── raw │ │ │ ├── secret_key_id_txt │ │ │ ├── data2beencrypted_txt │ │ │ ├── secretkey_b64u │ │ │ ├── json_data │ │ │ ├── a128cbc_hs256_json │ │ │ ├── ecprivatekey_jwk │ │ │ ├── json_signature │ │ │ ├── ecdh_es3_json │ │ │ ├── ecdh_es2_json │ │ │ ├── rsa_oaep_json │ │ │ ├── ecdh_es_json │ │ │ ├── rsa_oaep_256_json │ │ │ ├── rsaprivatekey_jwk │ │ │ └── ec_certpath_json │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap │ │ │ └── ic_launcher.png │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── webpki │ │ ├── crypto │ │ ├── KeyTypes.java │ │ ├── SignatureAlgorithms.java │ │ ├── AlgorithmPreferences.java │ │ ├── CryptoRandom.java │ │ ├── HmacVerifierInterface.java │ │ ├── X509SignerInterface.java │ │ ├── X509VerifierInterface.java │ │ ├── AsymKeySignerInterface.java │ │ ├── CryptoAlgorithms.java │ │ ├── HmacSignerInterface.java │ │ ├── OkpSupport.java │ │ ├── CertificateUtil.java │ │ ├── KeyEncryptionAlgorithms.java │ │ ├── ContentEncryptionAlgorithms.java │ │ ├── HashAlgorithms.java │ │ ├── HmacAlgorithms.java │ │ ├── SignatureWrapper.java │ │ └── AsymSignatureAlgorithms.java │ │ ├── json │ │ ├── JSONSignatureTypes.java │ │ ├── JSONVerifier.java │ │ ├── JSONSymKeyEncrypter.java │ │ ├── JSONTypes.java │ │ ├── JSONValue.java │ │ ├── JSONAsymKeyEncrypter.java │ │ ├── JSONX509Verifier.java │ │ ├── JSONX509Encrypter.java │ │ ├── JSONDecoder.java │ │ ├── JSONOutputFormats.java │ │ ├── JSONAsymKeyVerifier.java │ │ ├── JSONEncoder.java │ │ ├── JSONHmacVerifier.java │ │ ├── JSONHmacSigner.java │ │ ├── JSONSigner.java │ │ ├── JSONObject.java │ │ ├── JSONAsymKeySigner.java │ │ ├── JSONX509Signer.java │ │ ├── JSONDecoderCache.java │ │ ├── JSONArrayReader.java │ │ ├── JSONArrayWriter.java │ │ ├── JSONEncrypter.java │ │ └── JSONParser.java │ │ ├── jose │ │ ├── JOSEKeyWords.java │ │ └── jws │ │ │ ├── JWSHmacSigner.java │ │ │ ├── JWSHmacValidator.java │ │ │ ├── JWSAsymSignatureValidator.java │ │ │ ├── JWSValidator.java │ │ │ ├── JWSAsymKeySigner.java │ │ │ ├── JWSSigner.java │ │ │ └── JWSDecoder.java │ │ ├── androidjsondemo │ │ └── RawReader.java │ │ └── util │ │ ├── Base64URL.java │ │ ├── ISODateTime.java │ │ └── ArrayUtil.java ├── proguard-rules.pro └── build.gradle ├── .gitattributes ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/raw/secret_key_id_txt: -------------------------------------------------------------------------------- 1 | a256bitkey -------------------------------------------------------------------------------- /app/src/main/res/raw/data2beencrypted_txt: -------------------------------------------------------------------------------- 1 | Hello encrypted world! -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable LF normalization for all files 2 | * -text 3 | -------------------------------------------------------------------------------- /app/src/main/res/raw/secretkey_b64u: -------------------------------------------------------------------------------- 1 | f92FGjudLa_F8NAAMOIrk0OQDNQu3klIVopKLuZVKRo -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .idea 4 | **/*.iml 5 | /build 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | JSON/JSF/JEF 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberphone/android-json/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberphone/android-json/master/app/src/main/res/mipmap/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/raw/json_data: -------------------------------------------------------------------------------- 1 | { 2 | "now": "2017-04-16T11:23:06Z", 3 | "escapeMe": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", 4 | "numbers": [1e+30,4.5,6] 5 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/a128cbc_hs256_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A128CBC-HS256", 3 | "keyId": "a256bitkey", 4 | "iv": "ggcbWwo1vAuIblGfcvF5Gw", 5 | "tag": "nl3RRpUVDrVva0IrBSvPgQ", 6 | "cipherText": "GI1ctn2kh25OHGM95C2JYN7YuVh1w1jn3B0KDmoQlEk" 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 15 05:45:02 CEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/raw/ecprivatekey_jwk: -------------------------------------------------------------------------------- 1 | { 2 | "kid": "example.com:p256", 3 | "kty": "EC", 4 | "crv": "P-256", 5 | "x": "censDzcMEkgiePz6DXB7cDuwFemshAFR90UNVQFCg8Q", 6 | "y": "xq8rze6ewG0-eVcSF72J77gKiD0IHnzpwHaU7t6nVeY", 7 | "d": "nEsftLbi5u9pI8B0-drEjIuJzQgZie3yeqUR3BwWDl4" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-json 2 | Test/Demo of Android JSON, JSF and JEF support. 3 | 4 | Uses the *source* library described on:
5 | https://github.com/cyberphone/openkeystore/blob/master/README.md#android-json-jsf-and-jef-support 6 | 7 | There is also a pre-built APK which can be downloaded to an Android device or emulator:
8 | https://github.com/cyberphone/android-json/releases/download/1.2/android-json.apk 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/raw/json_signature: -------------------------------------------------------------------------------- 1 | { 2 | "now": "2019-02-10T11:23:06Z", 3 | "name": "Joe", 4 | "id": 2200063, 5 | "signature": { 6 | "algorithm": "ES256", 7 | "publicKey": { 8 | "kty": "EC", 9 | "crv": "P-256", 10 | "x": "censDzcMEkgiePz6DXB7cDuwFemshAFR90UNVQFCg8Q", 11 | "y": "xq8rze6ewG0-eVcSF72J77gKiD0IHnzpwHaU7t6nVeY" 12 | }, 13 | "value": "DaLAAenX3yOC7ycVyfjIe3tLyrH0U04lPcnQ7ct72ixryZVHdAWQazgDlWhpIDnrgLC0Pq03AvgsCc4ROOCInQ" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/raw/ecdh_es3_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A128GCM", 3 | "keyEncryption": { 4 | "algorithm": "ECDH-ES+A128KW", 5 | "keyId": "example.com:p256", 6 | "ephemeralKey": { 7 | "kty": "EC", 8 | "crv": "P-256", 9 | "x": "auWhfGK8gCQo4ahhqvYh4BA-v2uT_q2-8OByGHtN-5c", 10 | "y": "LYh8KAza6Ci68rcHtna8x2WioAMP7RnJKPLC1LvGHEI" 11 | }, 12 | "encryptedKey": "dCwGRPKrJjDzqJGOAbWqF8hD2iAfaZRS" 13 | }, 14 | "iv": "QviJjryoA7cM--8Z", 15 | "tag": "6DNEXPSK881_9Xy4nHE_Rw", 16 | "cipherText": "w7u0-d2U3gMkaJW90RE7L3jSpQO4JA" 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/raw/ecdh_es2_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A128CBC-HS256", 3 | "keyEncryption": { 4 | "algorithm": "ECDH-ES+A256KW", 5 | "keyId": "example.com:p256", 6 | "ephemeralKey": { 7 | "kty": "EC", 8 | "crv": "P-256", 9 | "x": "9DayVky8C_llf9Yk3mJiLNb29LJE-WC_ZekSztv8dEM", 10 | "y": "ttT5ThAu_Y4Is-n2FRyusngvK83dhpU8FlbJ1FTcnlo" 11 | }, 12 | "encryptedKey": "8VQf5MDUWgodtaT8u3nWEsGbHE-eJwSTkDga4qjCrjSpDD2P5rXbLg" 13 | }, 14 | "iv": "8nwdD6pQKJmfBgrZ40aYYw", 15 | "tag": "Dl5ZG1BAXGLJnmxjyNA12Q", 16 | "cipherText": "LrWMa0nIbc8ekFt5CEgv6DPDuXpzONbiEqIoq0OpCxg" 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/raw/rsa_oaep_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A128GCM", 3 | "keyEncryption": { 4 | "algorithm": "RSA-OAEP", 5 | "keyId": "example.com:r2048", 6 | "encryptedKey": "dBymKN6faZt1s8Abc2M6IPiRvMqhI085vMPwUm0eQbU3sZswnsTBcPngbEE362WuoEQjsmGXpE_nZ_XUp__X251iyRyTuAm62U-eaea6hn-eswB_RmUht1vlsnvbdnfIDnivi6ZT_B7dnhd0HqV5bKGRVM9ITmQEkFSBzu10w0cdqfh_M99qK1jH8qV63ObStKgMjBdCc1htoXaqNol449rVYJc911O1-J5h2cj1x1kEh4AgvACpSf3jN3znEsZ8i2TUg5pjKwV-6hJ5mcFNTLNq4B14FZTzaHz9RGgPdsw55abbaMQl6cVUEZI1SsgwVTk9vIVMU_kLS1cAhDVO4Q" 7 | }, 8 | "iv": "LO-3eomo-AaiApte", 9 | "tag": "oVyW7uI7ilzG0rdUXt4x2A", 10 | "cipherText": "JUiGExaRNWMUJvLjIOnAnAncV4DdVA" 11 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/ecdh_es_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A256CBC-HS512", 3 | "keyEncryption": { 4 | "algorithm": "ECDH-ES", 5 | "publicKey": { 6 | "kty": "EC", 7 | "crv": "P-256", 8 | "x": "censDzcMEkgiePz6DXB7cDuwFemshAFR90UNVQFCg8Q", 9 | "y": "xq8rze6ewG0-eVcSF72J77gKiD0IHnzpwHaU7t6nVeY" 10 | }, 11 | "ephemeralKey": { 12 | "kty": "EC", 13 | "crv": "P-256", 14 | "x": "T6gyVpdmYc1g4O9MW1R6DwaOZGvabeZteLaRF2bFA5U", 15 | "y": "CyIJPxp490UnaiT0T23tHAvpKEwozeEbMyjW8o0h5DU" 16 | } 17 | }, 18 | "iv": "4MsYlgMgwilPZEdGLnliHw", 19 | "tag": "fDGRE5k3roaf0s-aLHWpdciRncw60k5DYHRCYESD4AE", 20 | "cipherText": "83zTIabe4L3AUMw4iXxrJ3uXOhYFlkjcBEOge4w0w-8" 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | android.useAndroidX=true 15 | android.enableJetifier=true 16 | 17 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/KeyTypes.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 | * Basic key types. 21 | */ 22 | public enum KeyTypes {RSA, EC, EDDSA, XEC, SYM}; 23 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/json/JSONSignatureTypes.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 | * Supported signature types 21 | */ 22 | public enum JSONSignatureTypes {X509_CERTIFICATE, ASYMMETRIC_KEY, SYMMETRIC_KEY} 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/SignatureAlgorithms.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 | * Common signature interface. 21 | * 22 | */ 23 | public interface SignatureAlgorithms extends CryptoAlgorithms { 24 | 25 | HashAlgorithms getDigestAlgorithm(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/AlgorithmPreferences.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 | * The crypto library supports SKS and JOSE algorithms. 21 | * Algorithms and EC curves like Brainpool which does not have a JOSE counterpart will throw exceptions in the "JOSE" mode. 22 | */ 23 | public enum AlgorithmPreferences {SKS, JOSE, JOSE_ACCEPT_PREFER} 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/jose/JOSEKeyWords.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; 18 | 19 | /** 20 | * Holds JOSE key words 21 | */ 22 | public interface JOSEKeyWords { 23 | 24 | String ALG_JSON = "alg"; 25 | String KID_JSON = "kid"; 26 | String JWK_JSON = "jwk"; 27 | String X5C_JSON = "x5c"; 28 | 29 | String EdDSA = "EdDSA"; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/raw/rsa_oaep_256_json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "A256GCM", 3 | "keyEncryption": { 4 | "algorithm": "RSA-OAEP-256", 5 | "publicKey": { 6 | "kty": "RSA", 7 | "n": "hFWEXArvaZEpSP5qNX7x4C4Hl28GJQTNvnDwkfqiWs63kXbdyPeS06bz6GnY3tfQ_093nGauWsimqKBmGAGMPtsV83Qxw1OIeO4ujbIIb9pema0qtVqs0MWlHxklZGFkYfAmbuEUFxYDeLDHe0bkkXbSlB7_t8pCSvc8HLgHjEQjYOlFRwjR0D-uLo-xgsCbpmCtYkB5lcT_zFgpRgY4zJNLSv7GZiz2S4Fc5ArGjd34lL47-L8bozuYjqNOv9sqX0Zgll5XaJ1ndvr7UqZu1xQFgm38reoM3IarBP_SkEFbt_v9iak602VO3k28fQhMaocP7JWR2YLT3kZM0-WTFw", 8 | "e": "AQAB" 9 | }, 10 | "encryptedKey": "aNUidiyzHJb6u_hWjpJLMBBfqCb6Hjf9uqQTjSTSS6LRiY9rGgiooImFPnXWqSlolaiapes7cx68V_HJuw84DasvsSOwD8Ho-yxRcceMSas6Jum7iwV-yc9zgZfPnCYCNssVNBfP1eFGL55fzST1caf3_oM_kyeA60UnOMMhGFYfhp9dBGtSENblflXCW7GOT19tDYqMAeWp5E7U3PbyemNk3RHYwoaSCkDLCB1m-BUu4Gc7rRC_cO7Eq522uaIbNhRGrVvwGn48V0cI0FxtA62itPhU-d-M_TwnbsTP-LDAYOKuRV9Oc0uvlQz4S95s0FsrJ-D-Z5N1pn6lI9oMhg" 11 | }, 12 | "iv": "bKcUqBM1zrUvkkf1", 13 | "tag": "yRvyoaakowmGiEkM_nv6Vw", 14 | "cipherText": "nZL_xhUoWotDFf8Mj4Y8BUpicKgzdQ" 15 | } 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/CryptoRandom.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.SecureRandom; 20 | 21 | public class CryptoRandom { 22 | 23 | private CryptoRandom() { } 24 | 25 | public static byte[] generateRandom(int length) { 26 | byte[] random = new byte[length]; 27 | new SecureRandom().nextBytes(random); 28 | return random; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/HmacVerifierInterface.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 | public interface HmacVerifierInterface { 25 | 26 | public boolean verifyData(byte[] data, byte[] digest, HmacAlgorithms algorithm, String keyId) 27 | throws IOException, GeneralSecurityException; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/json/JSONVerifier.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 | * Support class for signature verifiers. 25 | */ 26 | public abstract class JSONVerifier { 27 | 28 | JSONVerifier(JSONSignatureTypes signatureType) { 29 | this.signatureType = signatureType; 30 | } 31 | 32 | abstract void verify(JSONSignatureDecoder signatureDecoder) 33 | throws IOException, GeneralSecurityException; 34 | 35 | JSONSignatureTypes signatureType; 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/X509SignerInterface.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 | import java.security.cert.X509Certificate; 25 | 26 | /** 27 | * Common interface for X509 signature creation. 28 | * 29 | */ 30 | public interface X509SignerInterface extends AsymKeySignerInterface { 31 | 32 | /** 33 | * Get certificate path. 34 | * 35 | * @return Certificate path 36 | * @throws IOException 37 | * @throws GeneralSecurityException 38 | */ 39 | X509Certificate[] getCertificatePath() throws IOException, GeneralSecurityException; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/json/JSONSymKeyEncrypter.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 | /** 22 | * Initiator object for symmetric key encryptions. 23 | */ 24 | public class JSONSymKeyEncrypter extends JSONEncrypter { 25 | 26 | /** 27 | * Constructor for JCE based solutions. 28 | * @param contentEncryptionKey Symmetric key 29 | * @throws IOException 30 | */ 31 | public JSONSymKeyEncrypter(byte[] contentEncryptionKey) throws IOException { 32 | this.contentEncryptionKey = contentEncryptionKey; 33 | this.keyEncryptionAlgorithm = null; 34 | } 35 | 36 | @Override 37 | void writeKeyData(JSONObjectWriter wr) throws IOException { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 32 5 | defaultConfig { 6 | applicationId "org.webpki.androidjsondemo" 7 | minSdkVersion 24 8 | targetSdkVersion 32 9 | versionCode 6 10 | versionName "1.6" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation "androidx.webkit:webkit:1.1.0" 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | def appcompat_version = "1.1.0" 29 | 30 | implementation "androidx.appcompat:appcompat:$appcompat_version" 31 | // For loading and tinting drawables on older versions of the platform 32 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 34 | testImplementation 'junit:junit:4.12' 35 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/json/JSONTypes.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 | /** 22 | * Basic JSON types read by the parser. 23 | */ 24 | public enum JSONTypes { 25 | 26 | NULL (false), 27 | BOOLEAN (false), 28 | NUMBER (false), 29 | STRING (false), 30 | ARRAY (true), 31 | OBJECT (true); 32 | 33 | boolean complex; 34 | 35 | JSONTypes(boolean complex) { 36 | this.complex = complex; 37 | } 38 | 39 | static void compatibilityTest(JSONTypes expectedType, JSONValue value) throws IOException { 40 | if (expectedType != value.type) { 41 | throw new IOException("Incompatible types, expected: " + expectedType + " actual: " + value.type); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/crypto/X509VerifierInterface.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 java.security.cert.X509Certificate; 24 | 25 | /** 26 | * Common interface for X509 signature validation. 27 | * 28 | */ 29 | public interface X509VerifierInterface { 30 | 31 | /** 32 | * Verify certificate path. 33 | * 34 | * @param certificatePath The path to be validated 35 | * @return true if the path is verified and trusted 36 | * @throws IOException 37 | * @throws GeneralSecurityException 38 | */ 39 | boolean verifyCertificatePath(X509Certificate[] certificatePath) 40 | throws IOException, GeneralSecurityException; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/webpki/json/JSONValue.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 | /** 22 | * Local support class for holding individual values of JSON objects. 23 | */ 24 | class JSONValue { 25 | 26 | JSONTypes type; // Number, String, etc. 27 | 28 | Object value; // Holds either a String, JSONObject or ArrayList 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 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 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 | --------------------------------------------------------------------------------