OPTiM Store との連携には、Tenant Contract Setup をはじめとして様々な場面で以下の4つの署名付き JSON データを扱うための仕様群が登場します。
各仕様の RFC およびその翻訳版、各言語ごとのライブラリ等は Tenant Contract API 冒頭で紹介しているのでそちらをご覧ください。
このチュートリアルでは、主要ライブラリを用いて署名付き JSON データの生成・署名検証のサンプルコードをご紹介します。
Ruby では、上記4つの仕様をすべてサポートしているライブラリとして json-jwt を利用します。このライブラリの詳しい使い方は JSON::JWT Wiki にも記載されています。
Contract API で contract_jwk を生成する際には、RSA 公開鍵を JWK 形式にエンコードします。
public_key = OpenSSL::PKey::RSA.new <<-PEM
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApG82+BzmQMLPTYD4G+ZD
:
(中略)
:
LQIDAQAB
-----END PUBLIC KEY-----
PEM
# NOTE:
# json-jwt gem では JSON::JWK インスタンスを生成すると
# 自動的に JWK Thumbprint 値が kid にセットされます。
public_jwk = JSON::JWK.new public_key
p public_jwk
# => {"kty": "RSA", "e": "AQAB", "n": "pG82-..(中略)..wgGLQ", "kid": "2wKYU..(中略)..yaFzY"}
json-jwt gem では JSON::JWK インスタンスを利用する限り自動的に JWK Thumbprint を kid
として利用するようになっているため、明示的に JWK Thumbprint 値を計算する必要はありません。
OpenID Connect Client Registration API へのリクエスト時には、software_statement 生成のため署名付き JWT を生成します。
private_key = OpenSSL::PKey::RSA.new <<-PEM
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEApG82+BzmQMLPTYD4G+ZD0nMOFfoxzJUmZaaZCId4eXem6mwX
:
(中略)
:
oMLvCmb7301JqWQRpYJ6WcyPchuMJ0x70WMqENXoBFYi1Sfnt864uXo=
-----END RSA PRIVATE KEY-----
PEM
private_jwk = JSON::JWK.new private_key
payload = {
foo: 'bar',
hoge: 'fuga'
}
# NOTE:
# json-jwt gem では JSON::JWK インスタンスで署名を行うと
# 自動的に当該 JWK の JWK Thumbprint 値が JWT Header の kid にセットされます。
jwt = JSON::JWT.new payload
signed_jwt = jwt.sign private_jwk
puts singed_jwt.to_s # => "eyJ0eXAi..(中略).._pBTA"
Contract API Request や SCIM Client Registration Request の検証時には、署名付き JWT の署名検証を行います。
jwt = JSON::JWT.decode jwt_string, :skip_verification
jwt.kid # => "2wKYU..(中略)..yaFzY"
public_jwk_components = {
kty: 'RSA',
e: 'AQAB',
n: 'pG82-..(中略)..wgGLQ',
kid: '2wKYU..(中略)..yaFzY'
}
public_jwk = JSON::JWK.new public_jwk_components
jwt = JSON::JWT.decode jwt_string, public_key
Ruby では、上記4つの仕様をすべてサポートしているライブラリとして jose-php を利用します。このライブラリの詳しい使い方は jose-php README にも記載されています。
Contract API で contract_jwk を生成する際には、RSA 公開鍵を JWK 形式にエンコードします。
<?php
$public_key = new phpseclib\Crypt\RSA();
$public_key->loadKey('-----BEGIN RSA PUBLIC KEY-----\n...');
$public_jwk = JOSE_JWK::encode($public_key);
jose-php では、JOSE_JWK インスタンスを生成した時点で kid
に JWK Thumbprint 値が自動的にセットされます。
<?php
$public_jwk = JOSE_JWK::encode($public_key);
echo $public_jwk->components['kid'];
OpenID Connect Client Registration API へのリクエスト時には、software_statement 生成のため署名付き JWT を生成します。
署名生成時には、署名生成を行う前に 鍵の kid
値を JWT Header に指定する必要があります。(署名生成後に JWT Header に変更を加えると、署名検証時にエラーになります)
なお jose-php は JWK 形式の RSA Private Key による署名生成をサポートしていないため、署名用の秘密鍵として JOSE_JWK インスタンス自体を指定することができません。
<?php
$private_key = new phpseclib\Crypt\RSA();
$private_key->loadKey('-----BEGIN RSA PRIVATE KEY-----\n...');
$private_jwk = JOSE_JWK::encode($private_key);
$payload = array(
foo: 'bar',
hoge: 'fuga'
);
$jwt = new JOSE_JWT($payload);
$jwt->header['kid'] = $private_jwk->components['kid']; # NOTE: JOSE_JWT::sign() を呼ぶ前に!
$jwt->sign($private_key, 'RS256'); # NOTE: $private_jwk ではなく $private_key を指定
echo $jwt->toString(); # => "eyJ0eXAi..(中略).._pBTA"
Contract API Request や SCIM Client Registration Request の検証時には、署名付き JWT の署名検証を行います。
<?php
$jwt_string = 'eyJ0eXAi..(中略).._pBTA';
$jwt = JOSE_JWT::decode($jwt_string);
echo $jwt->header['kid']; # => "2wKYU..(中略)..yaFzY";
$public_jwk_components = array(
kty: 'RSA',
e: 'AQAB',
n: 'pG82-..(中略)..wgGLQ',
kid: '2wKYU..(中略)..yaFzY'
);
$public_jwk = new JOSE_JWK($public_jwk_components);
$jwt->verify($public_jwk, 'RS256');
Java では、上記4つの仕様をすべてサポートしているライブラリとして nimbus-jose-jwt を利用します。このライブラリの詳しい使い方は Nimbus JOSE + JWT examples にも記載されています。
Contract API で contract_jwk を生成する際には、RSA 公開鍵を JWK 形式にエンコードします。
import java.security.*;
import java.security.interfaces.*;
import com.nimbusds.jose.jwk.RSAKey;
public class JWK {
public static void main(String[] args) throws Exception {
// NOTE: 実際には DB などから適宜鍵を取得するようにしてください。
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(2048);
KeyPair kp = keyGenerator.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic();
RSAKey jwk = new RSAKey.Builder(publicKey)
.keyIDFromThumbprint()
.build();
System.out.println(jwk.toJSONObject().toJSONString());
}
}
nimbus-jose-jwt では RSAKey.Builder.keyIDFromThumbprint()
を呼び出すことでに JWK Thumbprint を当該 JWK の kid
として利用するようになっています。
OpenID Connect Client Registration API へのリクエスト時には、software_statement 生成のため署名付き JWT を生成します。
import java.security.*;
import java.security.interfaces.*;
import java.util.*;
import javax.crypto.*;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;
public class JWTSign {
public static void main(String[] args) throws Exception {
// NOTE: 実際には DB などから適宜鍵を取得するようにしてください。
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(2048);
KeyPair kp = keyGenerator.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey)kp.getPrivate();
JWTClaimsSet payload = new JWTClaimsSet.Builder()
.claim("foo", "bar")
.claim("hoge", "fuga")
.build();
RSAKey jwk = new RSAKey.Builder(publicKey)
.keyIDFromThumbprint()
.build();
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(jwk.getKeyID())
.type(JOSEObjectType.JWT)
.build();
JWSSigner signer = new RSASSASigner(privateKey);
SignedJWT signedJWT = new SignedJWT(header, payload);
signedJWT.sign(signer);
System.out.println(signedJWT.serialize());
}
}
Contract API Request や SCIM Client Registration Request の検証時には、署名付き JWT の署名検証を行います。
import java.security.*;
import java.security.interfaces.*;
import java.util.*;
import javax.crypto.*;
import net.minidev.json.JSONObject;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;
public class JWTVerify {
public static void main(String[] args) throws Exception {
String jwkString = "{\"kty\":\"RSA\",\"e\":\"AQAB\",...}";
JSONObject pubicJwkComponents = (JSONObject) JSONSerializer.toJSON(jwkString);
RSAKey rsaKey = RSAKey.parse(pubicJwkComponents);
RSAPublicKey publicKey = (RSAPublicKey) rsaKey.toPublicKey();
String signedJWTString = "eyJ0eXAi..(中略).._pBTA";
SignedJWT signedJWT = SignedJWT.parse(signedJWTString);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
if (signedJWT.verify(verifier)) {
System.out.println("Verified!");
System.out.println(signedJWT.getJWTClaimsSet().toJSONObject().toJSONString());
} else {
System.out.println("Verification Failed!");
}
}
}
Java では、上記4つの仕様をすべてサポートしているライブラリとして jsrsasign を利用します。このライブラリの詳しい使い方は jsrsasign wiki にも記載されています。
Contract API で contract_jwk を生成する際には、RSA 公開鍵を JWK 形式にエンコードします。
var pem = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9...(後略)";
var pubKey = KEYUTIL.getKey(pem);
var jwk = {
kty: pubKey.type,
e: hextob64u(pubKey.e.toString(16)),
n: hextob64u(pubKey.n.toString(16))
}
jwk.kid = KJUR.jws.JWS.getJWKthumbprint(jwk);
console.info(jwk);
上記 JWK 生成のサンプルにある通り、KJUR.jws.JWS.getJWKthumbprint(jwk)
を利用することで JWK Thumbprint が得られます。
penID Connect Client Registration API へのリクエスト時には、software_statement 生成のため署名付き JWT を生成します。
var pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQE...(後略)"
var prvKey = KEYUTIL.getKey(pem);
var jwk = {
kty: prvKey.type,
e: hextob64u(prvKey.e.toString(16)),
n: hextob64u(prvKey.n.toString(16))
}
var header = {
alg: 'RS256',
typ: 'JWT',
kid: KJUR.jws.JWS.getJWKthumbprint(jwk)
};
var payload = {
foo: 'bar',
hoge: 'fuga'
};
var jwtString = KJUR.jws.JWS.sign("RS256", header, payload, prvKey);
console.info(jwtString);
Contract API Request や SCIM Client Registration Request の検証時には、署名付き JWT の署名検証を行います。
var jwtString = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV...(後略)";
var public_jwk_component = {
kty: 'RSA',
e: 'AQAB',
n: 'pG82-..(中略)..wgGLQ',
kid: '2wKYU..(中略)..yaFzY'
};
var pubKey = KEYUTIL.getKey(pem);
var isValid = KJUR.jws.JWS.verify(jwtString, pubKey, 'RS256');
if (isValid) {
var verifiedJWT = KJUR.jws.JWS.parse(jwtString)
console.info(verifiedJWT.headerObj);
console.info(verifiedJWT.payloadObj);
} else {
console.info('JWT signature invalid.');
}