package irma import ( "encoding/json" "path/filepath" "testing" "time" "github.com/privacybydesign/gabi/big" "github.com/privacybydesign/irmago/internal/fs" "github.com/privacybydesign/irmago/internal/test" "github.com/stretchr/testify/require" ) func parseConfiguration(t *testing.T) *Configuration { conf, err := NewConfiguration("testdata/irma_configuration", "") require.NoError(t, err) require.NoError(t, conf.ParseFolder()) return conf } // A convenience function for initializing big integers from known correct (10 // base) strings. Use with care, errors are ignored. func s2big(s string) (r *big.Int) { r, _ = new(big.Int).SetString(s, 10) return } func TestConfigurationAutocopy(t *testing.T) { test.CreateTestStorage(t) defer test.ClearTestStorage(t) path := filepath.Join("testdata", "storage", "test", "irma_configuration") require.NoError(t, fs.CopyDirectory(filepath.Join("testdata", "irma_configuration"), path)) conf, err := NewConfiguration(path, filepath.Join("testdata", "irma_configuration_updated")) require.NoError(t, err) require.NoError(t, conf.ParseFolder()) credid := NewCredentialTypeIdentifier("irma-demo.RU.studentCard") attrid := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute") require.True(t, conf.CredentialTypes[credid].ContainsAttribute(attrid)) } func TestParseInvalidIrmaConfiguration(t *testing.T) { // The description.xml of the scheme manager under this folder has been edited // to invalidate the scheme manager signature conf, err := NewConfiguration("testdata/irma_configuration_invalid", "") require.NoError(t, err) // Parsing it should return a SchemeManagerError err = conf.ParseFolder() require.Error(t, err) smerr, ok := err.(*SchemeManagerError) require.True(t, ok) require.Equal(t, SchemeManagerStatusInvalidSignature, smerr.Status) // The manager should still be in conf.SchemeManagers, but also in DisabledSchemeManagers require.Contains(t, conf.SchemeManagers, smerr.Manager) require.Contains(t, conf.DisabledSchemeManagers, smerr.Manager) require.Equal(t, SchemeManagerStatusInvalidSignature, conf.SchemeManagers[smerr.Manager].Status) require.Equal(t, false, conf.SchemeManagers[smerr.Manager].Valid) } func TestRetryHTTPRequest(t *testing.T) { test.StartBadHttpServer(3, 1*time.Second, "42") defer test.StopBadHttpServer() transport := NewHTTPTransport("http://localhost:48682") transport.client.HTTPClient.Timeout = 500 * time.Millisecond bts, err := transport.GetBytes("") require.NoError(t, err) require.Equal(t, "42\n", string(bts)) } func TestInvalidIrmaConfigurationRestoreFromRemote(t *testing.T) { test.StartSchemeManagerHttpServer() defer test.StopSchemeManagerHttpServer() test.CreateTestStorage(t) defer test.ClearTestStorage(t) conf, err := NewConfiguration("testdata/storage/test/irma_configuration", "testdata/irma_configuration_invalid") require.NoError(t, err) err = conf.ParseOrRestoreFolder() require.NoError(t, err) require.Empty(t, conf.DisabledSchemeManagers) require.Contains(t, conf.SchemeManagers, NewSchemeManagerIdentifier("irma-demo")) require.Contains(t, conf.CredentialTypes, NewCredentialTypeIdentifier("irma-demo.RU.studentCard")) } func TestInvalidIrmaConfigurationRestoreFromAssets(t *testing.T) { test.CreateTestStorage(t) defer test.ClearTestStorage(t) conf, err := NewConfiguration("testdata/storage/test/irma_configuration", "testdata/irma_configuration_invalid") require.NoError(t, err) // Fails: no remote and the version in the assets is broken err = conf.ParseOrRestoreFolder() require.Error(t, err) require.NotEmpty(t, conf.DisabledSchemeManagers) // Try again from correct assets conf.assets = "testdata/irma_configuration" err = conf.ParseOrRestoreFolder() require.NoError(t, err) require.Empty(t, conf.DisabledSchemeManagers) require.Contains(t, conf.SchemeManagers, NewSchemeManagerIdentifier("irma-demo")) require.Contains(t, conf.CredentialTypes, NewCredentialTypeIdentifier("irma-demo.RU.studentCard")) } func TestParseIrmaConfiguration(t *testing.T) { conf := parseConfiguration(t) require.Contains(t, conf.SchemeManagers, NewSchemeManagerIdentifier("irma-demo")) require.Contains(t, conf.SchemeManagers, NewSchemeManagerIdentifier("test")) pk, err := conf.PublicKey(NewIssuerIdentifier("irma-demo.RU"), 0) require.NoError(t, err) require.NotNil(t, pk) require.NotNil(t, pk.N, "irma-demo.RU public key has no modulus") require.Equal(t, "Irma Demo", conf.SchemeManagers[NewSchemeManagerIdentifier("irma-demo")].Name["en"], "irma-demo scheme manager has unexpected name") require.Equal(t, "Radboud University Nijmegen", conf.Issuers[NewIssuerIdentifier("irma-demo.RU")].Name["en"], "irma-demo.RU issuer has unexpected name") require.Equal(t, "Student Card", conf.CredentialTypes[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"], "irma-demo.RU.studentCard has unexpected name") require.Equal(t, "studentID", conf.CredentialTypes[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].AttributeTypes[2].ID, "irma-demo.RU.studentCard.studentID has unexpected name") // Hash algorithm pseudocode: // Base64(SHA256("irma-demo.RU.studentCard")[0:16]) //require.Contains(t, conf.reverseHashes, "1stqlPad5edpfS1Na1U+DA==", // "irma-demo.RU.studentCard had improper hash") //require.Contains(t, conf.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==", // "irma-demo.MijnOverheid.root had improper hash") } func TestAttributeDisjunctionMarshaling(t *testing.T) { conf := parseConfiguration(t) disjunction := AttributeDisjunction{} var _ json.Unmarshaler = &disjunction var _ json.Marshaler = &disjunction id := NewAttributeTypeIdentifier("MijnOverheid.ageLower.over18") attrsjson := ` { "label": "Over 18", "attributes": { "MijnOverheid.ageLower.over18": "yes", "Thalia.age.over18": "Yes" } }` require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction)) require.True(t, disjunction.HasValues()) require.Contains(t, disjunction.Attributes, id) require.Contains(t, disjunction.Values, id) require.Equal(t, *disjunction.Values[id], "yes") disjunction = AttributeDisjunction{} attrsjson = ` { "label": "Over 18", "attributes": [ "MijnOverheid.ageLower.over18", "Thalia.age.over18" ] }` require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction)) require.False(t, disjunction.HasValues()) require.Contains(t, disjunction.Attributes, id) require.True(t, disjunction.MatchesConfig(conf)) require.False(t, disjunction.satisfied()) index := 0 disjunction.selected = &disjunction.Attributes[0] disjunction.index = &index require.True(t, disjunction.satisfied()) } func TestMetadataAttribute(t *testing.T) { metadata := NewMetadataAttribute(0x02) if metadata.Version() != 0x02 { t.Errorf("Unexpected metadata version: %d", metadata.Version()) } expiry := metadata.SigningDate().Unix() + int64(metadata.ValidityDuration()*ExpiryFactor) if !time.Unix(expiry, 0).Equal(metadata.Expiry()) { t.Errorf("Invalid signing date") } if metadata.KeyCounter() != 0 { t.Errorf("Unexpected key counter") } } func TestMetadataCompatibility(t *testing.T) { conf, err := NewConfiguration("testdata/irma_configuration", "") require.NoError(t, err) require.NoError(t, conf.ParseFolder()) // An actual metadata attribute of an IRMA credential extracted from the IRMA app attr := MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"), conf) require.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil") require.Equal(t, NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), attr.CredentialType().Identifier(), "Metadata credential type was not irma-demo.RU.studentCard", ) require.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version") require.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date") require.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date") require.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter") } func TestTimestamp(t *testing.T) { mytime := Timestamp(time.Unix(1500000000, 0)) timestruct := struct{ Time *Timestamp }{Time: &mytime} bytes, err := json.Marshal(timestruct) require.NoError(t, err) timestruct = struct{ Time *Timestamp }{} require.NoError(t, json.Unmarshal(bytes, ×truct)) require.Equal(t, time.Time(*timestruct.Time).Unix(), int64(1500000000)) } func TestServiceProvider(t *testing.T) { var spjwt ServiceProviderJwt var spjson = `{ "sprequest": { "validity": 60, "timeout": 60, "request": { "content": [ { "label": "ID", "attributes": ["irma-demo.RU.studentCard.studentID"] } ] } } }` require.NoError(t, json.Unmarshal([]byte(spjson), &spjwt)) require.NotEmpty(t, spjwt.Request.Request.Content) require.NotEmpty(t, spjwt.Request.Request.Content[0]) require.NotEmpty(t, spjwt.Request.Request.Content[0].Attributes) require.Equal(t, spjwt.Request.Request.Content[0].Attributes[0].Name(), "studentID") require.NotNil(t, spjwt.Request.Request.Content.Find(NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))) } func TestVerifyValidSig(t *testing.T) { conf := parseConfiguration(t) irmaSignedMessageJson := "{\"signature\":[{\"c\":\"pliyrSE7wXcDcKXuBtZW5bnucvBSXpILIRvnNBgx7hQ=\",\"A\":\"D/8wLPq9860bpXZ5c+VYyoPJ+Z8CWDZNQ0jXvst8qnPRdivy/GQIfJHjVnpOPlHbguphb/7JVbfcV3bZeybA3bCF/4UesjRUZlMf/iJ/QgKHbt41ogN1PPT5z7qBJpkxuNTIkHxaUPoDvhouHmuC9pNj4afRUyLJerxKPkpdBw0=\",\"e_response\":\"YOrKTrMSs4/QOUtPkT0YaYNEmW7Cs+cu624zr2xrHodyL88ub6yaXB7MGHAcQ1+iXsGN8jkfxB/0\",\"v_response\":\"AYSa1p8ISs//MsocJjODwWuPB/z6+iKHHi+sTToRs0eJ2X1gwmWoA5QB0aHjRkWye3/+2rtosfUzI77FlPQVnrbMERwcuYM/fx3fpNCpjm2qcs3AOJRcSRxcNFMe1+4ECsmJhByMDutS1KXAAKiNvnhEXx9f0JrQGwQFtpSFPh8dOuvEKUZHAUALr4FcHCa2HL9nDRiqy2KAOxE0nAANAcMaBo/ed+WZeHtv4CTB7egyYs27cklVbwlBzmRrbjNZk57ICd0jVd6SZ2Ir93r/aPejkyhQ03xh9RVVyhOn4bkbjKIBzEybXTJAXgNmvd6F8Ds00srBZVWlo7Z23JZ7\",\"a_responses\":{\"0\":\"QHTznWWrECRNNmUNcy0yGu2L6qsZU6qkvaII8QB8QjbUxpwHzSeJWkzrn/Kk1KIowfoqB1DKGaFLATvuBl+bCoJjea+2VfK9Ns8=\",\"2\":\"H57Y9CTXJ5MAVo+aFfNSbmRMFQpraBIZVOXiRxCD/P7Aw4fW8r9P5l9pO9DTUeExaqFzsLyF5i5EridVWxlP2Wv0zbH8ku9Sg9w=\",\"3\":\"joggAmOhqM4QsKdoLHAfaslzXqJswS7MwZ/5+AKYdkMaHQ45biMdZU/6R+B7bjvsumg2f6KyTyg0G+BI+wVdJOjh3kGezdANB7Y=\",\"5\":\"5YP4A82WWeqc33e5Zg/Q8lqQQ1amLE8mOxMwCXb3N4J0UJRfV9lUFvbH1Q3Yb3YHAZpzGvhN/pBacwqktMkP4L71PnMldqA+nqA=\"},\"a_disclosed\":{\"1\":\"AgAJuwB+AALWy2qU9p3l52l9LU1rVT4M\",\"4\":\"NDU2\"}}],\"nonce\":\"Kg==\",\"context\":\"BTk=\",\"message\":\"I owe you everything\",\"timestamp\":{\"Time\":1527196489,\"ServerUrl\":\"https://metrics.privacybydesign.foundation/atum\",\"Sig\":{\"Alg\":\"ed25519\",\"Data\":\"ZV1qkvDrFK14QrUSC66xTNr9HitCOV4vwfGX0bh3iwY7qyHCi9rIOE97KY8CZifU5oLgVhFWy5E+ALR+gEpACw==\",\"PublicKey\":\"e/nMAJF7nwrvNZRpuJljNpRx+CsT7caaXyn9OX683R8=\"}}}" irmaSignedMessage := &SignedMessage{} json.Unmarshal([]byte(irmaSignedMessageJson), irmaSignedMessage) request := "{\"nonce\": \"Kg==\", \"context\": \"BTk=\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}" sigRequestJSON := []byte(request) sigRequest := &SignatureRequest{} json.Unmarshal(sigRequestJSON, sigRequest) // Test marshalling of 'string' fields: require.Equal(t, sigRequest.Nonce, big.NewInt(42)) require.Equal(t, sigRequest.Context, big.NewInt(1337)) // Test if we can verify it with the original request var err error attrs, status, err := irmaSignedMessage.Verify(conf, sigRequest) require.NoError(t, err) require.Equal(t, status, ProofStatusValid) require.Len(t, attrs, 1) require.Equal(t, attrs[0].Status, AttributeProofStatusPresent) require.Equal(t, attrs[0].Value["en"], "456") // Test verify against unmatched request (i.e. different nonce, context or message) unmatched := "{\"nonce\": \"Kg==\", \"context\": \"BTk=\", \"message\":\"I owe you NOTHING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}" unmatchedSigRequestJSON := []byte(unmatched) unmatchedSigRequest := &SignatureRequest{} json.Unmarshal(unmatchedSigRequestJSON, unmatchedSigRequest) _, status, err = irmaSignedMessage.Verify(conf, unmatchedSigRequest) require.NoError(t, err) require.Equal(t, status, ProofStatusUnmatchedRequest) // Test if we can also verify it without using the original request attrs, status, err = irmaSignedMessage.Verify(conf, nil) require.NoError(t, err) require.Equal(t, status, ProofStatusValid) require.Len(t, attrs, 1) require.Equal(t, attrs[0].Value["en"], "456") } func TestVerifyInValidSig(t *testing.T) { conf := parseConfiguration(t) // Same json as valid case, but has modified c irmaSignedMessageJson := "{\"signature\":[{\"c\":\"blablaE7wXcDcKXuBtZW5bnucvBSXpILIRvnNBgx7hQ=\",\"A\":\"D/8wLPq9860bpXZ5c+VYyoPJ+Z8CWDZNQ0jXvst8qnPRdivy/GQIfJHjVnpOPlHbguphb/7JVbfcV3bZeybA3bCF/4UesjRUZlMf/iJ/QgKHbt41ogN1PPT5z7qBJpkxuNTIkHxaUPoDvhouHmuC9pNj4afRUyLJerxKPkpdBw0=\",\"e_response\":\"YOrKTrMSs4/QOUtPkT0YaYNEmW7Cs+cu624zr2xrHodyL88ub6yaXB7MGHAcQ1+iXsGN8jkfxB/0\",\"v_response\":\"AYSa1p8ISs//MsocJjODwWuPB/z6+iKHHi+sTToRs0eJ2X1gwmWoA5QB0aHjRkWye3/+2rtosfUzI77FlPQVnrbMERwcuYM/fx3fpNCpjm2qcs3AOJRcSRxcNFMe1+4ECsmJhByMDutS1KXAAKiNvnhEXx9f0JrQGwQFtpSFPh8dOuvEKUZHAUALr4FcHCa2HL9nDRiqy2KAOxE0nAANAcMaBo/ed+WZeHtv4CTB7egyYs27cklVbwlBzmRrbjNZk57ICd0jVd6SZ2Ir93r/aPejkyhQ03xh9RVVyhOn4bkbjKIBzEybXTJAXgNmvd6F8Ds00srBZVWlo7Z23JZ7\",\"a_responses\":{\"0\":\"QHTznWWrECRNNmUNcy0yGu2L6qsZU6qkvaII8QB8QjbUxpwHzSeJWkzrn/Kk1KIowfoqB1DKGaFLATvuBl+bCoJjea+2VfK9Ns8=\",\"2\":\"H57Y9CTXJ5MAVo+aFfNSbmRMFQpraBIZVOXiRxCD/P7Aw4fW8r9P5l9pO9DTUeExaqFzsLyF5i5EridVWxlP2Wv0zbH8ku9Sg9w=\",\"3\":\"joggAmOhqM4QsKdoLHAfaslzXqJswS7MwZ/5+AKYdkMaHQ45biMdZU/6R+B7bjvsumg2f6KyTyg0G+BI+wVdJOjh3kGezdANB7Y=\",\"5\":\"5YP4A82WWeqc33e5Zg/Q8lqQQ1amLE8mOxMwCXb3N4J0UJRfV9lUFvbH1Q3Yb3YHAZpzGvhN/pBacwqktMkP4L71PnMldqA+nqA=\"},\"a_disclosed\":{\"1\":\"AgAJuwB+AALWy2qU9p3l52l9LU1rVT4M\",\"4\":\"NDU2\"}}],\"nonce\":\"Kg==\",\"context\":\"BTk=\",\"message\":\"I owe you everything\",\"timestamp\":{\"Time\":1527196489,\"ServerUrl\":\"https://metrics.privacybydesign.foundation/atum\",\"Sig\":{\"Alg\":\"ed25519\",\"Data\":\"ZV1qkvDrFK14QrUSC66xTNr9HitCOV4vwfGX0bh3iwY7qyHCi9rIOE97KY8CZifU5oLgVhFWy5E+ALR+gEpACw==\",\"PublicKey\":\"e/nMAJF7nwrvNZRpuJljNpRx+CsT7caaXyn9OX683R8=\"}}}" irmaSignedMessage := &SignedMessage{} json.Unmarshal([]byte(irmaSignedMessageJson), irmaSignedMessage) request := "{\"nonce\": \"Kg==\", \"context\": \"BTk=\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}" sigRequestJSON := []byte(request) sigRequest := &SignatureRequest{} json.Unmarshal(sigRequestJSON, sigRequest) var err error _, status, err := irmaSignedMessage.Verify(conf, sigRequest) require.NoError(t, err) require.Equal(t, status, ProofStatusInvalid) _, status, err = irmaSignedMessage.Verify(conf, nil) require.NoError(t, err) require.Equal(t, status, ProofStatusInvalid) } func TestVerifyInValidNonce(t *testing.T) { conf := parseConfiguration(t) // Same json as valid case, but with modified nonce irmaSignedMessageJson := "{\"signature\":[{\"c\":\"pliyrSE7wXcDcKXuBtZW5bnucvBSXpILIRvnNBgx7hQ=\",\"A\":\"D/8wLPq9860bpXZ5c+VYyoPJ+Z8CWDZNQ0jXvst8qnPRdivy/GQIfJHjVnpOPlHbguphb/7JVbfcV3bZeybA3bCF/4UesjRUZlMf/iJ/QgKHbt41ogN1PPT5z7qBJpkxuNTIkHxaUPoDvhouHmuC9pNj4afRUyLJerxKPkpdBw0=\",\"e_response\":\"YOrKTrMSs4/QOUtPkT0YaYNEmW7Cs+cu624zr2xrHodyL88ub6yaXB7MGHAcQ1+iXsGN8jkfxB/0\",\"v_response\":\"AYSa1p8ISs//MsocJjODwWuPB/z6+iKHHi+sTToRs0eJ2X1gwmWoA5QB0aHjRkWye3/+2rtosfUzI77FlPQVnrbMERwcuYM/fx3fpNCpjm2qcs3AOJRcSRxcNFMe1+4ECsmJhByMDutS1KXAAKiNvnhEXx9f0JrQGwQFtpSFPh8dOuvEKUZHAUALr4FcHCa2HL9nDRiqy2KAOxE0nAANAcMaBo/ed+WZeHtv4CTB7egyYs27cklVbwlBzmRrbjNZk57ICd0jVd6SZ2Ir93r/aPejkyhQ03xh9RVVyhOn4bkbjKIBzEybXTJAXgNmvd6F8Ds00srBZVWlo7Z23JZ7\",\"a_responses\":{\"0\":\"QHTznWWrECRNNmUNcy0yGu2L6qsZU6qkvaII8QB8QjbUxpwHzSeJWkzrn/Kk1KIowfoqB1DKGaFLATvuBl+bCoJjea+2VfK9Ns8=\",\"2\":\"H57Y9CTXJ5MAVo+aFfNSbmRMFQpraBIZVOXiRxCD/P7Aw4fW8r9P5l9pO9DTUeExaqFzsLyF5i5EridVWxlP2Wv0zbH8ku9Sg9w=\",\"3\":\"joggAmOhqM4QsKdoLHAfaslzXqJswS7MwZ/5+AKYdkMaHQ45biMdZU/6R+B7bjvsumg2f6KyTyg0G+BI+wVdJOjh3kGezdANB7Y=\",\"5\":\"5YP4A82WWeqc33e5Zg/Q8lqQQ1amLE8mOxMwCXb3N4J0UJRfV9lUFvbH1Q3Yb3YHAZpzGvhN/pBacwqktMkP4L71PnMldqA+nqA=\"},\"a_disclosed\":{\"1\":\"AgAJuwB+AALWy2qU9p3l52l9LU1rVT4M\",\"4\":\"NDU2\"}}],\"nonce\":\"aa==\",\"context\":\"BTk=\",\"message\":\"I owe you everything\",\"timestamp\":{\"Time\":1527196489,\"ServerUrl\":\"https://metrics.privacybydesign.foundation/atum\",\"Sig\":{\"Alg\":\"ed25519\",\"Data\":\"ZV1qkvDrFK14QrUSC66xTNr9HitCOV4vwfGX0bh3iwY7qyHCi9rIOE97KY8CZifU5oLgVhFWy5E+ALR+gEpACw==\",\"PublicKey\":\"e/nMAJF7nwrvNZRpuJljNpRx+CsT7caaXyn9OX683R8=\"}}}" irmaSignedMessage := &SignedMessage{} json.Unmarshal([]byte(irmaSignedMessageJson), irmaSignedMessage) // Original request also has the same invalid nonce (otherwise we would get unmatched_request) request := "{\"nonce\": \"aa==\", \"context\": \"BTk=\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}" sigRequestJSON := []byte(request) sigRequest := &SignatureRequest{} json.Unmarshal(sigRequestJSON, sigRequest) var err error _, status, err := irmaSignedMessage.Verify(conf, sigRequest) require.NoError(t, err) require.Equal(t, status, ProofStatusInvalid) _, status, err = irmaSignedMessage.Verify(conf, nil) require.NoError(t, err) require.Equal(t, status, ProofStatusInvalid) } // Test attribute decoding with both old and new metadata versions func TestAttributeDecoding(t *testing.T) { expected := "male" newAttribute, _ := new(big.Int).SetString("3670202571", 10) newString := decodeAttribute(newAttribute, 3) require.Equal(t, *newString, expected) oldAttribute, _ := new(big.Int).SetString("1835101285", 10) oldString := decodeAttribute(oldAttribute, 2) require.Equal(t, *oldString, expected) }